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; internal int _progress = -1; internal string _status = ""; 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 { Thread.CurrentThread.Name = "ProgressReporterDialogue Background thread"; } catch { } // ok on windows - fails on mono // mono fix - ensure the dialog is running while (this.IsHandleCreated == false) System.Threading.Thread.Sleep(1); this.Invoke((MethodInvoker)delegate { // if this windows isnt the current active windows, popups inherit the wrong parent. this.Focus(); Application.DoEvents(); }); 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 timer1.Stop(); ShowDoneWithError(e, doWorkArgs.ErrorMessage); return; } // stop the timer timer1.Stop(); // run once more to do final message and progressbar this.Invoke((MethodInvoker)delegate { timer1_Tick(null, null); }); 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; _progress = progress; _status = status; } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { var message = this.workerException.Message + Environment.NewLine + Environment.NewLine + this.workerException.StackTrace; CustomMessageBox.Show(message,"Exception Details",MessageBoxButtons.OK,MessageBoxIcon.Information); } /// /// prevent using invokes on main update status call "UpdateProgressAndStatus", as this is slow on mono /// /// /// private void timer1_Tick(object sender, EventArgs e) { // this value is being currupted - not thread safe int pgv = _progress; lblProgressMessage.Text = _status; if (pgv == -1) { this.progressBar1.Style = ProgressBarStyle.Marquee; } else { this.progressBar1.Style = ProgressBarStyle.Continuous; try { this.progressBar1.Value = pgv; } catch { } // clean fail. and ignore, chances are we will hit this again in the next 100 ms } } } public class ProgressWorkerEventArgs : EventArgs { public string ErrorMessage; public volatile bool CancelRequested; public volatile bool CancelAcknowledged; } }