using System; using System.ComponentModel; using System.Threading; using System.Windows.Forms; using System.Reflection; using log4net; 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 static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private Exception workerException; public ProgressWorkerEventArgs doWorkArgs; internal object locker = new object(); 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) { log.Info("RunBackgroundOperation"); 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(100); } log.Info("Focus ctl"); this.Invoke((MethodInvoker)delegate { // if this windows isnt the current active windows, popups inherit the wrong parent. this.Focus(); Application.DoEvents(); }); try { log.Info("DoWork"); if (this.DoWork != null) this.DoWork(this, doWorkArgs); log.Info("DoWork Done"); } 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; lock (locker) { _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) { int pgv = -1; lock (locker) { pgv = _progress; lblProgressMessage.Text = _status; } if (pgv == -1) { this.progressBar1.Style = ProgressBarStyle.Marquee; } else { this.progressBar1.Style = ProgressBarStyle.Continuous; try { this.progressBar1.Value = pgv; } // Exception System.ArgumentOutOfRangeException: Value of '-12959800' is not valid for 'Value'. 'Value' should be between 'minimum' and 'maximum'. catch { } // clean fail. and ignore, chances are we will hit this again in the next 100 ms } } private void ProgressReporterDialogue_Load(object sender, EventArgs e) { this.Focus(); } } public class ProgressWorkerEventArgs : EventArgs { public string ErrorMessage; public volatile bool CancelRequested; public volatile bool CancelAcknowledged; } }