Xamarin.Forms 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 巡覽完成時引發事件。
注意
流覽仍然可以使用 Navigation 屬性在 Shell 應用程式中的頁面之間執行。 如需詳細資訊,請參閱階層式導覽。
路由
導覽是在 Shell 應用程式中,透過指定要導覽的 URI 執行的。 導覽 URL 可以有三個元件:
- 「路線」,定義作為殼層視覺階層中現有內容的路徑。
- 「頁面」。 Shell 視覺階層中不存在的頁面可以從 Shell 應用程式中的任何位置推送到導覽堆疊上。 例如,詳細資料頁面未定義於殼層視覺階層,但可以視需要推送至導覽堆疊。
- 一或多個「查詢參數」。 查詢參數是可在導覽時傳遞至目的地頁面的參數。
導覽 URI 包括所有這三個元件時,結構為://route/page?queryParameters
註冊路線
路由可以透過其Route屬性定義於 、TabBar、 Tab和 ShellContent 物件上FlyoutItem:
<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 子類別建構函式中,或在叫用路線之前執行的任何其他位置中,可以針對任何未在殼層視覺階層中呈現的詳細資料頁面,明確地註冊其他路線。 這會使用 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 可以透過 Shell.Current.CurrentState 屬性擷取,其中包含屬性中顯示路由的 Location URI。
絕對路由
將有效的絕對 URI 指定為 GoToAsync 方法的引數可執行導覽:
await Shell.Current.GoToAsync("//animals/monkeys");
此範例會導覽至 monkeys 路由的頁面,並在 ShellContent 物件上定義路由。 表示 monkeys 路由的 ShellContent 物件為 FlyoutItem 物件子系,其路由是 animals。
相對路由
將有效的相對 URI 指定為 GoToAsync 方法的引數也可以執行導覽。 路由系統將會嘗試比對 ShellContent 物件的 URI。 因此,如果應用程式中的所有路由都是唯一的,則只能藉由將唯一路由名稱指定為相對 URI 來執行導覽。
支援下列相對路由格式:
| 格式 | 描述 |
|---|---|
| route | 路由階層會從目前的位置向上搜尋指定的路由。 比對頁面將會推送至流覽堆疊。 |
| /route | 路由階層會從指定的路由搜尋,從目前位置向下搜尋。 比對頁面將會推送至流覽堆疊。 |
| //route | 路由階層會從目前的位置向上搜尋指定的路由。 比對頁面將會取代瀏覽堆疊。 |
| ///route | 路由階層會從目前的位置向下搜尋指定的路由。 比對頁面將會取代瀏覽堆疊。 |
下列範例會巡覽至路由的頁面 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}");
在此範例中,會執行向後流覽,並將查詢參數值傳遞至上一頁的查詢參數。
注意
查詢參數可以附加至任何向後流覽要求。
如需在巡覽時傳遞數據的詳細資訊,請參閱 傳遞數據。
無效的路由
下列路由格式無效:
| 格式 | 說明 |
|---|---|
| //page 或 ///page | 全域路由目前不得為導覽堆疊上的唯一頁面。 因此,不支援以絕對路由傳送至全域路由。 |
使用這些路由格式會導致 Exception 擲回 。
警告
嘗試導覽至不存在的路由會導致擲回 ArgumentException 例外狀況。
為導覽偵錯
部分 Shell 類別是以 DebuggerDisplayAttribute 裝飾,以指定偵錯工具如何顯示類別或欄位。 顯示與導覽要求相關的資料有助於為導覽要求偵錯。 例如,下列螢幕擷取畫面顯示 Shell.Current 物件的 CurrentItem 和 CurrentState 屬性:

在此範例中,CurrentItem 屬性屬於 FlyoutItem 類型,可顯示 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
}
}
在此範例中, MyTab 應該在Shell視覺階層中取用物件, 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呼叫 方法。 同樣地, OnNavigated 當事件引發時 Navigated ,會呼叫 方法。 您可以在子類別中 Shell 覆寫這兩種方法,以攔截流覽要求。
例如,ShellNavigatedEventArgs 及 ShellNavigatingEventArgs 類別都擁有 ShellNavigationSource 類型的 Source 屬性。 此列舉提供下列值:
UnknownPushPopPopToRootInsertRemoveShellItemChangedShellSectionChangedShellContentChanged
因此,可以在覆寫中 OnNavigating 攔截流覽,而且可以根據流覽來源執行動作。 例如,下列的程式碼會示範如何在未儲存頁面上的資料時,取消向後導覽:
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
// Cancel any back navigation.
if (args.Source == ShellNavigationSource.Pop)
{
args.Cancel();
}
// }
瀏覽延遲
您可以根據使用者選擇攔截和完成或取消殼層流覽。 藉由覆寫OnNavigating子Shell類別中的方法,以及在 物件上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();
}
}
在此範例中,會顯示一個動作表,邀請使用者完成流覽要求,或取消它。 藉由叫用 Cancel 物件上的 ShellNavigatingEventArgs 方法來取消流覽。 巡覽是藉由 Complete 叫用 物件上 ShellNavigatingDeferral 方法所擷取 GetDeferral 之令牌上的 ShellNavigatingEventArgs 方法來完成。
警告
如果使用者嘗試在有擱置的瀏覽延遲時流覽,方法 GoToAsync 會擲回 InvalidOperationException 。
傳遞資料
執行以 URI 為基礎的程式設計導覽時,數據可以當做查詢參數傳遞。 這是藉由在路由後面附加 ? ,後面接著查詢參數標識碼 和 =值來達成此目的。 例如,當使用者在 ElephantsPage 上選取大象時,範例應用程式中會執行下列程式碼:
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 作為查詢參數傳遞。
接收導覽資料的方法有兩種:
- 類別,表示要巡覽的頁面,或頁面 的
BindingContext類別,可以使用 每個查詢參數的QueryPropertyAttribute裝飾。 如需詳細資訊,請參閱 使用查詢屬性處理導覽數據。 - 表示所瀏覽頁面的類別,或頁面 的
BindingContext類別,可以實IQueryAttributable作 介面。 如需詳細資訊,請參閱 使用單一方法處理導覽數據。
使用查詢屬性屬性處理導覽數據
使用每個查詢參數的 裝飾接收類別 QueryPropertyAttribute ,即可接收導覽數據:
[QueryProperty(nameof(Name), "name")]
public partial class ElephantDetailPage : ContentPage
{
public string Name
{
set
{
LoadAnimal(value);
}
}
...
void LoadAnimal(string name)
{
try
{
Animal animal = ElephantData.Elephants.FirstOrDefault(a => a.Name == name);
BindingContext = animal;
}
catch (Exception)
{
Console.WriteLine("Failed to load animal.");
}
}
}
的第一個自變數QueryPropertyAttribute會指定將接收數據的屬性名稱,而第二個自變數則指定查詢參數標識符。因此,QueryPropertyAttribute上述範例中的 會Name指定 屬性會從方法呼叫中的 GoToAsync URI 接收查詢參數中name傳遞的數據。 屬性 setter 會Name呼叫 LoadAnimal 方法來擷name取 Animal 的物件,並將它設定為BindingContext頁面的 。
注意
透過 QueryPropertyAttribute 接收的查詢參數值會自動譯碼 URL。
使用單一方法處理導覽數據
藉由在接收類別上實 IQueryAttributable 作 介面,即可接收導覽數據。 介面 IQueryAttributable 指定實作類別必須實 ApplyQueryAttributes 作 方法。 這個方法具有 query 類型 IDictionary<string, string>為 的自變數,其中包含瀏覽期間傳遞的任何數據。 字典中的每個索引鍵都是查詢參數標識碼,其值是查詢參數值。 使用此方法的優點是,瀏覽數據可以使用單一方法來處理,當您有多個需要整體處理的瀏覽數據專案時,這非常有用。
下列範例顯示實作 介面的 IQueryAttributable 檢視模型類別:
public class MonkeyDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
public Animal Monkey { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
// The query parameter requires URL decoding.
string name = HttpUtility.UrlDecode(query["name"]);
LoadAnimal(name);
}
void LoadAnimal(string name)
{
try
{
Monkey = MonkeyData.Monkeys.FirstOrDefault(a => a.Name == name);
OnPropertyChanged("Monkey");
}
catch (Exception)
{
Console.WriteLine("Failed to load animal.");
}
}
...
}
在此範例中,ApplyQueryAttributes方法會從方法呼叫中的 GoToAsync URI 擷取查詢參數的值name。 然後, LoadAnimal 會呼叫 方法來擷取 Animal 物件,其中其設定為系結數據之 Monkey 屬性的值。
重要
透過介面接收的 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
{
set
{
// Custom logic
}
}
public string Location
{
set
{
// Custom logic
}
}
...
}
在這裡範例中,類別會針對 QueryPropertyAttribute 每個查詢參數使用 裝飾 。 第一個 QueryPropertyAttribute 指定 Name 屬性將接收傳入 name 查詢參數的數據,而第二個 QueryPropertyAttribute 指定 Location 屬性將接收傳入 location 查詢參數的數據。 在這兩種情況下,查詢參數值都會在方法呼叫的 GoToAsync URI 中指定。
或者,藉由在代表所瀏覽頁面的 類別上實 IQueryAttributable 作 介面,或頁面 的 BindingContext類別,即可處理導覽數據:
public class ElephantDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
public Animal Elephant { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
string name = HttpUtility.UrlDecode(query["name"]);
string location = HttpUtility.UrlDecode(query["location"]);
...
}
...
}
在此範例中ApplyQueryAttributes,方法會從方法呼叫中的 GoToAsync URI 擷取 和 location 查詢參數的值name。
上一頁按鈕行為
您可以將附加屬性設定 BackButtonBehavior 為 BackButtonBehavior 物件,以重新定義返回按鈕的外觀和行為。 類別 BackButtonBehavior 會定義下列屬性:
Command,屬於ICommand類型,會在按 [上一頁] 按鈕時執行。CommandParameter,屬於object類型,這是傳遞至Command的參數。IconOverride,屬於ImageSource類型,這是用於上一頁按鈕的圖示。IsEnabled,屬於boolean類型,可指出是否啟用上一頁按鈕。 預設值是true。TextOverride,屬於string類型,這是用於上一頁按鈕的文字。
所有這些屬性都以 BindableProperty 物件為後盾,也就是說,這些屬性可以是資料繫結的目標。
下列程式代碼顯示重新定義返回按鈕外觀和行為的範例:
<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>
對等的 C# 程式碼為:
Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
Command = new Command(() =>
{
...
}),
IconOverride = "back.png"
});
Command 屬性會設定要按 [上一頁] 按鈕時執行的 ICommand,而 IconOverride 屬性則會設定為用於 [上一頁] 按鈕的圖示:
