diff --git a/ReadMe.md b/ReadMe.md
index 7cd8cce..99b9927 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -118,7 +118,7 @@ you have set up in your Ninject kernel.
class MainViewModel {
public MainViewModel(IMyService myService, IMySettings mySettings) {
- // ...
+ // ...
}
}
diff --git a/Source/IWindowScope.cs b/Source/IWindowScope.cs
new file mode 100644
index 0000000..43c55c3
--- /dev/null
+++ b/Source/IWindowScope.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace Nuclex.Windows.Forms {
+
+ /// Constructs views and view model in a scope
+ ///
+ ///
+ /// By default the uses its own
+ /// method to construct views
+ /// and view models via ,
+ /// which is enough to create forms (which the Windows Forms designer already
+ /// requires to have parameterless constructors) and view models, so long as
+ /// they also have parameterless constructors.
+ ///
+ ///
+ /// To support dependency injection via constructor parameters, you can
+ /// inherit from the and provide your own override
+ /// of that constructs the required
+ /// instance via your dependency injector. This is decent until you have multiple
+ /// view models all accessing the same resource (i.e. a database) via threads.
+ ///
+ ///
+ /// In this final case, "scopes" have become a common solution. Each
+ /// scope has access to singleton services (these exist for the lifetime of
+ /// the entire application), but there are also scoped services which will have
+ /// new instances constructed within each scope. By implementing the
+ /// method, you can make
+ /// the window manager set up an implicit scope per window or dialog.
+ ///
+ ///
+ public interface IWindowScope : IDisposable {
+
+ /// Creates an instance of the specified type in the scope
+ /// Type an instance will be created of
+ /// The created instance
+ ///
+ /// Use this to wire up your dependency injection container. By default,
+ /// the Activator class will be used to create instances which only works
+ /// if all of your view models are concrete classes.
+ ///
+ object CreateInstance(Type type);
+
+ }
+
+} // namespace Nuclex.Windows.Forms
diff --git a/Source/WindowManager.cs b/Source/WindowManager.cs
index 4d0fea8..b83c448 100644
--- a/Source/WindowManager.cs
+++ b/Source/WindowManager.cs
@@ -39,6 +39,36 @@ namespace Nuclex.Windows.Forms {
#endif
public class WindowManager : Observable, IWindowManager {
+ #region class WindowManagerScope
+
+ /// Global scope that uses the WindowManager's CreateInstance()
+ public class WindowManagerScope : IWindowScope {
+
+ /// Initializes a new global window scope
+ ///
+ /// Window manager whose CreateInstance() method will be used
+ ///
+ public WindowManagerScope(WindowManager windowManager) {
+ this.windowManager = windowManager;
+ }
+
+ /// Creates an instance of the specified type in the scope
+ /// Type an instance will be created of
+ /// The created instance
+ object IWindowScope.CreateInstance(Type type) {
+ return this.windowManager.CreateInstance(type);
+ }
+
+ /// Does nothing because this is the global fallback scope
+ void IDisposable.Dispose() {}
+
+ /// WindowManager whose CreateInstance() method will be wrapped
+ private WindowManager windowManager;
+
+ }
+
+ #endregion // class WindwoManagerScope
+
#region class CancellableDisposer
/// Disposes an object that potentially implements IDisposable
@@ -91,6 +121,7 @@ namespace Nuclex.Windows.Forms {
this.rootWindowActivatedDelegate = rootWindowActivated;
this.rootWindowClosedDelegate = rootWindowClosed;
+ this.windowManagerAsScope = new WindowManagerScope(this);
this.viewTypesForViewModels = new ConcurrentDictionary();
}
@@ -193,45 +224,55 @@ namespace Nuclex.Windows.Forms {
public virtual Control CreateView(
TViewModel viewModel = null
) where TViewModel : class {
- Type viewType = LocateViewForViewModel(typeof(TViewModel));
- Control viewControl = (Control)CreateInstance(viewType);
- using(var viewDisposer = new CancellableDisposer(viewControl)) {
+ Control viewControl;
+ {
+ Type viewType = LocateViewForViewModel(typeof(TViewModel));
- // 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);
+ IWindowScope scope = CreateWindowScope();
+ using(var scopeDisposer = new CancellableDisposer(scope)) {
+ viewControl = (Control)scope.CreateInstance(viewType);
+ using(var viewDisposer = new CancellableDisposer(viewControl)) {
+
+ // 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)scope.CreateInstance(typeof(TViewModel));
+ viewModelDisposer.Set(viewModel);
+ } else if(viewControlAsView.DataContext == null) { // View has no view model
+ viewModel = (TViewModel)scope.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)scope.CreateInstance(typeof(TViewModel));
+ viewModelDisposer.Set(viewModel);
+ viewControlAsView.DataContext = viewModel;
+ }
+ }
+ } else if(viewControlAsView != null) { // Caller has provided a view model
viewControlAsView.DataContext = viewModel;
}
+
+ // 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
}
- } else if(viewControlAsView != null) { // Caller has provided a view model
- viewControlAsView.DataContext = viewModel;
+
+ viewDisposer.Dismiss(); // Everything went well, we keep the view
}
- // 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
+ scopeDisposer.Dismiss(); // Everything went well, we keep the scope
}
- viewDisposer.Dismiss(); // Everything went well, we keep the view
-
- }
+ setupScopeDisposal(viewControl, scope);
+ } // beauty scope
return viewControl;
}
@@ -322,8 +363,18 @@ namespace Nuclex.Windows.Forms {
return Activator.CreateInstance(type);
}
- protected virtual object CreateServiceScope() {
- return null;
+ /// Creates an instance of the specified type in a new scope
+ /// Type an instance will be created of
+ /// The created instance and the scope in which it lives
+ ///
+ /// This is identical to but, if used together
+ /// with a dependency injector, should also create a service scope. This way,
+ /// an implicit service scope will cover the lifetime of a view model and
+ /// any non-singleton services will use new instances, avoiding, for example,
+ /// that multiple dialogs access the same database connection simultaneously.
+ ///
+ protected virtual IWindowScope CreateWindowScope() {
+ return this.windowManagerAsScope;
}
/// Called when one of the application's root windows is closed
@@ -338,6 +389,7 @@ namespace Nuclex.Windows.Forms {
ActiveWindow = null;
}
+ // Application.Run() already does this and it's the user's responsibility anyways
//disposeIfDisposable(closedWindow);
}
@@ -392,7 +444,7 @@ namespace Nuclex.Windows.Forms {
///
/// Control whose view model will be disposed when it is itself disposed
///
- private void setupViewModelDisposal(Control control) {
+ private static void setupViewModelDisposal(Control control) {
IView controlAsView = control as IView;
if(controlAsView != null) {
IDisposable disposableViewModel = controlAsView.DataContext as IDisposable;
@@ -408,6 +460,21 @@ namespace Nuclex.Windows.Forms {
//window.SetValue(DisposeViewModelOnCloseProperty, true);
}
+ /// Attaches a scope disposer to a control
+ ///
+ /// Control that will dispose a scope when it is itself disposed
+ ///
+ private void setupScopeDisposal(Control control, IWindowScope scope) {
+ if(!ReferenceEquals(scope, this.windowManagerAsScope)) {
+ IDisposable disposableScope = scope as IDisposable;
+ if(disposableScope != null) {
+ control.Disposed += delegate(object sender, EventArgs arguments) {
+ disposableScope.Dispose();
+ };
+ }
+ }
+ }
+
/// Filters a list of types to contain only those in a specific namespace
/// List of exported types that will be filtered
///
@@ -432,6 +499,8 @@ namespace Nuclex.Windows.Forms {
private EventHandler rootWindowActivatedDelegate;
/// Invoked when a root window has been closed
private EventHandler rootWindowClosedDelegate;
+ /// Scope that uses the WindowManager's global CreateInstance() method
+ private WindowManagerScope windowManagerAsScope;
/// View model binder that will be used on all created views
private IAutoBinder autoBinder;
/// Caches the view types to use for a view model