如何在ReactiveUI中为同一类型配置多视图并自定义ViewLocation选择?
我正在结合Splat Autofac使用ReactiveUI的ViewLocation特性,实现视图与视图模型的自动关联。想给同一类型(示例中为Person类)配置多个视图,并根据特定参数选择使用对应的视图,但目前注册多个视图时,最后注册的视图会被默认选中,请问如何实现自定义视图选择逻辑?
简化示例代码如下:
定义Person类:
public class Person { public string Name { get; set; } public int Age { get; set; } }
为Person创建两个视图:PersonNameView(绑定Name属性)和PersonAgeView(绑定Age属性),并在主视图中通过ItemsControl展示Person集合。
已通过Autofac注册两个视图:
builder.RegisterType<PersonNameView>().As<IViewFor<Person>>().InstancePerDependency(); builder.RegisterType<PersonAgeView>().As<IViewFor<Person>>().InstancePerDependency();
要实现同一类型多视图的自定义选择,核心是通过Autofac键控注册区分不同视图,并配合自定义视图解析逻辑(或数据模板选择器)根据参数获取对应视图,具体步骤如下:
1. 定义视图标识枚举
先创建一个枚举,用来标记不同的视图类型,方便后续区分:
public enum PersonViewType { Name, // 对应PersonNameView Age // 对应PersonAgeView }
2. 调整Autofac键控注册
将原来的普通注册改为键控注册,用上面定义的枚举作为键,明确每个视图对应的标识:
// 注册PersonNameView,键为PersonViewType.Name builder.RegisterType<PersonNameView>() .Keyed<IViewFor<Person>>(PersonViewType.Name) .InstancePerDependency(); // 注册PersonAgeView,键为PersonViewType.Age builder.RegisterType<PersonAgeView>() .Keyed<IViewFor<Person>>(PersonViewType.Age) .InstancePerDependency();
3. 自定义视图解析逻辑
根据场景不同,有两种常用实现方式:
方式一:扩展ReactiveUI的ViewLocator
通过自定义IViewLocator,让ReactiveUI支持根据参数(如视图标识)解析对应视图:
// 注册自定义ViewLocator到Splat Locator.CurrentMutable.Register(() => new PersonViewLocator(Locator.Current.GetService<IComponentContext>()), typeof(IViewLocator)); public class PersonViewLocator : IViewLocator { private readonly IComponentContext _autofacContext; public PersonViewLocator(IComponentContext autofacContext) { _autofacContext = autofacContext; } public IViewFor ResolveView<T>(T viewModel, string contract = null) where T : class { // 如果传入了contract,尝试解析为PersonViewType并获取对应视图 if (!string.IsNullOrEmpty(contract) && Enum.TryParse<PersonViewType>(contract, out var viewType)) { return _autofacContext.ResolveKeyed<IViewFor<T>>(viewType); } // 默认逻辑:可返回默认视图或抛出异常 throw new InvalidOperationException("请指定有效的视图类型参数"); } }
使用时,通过指定contract参数选择视图:
// 获取展示Name的视图 var nameView = ViewLocator.Current.ResolveView(personInstance, PersonViewType.Name.ToString()); // 获取展示Age的视图 var ageView = ViewLocator.Current.ResolveView(personInstance, PersonViewType.Age.ToString());
方式二:使用DataTemplateSelector(WPF场景)
如果是在WPF的ItemsControl中展示,可通过自定义DataTemplateSelector,根据外部参数(如切换按钮的绑定值)动态选择视图:
public class PersonViewTemplateSelector : DataTemplateSelector { private readonly IComponentContext _autofacContext; // 注入Autofac容器上下文 public PersonViewTemplateSelector(IComponentContext autofacContext) { _autofacContext = autofacContext; } // 绑定到外部控制参数(如ViewModel中的CurrentViewType属性) public PersonViewType CurrentViewType { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item is not Person person) return base.SelectTemplate(item, container); // 根据CurrentViewType从Autofac获取对应视图 var targetView = _autofacContext.ResolveKeyed<IViewFor<Person>>(CurrentViewType); // 动态创建DataTemplate并绑定ViewModel var dataTemplate = new DataTemplate(); var elementFactory = new FrameworkElementFactory(targetView.GetType()); elementFactory.SetBinding(ViewFor.ViewModelProperty, new Binding()); dataTemplate.VisualTree = elementFactory; return dataTemplate; } }
在主视图中配置ItemsControl的ItemTemplateSelector:
<ItemsControl ItemsSource="{Binding Persons}"> <ItemsControl.ItemTemplateSelector> <local:PersonViewTemplateSelector CurrentViewType="{Binding SelectedViewType}" /> </ItemsControl.ItemTemplateSelector> </ItemsControl>
只需修改SelectedViewType的值,就能切换ItemsControl中展示的视图类型。
内容的提问来源于stack exchange,提问作者Miljac

