3

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

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

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

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

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

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

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

        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 的处理方式是什么?那是指挥家应该做的吗?

4

1 回答 1

5

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

例如

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<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 安装程序可能很简单:

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

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

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<INavigationService>().ImplementedBy<NavigationService>());
    }
}

NavigationService工作原理很像,暴露导航参数的EventAggregator类型应该为它可以接收的每个参数类实现一个通用接口......

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

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

例子:

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

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

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<ShellViewModel>(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<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 实际上看起来像这样:

public class NavigationEventMessage
{
    public IScreen ViewModel { get; private set; }

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

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

于 2013-03-30T20:10:28.997 回答