首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Caliburn Micro导航

使用Caliburn Micro导航
EN

Stack Overflow用户
提问于 2013-03-30 02:27:28
回答 1查看 3.6K关注 0票数 3

我正在尝试使用Caliburn.Micro,现在有一个非常简单的应用程序。

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

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

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

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

代码语言:javascript
复制
public void NavigateOverview()
{
    base._eventAggregator.Publish(new NavigateEvent("OverviewViewModel"));
}

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

代码语言:javascript
复制
        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;
        }

传递字符串是可行的,但是容易出错,而且不是很干净。

卡利本的处理方式是什么?这是指挥应该做的吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-03-31 04:10:29

为什么不直接传递一个类型呢?这样就不会有魔法弦了

例如:

代码语言:javascript
复制
public void NavigateOverview()
{
    base._eventAggregator.Publish(new NavigateEvent(typeof(OverviewViewModel)));
}

然后:

代码语言:javascript
复制
    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)。

bootstrapper只需要一些零碎的东西来配置容器:

代码语言:javascript
复制
public class AppBootstrapper : Bootstrapper<ShellViewModel>
{
    // 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<object> GetAllInstances(Type service)
    {
        return (IEnumerable<object>)_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安装程序可能会像下面这样简单:

代码语言:javascript
复制
public class CaliburnMicroInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        // Register the window manager
        container.Register(Component.For<IWindowManager>().ImplementedBy<WindowManager>());

        // Register the event aggregator
        container.Register(Component.For<IEventAggregator>().ImplementedBy<EventAggregator>());
    }
}

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

代码语言:javascript
复制
public interface INavigationService
{
    void Navigate(Type viewModelType, object modelParams);
}

这是由NavigationService实现的(请稍后向您展示)

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

代码语言:javascript
复制
public class NavigationInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<INavigationService>().ImplementedBy<NavigationService>());
    }
}

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

界面看起来像这样(大量借鉴了EventAggregator):

代码语言:javascript
复制
// This is just to help with some reflection stuff
public interface IViewModelParams { }

public interface IViewModelParams<T> : 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);
}

示例:

代码语言:javascript
复制
public class ExampleViewModel : Screen, 
    // We can navigate to this using DefaultNavigationArgs...
    IViewModelParams<DefaultNavigationArgs>, 
    // or SomeNavigationArgs, both of which are nested classes...
    IViewModelParams<SomeOtherNavigationArgs>
{
    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();
    }
}

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

代码语言:javascript
复制
NavigationService.Navigate(typeof(ExampleViewModel), new ExampleViewModel.DefaultNavigationArgs("hello"));

代码语言:javascript
复制
NavigationService.Navigate(typeof(ExampleViewModel), new ExampleViewModel.OtherNavigationArgs(15));

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

好了,回到Windsor;显然,我们需要安装视图命名空间中的任何视图-Windsor fluent API使这变得非常简单:

代码语言:javascript
复制
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<ShellViewModel>(true));
    }
}

好的,现在是NavigationService实现:

代码语言:javascript
复制
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可以处理聚合器消息,并激活新的注入虚拟机和额外配置的虚拟机

代码语言:javascript
复制
public class ShellViewModel : Conductor<IScreen>, IHandle<NavigationEventMessage>
{
    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实际上看起来像这样:

代码语言:javascript
复制
public class NavigationEventMessage
{
    public IScreen ViewModel { get; private set; }

    public NavigationEventMessage(IScreen viewModel)
    {
        ViewModel = viewModel;
    }
}

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

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15709207

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档