maui-shell-navigation について
maui-shell-navigationは、Shellを使ったナビゲーションを構築する.NET MAUI開発者向けのリファレンススキルです。宣言的なタブやフライアウト、URIルート、ページ間のデータ受け渡し、ナビゲーションのガードを扱います。
主な機能
- 01Shell階層とメニューの設定
- 02URIルートとGoToAsyncによる遷移
- 03クエリ・オブジェクトパラメーターの処理
- 04非同期ナビゲーションガード
- 05タブとフライアウトの設定
活用シーン
- 01トップレベルのタブやフライアウトの設定
- 02登録済み詳細ページへの遷移
- 03ページ間のデータ受け渡し
- 04未保存の変更がある状態での離脱確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill maui-shell-navigationインストール後、Claude Code で/maui-shell-navigationを入力して読み込まれたか確認します。
同梱ファイルreferences/shell-navigation-api.md
詳細
.NET MAUI Shell ナビゲーション
Shell を使用して、.NET MAUI アプリでページ間のナビゲーションを実装します。Shell は、URI ベースのナビゲーション、フライアウトメニュー、タブバー、そして 4 階層のビジュアル階層を提供します。これらはすべて XAML で宣言的に設定できます。
使用する場合
- タブやフライアウトメニューを使った、アプリのトップレベルナビゲーションを設定する
GoToAsyncを使って、プログラムからページ間を移動する- クエリパラメーターやオブジェクトパラメーターを使って、ページ間でデータを渡す
- プッシュナビゲーション用に詳細ページのルートを登録する
- 確認ダイアログ(未保存の変更など)でナビゲーションを制御する
- ページごとに戻るボタンの動作をカスタマイズする
使用しない場合
- 外部 URL やアプリリンクからのディープリンク — .NET MAUI deep linking docsを参照してください
- ナビゲーション先のページでのデータバインディング —
maui-data-bindingを使用してください - ページやビューモデルの依存関係の注入 —
maui-dependency-injectionを使用してください - Shell を使わずに
NavigationPageを使用するアプリ(ナビゲーション API が異なります)
入力
- ルート Shell として
AppShell.xamlを使用する .NET MAUI プロジェクト - ナビゲーション対象のページ(
ContentPage) - ビジュアル階層に含まれない詳細ページのルート名
回答を左右するルール
以下は、間違えやすい Shell 固有の判断です。ユーザーの質問に該当する場合は、 必ず適用してください。
| 状況 | こちらを使う | こちらは使わない |
|---|---|---|
AppShell.xaml でページを宣言する | xmlns:views="clr-namespace:MyApp.Views" が宣言されている状態で、<ShellContent ContentTemplate="{DataTemplate views:MyPage}" /> を使います — ページは最初のナビゲーション時に作成されます | <ShellContent><views:MyPage /></ShellContent>。これは起動時にすべてのページを生成します |
| ビジュアル階層にないページに移動する | 先に Routing.RegisterRoute("details", typeof(DetailsPage)) を呼び出します | 登録せずに GoToAsync("details") を呼び出すこと。実行時に例外がスローされます |
| ナビゲーションパラメーターを受け取る | ViewModel に IQueryAttributable を実装します | Page に実装すること。状態が BindingContext から分離されます |
| オブジェクト全体を渡す | ShellNavigationQueryParameters を使います | オブジェクトをクエリ文字列にシリアル化すること |
GoToAsync を呼び出す場合 | await を付けます | fire-and-forget にすること。例外が握りつぶされ、ナビゲーションの競合が発生します |
| 戻るナビゲーションの前に確認する | ShellNavigatingEventArgs.GetDeferral() … deferral.Complete() を使います | ダイアログのタスクを同期的にブロックすること |
| 戻るナビゲーションを検出する | e.Source == ShellNavigationSource.Pop を確認します | すべてのナビゲーションが戻る操作だと想定すること |
Shell アプリに対して NavigationPage / PushAsync を使う解決策を提案しないでください。また、ユーザーから依頼されていない限り、正常に動作している AppShell の階層を再構成しないでください。
的を絞りつつ、必要な内容を漏れなく回答してください。 話題に沿うことは、回答を簡潔にしすぎることではありません。ナビゲーションの変更を示す場合は、実行に必要な要素を含めてください。たとえば、AppShell.xaml のマークアップと Routing.RegisterRoute の呼び出し、または GoToAsync の呼び出しと、受け取り側の IQueryAttributable / [QueryProperty] のコードを示します。クエリ文字列と ShellNavigationQueryParameters のように、どちらも有効な方法がある場合は両方を示し、それぞれの用途を説明してください。ユーザーが補完しなければならないコードスニペットを 1 つだけ示すよりも、両方を提示するほうが適切です。
Shell のビジュアル階層
Shell は 4 階層の階層構造を使用します。各レベルは、その下のレベルを包みます。
Shell
├── FlyoutItem / TabBar (top-level grouping)
│ ├── Tab (bottom-tab grouping)
│ │ ├── ShellContent (page slot → ContentPage)
│ │ └── ShellContent (multiple = top tabs)
│ └── Tab
└── FlyoutItem / TabBar
- FlyoutItem — フライアウトメニューに表示され、子として
Tabを含みます - TabBar — フライアウト項目を持たない下部タブバーです
- Tab —
ShellContentをグループ化します。複数の子を持つ場合は、上部にタブが表示されます - ShellContent — それぞれが
ContentPageを参照します
暗黙的な変換
中間のラッパーは省略できます。Shell が自動的にラップします。
| 記述するもの | Shell が作成するもの |
|---|---|
ShellContent のみ | FlyoutItem > Tab > ShellContent |
Tab のみ | FlyoutItem > Tab |
TabBar 内の ShellContent | TabBar > Tab > ShellContent |
ワークフロー:AppShell のセットアップ
Shellを継承するAppShell.xamlを定義します- トップレベルのナビゲーションに
FlyoutItemまたはTabBar要素を追加します - 下部タブには
Tab要素を追加し、上部タブには複数のShellContentをネストします - 必ず
DataTemplateを使ったContentTemplateを使用します。ページをオンデマンドで読み込むためです - すべての
ShellContentに明示的なRouteを指定します(以下を参照) - 詳細ページのルートを
AppShellコンストラクターで登録します
すべての
ShellContentにRoute=を設定してください。 省略すると、MAUI は共有カウンターから名前を自動生成し、Routing.csはD_FAULT_{TypeName}{n}を出力します。名前のないShellContentが 3 つある実際のシェルでは、D_FAULT_ShellContent2やD_FAULT_ShellContent5のようなルートが生成されます。番号は連番ではなく、先に構築された Shell 要素の数に依存し、ページの順序を変更したり追加したりすると変わります。そのため、安定した絶対ルート(//dashboard)を記述したり、ディープリンクの対象にしたりすることはできません。明示的なRoute="dashboard"を指定すれば、いつまでも安定します。
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MyApp.Views"
x:Class="MyApp.AppShell"
FlyoutBehavior="Flyout">
<FlyoutItem Title="Animals" Icon="animals.png">
<Tab Title="Cats">
<ShellContent Title="Domestic" Route="domesticcats"
ContentTemplate="{DataTemplate views:DomesticCatsPage}" />
<ShellContent Title="Wild" Route="wildcats"
ContentTemplate="{DataTemplate views:WildCatsPage}" />
</Tab>
<Tab Title="Dogs" Icon="dogs.png">
<ShellContent Route="dogs" ContentTemplate="{DataTemplate views:DogsPage}" />
</Tab>
</FlyoutItem>
<TabBar>
<ShellContent Title="Home" Icon="home.png" Route="home"
ContentTemplate="{DataTemplate views:HomePage}" />
<ShellContent Title="Settings" Icon="settings.png" Route="settings"
ContentTemplate="{DataTemplate views:SettingsPage}" />
</TabBar>
</Shell>
// AppShell.xaml.cs
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute("animaldetails", typeof(AnimalDetailsPage));
Routing.RegisterRoute("editanimal", typeof(EditAnimalPage));
}
}
ワークフロー:GoToAsync でナビゲートする
プログラムによるナビゲーションには、常に Shell.Current.GoToAsync を使用します。呼び出しには必ず await を付けてください。
ルートのプレフィックス
| プレフィックス | 意味 |
|---|---|
// | Shell のルートからの絶対ルート |
| (なし) | 相対ルート。現在のナビゲーションスタックに追加します |
.. | 1 つ前の階層に戻ります |
../ | 戻ってから前方へナビゲートします |
ナビゲーションの例
// 1. Absolute — switch to a specific hierarchy location
await Shell.Current.GoToAsync("//animals/cats/domestic");
// 2. Relative — push a registered detail page
await Shell.Current.GoToAsync("animaldetails");
// 3. With query string parameters
await Shell.Current.GoToAsync($"animaldetails?id={animal.Id}");
// 4. Go back one page
await Shell.Current.GoToAsync("..");
// 5. Go back two pages
await Shell.Current.GoToAsync("../..");
// 6. Go back one page, then push a different page
await Shell.Current.GoToAsync("../editanimal");
ワークフロー:ページ間でデータを渡す
オプション 1:IQueryAttributable(推奨)
すべてのパラメーターを 1 回の呼び出しで受け取るには、ViewModel に実装します。
public class AnimalDetailsViewModel : ObservableObject, IQueryAttributable
{
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.TryGetValue("id", out var id))
AnimalId = id.ToString();
}
}
オプション 2:QueryProperty 属性
ViewModel クラスに適用します(状態を実際に所有する場合はページに適用してもかまいません)。
ViewModel で IQueryAttributable を使用することを推奨します。これによりナビゲーション状態が BindingContext に保持され、複数のパラメーターを 1 回の呼び出しで処理できます。
[QueryProperty(nameof(AnimalId), "id")]
public partial class AnimalDetailsViewModel : ObservableObject
{
[ObservableProperty]
private string _animalId = string.Empty;
}
Shell はページコンストラクターが BindingContext を設定した後にクエリー属性を適用するため、プロパティは変更通知を発行する必要があります。通常の自動実装プロパティでは、バインディングが初期値のまま更新されません。
オプション 3:ShellNavigationQueryParameters を使った複雑なオブジェクト
文字列にシリアル化せずにオブジェクトを渡します。
var parameters = new ShellNavigationQueryParameters
{
{ "animal", selectedAnimal }
};
await Shell.Current.GoToAsync("animaldetails", parameters);
IQueryAttributable で受け取ります。
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
Animal = query["animal"] as Animal;
}
ワークフロー:ナビゲーションを制御する
非同期チェック(例:「未保存の変更を保存しますか?」)には、OnNavigating で GetDeferral() を使用します。
// In AppShell.xaml.cs
protected override async void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
if (hasUnsavedChanges && args.Source == ShellNavigationSource.Pop)
{
var deferral = args.GetDeferral();
bool discard = await ShowConfirmationDialog();
if (!discard)
args.Cancel();
deferral.Complete();
}
}
タブの構成
下部タブ
TabBar または FlyoutItem 内に複数の ShellContent(または Tab)子要素を配置すると、下部タブが生成されます。
上部タブ
単一の Tab 内に複数の ShellContent 子要素を配置すると、上部タブが生成されます。
<Tab Title="Photos">
<ShellContent Title="Recent" ContentTemplate="{DataTemplate views:RecentPage}" />
<ShellContent Title="Favorites" ContentTemplate="{DataTemplate views:FavoritesPage}" />
</Tab>
タブバーの外観
| 添付プロパティ | 型 | 目的 |
|---|---|---|
Shell.TabBarBackgroundColor | Color | タブバーの背景 |
Shell.TabBarForegroundColor | Color | 選択中のアイコンの色 |
Shell.TabBarTitleColor | Color | 選択中のタブタイトルの色 |
Shell.TabBarUnselectedColor | Color | 選択されていないタブのアイコン/タイトル |
Shell.TabBarIsVisible | bool | タブバーの表示/非表示 |
<ContentPage Shell.TabBarIsVisible="False" ... />
フライアウトの構成
FlyoutBehavior
Shell に設定します:Disabled、Flyout、または Locked。
<Shell FlyoutBehavior="Flyout"> ... </Shell>
FlyoutDisplayOptions
子要素をフライアウトに表示する方法を制御します。
AsSingleItem(既定)— グループに対してフライアウト項目を 1 つ表示しますAsMultipleItems— 子要素のTabごとに項目を表示します
<FlyoutItem Title="Animals" FlyoutDisplayOptions="AsMultipleItems">
<Tab Title="Cats" ... />
<Tab Title="Dogs" ... />
</FlyoutItem>
MenuItem(ナビゲーションを行わないフライアウト項目)
<MenuItem Text="Log Out"
Command="{Binding LogOutCommand}"
IconImageSource="logout.png" />
戻るボタンの動作
ページごとに戻るボタンをカスタマイズします。
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back_arrow.png"
TextOverride="Cancel"
IsVisible="True" />
</Shell.BackButtonBehavior>
プロパティ:Command、CommandParameter、IconOverride、TextOverride、IsVisible、IsEnabled。
ナビゲーション状態の確認
// Current URI location
string location = Shell.Current.CurrentState.Location.ToString();
// Current page
Page page = Shell.Current.CurrentPage;
// Navigation stack of the current tab
IReadOnlyList<Page> stack = Shell.Current.Navigation.NavigationStack;
ナビゲーションイベント
AppShell でオーバーライドします。
protected override void OnNavigated(ShellNavigatedEventArgs args)
{
base.OnNavigated(args);
// args.Current, args.Previous, args.Source
}
ShellNavigationSource の値:Push、Pop、PopToRoot、Insert、Remove、ShellItemChanged、ShellSectionChanged、ShellContentChanged、Unknown。
よくある落とし穴
- ページの先行作成:
DataTemplateを使ったContentTemplateではなく、直接Contentを使うと、Shell の初期化時にすべてのページが作成され、起動時間が長くなります。必ずContentTemplateを使用してください。 - ルート名の重複:ルート名が既存のルートまたはビジュアル階層のルートと一致する場合、
Routing.RegisterRouteはArgumentExceptionをスローします。アプリ内のすべてのルート名は一意である必要があります。 - 登録されていない相対ルート:
somepageがRouting.RegisterRouteで登録されていない限り、GoToAsync("somepage")は呼び出せません。ビジュアル階層のページでは絶対//ルートを使用します。 GoToAsyncの非同期呼び出しを待機しない:GoToAsyncを待機しないと、競合状態や気づかれない失敗が発生します。必ず呼び出しをawaitしてください。- 絶対ルートパスの誤り:絶対ルートは、ビジュアル階層を通る完全なパス(
//FlyoutItem/Tab/ShellContent)と一致する必要があります。パスが誤っている場合、例外ではなく、何も行われません。 Tab.Stackの直接操作:ナビゲーションスタックは読み取り専用です。ナビゲーションの変更にはすべてGoToAsyncを使用してください。- 非同期ガードでの
GetDeferral()の呼び出し忘れ:OnNavigatingでの同期的なキャンセルは機能しますが、非同期チェックでは競合状態を避けるためにGetDeferral()とdeferral.Complete()が必要です。
参考資料
references/shell-navigation-api.md— Shell の階層、ルート、タブ、フライアウト、ナビゲーションに関する API リファレンス- .NET MAUI Shell ナビゲーション
- .NET MAUI Shell タブ
- .NET MAUI Shell フライアウト
- .NET MAUI Shell ページ