无法使用 Prism.Forms Xamarin 将 MasterDetailPage 包装在 NavigationPage 中以显示后退按钮

Unable to wrap MasterDetailPage in NavigationPage with Prism.Forms Xamarin to show Back Button

我使用默认的 Xamarin.Forms 项目并尝试使用 Prism.Forms.

转换它们

我设法在 SplitView 中显示 MenuPage 并在 App.xaml.cs 中显示 NavigateAsync("MainPage/ItemsPage") 的内容 ItemsPage

接下来,我注意到当我导航到新页面时,我没有在 UWP 应用程序上显示后退按钮,而且我读到我需要将 MainPage 包装在 [=21= 中], 所以我尝试通过将 NavigationPage 注册到容器和 NavigateAsync("NavigationPage/MainPage/ItemsPage"), 但不幸的是我变得空白 UI.

我做错了什么?

如果这还不够,我可以提供更多代码。

在App.xaml.cs

protected override void OnInitialized()
{
    this.InitializeComponent();


    var navigationPage = $"{nameof(NavigationPage)}/{nameof(Views.MainPage)}/{nameof(ItemsPage)}";    // why does this shows blank UI?
    var navigationPage2 = $"{nameof(Views.MainPage)}/{nameof(Views.ItemsPage)}"; // this shows MenuPage on the menu and items page on the main content.
    _ = this.NavigationService.NavigateAsync($"{navigationPage}");
}

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation<NavigationPage>(nameof(NavigationPage));
    containerRegistry.RegisterForNavigation<AboutPage>(nameof(AboutPage));
    containerRegistry.RegisterForNavigation<ItemDetailPage>(nameof(ItemDetailPage));
    containerRegistry.RegisterForNavigation<ItemsPage>(nameof(ItemsPage));
    containerRegistry.RegisterForNavigation<MainPage>(nameof(MainPage));
    containerRegistry.RegisterForNavigation<MenuPage>(nameof(MenuPage));
    containerRegistry.RegisterForNavigation<NewItemPage>(nameof(NewItemPage));
}

在MainPage.xaml(这是MasterDetailPage)

<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:d="http://xamarin.com/schemas/2014/forms/design"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            xmlns:views="clr-namespace:XamarinApp.Views"
            x:Class="XamarinApp.Views.MainPage">

    <MasterDetailPage.Master>
        <views:MenuPage />
    </MasterDetailPage.Master>

</MasterDetailPage>

在MenuPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:b="clr-namespace:XamarinApp.Behaviors"
             xmlns:prism="http://prismlibrary.com"
             mc:Ignorable="d"
             x:Class="XamarinApp.Views.MenuPage"
             prism:ViewModelLocator.AutowireViewModel="True"
             Title="Menu">

    <StackLayout VerticalOptions="FillAndExpand">
        <ListView
            x:Name="ListViewMenu"
            HasUnevenRows="True"
            ItemsSource="{Binding MenuItems, Mode=OneTime}"
            SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}">
            <ListView.Behaviors>
                <b:EventToCommandBehavior
                    EventName="ItemSelected"
                    Command="{Binding ItemSelectedCommand, Mode=OneTime}"/>
            </ListView.Behaviors>
            <d:ListView.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>Item 1</x:String>
                    <x:String>Item 2</x:String>
                </x:Array>
            </d:ListView.ItemsSource>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid Padding="10">
                            <Label Text="{Binding Title}" d:Text="{Binding .}" FontSize="20"/>
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>

</ContentPage>

在 ItemsPage.xaml(虽然不那么相关)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:b="clr-namespace:XamarinApp.Behaviors"
             xmlns:prism="http://prismlibrary.com"
             mc:Ignorable="d"
             x:Class="XamarinApp.Views.ItemsPage"
             Title="{Binding Title}"
             x:Name="BrowseItemsPage"
             prism:ViewModelLocator.AutowireViewModel="True">

    <ContentPage.Behaviors>
        <b:EventToCommandBehavior
            EventName="Appearing"
            Command="{Binding LoadItemsCommand, Mode=OneTime}"/>
    </ContentPage.Behaviors>

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Add" Command="{Binding AddItemCommand, Mode=OneTime}"/>
    </ContentPage.ToolbarItems>

    <StackLayout Grid.Row="1">
        <ListView
            x:Name="ItemsListView"
            ItemsSource="{Binding Items, Mode=OneWay}"
            SelectedItem="{Binding SelectedItem,Mode=TwoWay}"
            VerticalOptions="FillAndExpand"
            HasUnevenRows="true"
            RefreshCommand="{Binding LoadItemsCommand, Mode=OneTime}"
            IsPullToRefreshEnabled="true"
            IsRefreshing="{Binding IsBusy, Mode=OneWay}"
            CachingStrategy="RecycleElement">
            <ListView.Behaviors>
                <b:EventToCommandBehavior
                    EventName="ItemSelected"
                    Command="{Binding OnItemSelectedCommand, Mode=OneTime}"/>
            </ListView.Behaviors>
            <d:ListView.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>First Item</x:String>
                    <x:String>Second Item</x:String>
                    <x:String>Third Item</x:String>
                    <x:String>Forth Item</x:String>
                    <x:String>Fifth Item</x:String>
                    <x:String>Sixth Item</x:String>
                </x:Array>
            </d:ListView.ItemsSource>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Padding="10">
                            <Label Text="{Binding Text}" 
                                d:Text="{Binding .}"
                                LineBreakMode="NoWrap" 
                                Style="{DynamicResource ListItemTextStyle}" 
                                FontSize="16" />
                            <Label Text="{Binding Description}" 
                                d:Text="Item descripton"
                                LineBreakMode="NoWrap"
                                Style="{DynamicResource ListItemDetailTextStyle}"
                                FontSize="13" />
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>

</ContentPage>

在ItemsPageViewModel.cs

private readonly INavigationService navigationService;
private ObservableCollection<Item> items;
private Item selectedItem;

public ItemsPageViewModel()
{
    this.Title = "Browse";
    this.Items = new ObservableCollection<Item>();
    this.LoadItemsCommand = new DelegateCommand(this.LoadItems, this.CanLoadItems).ObservesProperty(() => this.Items);
    this.OnItemSelectedCommand = new DelegateCommand(this.OnItemsSelected_Execute, this.OnItemsSelected_CanExecute).ObservesProperty(() => this.SelectedItem);
    this.AddItemCommand = new DelegateCommand(this.AddItem);

}

public ItemsPageViewModel(INavigationService navigationService)
    : this()
{
    this.navigationService = navigationService;
}

public ObservableCollection<Item> Items
{
    get => this.items;
    set => this.SetProperty(ref this.items, value);
}

public Item SelectedItem
{
    get => this.selectedItem;
    set => this.SetProperty(ref this.selectedItem, value);
}

public ICommand AddItemCommand { get; }

public ICommand OnItemSelectedCommand { get; }

public ICommand LoadItemsCommand { get; }

private bool CanLoadItems()
{
    return !(this.Items?.Any() ?? false);
}

private async void LoadItems()
{
    if (this.IsBusy)
    {
        return;
    }

    this.IsBusy = true;

    try
    {
        this.Items.Clear();
        var items = await this.DataStore.GetItemsAsync(true);
        foreach (var item in items)
        {
            this.Items.Add(item);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
    finally
    {
        this.IsBusy = false;
    }
}

private bool OnItemsSelected_CanExecute()
{
    return this.SelectedItem != null;
}

private async void OnItemsSelected_Execute()
{
    var parameters = new NavigationParameters
    {
        { "hash", this.selectedItem.ToString() },
    };

    await this.navigationService.NavigateAsync($"{nameof(ItemDetailPage)}", parameters);
    // When I navigated to the ItemDetailPage, I do not see the (SplitView/HamburgerMenu) PageMenu
    // and I do not see Back button

    // Manually deselect item.
    this.SelectedItem = null;
}

private async void AddItem()
{
    await this.navigationService.NavigateAsync($"{nameof(NewItemPage)}");
}

我找到了答案:

  1. 我们不应将 MasterDetailPage 包装在 NavigationPage 中。而是将内容包装在您希望后退按钮显示的导航页面中。就我而言,我需要将 ItemsPage 包装在 Navigation

在App.xaml.cs

var navigationPage = $"{nameof(Views.MainPage)}/{nameof(NavigationPage)}/{nameof(ItemsPage)}";
_ = this.NavigationService.NavigateAsync($"{navigationPage}");
  1. 在 ItemsPageViewModel 中,我只需要导航到目标页面而无需指定 MainPage 和 NavigationPage,因此会出现后退按钮(并保持 Hamburger 菜单完好无损)

在ItemsPageViewModel.cs

// keep this code intact from example above to navigate, have a back button on the new page, and also keep the hamburger menu intact.
await this.navigationService.NavigateAsync($"{nameof(ItemDetailPage)}", parameters);
  1. 在 MenuPageViewModel 中,我会通过以下方式调整导航以始终完整显示汉堡菜单:

在MenuPageViewModel.cs

private async void ItemSelectedHandler()
{
    if (this.SelectedMenuItem == null)
    {
        return;
    }

    var id = this.SelectedMenuItem.Id;
    switch (id)
    {
        case MenuItemType.Browse:
            // re-show the MenuPage in HamburgerMenu and wrap ItemsPage in NavigationPage.
            // However, navigating with this method will not show the back button in the new page.
            await this.navigationService.NavigateAsync($"/{nameof(MainPage)}/{nameof(NavigationPage)}/{nameof(ItemsPage)}");
            break;
        case MenuItemType.About:
            // re-show the MenuPage in HamburgerMenu and wrap ItemsPage in NavigationPage.
            // However, navigating with this method will not show the back button in the new page.
            await this.navigationService.NavigateAsync($"/{nameof(MainPage)}/{nameof(NavigationPage)}/{nameof(AboutPage)}");
            break;
    }
}