使用ModernUI + Caliburn.Micro组合重新实现WindowManager

时间:2015-03-16 14:28:27

标签: c# .net dependency-injection caliburn.micro modern-ui

Here Caliburn.Micro与ModernUI成功合并。 但是如果我们想要使用多个窗口,我们还需要重新实现Caliburn的WindowManager才能与ModernUI一起正常工作。怎么办呢?

更新 (关于IoC-Container / Dependency Injection的其他问题)

好的,我得到它:我在这里使用了一个构造函数注入:

public class BuildingsViewModel : Conductor<IScreen>

{
    public BuildingsViewModel(IWindowManager _windowManager)
    {
        windowManager = _windowManager;
    }
}

就IoC容器解析BuildingsViewModel而言, 容器本身注入了ModernWindowManager IWindowManager接口的实现,因为Bootstrapper Configure()方法中的这一行:

container.Singleton<IWindowManager, ModernWindowManager>();

如果我从容器中解析对象实例,它会注入所有必需的依赖项。就像一棵树。

1)所以现在我想知道如何使用注射(带接口)替换这条线?  _windowManager.ShowWindow(new PopupViewModel());

2)如果我希望我的整个项目匹配DI模式,那么必须将所有对象实例注入ModernWindowViewModel,从容器首先解析?

3)可以在整个项目中使用Caliburn的SimpleContainer,还是更好地使用Castle Windsor这样的成熟框架?我应该避免混合吗?

UPDATE2:

4)将IoC容器集成到现有应用程序中需要首先创建此容器(例如,在控制台应用程序的Main()方法中),然后所有对象实例必须通过注入的依赖项从中增长?

1 个答案:

答案 0 :(得分:3)

只需创建自己的派生WindowManager并覆盖EnsureWindow

public class ModernWindowManager : WindowManager
{
    protected override Window EnsureWindow(object rootModel, object view, bool isDialog)
    {
        var window = view as ModernWindow;

        if (window == null)
        {
            window = new ModernWindow();
            window.SetValue(View.IsGeneratedProperty, true);
        }

        return window;
    }
}

您要用作弹出窗口的任何视图都必须基于ModernWindow,并且必须使用LinkGroupCollection,或者您必须设置窗口的ContentSource属性,否则会有没有内容。

您可以使用上述方法制作View-First ViewModel-First

e.g。弹出我的PopupView我做了以下

<强> PopupView.xaml

<mui:ModernWindow x:Class="TestModernUI.ViewModels.PopupView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mui="http://firstfloorsoftware.com/ModernUI"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300" ContentSource="/ViewModels/ChildView.xaml">
 </mui:ModernWindow>

<强> PopupViewModel.cs

public class PopupViewModel : Screen
{
    // Blah
}

从另一个ViewModel弹出视图的代码:

public void SomeMethod()
{
    _windowManager.ShowWindow(new PopupViewModel()); // Or use injection etc
}

请勿忘记在您的容器中注册ModernWindowManager代替WindowManager

<强> e.g。使用CM的SimpleContainer

container.Singleton<IWindowManager, ModernWindowManager>();

显然,我可以看到上面唯一的缺点是你似乎无法将内容直接放在ModernWindow中,所以每个弹出窗口必须有两个UserControls

解决方法是更改​​EnsureWindow中的ModernWindowManager,以便根据UserControl创建ModernWindow并将ContentSource设置为ViewModel的URI要加载的视图,这将触发内容加载器并连接您的protected override Window EnsureWindow(object rootModel, object view, bool isDialog) { var window = view as ModernWindow; if (window == null) { window = new ModernWindow(); // Get the namespace of the view control var t = view.GetType(); var ns = t.Namespace; // Subtract the project namespace from the start of the full namespace ns = ns.Remove(0, 12); // Replace the dots with slashes and add the view name and .xaml ns = ns.Replace(".", "/") + "/" + t.Name + ".xaml"; // Set the content source to the Uri you've made window.ContentSource = new Uri(ns, UriKind.Relative); window.SetValue(View.IsGeneratedProperty, true); } return window; } 。如果我有一分钟的尝试,我会更新。

<强>更新

好的,所以目前它非常hacky,但这可能是一些有用的东西的起点。基本上我是根据视图的名称空间和名称生成URI。

我确信有一种更可靠的方法可以做到这一点,但对于我的测试项目,它可行:

TestModernUI.ViewModels.PopupView

我的视图的完整命名空间是/ViewModels/PopupView.xaml,生成的URI是Bootstrapper,然后通过内容加载器自动加载和绑定。

更新2

这里是我的protected override void Configure() { container = new SimpleContainer(); container.Singleton<IWindowManager, ModernWindowManager>(); container.Singleton<IEventAggregator, EventAggregator>(); container.PerRequest<ChildViewModel>(); container.PerRequest<ModernWindowViewModel>(); container.PerRequest<IShell, ModernWindowViewModel>(); } 配置方法:

WindowManager

这里我创建了容器,并注册了一些类型。

EventAggregatorPerRequest等CM服务都是针对各自的接口和单例进行注册的,因此在运行时只有1个实例可用。

视图模型注册为1) So now I wonder how can I replace this line using an injection(with interface)? _windowManager.ShowWindow(new PopupViewModel());,每次从容器中请求时都会创建一个新实例 - 这样您可以多次弹出相同的窗口,而不会出现奇怪的行为!

这些依赖项被注入到运行时解析的任何对象的构造函数中。

更新3

回答您的IoC问题:

PopupViewModel

由于您的viewmodels现在通常需要依赖项,因此您需要有一些方法将它们注入实例。如果PopupViewModel有多个依赖项,您可以将它们注入父类,但这会以某种方式将父视图模型耦合到PopupViewModel

您可以使用其他几种方法来获取PopupViewModel的实例。

注入它!

如果您将PerRequest注册为public class MyViewModel { private PopupViewModel _popup; private IWindowManager _windowManager; public MyViewModel(PopupViewModel popup, IWindowManager windowManager) { _popup = popup; _windowManager = windowManager; } public void ShowPopup() { _windowManager.ShowPopup(_popup); } } ,则每次请求时都会获得一个新实例。如果在viewmodel中只需要一个弹出窗口实例,则可以将其注入:

PopupViewModel

唯一的缺点是如果你需要在同一个视图模型中多次使用它,那么实例将是同一个实例,但如果你知道你需要多少个实例,你可以注入多个IoC.Get<T>实例时间

使用某种形式的按需注射

对于稍后需要的依赖项,您可以使用按需注入,例如工厂

我不认为Caliburn或SimpleContainer支持工厂开箱即用,所以替代方法是使用IoC public void ShowPopup() { var popup = IoC.Get<PopupViewModel>(); _windowManager.ShowWindow(popup); } 是一个静态类,可让您在实例化后访问DI容器

IoC

您需要确保在引导程序中正确注册了容器,并将对CM的IoC.Get<T>方法的任何调用委派给容器 - GetInstance调用引导程序public class AppBootstrapper : BootstrapperBase { SimpleContainer container; public AppBootstrapper() { Initialize(); } protected override void Configure() { container = new SimpleContainer(); container.Singleton<IWindowManager, ModernWindowManager>(); container.Singleton<IEventAggregator, EventAggregator>(); container.PerRequest<IShell, ModernWindowViewModel>(); // Register viewmodels etc here.... } // IoC.Get<T> or IoC.GetInstance(Type type, string key) .... protected override object GetInstance(Type service, string key) { var instance = container.GetInstance(service, key); if (instance != null) return instance; throw new InvalidOperationException("Could not locate any instances."); } // IoC.GetAll<T> or IoC.GetAllInstances(Type type) .... protected override IEnumerable<object> GetAllInstances(Type service) { return container.GetAllInstances(service); } // IoC.BuildUp(object obj) .... protected override void BuildUp(object instance) { container.BuildUp(instance); } protected override void OnStartup(object sender, System.Windows.StartupEventArgs e) { DisplayRootViewFor<IShell>(); } {1}}和其他方法:

以下是一个例子:

Resolve

Castle.Windsor支持工厂,以便您可以Release2) If I want my whole project match DI pattern, all objects instances must be injected into ModernWindowViewModel, that resolves from container first?组件并更明确地管理他们的生命,但我不会在这里进入

ModernWindowViewModel

您只需要注入public class ParentViewModel { private ChildViewModel _child; public ParentViewModel(ChildViewModel child) { _child = child; } } public class ChildViewModel { private IWindowManager _windowManager; private IEventAggregator _eventAggregator; public ChildViewModel(IWindowManager windowManager, IEventAggregator eventAggregator) { _windowManager = windowManager; _eventAggregator = eventAggregator; } } 所需的依赖项。儿童需要的任何东西都会自动解决并注入,例如:

ParentViewModel

在上述情况下,如果您从容器中解析ChildViewModel,则3) Is it okay to use Caliburn's SimpleContainer for whole project, or better use mature framework like Castle Windsor? Should I avoid mixing?将获得其所有依赖关系。您不需要将它们注入父母。

SimpleContainer

你可以混合,但它可能会让人感到困惑,因为他们不会互相合作(一个容器不会知道另一个容器)。坚持使用一个容器,4) Integrating an IoC container into an existing application requires creating this container first(in Main() method of console app for example), and then all object instanses must grow from it with injected dependencies?很好 - 温莎城堡有很多功能,但你可能永远不需要它们(我只使用了一些高级功能)

// The bootstrapper sets up the container/engine etc public class Bootstrapper { // Castle Windsor Container private readonly IWindsorContainer _container; // Service for writing to logs private readonly ILogService _logService; // Bootstrap the service public Bootstrapper() { _container = new WindsorContainer(); // Some Castle Windsor features: // Add a subresolver for collections, we want all queues to be resolved generically _container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel)); // Add the typed factory facility and wcf facility _container.AddFacility<TypedFactoryFacility>(); _container.AddFacility<WcfFacility>(); // Winsor uses Installers for registering components // Install the core dependencies _container.Install(FromAssembly.This()); // Windsor supports plugins by looking in directories for assemblies which is a nice feature - I use that here: // Install any plugins from the plugins directory _container.Install(FromAssembly.InDirectory(new AssemblyFilter("plugins", "*.dll"))); _logService = _container.Resolve<ILogService>(); } /// <summary> /// Gets the engine instance after initialisation or returns null if initialisation failed /// </summary> /// <returns>The active engine instance</returns> public IIntegrationEngine GetEngine() { try { return _container.Resolve<IIntegrationEngine>(); } catch (Exception ex) { _logService.Fatal(new Exception("The engine failed to initialise", ex)); } return null; } // Get an instance of the container (for debugging) public IWindsorContainer GetContainer() { return _container; } }

是的,您创建容器,然后解析根组件(在99.9%的应用程序中有一个主要组件,称为组合根),然后构建完整的树。

以下是基于服务的应用程序的引导程序示例。我正在使用Castle Windsor,我希望能够在Windows服务或WPF应用程序中或甚至在控制台窗口(用于测试/调试)中托管引擎:

GetEngine

创建引导程序后,它会设置容器并注册所有服务以及插件dll。对Engine的调用通过从创建完整依赖关系树的容器中解析public partial class IntegrationService : ServiceBase { private readonly Bootstrapper _bootstrapper; private IIntegrationEngine _engine; public IntegrationService() { InitializeComponent(); _bootstrapper = new Bootstrapper(); } protected override void OnStart(string[] args) { // Resolve the engine which resolves all dependencies _engine = _bootstrapper.GetEngine(); if (_engine == null) Stop(); else _engine.Start(); } protected override void OnStop() { if (_engine != null) _engine.Stop(); } } 来启动应用程序。

我这样做是为了让我能够像这样创建应用程序的服务或控制台版本:

服务代码:

public class ConsoleAppExample
{
    private readonly Bootstrapper _bootstrapper;

    private IIntegrationEngine _engine;

    public ConsoleAppExample()
    {
        _bootstrapper = new Bootstrapper();

        // Resolve the engine which resolves all dependencies
        _engine = _bootstrapper.GetEngine();
        _engine.Start();
    }
}

控制台应用

public class IntegrationEngine : IIntegrationEngine
{
    private readonly IScheduler _scheduler;
    private readonly ICommsService _commsService;
    private readonly IEngineStateService _engineState;
    private readonly IEnumerable<IEngineComponent> _components;
    private readonly ConfigurationManager _configurationManager;
    private readonly ILogService _logService;

    public IntegrationEngine(ICommsService commsService, IEngineStateService engineState, IEnumerable<IEngineComponent> components,
        ConfigurationManager configurationManager, ILogService logService)
    {
        _commsService = commsService;
        _engineState = engineState;
        _components = components;
        _configurationManager = configurationManager;
        _logService = logService;

        // The comms service needs to be running all the time, so start that up
        commsService.Start();
    }

这是IIntegrationEngine实施的一部分

IntegrationEngine

所有其他组件都有依赖关系,但我没有将它们注入{{1}} - 它们由容器处理