Windows 向け .NET MAUI アプリで、DisplayAlert() の見た目が突然「ヘッダーが右寄せ」「ボタンの左右が逆」に見える――そんな現象は、アプリの不具合ではなく OS の言語設定に由来する既定動作です。本記事ではそのメカニズムと、影響を受けやすい確認ダイアログだけ LTR(左→右)に固定する実装パターンを、設計判断の勘所とあわせて詳しく解説します。
アラートダイアログのヘッダーが右寄せ・ボタン順が逆に見える理由
Windows 版 .NET MAUI は内部的に WinUI 3 を利用しており、UI のフロー方向(FlowDirection)は、既定では実行環境のカルチャ(言語・地域)に自動追従します。OS がアラビア語・ヘブライ語などの RTL(Right‑to‑Left)言語に切り替わる、もしくはユーザーの地域設定が RTL 文化圏である場合、ページやダイアログは RTL レイアウトを継承します。
その結果、DisplayAlert() で表示される確認ダイアログも同様に RTL になり、次の変化が発生します。
- ダイアログのタイトル(ヘッダー)が右寄せに見える
- 肯定ボタン/否定ボタンの左右が LTR 時と反転する
これは仕様に沿った挙動であり、アプリを .exe(自動更新付きの自己完結型配布など)としてインストールしたか、MSIX で配布したかといった「配布形態の違い」そのものが直接の原因ではありません。実際には、配布後にアプリが動作する「端末側の言語・地域設定」によって UI フロー方向が切り替わり、それをダイアログも継承しているのが根本要因です。
現象の確認ポイント(チェックリスト)
- Windows の「表示言語」または「地域形式」が RTL 言語に設定されていないか
- アプリ起動時点の
CultureInfo.CurrentUICulture/CultureInfo.CurrentCultureのTextInfo.IsRightToLeftがtrueになっていないか - アプリのルートページ(
MainPage)や各ページのFlowDirectionをコード/XAML で明示設定していないか(している場合はそれが優先されます)
診断用コード(現在のカルチャとフロー方向をログ出力)
using System.Globalization;
using Microsoft.Maui.Controls;
void DumpCultureAndFlow()
{
var ui = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
var isRtl = ui.TextInfo.IsRightToLeft || culture.TextInfo.IsRightToLeft;
var page = Application.Current?.MainPage;
System.Diagnostics.Debug.WriteLine($@"
UI Culture: {ui} (RTL={ui.TextInfo.IsRightToLeft})
Culture : {culture} (RTL={culture.TextInfo.IsRightToLeft})
App/MainPage FlowDirection: {page?.FlowDirection}
Effective RTL? {isRtl}
");
}
最短の対処:重要ダイアログだけ LTR に固定する
重要な確認(例:フォーマット、削除、決済確定など)に使うダイアログだけ、RTL 継承を切って LTR に固定すると、利用者の誤押下リスクを最小化できます。
ダイアログ単位で LTR を固定(推奨)
確認ダイアログを表示するときに、FlowDirection.LeftToRight を明示して呼び出します。
bool answer = await Application.Current.MainPage.DisplayAlert(
"USB をフォーマット",
"USB ドライブを本当にフォーマットしますか?",
"はい",
"いいえ",
FlowDirection.LeftToRight); // ← 方向を固定(RTL 継承を無効化)
この呼び出しにより、端末が RTL 文化圏であっても、当該アラートは LTR レイアウト(タイトル左寄せ/肯定ボタンが左側)で確実に表示されます。
ページ/アプリ全体に適用(全 UI を LTR に固定)
アラートだけでなくアプリ全体の見た目を LTR に固定したいときは、アプリ起動時に MainPage へ設定します。
// App.xaml.cs
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
MainPage.FlowDirection = FlowDirection.LeftToRight; // ← 全画面 LTR 固定
}
}
この方法は影響範囲が広く、多言語(RTL 含む)を公式サポートするアプリには非推奨です。ユーザーの文化圏に合わせて自然なレイアウトにしたい場合は、次節の設計方針をご覧ください。
設計判断の指針(どこまで固定すべきか)
| シナリオ | 推奨設定 | 理由 |
|---|---|---|
| 対象市場が LTR のみ | 特に設定不要(既定で LTR) | OS が LTR なら自動で LTR。余計な固定は将来の多言語化を阻害。 |
| 社内配布などで稀に RTL 端末が混在 | 誤操作が痛いダイアログに限り FlowDirection.LeftToRight を明示 | UI 全体は文化圏に従わせ、重要箇所だけ視覚的一貫性を担保。 |
| RTL を含む多言語を公式サポート | 基本は自動継承。必要に応じて個別ビューで LTR/RTL を切替 | 文化圏に適したアクセシビリティと予測可能性を優先。 |
互換性と代替案(.NET のバージョン別)
既存プロジェクトのテンプレートや MAUI のバージョンによっては、DisplayAlert(..., FlowDirection) オーバーロードが使えない場合があります。その場合の現実解を以下にまとめます。
1) 一時的にページのフロー方向だけ LTR に切り替える
ダイアログ表示の前後で MainPage.FlowDirection を退避/復元する方法です。副作用が出ないよう try/finally で確実に戻します。
var page = Application.Current.MainPage;
var prev = page.FlowDirection;
try
{
page.FlowDirection = FlowDirection.LeftToRight;
bool ok = await page.DisplayAlert(
"危険な操作",
"この操作は元に戻せません。続行しますか?",
"はい",
"いいえ");
// ... 判定処理
}
finally
{
page.FlowDirection = prev; // ← 必ず元に戻す
}
2) Windows 専用のネイティブダイアログ(ContentDialog)を使う
Windows 固有実装として、WinUI 3 の ContentDialog を直接呼び出せば、ダイアログ単位で FlowDirection を明示できます。プラットフォーム分岐が必要ですが、見た目と動作を厳密に制御できます。
#if WINDOWS
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Maui.Platform;
// 現在の Window(MauiWinUIWindow)を取得
var window = Application.Current.Windows.First().Handler.PlatformView as MauiWinUIWindow;
var dialog = new ContentDialog
{
XamlRoot = window.Content.XamlRoot,
Title = "USB をフォーマット",
Content = "USB ドライブを本当にフォーマットしますか?",
PrimaryButtonText = "はい",
CloseButtonText = "いいえ",
DefaultButton = ContentDialogButton.Close, // ← デフォルトにしたい側
FlowDirection = FlowDirection.LeftToRight // ← 方向を固定
};
var result = await dialog.ShowAsync();
bool answer = result == ContentDialogResult.Primary;
#endif
3) 拡張メソッドで呼び出しを統一
プロジェクトのバージョン差やプラットフォーム差を吸収するために、アプリ内にダイアログ用サービス/拡張メソッドを設けるのが保守に有利です。
public static class AlertExtensions
{
public static async Task<bool> DisplayAlertLtrAsync(
this Page page, string title, string message, string accept, string cancel)
{
#if WINDOWS
// Windows 専用の厳密制御
var window = Application.Current.Windows.First().Handler.PlatformView as MauiWinUIWindow;
var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog
{
XamlRoot = window.Content.XamlRoot,
Title = title,
Content = message,
PrimaryButtonText = accept,
CloseButtonText = cancel,
FlowDirection = Microsoft.UI.Xaml.FlowDirection.LeftToRight
};
var r = await dialog.ShowAsync();
return r == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary;
#else
// 他プラットフォームはページ側で一時固定
var prev = page.FlowDirection;
try
{
page.FlowDirection = FlowDirection.LeftToRight;
return await page.DisplayAlert(title, message, accept, cancel);
}
finally
{
page.FlowDirection = prev;
}
#endif
}
}
呼び出し側は await this.DisplayAlertLtrAsync(...) と書けるため、誤用を防げます。
ボタン順序の一貫性とアクセシビリティの両立
Windows の LTR 文化圏では一般に「肯定(OK/Yes)が左、否定(Cancel/No)が右」が自然です。一方、RTL 文化圏ではこれが反転します。ユーザーにとって自然な順序=事故を減らす順序である点を踏まえると、アプリが公式に RTL をサポートする場合は、安易な全体 LTR 固定は避けるべきです。
誤操作が致命的なダイアログのみ LTR を強制し、その他は既定の文化圏に従わせる設計が現実解です。スクリーンリーダー等の支援技術は文化圏に整合したフロー方向を前提に最適化されているため、むやみに反転させると読み上げ順やフォーカス移動の予測可能性が低下します。
「.exe でインストールしたら発生した」の真相
MSIX と自己完結型 .exe(Self‑contained)では、ランタイムやリソースのパッケージングが異なるため、結果として「デフォルト言語の選ばれ方」が変わるケースがあります。たとえば、自己完結型配布で多言語リソースを含めなかった場合、Windows 側の言語優先順位との組み合わせで CurrentUICulture が RTL 言語にフォールバックし、アプリ起動直後から RTL と判定されることがあります。ただし重要なのは、発生要因の本質は『OS から受け取るカルチャが RTL だった』ことであり、配布形式そのものは引き金であって直接原因ではないという点です。
UI 全体を LTR/RTL で安全に制御するためのベストプラクティス
- カルチャに依存しない重要箇所の固定化: 破壊的操作の確認ダイアログは LTR に固定し、ラベル・説明文は明確に。
- テキストとアイコンの整合: RTL 環境では矢印・三角アイコンの向きが意味に直結します。LTR 固定する要素ではアイコンの向きも合わせる。
- ナビゲーションの一貫性: ページ全体を LTR に固定する場合、ナビゲーションボタンの位置(戻る/進む)も LTR 前提で配置。
- テストのバリエーション: CI で
ar-SAとhe-ILのヘッドレス UI テストを走らせ、RTL と LTR の両方で誤押下や視覚ずれがないか検証。
簡易ユニットテスト例(FlowDirection の既定値検証)
using System.Globalization;
using NUnit.Framework;
[TestFixture]
public class FlowDirectionTests
{
[TestCase("ja-JP", ExpectedResult = false)]
[TestCase("en-US", ExpectedResult = false)]
[TestCase("ar-SA", ExpectedResult = true)]
[TestCase("he-IL", ExpectedResult = true)]
public bool IsRtl_ByCulture(string culture)
{
var ci = new CultureInfo(culture);
return ci.TextInfo.IsRightToLeft;
}
}
ActionSheet(選択肢ダイアログ)も同じ影響を受ける
DisplayActionSheet() で表示するボトムシート/選択肢ダイアログも、同様にフロー方向の継承を受けます。重要な選択を促す場合は、DisplayAlert() と同様の方針(個別 LTR 固定またはページ一時切替)で制御してください。
具体的な UI デザインのヒント
- 文言の左右依存を避ける: 「左のボタンを押してください」のような指示は文化圏で意味が変わるため、ボタン名 をそのまま指示に用いる(例:「はい を押してください」)。
- 色だけに頼らない: 赤=否定、青=肯定といった色の意味付けはテーマ/ハイコントラスト設定で崩れることがある。テキストとアイコンで補強。
- デフォルトフォーカスの明示: どのボタンが既定か(Enter 確定か)をコードで設定し、誤確定を防止(WinUI の
ContentDialog.DefaultButton等)。
導入から運用までの落とし穴と回避策
| 落とし穴 | 症状 | 回避策 |
|---|---|---|
| ページ LTR 固定の置き忘れ | 他ページが RTL なのに、特定ページだけ LTR のまま残る | ナビゲーション遷移時に既定フロー方向へ戻す/DI 化した AlertService で一元管理 |
| カルチャ依存の単体テスト不足 | 開発者のローカル環境では再現しない | CI で ja-JP と ar-SA の 2 パターンを実行 |
| プラットフォーム差の吸収漏れ | Android/iOS と Windows で見た目・順序がずれる | 拡張メソッド/サービスで呼び出しを統一し、差分を内部で吸収 |
実装をチームに浸透させるためのコーディング標準
// 例: IAlertService による統一 API
public interface IAlertService
{
Task<bool> ConfirmAsync(string title, string message, string accept, string cancel, bool forceLtr = false);
}
public class AlertService : IAlertService
{
private readonly Page _page;
public AlertService(Page hostPage) => _page = hostPage;
public async Task<bool> ConfirmAsync(string title, string message, string accept, string cancel, bool forceLtr = false)
{
if (!forceLtr)
return await _page.DisplayAlert(title, message, accept, cancel);
// LTR 強制(プラットフォーム差は内部で吸収)
#if WINDOWS
var window = Application.Current.Windows.First().Handler.PlatformView as MauiWinUIWindow;
var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog
{
XamlRoot = window.Content.XamlRoot,
Title = title, Content = message,
PrimaryButtonText = accept, CloseButtonText = cancel,
FlowDirection = Microsoft.UI.Xaml.FlowDirection.LeftToRight
};
var result = await dialog.ShowAsync();
return result == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary;
#else
var prev = _page.FlowDirection;
try
{
_page.FlowDirection = FlowDirection.LeftToRight;
return await _page.DisplayAlert(title, message, accept, cancel);
}
finally
{
_page.FlowDirection = prev;
}
#endif
}
}
アプリ全域で IAlertService を使うことで、「危険操作の確認は LTR 固定にする」という方針を守りやすくなります。
FAQ
Q. OS 言語が日本語でも発生することはある?
A. 端末の「地域形式」やユーザーの言語優先順位、またはアプリのローカライズ・パッケージングの都合で、CurrentUICulture が RTL にフォールバックする場合があります。診断コードで実値を確認してください。
Q. LTR 固定はローカライズ的に正しいの?
A. 公式に RTL をサポートするプロダクトでは、基本は自動継承が望ましいです。安全性と一貫性の担保が必要な重要ダイアログに限って LTR 固定を検討しましょう。
Q. DisplayActionSheet も反転する?
A. はい。アラート同様にフロー方向を継承します。重要な選択肢一覧で誤操作リスクがある場合は、同様の固定を検討してください。
まとめ
- 現象の正体: .NET MAUI は OS のカルチャに基づく自動 RTL/LTR 切替を行い、
DisplayAlertもそれを継承するため、ヘッダー右寄せやボタン順の反転が起きる。 - 最短の解決: 重要ダイアログだけ
FlowDirection.LeftToRightを明示、もしくは表示前後でページのフロー方向を一時的に LTR に切替える。 - 設計判断: 多言語対応を念頭に、固定は最小限に留め、ユーザーの文化圏に自然な UI を基本にする。
- 保守性: AlertService/拡張メソッドで呼び出しを統一し、プラットフォーム差とバージョン差を内部で吸収。
詳細仕様や追加のヒントは、.NET MAUI の公式ドキュメント「Right‑to‑left localization」「Display an alert」を参照してください。公開運用中のアプリで誤操作が致命傷になり得る箇所ほど、今回の方針(重要ダイアログのみ LTR 固定)を適用する価値があります。
実装スニペット集(コピー&ペースト可)
アラート:個別 LTR 固定(推奨)
bool answer = await Application.Current.MainPage.DisplayAlert(
"USB をフォーマット",
"USB ドライブを本当にフォーマットしますか?",
"はい",
"いいえ",
FlowDirection.LeftToRight);
アプリ全体を LTR 固定(多言語アプリでは慎重に)
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
MainPage.FlowDirection = FlowDirection.LeftToRight;
}
}
ページ一時固定(オーバーロードがない場合)
var page = Application.Current.MainPage;
var prev = page.FlowDirection;
try
{
page.FlowDirection = FlowDirection.LeftToRight;
var ok = await page.DisplayAlert("確認", "続行しますか?", "はい", "いいえ");
}
finally
{
page.FlowDirection = prev;
}
Windows ネイティブ ContentDialog で厳密制御
#if WINDOWS
using Microsoft.UI.Xaml.Controls;
using Microsoft.Maui.Platform;
var window = Application.Current.Windows.First().Handler.PlatformView as MauiWinUIWindow;
var dlg = new ContentDialog
{
XamlRoot = window.Content.XamlRoot,
Title = "確認",
Content = "続行しますか?",
PrimaryButtonText = "はい",
CloseButtonText = "いいえ",
FlowDirection = Microsoft.UI.Xaml.FlowDirection.LeftToRight
};
var r = await dlg.ShowAsync();
bool ok = r == ContentDialogResult.Primary;
#endif
運用チェックリスト(配布前に必ず確認)
- RTL 端末(ar‑SA など)で「肯定は左・否定は右」になるべき箇所だけ LTR 固定が効いているか
- LTR 端末(ja‑JP 等)では全 UI が自然な見た目か(タイトル左寄せ、読み順、フォーカス順)
- テーマ・ハイコントラスト・スケーリング(125% 以上)でも視認性に問題がないか
- アクセシビリティ(キーボード操作・スクリーンリーダー)で操作順序が混乱しないか
おわりに
「アラートのヘッダーが右寄せ」「ボタンの左右が逆」という現象は、MAUI×Windows における文化圏適応の副作用です。誤操作リスクがあるダイアログだけを LTR に固定する――この最小介入の方針を採ることで、ユーザーの文化圏を尊重しながら安全性と一貫性を両立できます。既存プロダクトにも導入しやすい実装パターンとガイドラインを用意しましたので、ぜひ今日から適用してみてください。

コメント