.NET MAUI .NET 9で画面をリセットする方法|CreateWindowとWindow.Page差し替えガイド

.NET MAUI の画面を「初期状態に戻す」処理は、ログアウトや権限変更時に必須です。これまで Application.Current.MainPage を差し替えていた人が、.NET 9 の CreateWindow() 構成で迷いがち。この記事では Window 起点の考え方と、安全にページをリセットする実装パターンを解説します。

目次

.NET MAUI(.NET 9)で「ページ(画面)をリセット」したくなる典型シーン

「画面をリセットしたい」と言っても、現場で求められる要件はだいたい次のどれかです。目的を先に整理しておくと、実装の迷いが減ります。

よくある場面なぜリセットが必要か実装でやりたいこと
ログアウト戻る操作で前ユーザーの画面に戻れてしまう事故を防ぐナビゲーション履歴を破棄し、ログイン画面(またはトップ)に戻す
権限・ロール切り替えメニュー構成や表示可否が変わるShell を作り直す/ルートを作り直す
初期セットアップ完了セットアップフローの戻りを消したい「セットアップ用のルート」から「通常ルート」に切り替える
致命的な状態不整合からの復帰画面スタックが壊れた・複数モーダルが積み上がったルートページを再生成し、UI を作り直す

従来の Xamarin.Forms や .NET MAUI の初期(.NET 7/8 で単一ウィンドウ運用が前提になっていたケース)では、次のように MainPage を差し替えるのが「手っ取り早いリセット」でした。

Application.Current.MainPage = new AppShell();

ところが .NET 9 のテンプレートや、マルチウィンドウを前提にした構成では「アプリの起点が Window」になります。ここを理解すると、リセットのやり方が自然に見えてきます。

なぜ .NET 9 では Application.Current.MainPage の差し替えだけだと不安になるのか

.NET MAUI は内部的に「アプリケーション(Application)」が「ウィンドウ(Window)」を作り、ウィンドウが「ルートページ(Page)」を持つ構造になっています。CreateWindow() をオーバーライドする構成では、この関係がコード上でも明確になります。

概念役割リセット観点で重要な点
Applicationアプリ全体のライフサイクルと設定の中心複数 Window を持てる。単一の MainPage 前提が崩れる
Window表示単位(デスクトップなら本当に複数開ける)Page が「そのウィンドウのルート」。ここを差し替えると画面が作り直せる
Page(AppShell / MainPage など)実際の UI ルートナビゲーションスタック、Shell のルート、メニュー、Tab 構成などに直結

つまり、.NET 9 の「Window 起点」構成で “画面リセット” をしたいなら、狙うべき差し替え先は Application.MainPage ではなく、対象の Window の Page です。

結論:生成した Window インスタンスの Page を差し替える

受理された回答の要点は非常にシンプルです。

  • CreateWindow() で作った Window を保持する
  • リセット時に WindowInstance.Page = new XXXPage() のようにルートページを置き換える

最小構成の実装例

まずは「仕組みが分かる」最小例です。ログアウトなどのタイミングで呼べる Reset() を用意します。

public partial class App : Application
{
    public static Window WindowInstance { get; private set; }

    protected override Window CreateWindow(IActivationState? activationState)
    {
        WindowInstance = new Window(new AppShell());
        return WindowInstance;
    }

    public static void Reset()
    {
        // 例:ログアウト後にトップへ戻す、など
        WindowInstance.Page = new MainPage();   // または new AppShell()
    }
}

これで、ウィンドウのルートページが差し替わるため、ナビゲーション履歴も含めて UI を「作り直す」動きになります。従来の MainPage 差し替えと近い感覚で使えます。

実務向け:UI スレッドで安全に差し替える

ViewModel からログアウト処理を呼ぶ、API 応答後にリセットする、といった現場あるあるでは、呼び出しスレッドが UI スレッドとは限りません。Window.Page の差し替えは UI スレッドで行うのが安全です。

using Microsoft.Maui.ApplicationModel;

public partial class App : Application
{
    public static Window? WindowInstance { get; private set; }

    protected override Window CreateWindow(IActivationState? activationState)
    {
        var window = new Window(new AppShell());
        WindowInstance = window;
        return window;
    }

    public static void ResetTo(Page newRoot)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            var window = WindowInstance ?? Application.Current?.Windows?.FirstOrDefault();
            if (window == null)
                return;

            window.Page = newRoot;
        });
    }
}

ポイントは次の 3 点です。

  • UI スレッドで実行:例外回避と安定性のため
  • null 対策:テストや特殊な起動経路で WindowInstance が未セットの可能性に備える
  • 引数でルートを受け取る:ログイン画面へ戻す、Shell を作り直すなど用途に合わせやすい

Dispatcher を使う選択肢もある

UI スレッドへの切り替えは MainThread 以外にも手段があります。アプリ側の実装方針(テストのしやすさ、依存の少なさ)に合わせて選んでください。

方法特徴向いているケース
MainThread.BeginInvokeOnMainThread呼び出しが簡単。どこからでも使いやすいViewModel から呼ぶ、サービス層から呼ぶなど、UI 層から離れた場所
Application.Current.Dispatcher.DispatchDispatcher を中心に統一できるアプリ全体で Dispatcher を使う方針、UI 周りの処理を統一したい
ページや要素の Dispatcher対象の UI に紐づく Dispatcher を使える「今表示しているページ」での UI 更新を確実にしたい

「リセット=アプリを作り直す」ではない:状態の残り方を理解する

Window.Page を差し替えると UI は作り直せますが、アプリの DI コンテナ(MauiProgram で登録したシングルトンなど)やプロセス自体が再起動するわけではありません。ここを勘違いすると「リセットしたのに状態が残る」現象にハマります。

リセットで消えるものリセットしても残るもの対策の考え方
画面ツリー、ナビゲーション履歴、モーダルスタック(基本的に)Singleton サービス、静的フィールド、キャッシュ、HTTP クライアントの状態ログアウト時は「セキュアストレージ」「トークン」「ユーザー情報キャッシュ」を明示的に破棄する
Shell のメニュー構成(Shell を作り直した場合)イベント購読(解除しない限り残ることがある)画面側で購読したイベントは OnDisappearing 等で解除する
BindingContext(ページ生成し直しでリセット)バックグラウンドタスク、タイマーログアウト時に停止する、CancellationToken で止める

「UI の初期化」と「アプリ内状態の初期化」は別物です。リセット処理は、UI の再生成とセットで “残すべきでない状態” を消すところまで設計すると、再ログイン後の不具合が激減します。

実務で使えるリセット設計パターン

パターンA:単一ウィンドウ前提で WindowInstance を静的に保持

スマホ中心・シングルウィンドウ前提であれば、静的プロパティ方式が最短です。テンプレートに近く、理解もしやすいのがメリットです。

  • メリット:実装が簡単、呼び出し側が楽
  • デメリット:将来マルチウィンドウにしたいときに設計変更が必要

パターンB:複数 Window を想定して対象 Window を選んで差し替える

Windows / Mac で「複数ウィンドウ」を使う可能性があるなら、静的に 1 個だけ持つ方式は注意が必要です。用途によっては Application.Current.Windows から対象ウィンドウを選ぶ設計の方が事故が減ります。

まずは割り切った例として、「先頭のウィンドウを対象にする」パターンです。

public static void ResetFirstWindowTo(Page newRoot)
{
    MainThread.BeginInvokeOnMainThread(() =>
    {
        var window = Application.Current?.Windows?.FirstOrDefault();
        if (window == null)
            return;


    window.Page = newRoot;
});


}

「どの Window が対象か」を確実にしたい場合は、ウィンドウ生成時に ID を持たせたり、ページ側から “自分が属する Window” を参照してリセット対象を決めたりします。簡単な運用ルールを決めるだけでも、マルチウィンドウ時の事故(別ウィンドウが突然ログアウト画面になる等)を避けられます。

対象 Window の決め方向いているケース注意点
Application.Current.Windows.FirstOrDefault()とにかく簡単にしたい。実質 1 Window 運用複数 Window があると意図しないものを対象にする可能性
生成時に辞書で保持(ID → Window)複数 Window を開くが、役割が固定破棄時の後始末(辞書から除去)を忘れるとリーク要因
現在アクティブな Window を追跡デスクトップで “今操作しているウィンドウ” を確実に切り替えたいプラットフォーム差異が出やすい。イベントで追跡する設計が必要

パターンC:ナビゲーションだけ初期化する(Shell / NavigationPage)

「画面をリセットしたい」と言いつつ、本当は “ナビゲーション履歴だけ消してトップへ戻したい” だけのことも多いです。この場合、ルートページの差し替えよりも軽量で、ユーザー体験も自然な方法があります。

やりたいことおすすめ例
Shell の特定ルートに戻る(履歴をリセット)GoToAsync("//...")await Shell.Current.GoToAsync("//home");
NavigationPage のルートに戻るPopToRootAsync()await Navigation.PopToRootAsync();
UI 構造自体を作り直したい(メニューも変えたい)Window.Page の差し替えwindow.Page = new AppShell();

「Shell のメニューやタブを、ユーザーの権限に合わせて動的に変えたい」など、UI 構造ごと変える必要があるなら Window.Page を差し替える価値があります。一方で、単にトップへ戻るだけなら GoToAsync の方が実装も軽く、状態も残せて便利です。

ログアウト後にトップへ戻す実装例:認証状態でルートを切り替える

実務では「ログアウトしたらログイン画面へ」「ログインしたら AppShell へ」という切り替えが王道です。CreateWindow() で起動時のルートを決め、ログアウト時は同じルールで戻すようにしておくと、挙動が一貫します。

起動時:認証状態でルートを分岐

public partial class App : Application
{
    public static Window? WindowInstance { get; private set; }


private readonly IAuthState _authState;

public App(IAuthState authState)
{
    InitializeComponent();
    _authState = authState;
}

protected override Window CreateWindow(IActivationState? activationState)
{
    Page root = _authState.IsSignedIn
        ? new AppShell()
        : new NavigationPage(new LoginPage());

    var window = new Window(root);
    WindowInstance = window;
    return window;
}

public static void ResetToSignedOut()
    => ResetTo(new NavigationPage(new LoginPage()));

public static void ResetToSignedIn()
    => ResetTo(new AppShell());

public static void ResetTo(Page newRoot)
{
    MainThread.BeginInvokeOnMainThread(() =>
    {
        var window = WindowInstance ?? Application.Current?.Windows?.FirstOrDefault();
        if (window == null)
            return;

        window.Page = newRoot;
    });
}


}

この形にしておくと、ログイン・ログアウトの “ルート切り替え” が App に集約され、画面や ViewModel が複雑になりにくいです。

ログアウト処理:セッション破棄+画面リセットをセットで

public class SettingsViewModel
{
    private readonly IAuthService _authService;


public SettingsViewModel(IAuthService authService)
{
    _authService = authService;
}

public async Task LogoutAsync()
{
    await _authService.SignOutAsync();

    // UI をログイン画面へ戻し、戻る履歴を消す
    App.ResetToSignedOut();
}


}

ログアウトは「トークンを消したのに画面が残っている」状態が最も危険です。サインアウト処理が成功したら、速やかにルートページを差し替えて UI を作り直すと、戻る操作やディープリンクで前画面が出る事故を抑えられます。

どの方法が最適か:リセット手段の比較表

最後に、現場で迷いがちな選択肢を “目的別” に整理します。

方法できること向いている目的注意点
Window.Page = new ...ルート UI を再生成(実質フルリセットに近い)ログアウト、権限変更、セットアップ完了後のルート切り替えDI/Singleton は残る。イベント購読やキャッシュは別途クリアが必要
Shell.Current.GoToAsync("//...")Shell の履歴をリセットしてルートへホームへ戻す、タブ移動の統一、深い階層からの復帰Shell 自体は作り直さない。メニュー構造を変えたい場合は不向き
Navigation.PopToRootAsync()NavigationPage の履歴を戻す単純な戻り履歴リセットShell と併用している場合は設計がブレやすい
Application.Current.MainPage = ...(単一 Window 前提なら)ルート差し替え古いコード資産の延命、シンプルなアプリ.NET 9 の Window 起点・マルチウィンドウで意図が曖昧になりやすい

よくある質問とトラブルシューティング

リセットしたのに「前の画面の情報」が残って見える

ルートページを差し替えても、次のような “アプリ内状態” が残っていると、結果的に同じ画面に見えてしまうことがあります。

  • ViewModel が Singleton で登録されていて、プロパティが保持されている
  • 静的フィールドにユーザー情報を保持している
  • イベント購読で古い ViewModel が生き残っている

ログアウトや権限変更で確実に初期化したいデータは、「どこに状態がいるか」 を棚卸しして、リセット時に明示的に消すのが近道です。

マルチウィンドウ環境で、違うウィンドウが切り替わってしまう

静的に 1 個だけ Window を保持していると、意図せず別ウィンドウを差し替えることがあります。対策は次のいずれかです。

  • リセット対象の Window を引数で渡す(呼び出し元が責任を持つ)
  • Window を ID 管理して、どのルートをどの Window に割り当てるか決める
  • アクティブ Window 追跡の仕組みを入れる

Shell を作り直したらルーティングや DI が絡んで例外が出る

Shell でルートを組み立て直すときは、ルートページ生成時に参照しているサービスやルート登録の順序が影響します。次のチェックが有効です。

  • ルート生成に必要なサービス(ViewModel/Service)が DI に登録されているか
  • ルート(Route)の登録が AppShell のコンストラクタなどで確実に行われているか
  • 初期表示のルート(// から始まるルート)へ遷移する前に Shell が表示済みか

まとめ:.NET 9 の画面リセットは「Window 起点」で考えるのが最短

.NET MAUI(.NET 9)では、起動の中心が CreateWindow() に寄り、Window がルートページを持つ構造がより前面に出ます。従来の MainPage 差し替えで悩んだら、次の方針に切り替えるのがスムーズです。

  • 「画面を作り直したい」なら Window.Page を差し替える
  • 「履歴だけ戻したい」なら Shell のルート遷移や PopToRoot を使う
  • リセットしても残る状態(DI/キャッシュ/イベント購読)を意識して設計する

ログアウトや権限変更といった “戻れたら困る” 画面遷移こそ、Window ルート差し替えが効きます。まずはシングルウィンドウ前提で実装し、将来マルチウィンドウにする可能性がある場合は、Window 選択の設計だけ先に決めておくと安心です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次