简体   繁体   English

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

[英]Re-implementing WindowManager using ModernUI + Caliburn.Micro combination

Here Caliburn.Micro was successfully combined with ModernUI. 在这里, Caliburn.Micro成功地与ModernUI结合。 But if we want to use multiple windows we also need to re-implement Caliburn's WindowManager to work properly with ModernUI. 但是如果我们想要使用多个窗口,我们还需要重新实现Caliburn的WindowManager才能与ModernUI一起正常工作。 How can it be done? 如何做呢?

UPDATE: (Additional question about IoC-Container/Dependency Injection) 更新:(关于IoC容器/依赖注入的附加问题)

Ok, as I get it: I used a Constructor Injection here: 好吧,我得到它:我在这里使用了一个构造函数注入:

public class BuildingsViewModel : Conductor<IScreen>

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

As far as BuildingsViewModel resolved from IoC container, container itself injected ModernWindowManager implementation of IWindowManager interface because of this line in Bootstrapper 's Configure() method: 至于BuildingsViewModel从IoC容器解析,容器本身注入了IWindowManager接口的ModernWindowManager实现,因为BootstrapperConfigure()方法中的这一行:

container.Singleton<IWindowManager, ModernWindowManager>();

If I resolving an object instance from container, it injects all needed dependencies. 如果我从容器中解析对象实例,它会注入所有必需的依赖项。 Like a tree. 就像一棵树。

1) So now I wonder how can I replace this line using an injection(with interface)? 1)所以现在我想知道如何使用注射(带接口)替换这条线? _windowManager.ShowWindow(new PopupViewModel());

2) If I want my whole project match DI pattern, all objects instances must be injected into ModernWindowViewModel , that resolves from container first? 2)如果我希望我的整个项目匹配DI模式,所有对象实例必须注入ModernWindowViewModel ,它首先从容器解析?

3) Is it okay to use Caliburn's SimpleContainer for whole project, or better use mature framework like Castle Windsor? 3)可以在整个项目中使用Caliburn的SimpleContainer ,还是更好地使用像Castle Windsor这样的成熟框架? Should I avoid mixing? 我应该避免混合吗?

UPDATE2: UPDATE2:

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

Simply create your own derived WindowManager and override EnsureWindow : 只需创建自己的派生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;
    }
}

Any views that you want to use as popups must be based on ModernWindow and must either use a LinkGroupCollection or you must set the ContentSource property of the window, otherwise there will be no content. 您要用作弹出窗口的任何视图都必须基于ModernWindow并且必须使用LinkGroupCollection或者必须设置窗口的ContentSource属性,否则将不会有内容。

You could possibly make this View-First but it works ViewModel-First using the method above. 您可以使用View-First但它使用上面的方法运行ViewModel-First

eg to popup my PopupView I did the following 例如,为了弹出我的PopupView我做了以下操作

PopupView.xaml 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 PopupViewModel.cs

public class PopupViewModel : Screen
{
    // Blah
}

Code to popup the view from another ViewModel: 用于从另一个ViewModel弹出视图的代码:

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

Don't forget to register ModernWindowManager in place of WindowManager in your container! 不要忘记在容器中注册ModernWindowManager来代替WindowManager

eg using CM's SimpleContainer 例如,使用CM的SimpleContainer

container.Singleton<IWindowManager, ModernWindowManager>();

Obviously the only downside I can see to the above is that you can't seem to put content directly in a ModernWindow , so you have to have two UserControls for every popup! 显然,我可以看到的唯一缺点是你似乎无法将内容直接放在ModernWindow ,所以你必须为每个弹出窗口都有两个UserControls

A workaround would be to change EnsureWindow in ModernWindowManager so that it created a UserControl based on ModernWindow and set the ContentSource to the URI of the view you want to load, this will trigger the content loader and wire up your ViewModel . 一种解决方法是改变EnsureWindowModernWindowManager以便它创建了一个UserControl基于ModernWindow和设置ContentSource你要加载的视图的URI,这将触发内容加载器和电线您的ViewModel I'll update if I get a minute to try it. 如果我有一分钟尝试,我会更新。

Update: 更新:

Ok, so at the moment it's very hacky, but this could be a starting point for something useful. 好的,所以目前它非常hacky,但这可能是一些有用的东西的起点。 Basically I'm generating a URI based on the namespace and name of the view. 基本上我是根据命名空间和视图名称生成URI。

I'm sure there is a more reliable way of doing this, but for my test project it works: 我确信有一种更可靠的方法可以做到这一点,但对于我的测试项目,它可以工作:

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;
}

My full namespace for my view was TestModernUI.ViewModels.PopupView and the URI generated was /ViewModels/PopupView.xaml which then was loaded and bound via the content loader automagically. 我的视图的完整命名空间是TestModernUI.ViewModels.PopupView ,生成的URI是/ViewModels/PopupView.xaml ,然后通过内容加载器自动加载和绑定。

Update 2 更新2

FYI here is my Bootstrapper configure method: 仅供参考我的Bootstrapper配置方法:

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>();
}

Here I create the container, and register some types. 在这里,我创建容器,并注册一些类型。

The CM services such as WindowManager and EventAggregator are both registered against their respective interfaces and as singletons so only 1 instance of each will be available at run time. 诸如WindowManagerEventAggregator类的CM服务都是针对它们各自的接口和单例进行注册的,因此在运行时只有1个实例可用。

The view models are registered as PerRequest which creates a new instance every time you request one from the container - this way you can have the same window popup multiple times without strange behaviour! 视图模型注册为PerRequest ,每次从容器中请求时都会创建一个新实例 - 这样您就可以多次弹出相同的窗口而不会出现奇怪的行为!

These dependencies are injected into the constructor of any objects resolved at run time. 这些依赖项将注入到运行时解析的任何对象的构造函数中。

Update 3 更新3

In answer to your IoC questions: 在回答您的IoC问题时:

1) So now I wonder how can I replace this line using an injection(with interface)? _windowManager.ShowWindow(new PopupViewModel());

Since your viewmodels will now usually need dependencies you need to have some way of injecting them into the instances. 由于您的viewmodels现在通常需要依赖项,因此您需要有一些方法将它们注入到实例中。 If PopupViewModel had several dependencies, you could inject them into the parent class but this would couple the parent viewmodel to PopupViewModel in some way. 如果PopupViewModel有多个依赖项,您可以将它们注入父类,但这会以某种方式将父视图模型耦合到PopupViewModel

There are a couple of other methods you can use to get an instance of PopupViewModel . 您可以使用其他几种方法来获取PopupViewModel的实例。

Inject it! 注入它!

If you register PopupViewModel as PerRequest you will get a new instance of it every time you request it. 如果您将PopupViewModel注册为PerRequest ,则每次请求时都会获得它的新实例。 If you only need one popup instance in your viewmodel you can just inject it: 如果在viewmodel中只需要一个弹出窗口实例,则可以将其注入:

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);
    }    
}

The only downside is that the instance will be the same one if you need to use it multiple times in the same viewmodel, though you could inject multiple instances of PopupViewModel if you knew how many you needed at the same time 唯一的缺点是如果你需要在同一个视图模型中多次使用它,那么实例将是同一个实例,但如果你知道你需要多少同时注入PopupViewModel实例

Use some form of on-demand injection 使用某种形式的按需注射

For dependencies which are required later on you can use on-demand injection such as a factory 对于以后需要的依赖项,您可以使用按需注入,例如工厂

I don't think Caliburn or SimpleContainer support factories out of the box, so the alternative is to use IoC.Get<T> . 我不认为Caliburn或SimpleContainer支持工厂开箱即用,所以替代方案是使用IoC.Get<T> IoC is a static class which lets you access your DI container after instantiation IoC是一个静态类,允许您在实例化后访问DI容器

 public void ShowPopup()
 {
     var popup = IoC.Get<PopupViewModel>();
     _windowManager.ShowWindow(popup);
 }

You need to make sure you have correctly registered the container in your bootstrapper and delegated any calls to CM's IoC methods to the container - IoC.Get<T> calls the bootstrapper's GetInstance and other methods: 您需要确保在引导程序中正确注册了容器,并将对CM的IoC方法的任何调用委托给容器 - IoC.Get<T>调用引导程序的GetInstance和其他方法:

Here's an example: 这是一个例子:

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>();
    }

Castle.Windsor supports factories so that you can Resolve and Release your components and manage their lifetime more explicitly, but I won't go into that here Castle.Windsor支持工厂,以便您可以更明确地ResolveRelease组件并管理其生命周期,但我不会在此处讨论

2) If I want my whole project match DI pattern, all objects instances must be injected into ModernWindowViewModel, that resolves from container first?

You only need to inject the dependencies that the ModernWindowViewModel needs. 您只需要注入ModernWindowViewModel所需的依赖ModernWindowViewModel Anything that is required by children is automatically resolved and injected eg: 儿童需要的任何东西都会自动解决并注入,例如:

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;
    }
}

In the above situation, if you resolve ParentViewModel from the container - the ChildViewModel will get all it's dependencies. 在上面的情况下,如果您从容器中解析ParentViewModel - ChildViewModel将获得它的所有依赖项。 You don't need to inject them into the parent. 您不需要将它们注入父级。

3) Is it okay to use Caliburn's SimpleContainer for whole project, or better use mature framework like Castle Windsor? Should I avoid mixing?

You can mix, but it might be confusing as they won't work with each other (one container won't know about the other). 你可以混合,但它可能会让人感到困惑,因为它们不能相互协作(一个容器不会知道另一个容器)。 Just stick with one container, and SimpleContainer is fine - Castle Windsor has a lot more features, but you might never need them (I've only used a few of the advanced features) 只需坚持使用一个容器, SimpleContainer就可以了 - Castle Windsor有很多功能,但你可能永远不需要它们(我只使用了一些高级功能)

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?

Yes, you create the container, then you resolve the root component (in 99.9% of applications there is one main component which is called the composition root), and this then builds the full tree. 是的,您创建容器,然后解析根组件(99.9%的应用程序中有一个主组件称为组合根),然后构建完整的树。

Here is an example of a bootstrapper for a service based application. 以下是基于服务的应用程序的引导程序示例。 I'm using Castle Windsor and I wanted to be able to host the engine in a Windows service or in a WPF application or even in a Console Window (for testing/debug): 我正在使用Castle Windsor,我希望能够在Windows服务或WPF应用程序中或甚至在控制台窗口(用于测试/调试)中托管引擎:

// 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;
    }
}

Once the bootstrapper is created, it sets up the container and registers all services and also plugin dlls. 创建引导程序后,它会设置容器并注册所有服务以及插件dll。 The call to GetEngine starts the application by resolving Engine from the container which creates the full dependency tree. GetEngine的调用通过从容器中解析Engine来启动应用程序,该容器创建完整的依赖关系树。

I did this so that it allows me to create a service or a console version of the application like this: 我这样做是为了让我可以像这样创建应用程序的服务或控制台版本:

Service Code: 服务代码:

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();
    }
}

Console App: 控制台应用:

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();
    }
}

Here's part of the implementation of IIntegrationEngine 这是IIntegrationEngine实现的一部分

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();
    }

All of the other components have dependencies, but I don't inject those into the IntegrationEngine - they are handled by the container 所有其他组件都有依赖项,但我没有将它们注入IntegrationEngine - 它们由容器处理

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM