gpt4 book ai didi

c# - 使用 Caliburn Micro 进行导航

转载 作者:行者123 更新时间:2023-11-30 15:00:13 24 4
gpt4 key购买 nike

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

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

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

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

这是我对 Publish 的调用(所有 InnerView 都有相同的基类,它有一个 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 的处理方式是什么?这是指挥应该做的吗?

最佳答案

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

例如

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 借用)

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

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 Installer 可能会很简单:

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

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

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 非常相似,因为公开导航参数的类型应该为它可以接收的每个参数类实现一个通用接口(interface)...

界面看起来像这样(大量借鉴了 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 仍然控制着它自己的导航参数

好的,回到温莎一会儿;显然,我们需要从我们的 View 命名空间安装任何 View - 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 可以处理聚合器消息并激活新注入(inject)和额外配置的 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;
}
}

这是因为我总是希望我的 subview 模型有生命周期

关于c# - 使用 Caliburn Micro 进行导航,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15709207/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com