无法使用 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)}");
}
我找到了答案:
- 我们不应将 MasterDetailPage 包装在 NavigationPage 中。而是将内容包装在您希望后退按钮显示的导航页面中。就我而言,我需要将 ItemsPage 包装在 Navigation
中
在App.xaml.cs
var navigationPage = $"{nameof(Views.MainPage)}/{nameof(NavigationPage)}/{nameof(ItemsPage)}";
_ = this.NavigationService.NavigateAsync($"{navigationPage}");
- 在 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);
- 在 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;
}
}
我使用默认的 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)}");
}
我找到了答案:
- 我们不应将 MasterDetailPage 包装在 NavigationPage 中。而是将内容包装在您希望后退按钮显示的导航页面中。就我而言,我需要将 ItemsPage 包装在 Navigation 中
在App.xaml.cs
var navigationPage = $"{nameof(Views.MainPage)}/{nameof(NavigationPage)}/{nameof(ItemsPage)}";
_ = this.NavigationService.NavigateAsync($"{navigationPage}");
- 在 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);
- 在 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;
}
}