.NET MAUI Shell 导航
.NET Multi-Platform App UI (.NET MAUI) Shell 包含基于 URI 的导航体验,该体验使用路由导航到应用中的任何页面,而无需遵循设置的导航层次结构。 此外,它还能够向后导航,不必访问导航堆栈上的所有页面。
Shell 类定义以下与导航相关的属性:
- BackButtonBehavior 属于 BackButtonBehavior 类型,是用于定义“后退”按钮行为的附加属性。
CurrentItem
属于ShellItem
类型,是当前选定的项。CurrentPage
属于Page 类型,是当前显示的页面。CurrentState
属于ShellNavigationState
类型,是当前 Shell 的导航状态。Current
,类型 Shell,提供对当前 Shell 的访问权限。
BackButtonBehavior、CurrentItem
和 CurrentState
属性由 BindableProperty 对象提供支持,这意味着这些属性可以作为数据绑定的目标。
导航是通过从 Shell 类调用 GoToAsync 方法来执行的。 即将执行导航时,将触发 Navigating
事件,并在导航完成时触发 Navigated
事件。
注意
仍然可以使用 Navigation
属性在 Shell 应用中的页面之间执行导航。 有关详细信息,请参阅执行无模式导航。
路由
导航是在 Shell 应用中通过指定要导航到的 URI 执行的。 导航 URI 可以有三个组件:
- 一个路由,它定义了作为 Shell 视觉层次结构的一部分存在的内容的路径。
- 一个页。 Shell 视觉层次结构中不存在的页面可以从 Shell 应用中的任何位置推送到堆叠导航。 例如,不会在 Shell 视觉层次结构中定义详细信息页,但可以根据需要将其推送到导航堆栈。
- 一个或多个查询参数。 查询参数是可以在导航时传递到目标页的参数。
当导航 URI 包含所有三个组件时,结构为://route/page?queryParameters
注册路由
可以通过 FlyoutItem、TabBar、Tab 和 ShellContent 对象的 Route
属性在这些对象上定义路由:
<Shell ...>
<FlyoutItem ...
Route="animals">
<Tab ...
Route="domestic">
<ShellContent ...
Route="cats" />
<ShellContent ...
Route="dogs" />
</Tab>
<ShellContent ...
Route="monkeys" />
<ShellContent ...
Route="elephants" />
<ShellContent ...
Route="bears" />
</FlyoutItem>
<ShellContent ...
Route="about" />
...
</Shell>
注意
Shell 层次结构中的所有项都有一个与之关联的路由。 如果未设置路由,则会在运行时生成一个路由。 但是,不能保证生成的路由在不同应用会话之间均一致。
上述示例创建了以下路由层次结构,可用于编程导航:
animals
domestic
cats
dogs
monkeys
elephants
bears
about
要导航到 dogs
路由的 ShellContent 对象,绝对路由 URI 为 //animals/domestic/dogs
。 同样,要导航到 about
路由的 ShellContent 对象,绝对路由 URI 为 //about
。
警告
如果检测到重复路由,则将在应用启动时引发 ArgumentException
。 如果层次结构中同一级别的两个或更多路由共享一个路由名称,也会引发此异常。
注册详细信息页路由
在 Shell 子类构造函数或者在调用路由前运行的任何其他位置中,可为未在 Shell 视觉层次结构中表示的任何详细信息页面显式地注册其他路由。 这是使用 Routing.RegisterRoute
方法完成的:
Routing.RegisterRoute("monkeydetails", typeof(MonkeyDetailPage));
Routing.RegisterRoute("beardetails", typeof(BearDetailPage));
Routing.RegisterRoute("catdetails", typeof(CatDetailPage));
Routing.RegisterRoute("dogdetails", typeof(DogDetailPage));
Routing.RegisterRoute("elephantdetails", typeof(ElephantDetailPage));
此示例将 Shell 子类中未定义的详细信息页注册为路由。 然后可以使用基于 URI 的导航从应用中的任何位置导航到这些详细信息页面。 这些页面的路由被称为“全局路由”。
警告
如果 Routing.RegisterRoute
方法尝试将同一路由注册到两个或多个不同类型,将引发 ArgumentException
。
此外,如果需要,页面可在不同的路由层次结构上注册:
Routing.RegisterRoute("monkeys/details", typeof(MonkeyDetailPage));
Routing.RegisterRoute("bears/details", typeof(BearDetailPage));
Routing.RegisterRoute("cats/details", typeof(CatDetailPage));
Routing.RegisterRoute("dogs/details", typeof(DogDetailPage));
Routing.RegisterRoute("elephants/details", typeof(ElephantDetailPage));
此示例启用上下文页面导航,其中从 monkeys
路由的页面导航到 details
路由将显示 MonkeyDetailPage
。 同样,从 elephants
路由的页面导航到 details
路由将显示 ElephantDetailPage
。 有关详细信息,请参阅上下文导航。
注意
如果需要,已经使用 Routing.RegisterRoute
方法注册其路由的页面可以通过 Routing.UnRegisterRoute
方法注销。
执行导航
要执行导航,必须首先获得对 Shell 子类的引用。 可以通过属性获取 Shell.Current
此引用。 然后,可以通过调用 Shell 对象上的 GoToAsync 方法来执行导航。 该方法导航到 ShellNavigationState
并返回 Task
,后者将在导航动画完成后完成。 ShellNavigationState
对象是通过 GoToAsync 方法从 string
或 Uri
构造的,并将其 Location
属性设置为 string
或 Uri
参数。
重要说明
当导航到 Shell 视觉层次结构中的路由时,不会创建导航堆栈。 但是,当导航到不在 Shell 视觉层次结构中的页面时,将创建一个导航堆栈。
可以通过 Shell.Current.CurrentState
属性检索 Shell 对象的当前导航状态,该属性包括 Location
属性中显示的路由的 URI。
绝对路由
可以通过将一个有效的绝对 URI 指定为 GoToAsync 方法的参数来执行导航:
await Shell.Current.GoToAsync("//animals/monkeys");
本示例导航到 monkeys
路由的页面,该路由在 ShellContent 对象上定义。 表示 monkeys
路由的 ShellContent 对象是其路由为 animals
的 FlyoutItem 对象的子对象。
相对路由
还可以通过将一个有效的相对 URI 指定为 GoToAsync 方法的参数来执行导航。 路由系统将尝试将 URI 与 ShellContent 对象进行匹配。 因此,如果应用中的所有路由都是唯一的,则仅可通过将唯一路由名称指定为相对 URI 来执行导航。
支持下列相对路由格式:
格式 | 描述 |
---|---|
路由 | 将从当前位置向上搜索路由层次结构来获取指定的路由。 匹配的页面将被推送到导航堆栈。 |
/路由 | 将在指定路由中从当前位置向下搜索路由层次结构。 匹配的页面将被推送到导航堆栈。 |
//路由 | 将从当前位置向上搜索路由层次结构来获取指定的路由。 匹配的页面将替换导航堆栈。 |
///路由 | 将从当前位置向下搜索路由层次结构来获取指定的路由。 匹配的页面将替换导航堆栈。 |
以下示例导航到 monkeydetails
路由的页面:
await Shell.Current.GoToAsync("monkeydetails");
在本例中,在 monkeyDetails
路由中向上搜索层次结构,直到找到匹配的页面。 找到该页面后,会将它推送到导航堆栈。
上下文导航
相对路由支持上下文导航。 以下列路由层次结构为例:
monkeys
details
bears
details
当显示 monkeys
路由的注册页时,导航到 details
路由将显示 monkeys/details
路由的注册页。 同样,当显示 bears
路由的注册页时,导航到 details
路由将显示 bears/details
路由的注册页。 有关如何注册本示例中的路由的信息,请参阅注册页面路由。
向后导航
向后导航可以通过将“..”指定为 GoToAsync 方法的参数来执行:
await Shell.Current.GoToAsync("..");
通过“..”执行的向后导航还可与路由结合使用:
await Shell.Current.GoToAsync("../route");
在本例中,会执行向后导航,然后导航到指定的路由。
重要说明
仅当向后导航将你置于路由层次结构中的当前位置以导航到指定路由时,才可在向后导航后导航到指定路由。
同样,可以向后导航多次,然后导航到指定路由:
await Shell.Current.GoToAsync("../../route");
在本例中,会执行向后导航两次,然后导航到指定的路由。
此外,在向后导航时,可通过查询属性传递数据:
await Shell.Current.GoToAsync($"..?parameterToPassBack={parameterValueToPassBack}");
在本例中,会执行向后导航,并将查询参数值传递到上一页上的查询参数。
注意
可将查询参数追加到任何向后导航请求。
若要详细了解如何在导航时传递数据,请参阅传递数据。
无效路由
以下路由格式无效:
Format | 说明 |
---|---|
//page 或 ///page | 全局路由当前不能是导航堆栈上的唯一页面。 因此,不支持绝对路由到全局路由。 |
使用这些路由格式会导致引发 Exception
。
警告
尝试导航到不存在的路由会导致引发 ArgumentException
异常。
调试导航
一些 Shell 类通过 DebuggerDisplayAttribute
修饰,它指定调试程序如何显示类或字段。 这可以通过显示与导航请求相关的数据来帮助调试导航请求。 例如,下面的屏幕截图显示了 Shell.Current
对象的 CurrentItem
和 CurrentState
属性:
在本例中,类型为 FlyoutItem 的 CurrentItem
属性显示了 FlyoutItem 对象的标题和路由。 同样,类型为 ShellNavigationState
的 CurrentState
属性会显示 Shell 应用中所显示的路由的 URI。
导航堆栈
Tab 类定义类型为 IReadOnlyList<Page>
的 Stack
属性,该属性表示 Tab 中的当前导航堆栈。该类还提供以下可重写的导航方法:
GetNavigationStack
,返回IReadOnlyList<Page>
,表示当前导航堆栈。OnInsertPageBefore
,调用INavigation.InsertPageBefore
时会对其进行调用。OnPopAsync
,返回Task<Page>
,调用INavigation.PopAsync
时会对其进行调用。OnPopToRootAsync
,返回Task
,调用INavigation.OnPopToRootAsync
时会对其进行调用。OnPushAsync
,返回Task
,调用INavigation.PushAsync
时会对其进行调用。OnRemovePage
,调用INavigation.RemovePage
时会对其进行调用。
下面的示例演示如何重写 OnRemovePage
方法:
public class MyTab : Tab
{
protected override void OnRemovePage(Page page)
{
base.OnRemovePage(page);
// Custom logic
}
}
在此示例中,应在 Shell 视觉对象层次结构中使用 MyTab
对象,而不是使用 Tab 对象。
导航事件
Shell 类定义 Navigating
事件,该事件在即将执行导航时触发,原因可能是编程导航或用户交互。 Navigating
事件随附的 ShellNavigatingEventArgs
对象提供以下属性:
属性 | 类型 | 描述 |
---|---|---|
Current |
ShellNavigationState |
当前页的 URI。 |
Source |
ShellNavigationSource |
发生的导航类型。 |
Target |
ShellNavigationState |
表示导航目标位置的 URI。 |
CanCancel |
bool |
指示是否可以取消导航的值。 |
Cancelled |
bool |
指示是否已取消导航的值。 |
此外,ShellNavigatingEventArgs
类还提供 Cancel
方法和 GetDeferral
方法,前者可用于取消导航,后者返回可用于完成导航的 ShellNavigatingDeferral
令牌。 有关导航延迟的详细信息,请参阅导航延迟。
Shell 类还定义 Navigated
事件,该事件在导航完成时触发。 Navigated
事件随附的 ShellNavigatedEventArgs
对象提供以下属性:
属性 | 类型 | 描述 |
---|---|---|
Current |
ShellNavigationState |
当前页的 URI。 |
Previous |
ShellNavigationState |
上一页的 URI。 |
Source |
ShellNavigationSource |
发生的导航类型。 |
重要说明
触发 Navigating
事件时将调用 OnNavigating
方法。 同样,触发 Navigated
事件时将调用 OnNavigated
方法。 这两种方法都可以在 Shell 子类中被替代,以截获导航请求。
ShellNavigatedEventArgs
和 ShellNavigatingEventArgs
类均具有类型为 ShellNavigationSource
的 Source
属性。 此枚举提供下列值:
Unknown
Push
Pop
PopToRoot
Insert
Remove
ShellItemChanged
ShellSectionChanged
ShellContentChanged
因此,可以在 OnNavigating
替代中截获导航,并可根据导航源执行操作。 例如,下面的代码显示页面数据未保存时如何取消向后导航:
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
// Cancel any back navigation.
if (args.Source == ShellNavigationSource.Pop)
{
args.Cancel();
}
}
导航延迟
可以根据用户的选择截获并完成或取消 Shell 导航。 此操作可通过以下方式实现:重写 Shell 子类中的 OnNavigating
方法,并对 ShellNavigatingEventArgs
对象调用 GetDeferral
方法。 此方法返回 ShellNavigatingDeferral
令牌,该令牌有一个可用于完成导航请求的 Complete
方法:
public MyShell : Shell
{
// ...
protected override async void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
ShellNavigatingDeferral token = args.GetDeferral();
var result = await DisplayActionSheet("Navigate?", "Cancel", "Yes", "No");
if (result != "Yes")
{
args.Cancel();
}
token.Complete();
}
}
在此示例中,将显示一个操作工作表,邀请用户完成导航请求,或取消导航。 可通过对 ShellNavigatingEventArgs
对象调用 Cancel
方法来取消导航。 通过对在 ShellNavigatingEventArgs
对象上使用 GetDeferral
方法检索到的 ShellNavigatingDeferral
令牌调用 Complete
方法来完成导航。
警告
如果用户在存在挂起的导航延迟的情况下尝试导航,GoToAsync 方法将引发 InvalidOperationException
。
传递数据
执行基于 URI 的编程导航时,可将基元数据作为基于字符串的查询参数传递。 为此,可在路由后追加 ?
,后接查询参数 ID、=
和一个值:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}");
}
此示例在 CollectionView 中检索当前选中的大象,并导航到 elephantdetails
路由,将 elephantName
作为查询参数传递。
传递多个使用基于对象的导航数据
基于多用途对象的导航数据可以通过指定 IDictionary<string, object>
参数的 GoToAsync 重载传递:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
Animal animal = e.CurrentSelection.FirstOrDefault() as Animal;
var navigationParameter = new Dictionary<string, object>
{
{ "Bear", animal }
};
await Shell.Current.GoToAsync($"beardetails", navigationParameter);
}
本示例检索 CollectionView 中当前选择的熊,作为 Animal
。 Animal
对象将添加到具有键 Bear
的 Dictionary
。 然后,将执行到 beardetails
路由的导航,并将 Dictionary
作为导航参数传递。
作为 IDictionary<string, object>
参数传递的任何数据都会在页面生存期内保留在内存中,并且直到从导航堆栈中删除页面才会释放。 这可能会造成问题,如以下应用场景所示:
Page1
使用 GoToAsync 方法导航到Page2
,传入一个名为MyData
的对象。Page2
然后接收MyData
作为查询参数。Page2
使用 GoToAsync 方法导航到Page3
,不传递任何数据。Page3
使用 GoToAsync 方法向后导航。 然后Page2
再次接收MyData
作为查询参数。
虽然在许多应用场景下是可取的,但如果不需要,则应在页面首次收到该方法后使用 Clear
方法清除 IDictionary<string, object>
参数。
传递一次性使用的基于对象的导航数据
一次性使用的基于对象的导航数据可以通过指定 ShellNavigationQueryParameters 参数的 GoToAsync 重载传递。 ShellNavigationQueryParameters 对象适用于在导航发生后清除的一次性使用导航数据。 以下示例演示在传递一次性使用数据时导航:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
Animal animal = e.CurrentSelection.FirstOrDefault() as Animal;
var navigationParameter = new ShellNavigationQueryParameters
{
{ "Bear", animal }
};
await Shell.Current.GoToAsync($"beardetails", navigationParameter);
}
本例检索 CollectionView 中当前选择的熊,作为添加到 ShellNavigationQueryParameters 对象中的 Animal
。 然后,将执行到路由的 beardetails
导航,并将 ShellNavigationQueryParameters 对象作为导航参数传递。 导航发生后,ShellNavigationQueryParameters 对象中的数据将被清除。
接收导航数据
可以通过两种方法接收导航数据:
- 对于每个查询参数,可以使用 QueryPropertyAttribute 修饰表示要导航到的页面的类或页面的
BindingContext
的类。 有关更多信息,请参阅使用查询属性的特性处理导航数据。 - 表示要导航到的页面的类,或页面的类
BindingContext
可以实现 IQueryAttributable 接口。 有关更多信息,请参阅使用单一方法处理导航数据。
使用查询属性特性处理导航数据
对于每个基于字符串的查询参数、基于对象的导航参数或 ShellNavigationQueryParameters 对象,可以通过使用 QueryPropertyAttribute 修饰接收类来接收导航数据:
[QueryProperty(nameof(Bear), "Bear")]
public partial class BearDetailPage : ContentPage
{
Animal bear;
public Animal Bear
{
get => bear;
set
{
bear = value;
OnPropertyChanged();
}
}
public BearDetailPage()
{
InitializeComponent();
BindingContext = this;
}
}
在本示例中,QueryPropertyAttribute 的第一个参数指定将接收数据的属性的名称,第二个参数指定参数 ID。因此,上述示例中的 QueryPropertyAttribute 指定 Bear
属性将接收在 GoToAsync 方法调用中传入 Bear
导航参数的数据。
重要
通过 QueryPropertyAttribute 接收的基于字符串的查询参数值将自动进行 URL 解码。
警告
使用 QueryPropertyAttribute 导航数据接收不是安全的,不应与完整修整或 NativeAOT 一起使用。 相反,应在需要接受查询参数的类型上实现 IQueryAttributable 接口。 有关详细信息,请参阅 使用单个方法处理导航数据、 剪裁 .NET MAUI 应用和 本机 AOT 部署。
使用单一方法处理导航数据
可以通过在接收类上实现 IQueryAttributable 接口来接收导航数据。 IQueryAttributable 接口指定必须由实现类来实现 ApplyQueryAttributes
方法。 此方法具有一个 IDictionary<string, object>
类型的 query
变量,其中包含在导航过程中传递的任何数据。 字典中的每个键都是一个查询参数 ID,其值对应于表示数据的对象。 使用此方法的优点是可以使用单一方法来处理导航数据,在有多个需要作为整体处理的导航数据项时,这会很有用。
以下示例显示了实现 IQueryAttributable 接口的视图模型类:
public class MonkeyDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
public Animal Monkey { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
Monkey = query["Monkey"] as Animal;
OnPropertyChanged("Monkey");
}
...
}
在此示例中,ApplyQueryAttributes
方法检索与 query
字典中的 Monkey
键对应的对象,且该键已作为参数传递到 GoToAsync 方法调用。
重要说明
通过 IQueryAttributable 接口接收的基于字符串的查询参数值不会自动进行 URL 解码。
传递和处理多个数据项
可以通过将多个基于字符串的查询参数与 &
连接来传递这些参数。 例如,以下代码传递两个数据项:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
string elephantLocation = (e.CurrentSelection.FirstOrDefault() as Animal).Location;
await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}&location={elephantLocation}");
}
此代码示例在 CollectionView 中检索当前选中的大象,并导航到 elephantdetails
路由,将 elephantName
和 elephantLocation
作为查询参数传递。
若要接收多个数据项,对于每个基于字符串的查询参数,可以使用 QueryPropertyAttribute 修饰表示导航到的页面的类或页面的 BindingContext
的类:
[QueryProperty(nameof(Name), "name")]
[QueryProperty(nameof(Location), "location")]
public partial class ElephantDetailPage : ContentPage
{
public string Name
{
set
{
// Custom logic
}
}
public string Location
{
set
{
// Custom logic
}
}
...
}
在此示例中,每个查询参数的类使用 QueryPropertyAttribute 进行修饰。 第一个 QueryPropertyAttribute 指定 Name
属性将接收在 name
查询参数中传递的数据,而第二个 QueryPropertyAttribute 指定 Location
属性将接收在 location
查询参数中传递的数据。 在这两种情况下,查询参数值都是在 GoToAsync 方法调用的 URI 中指定的。
警告
使用 QueryPropertyAttribute 导航数据接收不是安全的,不应与完整修整或 NativeAOT 一起使用。 相反,应在需要接受查询参数的类型上实现 IQueryAttributable 接口。 有关详细信息,请参阅 剪裁 .NET MAUI 应用 和 本机 AOT 部署。
或者,在表示导航到的页面的类或页面的 BindingContext
的类上实现 IQueryAttributable 接口,通过单一方法来处理导航数据:
public class ElephantDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
public Animal Elephant { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
string name = HttpUtility.UrlDecode(query["name"].ToString());
string location = HttpUtility.UrlDecode(query["location"].ToString());
...
}
...
}
在该示例中,ApplyQueryAttributes
方法从 GoToAsync 方法调用中的 URI 检索 name
和 location
查询参数的值。
注意
执行基于路由的导航时,可以同时传递基于字符串的查询参数和基于对象的导航参数。
“后退”按钮行为
通过将 BackButtonBehavior 附加属性设置为 BackButtonBehavior 对象,可以重新定义“后退”按钮的外观和行为。 BackButtonBehavior 类定义以下属性:
Command
属于 ICommand 类型,在按下“后退”按钮时执行。CommandParameter
属于object
类型,是传递给Command
的参数。IconOverride
属于 ImageSource 类型,是用于“后退”按钮的图标。IsEnabled
属于boolean
类型,指示是否已启用“后退”按钮。 默认值为true
。IsVisible
,类型为boolean
,指示“后退”按钮是否可见。 默认值为true
。TextOverride
属于string
类型,是用于“后退”按钮的文本。
所有这些属性都由 BindableProperty 对象提供支持,这意味着这些属性可以作为数据绑定的目标。 每个 BindableProperty 都有一个 OneTime
绑定模式,这意味着只有在 BindingContext
更改时,数据才从源传输到目标。
所有这些属性都由 BindableProperty 对象提供支持,这意味着这些属性可以作为数据绑定的目标。 Command
、CommandParameter
、IconOveride
和 TextOveride
BindableProperty 对象具有 OneTime
绑定模式,这意味着只有在 BindingContext
更改时,数据才从源传输到目标。 IsEnabled
和 IsVisible
BindableProperty 对象具有 OneWay
绑定模式,这意味着数据会从源传输到目标。
下面的代码演示了重新定义“后退”按钮的外观和行为的示例:
<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>
将 Command
属性设置为按下“后退”按钮时执行的 ICommand,将 IconOverride
属性设置为用于“后退”按钮的图标: