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>
|
||||
<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.Test.cs">
|
||||
<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 Nuclex.Support;
|
||||
using Nuclex.Windows.Forms.AutoBinding;
|
||||
using Nuclex.Windows.Forms.Views;
|
||||
|
||||
namespace Nuclex.Windows.Forms {
|
||||
|
@ -31,8 +32,56 @@ namespace Nuclex.Windows.Forms {
|
|||
/// <summary>Manages an application's windows and views</summary>
|
||||
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>
|
||||
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.rootWindowClosedDelegate = rootWindowClosed;
|
||||
this.viewTypesForViewModels = new ConcurrentDictionary<Type, Type>();
|
||||
|
@ -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 {
|
||||
// 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(viewControlAsView != null) {
|
||||
if(viewModel != null) {
|
||||
viewControlAsView.DataContext = viewModel;
|
||||
} else if(viewControlAsView.DataContext == 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;
|
||||
createdViewModel = true;
|
||||
}
|
||||
}
|
||||
} else if(viewControlAsView != null) { // Caller has provided a view model
|
||||
viewControlAsView.DataContext = viewModel;
|
||||
}
|
||||
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;
|
||||
/// <summary>Invoked when a root window has been closed</summary>
|
||||
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>
|
||||
private ConcurrentDictionary<Type, Type> viewTypesForViewModels;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user