Merge branch 'release/1.1.1'

* release/1.1.1:
  Bump version
  Update changelog
  WindowManager avoids creating a new View if ViewModel already has a View attached
  Cheat to remove another line from failed coverage
  Remove a bit of untested code through interface separation
  Tweak name of generated abstract factory implementations
  Improve test coverage slightly
  Add DisposeChildren to IConductor, since it's really part of that...
  Improve error message when a user shows a non-Window view as a window
  If the located View is derived from UIElement, then it can't be an interface
  Change 'as' to explicit cast, as we're not expecting it to fail
This commit is contained in:
Antony Male 2015-02-22 16:50:47 +00:00
commit 0204c0e4de
14 changed files with 134 additions and 76 deletions

View File

@ -1,6 +1,14 @@
Stylet Changelog
================
v1.1.0
------
- WindowManager.ShowWindow/ShowDialog won't create a new View for the ViewModel if one is already attached
- Add DisposeChildren to IConductor
- Improve error message when WindowManager.ShowWindow/ShowDialog is used to show something that isn't a Window
- Minor semantic fixes and code coverage improvements
v1.1.0
------

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
<metadata>
<id>Stylet</id>
<version>1.1.0</version>
<version>1.1.1</version>
<title>Stylet</title>
<authors>Antony Male</authors>
<owners>Antony Male</owners>

View File

@ -50,6 +50,11 @@ namespace Stylet
// Not sure whether this might change in future...
public interface IConductor<T>
{
/// <summary>
/// Gets or sets a value indicating whether to dispose a child when it's closed. True by default
/// </summary>
bool DisposeChildren { get; set; }
/// <summary>
/// Activate the given item
/// </summary>

View File

@ -35,5 +35,5 @@ using System.Windows.Markup;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyVersion("1.1.1.0")]
[assembly: AssemblyFileVersion("1.1.1.0")]

View File

@ -315,16 +315,21 @@ namespace StyletIoC.Internal
return this.GetRegistrations(new TypeKey(type, key), searchGetAllTypes).GetAll();
}
internal IRegistrationCollection GetRegistrations(TypeKey typeKey, bool searchGetAllTypes)
internal IReadOnlyRegistrationCollection GetRegistrations(TypeKey typeKey, bool searchGetAllTypes)
{
this.CheckDisposed();
IRegistrationCollection registrations;
IReadOnlyRegistrationCollection readOnlyRegistrations;
IRegistrationCollection registrations;
// Try to get registrations. If there are none, see if we can add some from unbound generics
if (!this.registrations.TryGetValue(typeKey, out registrations) &&
!this.TryCreateFuncFactory(typeKey, out registrations) &&
!this.TryCreateGenericTypesForUnboundGeneric(typeKey, out registrations))
if (this.registrations.TryGetValue(typeKey, out registrations) ||
this.TryCreateFuncFactory(typeKey, out registrations) ||
this.TryCreateGenericTypesForUnboundGeneric(typeKey, out registrations))
{
readOnlyRegistrations = registrations;
}
else
{
if (searchGetAllTypes)
{
@ -334,16 +339,16 @@ namespace StyletIoC.Internal
throw new StyletIoCRegistrationException(String.Format("No registrations found for service {0}.", typeKey.Type.GetDescription()));
// Got this far? Good. There's actually a 'get all' collection type. Proceed with that
registrations = new SingleRegistration(registration);
readOnlyRegistrations = new SingleRegistration(registration);
}
else
{
// This will throw a StyletIoCRegistrationException is GetSingle is requested
registrations = new EmptyRegistrationCollection(typeKey.Type);
// This will throw a StyletIoCRegistrationException if GetSingle is requested
readOnlyRegistrations = new EmptyRegistrationCollection(typeKey.Type);
}
}
return registrations;
return readOnlyRegistrations;
}
internal IRegistrationCollection AddRegistration(TypeKey typeKey, IRegistration registration)
@ -389,8 +394,8 @@ namespace StyletIoC.Internal
this.factoryBuilder = moduleBuilder;
}
// If the service is 'ISomethingFactory', call out new class 'SomethingFactory'
var typeBuilder = this.factoryBuilder.DefineType(serviceType.Name.Substring(1), TypeAttributes.Public);
// If the service is 'ISomethingFactory', call our new class 'GeneratedSomethingFactory'
var typeBuilder = this.factoryBuilder.DefineType("Generated" + serviceType.Name.Substring(1), TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(serviceType);
// Define a field which holds a reference to the registration context

View File

@ -3,10 +3,14 @@ using System.Collections.Generic;
namespace StyletIoC.Internal
{
internal interface IRegistrationCollection
internal interface IRegistrationCollection : IReadOnlyRegistrationCollection
{
IRegistrationCollection AddRegistration(IRegistration registration);
}
internal interface IReadOnlyRegistrationCollection
{
IRegistration GetSingle();
List<IRegistration> GetAll();
IRegistrationCollection AddRegistration(IRegistration registration);
}
}

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace StyletIoC.Internal.RegistrationCollections
{
internal class EmptyRegistrationCollection : IRegistrationCollection
internal class EmptyRegistrationCollection : IReadOnlyRegistrationCollection
{
private readonly Type type;
@ -22,10 +22,5 @@ namespace StyletIoC.Internal.RegistrationCollections
{
return new List<IRegistration>();
}
public IRegistrationCollection AddRegistration(IRegistration registration)
{
return new SingleRegistration(registration);
}
}
}

View File

@ -37,11 +37,11 @@ namespace Stylet
void BindViewToModel(UIElement view, object viewModel);
/// <summary>
/// Create a View for the given ViewModel, and bind the two together
/// Create a View for the given ViewModel, and bind the two together, if the model doesn't already have a view
/// </summary>
/// <param name="model">ViewModel to create a Veiw for</param>
/// <returns>Newly created View, bound to the given ViewModel</returns>
UIElement CreateAndBindViewForModel(object model);
UIElement CreateAndBindViewForModelIfNecessary(object model);
}
/// <summary>
@ -103,37 +103,44 @@ namespace Stylet
if (newValue != null)
{
UIElement view;
var viewModelAsViewAware = newValue as IViewAware;
if (viewModelAsViewAware != null && viewModelAsViewAware.View != null)
{
logger.Info("View.Model changed for {0} from {1} to {2}. The new View was already stored the new ViewModel", targetLocation, oldValue, newValue);
view = viewModelAsViewAware.View;
}
else
{
logger.Info("View.Model changed for {0} from {1} to {2}. Instantiating and binding a new View instance for the new ViewModel", targetLocation, oldValue, newValue);
view = this.CreateAndBindViewForModel(newValue);
}
logger.Info("View.Model changed for {0} from {1} to {2}", targetLocation, oldValue, newValue);
var view = this.CreateAndBindViewForModelIfNecessary(newValue);
View.SetContentProperty(targetLocation, view);
}
else
{
logger.Info("View.Model clear for {0}, from {1}", targetLocation, oldValue);
logger.Info("View.Model cleared for {0}, from {1}", targetLocation, oldValue);
View.SetContentProperty(targetLocation, null);
}
}
/// <summary>
/// Create a View for the given ViewModel, and bind the two together, if the model doesn't already have a view
/// </summary>
/// <param name="model">ViewModel to create a Veiw for</param>
/// <returns>Newly created View, bound to the given ViewModel</returns>
public virtual UIElement CreateAndBindViewForModelIfNecessary(object model)
{
var modelAsViewAware = model as IViewAware;
if (modelAsViewAware != null && modelAsViewAware.View != null)
{
logger.Info("ViewModel {0} already has a View attached to it. Not attaching another", model);
return modelAsViewAware.View;
}
return this.CreateAndBindViewForModel(model);
}
/// <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)
protected 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
logger.Info("Instantiating and binding a new View to ViewModel {0}", model);
var view = this.CreateViewForModel(model);
this.BindViewToModel(view, model);
return view;
@ -199,16 +206,17 @@ namespace Stylet
{
var viewType = this.LocateViewForModel(model.GetType());
if (viewType.IsInterface || viewType.IsAbstract || !typeof(UIElement).IsAssignableFrom(viewType))
if (viewType.IsAbstract || !typeof(UIElement).IsAssignableFrom(viewType))
{
var e = new StyletViewLocationException(String.Format("Found type for view: {0}, but it wasn't a class derived from UIElement", viewType.Name), viewType.Name);
logger.Error(e);
throw e;
}
var view = this.ViewFactory(viewType) as UIElement;
var view = (UIElement)this.ViewFactory(viewType);
// If it doesn't have a code-behind, this won't be called
// We have to use this reflection here, since the InitializeComponent is a method on the View, not on any of its base classes
var initializer = viewType.GetMethod("InitializeComponent", BindingFlags.Public | BindingFlags.Instance);
if (initializer != null)
initializer.Invoke(view, null);

View File

@ -120,11 +120,12 @@ namespace Stylet
/// <returns>Window which was created and set up</returns>
protected virtual Window CreateWindow(object viewModel, bool isDialog)
{
var view = this.viewManager.CreateAndBindViewForModel(viewModel);
var view = this.viewManager.CreateAndBindViewForModelIfNecessary(viewModel);
var window = view as Window;
if (window == null)
{
var e = new ArgumentException(String.Format("Tried to show {0} as a window, but it isn't a Window", view == null ? "(null)" : view.GetType().Name));
var e = new ArgumentException(String.Format("WindowManager.ShowWindow or .ShowDialog tried to show a View of type '{0}', but that View doesn't derive from the Window class. " +
"Make sure any Views you display derive from Window (not UserControl, etc)", view == null ? "(null)" : view.GetType().Name));
logger.Error(e);
throw e;
}

View File

@ -119,7 +119,6 @@ namespace Stylet.Xaml
}
// Stop someone from instantiating us
private View()
{ }
private View() { }
}
}

View File

@ -20,10 +20,15 @@ namespace StyletUnitTests
this.C1Func = c1Func;
}
}
private interface I1 { }
public interface I1 { }
private class C11 : I1 { }
private class C12 : I1 { }
public interface I1Factory
{
I1 GetI1();
}
[Test]
public void FuncFactoryWorksForGetNoKey()
{
@ -101,5 +106,19 @@ namespace StyletUnitTests
Assert.IsInstanceOf<C11>(funcCollection[0]());
Assert.IsInstanceOf<C12>(funcCollection[1]());
}
[Test]
public void FuncFactoryOfAbstractFactoryWorksAsExpected()
{
var builder = new StyletIoCBuilder();
builder.Bind<I1>().To<C11>();
builder.Bind<I1Factory>().ToAbstractFactory();
var ioc = builder.BuildContainer();
var func = ioc.Get<Func<I1Factory>>();
Assert.IsNotNull(func);
var i1 = func().GetI1();
Assert.IsInstanceOf<C11>(i1);
}
}
}

View File

@ -185,22 +185,22 @@ namespace StyletUnitTests
}
[Test]
public void CreateViewForModelThrowsIfViewIsNotConcreteUIElement()
public void CreateViewForModelIfNecessaryThrowsIfViewIsNotConcreteUIElement()
{
var viewManager = new LocatingViewManager(this.viewManagerConfig.Object);
viewManager.LocatedViewType = typeof(I1);
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModel(new object()));
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModelIfNecessary(new object()));
viewManager.LocatedViewType = typeof(AC1);
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModel(new object()));
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModelIfNecessary(new object()));
viewManager.LocatedViewType = typeof(C1);
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModel(new object()));
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModelIfNecessary(new object()));
}
[Test]
public void CreateViewForModelCallsFetchesViewAndCallsInitializeComponent()
public void CreateAndBindViewForModelIfNecessaryCallsFetchesViewAndCallsInitializeComponent()
{
var view = new TestView();
var config = new Mock<IViewManagerConfig>();
@ -208,12 +208,24 @@ namespace StyletUnitTests
var viewManager = new LocatingViewManager(config.Object);
viewManager.LocatedViewType = typeof(TestView);
var returnedView = viewManager.CreateAndBindViewForModel(new object());
var returnedView = viewManager.CreateAndBindViewForModelIfNecessary(new object());
Assert.True(view.InitializeComponentCalled);
Assert.AreEqual(view, returnedView);
}
[Test]
public void CreateAndBindViewForModelReturnsViewIfAlreadySet()
{
var view = new TestView();
var viewModel = new Mock<IViewAware>();
viewModel.SetupGet(x => x.View).Returns(view);
var returnedView = this.viewManager.CreateAndBindViewForModelIfNecessary(viewModel.Object);
Assert.AreEqual(view, returnedView);
}
[Test]
public void CreateViewForModelDoesNotComplainIfNoInitializeComponentMethod()
{
@ -223,7 +235,7 @@ namespace StyletUnitTests
var viewManager = new LocatingViewManager(config.Object);
viewManager.LocatedViewType = typeof(UIElement);
var returnedView = viewManager.CreateAndBindViewForModel(new object());
var returnedView = viewManager.CreateAndBindViewForModelIfNecessary(new object());
Assert.AreEqual(view, returnedView);
}
@ -261,6 +273,8 @@ namespace StyletUnitTests
model.Verify(x => x.AttachView(view));
}
[Test]
public void ViewNameResolutionWorksAsExpected()
{

View File

@ -76,7 +76,7 @@ namespace StyletUnitTests
public void CreateWindowAsksViewManagerForView()
{
var model = new object();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Verifiable();
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Verifiable();
// Don't care if this throws - that's OK
try { this.windowManager.CreateWindow(model, false); }
catch (Exception) { }
@ -87,7 +87,7 @@ namespace StyletUnitTests
public void CreateWindowThrowsIfViewIsntAWindow()
{
var model = new object();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(new UIElement());
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(new UIElement());
Assert.Throws<ArgumentException>(() => this.windowManager.CreateWindow(model, false));
}
@ -96,7 +96,7 @@ namespace StyletUnitTests
{
var model = new Screen();
var window = new Window();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
@ -112,7 +112,7 @@ namespace StyletUnitTests
var model = new Screen();
var window = new Window();
window.Title = "Foo";
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
@ -128,7 +128,7 @@ namespace StyletUnitTests
var window = new Window();
var binding = new Binding("Test") { Mode = BindingMode.TwoWay };
window.SetBinding(Window.TitleProperty, binding);
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
@ -140,7 +140,7 @@ namespace StyletUnitTests
public void CreateWindowActivatesViewModel()
{
var model = new Mock<IScreen>();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(new Window());
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model.Object)).Returns(new Window());
this.windowManager.CreateWindow(model.Object, false);
model.Verify(x => x.Activate());
}
@ -150,7 +150,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
window.WindowState = WindowState.Maximized;
window.OnStateChanged(EventArgs.Empty);
@ -162,7 +162,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
window.WindowState = WindowState.Normal;
window.OnStateChanged(EventArgs.Empty);
@ -174,7 +174,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
window.WindowState = WindowState.Minimized;
window.OnStateChanged(EventArgs.Empty);
@ -186,7 +186,7 @@ namespace StyletUnitTests
{
var model = new Screen();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
window.OnClosing(new CancelEventArgs(true));
}
@ -196,7 +196,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
model.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(false));
var ea = new CancelEventArgs();
@ -209,7 +209,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
model.Setup(x => x.CanCloseAsync()).Returns(Task.FromResult(true));
var ea = new CancelEventArgs();
@ -222,7 +222,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(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();
@ -235,7 +235,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(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();
@ -248,7 +248,7 @@ namespace StyletUnitTests
{
var model = new Mock<IMyScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model.Object)).Returns(window);
this.windowManager.CreateWindow(model.Object, false);
var tcs = new TaskCompletionSource<bool>();
model.Setup(x => x.CanCloseAsync()).Returns(tcs.Task);
@ -268,7 +268,7 @@ namespace StyletUnitTests
{
var model = new Screen();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
((IChildDelegate)model.Parent).CloseItem(new object());
}
@ -278,7 +278,7 @@ namespace StyletUnitTests
{
var model = new Mock<IScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(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);
@ -292,7 +292,7 @@ namespace StyletUnitTests
{
var model = new Mock<IMyScreen>();
var window = new MyWindow();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model.Object)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(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);
@ -320,7 +320,7 @@ namespace StyletUnitTests
{
var model = new object();
var window = new Window();
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
@ -333,7 +333,7 @@ namespace StyletUnitTests
var model = new object();
var window = new Window();
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
@ -346,7 +346,7 @@ namespace StyletUnitTests
var model = new object();
var window = new Window();
window.Left = 1;
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);
@ -359,7 +359,7 @@ namespace StyletUnitTests
var model = new object();
var window = new Window();
window.Top = 1;
this.viewManager.Setup(x => x.CreateAndBindViewForModel(model)).Returns(window);
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.windowManager.CreateWindow(model, false);