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:
parent
826f2eb763
commit
9aa64c4dac
|
@ -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>
|
||||||
|
|
47
Source/AutoBinding/ConventionBinder.cs
Normal file
47
Source/AutoBinding/ConventionBinder.cs
Normal 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
|
28
Source/AutoBinding/IAutoBinder.cs
Normal file
28
Source/AutoBinding/IAutoBinder.cs
Normal 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
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user