using System; using System.ComponentModel; using System.Threading; using System.Windows.Forms; namespace ArdupilotMega.Controls { /// /// Form that is shown to the user during a background operation /// /// /// Performs operation excplicitely on a threadpool thread due to /// Mono not playing nice with the BackgroundWorker /// public partial class ProgressReporterDialogue : Form { private Exception workerException; public ProgressWorkerEventArgs doWorkArgs; public delegate void DoWorkEventHandler(object sender, ProgressWorkerEventArgs e); // This is the event that will be raised on the BG thread public event DoWorkEventHandler DoWork; public ProgressReporterDialogue() { InitializeComponent(); doWorkArgs = new ProgressWorkerEventArgs(); this.btnClose.Visible = false; } /// /// Called at setup - will kick off the background process on a thread pool thread /// public void RunBackgroundOperationAsync() { ThreadPool.QueueUserWorkItem(RunBackgroundOperation); this.ShowDialog(); } private void RunBackgroundOperation(object o) { try { if (this.DoWork != null) this.DoWork(this, doWorkArgs); } catch(Exception e) { // The background operation thew an exception. // Examine the work args, if there is an error, then display that and the exception details // Otherwise display 'Unexpected error' and exception details ShowDoneWithError(e, doWorkArgs.ErrorMessage); return; } if (doWorkArgs.CancelRequested && doWorkArgs.CancelAcknowledged) { ShowDoneCancelled(); return; } if (!string.IsNullOrEmpty(doWorkArgs.ErrorMessage)) { ShowDoneWithError(null, doWorkArgs.ErrorMessage); return; } if (doWorkArgs.CancelRequested) { ShowDoneWithError(null, "Operation could not cancel"); return; } ShowDone(); } // Called as a possible last operation of the bg thread that was cancelled // - Hide progress bar // - Set label text private void ShowDoneCancelled() { this.Invoke((MethodInvoker)delegate { this.progressBar1.Visible = false; this.lblProgressMessage.Text = "Cancelled"; this.btnClose.Visible = true; }); } // Called as a possible last operation of the bg thread // - Set progress bar to 100% // - Wait a little bit to allow the Aero progress animatiom to catch up // - Signal that we can close private void ShowDone() { this.Invoke((MethodInvoker) delegate { this.progressBar1.Style = ProgressBarStyle.Continuous; this.progressBar1.Value = 100; this.btnCancel.Visible = false; this.btnClose.Visible = false; }); Thread.Sleep(1000); this.BeginInvoke((MethodInvoker) this.Close); } // Called as a possible last operation of the bg thread // There was an exception on the worker event, so: // - Show the error message supplied by the worker, or a default message // - Make visible the error icon // - Make the progress bar invisible to make room for: // - Add the exception details and stack trace in an expansion panel // - Change the Cancel button to 'Close', so that the user can look at the exception message a bit private void ShowDoneWithError(Exception exception, string doWorkArgs) { var errMessage = doWorkArgs ?? "There was an unexpected error"; if (this.InvokeRequired) { this.Invoke((MethodInvoker) delegate { this.Text = "Error"; this.lblProgressMessage.Left = 65; this.lblProgressMessage.Text = errMessage; this.imgWarning.Visible = true; this.progressBar1.Visible = false; this.btnCancel.Visible = false; this.btnClose.Visible = true; this.linkLabel1.Visible = exception != null; this.workerException = exception; }); } } private void btnCancel_Click(object sender, EventArgs e) { // User wants to cancel - // * Set the text of the Cancel button to 'Close' // * Set the cancel button to disabled, will enable it and let the user dismiss the dialogue // when the async operation is complete // * Set the status text to 'Cancelling...' // * Set the progress bar to marquee, we don't know how long the worker will take to cancel // * Signal the worker. this.btnCancel.Visible = false; this.lblProgressMessage.Text = "Cancelling..."; this.progressBar1.Style = ProgressBarStyle.Marquee; doWorkArgs.CancelRequested = true; } private void btn_Close_Click(object sender, EventArgs e) { // we have already cancelled, and this now a 'close' button this.Close(); } /// /// Called from the BG thread /// /// progress in %, -1 means inderteminate /// public void UpdateProgressAndStatus(int progress, string status) { // we don't let the worker update progress when a cancel has been // requested, unless the cancel has been acknowleged, so we know that // this progress update pertains to the cancellation cleanup if (doWorkArgs.CancelRequested && !doWorkArgs.CancelAcknowledged) return; if (this.InvokeRequired) { this.Invoke((MethodInvoker) delegate { lblProgressMessage.Text = status; if (progress == -1) { this.progressBar1.Style = ProgressBarStyle.Marquee; } else { this.progressBar1.Style = ProgressBarStyle.Continuous; this.progressBar1.Value = progress; } }); } } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { var message = this.workerException.Message + Environment.NewLine + Environment.NewLine + this.workerException.StackTrace; MessageBox.Show(message,"Exception Details",MessageBoxButtons.OK,MessageBoxIcon.Information); } } public class ProgressWorkerEventArgs : EventArgs { public string ErrorMessage; public volatile bool CancelRequested; public volatile bool CancelAcknowledged; } }