[RxUI] 16 – View定位

[RxUI] 16 – View定位

IViewFor、激活和数据绑定

为了在视图中使用绑定,必须先在视图上实现IViewFor<TViewModel>。一旦实现了IViewFor<T>,就可以在类上使用绑定扩展方法了,同时实现了视图和那些与其相关的继承了IActivatableViewModel接口的视图模型的激活和失活功能。有关详细信息和特定于平台的示例,请参见数据绑定部分。

View定位

View定位是ReactiveUI的一项功能,可以将View和ViewModel关联并自动设置它们。

ViewModelViewHost

使用View定位最简单方法是通过ViewModelViewHost控件,该控件是一个View(在Cocoa中,是一个UIVIew/NSView,在基于XAML的平台上是一个控件),该控件仅有一个ViewModel属性。当ViewModel属性被设置后,View定位将查找关联的视图并将其加载到容器中。

ViewModelViewHost非常适合列表 — 如此一来,如果在基于XAML的平台上绑定到ItemsSource且未设置DataTemplateDisplayMemberPath属性,则就会使用ViewModelViewHost来配置。

<ListBox x:Name="ToasterList" />
// Now ListBox automatically gets a DataTemplate
this.OneWayBind(ViewModel, vm => vm.ToasterList, v => v.ToasterList.ItemsSource);

Xamarin.Forms不支持此功能!必须在此处使用DataTemplate并指定要使用的视图类

注册新View

要使用View定位,必须先通过Splat的Service Location功能来注册类型。

Locator.CurrentMutable.Register(() => new ToasterView(), typeof(IViewFor<ToasterViewModel>));

View定位在内部使用一个称为ViewLocator的类,该类可以替换,也可以使用默认的类。ResolveView方法将返回与给定ViewModel对象关联的View。

使用反射注册View

ReactiveUI有一些帮助方法,这些方法使用反射来注册实现了IViewFor接口的所有视图。请注意,由于使用的是反射,因此比手动注册每个视图要慢。

Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());

重载ViewLocator

如要想要重载视图定位器,首先要创建一个继承IViewLocator接口的类。

public class ConventionalViewLocator : IViewLocator
{
    public IViewFor ResolveView<T>(T viewModel, string contract = null) where T : class
    {
        // Find view's by chopping of the 'Model' on the view model name
        // MyApp.ShellViewModel => MyApp.ShellView
        var viewModelName = viewModel.GetType().FullName;
        var viewTypeName = viewModelName.TrimEnd("Model".ToCharArray());

        try
        {
            var viewType = Type.GetType(viewTypeName);
            if (viewType == null)
            {
                this.Log().Error($"Could not find the view {viewTypeName} for view model {viewModelName}.");
                return null;
            }
            return Activator.CreateInstance(viewType) as IViewFor;
        }
        catch (Exception)
        {
            this.Log().Error($"Could not instantiate view {viewTypeName}.");
            throw;
        }
    }
}

然后,当启动程序时,告诉ReactiveUI新的视图定位器:

// Make sure Splat and ReactiveUI are already configured in the locator
// so that our override runs last
Locator.CurrentMutable.RegisterLazySingleton(() => new ConventionalViewLocator(), typeof(IViewLocator));

手动设置View

有时,需要在ItemsControl或其子类中手动设置子项视图。最简单的选择是设置DisplayMemberPath,这将导致ReactiveUI不为ItemTemplate分配值,而是将引用的属性显示为文本。更强大的选项是手动设置ItemTemplate的值,其可以完全控制每个子项的显示方式。

<!--  ReactiveUI will set ItemTemplate to ViewModelViewHost -->
<ItemsControl />

<!--  ReactiveUI ignores this one because ItemTemplate is already set -->
<ItemsControl> 
    <ItemsControl.ItemTemplate>
        ...
    </ItemsControl.ItemTemplate>
</ItemsControl>

<!--  ReactiveUI ignores this one because DisplayMemberPath is already set -->
<ItemsControl DisplayMemberPath="SomeValue" /> 

可能想要使用DisplayMemberPath,但是在编译时不知道它的值。视图绑定该属性将引发以下异常:“InvalidOperationException: Cannot set both DisplayMemberPath and ItemTemplate”。这是因为ReactiveUI在初始化时查看控件,没有看到DisplayMemberPathItemTemplate的任何预设值,而是决定将ItemTemplate设置为ViewModelViewHost。然后,将ViewModel附加到视图是,绑定尝试设置DisplayMemberPath的值,并引发上述异常。解决方案是为DisplayMemberPath设置一个虚拟值,该虚拟值将由绑定替换,但是会阻止ReactiveUI尝试设置ItemTemplate

<!-- The dummy value will cause ReactiveUI to ignore this control -->
<ItemsControl Name="MyItemsControl" DisplayMemberPath="DummyValue" /> 
//This binding will override the dummy value
this.OneWayBind(ViewModel, vm => vm.MyDisplayMemberPath, v => v.MyItemsControl.DisplayMemberPath);

原文 https://www.reactiveui.net/docs/handbook/view-location

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注