導覽
提示
本內容節錄自《Enterprise Application Patterns Using .NET MAUI》電子書,可以從 .NET Docs 取得,也可以免費下載 PDF 離線閱讀。
.NET MAUI 支援頁面導覽,這通常是因為使用者與 UI 的互動,或因內部邏輯驅動狀態變更而從應用程式本身產生。 不過,在使用 Model-View-ViewModel (MVVM) 模式的應用程式中實作導覽可能相當複雜,因為必須符合下列挑戰:
- 識別要導覽至的檢視,其使用的方法不會在檢視之間產生緊密結合程度和相依性。
- 依要導覽至之檢視協調流程會具現化和初始化。 使用 MVVM 時,必須透過檢視的繫結內容,將檢視和檢視模型具現化並彼此關聯。 應用程式使用相依性插入容器時,將檢視和檢視模型具現化可能需要特定建構機制。
- 要執行檢視優先導覽,還是檢視模型優先導覽。 使用檢視優先導覽時,要導覽至的頁面會參考檢視類型的名稱。 在導覽期間,會將指定的檢視具現化,以及其相應的檢視模型和其他相依服務。 另一個方法是使用檢視模型導覽,其中要導覽至的頁面會參考檢視模型類型的名稱。
- 決定如何在檢視和檢視模型之間清楚分開應用程式的導覽行為。 MVVM 模式會分開應用程式的 UI 及其展示和商務邏輯,但不會提供直接機制將它們繫結在一起。 但應用程式的導覽行為通常會涵蓋應用程式的 UI 和展示部分。 使用者通常會從檢視開始導覽,而檢視會因導覽遭取代。 不過,導覽通常也可能需要從檢視模型內開始或協調。
- 決定如何在導覽期間傳遞參數以進行初始化。 舉例來說,如果使用者導覽至檢視以更新訂單詳細資料,訂單資料必須傳遞至檢視,才能顯示正確的資料。
- 協調導覽以確保遵守特定商務規則。 例如,在離開檢視之前,可能會提示使用者以便更正任何不正確的資料,或提示使用者提交或捨棄在檢視內所做的任何資料變更。
本章藉由呈現名為 MauiNavigationService
的導覽服務類別,用來執行檢視模型優先導覽,以解決這些挑戰。
注意
應用程式使用的 MauiNavigationService
簡單,且未涵蓋所有可能的導覽類型。 應用程式所需導覽類型可能需要額外的功能。
在頁面之間導覽
導覽邏輯可位於檢視的程式碼後置或資料繫結檢視模型中。 雖然在檢視中設定導覽邏輯可能是最簡單的方法,但無法透過單元測試輕鬆測試。 將導覽邏輯放在檢視模型類別中,表示可透過單元測試來驗證邏輯。 此外,檢視模型接著可以實作邏輯來控制導覽,確保強制執行特定商務規則。 例如,應用程式可能不允許使用者在沒有先確保輸入的資料有效的情況下,離開頁面。
導覽服務通常會從檢視模型叫用,以提升可測試性。 不過,從檢視模型導覽至檢視時,需要檢視模型以參考檢視,特別是未與使用中檢視模型相關聯的檢視,不建議這麼做。 因此,此處顯示的 MauiNavigationService
會將檢視模型類型指定為要導覽至的目標。
eShop 多平臺應用程式會 MauiNavigationService
使用 類別來提供檢視模型優先導覽。 這個類別會實作 INavigationService
介面,如以下程式碼範例所示:
public interface INavigationService
{
Task InitializeAsync();
Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null);
Task PopAsync();
}
此介面指會定實作類別必須提供下列方法:
方法 | 目的 |
---|---|
InitializeAsync |
在啟動應用程式時,導覽至兩個頁面的其中一個。 |
NavigateToAsync(string route, IDictionary<string, object> routeParameters = null) |
使用已註冊的導覽路由,對指定的頁面執行階層式導覽。 可以選擇性傳遞具名路由參數,用於在目的地頁面上進行處理 |
PopAsync |
從導覽堆疊中移除目前的頁面。 |
注意
INavigationService
介面也經常會指定 GoBackAsync
方法,用來以程式設計方式返回導覽堆疊中的上一頁。 不過,eShop 多平臺應用程式缺少這個方法,因為它並非必要。
建立 MauiNavigationService 執行個體
實作 INavigationService
介面的 MauiNavigationService
類別,會向 MauiProgram.CreateMauiApp()
方法中的相依性插入容器註冊為 singleton,如以下程式碼範例所示:
mauiAppBuilder.Services.AddSingleton<INavigationService, MauiNavigationService>();;
您接著可以將 INavigationService
介面新增至檢視和檢視模型的建構函式,來解析介面,如以下程式碼範例所示:
public AppShell(INavigationService navigationService)
這會傳回儲存在相依性插入容器中的 MauiNavigationService
物件參考。
ViewModelBase
類別會將 MauiNavigationService
執行個體儲存在型別為 INavigationService
的 NavigationService
。 因此,衍生自 ViewModelBase
類別的所有檢視模型類別,都可以使用 NavigationService
屬性來存取 INavigationService
介面指定的方法。
處理導覽要求
.NET MAUI 提供多種方式在應用程式內導覽。 傳統導覽方式是使用 NavigationPage
類別,其會實作階層式導覽體驗,讓使用者可以視需要向前和向後瀏覽頁面。 eShop 應用程式會 Shell
使用元件作為應用程式的根容器,以及做為瀏覽主機。 如需 Shell 導覽的詳細資訊,請參閱 Microsoft 開發人員中心的 Shell 導覽 (機器翻譯)。
導覽是透過叫用其中一種 NavigateToAsync
方法,並指定所要導覽至之頁面的路由路徑,在檢視模型類別內執行,如以下程式碼範例所示:
await NavigationService.NavigateToAsync("//Main");
下列程式碼範例示範 MauiNavigationService
類別提供的 NavigateToAsync
方法:
public Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null)
{
return
routeParameters != null
? Shell.Current.GoToAsync(route, routeParameters)
: Shell.Current.GoToAsync(route);
}
.NET MAUIShell
控制項已熟悉路由型導覽,因此 NavigateToAsync
方法可用來遮蔽這項功能。 NavigateToAsync
方法可讓導覽資料指定為引數,該引數會傳遞至要導覽的檢視模型,通常會在其中執行初始化。 如需詳細資訊,請參閱在導覽期間傳遞參數 (機器翻譯)。
重要
在 .NET MAUI 中執行導覽的方式有很多種。 MauiNavigationService
是專門搭配 Shell
使用而建置。 如果您使用 NavigationPage
或 TabbedPage
或其他導覽機制,則必須更新此路由服務,才能使用這些元件運作。
為了註冊 MauiNavigationService
的路由,我們必須從 XAML 或在程式碼後置中提供路由資訊。 下列範例顯示透過 XAML 註冊路由。
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:eShop.Views"
x:Class="eShop.AppShell">
<!-- Omitted for brevity -->
<FlyoutItem >
<ShellContent x:Name="login" ContentTemplate="{DataTemplate views:LoginView}" Route="Login" />
</FlyoutItem>
<TabBar x:Name="main" Route="Main">
<ShellContent Title="CATALOG" Route="Catalog" Icon="{StaticResource CatalogIconImageSource}" ContentTemplate="{DataTemplate views:CatalogView}" />
<ShellContent Title="PROFILE" Route="Profile" Icon="{StaticResource ProfileIconImageSource}" ContentTemplate="{DataTemplate views:ProfileView}" />
</TabBar>
</Shell>
在此範例中,ShellContent
和 TabBar
使用者介面物件正在設定其 Route
屬性。 這是註冊使用者介面物件路由的慣用方法,這些物件由 Shell
控制。
如果有稍後將新增至導覽堆疊的物件,則必須透過程式碼後置新增這些物件。 下列範例顯示在程式碼後置中註冊路由。
Routing.RegisterRoute("Filter", typeof(FiltersView));
Routing.RegisterRoute("Basket", typeof(BasketView));
在程式碼後置中,我們會呼叫 Routing.RegisterRoute
方法,該方法會將路由名稱當作第一個參數,並將檢視類型當作第二個參數。 檢視模型使用 NavigationService
屬性來導覽時,應用程式的 Shell
物件會尋找已註冊的路由,並將其推送至導覽堆疊。
建立並導覽至檢視之後,會執行檢視相關聯檢視模型的 ApplyQueryAttributes
和 InitializeAsync
方法。 如需詳細資訊,請參閱在導覽期間傳遞參數 (機器翻譯)。
啟動應用程式時導覽
啟動應用程式時,Shell
物件會設定為應用程式的根檢視。 設定後,Shell
會用來控制路由註冊,且之後會顯示在我們應用程式的根目錄中。 建立 Shell
後,我們可以使用 OnParentSet
方法,待其附加至應用程式,初始化導覽路由。 下列程式碼範例示範此方法:
protected override async void OnParentSet()
{
base.OnParentSet();
if (Parent is not null)
{
await _navigationService.InitializeAsync();
}
}
此方法會使用 INavigationService
執行個體,其具有相依性插入的建構函式,並叫用其 InitializeAsync
方法。
下列程式碼範例示範實作 MauiNavigationService.InitializeAsync
方法:
public Task InitializeAsync()
{
return NavigateToAsync(string.IsNullOrEmpty(_settingsService.AuthAccessToken)
? "//Login"
: "//Main/Catalog");
}
如果應用程式有用於驗證的快取存取權杖,會導覽至 //Main/Catalog
路由。 否則,會導覽至 //Login
路由。
在導覽期間傳遞參數
INavigationService
介面指定的 NavigateToAsync
方法,可讓導覽資料指定為資料的 IDictionary<string, object>
,其會傳遞至要導覽的檢視模型,通常會在其中執行初始化。
例如,ProfileViewModel
類別包含使用者在 ProfileView
頁面上選取訂單時執行的 OrderDetailCommand
。 接著,這會執行 OrderDetailAsync
方法,如下列程式碼範例所示:
private async Task OrderDetailAsync(Order order)
{
if (order is null)
{
return;
}
await NavigationService.NavigateToAsync(
"OrderDetail",
new Dictionary<string, object>{ { "OrderNumber", order.OrderNumber } });
}
這個方法會叫用導覽至 OrderDetail
路由,傳遞訂單號碼資訊給使用者選取的訂單。 相依性插入架構建立 OrderDetail
路由的 OrderDetailView
,以及指派給檢視 BindingContext
的 OrderDetailViewModel
類別時。 OrderDetailViewModel
已將屬性新增至其中,允許它從導覽服務接收資料,如下列程式碼範例所示。
[QueryProperty(nameof(OrderNumber), "OrderNumber")]
public class OrderDetailViewModel : ViewModelBase
{
public int OrderNumber { get; set; }
}
QueryProperty
屬性 (attribute) 可讓我們提供參數,可將值對應至屬性 (property),以及可從查詢參數字典中尋找值的索引鍵。 在此範例中,NavigateToAsync
呼叫期間會提供索引鍵 "OrderNumber" 和訂單編號值。 檢視模型找到 "OrderNumber" 索引鍵,並將值對應至 OrderNumber
屬性。 稍後可以使用 OrderNumber
屬性,從 OrderService
執行個體擷取完整訂單詳細資料。
使用行為叫用導覽
導覽通常會由使用者互動從檢視觸發。 例如,LoginView
會在驗證成功之後執行導覽。 下列程式碼範例示範如何透過行為叫用導覽:
<WebView>
<WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</WebView.Behaviors>
</WebView>
在執行階段,EventToCommandBehavior
會回應與 WebView
的互動。 WebView
導覽至網頁時,會引發 Navigating
事件,這會在 LoginViewMode
l 中執行 NavigateCommand
。 預設會將事件的事件引數傳遞給命令。 資料因其在來源和目標之間傳遞,由 EventArgsConverter
屬性中指定的轉換器轉換,這會從 WebNavigatingEventArgs
傳回 Url
。 因此,執行 NavigationCommand
時,網頁的 Url
會當作參數傳遞至已註冊的動作。
接著,NavigationCommand
會執行 NavigateAsync
方法,如下列程式碼範例所示:
private async Task NavigateAsync(string url)
{
// Omitted for brevity.
if (!string.IsNullOrWhiteSpace(accessToken))
{
_settingsService.AuthAccessToken = accessToken;
_settingsService.AuthIdToken = authResponse.IdentityToken;
await NavigationService.NavigateToAsync("//Main/Catalog");
}
}
這個方法會叫用 NavigationService
,將應用程式路由至 //Main/Catalog
路由。
確認或取消導覽
應用程式可能需要在導覽作業期間與使用者互動,讓使用者可以確認或取消導覽。 例如,使用者嘗試在顯示完全完成的資料輸入頁面之前導覽時,可能必須這麼做。 在此情況下,應用程式應該提供通知,讓使用者可離開頁面,或在離開之前取消導覽作業。 這可透過使用通知的回應,控制是否叫用導覽,在檢視模型中達成。
摘要
.NET MAUI 支援頁面導覽,這通常是因為使用者與 UI 的互動,或因內部邏輯驅動狀態變更而從應用程式本身產生。 但在使用 MVVM 模式的應用程式中實作導覽,可能相當複雜。
本章介紹 NavigationService 類別,用來從檢視模型執行檢視模型優先導覽。 在檢視模型類別中設定導覽邏輯,表示可以透過自動化的測試來運用邏輯。 此外,檢視模型接著可以實作邏輯來控制導覽,確保強制執行特定商務規則。