【问题标题】:Pattern for implementing ReactUI.XamForms Master Detail Page用于实现 ReactUI.XamForms 主详细信息页面的模式
【发布时间】:2018-09-01 19:29:20
【问题描述】:

我目前在 Xamarin Forms 项目中工作,并尝试将 ReactiveUI 与 Master Detail Page 导航标准 Xamarin Forms 项目一起使用。

在阅读了 ReactiveUI 网站上的文档以及 Kent Boogaart 的“你、我和 ReactiveUI”一书后,我仍然不知道如何使用 ReactiveUI 和 Xamarin Forms 进行主从设置以进行应用导航。

我正在引导应用程序,它会加载根视图(主详细信息页面):

internal sealed class AppBootstrap: ReactiveObject, IScreen
{
    public AppBootstrap()
    {
        RegisterDependencies();

        this
            .Router
            .NavigateAndReset
            .Execute(GetRootViewModel())
            .Subscribe();
    }

    public RoutingState Router { get; } = new RoutingState();

    public Page GetMainPage() => new RoutedViewHost();

    private void RegisterDependencies()
    {
        Locator.CurrentMutable.RegisterConstant(this, typeof(IScreen));

        /*View Model Registrations*/
        Locator.CurrentMutable.Register(() => new RootView(), typeof(IViewFor<RootViewModel>));
        Locator.CurrentMutable.Register(()=> new MenuView(), typeof(IViewFor<MenuViewModel>));
        Locator.CurrentMutable.Register(()=> new DetailView(), typeof(IViewFor<DetailViewModel>));

        /*Service Registrations*/
    }

    private IRoutableViewModel GetRootViewModel()
    {
        //Add check for login here
        return new RootViewModel(this);
    }
}

然后在我的 RootViewModel 中:

public sealed class RootViewModel: ViewModelBase
{
    private Page _masterPage;
    private Page _detailPage;

    public RootViewModel(IScreen hostScreen) : base(hostScreen)
    { }

    public Page MasterPage
    {
        get => _masterPage;
        set => this.RaiseAndSetIfChanged(ref _masterPage, value);
    }

    public Page DetailPage
    {
        get => _detailPage;
        set => this.RaiseAndSetIfChanged(ref _detailPage, value);
    }

    public override Action<CompositeDisposable> OnActivated()
    {
        //What do I do?
    }
}

在谷歌搜索和搜索堆栈溢出之后,这里似乎没有答案,'documentation' 也没有太大帮助。 ReactiveUI 网站上列出的示例以及所有在线博客文章都只处理单页导航;与大多数现实世界的手机应用程序实现的更标准的基于抽屉的导航相反。

任何关于做什么的帮助或指向实际工作的基于主详细信息页面的导航的链接都将非常有帮助,因为我刚刚尝试找到任何实质内容时失败了。

【问题讨论】:

  • 你介意在GitHub.com/ReactiveUI/website 上打开一个问题吗?似乎我们需要修复文档中的一个重要漏洞。
  • @GlennWatson 没问题,我会打开一个问题。
  • 你好安德鲁。我很好奇你最后是怎么解决这个问题的?我在这里做同样的事情,因为我正在尝试路由链接到主详细信息页面的视图模型,但我不知道如何做到这一点。

标签: xamarin.forms navigation master-detail reactiveui


【解决方案1】:

MasterDetailPage 缺乏文档是对的。感谢您在网站上创建该问题。在我们更新之前,这是一种方法。

在此示例中,我假设详细信息页面的每个项目都具有完全相同的布局。即使这不适合您的用例,也很容易对其进行自定义以使其适用于各种布局。

我们无需在每次选择项目时都创建一个新的 DetailViewModel 和 DetailPage,而是简单地更换模型。视图侦听此更改并重新绑定。

public class MyMasterDetailViewModel : ReactiveObject, IRoutableViewModel
{
    private IScreen _hostScreen;

    public MyMasterDetailViewModel(IScreen hostScreen = null)
    {
        _hostScreen = hostScreen ?? Locator.Current.GetService<IScreen>();

        var cellVms = GetData().Select(model => new CustomCellViewModel(model));
        MyList = new ObservableCollection<CustomCellViewModel>(cellVms);

        // Set the first list item as the default detail view content.
        Detail = new DetailViewModel();
        Detail.Model = cellVms.First().Model;

        // Swap out the detail's model property every time the user selects an item.
        this.WhenAnyValue(x => x.Selected)
            .Where(x => x != null)
            .Subscribe(cellVm => Detail.Model = cellVm.Model);
    }

    private CustomCellViewModel _selected;
    public CustomCellViewModel Selected
    {
        get => _selected;
        set => this.RaiseAndSetIfChanged(ref _selected, value);
    }

    public DetailViewModel Detail { get; }

    public ObservableCollection<CustomCellViewModel> MyList { get; }
}

...

public partial class MyMasterDetailPage : ReactiveMasterDetailPage<MyMasterDetailViewModel>
{
    public MyMasterDetailPage()
    {
        InitializeComponent();

        ViewModel = new MasterDetailViewModel();
        Detail = new NavigationPage(new DetailPage(ViewModel.Detail));

        this.WhenActivated(
            disposables =>
            {
                this
                    .OneWayBind(ViewModel, vm => vm.MyList, v => v.MyListView.ItemsSource)
                    .DisposeWith(disposables);
                this
                    .Bind(ViewModel, vm => vm.Selected, v => v.MyListView.SelectedItem)
                    .DisposeWith(disposables);
                this
                    .WhenAnyValue(x => x.ViewModel.Selected)
                    .Where(x => x != null)
                    .Subscribe(
                        model =>
                        {
                            // Hide the master list every time the user selects an item
                            // and reset the SelectedItem "trigger."
                            MyListView.SelectedItem = null;
                            IsPresented = false;
                        })
                    .DisposeWith(disposables);
            });
    }
}

...

public class DetailViewModel : ReactiveObject
{
    private CustomData _model;

    public CustomData Model
    {
        get => _model;
        set => this.RaiseAndSetIfChanged(ref _model, value);
    }

    public string Title => Model.Title;
}

...

public partial class DetailPage : ReactiveContentPage<DetailViewModel>
{
    public DetailPage(DetailViewModel viewModel)
    {
        InitializeComponent();

        ViewModel = viewModel;

        this.WhenActivated(
            disposables =>
            {
                this
                    .WhenAnyValue(x => x.ViewModel.Model)
                    .Where(x => x != null)
                    .Subscribe(model => PopulateFromModel(model))
                    .DisposeWith(disposables);
            });
    }

    private void PopulateFromModel(MyModel model)
    {
        Title = model.Title;
        TitleLabel.Text = model.Title;
    }
}

...

<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveMasterDetailPage
         xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
         xmlns:local="clr-namespace:XamFormsSandbox"
         x:Class="XamFormsSandbox.MyMasterDetailPage"
         x:TypeArguments="local:MyMasterDetailViewModel"
         NavigationPage.HasNavigationBar="False">
    <MasterDetailPage.Master>
        <ContentPage Title="Master">
            <StackLayout>
                <ListView x:Name="MyListView">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <local:CustomCell />
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackLayout>
        </ContentPage>
    </MasterDetailPage.Master>
</rxui:ReactiveMasterDetailPage>

请注意,我在上面的 XAML 中隐藏了带有 NavigationPage.HasNavigationBar="False" 的 RoutedViewHost 导航栏。否则,我们将有两个相互重叠的导航栏。

更新

这里是示例项目的链接(目前是 PR):https://github.com/reactiveui/ReactiveUI/pull/1741

【讨论】:

  • 我试图让它的修改版本工作,每次我收到一个空参数异常,说主页对于母版页是空的。它闯入自动生成的 XAML 类,我分配给 master 的视图为空。现在我已经将我的视图定义为一个普通的内容页面。它是否需要来自 reactiveUI 家族才能工作?
  • 主页面可以是普通页面。实际上,由于 master 没有做任何有趣的事情(只是保存了一个单元格列表),我修改了我的答案以在 XAML 中内联该页面。我还从 XAML 中删除了 MasterDetail.Detail,并改为在代码隐藏中分配它。然后将 Detail 绑定代码移至 DetailPage.cs。我即将发布 Github 存储库,所以我很快会再次评论并提供链接。
  • 好的,整理一个样例项目,做个PR。用链接更新了答案。如果您有任何问题,请告诉我。
  • 好的,谢谢,我将内联我的母版页并报告是否解决了 null 问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-23
  • 1970-01-01
  • 2016-10-07
  • 2020-01-28
  • 2014-11-08
相关资源
最近更新 更多