CommunityToolkit.Maui の Popup を「画面幅いっぱい」にしたいのに、HorizontalOptions="Fill" が効かない——そんな悩みを確実に解決するための実装手順と落とし穴を、実運用で使えるコードとあわせて整理しました。DIP と物理ピクセルの違い、プラットフォーム差異、回転やマルチディスプレイ対応まで、これ一つで迷わないように解説します。
.NET MAUI Toolkit の Popup をフル幅にする考え方
結論:HorizontalOptions は「子要素の配置ヒント」であり、Popup のサイズ自体は変わりません。フル幅を実現するには、ルートレイアウト(Grid / StackLayout など)の WidthRequest を明示し、DIP(論理ピクセル)で画面幅を指定します。画面幅は DeviceDisplay.Current.MainDisplayInfo が返す「物理ピクセル」を Density で割って DIP に変換してから使用します。
なぜ DIP が必要か
Android や Windows は DPI(密度)によって物理ピクセルと論理ピクセルが異なるため、物理ピクセルのまま幅を指定すると想定よりも巨大になり、表示崩れや余白・太枠の原因になります。DIP に正規化することが安定表示の鍵です。
実装の全体像(ステップバイステップ)
| 手順 | 解説・補足 |
|---|---|
| 1. 画面サイズの取得 | using Microsoft.Maui.Devices; var info = DeviceDisplay.Current.MainDisplayInfo; // 物理ピクセル double screenWidthDip = info.Width / info.Density; // DIP に変換 DeviceDisplay は物理ピクセルを返すため、必ず Density で割って DIP に直します。 |
| 2. Popup(またはルートレイアウト)に幅を指定 | // 例: x:Name="Root" の Grid / StackLayout に対して Root.WidthRequest = screenWidthDip; // フル幅 // 95% 幅にしたい場合 Root.WidthRequest = screenWidthDip * 0.95; HorizontalOptions ではサイズが変わらないため、WidthRequest を明示するのが確実です。 |
| 3. 余白をなくす(Margin / Padding を 0) | Windows でタイトルバーまで覆いたい場合は、Popup 側または Window 側(アプリのルート)の Margin / Padding を 0 にします。余白が残ると「フル幅じゃない見え方」になります。 |
| 4. 内部コンテンツがはみ出す場合の対策 | 内部コンテンツ側に HorizontalOptions="FillAndExpand" を設定し、必要に応じて Padding を調整します。スクロールが必要なら ScrollView を噛ませます。 |
| 5. バージョン依存の不具合への注意 | .NET 8 / MAUI 8 系では iOS で HorizontalOptions="Fill" がフリーズを引き起こす既知の不具合が報告されています。MAUI と .NET を最新(.NET 9 / MAUI 9 以上)へ更新すると解消が期待できます。 |
最小構成のサンプル(XAML + C#)
XAML(Popup)
<toolkit:Popup
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="YourApp.Views.FullWidthPopup"
CanBeDismissedByTappingOutsideOfPopup="True">
<Grid x:Name="Root" RowDefinitions="Auto,Auto" Margin="0" Padding="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" StrokeThickness="0" Background="White"
Margin="0" Padding="16"
Shadow="Medium" StrokeShape="RoundRectangle 12">
<VerticalStackLayout Spacing="12" HorizontalOptions="FillAndExpand">
<Label Text="フル幅 Popup" FontAttributes="Bold" FontSize="18"/>
<Label Text="画面幅に追従するサンプルです。"/>
<Entry Placeholder="テキストを入力"/>
</VerticalStackLayout>
</Border>
<HorizontalStackLayout Grid.Row="1" Padding="16" Spacing="12" HorizontalOptions="FillAndExpand">
<Button Text="閉じる" Command="{Binding CloseCommand}"/>
<Button Text="確定" Command="{Binding SubmitCommand}" HorizontalOptions="EndAndExpand"/>
</HorizontalStackLayout>
</Grid>
コードビハインド(幅の適用と回転追従)
using CommunityToolkit.Maui.Views;
using Microsoft.Maui.Devices;
namespace YourApp.Views;
public partial class FullWidthPopup : Popup
{
public FullWidthPopup()
{
InitializeComponent();
// 初回表示分のサイズ反映
SizeToFullWidth();
// 端末の回転・DPI 変更に追従
DeviceDisplay.MainDisplayInfoChanged += OnDisplayChanged;
}
void OnDisplayChanged(object? sender, DisplayInfoChangedEventArgs e)
=> SizeToFullWidth();
void SizeToFullWidth(double ratio = 1.0)
{
var info = DeviceDisplay.Current.MainDisplayInfo; // 物理ピクセル
var widthDip = info.Width / info.Density; // DIP に変換
Root.WidthRequest = widthDip * ratio; // 0.95 なども可
Root.Margin = new Thickness(0);
Root.Padding = new Thickness(0);
}
protected override void OnHandlerChanging(HandlerChangingEventArgs args)
{
base.OnHandlerChanging(args);
if (args.OldHandler is not null)
{
// Popup 破棄時に購読解除
DeviceDisplay.MainDisplayInfoChanged -= OnDisplayChanged;
}
}
}
95% 幅・左右センター配置のバリエーション
「完全フル幅だと窮屈」という UI では、95% 幅にして左右中央に配置すると視認性が上がります。
void SizeToFullWidth(double ratio = 0.95) // ← 既定を 95% に
{
var info = DeviceDisplay.Current.MainDisplayInfo;
var widthDip = info.Width / info.Density;
Root.WidthRequest = widthDip * ratio;
Root.HorizontalOptions = LayoutOptions.Center; // 中央配置
}
プラットフォーム別の注意点
| プラットフォーム | 注意点 | 対策 |
|---|---|---|
| iOS | 一部バージョンで HorizontalOptions="Fill" 利用時にフリーズ報告あり。キーボード表示時は下部が隠れる場合も。 | WidthRequest に一本化。必要に応じて ScrollView で可視領域を確保し、キーボード出現時の余白はコンテンツ側 Padding を調整。 |
| Android | DPI が端末差で大きく異なる。物理ピクセルのまま指定するとオーバーフロー。 | DIP 変換は必須。端末回転やマルチウィンドウでも MainDisplayInfoChanged に追従。 |
| Windows | ウィンドウの外周余白やタイトルバーと重なる印象が出ることがある。 | アプリ側の Margin / Padding を 0、Popup のルートも 0。必要に応じて角丸や影を付けて視認性を担保。 |
| マルチディスプレイ | ウィンドウを別モニターに移動した直後はサイズ情報が古いことがある。 | MainDisplayInfoChanged での再計算を確実に行い、Popup 再表示時にもう一度 SizeToFullWidth() を適用。 |
「うまくいかない」を一掃するチェックリスト
- 必ず DIP に変換してから
WidthRequestに代入しているか(info.Width / info.Density)。 - フル幅にするのは Popup 本体ではなくルートレイアウト(
x:Name="Root"の Grid 等)に行っているか。 - 余白の原因となる
Margin/Paddingを Popup 側・Window 側の両方で確認したか。 - 内部コンテンツに
HorizontalOptions="FillAndExpand"を設定しているか。 - 向き変更(縦/横)や外部ディスプレイ移動を想定し、
MainDisplayInfoChangedを購読しているか。 - 旧バージョン依存の不具合を疑ったら、MAUI / .NET を最新安定版に更新して再検証したか。
MVVM と組み合わせる実戦テンプレート
Popup の表示・閉鎖や結果返却はコマンド化しておくと再利用性が高まります。
// 表示側(ViewModel)
public IAsyncRelayCommand ShowPopupCommand => new AsyncRelayCommand(async () =>
{
var popup = new FullWidthPopup();
var result = await Application.Current.MainPage.ShowPopupAsync(popup);
// result をハンドリング(OK/Cancel など)
});
// Popup 側(ViewModel): CloseCommand / SubmitCommand
public IRelayCommand CloseCommand => new RelayCommand(() => Close(null));
public IRelayCommand SubmitCommand => new RelayCommand(() => Close("OK"));
アクセシビリティと操作性の最適化
- タップ領域は 48 DIP 以上を意識(ボタン・アイコン)。
- 初回フォーカスは不要な自動キーボード発火を避ける(特に iOS)。
- スクリーンリーダーで Popup 開閉時にフォーカスが見失われないよう、閉じたら呼び出し元要素へフォーカスを戻す設計を。
- 背景の暗幕(オーバーレイ)は不透明度を上げすぎない(読み上げユーザーがコンテキストを把握しづらくなる)。
スタイルの一元化(App.xaml)
全 Popup で余白ゼロ・角丸・影などを共通化すると実装ミスが減ります。
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
<Style TargetType="toolkit:Popup">
<Setter Property="CanBeDismissedByTappingOutsideOfPopup" Value="True"/>
</Style>
実践 Tips 集
- 角丸の見切れ:フル幅で角丸を使うと左右端が切れたように見えることがあります。
Borderの外側に 4〜8 DIP 程度のPaddingを取り、背景色とのコントラストで「浮かせる」表現に。 - 影の強さ:Windows では影が弱く見えがち。
Shadow="Medium"以上にするか、背景のオーバーレイ濃度を上げて前景を際立たせます。 - アニメーション:開閉アニメーションを短く(150–200ms)するとフル幅でも軽快に感じられます。複雑アニメは低スペック端末でカクつくため注意。
- テキスト選択:入力フォームが多い Popup は
ScrollView+Keyboard出現時の余白調整(下Padding)で可読性を確保。
よくあるエラーと対処一覧
| 症状 | 原因 | 対処 |
|---|---|---|
| Android で右側に灰色の太枠が出る | 物理ピクセルのまま幅指定している | info.Width / info.Density で DIP 化してから WidthRequest へ。 |
iOS で HorizontalOptions="Fill" を使うと固まる | バージョン依存の不具合 | 幅は必ず WidthRequest で制御。最新の MAUI/.NET へ更新して再現有無を確認。 |
| 横向きにするとレイアウトが崩れる | 回転時の再計算をしていない | MainDisplayInfoChanged を購読し、回転検知で SizeToFullWidth() を再実行。 |
| ボタンが左に寄る | 子要素の配置指定が不足 | ボタン行は HorizontalOptions="FillAndExpand"、右寄せボタンは EndAndExpand を併用。 |
拡張:「常にフル幅」を保証するカスタム Popup
プロジェクト内で毎回同じコードを書くのを避けるため、共通基底クラスを用意しておくと便利です。
public abstract class FullWidthPopupBase : Popup
{
protected Grid? Root; // 派生側でセット
protected FullWidthPopupBase()
{
DeviceDisplay.MainDisplayInfoChanged += (_, __) => ApplyWidth();
}
protected void ApplyWidth(double ratio = 1.0)
{
if (Root is null) return;
var info = DeviceDisplay.Current.MainDisplayInfo;
var w = info.Width / info.Density;
Root.WidthRequest = w * ratio;
Root.Margin = Root.Padding = new Thickness(0);
}
protected override void OnHandlerChanging(HandlerChangingEventArgs args)
{
base.OnHandlerChanging(args);
if (args.OldHandler is not null)
{
DeviceDisplay.MainDisplayInfoChanged -= (_, __) => ApplyWidth();
}
}
}
派生側では XAML の Grid x:Name="Root" をそのまま Root に割り当て、OnOpened / コンストラクタで ApplyWidth() を呼ぶだけで済みます。
デザインガイド:フル幅でも読みやすく
- 左右 16–24 DIP の内側余白を保つと、端末ごとの可読性が安定します(Root はフル幅、コンテンツ側に
Padding)。 - モバイルでは 600 DIP を超える長文は視線移動が増えます。段落を細かく分け、見出しと余白でリズムを作ると良いです。
- 閉じる操作は左、確定操作は右に配置し、キーボードが出ても押しやすい高さにボタンを置きます。
テスト観点(品質保証)
- 回転試験:縦⇄横で幅が更新されるか、崩れがないか。
- DPI 変更:Windows の拡大率 100/150/200% でフル幅が成立するか。
- マルチモニター:モニターをまたいだ移動直後に幅が追従するか。
- キーボード出現:iOS/Android でテキスト入力時に主要ボタンが隠れないか。
- アクセシビリティ:スクリーンリーダーで開閉・フォーカス遷移が自然か。
トラブルシューティングの深掘り
ケース:なぜか右端に 8〜12 DIP の隙間が残る
親階層(ページやシェル側)に Padding が設定されていると、その値が視覚的な余白に見えることがあります。Popup 自体は画面全体の上に重なるため、Root をフル幅にしても、背景側のレイアウトが視覚ノイズを作る場合があります。開発時は背景を一時的に派手な色にして境界を見極めると原因切り分けが容易です。
ケース:フル幅だが角丸が欠けるように見える(Windows)
ウィンドウの描画順やスケーリングの都合で、白背景に白い Popup をフル幅で重ねると角丸が見えづらくなることがあります。Border に影(Shadow)や薄い枠線を加えると改善します。
ケース:Popup を閉じるとフォーカスが不定になる
開く直前にフォーカスを持っていた要素を保持しておき、Close() 後に Focus() を戻すハンドリングを挟むとユーザー体験が向上します。
まとめ:この順でやれば失敗しない
- 画面幅(物理ピクセル)を取得し、必ず
Densityで割って DIP にする。 - Popup のルートレイアウトに
WidthRequestを適用する(必要に応じて 95% など比率指定)。 - 余白は
Margin=0/Padding=0を徹底。内部コンテンツはFillAndExpand。 - 回転やディスプレイ変更に備え、
MainDisplayInfoChangedで再計算。 - 挙動が不安定なら MAUI / .NET を最新安定版へアップデートして再検証。
コピーして使える完成版(XAML + C#)
そのまま貼り付けて動作を確認できる、完成度の高いスニペットです。
XAML
<toolkit:Popup
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="YourApp.Views.SampleFullWidthPopup"
CanBeDismissedByTappingOutsideOfPopup="True">
<Grid x:Name="Root" Style="{StaticResource FullWidthRoot}">
<Border StrokeThickness="0" Background="White" Padding="16"
StrokeShape="RoundRectangle 12" Shadow="Medium">
<VerticalStackLayout Spacing="12" HorizontalOptions="FillAndExpand">
<Label Text="お知らせ" FontSize="20" FontAttributes="Bold" />
<Label Text="これは画面幅いっぱいの Popup サンプルです。" />
<Entry Placeholder="ご意見をどうぞ" />
</VerticalStackLayout>
</Border>
<Grid Padding="16" RowDefinitions="Auto"
HorizontalOptions="FillAndExpand" VerticalOptions="End">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="閉じる" Grid.Column="0" Command="{Binding CloseCommand}" />
<Button Text="送信" Grid.Column="1" Command="{Binding SubmitCommand}" />
</Grid>
</Grid>
C#
using CommunityToolkit.Maui.Views;
using Microsoft.Maui.Devices;
namespace YourApp.Views;
public partial class SampleFullWidthPopup : Popup
{
public SampleFullWidthPopup()
{
InitializeComponent();
ApplyWidth(1.0); // フル幅(95% にしたければ 0.95)
DeviceDisplay.MainDisplayInfoChanged += (_, __) => ApplyWidth(1.0);
}
void ApplyWidth(double ratio)
{
var info = DeviceDisplay.Current.MainDisplayInfo;
var widthDip = info.Width / info.Density;
Root.WidthRequest = widthDip * ratio;
Root.Margin = new Thickness(0);
Root.Padding = new Thickness(0);
}
// VM と連携する場合は Close(result) を適宜呼ぶ
}
FAQ
Q. WidthRequest を設定しても横スクロールが出ます。 A. 内部コンテンツに固定幅が残っていませんか?画像や長いテキストに LineBreakMode、画像は Aspect=AspectFit を検討してください。 Q. iOS で角丸がページ端と干渉して汚く見えます。 A. 角丸の直下に 8〜12 DIP の余白を確保し、背景の暗幕をやや濃くする(不透明度 0.4〜0.6)。 Q. マルチディスプレイでサイズが安定しません。 A. Popup 表示直前に ApplyWidth() をもう一度呼ぶと安定します。加えて MainDisplayInfoChanged を購読してください。
一行まとめ
「物理ピクセル ÷ Density → DIP」で幅を求め、ルートレイアウトに WidthRequest を与える——この二点さえ守れば、CommunityToolkit.Maui の Popup をどのプラットフォームでも「画面幅いっぱい」に安定表示できます。

コメント