Шаблон для реализации мастер-страницы ReactUI.XamForms - PullRequest
0 голосов
/ 01 сентября 2018

В настоящее время я работаю в проекте Xamarin Forms и пытаюсь использовать ReactiveUI в стандартном проекте Xamarin Forms для навигации по страницам с основными данными.

После прочтения документации на веб-сайте ReactiveUI, а также книги Кента Бугаарта «Ты, я и ReactiveUI» я все еще теряюсь в том, как настроить мастер-детали для работы с ReactiveUI и формами Xamarin для навигации по приложениям.

У меня есть приложение, которое загружается при загрузке, который загружает корневое представление (Master Detail Page):

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?
    }
}

После поиска и поиска переполнения стека здесь, похоже, нет ответа, и 'документация' не слишком полезна. Оба примера, перечисленные на сайте ReactiveUI, а также все сообщения блога в Интернете, имеют дело только с одностраничной навигацией; Используйте более стандартную навигацию на основе ящиков, которую реализует большинство реальных телефонных приложений.

Любая помощь в отношении того, что делать или ссылки на фактическую рабочую навигацию на основе страниц с мастер-данными, была бы чрезвычайно полезной, поскольку я только что потерпел неудачу в своих попытках найти что-либо существенное.

1 Ответ

0 голосов
/ 02 сентября 2018

Вы правы насчет отсутствия документации для 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>

Обратите внимание, что я скрываю панель навигации RoutedViewHost с NavigationPage.HasNavigationBar="False" в XAML выше. В противном случае у нас было бы две панели навигации друг над другом.

UPDATE

Вот ссылка на пример проекта (в настоящее время PR): https://github.com/reactiveui/ReactiveUI/pull/1741

...