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
This commit is contained in:
Markus Ewald 2019-02-06 18:34:47 +00:00
parent 826f2eb763
commit 9aa64c4dac
4 changed files with 164 additions and 20 deletions

View File

@ -55,6 +55,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Source\AutoBinding\ConventionBinder.cs" />
<Compile Include="Source\AutoBinding\IAutoBinder.cs" />
<Compile Include="Source\ViewModels\DialogViewModel.cs" /> <Compile Include="Source\ViewModels\DialogViewModel.cs" />
<Compile Include="Source\ViewModels\DialogViewModel.Test.cs"> <Compile Include="Source\ViewModels\DialogViewModel.Test.cs">
<DependentUpon>DialogViewModel.cs</DependentUpon> <DependentUpon>DialogViewModel.cs</DependentUpon>

View File

@ -0,0 +1,47 @@
using Nuclex.Windows.Forms.Views;
using System;
using System.Windows.Forms;
namespace Nuclex.Windows.Forms.AutoBinding {
/// <summary>
/// Binds a view to its model using a convention-over-configuration approach
/// </summary>
public class ConventionBinder : IAutoBinder {
/// <summary>Binds the specified view to an explicitly selected view model</summary>
/// <typeparam name="TViewModel">
/// Type of view model the view will be bound to
/// </typeparam>
/// <param name="view">View that will be bound to a view model</param>
/// <param name="viewModel">View model the view will be bound to</param>
public void Bind<TViewModel>(Control view, TViewModel viewModel)
where TViewModel : class {
bind(view, viewModel);
}
/// <summary>
/// Binds the specified view to the view model specified in its DataContext
/// </summary>
/// <param name="viewControl">View that will be bound</param>
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);
}
/// <summary>Binds a view to a view model</summary>
/// <param name="view">View that will be bound</param>
/// <param name="viewModel">View model the view will be bound to</param>
private void bind(Control view, object viewModel) {
}
}
} // namespace Nuclex.Windows.Forms.AutoBinding

View File

@ -0,0 +1,28 @@
using System;
using System.Windows.Forms;
using Nuclex.Windows.Forms.Views;
namespace Nuclex.Windows.Forms.AutoBinding {
/// <summary>Binds views to their view models</summary>
public interface IAutoBinder {
/// <summary>Binds the specified view to an explicitly selected view model</summary>
/// <typeparam name="TViewModel">
/// Type of view model the view will be bound to
/// </typeparam>
/// <param name="view">View that will be bound to a view model</param>
/// <param name="viewModel">View model the view will be bound to</param>
void Bind<TViewModel>(Control view, TViewModel viewModel)
where TViewModel : class;
/// <summary>
/// Binds the specified view to the view model specified in its DataContext
/// </summary>
/// <param name="view">View that will be bound</param>
void Bind(Control view);
}
} // namespace Nuclex.Windows.Forms.AutoBinding

View File

@ -24,6 +24,7 @@ using System.Collections.Generic;
using System.Windows.Forms; using System.Windows.Forms;
using Nuclex.Support; using Nuclex.Support;
using Nuclex.Windows.Forms.AutoBinding;
using Nuclex.Windows.Forms.Views; using Nuclex.Windows.Forms.Views;
namespace Nuclex.Windows.Forms { namespace Nuclex.Windows.Forms {
@ -31,8 +32,56 @@ namespace Nuclex.Windows.Forms {
/// <summary>Manages an application's windows and views</summary> /// <summary>Manages an application's windows and views</summary>
public class WindowManager : Observable, IWindowManager { public class WindowManager : Observable, IWindowManager {
#region class CancellableDisposer
/// <summary>Disposes an object that potentially implements IDisposable</summary>
private struct CancellableDisposer : IDisposable {
/// <summary>Initializes a new cancellable disposer</summary>
/// <param name="potentiallyDisposable">
/// Object that potentially implements IDisposable
/// </param>
public CancellableDisposer(object potentiallyDisposable = null) {
this.potentiallyDisposable = potentiallyDisposable;
}
/// <summary>
/// Disposes the assigned object if the disposer has not been cancelled
/// </summary>
public void Dispose() {
var disposable = this.potentiallyDisposable as IDisposable;
if(disposable != null) {
disposable.Dispose();
}
}
/// <summary>Cancels the call to Dispose(), keeping the object alive</summary>
public void Dismiss() {
this.potentiallyDisposable = null;
}
/// <summary>Assigns a new potentially disposable object</summary>
/// <param name="potentiallyDisposable">
/// Potentially disposable object that the disposer will dispose
/// </param>
public void Set(object potentiallyDisposable) {
this.potentiallyDisposable = potentiallyDisposable;
}
/// <summary>Object that will be disposed unless the disposer is cancelled</summary>
private object potentiallyDisposable;
}
#endregion // class CancellableDisposer
/// <summary>Initializes a new window manager</summary> /// <summary>Initializes a new window manager</summary>
public WindowManager() { /// <param name="autoBinder">
/// View model binder that will be used to bind all created views to their models
/// </param>
public WindowManager(IAutoBinder autoBinder = null) {
this.autoBinder = autoBinder;
this.rootWindowActivatedDelegate = rootWindowActivated; this.rootWindowActivatedDelegate = rootWindowActivated;
this.rootWindowClosedDelegate = rootWindowClosed; this.rootWindowClosedDelegate = rootWindowClosed;
this.viewTypesForViewModels = new ConcurrentDictionary<Type, Type>(); this.viewTypesForViewModels = new ConcurrentDictionary<Type, Type>();
@ -74,7 +123,7 @@ namespace Nuclex.Windows.Forms {
// when we're done (but still allow the user to change his mind) // when we're done (but still allow the user to change his mind)
if((viewModel == null) || disposeOnClose) { if((viewModel == null) || disposeOnClose) {
window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead? window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead?
//window.SetValue(DisposeViewModelOnCloseProperty, true); //window.SetValue(DisposeViewModelOnCloseProperty, true);
} }
window.Show(); window.Show();
@ -107,7 +156,7 @@ namespace Nuclex.Windows.Forms {
// when we're done (but still allow the user to change his mind) // when we're done (but still allow the user to change his mind)
if((viewModel == null) || disposeOnClose) { if((viewModel == null) || disposeOnClose) {
window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead? window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead?
//window.SetValue(DisposeViewModelOnCloseProperty, true); //window.SetValue(DisposeViewModelOnCloseProperty, true);
} }
DialogResult result = window.ShowDialog(this.activeWindow); DialogResult result = window.ShowDialog(this.activeWindow);
@ -149,27 +198,42 @@ namespace Nuclex.Windows.Forms {
) where TViewModel : class { ) where TViewModel : class {
Type viewType = LocateViewForViewModel(typeof(TViewModel)); Type viewType = LocateViewForViewModel(typeof(TViewModel));
Control viewControl = (Control)CreateInstance(viewType); Control viewControl = (Control)CreateInstance(viewType);
using(var viewDisposer = new CancellableDisposer(viewControl)) {
bool createdViewModel = false; // Create a view model if none was provided, and in either case assign
try { // the view model to the view (provided it implements IView).
IView viewControlAsView = viewControl as IView; using(var viewModelDisposer = new CancellableDisposer()) {
if(viewControlAsView != null) { IView viewControlAsView = viewControl as IView;
if(viewModel != null) { 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; 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; return viewControl;
@ -215,6 +279,7 @@ namespace Nuclex.Windows.Forms {
); );
} }
// Still no view found? We give up!
if(viewType == null) { if(viewType == null) {
throw new InvalidOperationException( throw new InvalidOperationException(
string.Format("Could not locate view for view model '{0}'", viewModelType.Name) string.Format("Could not locate view for view model '{0}'", viewModelType.Name)
@ -346,6 +411,8 @@ namespace Nuclex.Windows.Forms {
private EventHandler rootWindowActivatedDelegate; private EventHandler rootWindowActivatedDelegate;
/// <summary>Invoked when a root window has been closed</summary> /// <summary>Invoked when a root window has been closed</summary>
private EventHandler rootWindowClosedDelegate; private EventHandler rootWindowClosedDelegate;
/// <summary>View model binder that will be used on all created views</summary>
private IAutoBinder autoBinder;
/// <summary>Caches the view types to use for a view model</summary> /// <summary>Caches the view types to use for a view model</summary>
private ConcurrentDictionary<Type, Type> viewTypesForViewModels; private ConcurrentDictionary<Type, Type> viewTypesForViewModels;