Merge branch 'release/1.1.7'

This commit is contained in:
Antony Male 2015-09-09 12:48:42 +01:00
commit 08a9178b34
28 changed files with 291 additions and 127 deletions

View File

@ -18,19 +18,12 @@ namespace Bootstrappers
protected override void ConfigureBootstrapper()
{
this.Configure();
var builder = new ContainerBuilder();
this.DefaultConfigureIoC(builder);
this.ConfigureIoC(builder);
this.container = builder.Build();
}
/// <summary>
/// Override to configure anything that needs configuring
/// </summary>
protected virtual void Configure() { }
/// <summary>
/// Carries out default configuration of the IoC container. Override if you don't want to do this
/// </summary>
@ -38,6 +31,7 @@ namespace Bootstrappers
{
builder.RegisterInstance<IViewManagerConfig>(this);
builder.RegisterType<ViewManager>().As<IViewManager>().SingleInstance();
builder.RegisterInstance<IWindowManagerConfig>(this);
builder.RegisterType<WindowManager>().As<IWindowManager>().SingleInstance();
builder.RegisterType<EventAggregator>().As<IEventAggregator>().SingleInstance();
builder.RegisterType<MessageBoxViewModel>().As<IMessageBoxViewModel>(); // Not singleton!

View File

@ -19,18 +19,11 @@ namespace Bootstrappers
protected override void ConfigureBootstrapper()
{
this.Configure();
this.container = new WindsorContainer();
this.DefaultConfigureIoC(this.container);
this.ConfigureIoC(this.container);
}
/// <summary>
/// Override to configure anything that needs configuring
/// </summary>
protected virtual void Configure() { }
/// <summary>
/// Carries out default configuration of the IoC container. Override if you don't want to do this
/// </summary>
@ -38,7 +31,7 @@ namespace Bootstrappers
{
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<IViewManagerConfig>().Instance(this),
Component.For<IViewManagerConfig, IWindowManagerConfig>().Instance(this),
Component.For<IViewManager>().ImplementedBy<ViewManager>().LifestyleSingleton(),
Component.For<IWindowManager>().ImplementedBy<WindowManager>().LifestyleSingleton(),
Component.For<IEventAggregator>().ImplementedBy<EventAggregator>().LifestyleSingleton(),

View File

@ -17,18 +17,11 @@ namespace Bootstrappers
protected override void ConfigureBootstrapper()
{
this.Configure();
this.kernel = new StandardKernel();
this.DefaultConfigureIoC(this.kernel);
this.ConfigureIoC(this.kernel);
}
/// <summary>
/// Override to configure anything that needs configuring
/// </summary>
protected virtual void Configure() { }
/// <summary>
/// Carries out default configuration of the IoC container. Override if you don't want to do this
/// </summary>
@ -36,7 +29,8 @@ namespace Bootstrappers
{
kernel.Bind<IViewManagerConfig>().ToConstant(this);
kernel.Bind<IViewManager>().To<ViewManager>().InSingletonScope();
kernel.Bind<IWindowManager>().ToMethod(c => new WindowManager(c.Kernel.Get<IViewManager>(), () => c.Kernel.Get<IMessageBoxViewModel>())).InSingletonScope();
kernel.Bind<IWindowManagerConfig>().ToConstant(this);
kernel.Bind<IWindowManager>().ToMethod(c => new WindowManager(c.Kernel.Get<IViewManager>(), () => c.Kernel.Get<IMessageBoxViewModel>(), c.Kernel.Get<IWindowManagerConfig>())).InSingletonScope();
kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
kernel.Bind<IMessageBoxViewModel>().To<MessageBoxViewModel>(); // Not singleton!
}

View File

@ -10,22 +10,16 @@ namespace Bootstrappers
protected override void ConfigureBootstrapper()
{
this.Configure();
this.DefaultConfigureContainer();
this.ConfigureContainer();
}
/// <summary>
/// Override to configure anything that needs configuring
/// </summary>
protected virtual void Configure() { }
protected virtual void DefaultConfigureContainer()
{
var viewManager = new ViewManager(this);
this.Container.Add(typeof(IViewManager), () => viewManager);
var windowManager = new WindowManager(viewManager, () => (IMessageBoxViewModel)this.Container[typeof(IMessageBoxViewModel)]());
var windowManager = new WindowManager(viewManager, () => (IMessageBoxViewModel)this.Container[typeof(IMessageBoxViewModel)](), this);
this.Container.Add(typeof(IWindowManager), () => windowManager);
var eventAggregator = new EventAggregator();

View File

@ -18,8 +18,6 @@ namespace Bootstrappers
protected override void ConfigureBootstrapper()
{
this.Configure();
this.container = new Container(config =>
{
this.DefaultConfigureIoC(config);
@ -27,11 +25,6 @@ namespace Bootstrappers
});
}
/// <summary>
/// Override to configure anything that needs configuring
/// </summary>
protected virtual void Configure() { }
/// <summary>
/// Carries out default configuration of the IoC container. Override if you don't want to do this
/// </summary>
@ -39,6 +32,7 @@ namespace Bootstrappers
{
config.For<IViewManagerConfig>().Add(this);
config.For<IViewManager>().Add<ViewManager>().LifecycleIs<SingletonLifecycle>();
config.For<IWindowManagerConfig>().Add(this);
config.For<IWindowManager>().Add<WindowManager>().LifecycleIs<SingletonLifecycle>();
config.For<IEventAggregator>().Add<EventAggregator>().LifecycleIs<SingletonLifecycle>();
config.For<IMessageBoxViewModel>().Add<MessageBoxViewModel>().LifecycleIs<UniquePerRequestLifecycle>();

View File

@ -17,18 +17,11 @@ namespace Bootstrappers
protected override void ConfigureBootstrapper()
{
this.Configure();
this.container = new UnityContainer();
this.DefaultConfigureIoC(this.container);
this.ConfigureIoC(this.container);
}
/// <summary>
/// Override to configure anything that needs configuring
/// </summary>
protected virtual void Configure() { }
/// <summary>
/// Carries out default configuration of the IoC container. Override if you don't want to do this
/// </summary>
@ -38,7 +31,7 @@ namespace Bootstrappers
// This is a workaround
var viewManager = new ViewManager(this);
container.RegisterInstance<IViewManager>(viewManager);
container.RegisterInstance<IWindowManager>(new WindowManager(viewManager, () => container.Resolve<IMessageBoxViewModel>()));
container.RegisterInstance<IWindowManager>(new WindowManager(viewManager, () => container.Resolve<IMessageBoxViewModel>(), this));
container.RegisterInstance<IEventAggregator>(new EventAggregator());
container.RegisterType<IMessageBoxViewModel, MessageBoxViewModel>(new PerResolveLifetimeManager());
container.RegisterTypes(AllClasses.FromAssemblies(this.Assemblies), WithMappings.None, WithName.Default, WithLifetime.PerResolve);

View File

@ -1,6 +1,13 @@
Stylet Changelog
================
v1.1.7
------
- Fix issue where OnInitialActivate may not be invoked if Screen was deactivated before being activated
- Action s:Action to be instantiated with zero parameters
- Make Stylet more modular by removing dependency on Application.Current from WindowManager, and View.ViewManager static property
v1.1.6
------

BIN
NuGet/GitLink.exe Normal file

Binary file not shown.

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
<metadata>
<id>Stylet</id>
<version>1.1.6</version>
<version>1.1.7</version>
<title>Stylet</title>
<authors>Antony Male</authors>
<owners>Antony Male</owners>
@ -18,9 +18,8 @@
<files>
<file src="..\Stylet\bin\Release\Stylet.dll" target="lib\net45"/>
<file src="..\Stylet\bin\Release\Stylet.pdb" target="lib\net45"/>
<file src="..\Stylet\bin\Release\Stylet.pdb.srcsrv" target="lib\net45"/>
<file src="..\Stylet\bin\Release\Stylet.xml" target="lib\net45"/>
<file src="..\Stylet\**\*.cs" target="src"/>
<file src="..\Stylet\**\*.xaml" target="src"/>
<file src="tools\**.*" target="tools"/>
<file src="content\**.*" target="content"/>
</files>

View File

@ -46,14 +46,11 @@ Don't forget to right-click your solution, and click "Enable NuGet package resto
I also publish symbols on [SymbolSource](http://www.symbolsource.org/Public), so you can use the NuGet package but still have access to Stylet's source when debugging. If you haven't yet set up Visual Studio to use SymbolSource, do that now:
In Visual Studio, go to Debug -> Options and Settings, and make the following changes:
Go to Debug -> Options and Settings -> General, and make the following changes:
1. In General
1. Turn **off** "Enable Just My Code"
2. Turn **off** "Enable .NET Framework source stepping". Yes, it is misleading, but if you don't, then Visual Studio will ignore your custom server order and only use its own servers.
3. Turn **on** "Enable source server support". You may have to OK a security warning.
2. In Symbols
1. Add "http://srv.symbolsource.org/pdb/Public" to the list.
1. Turn **off** "Enable Just My Code"
2. Turn **off** "Enable .NET Framework source stepping". Yes, it is misleading, but if you don't, then Visual Studio will ignore your custom server order and only use its own servers.
3. Turn **on** "Enable source server support". You may have to OK a security warning.
### Source

View File

@ -10,6 +10,11 @@ CONFIG = ENV['CONFIG'] || 'Debug'
COVERAGE_DIR = 'Coverage'
COVERAGE_FILE = File.join(COVERAGE_DIR, 'coverage.xml')
GITLINK_REMOTE = 'https://github.com/canton7/stylet'
NUSPEC = 'NuGet/Stylet.nuspec'
ASSEMBLY_INFO = 'Stylet/Properties/AssemblyInfo.cs'
directory COVERAGE_DIR
desc "Build Stylet.sln using the current CONFIG (or Debug)"
@ -96,6 +101,31 @@ def coverage(coverage_files)
sh REPORT_GENERATOR, %Q{-reports:"#{coverage_files.join(';')}" "-targetdir:#{COVERAGE_DIR}"}
end
desc "Create NuGet package"
task :package do
local_hash = `git rev-parse HEAD`.chomp
sh "NuGet/GitLink.exe . -s #{local_hash} -u #{GITLINK_REMOTE} -f Stylet.sln -ignore StyletUnitTests,StyletIntegrationTests"
Dir.chdir(File.dirname(NUSPEC)) do
sh "nuget.exe pack #{File.basename(NUSPEC)}"
end
end
desc "Bump version number"
task :version, [:version] do |t, args|
parts = args[:version].split('.')
parts << '0' if parts.length == 3
version = parts.join('.')
content = IO.read(ASSEMBLY_INFO)
content[/^\[assembly: AssemblyVersion\(\"(.+?)\"\)\]/, 1] = version
content[/^\[assembly: AssemblyFileVersion\(\"(.+?)\"\)\]/, 1] = version
File.open(ASSEMBLY_INFO, 'w'){ |f| f.write(content) }
content = IO.read(NUSPEC)
content[/<version>(.+?)<\/version>/, 1] = args[:version]
File.open(NUSPEC, 'w'){ |f| f.write(content) }
end
desc "Extract StyletIoC as a standalone file"
task :"extract-stylet-ioc" do
filenames = Dir['Stylet/StyletIoC/**/*.cs']
@ -139,3 +169,4 @@ task :"extract-stylet-ioc" do
# puts merged_contents
end

View File

@ -40,15 +40,8 @@ namespace Stylet
this.DefaultConfigureIoC(builder);
this.Container = builder.BuildContainer();
this.Configure();
}
/// <summary>
/// Hook called after the IoC container has been set up
/// </summary>
protected virtual void Configure() { }
/// <summary>
/// Carries out default configuration of StyletIoC. Override if you don't want to do this
/// </summary>
@ -57,6 +50,7 @@ namespace Stylet
{
// Mark these as auto-bindings, so the user can replace them if they want
builder.Bind<IViewManagerConfig>().ToInstance(this).AsWeakBinding();
builder.Bind<IWindowManagerConfig>().ToInstance(this).AsWeakBinding();
builder.Bind<IViewManager>().To<ViewManager>().InSingletonScope().AsWeakBinding();
builder.Bind<IWindowManager>().To<WindowManager>().InSingletonScope().AsWeakBinding();
builder.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope().AsWeakBinding();

View File

@ -2,7 +2,7 @@
using Stylet.Xaml;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Threading;
@ -12,7 +12,7 @@ namespace Stylet
/// <summary>
/// Bootstrapper to be extended by applications which don't want to use StyletIoC as the IoC container.
/// </summary>
public abstract class BootstrapperBase : IBootstrapper, IViewManagerConfig, IDisposable
public abstract class BootstrapperBase : IBootstrapper, IViewManagerConfig, IWindowManagerConfig, IDisposable
{
/// <summary>
/// Gets the current application
@ -85,12 +85,20 @@ namespace Stylet
this.ConfigureBootstrapper();
View.ViewManager = (IViewManager)this.GetInstance(typeof(IViewManager));
// Cater for the unit tests, which can't sensibly stub Application
if (this.Application != null)
this.Application.Resources.Add(View.ViewManagerResourceKey, this.GetInstance(typeof(IViewManager)));
this.Configure();
this.Launch();
this.OnLaunch();
}
/// <summary>
/// Hook called after the IoC container has been set up
/// </summary>
protected virtual void Configure() { }
/// <summary>
/// Launch the root view
/// </summary>
@ -100,6 +108,15 @@ namespace Stylet
windowManager.ShowWindow(this.RootViewModel);
}
/// <summary>
/// Returns the currently-displayed window, or null if there is none (or it can't be determined)
/// </summary>
/// <returns>The currently-displayed window, or null</returns>
public virtual Window GetActiveWindow()
{
return this.Application.Windows.OfType<Window>().FirstOrDefault(x => x.IsActive) ?? this.Application.MainWindow;
}
/// <summary>
/// Override to configure your IoC container, and anything else
/// </summary>

View File

@ -110,7 +110,9 @@ namespace Stylet
{
// We've already been deactivated by this point
foreach (var item in this.items)
{
this.CloseAndCleanUp(item, this.DisposeChildren);
}
this.items.Clear();
}

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.6.0")]
[assembly: AssemblyFileVersion("1.1.6.0")]
[assembly: AssemblyVersion("1.1.7.0")]
[assembly: AssemblyFileVersion("1.1.7.0")]

View File

@ -71,6 +71,8 @@ namespace Stylet
get { return this.State == ScreenState.Active; }
}
private bool haveActivated = false;
/// <summary>
/// Raised when the Screen's state changed, for any reason
/// </summary>
@ -146,9 +148,12 @@ namespace Stylet
{
this.SetState(ScreenState.Active, (oldState, newState) =>
{
var isInitialActivate = oldState == ScreenState.Initial;
if (isInitialActivate)
bool isInitialActivate = !this.haveActivated;
if (!this.haveActivated)
{
this.OnInitialActivate();
this.haveActivated = true;
}
this.OnActivate();
@ -174,7 +179,7 @@ namespace Stylet
[SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "As this is a framework type, don't want to make it too easy for users to call this method")]
void IScreenState.Close()
{
// Avoid going from Closed back to Deactivated
// Avoid going from Activated -> Closed without going via Deactivated
if (this.State != ScreenState.Closed)
((IScreenState)this).Deactivate();

View File

@ -20,7 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRuleSet>Stylet.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\Debug\Stylet.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@ -31,6 +31,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Stylet.xml</DocumentationFile>
<CodeAnalysisRuleSet>Stylet.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
@ -140,6 +141,9 @@
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<None Include="Stylet.ruleset" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

70
Stylet/Stylet.ruleset Normal file
View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for Stylet" Description="Code analysis rules for Stylet.csproj." ToolsVersion="14.0">
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1001" Action="Warning" />
<Rule Id="CA1009" Action="Warning" />
<Rule Id="CA1016" Action="Warning" />
<Rule Id="CA1033" Action="Warning" />
<Rule Id="CA1049" Action="Warning" />
<Rule Id="CA1060" Action="Warning" />
<Rule Id="CA1061" Action="Warning" />
<Rule Id="CA1063" Action="Warning" />
<Rule Id="CA1065" Action="Warning" />
<Rule Id="CA1301" Action="Warning" />
<Rule Id="CA1400" Action="Warning" />
<Rule Id="CA1401" Action="Warning" />
<Rule Id="CA1403" Action="Warning" />
<Rule Id="CA1404" Action="Warning" />
<Rule Id="CA1405" Action="Warning" />
<Rule Id="CA1410" Action="Warning" />
<Rule Id="CA1415" Action="Warning" />
<Rule Id="CA1821" Action="Warning" />
<Rule Id="CA1900" Action="Warning" />
<Rule Id="CA1901" Action="Warning" />
<Rule Id="CA2002" Action="Warning" />
<Rule Id="CA2100" Action="Warning" />
<Rule Id="CA2101" Action="Warning" />
<Rule Id="CA2108" Action="Warning" />
<Rule Id="CA2111" Action="Warning" />
<Rule Id="CA2112" Action="Warning" />
<Rule Id="CA2114" Action="Warning" />
<Rule Id="CA2116" Action="Warning" />
<Rule Id="CA2117" Action="Warning" />
<Rule Id="CA2122" Action="Warning" />
<Rule Id="CA2123" Action="Warning" />
<Rule Id="CA2124" Action="Warning" />
<Rule Id="CA2126" Action="Warning" />
<Rule Id="CA2131" Action="Warning" />
<Rule Id="CA2132" Action="Warning" />
<Rule Id="CA2133" Action="Warning" />
<Rule Id="CA2134" Action="Warning" />
<Rule Id="CA2137" Action="Warning" />
<Rule Id="CA2138" Action="Warning" />
<Rule Id="CA2140" Action="Warning" />
<Rule Id="CA2141" Action="Warning" />
<Rule Id="CA2146" Action="Warning" />
<Rule Id="CA2147" Action="Warning" />
<Rule Id="CA2149" Action="Warning" />
<Rule Id="CA2200" Action="Warning" />
<Rule Id="CA2202" Action="Warning" />
<Rule Id="CA2207" Action="Warning" />
<Rule Id="CA2212" Action="Warning" />
<Rule Id="CA2213" Action="Warning" />
<Rule Id="CA2214" Action="Warning" />
<Rule Id="CA2216" Action="Warning" />
<Rule Id="CA2220" Action="Warning" />
<Rule Id="CA2229" Action="Warning" />
<Rule Id="CA2231" Action="Warning" />
<Rule Id="CA2232" Action="Warning" />
<Rule Id="CA2235" Action="Warning" />
<Rule Id="CA2236" Action="Warning" />
<Rule Id="CA2237" Action="Warning" />
<Rule Id="CA2238" Action="Warning" />
<Rule Id="CA2240" Action="Warning" />
<Rule Id="CA2241" Action="Warning" />
<Rule Id="CA2242" Action="Warning" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.Features" RuleNamespace="Microsoft.CodeAnalysis.CSharp.Features">
<Rule Id="IDE0003" Action="None" />
</Rules>
</RuleSet>

View File

@ -288,7 +288,7 @@ namespace Stylet
public class StyletInvalidViewTypeException : Exception
{
/// <summary>
/// Initialises a new instance of the <see cref="StyletInvalidViewException"/> class
/// Initialises a new instance of the <see cref="StyletInvalidViewTypeException"/> class
/// </summary>
/// <param name="message">Message associated with the Exception</param>
public StyletInvalidViewTypeException(string message)

View File

@ -47,6 +47,18 @@ namespace Stylet
IDictionary<MessageBoxResult, string> buttonLabels = null);
}
/// <summary>
/// Configuration passed to WindowManager (normally implemented by BootstrapperBase)
/// </summary>
public interface IWindowManagerConfig
{
/// <summary>
/// Returns the currently-displayed window, or null if there is none (or it can't be determined)
/// </summary>
/// <returns>The currently-displayed window, or null</returns>
Window GetActiveWindow();
}
/// <summary>
/// Default implementation of IWindowManager, is capable of showing a ViewModel's View as a dialog or a window
/// </summary>
@ -55,16 +67,19 @@ namespace Stylet
private static readonly ILogger logger = LogManager.GetLogger(typeof(WindowManager));
private readonly IViewManager viewManager;
private readonly Func<IMessageBoxViewModel> messageBoxViewModelFactory;
private readonly Func<Window> getActiveWindow;
/// <summary>
/// Initialises a new instance of the <see cref="WindowManager"/> class, using the given <see cref="IViewManager"/>
/// </summary>
/// <param name="viewManager">IViewManager to use when creating views</param>
/// <param name="messageBoxViewModelFactory">Delegate which returns a new IMessageBoxViewModel instance when invoked</param>
public WindowManager(IViewManager viewManager, Func<IMessageBoxViewModel> messageBoxViewModelFactory)
/// <param name="config">Configuration object</param>
public WindowManager(IViewManager viewManager, Func<IMessageBoxViewModel> messageBoxViewModelFactory, IWindowManagerConfig config)
{
this.viewManager = viewManager;
this.messageBoxViewModelFactory = messageBoxViewModelFactory;
this.getActiveWindow = config.GetActiveWindow;
}
/// <summary>
@ -178,10 +193,7 @@ namespace Stylet
private Window InferOwnerOf(Window window)
{
if (Application.Current == null)
return null;
var active = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.IsActive) ?? Application.Current.MainWindow;
var active = this.getActiveWindow();
return ReferenceEquals(active, window) ? null : active;
}

View File

@ -40,6 +40,7 @@ namespace Stylet.Xaml
/// <summary>
/// Gets or sets the name of the method to call
/// </summary>
[ConstructorArgument("method")]
public string Method { get; set; }
/// <summary>
@ -55,6 +56,13 @@ namespace Stylet.Xaml
/// <summary>
/// Initialises a new instance of the <see cref="ActionExtension"/> class
/// </summary>
public ActionExtension()
{
}
/// <summary>
/// Initialises a new instance of the <see cref="ActionExtension"/> class with the given method name
/// </summary>
/// <param name="method">Name of the method to call</param>
public ActionExtension(string method)
{
@ -88,6 +96,9 @@ namespace Stylet.Xaml
/// <returns>The object value to set on the property where the extension is applied.</returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.Method == null)
throw new InvalidOperationException("Method has not been set");
var valueService = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
// Seems this is the case when we're in a template. We'll get called again properly in a second.

View File

@ -3,27 +3,26 @@ using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Reflection;
namespace Stylet.Xaml
{
/// <summary>
/// Holds attached properties relating to various bits of the View which are used by Stylet
/// </summary>
public class View : DependencyObject
public static class View
{
/// <summary>
/// Key which will be used to retrieve the ViewManager associated with the current application, from application's resources
/// </summary>
public const string ViewManagerResourceKey = "b9a38199-8cb3-4103-8526-c6cfcd089df7";
/// <summary>
/// Initial value of the ActionTarget property.
/// This can be used as a marker - if the property has this value, it hasn't yet been assigned to anything else.
/// </summary>
public static readonly object InitialActionTarget = new object();
private static readonly ContentPropertyAttribute defaultContentProperty = new ContentPropertyAttribute("Content");
/// <summary>
/// Gets or sets the <see cref="IViewManager"/> to be used. This should be set by the Bootstrapper.
/// </summary>
public static IViewManager ViewManager { get; set; }
/// <summary>
/// Get the ActionTarget associated with the given object
/// </summary>
@ -78,7 +77,9 @@ namespace Stylet.Xaml
public static readonly DependencyProperty ModelProperty =
DependencyProperty.RegisterAttached("Model", typeof(object), typeof(View), new PropertyMetadata(defaultModelValue, (d, e) =>
{
if (ViewManager == null)
var viewManager = ((FrameworkElement)d).TryFindResource(ViewManagerResourceKey) as IViewManager;
if (viewManager == null)
{
if (Execute.InDesignMode)
{
@ -94,14 +95,14 @@ namespace Stylet.Xaml
}
else
{
throw new InvalidOperationException("View.ViewManager is unassigned. This should have been set by the Bootstrapper");
throw new InvalidOperationException("The ViewManager resource is unassigned. This should have been set by the Bootstrapper");
}
}
else
{
// It appears we can be reset to the default value on destruction
var newValue = e.NewValue == defaultModelValue ? null : e.NewValue;
ViewManager.OnModelChanged(d, e.OldValue, newValue);
viewManager.OnModelChanged(d, e.OldValue, newValue);
}
}));
@ -113,12 +114,13 @@ namespace Stylet.Xaml
public static void SetContentProperty(DependencyObject targetLocation, UIElement view)
{
var type = targetLocation.GetType();
var contentProperty = Attribute.GetCustomAttributes(type, true).OfType<ContentPropertyAttribute>().FirstOrDefault() ?? defaultContentProperty;
type.GetProperty(contentProperty.Name).SetValue(targetLocation, view, null);
var attribute = type.GetCustomAttribute<ContentPropertyAttribute>();
// No attribute? Try a property called 'Content'...
string propertyName = attribute != null ? attribute.Name : "Content";
var property = type.GetProperty(propertyName);
if (property == null)
throw new InvalidOperationException(String.Format("Unable to find a Content property on type {0}. Make sure you're using 's:View.Model' on a suitable container, e.g. a ContentControl", type.Name));
property.SetValue(targetLocation, view);
}
// Stop someone from instantiating us
private View() { }
}
}

View File

@ -129,13 +129,6 @@ namespace StyletUnitTests
Assert.True(this.bootstrapper.ConfigureCalled);
}
[Test]
public void StartAssignsViewManager()
{
this.bootstrapper.Start(new string[0]);
Assert.AreEqual(View.ViewManager, this.viewManager.Object);
}
[Test]
public void StartAssignsArgs()
{

View File

@ -147,6 +147,8 @@ namespace StyletUnitTests
[Test]
public void ActivateFiresCorrectEvents()
{
// Get the initial activate out of the way
((IScreenState)this.screen).Activate();
this.screen.SetState(ScreenState.Deactivated, (n, o) => { });
var changedEventArgs = new List<ScreenStateChangedEventArgs>();
@ -413,5 +415,13 @@ namespace StyletUnitTests
var screen = new MyScreen(adapter.Object);
Assert.AreEqual(adapter.Object, screen.Validator);
}
[Test]
public void InitialActivateFiredWhenComingFromDeactivated()
{
((IScreenState)this.screen).Deactivate();
((IScreenState)this.screen).Activate();
Assert.True(this.screen.OnInitialActivateCalled);
}
}
}

View File

@ -32,11 +32,29 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.2.1402.2112\lib\net40\Moq.dll</HintPath>
<Reference Include="Moq, Version=4.2.1507.118, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
<Reference Include="nunit.core, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<HintPath>..\packages\NUnitTestAdapter.2.0.0\lib\nunit.core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.core.interfaces, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<HintPath>..\packages\NUnitTestAdapter.2.0.0\lib\nunit.core.interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.util, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<HintPath>..\packages\NUnitTestAdapter.2.0.0\lib\nunit.util.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NUnit.VisualStudio.TestAdapter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=4cb40d35494691ac, processorArchitecture=MSIL">
<HintPath>..\packages\NUnitTestAdapter.2.0.0\lib\NUnit.VisualStudio.TestAdapter.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />

View File

@ -35,7 +35,6 @@ namespace StyletUnitTests
public void SetUp()
{
this.viewManager = new Mock<IViewManager>();
View.ViewManager = this.viewManager.Object;
}
[TearDown]
@ -55,7 +54,8 @@ namespace StyletUnitTests
[Test]
public void ModelStores()
{
var obj = new DependencyObject();
var obj = new FrameworkElement();
obj.Resources.Add(View.ViewManagerResourceKey, this.viewManager.Object);
View.SetModel(obj, 5);
Assert.AreEqual(5, View.GetModel(obj));
}
@ -63,7 +63,8 @@ namespace StyletUnitTests
[Test]
public void ChangingModelCallsOnModelChanged()
{
var obj = new DependencyObject();
var obj = new FrameworkElement();
obj.Resources.Add(View.ViewManagerResourceKey, this.viewManager.Object);
var model = new object();
View.SetModel(obj, null);
@ -92,11 +93,19 @@ namespace StyletUnitTests
Assert.AreEqual(obj.Content, view);
}
[Test]
public void SetContentControlThrowsIfNoContentProperty()
{
var obj = new DependencyObject();
var view = new UIElement();
Assert.Throws<InvalidOperationException>(() => View.SetContentProperty(obj, view));
}
[Test]
public void SettingModelThrowsExceptionIfViewManagerNotSet()
{
View.ViewManager = null;
var view = new UIElement();
var view = new FrameworkElement();
Assert.Throws<InvalidOperationException>(() => View.SetModel(view, new object()));
}
@ -104,7 +113,6 @@ namespace StyletUnitTests
public void InDesignModeSettingViewModelWithBrokenBindingGivesAppropriateMessage()
{
Execute.InDesignMode = true;
View.ViewManager = null;
var element = new ContentControl();
// Don't set View.Model to a binding - just a random object
@ -120,7 +128,6 @@ namespace StyletUnitTests
public void InDesignModeSettingViewModelWithCollectionBindingGivesAppropriateMessage()
{
Execute.InDesignMode = true;
View.ViewManager = null;
var element = new ContentControl();
var vm = new TestViewModel();
@ -139,7 +146,6 @@ namespace StyletUnitTests
public void InDesignModeSettingViewModelWithGoodBindingGivesAppropriateMessage()
{
Execute.InDesignMode = true;
View.ViewManager = null;
var element = new ContentControl();
var vm = new TestViewModel();

View File

@ -22,7 +22,8 @@ namespace StyletUnitTests
private class MyWindowManager : WindowManager
{
public MyWindowManager(IViewManager viewManager, Func<IMessageBoxViewModel> messageBoxViewModelFactory) : base(viewManager, messageBoxViewModelFactory) { }
public MyWindowManager(IViewManager viewManager, Func<IMessageBoxViewModel> messageBoxViewModelFactory, IWindowManagerConfig config)
: base(viewManager, messageBoxViewModelFactory, config) { }
public new Window CreateWindow(object viewModel, bool isDialog)
{
@ -32,7 +33,8 @@ namespace StyletUnitTests
private class WindowManagerWithoutCreateWindow : WindowManager
{
public WindowManagerWithoutCreateWindow(IViewManager viewManager, Func<IMessageBoxViewModel> messageBoxViewModelFactory) : base(viewManager, messageBoxViewModelFactory) { }
public WindowManagerWithoutCreateWindow(IViewManager viewManager, Func<IMessageBoxViewModel> messageBoxViewModelFactory, IWindowManagerConfig config)
: base(viewManager, messageBoxViewModelFactory, config) { }
protected override Window CreateWindow(object viewModel, bool isDialog)
{
@ -62,6 +64,7 @@ namespace StyletUnitTests
private Mock<IViewManager> viewManager;
private Mock<IMessageBoxViewModel> messageBoxViewModel;
private Mock<IWindowManagerConfig> config;
private MyWindowManager windowManager;
[SetUp]
@ -69,7 +72,8 @@ namespace StyletUnitTests
{
this.viewManager = new Mock<IViewManager>();
this.messageBoxViewModel = new Mock<IMessageBoxViewModel>();
this.windowManager = new MyWindowManager(this.viewManager.Object, () => this.messageBoxViewModel.Object);
this.config = new Mock<IWindowManagerConfig>();
this.windowManager = new MyWindowManager(this.viewManager.Object, () => this.messageBoxViewModel.Object, this.config.Object);
}
[Test]
@ -238,20 +242,21 @@ namespace StyletUnitTests
var window = new MyWindow();
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));
model.Setup(x => x.CanCloseAsync()).Returns(Task.Delay(10).ContinueWith(t => false));
var ea = new CancelEventArgs();
window.OnClosing(ea);
Assert.True(ea.Cancel);
}
[Test]
public void WindowClosingCancelsIfCanCloseAsyncReturnsAsynchronousTrue()
public void WindowClosingCancelsIfCanCloseAsyncReturnsAsynchronous()
{
var model = new Mock<IScreen>();
var window = new MyWindow();
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 tcs = new TaskCompletionSource<bool>();
model.Setup(x => x.CanCloseAsync()).Returns(tcs.Task);
var ea = new CancelEventArgs();
window.OnClosing(ea);
Assert.True(ea.Cancel);
@ -297,7 +302,7 @@ namespace StyletUnitTests
model.SetupSet(x => x.Parent = It.IsAny<object>()).Callback((object x) => parent = x);
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(10).ContinueWith(t => false));
((IChildDelegate)parent).CloseItem(model.Object);
}
@ -321,7 +326,7 @@ namespace StyletUnitTests
[Test]
public void ShowMessageBoxShowsMessageBox()
{
var wm = new WindowManagerWithoutCreateWindow(this.viewManager.Object, () => this.messageBoxViewModel.Object);
var wm = new WindowManagerWithoutCreateWindow(this.viewManager.Object, () => this.messageBoxViewModel.Object, this.config.Object);
try { wm.ShowMessageBox("text", "title", MessageBoxButton.OKCancel, MessageBoxImage.Error, MessageBoxResult.OK, MessageBoxResult.Cancel, MessageBoxOptions.RtlReading); }
catch (TestException) { }
@ -379,5 +384,23 @@ namespace StyletUnitTests
Assert.AreEqual(WindowStartupLocation.Manual, window.WindowStartupLocation);
}
[Test]
public void CreateWindowSetsOwnerIfAvailable()
{
// We can't actually set the window successfully, since it'll throw an InvalidOperationException
// ("can't set owner to a window which has not been shown previously")
var model = new object();
var window = new Window();
var activeWindow = new Window();
this.viewManager.Setup(x => x.CreateAndBindViewForModelIfNecessary(model)).Returns(window);
this.config.Setup(x => x.GetActiveWindow()).Returns(activeWindow).Verifiable();
this.windowManager.CreateWindow(model, true);
this.config.VerifyAll();
}
}
}

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Moq" version="4.2.1402.2112" targetFramework="net45" />
<package id="NUnit" version="2.6.3" targetFramework="net45" />
<package id="NUnit.Runners" version="2.6.3" />
<package id="OpenCover" version="4.5.2506" />
<package id="ReportGenerator" version="1.9.1.0" />
</packages>
<package id="Moq" version="4.2.1507.0118" targetFramework="net45" />
<package id="NUnit" version="2.6.4" targetFramework="net45" />
<package id="NUnit.Runners" version="2.6.4" targetFramework="net45" />
<package id="NUnitTestAdapter" version="2.0.0" targetFramework="net45" />
<package id="OpenCover" version="4.6.166" targetFramework="net45" />
<package id="ReportGenerator" version="2.3.0.0" targetFramework="net45" />
</packages>