导航与微卡利(Navigation with Caliburn Micro)

2019-08-18 06:55发布

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

它有一个APPVIEW,这实际上有一个导航栏,一个InnerView和状态栏一个ContentControl中。

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

现在我用的是eventaggregator发布一个NavigationEvent,应在主窗口的innerview切换到其他视图。

这是我的呼吁发布(所有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;
        }

传递字符串的作品,但容易出错,不是很干净。

什么是处理的卡利方式? 那是什么指挥应该怎么办?

Answer 1:

为什么不只是传递一个类型呢? 这样,没有神奇的字符串

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:

好吧,因为你问起建设成为国际奥委会的CM,这里是使用与温莎城堡国际奥委会和传递其他参数导航(从借用的解决方案的例子EventAggregator

引导程序只需要几点点滴滴Config中的容器:

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可能会是简单的:

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 (告诉你,在几秒钟之)

这需要一个温莎安装过:

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

这也意味着,视图模型仍然是在它自己的导航参数的控制

确定回到温莎为秒; 很明显,我们需要从我们的观点命名空间中安装任何意见 - 温莎公爵流利的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));
    }
}

现在,外壳可以只处理聚合信息并激活新注入和另外配置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;
    }
}

这是因为我总是想生命周期我的孩子的ViewModels



文章来源: Navigation with Caliburn Micro