From 9aa64c4dac078e5748a9e33ea5d6ec16251d3bfe Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Wed, 6 Feb 2019 18:34:47 +0000 Subject: [PATCH] Added a placeholder auto binding system to automate binding of views to view models without writing so much boilerplate code git-svn-id: file:///srv/devel/repo-conversion/nuwi@44 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Windows.Forms (net-4.0).csproj | 2 + Source/AutoBinding/ConventionBinder.cs | 47 +++++++++++ Source/AutoBinding/IAutoBinder.cs | 28 +++++++ Source/WindowManager.cs | 107 ++++++++++++++++++++----- 4 files changed, 164 insertions(+), 20 deletions(-) create mode 100644 Source/AutoBinding/ConventionBinder.cs create mode 100644 Source/AutoBinding/IAutoBinder.cs diff --git a/Nuclex.Windows.Forms (net-4.0).csproj b/Nuclex.Windows.Forms (net-4.0).csproj index 2a4f824..69f8384 100644 --- a/Nuclex.Windows.Forms (net-4.0).csproj +++ b/Nuclex.Windows.Forms (net-4.0).csproj @@ -55,6 +55,8 @@ + + DialogViewModel.cs diff --git a/Source/AutoBinding/ConventionBinder.cs b/Source/AutoBinding/ConventionBinder.cs new file mode 100644 index 0000000..3b27b59 --- /dev/null +++ b/Source/AutoBinding/ConventionBinder.cs @@ -0,0 +1,47 @@ +using Nuclex.Windows.Forms.Views; +using System; +using System.Windows.Forms; + +namespace Nuclex.Windows.Forms.AutoBinding { + + /// + /// Binds a view to its model using a convention-over-configuration approach + /// + public class ConventionBinder : IAutoBinder { + + /// Binds the specified view to an explicitly selected view model + /// + /// Type of view model the view will be bound to + /// + /// View that will be bound to a view model + /// View model the view will be bound to + public void Bind(Control view, TViewModel viewModel) + where TViewModel : class { + bind(view, viewModel); + } + + /// + /// Binds the specified view to the view model specified in its DataContext + /// + /// View that will be bound + public void Bind(Control viewControl) { + IView viewControlAsView = viewControl as IView; + if(viewControlAsView == null) { + throw new InvalidOperationException( + "The specified view has no view model associated. Either assign your " + + "view model to the view's data context beforehand or use the overload " + + "of Bind() that allows you to explicitly specify the view model." + ); + } + + bind(viewControl, viewControlAsView.DataContext); + } + + /// Binds a view to a view model + /// View that will be bound + /// View model the view will be bound to + private void bind(Control view, object viewModel) { + } + + } +} // namespace Nuclex.Windows.Forms.AutoBinding diff --git a/Source/AutoBinding/IAutoBinder.cs b/Source/AutoBinding/IAutoBinder.cs new file mode 100644 index 0000000..e1b514d --- /dev/null +++ b/Source/AutoBinding/IAutoBinder.cs @@ -0,0 +1,28 @@ +using System; + +using System.Windows.Forms; +using Nuclex.Windows.Forms.Views; + +namespace Nuclex.Windows.Forms.AutoBinding { + + /// Binds views to their view models + public interface IAutoBinder { + + /// Binds the specified view to an explicitly selected view model + /// + /// Type of view model the view will be bound to + /// + /// View that will be bound to a view model + /// View model the view will be bound to + void Bind(Control view, TViewModel viewModel) + where TViewModel : class; + + /// + /// Binds the specified view to the view model specified in its DataContext + /// + /// View that will be bound + void Bind(Control view); + + } + +} // namespace Nuclex.Windows.Forms.AutoBinding diff --git a/Source/WindowManager.cs b/Source/WindowManager.cs index 72c12d4..903169e 100644 --- a/Source/WindowManager.cs +++ b/Source/WindowManager.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Windows.Forms; using Nuclex.Support; +using Nuclex.Windows.Forms.AutoBinding; using Nuclex.Windows.Forms.Views; namespace Nuclex.Windows.Forms { @@ -31,8 +32,56 @@ namespace Nuclex.Windows.Forms { /// Manages an application's windows and views public class WindowManager : Observable, IWindowManager { + #region class CancellableDisposer + + /// Disposes an object that potentially implements IDisposable + private struct CancellableDisposer : IDisposable { + + /// Initializes a new cancellable disposer + /// + /// Object that potentially implements IDisposable + /// + public CancellableDisposer(object potentiallyDisposable = null) { + this.potentiallyDisposable = potentiallyDisposable; + } + + /// + /// Disposes the assigned object if the disposer has not been cancelled + /// + public void Dispose() { + var disposable = this.potentiallyDisposable as IDisposable; + if(disposable != null) { + disposable.Dispose(); + } + } + + /// Cancels the call to Dispose(), keeping the object alive + public void Dismiss() { + this.potentiallyDisposable = null; + } + + /// Assigns a new potentially disposable object + /// + /// Potentially disposable object that the disposer will dispose + /// + public void Set(object potentiallyDisposable) { + this.potentiallyDisposable = potentiallyDisposable; + } + + /// Object that will be disposed unless the disposer is cancelled + private object potentiallyDisposable; + + } + + #endregion // class CancellableDisposer + /// Initializes a new window manager - public WindowManager() { + /// + /// View model binder that will be used to bind all created views to their models + /// + public WindowManager(IAutoBinder autoBinder = null) { + this.autoBinder = autoBinder; + this.rootWindowActivatedDelegate = rootWindowActivated; this.rootWindowClosedDelegate = rootWindowClosed; this.viewTypesForViewModels = new ConcurrentDictionary(); @@ -74,7 +123,7 @@ namespace Nuclex.Windows.Forms { // when we're done (but still allow the user to change his mind) if((viewModel == null) || disposeOnClose) { window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead? - //window.SetValue(DisposeViewModelOnCloseProperty, true); + //window.SetValue(DisposeViewModelOnCloseProperty, true); } window.Show(); @@ -107,7 +156,7 @@ namespace Nuclex.Windows.Forms { // when we're done (but still allow the user to change his mind) if((viewModel == null) || disposeOnClose) { window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead? - //window.SetValue(DisposeViewModelOnCloseProperty, true); + //window.SetValue(DisposeViewModelOnCloseProperty, true); } DialogResult result = window.ShowDialog(this.activeWindow); @@ -149,27 +198,42 @@ namespace Nuclex.Windows.Forms { ) where TViewModel : class { Type viewType = LocateViewForViewModel(typeof(TViewModel)); Control viewControl = (Control)CreateInstance(viewType); + using(var viewDisposer = new CancellableDisposer(viewControl)) { - bool createdViewModel = false; - try { - IView viewControlAsView = viewControl as IView; - if(viewControlAsView != null) { - if(viewModel != null) { + // Create a view model if none was provided, and in either case assign + // the view model to the view (provided it implements IView). + using(var viewModelDisposer = new CancellableDisposer()) { + IView viewControlAsView = viewControl as IView; + if(viewModel == null) { // No view model provided, create one + if(viewControlAsView == null) { // View doesn't implement IView + viewModel = (TViewModel)CreateInstance(typeof(TViewModel)); + viewModelDisposer.Set(viewModel); + } else if(viewControlAsView.DataContext == null) { // View has no view model + viewModel = (TViewModel)CreateInstance(typeof(TViewModel)); + viewModelDisposer.Set(viewModel); + viewControlAsView.DataContext = viewModel; + } else { // There's an existing view model + viewModel = viewControlAsView.DataContext as TViewModel; + if(viewModel == null) { // The existing view model is another type + viewModel = (TViewModel)CreateInstance(typeof(TViewModel)); + viewModelDisposer.Set(viewModel); + viewControlAsView.DataContext = viewModel; + } + } + } else if(viewControlAsView != null) { // Caller has provided a view model viewControlAsView.DataContext = viewModel; - } else if(viewControlAsView.DataContext == null) { - viewModel = (TViewModel)CreateInstance(typeof(TViewModel)); - viewControlAsView.DataContext = viewModel; - createdViewModel = true; } - } - } - catch(Exception) { - if(createdViewModel) { // If we created it, we kill it. - disposeIfDisposable(viewModel); - } - disposeIfDisposable(viewControl); - throw; + // If an auto binder was provided, automatically bind the view to the view model + if(this.autoBinder != null) { + this.autoBinder.Bind(viewControl, viewModel); + } + + viewModelDisposer.Dismiss(); // Everything went well, we keep the view model + } + + viewDisposer.Dismiss(); // Everything went well, we keep the view + } return viewControl; @@ -215,6 +279,7 @@ namespace Nuclex.Windows.Forms { ); } + // Still no view found? We give up! if(viewType == null) { throw new InvalidOperationException( string.Format("Could not locate view for view model '{0}'", viewModelType.Name) @@ -346,6 +411,8 @@ namespace Nuclex.Windows.Forms { private EventHandler rootWindowActivatedDelegate; /// Invoked when a root window has been closed private EventHandler rootWindowClosedDelegate; + /// View model binder that will be used on all created views + private IAutoBinder autoBinder; /// Caches the view types to use for a view model private ConcurrentDictionary viewTypesForViewModels;