gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 23:40:49 24 4
gpt4 key购买 nike

Here Caliburn.Micro成功与ModernUI结合。
但是,如果要使用多个窗口,则还需要重新实现Caliburn的WindowManager以与ModernUI一起正常使用。怎么做到呢?

更新:
(有关IoC容器/依赖项注入的其他问题)

好的,我知道了:我在这里使用了构造函数注入:

public class BuildingsViewModel : Conductor<IScreen>

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


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

container.Singleton<IWindowManager, ModernWindowManager>();


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

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

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

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

UPDATE2:

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

最佳答案

只需创建自己的派生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

例如弹出我的 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

例如使用CM的SimpleContainer

container.Singleton<IWindowManager, ModernWindowManager>();


显然,我对以上内容的唯一缺点是,您似乎无法将内容直接放在 ModernWindow中,因此每个弹出窗口都必须有两个 UserControls

一种解决方法是更改​​ EnsureWindow中的 ModernWindowManager,以便它基于 UserControl创建一个 ModernWindow并将 ContentSource设置为要加载的视图的URI,这将触发内容加载器和连接您的 ViewModel。如果有时间尝试,我会进行更新。

更新:

好的,所以目前它很hacky,但这可能是有用的东西的起点。基本上,我基于视图的名称空间和名称生成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;
}


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

更新2

仅供参考,这是我的 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>();
}


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

诸如 WindowManagerEventAggregator之类的CM服务均已针对它们各自的接口注册为单例,因此在运行时每个实例只有1个实例可用。

视图模型注册为 PerRequest,每次您从容器中请求一个实例时,该实例都会创建一个新实例-这样,您可以多次弹出相同的窗口,而不会出现奇怪的行为!

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

更新3

在回答您的IoC问题时:

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

由于您的视图模型现在通常需要依赖项,因此您需要某种方式将其注入实例。如果 PopupViewModel具有多个依赖项,则可以将其注入到父类中,但这会将父视图模型以某种方式耦合到 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);
}
}


唯一的缺点是,如果您需要在同一视图模型中多次使用该实例,则该实例将是同一实例,尽管如果您知道同时需要多少个实例,则可以注入多个 PopupViewModel实例。

使用某种形式的按需注射

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

我认为Caliburn或SimpleContainer都不支持开箱即用的工厂,因此替代方法是使用 IoC.Get<T>IoC是静态类,可让您在实例化后访问DI容器

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


您需要确保已在引导程序中正确注册了容器,并将对CM的 IoC方法的任何调用委派给了该容器- 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>();
}


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?

您只需要注入 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?

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

这是基于服务的应用程序的引导程序的示例。我正在使用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;
}
}


创建引导程序后,它将设置容器并注册所有服务以及插件dll。对 GetEngine的调用通过从创建完整依赖关系树的容器中解析 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();
}
}


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


所有其他组件都具有依赖性,但我不会将其注入 IntegrationEngine-它们由容器处理

关于c# - 使用ModernUI + Caliburn.Micro组合重新实现WindowManager,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29079328/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com