///<summary>Non-blocking call. Every type of progress window should eventually call this method which does the hard stuff for the calling method. ///Spawns a separate thread that will instantiate a FormProgressBase within the new thread's context by invoking the func passed in. ///The FormProgressBase that funcGetNewProgress returns should be a form that is instatiated within the func (in order to avoid cross-threading). ///The global static FormProgressCurS will get set to the newly instantiated progress so that the entire application knows progress is showing. ///Finally returning a close action for the calling method to invoke whenever long computations are finished. ///Two critical portions of the closing method are 1 - it closes progress gracefully and 2 - FormProgressCurS gets set to null.</summary> public static Action ShowProgressBase(Func <FormProgressBase> funcGetNewProgress, string threadName = "Thread_ODProgress_ShowProgressBase") { if (ODEnvironment.IsWindows7(false) || ODInitialize.IsRunningInUnitTest) { return(new Action(() => { //Do nothing. })); } FormProgressBase FormPB = null; ManualResetEvent manualReset = new ManualResetEvent(false); ODThread odThread = new ODThread(o => { //It is very important that this particular thread instantiates the form and not the calling method. //This is what allows the progress window to show and be interacted with without joining or invoking back to the parent thread. FormPB = funcGetNewProgress(); AddActiveProgressWindow(FormPB); //Let the entire application know that a progress window is showing. FormPB.Shown += (obj, eArg) => manualReset.Set(); FormPB.FormClosed += (obj, eArg) => RemoveActiveProgressWindow(FormPB); FormPB.ShowDialog(); //We cannot utilize the "owner" overload because that would cause a cross threaded exception. }); odThread.SetApartmentState(ApartmentState.STA); //This is required for ReportComplex due to the history UI elements. odThread.AddExceptionHandler(e => e.DoNothing()); //The progress window had an exception... Not worth crashing the program over this. odThread.Name = threadName; odThread.Start(); //Force the calling thread to wait for the progress window to actually show to the user before continuing. manualReset.WaitOne(); return(() => { FormPB.ForceClose = true; odThread.Join(Timeout.Infinite); }); }
private static void RemoveActiveProgressWindow(FormProgressBase formPB) { _lockProgressCur.EnterWriteLock(); try { _listActiveProgressForms.Remove(formPB); } finally { _lockProgressCur.ExitWriteLock(); } }
///<summary>Invokes one of the funcs passed in based on if there are any active progress windows showing and has focus. ///Will invoke funcShowProgress if a progress window is active. Otherwise; invokes funcShow. ///Recursively calls itself as needed if the active progress window was in the middle of closing when this method was invoked.</summary> ///<param name="funcShowOverProgress">The func that should execute if a progress window is currently showing to the user.</param> ///<param name="funcShow">The func that should execute if no progress window is currently showing to the user.</param> ///<returns>The dialog result from the func that ended up getting invoked.</returns> private static System.Windows.Forms.DialogResult ShowHelper(Func <FormProgressBase, System.Windows.Forms.DialogResult> funcShowOverProgress, Func <System.Windows.Forms.DialogResult> funcShow) { //Unit tests are not designed to display message boxes. //Throw an exception instead of displaying the message so that unit tests cannot get locked up. if (ODInitialize.IsRunningInUnitTest) { throw new ApplicationException("Message boxes are not allowed for unit tests."); } //Get the active form for the current application. This property will return null if another application has focus (not our application). //This is rare enough that it is acceptable to default the parent of the message box to the progress window (if one is present). //This may cause the MessageBox to show up behind a form that HAD focus with a progress window behind it (e.g. Registration Key Edit window). Form FormActive = Form.ActiveForm; //Get the "active" progress window if one is present. Utilize a shallow copy so race conditions don't affect us inadvertently. FormProgressBase FormPB = ODProgress.FormProgressActive; //So that the logic is easier to follow, check for the two scenarios that can cause an immediate kick out. if (FormPB == null || //The easiest scenario is when there is no active progress window. (FormActive != null && FormActive != FormPB)) //There is a progress window but there is another form owned by the application that has focus. { //There is no progress window showing or there is one showing but we know that a different window of our application has focus. return(funcShow()); //Show the message box like normal and don't override its Parent property with the progress window. } //There is a progress window present and it could be the active form for the application or another application has focus and we don't know. //It is rare enough for applications to leave progress windows open while showing dialogs or new forms to the user. //Default to forcing the active progress window to be the parent form of the new message box because that scenario is so rare. System.Windows.Forms.DialogResult dialogResult = System.Windows.Forms.DialogResult.Abort; try { FormPB.InvokeIfRequired(() => dialogResult = funcShowOverProgress(FormPB)); } catch (ObjectDisposedException ode) { //Explicitly catch object disposed exceptions due to rare race conditions. //The active progress window that was just showing could have been placed on the "invoke stack" for close and disposal prior to our invoke. //The active progress window would successfully close and dispose first and then FormPB would be a reference to a disposed window //which would cause .InvokeIfRequired() to throw an ObjectDisposedException. //No error should be thrown in this scenario and instead we should retry this method because the active progress window could be different. //E.g. there will be a different active progress window if multiple progress windows were showing at the same time //OR we we eventually get back to the main thread which will not require invoking over to a progress window at all. ode.DoNothing(); dialogResult = ShowHelper(funcShowOverProgress, funcShow); //Recursive call on purpose. } return(dialogResult); }