#region CPL License /* Nuclex Framework Copyright (C) 2002-2019 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.ComponentModel; using System.Windows.Forms; using Nuclex.Support; using Nuclex.Support.Threading; namespace Nuclex.Windows.Forms.ViewModels { /// View model that can execute tasks in a background thread public abstract class ThreadedViewModel : Observable, IDisposable { #region class ViewModelThreadRunner /// Thread runner for the threaded view model private class ViewModelThreadRunner : ThreadRunner { /// Initializes a new thread runner for the threaded view model public ViewModelThreadRunner(ThreadedViewModel viewModel) { this.viewModel = viewModel; } /// Reports an error /// Error that will be reported protected override void ReportError(Exception exception) { this.viewModel.reportErrorFromThread(exception); } /// Called when the status of the busy flag changes protected override void BusyChanged() { this.viewModel.OnIsBusyChanged(); } /// View model the thread runner belongs to private ThreadedViewModel viewModel; } #endregion // class ViewModelThreadRunner /// Initializes a new view model for background processing /// /// UI dispatcher that can be used to run callbacks in the UI thread /// protected ThreadedViewModel(ISynchronizeInvoke uiContext = null) { if(uiContext == null) { this.uiContext = getMainWindow(); } else { this.uiContext = uiContext; } this.reportErrorDelegate = new Action(ReportError); this.threadRunner = new ViewModelThreadRunner(this); } /// Immediately releases all resources owned by the instance public virtual void Dispose() { if(this.threadRunner != null) { this.threadRunner.Dispose(); this.threadRunner = null; } } /// Whether the view model is currently busy executing a task public bool IsBusy { get { return this.threadRunner.IsBusy; } } /// Reports an error to the user /// Error that will be reported /// /// /// You can use this method as a default handling method for your own error reporting /// (displaying the error to the user, logging it or whatever else is appropriate). /// /// /// When is used, this method will also /// be called in case an exception within the asynchronously running code goes unhandled. /// This choice was made because, in the context of UI code, you would wrap any /// operations that might fail in a try..catch pair anyway in order to inform /// the user instead of aborting the entire application. /// /// protected abstract void ReportError(Exception exception); /// Executes the specified operation in the background /// Action that will be executed in the background protected void RunInBackground(Action action) { this.threadRunner.RunInBackground(action); } /// Executes the specified operation in the background /// Action that will be executed in the background protected void RunInBackground(CancellableAction action) { this.threadRunner.RunInBackground(action); } /// Executes the specified operation in the background /// Action that will be executed in the background /// Parameter that will be passed to the action protected void RunInBackground(Action action, P1 parameter1) { this.threadRunner.RunInBackground(action, parameter1); } /// Executes the specified operation in the background /// Action that will be executed in the background /// Parameter that will be passed to the action protected void RunInBackground(CancellableAction action, P1 parameter1) { this.threadRunner.RunInBackground(action, parameter1); } /// Executes the specified operation in the background /// Action that will be executed in the background /// First parameter that will be passed to the action /// Second parameter that will be passed to the action protected void RunInBackground(Action action, P1 parameter1, P2 parameter2) { this.threadRunner.RunInBackground(action, parameter1, parameter2); } /// Executes the specified operation in the background /// Action that will be executed in the background /// First parameter that will be passed to the action /// Second parameter that will be passed to the action protected void RunInBackground( CancellableAction action, P1 parameter1, P2 parameter2 ) { this.threadRunner.RunInBackground(action, parameter1, parameter2); } /// Cancels the currently running background operation protected void CancelBackgroundOperation() { this.threadRunner.CancelBackgroundOperation(); } /// Cancels all queued and the currently running background operation protected void CancelAllBackgroundOperations() { this.threadRunner.CancelAllBackgroundOperations(); } /// Whether the background operation has been cancelled //[Obsolete("Please use a method accepting a cancellation token instead of using this")] protected bool IsBackgroundOperationCancelled { get { return this.threadRunner.IsBackgroundOperationCancelled; } } /// Throws an exception if the background operation was cancelled //[Obsolete("Please use a method accepting a cancellation token instead of using this")] protected void ThrowIfBackgroundOperationCancelled() { this.threadRunner.ThrowIfBackgroundOperationCancelled(); } /// Executes the specified action in the UI thread /// Action that will be executed in the UI thread protected void RunInUIThread(Action action) { this.uiContext.Invoke(action, EmptyObjectArray); } /// Executes the specified action in the UI thread /// Action that will be executed in the UI thread /// Parameter that will be passed to the action protected void RunInUIThread(Action action, P1 parameter1) { this.uiContext.Invoke(action, new object[1] { parameter1 }); } /// Executes the specified action in the UI thread /// Action that will be executed in the UI thread /// First parameter that will be passed to the action /// Second parameter that will be passed to the action protected void RunInUIThread(Action action, P1 parameter1, P2 parameter2) { this.uiContext.Invoke(action, new object[2] { parameter1, parameter2 }); } /// Called when the thread runner's busy flag changes protected virtual void OnIsBusyChanged() { OnPropertyChanged(nameof(IsBusy)); } /// Reports an error that occurred in the runner's background thread /// Exception that the thread has encountered private void reportErrorFromThread(Exception exception) { this.uiContext.Invoke(this.reportErrorDelegate, new object[1] { exception }); } /// Finds the application's main window /// Main window of the application private static Form getMainWindow() { IntPtr mainWindowHandle = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; // We can get two things: a list of all open windows and the handle of // the window that the process has registered as main window. Use the latter // to pick the correct window from the former. FormCollection openForms = Application.OpenForms; int openFormCount = openForms.Count; for(int index = 0; index < openFormCount; ++index) { if(openForms[index].IsHandleCreated) { if(openForms[index].Handle == mainWindowHandle) { return openForms[index]; } } } // No matching main window found: use the first one in good faith or fail. if(openFormCount > 0) { return openForms[0]; } else { return null; } } /// An array of zero objects private static readonly object[] EmptyObjectArray = new object[0]; /// UI dispatcher of the thread in which the view runs private ISynchronizeInvoke uiContext; /// Delegate for the ReportError() method private Action reportErrorDelegate; /// Thread runner that manages the view model's thread private ViewModelThreadRunner threadRunner; } } // namespace Nuclex.Windows.Forms.ViewModels