Simplify IViewManager, by aligning it to what someone actually wants the ViewManager to do

This commit is contained in:
Antony Male 2014-07-24 16:39:26 +01:00
parent b53d0f5195
commit 60aca05dd2
5 changed files with 67 additions and 59 deletions

View File

@ -21,18 +21,11 @@ namespace Stylet
void OnModelChanged(DependencyObject targetLocation, object oldValue, object newValue); void OnModelChanged(DependencyObject targetLocation, object oldValue, object newValue);
/// <summary> /// <summary>
/// Given a ViewModel instance, locate its View type (using LocateViewForModel), instantiates and initializes it /// Create a View for the given ViewModel, and bind the two together
/// </summary> /// </summary>
/// <param name="model">ViewModel to locate and instantiate the View for</param> /// <param name="model">ViewModel to create a Veiw for</param>
/// <returns>Instantiated and setup view</returns> /// <returns>Newly created View, bound to the given ViewModel</returns>
UIElement CreateAndSetupViewForModel(object model); UIElement CreateAndBindViewForModel(object model);
/// <summary>
/// Given an instance of a ViewModel and an instance of its View, bind the two together
/// </summary>
/// <param name="view">View to bind to the ViewModel</param>
/// <param name="viewModel">ViewModel to bind the View to</param>
void BindViewToModel(UIElement view, object viewModel);
} }
/// <summary> /// <summary>
@ -61,8 +54,7 @@ namespace Stylet
} }
else else
{ {
view = this.CreateAndSetupViewForModel(newValue); view = this.CreateAndBindViewForModel(newValue);
this.BindViewToModel(view, newValue);
} }
View.SetContentProperty(targetLocation, view); View.SetContentProperty(targetLocation, view);
@ -73,6 +65,20 @@ namespace Stylet
} }
} }
/// <summary>
/// Create a View for the given ViewModel, and bind the two together
/// </summary>
/// <param name="model">ViewModel to create a Veiw for</param>
/// <returns>Newly created View, bound to the given ViewModel</returns>
public virtual UIElement CreateAndBindViewForModel(object model)
{
// Need to bind before we initialize the view
// Otherwise e.g. the Command bindings get evaluated (by InitializeComponent) but the ActionTarget hasn't been set yet
var view = this.CreateViewForModel(model);
this.BindViewToModel(view, model);
return view;
}
/// <summary> /// <summary>
/// Given the expected name for a view, locate its type (or throw an exception if a suitable type couldn't be found) /// Given the expected name for a view, locate its type (or throw an exception if a suitable type couldn't be found)
/// </summary> /// </summary>
@ -102,11 +108,11 @@ namespace Stylet
} }
/// <summary> /// <summary>
/// Given a ViewModel instance, locate its View type (using LocateViewForModel), instantiates and initializes it, and binds it to the ViewModel (using BindViewToModel) /// Given a ViewModel instance, locate its View type (using LocateViewForModel), and instantiates it
/// </summary> /// </summary>
/// <param name="model">ViewModel to locate and instantiate the View for</param> /// <param name="model">ViewModel to locate and instantiate the View for</param>
/// <returns>Instantiated and setup view</returns> /// <returns>Instantiated and setup view</returns>
public virtual UIElement CreateAndSetupViewForModel(object model) protected virtual UIElement CreateViewForModel(object model)
{ {
var viewType = this.LocateViewForModel(model.GetType()); var viewType = this.LocateViewForModel(model.GetType());
@ -128,7 +134,7 @@ namespace Stylet
/// </summary> /// </summary>
/// <param name="view">View to bind to the ViewModel</param> /// <param name="view">View to bind to the ViewModel</param>
/// <param name="viewModel">ViewModel to bind the View to</param> /// <param name="viewModel">ViewModel to bind the View to</param>
public virtual void BindViewToModel(UIElement view, object viewModel) protected virtual void BindViewToModel(UIElement view, object viewModel)
{ {
View.SetActionTarget(view, viewModel); View.SetActionTarget(view, viewModel);

View File

@ -68,13 +68,11 @@ namespace Stylet
/// <returns>Window which was created and set up</returns> /// <returns>Window which was created and set up</returns>
protected virtual Window CreateWindow(object viewModel, bool isDialog) protected virtual Window CreateWindow(object viewModel, bool isDialog)
{ {
var view = this.viewManager.CreateAndSetupViewForModel(viewModel); var view = this.viewManager.CreateAndBindViewForModel(viewModel);
var window = view as Window; var window = view as Window;
if (window == null) if (window == null)
throw new ArgumentException(String.Format("Tried to show {0} as a window, but it isn't a Window", view == null ? "(null)" : view.GetType().Name)); throw new ArgumentException(String.Format("Tried to show {0} as a window, but it isn't a Window", view == null ? "(null)" : view.GetType().Name));
this.viewManager.BindViewToModel(window, viewModel);
var haveDisplayName = viewModel as IHaveDisplayName; var haveDisplayName = viewModel as IHaveDisplayName;
if (haveDisplayName != null && BindingOperations.GetBindingBase(window, Window.TitleProperty) == null) if (haveDisplayName != null && BindingOperations.GetBindingBase(window, Window.TitleProperty) == null)
{ {

View File

@ -6,7 +6,7 @@
<DockPanel LastChildFill="False"> <DockPanel LastChildFill="False">
<GroupBox DockPanel.Dock="Top" Header="ShowDialog and DialogResult" Padding="10"> <GroupBox DockPanel.Dock="Top" Header="ShowDialog and DialogResult" Padding="10">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Button Command="{s:Action ShowDialogAndDialogResult}">Show Dialog</Button> <Button Command="{s:Action ShowDialogAndDialogResult, NullTarget=Throw}">Show Dialog</Button>
<TextBlock Margin="50,0,0,0">Result: </TextBlock> <TextBlock Margin="50,0,0,0">Result: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ShowDialogAndDialogResultDialogResult}"/> <TextBlock Margin="10,0,0,0" Text="{Binding ShowDialogAndDialogResultDialogResult}"/>
</StackPanel> </StackPanel>

View File

@ -28,11 +28,24 @@ namespace StyletUnitTests
private abstract class AC1 { } private abstract class AC1 { }
private class C1 { } private class C1 { }
private class AccessibleViewManager : ViewManager
{
public new UIElement CreateViewForModel(object model)
{
return base.CreateViewForModel(model);
}
public new void BindViewToModel(UIElement view, object viewModel)
{
base.BindViewToModel(view, viewModel);
}
}
private class CreatingAndBindingViewManager : ViewManager private class CreatingAndBindingViewManager : ViewManager
{ {
public UIElement View; public UIElement View;
public object RequestedModel; public object RequestedModel;
public override UIElement CreateAndSetupViewForModel(object model) protected override UIElement CreateViewForModel(object model)
{ {
this.RequestedModel = model; this.RequestedModel = model;
return this.View; return this.View;
@ -40,7 +53,7 @@ namespace StyletUnitTests
public UIElement BindViewToModelView; public UIElement BindViewToModelView;
public object BindViewtoModelViewModel; public object BindViewtoModelViewModel;
public override void BindViewToModel(UIElement view, object viewModel) protected override void BindViewToModel(UIElement view, object viewModel)
{ {
this.BindViewToModelView = view; this.BindViewToModelView = view;
this.BindViewtoModelViewModel = viewModel; this.BindViewtoModelViewModel = viewModel;
@ -156,13 +169,13 @@ namespace StyletUnitTests
var viewManager = new LocatingViewManager(); var viewManager = new LocatingViewManager();
viewManager.LocatedViewType = typeof(I1); viewManager.LocatedViewType = typeof(I1);
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndSetupViewForModel(new object())); Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModel(new object()));
viewManager.LocatedViewType = typeof(AC1); viewManager.LocatedViewType = typeof(AC1);
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndSetupViewForModel(new object())); Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModel(new object()));
viewManager.LocatedViewType = typeof(C1); viewManager.LocatedViewType = typeof(C1);
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndSetupViewForModel(new object())); Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModel(new object()));
} }
[Test] [Test]
@ -178,7 +191,7 @@ namespace StyletUnitTests
var viewManager = new LocatingViewManager(); var viewManager = new LocatingViewManager();
viewManager.LocatedViewType = typeof(TestView); viewManager.LocatedViewType = typeof(TestView);
var returnedView = viewManager.CreateAndSetupViewForModel(new object()); var returnedView = viewManager.CreateAndBindViewForModel(new object());
Assert.True(view.InitializeComponentCalled); Assert.True(view.InitializeComponentCalled);
Assert.AreEqual(view, returnedView); Assert.AreEqual(view, returnedView);
@ -197,7 +210,7 @@ namespace StyletUnitTests
var viewManager = new LocatingViewManager(); var viewManager = new LocatingViewManager();
viewManager.LocatedViewType = typeof(UIElement); viewManager.LocatedViewType = typeof(UIElement);
var returnedView = viewManager.CreateAndSetupViewForModel(new object()); var returnedView = viewManager.CreateAndBindViewForModel(new object());
Assert.AreEqual(view, returnedView); Assert.AreEqual(view, returnedView);
} }
@ -207,7 +220,8 @@ namespace StyletUnitTests
{ {
var view = new UIElement(); var view = new UIElement();
var model = new object(); var model = new object();
this.viewManager.BindViewToModel(view, model); var viewManager = new AccessibleViewManager();
viewManager.BindViewToModel(view, model);
Assert.AreEqual(model, View.GetActionTarget(view)); Assert.AreEqual(model, View.GetActionTarget(view));
} }
@ -217,7 +231,8 @@ namespace StyletUnitTests
{ {
var view = new FrameworkElement(); var view = new FrameworkElement();
var model = new object(); var model = new object();
this.viewManager.BindViewToModel(view, model); var viewManager = new AccessibleViewManager();
viewManager.BindViewToModel(view, model);
Assert.AreEqual(model, view.DataContext); Assert.AreEqual(model, view.DataContext);
} }
@ -227,7 +242,8 @@ namespace StyletUnitTests
{ {
var view = new UIElement(); var view = new UIElement();
var model = new Mock<IViewAware>(); var model = new Mock<IViewAware>();
this.viewManager.BindViewToModel(view, model.Object); var viewManager = new AccessibleViewManager();
viewManager.BindViewToModel(view, model.Object);
model.Verify(x => x.AttachView(view)); model.Verify(x => x.AttachView(view));
} }

View File

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