Refactor the ViewManager, splitting out a few methods to make it easier to customize

This commit is contained in:
Antony Male 2014-05-26 18:48:25 +01:00
parent 2f4424a85d
commit a914d58668
5 changed files with 64 additions and 60 deletions

View File

@ -18,14 +18,14 @@ namespace Stylet
{
/// <summary>
/// Called by the View.Model attached property when the ViewModel its bound to changes
void OnModelChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs e);
void OnModelChanged(DependencyObject targetLocation, object oldValue, object newValue);
/// <summary>
/// Given an instance of a ViewModel, locate the correct view for it, and instantiate it
/// </summary>
/// <param name="model">ViewModel to locate the view for</param>
/// <returns>An instance of the correct view</returns>
UIElement CreateViewForModel(object model);
UIElement CreateAndSetupViewForModel(object model);
/// <summary>
/// Given an instance of a ViewModel and an instance of its View, bind the two together
@ -37,23 +37,23 @@ namespace Stylet
public class ViewManager : IViewManager
{
public virtual void OnModelChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs e)
public virtual void OnModelChanged(DependencyObject targetLocation, object oldValue, object newValue)
{
if (e.OldValue == e.NewValue)
if (oldValue == newValue)
return;
if (e.NewValue != null)
if (newValue != null)
{
UIElement view;
var viewModelAsViewAware = e.NewValue as IViewAware;
var viewModelAsViewAware = newValue as IViewAware;
if (viewModelAsViewAware != null && viewModelAsViewAware.View != null)
{
view = viewModelAsViewAware.View;
}
else
{
view = this.CreateViewForModel(e.NewValue);
this.BindViewToModel(view, e.NewValue);
view = this.CreateAndSetupViewForModel(newValue);
this.BindViewToModel(view, newValue);
}
View.SetContentProperty(targetLocation, view);
@ -67,7 +67,11 @@ namespace Stylet
public virtual Type ViewTypeForViewName(string viewName)
{
// TODO: This might need some more thinking
return AssemblySource.Assemblies.SelectMany(x => x.GetExportedTypes()).FirstOrDefault(x => x.FullName == viewName);
var viewType = AssemblySource.Assemblies.SelectMany(x => x.GetExportedTypes()).FirstOrDefault(x => x.FullName == viewName);
if (viewType == null)
throw new Exception(String.Format("Unable to find a View with type {0}", viewName));
return viewType;
}
public virtual Type LocateViewForModel(Type modelType)
@ -75,13 +79,22 @@ namespace Stylet
var viewName = Regex.Replace(modelType.FullName, @"ViewModel", "View");
var viewType = this.ViewTypeForViewName(viewName);
if (viewType == null)
throw new Exception(String.Format("Unable to find a View with type {0}", viewName));
return viewType;
}
public virtual UIElement CreateViewForModel(object model)
public virtual void BindViewToModel(UIElement view, object viewModel)
{
View.SetActionTarget(view, viewModel);
var viewAsFrameworkElement = view as FrameworkElement;
if (viewAsFrameworkElement != null)
viewAsFrameworkElement.DataContext = viewModel;
var viewModelAsViewAware = viewModel as IViewAware;
if (viewModelAsViewAware != null)
viewModelAsViewAware.AttachView(view);
}
public virtual UIElement CreateAndSetupViewForModel(object model)
{
var viewType = this.LocateViewForModel(model.GetType());
@ -97,18 +110,5 @@ namespace Stylet
return view;
}
public virtual void BindViewToModel(UIElement view, object viewModel)
{
View.SetActionTarget(view, viewModel);
var viewAsFrameworkElement = view as FrameworkElement;
if (viewAsFrameworkElement != null)
viewAsFrameworkElement.DataContext = viewModel;
var viewModelAsViewAware = viewModel as IViewAware;
if (viewModelAsViewAware != null)
viewModelAsViewAware.AttachView(view);
}
}
}

View File

@ -38,7 +38,7 @@ namespace Stylet.Xaml
}
public static readonly DependencyProperty ModelProperty =
DependencyProperty.RegisterAttached("Model", typeof(object), typeof(View), new PropertyMetadata(null, (d, e) => ViewManager.OnModelChanged(d, e) ));
DependencyProperty.RegisterAttached("Model", typeof(object), typeof(View), new PropertyMetadata(null, (d, e) => ViewManager.OnModelChanged(d, e.OldValue, e.NewValue) ));
public static void SetContentProperty(DependencyObject targetLocation, UIElement view)

View File

@ -32,7 +32,7 @@ namespace StyletUnitTests
{
public UIElement View;
public object RequestedModel;
public override UIElement CreateViewForModel(object model)
public override UIElement CreateAndSetupViewForModel(object model)
{
this.RequestedModel = model;
return this.View;
@ -84,14 +84,14 @@ namespace StyletUnitTests
public void OnModelChangedDoesNothingIfNoChange()
{
var val = new object();
this.viewManager.OnModelChanged(null, new DependencyPropertyChangedEventArgs(View.ModelProperty, val, val));
this.viewManager.OnModelChanged(null, val, val);
}
[Test]
public void OnModelChangedSetsNullIfNewValueNull()
{
var target = new ContentControl();
this.viewManager.OnModelChanged(target, new DependencyPropertyChangedEventArgs(View.ModelProperty, 5, null));
this.viewManager.OnModelChanged(target, 5, null);
Assert.Null(target.Content);
}
@ -103,7 +103,7 @@ namespace StyletUnitTests
var view = new UIElement();
model.Setup(x => x.View).Returns(view);
this.viewManager.OnModelChanged(target, new DependencyPropertyChangedEventArgs(View.ModelProperty, null, model.Object));
this.viewManager.OnModelChanged(target, null, model.Object);
Assert.AreEqual(view, target.Content);
}
@ -118,7 +118,7 @@ namespace StyletUnitTests
viewManager.View = view;
viewManager.OnModelChanged(target, new DependencyPropertyChangedEventArgs(View.ModelProperty, null, model));
viewManager.OnModelChanged(target, null, model);
Assert.AreEqual(viewManager.RequestedModel, model);
Assert.AreEqual(viewManager.BindViewToModelView, view);
@ -147,13 +147,13 @@ namespace StyletUnitTests
var viewManager = new LocatingViewManager();
viewManager.LocatedViewType = typeof(I1);
Assert.Throws<Exception>(() => viewManager.CreateViewForModel(new object()));
Assert.Throws<Exception>(() => viewManager.CreateAndSetupViewForModel(new object()));
viewManager.LocatedViewType = typeof(AC1);
Assert.Throws<Exception>(() => viewManager.CreateViewForModel(new object()));
Assert.Throws<Exception>(() => viewManager.CreateAndSetupViewForModel(new object()));
viewManager.LocatedViewType = typeof(C1);
Assert.Throws<Exception>(() => viewManager.CreateViewForModel(new object()));
Assert.Throws<Exception>(() => viewManager.CreateAndSetupViewForModel(new object()));
}
[Test]
@ -169,7 +169,7 @@ namespace StyletUnitTests
var viewManager = new LocatingViewManager();
viewManager.LocatedViewType = typeof(TestView);
var returnedView = viewManager.CreateViewForModel(new object());
var returnedView = viewManager.CreateAndSetupViewForModel(new object());
Assert.True(view.InitializeComponentCalled);
Assert.AreEqual(view, returnedView);
@ -188,7 +188,7 @@ namespace StyletUnitTests
var viewManager = new LocatingViewManager();
viewManager.LocatedViewType = typeof(UIElement);
var returnedView = viewManager.CreateViewForModel(new object());
var returnedView = viewManager.CreateAndSetupViewForModel(new object());
Assert.AreEqual(view, returnedView);
}

View File

@ -47,15 +47,19 @@ namespace StyletUnitTests
var model = new object();
View.SetModel(obj, null);
DependencyPropertyChangedEventArgs ea = default(DependencyPropertyChangedEventArgs);
this.viewManager.Setup(x => x.OnModelChanged(obj, It.IsAny<DependencyPropertyChangedEventArgs>()))
.Callback<DependencyObject, DependencyPropertyChangedEventArgs>((d, e) => ea = e).Verifiable();
object oldValue = null;
object newValue = null;
this.viewManager.Setup(x => x.OnModelChanged(obj, It.IsAny<object>(), It.IsAny<object>()))
.Callback<DependencyObject, object, object>((d, eOldValue, eNewValue) =>
{
oldValue = eOldValue;
newValue = eNewValue;
}).Verifiable();
View.SetModel(obj, model);
this.viewManager.Verify();
Assert.Null(ea.OldValue);
Assert.AreEqual(model, ea.NewValue);
Assert.AreEqual(View.ModelProperty, ea.Property);
Assert.Null(oldValue);
Assert.AreEqual(model, newValue);
}
[Test]

View File

@ -61,7 +61,7 @@ namespace StyletUnitTests
public void CreateWindowAsksViewManagerForView()
{
var model = new object();
this.viewManager.Setup(x => x.CreateViewForModel(model)).Verifiable();
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model)).Verifiable();
// Don't care if this throws - that's OK
try { this.windowManager.CreateWindow(model, false); }
catch (Exception) { }
@ -72,7 +72,7 @@ namespace StyletUnitTests
public void CreateWindowThrowsIfViewIsntAWindow()
{
var model = new object();
this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(new UIElement());
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model)).Returns(new UIElement());
Assert.Throws<ArgumentException>(() => this.windowManager.CreateWindow(model, false));
}
@ -81,7 +81,7 @@ namespace StyletUnitTests
{
var model = new object();
var window = new Window();
this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
@ -93,7 +93,7 @@ namespace StyletUnitTests
{
var model = new Screen();
var window = new Window();
this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
@ -106,7 +106,7 @@ namespace StyletUnitTests
public void CreateWindowActivatesViewModel()
{
var model = new Mock<IScreen>();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(new Window());
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(new Window());
this.windowManager.CreateWindow(model.Object, false);
model.Verify(x => x.Activate());
}
@ -116,7 +116,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
window.WindowState = WindowState.Maximized;
window.OnStateChanged(EventArgs.Empty);
@ -128,7 +128,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
window.WindowState = WindowState.Normal;
window.OnStateChanged(EventArgs.Empty);
@ -140,7 +140,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
window.WindowState = WindowState.Minimized;
window.OnStateChanged(EventArgs.Empty);
@ -152,7 +152,7 @@ namespace StyletUnitTests
{
var model = new Screen();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
window.OnClosing(new CancelEventArgs(true));
}
@ -162,7 +162,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
model.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(false));
var ea = new CancelEventArgs();
@ -175,7 +175,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
model.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(true));
var ea = new CancelEventArgs();
@ -188,7 +188,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
model.Setup(x => x.CanCloseAsync()).Returns(Task.Delay(1).ContinueWith(t => false));
var ea = new CancelEventArgs();
@ -201,7 +201,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
model.Setup(x => x.CanCloseAsync()).Returns(Task.Delay(1).ContinueWith(t => true));
var ea = new CancelEventArgs();
@ -214,7 +214,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
var tcs = new TaskCompletionSource<bool>();
model.Setup(x => x.CanCloseAsync()).Returns(tcs.Task);
@ -234,7 +234,7 @@ namespace StyletUnitTests
{
var model = new Screen();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
((IChildDelegate)model.Parent).CloseItem(new object());
}
@ -244,7 +244,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
object parent = null;
model.SetupSet(x => x.Parent = It.IsAny<object>()).Callback((object x) => parent = x);
this.windowManager.CreateWindow(model.Object, false);
@ -258,7 +258,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndSetupViewForModel(model.Object)).Returns(window);
object parent = null;
model.SetupSet(x => x.Parent = It.IsAny<object>()).Callback((object x) => parent = x);
this.windowManager.CreateWindow(model.Object, true);