#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