.NET 多平台应用 UI (.NET MAUI) Shell 包含基于 URI 的导航体验,该体验使用路线导航到应用中的任何页面,而无需遵循设置的导航层次结构。 此外,它还能够向后导航,不必访问导航堆栈上的所有页面。
Shell
类定义以下与导航相关的属性:
BackButtonBehavior
,属于
BackButtonBehavior
类型,是用于定义“后退”按钮行为的附加属性。
CurrentItem
,类型为
ShellItem
,是当前选定的项。
CurrentPage
,类型为
Page
,是当前显示的页面。
CurrentState
,属于
ShellNavigationState
类型,是当前
Shell
的导航状态。
Current
,属于
Shell
类型,是
Application.Current.MainPage
的类型转换别名。
BackButtonBehavior
、
CurrentItem
和
CurrentState
属性由
BindableProperty
对象提供支持,这意味着这些属性可以作为数据绑定的目标。
导航是通过从
Shell
类调用
GoToAsync
方法来执行的。 即将执行导航时,将触发
Navigating
事件,并在导航完成时触发
Navigated
事件。
仍可使用 属性在 Shell 应用中
Navigation
的页面之间执行导航。 有关详细信息,请参阅
执行无模式导航
。
导航是在 Shell 应用中通过指定要导航到的 URI 执行的。 导航 URI 可以有三个组件:
一个路由,它定义了作为 Shell 视觉层次结构的一部分存在的内容的路径。
一个页。 Shell 视觉层次结构中不存在的页面可以从 Shell 应用中的任何位置推送到堆叠导航。 例如,不会在 Shell 视觉层次结构中定义详细信息页,但可以根据需要将其推送到导航堆栈。
一个或多个查询参数。 查询参数是可以在导航时传递到目标页的参数。
当导航 URI 包含所有三个组件时,结构为://route/page?queryParameters
可以通过 、、
Tab
和
ShellContent
对象的属性
Route
定义
TabBar
FlyoutItem
路由:
<Shell ...>
<FlyoutItem ...
Route="animals">
<Tab ...
Route="domestic">
<ShellContent ...
Route="cats" />
<ShellContent ...
Route="dogs" />
<ShellContent ...
Route="monkeys" />
<ShellContent ...
Route="elephants" />
<ShellContent ...
Route="bears" />
</FlyoutItem>
<ShellContent ...
Route="about" />
</Shell>
Shell 层次结构中的所有项都有一个与之关联的路由。 如果未设置路由,则会在运行时生成一个路由。 但是,不保证生成的路由在不同的应用会话之间保持一致。
上述示例创建了以下路由层次结构,可用于编程导航:
animals
domestic
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 子类的引用。 通过将 App.Current.MainPage
属性转换为 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 即可执行导航。
支持下列相对路由格式:
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}");
在本例中,会执行向后导航,并将查询参数值传递到上一页上的查询参数。
可将查询参数追加到任何向后导航请求。
若要详细了解如何在导航时传递数据,请参阅传递数据。
以下路由格式无效:
一些 Shell 类通过 DebuggerDisplayAttribute
修饰,它指定调试程序如何显示类或字段。 这可以通过显示与导航请求相关的数据来帮助调试导航请求。 例如,下面的屏幕截图显示了 Shell.Current
对象的 CurrentItem
和 CurrentState
属性:
在本例中,类型为 FlyoutItem 的 CurrentItem
属性显示了 FlyoutItem 对象的标题和路由。 同样, CurrentState
类型 ShellNavigationState
为 的 属性显示 Shell 应用中所显示路由的 URI。
类Tab定义 类型Stack
IReadOnlyList<Page>
为 的属性,该属性表示 中的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
对象提供以下属性:
Property
此外,ShellNavigatingEventArgs
类还提供 Cancel
方法和 GetDeferral
方法,前者可用于取消导航,后者返回可用于完成导航的 ShellNavigatingDeferral
令牌。 有关导航延迟的详细信息,请参阅导航延迟。
Shell 类还定义 Navigated
事件,该事件在导航完成时触发。 随附 Navigated
事件的 ShellNavigatedEventArgs
对象提供以下属性:
Property
触发 Navigating
事件时将调用 OnNavigating
方法。 同样,触发 Navigated
事件时将调用 OnNavigated
方法。 这两种方法都可以在 Shell 子类中被替代,以截获导航请求。
ShellNavigatedEventArgs
和 ShellNavigatingEventArgs
类均具有 Source
属性,类型为 ShellNavigationSource
。 此枚举提供下列值:
Unknown
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
将添加到具有 Dictionary
键 Bear
的 。 然后,执行路由 beardetails
导航,并将 Dictionary
作为导航参数传递。
可以通过两种方法接收导航数据:
对于每个查询参数,可以使用 QueryPropertyAttribute
修饰表示要导航到的页面的类或页面的 BindingContext
的类。 有关更多信息,请参阅使用查询属性的特性处理导航数据。
表示要导航到的页面的类,或页面的类 BindingContext
可以实现 IQueryAttributable
接口。 有关更多信息,请参阅使用单一方法处理导航数据。
使用查询属性特性处理导航数据
对于每个基于字符串的查询参数和基于对象的导航参数,可以通过使用 QueryPropertyAttribute
修饰接收类来接收导航数据:
[QueryProperty(nameof(Bear), "Bear")]
public partial class BearDetailPage : ContentPage
Animal bear;
public Animal Bear
get => bear;
bear = value;
OnPropertyChanged();
public BearDetailPage()
InitializeComponent();
BindingContext = this;
在此示例中, 的第一个 QueryPropertyAttribute
参数指定将接收数据的属性的名称,第二个参数指定参数 ID。因此, QueryPropertyAttribute
上述示例中的 指定属性 Bear
将接收在方法调用的 Bear
导航参数中 GoToAsync
传递的数据。
通过 QueryPropertyAttribute
接收的基于字符串的查询参数值会自动对 URL 进行解码。
使用单一方法处理导航数据
可以通过在接收类上实现 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
方法检索与 Monkey
字典中的 query
键对应的 对象,该键作为参数 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
作为查询参数传递。
若要接收多个数据项,对于每个基于字符串的查询参数,可以使用 表示要导航到的页面的 类或页面 的 BindingContext
类进行修饰 QueryPropertyAttribute
:
[QueryProperty(nameof(Name), "name")]
[QueryProperty(nameof(Location), "location")]
public partial class ElephantDetailPage : ContentPage
public string Name
// Custom logic
public string Location
// Custom logic
在此示例中,每个查询参数的类使用 QueryPropertyAttribute
进行修饰。 第一个 QueryPropertyAttribute
指定 Name
属性将接收在 name
查询参数中传递的数据,而第二个 QueryPropertyAttribute
指定 Location
属性将接收在 location
查询参数中传递的数据。 在这两种情况下,查询参数值都是在 GoToAsync
方法调用的 URI 中指定的。
或者,在表示导航到的页面的类或页面的 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 对象提供支持,这意味着这些属性可以作为数据绑定的目标。
下面的代码演示了重新定义“后退”按钮的外观和行为的示例:
<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
</ContentPage>
将 Command
属性设置为按下“后退”按钮时执行的 ICommand
,将 IconOverride
属性设置为用于“后退”按钮的图标: