[RxUI] 13 – 路由

[RxUI] 13 – 路由

路由使程序可以通过多个视图及其相应视图模型来协调导航,并跟踪用户的导航状态。

ReactiveUI支持以下平台的路由:

  • Avalonia
  • Universal Windows Platform (UWP)
  • Windows Forms
  • Windows Presentation Foundation (WPF)
  • WinRT
  • WP8
  • Xamarin Forms

路由也可以在没有Xamarin.Forms的iOS和Android上运行,但并不总是好用。如果在这些平台上很难实现视图模型路由,则可以使用视图优先路由并自定义它的大多数方面。

关于ReactiveUI路由

以下元素参与路由:

  • 一个IScreen
  • 一个RoutingState(包含在IScreen实例内),
  • 两个或更多IRoutableViewModel
  • 和一个特定于平台的RoutedViewHost XAML控件。

IScreen是导航堆栈的根。尽管名字如此,但是与IScreen关联的视图无需扩展到全屏。RoutingState管理视图模型的导航堆栈,并允许视图模型导航到其它视图模型。RoutedViewHost监视RoutingState的一个实例,并通过创建和嵌入所需的视图来响应导航堆栈中的更改。

一个简单示例

注释 下面的示例使用的是WPF,但是ReactiveUI的路由支持更多平台。

使用Visual Studio创建一个新的WPF项目,并将其命名为“ReactiveRouting”。将ReactiveUI.WPF NuGet包安装到项目中。现在,创建一个名为FirstViewModel并实现了IRoutableViewModel接口的视图模型。IRoutableViewModel.UrlPathSegment属性是代表当前视图模型的字符串令牌,例如“login”或“user”。可以选择任何字符串。在本示例中,使用“first”。HostScreen属性通常包含程序使用的主屏幕示例。

FirstViewModel.cs
public class FirstViewModel : ReactiveObject, IRoutableViewModel
{
    public string UrlPathSegment => "first";

    public IScreen HostScreen { get; }

    public FirstViewModel(IScreen screen = null)
    {
        HostScreen = screen ?? Locator.Current.GetService<IScreen>();
    }
}

接下来,创建一个新的UserControl,它将作为上面声明的FirstViewModel的视图。派生于ReactiveUserControl<TViewModel>类:

FirstView.xaml
<rxui:ReactiveUserControl
    x:Class="ReactiveRouting.FirstView"
    x:TypeArguments="vm:FirstViewModel"
    xmlns:rxui="https://reactiveui.net"
    xmlns:vm="clr-namespace:ReactiveRouting"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    Background="LightSeaGreen"
    mc:Ignorable="d">
    <TextBlock x:Name="PathTextBlock" Foreground="White" Margin="10" />
</rxui:ReactiveUserControl>

在本示例中,在代码隐藏文件中为视图设置了绑定。

FirstView.xaml.cs
public partial class FirstView : ReactiveUserControl<FirstViewModel>
{
    public FirstView()
    {
        InitializeComponent();
        this.WhenActivated(disposables =>
        {
            this.OneWayBind(ViewModel, x => x.UrlPathSegment, x => x.PathTextBlock.Text)
                .DisposeWith(disposables);
        });
    }
}

接下来,创建一个IScreen的实现,其中包含管理导航堆栈的RoutingState

MainViewModel.cs
public class MainViewModel : ReactiveObject, IScreen
{
    // The Router associated with this Screen.
    // Required by the IScreen interface.
    public RoutingState Router { get; }

    // The command that navigates a user to first view model.
    public ReactiveCommand<Unit, IRoutableViewModel> GoNext { get; }

    // The command that navigates a user back.
    public ReactiveCommand<Unit, Unit> GoBack { get; }

    public MainViewModel()
    {
        // Initialize the Router.
        Router = new RoutingState();

        // Router uses Splat.Locator to resolve views for
        // view models, so we need to register our views
        // using Locator.CurrentMutable.Register* methods.
        //
        // Instead of registering views manually, you 
        // can use custom IViewLocator implementation,
        // see "View Location" section for details.
        //
        Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor<FirstViewModel>));

        // Manage the routing state. Use the Router.Navigate.Execute
        // command to navigate to different view models. 
        //
        // Note, that the Navigate.Execute method accepts an instance 
        // of a view model, this allows you to pass parameters to 
        // your view models, or to reuse existing view models.
        //
        GoNext = ReactiveCommand.CreateFromObservable(() => Router.Navigate.Execute(new FirstViewModel()));

        // You can also ask the router to go back.
        GoBack = Router.NavigateBack;
    }
}

接下来,需要将RoutedViewHost XAML控件放置到包含可路由视图和视图模型的视图中。将视图模型MainViewModel.Router属性绑定到RoutedViewHost.Router属性。RoutedViewHost控件是特定于平台的,包含在诸如ReactiveUI.WPF、ReactiveUI.XamForms、ReactiveUI.WinForms之类的包中。

MainWindow.xaml
<rxui:ReactiveWindow
    xmlns:rxui="https://reactiveui.net" 
    x:Class="ReactiveRouting.MainWindow"
    x:TypeArguments="vm:MainViewModel"
    xmlns:vm="clr-namespace:ReactiveRouting"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="ReactiveUI.Routing" Height="360" Width="620">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <rxui:RoutedViewHost 
            Grid.Row="0" 
            x:Name="RoutedViewHost"
            HorizontalContentAlignment="Stretch"
            VerticalContentAlignment="Stretch" />
        <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="15">
            <Button x:Name="GoNextButton" Content="Go to first" />
            <Button x:Name="GoBackButton" Content="Go back" Margin="5 0 0 0" />
        </StackPanel>
    </Grid>
</rxui:ReactiveWindow>
MainWindow.xaml.cs

下面是上面声明的MainWindow的隐藏代码。这里使用WPF的ReactiveWindow<TViewModel>。对于Xamarin.Forms,通常应为ReactiveMasterDetailPage<TViewModel>。此步骤主要目的是将MainViewModel.Router属性绑定到RoutedViewHost.Router属性,因此RoutedViewHost控件才会显示适当的视图。

// We use ReactiveWindow here for WPF, but could actually use
// ReactiveUserControl or a custom IViewFor implementation. For
// Xamarin.Forms, use ReactiveMasterDetailPage.
public partial class MainWindow : ReactiveWindow<MainViewModel>
{
    public MainWindow()
    {
        InitializeComponent();
        ViewModel = new MainViewModel();
        this.WhenActivated(disposables =>
        {
            // Bind the view model router to RoutedViewHost.Router property.
            this.OneWayBind(ViewModel, x => x.Router, x => x.RoutedViewHost.Router)
                .DisposeWith(disposables);
            this.BindCommand(ViewModel, x => x.GoNext, x => x.GoNextButton)
                .DisposeWith(disposables);
            this.BindCommand(ViewModel, x => x.GoBack, x => x.GoBackButton)
                .DisposeWith(disposables);
        });
    }
}

现在,ReactiveUI的视图模型优先路由应该可以按预期工作了。IScreenRoutedViewHost可以嵌套任意数量的层数,而不会影响路由。但是,这仅适用于XAML页面;对于模态对话框和弹出对话框,交互是更好的选择。

视图定位

重写默认的IViewLocator实现,以避免必须手动注册视图。在路由引导阶段,使用Locator.CurrentMutable.RegisterLazySingleton注册视图定位器。有关详细信息,请参见视图定位

public class SimpleViewLocator : IViewLocator
{
    public IViewFor ResolveView<T>(T viewModel, string contract = null) where T : class
    {
        if (viewModel is FirstViewModel)
            return new FirstView { ViewModel = viewModel };
        throw new Exception($"Could not find the view for view model {typeof(T).Name}.")
    }
}

// Register the SimpleViewLocator.
Locator.CurrentMutable.RegisterLazySingleton(() => new SimpleViewLocator(), typeof(IViewLocator));

程序集扫描

要在程序及其关联的视图中注册所有视图模型,可以使用下面的代码:

// Splat uses assembly scanning here to register all views and view models.
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly());

原文 https://www.reactiveui.net/docs/handbook/routing

发表评论

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