使用Caliburn Micro进行导航

我正在玩Caliburn.Micro并且现在有一个非常简单的应用程序。

它有一个AppView,它实际上有一个用于NavigationBar的ContentControl,一个InnerView和一个StatusBar。

现在我想处理不同内部视图之间的导航。

现在我使用eventaggregator发布一个NavigationEvent,它应该将主窗口的内部视图切换到另一个视图。

这是我对Publish的调用(所有InnerViews都有相同的基类,它有一个IEventAggregator)

public void NavigateOverview() { base._eventAggregator.Publish(new NavigateEvent("OverviewViewModel")); } 

现在我将一个字符串传递给AppViewModel,后者处理NavigateEvent:

  public void Handle(NavigateEvent navigate) { InnerViewModel target; switch (navigate.TargetViewModel) { case "SelectProjectViewModel": { target = new SelectProjectViewModel(_eventAggregator); break; } case "OverviewViewModel": { target = new OverviewViewModel(_eventAggregator); break; } default: { throw new InvalidOperationException("no target type found"); } } this.CurrentInnerViewModel = target; } 

传递字符串有效,但是错误并且不是很干净。

什么是Caliburn处理方式? 这是导体应该做的吗?

为什么不直接传递一个类型? 那样就没有神奇的字符串了

例如

 public void NavigateOverview() { base._eventAggregator.Publish(new NavigateEvent(typeof(OverviewViewModel))); } 

然后:

  public void Handle(NavigateEvent navigate) { InnerViewModel target; // EDIT: Remove the case (only works with integral types so you can't use typeof etc) // but you could do this with standard conditional logic this.CurrentInnerViewModel = target; } 

编辑2:

好了,因为您询问了如何构建CMs IoC,以下是使用IoC和Castle Windsor的示例以及将其他参数传递给导航的解决方案(从EventAggregator借用)

引导程序只需要一些零碎的东西来配置容器:

 public class AppBootstrapper : Bootstrapper { // The Castle Windsor container private IWindsorContainer _container; protected override void Configure() { base.Configure(); // Create the container, install from the current assembly (installer code shown in next section below) _container = new WindsorContainer(); _container.Install(FromAssembly.This()); } // Matches up with Windsors ResolveAll nicely protected override IEnumerable GetAllInstances(Type service) { return (IEnumerable)_container.ResolveAll(service); } // Matches up with Windsors Resolve protected override object GetInstance(Type service, string key) { return string.IsNullOrEmpty(key) ? _container.Resolve(service) : _container.Resolve(key, service); } // Windsor doesn't do property injection by default, but it's easy enough to get working: protected override void BuildUp(object instance) { // Get all writable public properties on the instance we will inject into instance.GetType().GetProperties().Where(property => property.CanWrite && property.PropertyType.IsPublic) // Make sure we have a matching service type to inject by looking at what's registered in the container .Where(property => _container.Kernel.HasComponent(property.PropertyType)) // ...and for each one inject the instance .ForEach(property => property.SetValue(instance, _container.Resolve(property.PropertyType), null)); } } 

适用于CM的Windsor安装程序可能非常简单:

 public class CaliburnMicroInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { // Register the window manager container.Register(Component.For().ImplementedBy()); // Register the event aggregator container.Register(Component.For().ImplementedBy()); } } 

我还有一个导航服务界面来帮助应用程序导航:

 public interface INavigationService { void Navigate(Type viewModelType, object modelParams); } 

这是由NavigationService实现的(在一秒钟内显示)

这也需要一个Windsor安装程序:

 public class NavigationInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register(Component.For().ImplementedBy()); } } 

NavigationService工作原理与EventAggregator非常相似,因为暴露导航参数的类型应该为它可以接收的每个参数类实现一个通用接口……

界面看起来像这样(从EventAggregator大量借用):

 // This is just to help with some reflection stuff public interface IViewModelParams { } public interface IViewModelParams : IViewModelParams { // It contains a single method which will pass arguments to the viewmodel after the nav service has instantiated it from the container void ProcessParameters(T modelParams); } 

例:

 public class ExampleViewModel : Screen, // We can navigate to this using DefaultNavigationArgs... IViewModelParams, // or SomeNavigationArgs, both of which are nested classes... IViewModelParams { public class DefaultNavigationArgs { public string Value { get; private set; } public DefaultNavigationArgs(string value) { Value = value; } } public class OtherNavigationArgs { public int Value { get; private set; } public DefaultNavigationArgs(int value) { Value = value; } } public void ProcessParameters(DefaultNavigationArgs modelParams) { // Do something with args DisplayName = modelParams.Value; } public void ProcessParameters(OtherNavigationArgs modelParams) { // Do something with args. this time they are int! DisplayName = modelParams.Value.ToString(); } } 

这导致一些强类型导航(例如,重构友好!)

 NavigationService.Navigate(typeof(ExampleViewModel), new ExampleViewModel.DefaultNavigationArgs("hello")); 

要么

 NavigationService.Navigate(typeof(ExampleViewModel), new ExampleViewModel.OtherNavigationArgs(15)); 

这也意味着ViewModel仍然可以控制它自己的导航参数

好吧,回到温莎一会儿; 显然我们需要从视图命名空间安装任何视图 – Windsors fluent API使这很简单:

 public class ViewInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { // The 'true' here on the InSameNamespaceAs causes windsor to look in all sub namespaces too container.Register(Classes.FromThisAssembly().InSameNamespaceAs(true)); } } 

好的,现在是NavigationService实现:

 public class NavigationService : INavigationService { // Depends on the aggregator - this is how the shell or any interested VMs will receive // notifications that the user wants to navigate to someplace else private IEventAggregator _aggregator; public NavigationService(IEventAggregator aggregator) { _aggregator = aggregator; } // And the navigate method goes: public void Navigate(Type viewModelType, object modelParams) { // Resolve the viewmodel type from the container var viewModel = IoC.GetInstance(viewModelType, null); // Inject any props by passing through IoC buildup IoC.BuildUp(viewModel); // Check if the viewmodel implements IViewModelParams and call accordingly var interfaces = viewModel.GetType().GetInterfaces() .Where(x => typeof(IViewModelParams).IsAssignableFrom(x) && x.IsGenericType); // Loop through interfaces and find one that matches the generic signature based on modelParams... foreach (var @interface in interfaces) { var type = @interface.GetGenericArguments()[0]; var method = @interface.GetMethod("ProcessParameters"); if (type.IsAssignableFrom(modelParams.GetType())) { // If we found one, invoke the method to run ProcessParameters(modelParams) method.Invoke(viewModel, new object[] { modelParams }); } } // Publish an aggregator event to let the shell/other VMs know to change their active view _aggregator.Publish(new NavigationEventMessage(viewModel)); } } 

现在,shell可以只处理聚合器消息并激活新注入和另外配置的VM

 public class ShellViewModel : Conductor, IHandle { private IEventAggregator _aggregator; private INavigationService _navigationService; public ShellViewModel(IEventAggregator aggregator, INavigationService _navigationService) { _aggregator = aggregator; _aggregator.Subscribe(this); _navigationService.Navigate(typeof (OneSubViewModel), null); } public void Handle(NavigationEventMessage message) { ActivateItem(message.ViewModel); } } 

实际上我将导航限制为IScreen实现,所以我的NavigationEventMessage实际上看起来像这样:

 public class NavigationEventMessage { public IScreen ViewModel { get; private set; } public NavigationEventMessage(IScreen viewModel) { ViewModel = viewModel; } } 

这是因为我总是想要我的孩子视图模型的生命周期