我玩弄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;
}
传递字符串的作品,但容易出错,不是很干净。
什么是处理的卡利方式? 那是什么指挥应该怎么办?
为什么不只是传递一个类型呢? 这样,没有神奇的字符串
如
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