相关文章推荐
欢快的夕阳  ·  Beautifulsoup 爬蟲 ...·  5 月前    · 
奔跑的警车  ·  生成第一个 SharePoint ...·  7 月前    · 

.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 方法从 stringUri 构造的,并将其 Location 属性设置为 stringUri 参数。

    当导航到 Shell 视觉层次结构中的路由时,不会创建导航堆栈。 但是,当导航到不在 Shell 视觉层次结构中的页面时,将创建一个导航堆栈。

    可以通过 Shell.Current.CurrentState 属性检索 Shell 对象的当前导航状态,该属性包括 Location 属性中显示的路由的 URI。

    导航可以通过指定一个有效的绝对 URI 作为 GoToAsync 方法的参数来执行:

    await Shell.Current.GoToAsync("//animals/monkeys");
    

    本例导航到 monkeys 路由的页面,该路由在 ShellContent 对象上定义。 表示 monkeys 路由的 ShellContent 对象是其路由为 animalsFlyoutItem 对象的子对象。

    导航还可以通过指定一个有效的相对 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 对象的 CurrentItemCurrentState 属性:

    在本例中,类型为 FlyoutItemCurrentItem 属性显示了 FlyoutItem 对象的标题和路由。 同样, CurrentState 类型 ShellNavigationState为 的 属性显示 Shell 应用中所显示路由的 URI。

    Tab定义 类型StackIReadOnlyList<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 子类中被替代,以截获导航请求。

    ShellNavigatedEventArgsShellNavigatingEventArgs 类均具有 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 将添加到具有 DictionaryBear的 。 然后,执行路由 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 路由,将 elephantNameelephantLocation 作为查询参数传递。

    若要接收多个数据项,对于每个基于字符串的查询参数,可以使用 表示要导航到的页面的 类或页面 的 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 检索 namelocation 查询参数的值。

    执行基于路由的导航时,可以同时传递基于字符串的查询参数和基于对象的导航参数。

    “后退”按钮行为

    通过将 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 属性设置为用于“后退”按钮的图标: