#region Apache License 2.0 /* Nuclex .NET Framework Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #endregion // Apache License 2.0 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 = LateCheckedSynchronizer.GetMainWindow(); if(this.uiContext == null) { this.uiContext = new LateCheckedSynchronizer(updateUiContext); } } 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)); } // For the ThreadedAction class - there should be a better way! /// Thread runner that manages the view model's thread internal ThreadRunner ThreadRunner { get { return this.threadRunner; } } /// 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 }); } /// Sets the UI context that will be used by the threaded action /// The UI context the threaded action will use private void updateUiContext(ISynchronizeInvoke uiContext) { this.uiContext = uiContext; } /// 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