Cleaned and added a base class for view models with background processing tha uses the new ThreadedAction class to do its background processing; added a view binding interface for WinForms that emulates the WPF design
git-svn-id: file:///srv/devel/repo-conversion/nuwi@41 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
		
							parent
							
								
									435935691a
								
							
						
					
					
						commit
						4ef5dd9430
					
				
					 6 changed files with 674 additions and 0 deletions
				
			
		|  | @ -55,6 +55,17 @@ | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> |     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||||
|  |     <Compile Include="Source\ViewModels\ThreadedViewModel.cs" /> | ||||||
|  |     <Compile Include="Source\ViewModels\ThreadedViewModel.Test.cs"> | ||||||
|  |       <DependentUpon>ThreadedViewModel.cs</DependentUpon> | ||||||
|  |     </Compile> | ||||||
|  |     <Compile Include="Source\Views\IView.cs" /> | ||||||
|  |     <Compile Include="Source\Views\ViewControl.cs"> | ||||||
|  |       <SubType>UserControl</SubType> | ||||||
|  |     </Compile> | ||||||
|  |     <Compile Include="Source\Views\ViewForm.cs"> | ||||||
|  |       <SubType>Form</SubType> | ||||||
|  |     </Compile> | ||||||
|     <EmbeddedResource Include="Source\ProgressReporter\ProgressReporterForm.resx"> |     <EmbeddedResource Include="Source\ProgressReporter\ProgressReporterForm.resx"> | ||||||
|       <DependentUpon>ProgressReporterForm.cs</DependentUpon> |       <DependentUpon>ProgressReporterForm.cs</DependentUpon> | ||||||
|       <SubType>Designer</SubType> |       <SubType>Designer</SubType> | ||||||
|  | @ -109,6 +120,7 @@ | ||||||
|       <Link>Foundation.snk</Link> |       <Link>Foundation.snk</Link> | ||||||
|     </None> |     </None> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |   <ItemGroup /> | ||||||
|   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||||
|   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.  |   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.  | ||||||
|        Other similar extension points exist, see Microsoft.Common.targets. |        Other similar extension points exist, see Microsoft.Common.targets. | ||||||
|  |  | ||||||
							
								
								
									
										208
									
								
								Source/ViewModels/ThreadedViewModel.Test.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								Source/ViewModels/ThreadedViewModel.Test.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,208 @@ | ||||||
|  | #if UNITTEST | ||||||
|  | 
 | ||||||
|  | using System; | ||||||
|  | using System.ComponentModel; | ||||||
|  | using System.Threading; | ||||||
|  | using NUnit.Framework; | ||||||
|  | 
 | ||||||
|  | namespace Nuclex.Windows.Forms.ViewModels { | ||||||
|  | 
 | ||||||
|  |   /// <summary>Unit test for the threaded view model base class</summary> | ||||||
|  |   [TestFixture] | ||||||
|  |   public class ThreadedViewModelTest { | ||||||
|  | 
 | ||||||
|  |     #region class DummyContext | ||||||
|  | 
 | ||||||
|  |     /// <summary>Synchronization context that does absolutely nothing</summary> | ||||||
|  |     private class DummyContext : ISynchronizeInvoke { | ||||||
|  | 
 | ||||||
|  |       #region class SimpleAsyncResult | ||||||
|  | 
 | ||||||
|  |       /// <summary>Barebones implementation of an asynchronous result</summary> | ||||||
|  |       private class SimpleAsyncResult : IAsyncResult { | ||||||
|  | 
 | ||||||
|  |         /// <summary>Ehether the asynchronous operation is complete</summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         ///   Always true because it completes synchronously | ||||||
|  |         /// </remarks> | ||||||
|  |         public bool IsCompleted { get { return true; } } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         ///   Wait handle that can be used to wait for the asynchronous operation | ||||||
|  |         /// </summary> | ||||||
|  |         public WaitHandle AsyncWaitHandle { | ||||||
|  |           get { throw new NotImplementedException("Not implemented"); } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary>Custom state that can be used to pass information around</summary> | ||||||
|  |         public object AsyncState { | ||||||
|  |           get { throw new NotImplementedException("Not implemented"); } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary>Whether the asynchronous operation completed synchronously</summary> | ||||||
|  |         public bool CompletedSynchronously { get { return true; } } | ||||||
|  | 
 | ||||||
|  |         /// <summary>The value returned from the asynchronous operation</summary> | ||||||
|  |         public object ReturnedValue; | ||||||
|  | 
 | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       #endregion // class SimpleAsyncResult | ||||||
|  | 
 | ||||||
|  |       /// <summary>Whether the calling thread needs to use Invoke()</summary> | ||||||
|  |       public bool InvokeRequired { | ||||||
|  |         get { return true; } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Schedules the specified method for execution in the target thread</summary> | ||||||
|  |       /// <param name="method">Method the target thread will execute when it is idle</param> | ||||||
|  |       /// <param name="arguments">Arguments that will be passed to the method</param> | ||||||
|  |       /// <returns> | ||||||
|  |       ///   An asynchronous result handle that can be used to check on the status of | ||||||
|  |       ///   the call and wait for its completion | ||||||
|  |       /// </returns> | ||||||
|  |       public IAsyncResult BeginInvoke(Delegate method, object[] arguments) { | ||||||
|  |         var asyncResult = new SimpleAsyncResult(); | ||||||
|  |         asyncResult.ReturnedValue = method.Method.Invoke(method.Target, arguments); | ||||||
|  |         return asyncResult; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Waits for the asychronous call to complete</summary> | ||||||
|  |       /// <param name="result"> | ||||||
|  |       ///   Asynchronous result handle returned by the <see cref="BeginInvoke" /> method | ||||||
|  |       /// </param> | ||||||
|  |       /// <returns>The original result returned by the asychronously called method</returns> | ||||||
|  |       public object EndInvoke(IAsyncResult result) { | ||||||
|  |         return ((SimpleAsyncResult)result).ReturnedValue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary> | ||||||
|  |       ///   Schedules the specified method for execution in the target thread and waits | ||||||
|  |       ///   for it to complete | ||||||
|  |       /// </summary> | ||||||
|  |       /// <param name="method">Method that will be executed by the target thread</param> | ||||||
|  |       /// <param name="arguments">Arguments that will be passed to the method</param> | ||||||
|  |       /// <returns>The result returned by the specified method</returns> | ||||||
|  |       public object Invoke(Delegate method, object[] arguments) { | ||||||
|  |         return method.Method.Invoke(method.Target, arguments); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #endregion // class DummyContext | ||||||
|  | 
 | ||||||
|  |     #region class TestViewModel | ||||||
|  | 
 | ||||||
|  |     /// <summary>View model used to unit test the threaded view model base class</summary> | ||||||
|  |     private class TestViewModel : ThreadedViewModel { | ||||||
|  | 
 | ||||||
|  |       /// <summary> | ||||||
|  |       ///   Initializes a new view model, letting the base class figure out the UI thread | ||||||
|  |       /// </summary> | ||||||
|  |       public TestViewModel() : base() { | ||||||
|  |         this.finishedGate = new ManualResetEvent(initialState: false); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary> | ||||||
|  |       ///   Initializes a new view model, using the specified context for the UI thread | ||||||
|  |       /// </summary> | ||||||
|  |       /// <param name="uiContext">Synchronization context of the UI thread</param> | ||||||
|  |       public TestViewModel(ISynchronizeInvoke uiContext) : base(uiContext) { | ||||||
|  |         this.finishedGate = new ManualResetEvent(initialState: false); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Immediately releases all resources owned by the instance</summary> | ||||||
|  |       public override void Dispose() { | ||||||
|  |         base.Dispose(); | ||||||
|  | 
 | ||||||
|  |         if(this.finishedGate != null) { | ||||||
|  |           this.finishedGate.Dispose(); | ||||||
|  |           this.finishedGate = null; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Waits until the first background operation is finished</summary> | ||||||
|  |       /// <returns> | ||||||
|  |       ///   True if the background operation is finished, false if it is ongoing | ||||||
|  |       /// </returns> | ||||||
|  |       public bool WaitUntilFinished() { | ||||||
|  |         return this.finishedGate.WaitOne(100); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Runs a background process that causes the specified error</summary> | ||||||
|  |       /// <param name="error">Error that will be caused in the background process</param> | ||||||
|  |       public void CauseErrorInBackgroundThread(Exception error) { | ||||||
|  |         RunInBackground(() => throw error); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Last error that was reported by the threaded view model</summary> | ||||||
|  |       public Exception ReportedError { | ||||||
|  |         get { return this.reportedError; } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Called when an error occurs in the background thread</summary> | ||||||
|  |       /// <param name="exception">Exception that was thrown in the background thread</param> | ||||||
|  |       protected override void ReportError(Exception exception) { | ||||||
|  |         this.reportedError = exception; | ||||||
|  |         this.finishedGate.Set(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Last error that was reported by the threaded view model</summary> | ||||||
|  |       private Exception reportedError; | ||||||
|  |       /// <summary>Triggered when the </summary> | ||||||
|  |       private ManualResetEvent finishedGate; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #endregion // class TestViewModel | ||||||
|  | 
 | ||||||
|  |     /// <summary>Verifies that the threaded view model has a default constructor</summary> | ||||||
|  |     [Test] | ||||||
|  |     public void HasDefaultConstructor() { | ||||||
|  |       using(var mainForm = new System.Windows.Forms.Form()) { | ||||||
|  |         mainForm.Show(); | ||||||
|  |         try { | ||||||
|  |           mainForm.Visible = false; | ||||||
|  |           using(new TestViewModel()) { } | ||||||
|  |         } | ||||||
|  |         finally { | ||||||
|  |           mainForm.Close(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     ///   Verifies that the threaded view model can be constructed with a custom UI context | ||||||
|  |     /// </summary> | ||||||
|  |     [Test] | ||||||
|  |     public void HasCustomSychronizationContextConstructor() { | ||||||
|  |       using(new TestViewModel(new DummyContext())) { } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Checks that a new view model starts out idle and not busy</summary> | ||||||
|  |     [Test] | ||||||
|  |     public void NewInstanceIsNotBusy() { | ||||||
|  |       using(var viewModel = new TestViewModel(new DummyContext())) { | ||||||
|  |         Assert.IsFalse(viewModel.IsBusy); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary> | ||||||
|  |     ///   Verifies that errors happening in the background processing threads are | ||||||
|  |     ///   reported to the main thread | ||||||
|  |     /// </summary> | ||||||
|  |     [Test] | ||||||
|  |     public void ErrorsInBackgroundThreadAreReported() { | ||||||
|  |       using(var viewModel = new TestViewModel(new DummyContext())) { | ||||||
|  |         var testError = new ArgumentException("Mooh"); | ||||||
|  |         viewModel.CauseErrorInBackgroundThread(testError); | ||||||
|  |         viewModel.WaitUntilFinished(); | ||||||
|  |         Assert.AreSame(testError, viewModel.ReportedError); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } // namespace Nuclex.Windows.Forms.ViewModels | ||||||
|  | 
 | ||||||
|  | #endif // UNITTEST | ||||||
							
								
								
									
										243
									
								
								Source/ViewModels/ThreadedViewModel.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								Source/ViewModels/ThreadedViewModel.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,243 @@ | ||||||
|  | #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 { | ||||||
|  | 
 | ||||||
|  |   /// <summary>View model that can execute tasks in a background thread</summary> | ||||||
|  |   public abstract class ThreadedViewModel : Observable, IDisposable { | ||||||
|  | 
 | ||||||
|  |     #region class ViewModelThreadRunner | ||||||
|  | 
 | ||||||
|  |     /// <summary>Thread runner for the threaded view model</summary> | ||||||
|  |     private class ViewModelThreadRunner : ThreadRunner { | ||||||
|  | 
 | ||||||
|  |       /// <summary>Initializes a new thread runner for the threaded view model</summary> | ||||||
|  |       public ViewModelThreadRunner(ThreadedViewModel viewModel) { | ||||||
|  |         this.viewModel = viewModel; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Reports an error</summary> | ||||||
|  |       /// <param name="exception">Error that will be reported</param> | ||||||
|  |       protected override void ReportError(Exception exception) { | ||||||
|  |         this.viewModel.reportErrorFromThread(exception); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>Called when the status of the busy flag changes</summary> | ||||||
|  |       protected override void BusyChanged() { | ||||||
|  |         this.viewModel.OnIsBusyChanged(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       /// <summary>View model the thread runner belongs to</summary> | ||||||
|  |       private ThreadedViewModel viewModel; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #endregion // class ViewModelThreadRunner | ||||||
|  | 
 | ||||||
|  |     /// <summary>Initializes a new view model for background processing</summary> | ||||||
|  |     /// <param name="uiContext"> | ||||||
|  |     ///   UI dispatcher that can be used to run callbacks in the UI thread | ||||||
|  |     /// </param> | ||||||
|  |     protected ThreadedViewModel(ISynchronizeInvoke uiContext = null) { | ||||||
|  |       if(uiContext == null) { | ||||||
|  |         this.uiContext = getMainWindow(); | ||||||
|  |       } else { | ||||||
|  |         this.uiContext = uiContext; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.reportErrorDelegate = new Action<Exception>(ReportError); | ||||||
|  | 
 | ||||||
|  |       this.threadRunner = new ViewModelThreadRunner(this); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Immediately releases all resources owned by the instance</summary> | ||||||
|  |     public virtual void Dispose() { | ||||||
|  |       if(this.threadRunner != null) { | ||||||
|  |         this.threadRunner.Dispose(); | ||||||
|  |         this.threadRunner = null; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Whether the view model is currently busy executing a task</summary> | ||||||
|  |     public bool IsBusy { | ||||||
|  |       get { return this.threadRunner.IsBusy; } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Reports an error to the user</summary> | ||||||
|  |     /// <param name="exception">Error that will be reported</param> | ||||||
|  |     /// <remarks> | ||||||
|  |     ///   <para> | ||||||
|  |     ///     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). | ||||||
|  |     ///   </para> | ||||||
|  |     ///   <para> | ||||||
|  |     ///     When <see cref="RunInBackground(System.Action)" /> 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. | ||||||
|  |     ///   </para> | ||||||
|  |     /// </remarks> | ||||||
|  |     protected abstract void ReportError(Exception exception); | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified operation in the background</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the background</param> | ||||||
|  |     protected void RunInBackground(Action action) { | ||||||
|  |       this.threadRunner.RunInBackground(action); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified operation in the background</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the background</param> | ||||||
|  |     protected void RunInBackground(CancellableAction action) { | ||||||
|  |       this.threadRunner.RunInBackground(action); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified operation in the background</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the background</param> | ||||||
|  |     /// <param name="parameter1">Parameter that will be passed to the action</param> | ||||||
|  |     protected void RunInBackground<P1>(Action<P1> action, P1 parameter1) { | ||||||
|  |       this.threadRunner.RunInBackground(action, parameter1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified operation in the background</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the background</param> | ||||||
|  |     /// <param name="parameter1">Parameter that will be passed to the action</param> | ||||||
|  |     protected void RunInBackground<P1>(CancellableAction<P1> action, P1 parameter1) { | ||||||
|  |       this.threadRunner.RunInBackground(action, parameter1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified operation in the background</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the background</param> | ||||||
|  |     /// <param name="parameter1">First parameter that will be passed to the action</param> | ||||||
|  |     /// <param name="parameter2">Second parameter that will be passed to the action</param> | ||||||
|  |     protected void RunInBackground<P1, P2>(Action<P1, P2> action, P1 parameter1, P2 parameter2) { | ||||||
|  |       this.threadRunner.RunInBackground(action, parameter1, parameter2); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified operation in the background</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the background</param> | ||||||
|  |     /// <param name="parameter1">First parameter that will be passed to the action</param> | ||||||
|  |     /// <param name="parameter2">Second parameter that will be passed to the action</param> | ||||||
|  |     protected void RunInBackground<P1, P2>( | ||||||
|  |       CancellableAction<P1, P2> action, P1 parameter1, P2 parameter2 | ||||||
|  |     ) { | ||||||
|  |       this.threadRunner.RunInBackground(action, parameter1, parameter2); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Cancels the currently running background operation</summary> | ||||||
|  |     protected void CancelBackgroundOperation() { | ||||||
|  |       this.threadRunner.CancelBackgroundOperation(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Cancels all queued and the currently running background operation</summary> | ||||||
|  |     protected void CancelAllBackgroundOperations() { | ||||||
|  |       this.threadRunner.CancelAllBackgroundOperations(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Whether the background operation has been cancelled</summary> | ||||||
|  |     //[Obsolete("Please use a method accepting a cancellation token instead of using this")] | ||||||
|  |     protected bool IsBackgroundOperationCancelled { | ||||||
|  |       get { return this.threadRunner.IsBackgroundOperationCancelled; } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Throws an exception if the background operation was cancelled</summary> | ||||||
|  |     //[Obsolete("Please use a method accepting a cancellation token instead of using this")] | ||||||
|  |     protected void ThrowIfBackgroundOperationCancelled() { | ||||||
|  |       this.threadRunner.ThrowIfBackgroundOperationCancelled(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified action in the UI thread</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the UI thread</param> | ||||||
|  |     protected void RunInUIThread(Action action) { | ||||||
|  |       this.uiContext.Invoke(action, EmptyObjectArray); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified action in the UI thread</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the UI thread</param> | ||||||
|  |     /// <param name="parameter1">Parameter that will be passed to the action</param> | ||||||
|  |     protected void RunInUIThread<P1>(Action<P1> action, P1 parameter1) { | ||||||
|  |       this.uiContext.Invoke(action, new object[1] { parameter1 }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Executes the specified action in the UI thread</summary> | ||||||
|  |     /// <param name="action">Action that will be executed in the UI thread</param> | ||||||
|  |     /// <param name="parameter1">First parameter that will be passed to the action</param> | ||||||
|  |     /// <param name="parameter2">Second parameter that will be passed to the action</param> | ||||||
|  |     protected void RunInUIThread<P1, P2>(Action<P1, P2> action, P1 parameter1, P2 parameter2) { | ||||||
|  |       this.uiContext.Invoke(action, new object[2] { parameter1, parameter2 }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Called when the thread runner's busy flag changes</summary> | ||||||
|  |     protected virtual void OnIsBusyChanged() { | ||||||
|  |       OnPropertyChanged(nameof(IsBusy)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Reports an error that occurred in the runner's background thread</summary> | ||||||
|  |     /// <param name="exception">Exception that the thread has encountered</param> | ||||||
|  |     private void reportErrorFromThread(Exception exception) { | ||||||
|  |       this.uiContext.Invoke(this.reportErrorDelegate, new object[1] { exception }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Finds the application's main window</summary> | ||||||
|  |     /// <returns>Main window of the application</returns> | ||||||
|  |     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; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>An array of zero objects</summary> | ||||||
|  |     private static readonly object[] EmptyObjectArray = new object[0]; | ||||||
|  | 
 | ||||||
|  |     /// <summary>UI dispatcher of the thread in which the view runs</summary> | ||||||
|  |     private ISynchronizeInvoke uiContext; | ||||||
|  |     /// <summary>Delegate for the ReportError() method</summary> | ||||||
|  |     private Action<Exception> reportErrorDelegate; | ||||||
|  |     /// <summary>Thread runner that manages the view model's thread</summary> | ||||||
|  |     private ViewModelThreadRunner threadRunner; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } // namespace Nuclex.Windows.Forms.ViewModels | ||||||
							
								
								
									
										37
									
								
								Source/Views/IView.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								Source/Views/IView.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | #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; | ||||||
|  | 
 | ||||||
|  | namespace Nuclex.Windows.Forms.Views { | ||||||
|  | 
 | ||||||
|  |   /// <summary>View with support for data binding</summary> | ||||||
|  |   public interface IView { | ||||||
|  | 
 | ||||||
|  |     /// <summary>Provides the data binding target for the view</summary> | ||||||
|  |     /// <remarks> | ||||||
|  |     ///   This property is identical to the same-named one in WPF, it provides | ||||||
|  |     ///   the view model to which the view should bind its controls. | ||||||
|  |     /// </remarks> | ||||||
|  |     object DataContext { get; set; } | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } // namespace Nuclex.Windows.Forms.Views | ||||||
							
								
								
									
										87
									
								
								Source/Views/ViewControl.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								Source/Views/ViewControl.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | ||||||
|  | #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; | ||||||
|  | 
 | ||||||
|  | namespace Nuclex.Windows.Forms.Views { | ||||||
|  | 
 | ||||||
|  |   /// <summary> | ||||||
|  |   ///   Base class for MVVM user controls that act as views connected to a view model | ||||||
|  |   /// </summary> | ||||||
|  |   public class ViewControl : UserControl, IView { | ||||||
|  | 
 | ||||||
|  |     /// <summary>Initializes a new view control</summary> | ||||||
|  |     public ViewControl() { | ||||||
|  |       this.onViewModelPropertyChangedDelegate = OnViewModelPropertyChanged; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 		/// <summary>Called when the control's data context is changed</summary> | ||||||
|  | 		/// <param name="sender">Control whose data context was changed</param> | ||||||
|  | 		/// <param name="oldDataContext">Data context that was previously used</param> | ||||||
|  | 		/// <param name="newDataContext">Data context that will be used from now on</param> | ||||||
|  | 		protected virtual void OnDataContextChanged( | ||||||
|  |       object sender, object oldDataContext, object newDataContext | ||||||
|  |     ) { | ||||||
|  |       var oldViewModel = oldDataContext as INotifyPropertyChanged; | ||||||
|  |       if(oldViewModel != null) { | ||||||
|  |         oldViewModel.PropertyChanged -= this.onViewModelPropertyChangedDelegate; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var newViewModel = newDataContext as INotifyPropertyChanged; | ||||||
|  |       if(newViewModel != null) { | ||||||
|  |         newViewModel.PropertyChanged += this.onViewModelPropertyChangedDelegate; | ||||||
|  |         InvalidateAllViewModelProperties(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Refreshes all properties from the view model</summary> | ||||||
|  |     protected void InvalidateAllViewModelProperties() { | ||||||
|  |       OnViewModelPropertyChanged(this.dataContext, PropertyChangedEventArgsHelper.Wildcard); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Called when a property of the view model is changed</summary> | ||||||
|  |     /// <param name="sender">View model in which a property was changed</param> | ||||||
|  |     /// <param name="arguments">Contains the name of the property that has changed</param> | ||||||
|  |     protected virtual void OnViewModelPropertyChanged( | ||||||
|  |       object sender, PropertyChangedEventArgs arguments | ||||||
|  |     ) { } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Provides the data binding target for the view</summary> | ||||||
|  |     public object DataContext { | ||||||
|  |       get { return this.dataContext; } | ||||||
|  |       set { | ||||||
|  |         if(value != this.dataContext) { | ||||||
|  |           this.dataContext = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Active data binding target, can be null</summary> | ||||||
|  |     private object dataContext; | ||||||
|  |     /// <summary>Delegate for the OnViewModelPropertyChanged() method</summary> | ||||||
|  |     private PropertyChangedEventHandler onViewModelPropertyChangedDelegate; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } // namespace Nuclex.Windows.Forms.Views | ||||||
							
								
								
									
										87
									
								
								Source/Views/ViewForm.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								Source/Views/ViewForm.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | ||||||
|  | #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; | ||||||
|  | 
 | ||||||
|  | namespace Nuclex.Windows.Forms.Views { | ||||||
|  | 
 | ||||||
|  |   /// <summary> | ||||||
|  |   ///   Base class for MVVM windows that act as views connected to a view model | ||||||
|  |   /// </summary> | ||||||
|  |   public class ViewForm : Form, IView { | ||||||
|  | 
 | ||||||
|  |     /// <summary>Initializes a new view control</summary> | ||||||
|  |     public ViewForm() { | ||||||
|  |       this.onViewModelPropertyChangedDelegate = OnViewModelPropertyChanged; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 		/// <summary>Called when the window's data context is changed</summary> | ||||||
|  | 		/// <param name="sender">Window whose data context was changed</param> | ||||||
|  | 		/// <param name="oldDataContext">Data context that was previously used</param> | ||||||
|  | 		/// <param name="newDataContext">Data context that will be used from now on</param> | ||||||
|  | 		protected virtual void OnDataContextChanged( | ||||||
|  |       object sender, object oldDataContext, object newDataContext | ||||||
|  |     ) { | ||||||
|  |       var oldViewModel = oldDataContext as INotifyPropertyChanged; | ||||||
|  |       if(oldViewModel != null) { | ||||||
|  |         oldViewModel.PropertyChanged -= this.onViewModelPropertyChangedDelegate; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var newViewModel = newDataContext as INotifyPropertyChanged; | ||||||
|  |       if(newViewModel != null) { | ||||||
|  |         newViewModel.PropertyChanged += this.onViewModelPropertyChangedDelegate; | ||||||
|  |         InvalidateAllViewModelProperties(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Refreshes all properties from the view model</summary> | ||||||
|  |     protected void InvalidateAllViewModelProperties() { | ||||||
|  |       OnViewModelPropertyChanged(this.dataContext, PropertyChangedEventArgsHelper.Wildcard); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Called when a property of the view model is changed</summary> | ||||||
|  |     /// <param name="sender">View model in which a property was changed</param> | ||||||
|  |     /// <param name="arguments">Contains the name of the property that has changed</param> | ||||||
|  |     protected virtual void OnViewModelPropertyChanged( | ||||||
|  |       object sender, PropertyChangedEventArgs arguments | ||||||
|  |     ) { } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Provides the data binding target for the view</summary> | ||||||
|  |     public object DataContext { | ||||||
|  |       get { return this.dataContext; } | ||||||
|  |       set { | ||||||
|  |         if(value != this.dataContext) { | ||||||
|  |           this.dataContext = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// <summary>Active data binding target, can be null</summary> | ||||||
|  |     private object dataContext; | ||||||
|  |     /// <summary>Delegate for the OnViewModelPropertyChanged() method</summary> | ||||||
|  |     private PropertyChangedEventHandler onViewModelPropertyChangedDelegate; | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } // namespace Nuclex.Windows.Forms.Views | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue