Android と iOS では動くのに、Windows(WinUI)だけ CollectionView がまったくスクロールしない――。この現象は .NET MAUI を使い始めた多くの開発者が一度は踏む落とし穴です。原因は難解なバグではなく「レイアウト計測の前提」が合っていないことがほとんど。本記事では“なぜ起こるのか”を仕組みから紐解き、再現コード・確実に効く修正パターン・デバッグ手順・代替案までまとめて解説します。
症状の整理と最小再現
まずは現象を明確にします。以下のような構成のページを想像してください。
- 上部に検索フォームやフィルタ UI
- 下部に一覧(
CollectionView)
Android / iOS ではリストがスクロールするのに、Windows(WinUI)だけ全件が縦に“展開”され、ホイールやタッチでスクロールしません。ListView に置き換えるとスクロールしますが、CollectionView に戻すと再発します。
最小再現例(問題が起きるパターン)
次の XAML は一見正しく見えますが、WinUI ではスクロールしない典型です。
<ContentPage ...>
<VerticalStackLayout>
<Grid Padding="16" RowDefinitions="Auto,Auto">
<Entry Placeholder="キーワード" />
<Button Grid.Row="1" Text="検索" />
</Grid>
<CollectionView
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource ItemTemplate}" /> <!-- ← 高さ未指定 -->
ポイント:親の VerticalStackLayout は縦方向に子を“積むだけ”で、子に与える高さには上限を設けません。そのため CollectionView は「無限の高さがある」と誤解し、仮想化をやめて全アイテムを展開します。結果としてスクロールの余地がなくなります。
原因:WinUI の計測と CollectionView の仮想化ルール
CollectionView は仮想化を前提に設計されています。スクロール可能な「ビューポートの高さ」が有限であると分からない限り、仮想化が効かず、すべてのアイテムを測定・配置しようとします。特に WinUI(Windows)では「親から子へ伝播するレイアウト計測」が厳密で、Infinity(無限)の高さが伝わると、スクロール領域を確定できず展開に倒れやすい傾向があります。
対照的に iOS/Android では、親がざっくりした制約でも子がうまく推測してくれる場面が多く、結果としてスクロール“しているように見える”ことがあります。ゆえに Windows だけ違うというギャップが発生します。
| プラットフォーム | 高さ制約が曖昧な場合 | 典型的な挙動 |
|---|---|---|
| Windows(WinUI) | 厳密 | 仮想化が無効化され、全アイテム展開 → スクロール不可 |
| Android | 比較的寛容 | 状況によりスクロール可能に見えるが不安定 |
| iOS | 比較的寛容 | 多くのケースでスクロール可能に見えるが前提は同じ |
解決策の核心:固定高さ(有限なビューポート)を明示する
最も確実で推奨されるのは 「CollectionView に有限の高さを与える」ことです。手段は複数あります。
Grid の行に固定高さを割り当てる
UI を上下 2 段に分け、下段(CollectionView の置き場)に固定高を与えます。WinUI ではこれが特に安定します。
<Grid RowDefinitions="Auto,400"> <!-- 下段は高さ 400 に固定 -->
<StackLayout Grid.Row="0" Padding="16">
<Entry Placeholder="キーワード" />
<Button Text="検索" />
</StackLayout>
固定値は要件に合わせて調整します。ウィンドウサイズ連動が必要なら *(スター)を使う設計もありますが、親が確実に有限の高さを持つレイアウトでないと期待通りに働かないことがあるため、最初は固定ピクセルで動作確認するのが安全です。
CollectionView 自身に HeightRequest を指定する
レイアウトの都合で Grid を使いにくい場合は、CollectionView 自身に HeightRequest を与えます。
<VerticalStackLayout>
<Grid Padding="16" RowDefinitions="Auto,Auto">
<Entry Placeholder="キーワード" />
<Button Grid.Row="1" Text="検索" />
</Grid>
HeightRequest は「最低限この高さで配置したい」という意思表示です。親側の設計次第ではこの値がそのまま採用されない場合もありますが、WinUI では多くのケースで仮想化・スクロールが復活します。
スター割り当て(応用)と注意点
ウィンドウサイズに応じて柔軟に伸縮させたい場合は、Grid の行を Auto, * にして下段で 残りの高さを丸ごと割り当てるのも有効です。ただし、親のさらに外側が VerticalStackLayout など“縦に伸び続ける”コンテナだと、* が有限の高さとして解決されないことがあります。ウィンドウ直下(または ContentPage 直下)に Grid を置き、下段 * に CollectionView を入れるのが鉄板です。
どうしても高さを固定できないとき:ScrollView でラップする
要件上、どうしても固定高さを与えられない場合は、ScrollView で CollectionView を包む方法があります。
<ScrollView>
<CollectionView
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource ItemTemplate}" />
</ScrollView>
この構成ならスクロール自体は可能ですが、ネストされたスクロールと仮想化の無効化によりパフォーマンスが低下しやすく、数百件以上のデータではスクロールが重くなる・メモリ使用量が増えるなどの副作用が出がちです。最終手段としてのみ選択してください。
デバッグの実践テクニック(可視化と計測)
「本当に固定高さが効いているのか?」「別の要素がタップを奪っていないか?」を素早く切り分けるには、次の手順が有効です。
背景色でレイアウト境界を可視化
<Grid BackgroundColor="LightGray" RowDefinitions="Auto,400">
<StackLayout Grid.Row="0" BackgroundColor="AliceBlue"> ... </StackLayout>
<CollectionView Grid.Row="1" BackgroundColor="Lavender" ... />
</Grid>
各コンテナや CollectionView 自体に薄い色をつけると、どこまでが表示領域かが直感的に分かります。
Live Visual Tree / レイアウトデバッグ
Visual Studio の Live Visual Tree と Live Property Explorer を使うと、実行中の UI 階層と要素サイズ(ActualWidth/ActualHeight)を確認できます。Windows で「CollectionView.ActualHeight が意図した値になっていない」場合は、親が無限の高さを渡しているシグナルです。
SizeChanged ログ出力
// 例:コードビハインド
collectionView.SizeChanged += (s, e) =>
System.Diagnostics.Debug.WriteLine(
$"CV Size = {collectionView.Width} x {collectionView.Height} (ActualH={collectionView.Height})");
イベントで実測値を追うと、「スクロール不可のときは異様に大きい高さになっている」ことが視覚的に分かります。
ヒットテストの妨害確認
ロード中インジケーター(ActivityIndicator)や全画面パネルが IsVisible のまま前面に残っていると、スクロールのタッチやホイールが届きません。疑わしい要素があれば一時的に InputTransparent="True" にして挙動を確認しましょう。
よくある落とし穴と対策
- 親が ScrollView、子が CollectionView:二重スクロールは避ける。スクロールはどちらか一方に。
- VerticalStackLayout 直下に CollectionView:ほぼ確実に高さ未確定。Grid で領域を切るか
HeightRequestを指定。 - DataTemplate の高さが極端に大きい:1 アイテムが画面より高いとスクロールが“効いていない”ように見える。テンプレートの余白や画像サイズを見直す。
- 余計な透明オーバーレイ:
IsVisibleがfalseでも実は残っているケースは少ないが、ZIndex の高い要素は疑う価値有り。 - ItemsLayout 未設定:縦リスト前提なら
ItemsLayout="VerticalList"を明示するのは良い習慣。
プラットフォーム別の注意事項(要点比較)
| 観点 | Windows (WinUI) | Android | iOS |
|---|---|---|---|
| 高さ未指定の影響 | 強く影響。スクロール不可になりやすい | ケースバイケース | ケースバイケース |
| 推奨対策 | 固定高または HeightRequest を明示 | 親の高さ制約を整える | 親の高さ制約を整える |
| 代替コンポーネント | 小規模なら ListView / BindableLayout | 同左 | 同左 |
| ScrollView でラップ | 最終手段(性能リスク) | 最終手段 | 最終手段 |
完成版レイアウト例(実戦向け)
以下は現場で扱いやすい構成です。上部にフィルタ、下部に固定高のリスト領域を作ります。
<ContentPage x:Class="SampleApp.Pages.ResultPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Title="検索結果">
<Grid Grid.Row="0" Padding="12" ColumnDefinitions="*,Auto">
<Entry Placeholder="キーワード" />
<Button Grid.Column="1" Text="検索" />
</Grid>
<Grid Grid.Row="1" RowDefinitions="Auto,*">
<Label Text="{Binding Items.Count, StringFormat='件数: {0}'}"
Padding="12,0" />
<CollectionView
Grid.Row="1"
ItemsSource="{Binding Items}"
ItemsLayout="VerticalList"
SelectionMode="None"
ItemTemplate="{DataTemplate}"
>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="12" ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<Image Grid.RowSpan="2" WidthRequest="48" HeightRequest="48" />
<Label Grid.Column="1" Text="{Binding Title}" FontAttributes="Bold" />
<Label Grid.Column="1" Grid.Row="1" Text="{Binding Summary}" LineBreakMode="TailTruncation" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
コツ:外側の Grid(RowDefinitions="Auto,*")で、下段に「残りの高さすべて」を確実に割り当てています。その下段の内側でもう一段 Grid を切り、件数表示(Auto)とリスト領域(*)を分離することで、常に有限のビューポートを確保できます。これにより WinUI でも確実にスクロールします。
チェックリスト:WinUI でスクロールを復活させるまで
- ページ直下に
Gridを置き、RowDefinitions="Auto,*"で下段に残りの高さを割り当てる。 - 下段に
CollectionViewを置く。うまくいかない場合は一時的にHeightRequestを指定して挙動を確認。 - 背景色を付けて領域を可視化。
ActualHeightが期待通りになっているか Live Visual Tree で確認。 - ロード中オーバーレイなど、タップを奪う要素が前面に残っていないかを確認(必要に応じて
InputTransparent)。 - 最後の手段としてのみ
ScrollViewでラップ。大量データでの性能影響を負荷試験でチェック。
代替アプローチの比較
| 手段 | 適合するケース | メリット | デメリット |
|---|---|---|---|
| CollectionView(本命) | 中〜大規模データ、仮想化が欲しい | 高速・柔軟、テンプレート自由度が高い | 高さ未確定だとスクロール不可に陥る |
| ListView | 小規模データ、シンプルなテンプレート | セットアップ容易、WinUI でも安定しがち | 将来性/拡張性は CollectionView に劣る |
| BindableLayout + VerticalStackLayout | ほんの数〜十数件程度 | 学習コストが低い、構造が簡単 | 仮想化なし。大量データに不向き |
| ScrollView で包む | 暫定運用・PoC | とりあえずスクロールはできる | 性能低下・メモリ増・ネストスクロール問題 |
運用のヒント:ウィンドウリサイズ、アクセシビリティ、性能
ウィンドウリサイズ対応
「固定 400px」などの値は、開発中の確認には便利ですが、最終的には Auto,* で“残り領域を丸ごと与える”のが実運用では快適です。ウィンドウ最大化・スナップ・モニター解像度変更にも自然に追従します。
アクセシビリティ
フォントスケールが上がるとテンプレートが大きくなり、1 画面の表示件数が減ります。RowDefinitions の * 配分で余白をケチらない、タップ領域を十分に確保するなど、実機での確認を忘れずに。
性能チューニング
- 画像はサムネイル化し、高さを固定する。
- テンプレート内の入れ子 Grid を減らし、不要な影・角丸を避ける。
- バインディングは必要最小限に。値コンバーター多用はコストになり得る。
- ページングや
RemainingItemsThresholdによる遅延読み込みを組み合わせ、大量データを一括で抱え込まない。
トラブルシューティングの深掘り Q&A
Grid の * を使ったのにスクロールしません
外側に VerticalStackLayout や ScrollView があると、* が“有限の高さ”に解決されないケースがあります。ページ直下(または Shell のコンテンツ直下)に Grid を置き、RowDefinitions="Auto,*" とし、CollectionView を Grid.Row="1" に移してください。
固定高さの指定値はどのくらいが妥当?
まずは 400〜600px 程度で感触を掴み、その後 * 構成に移行してウィンドウサイズ連動に切り替えるのが現実的です。どちらの場合も「有限のビューポート」が確保できているかを Live Visual Tree で確認するのが近道です。
ListView なら動くのに CollectionView では動かないのはなぜ?
ListView は内部でのスクロール管理が単純で、親の高さ制約が曖昧でも動いてしまうことがあります。一方 CollectionView は仮想化を前提にしているため、スクロール領域の確定がより厳密です。CollectionView を使うと決めたら、ビューポートの高さを明示する設計が必須と考えてください。
実装テンプレート(コピペ OK)
以下の 2 パターンは WinUI での安定動作が確認しやすい雛形です。
パターン A:Grid で下段を固定高
<Grid RowDefinitions="Auto,420">
<StackLayout Grid.Row="0" Padding="12">...検索UI...</StackLayout>
<CollectionView Grid.Row="1"
ItemsLayout="VerticalList"
ItemsSource="{Binding Items}" />
</Grid>
パターン B:Grid の * + もう一段 Grid で領域分割
<Grid RowDefinitions="Auto,*">
<StackLayout Grid.Row="0">...フィルタ...</StackLayout>
<Grid Grid.Row="1" RowDefinitions="Auto,*">
<Label Text="結果" Padding="8,0"/>
<CollectionView Grid.Row="1"
ItemsLayout="VerticalList"
ItemsSource="{Binding Items}" />
</Grid>
</Grid>
どちらも「CollectionView が入るコンテナの高さが有限」であることが肝です。
まとめ
WinUI で CollectionView がスクロールしない主因は「高さ未確定」です。解決の基本は次の 3 点。
- 有限のビューポートを与える:Grid の行を固定高にする/
HeightRequestを指定する。 - 外側のレイアウトを見直す:ページ直下に Grid を置き、
Auto,*で下段に残りの高さを割り当てる。 - どうしても難しいときは:最終手段として ScrollView でラップ。ただし性能に注意。
背景色で領域を可視化し、Live Visual Tree で実測値を確認しながら調整すれば、原因特定は難しくありません。いったん「有限の高さ」を確保できれば、WinUI でも CollectionView は本来の力(仮想化と高速描画)を発揮し、安定したスクロール体験を提供できます。
付録:トラブルシューティング・クイック表
| 症状 | 考えられる原因 | 対処 |
|---|---|---|
| スクロールしない | 親から無限の高さが渡っている | Grid で領域を切る/HeightRequest を設定 |
| スクロールバーが出ない | 全アイテムが展開されている | ビューポート有限化(固定高・*割当) |
| タッチしても反応しない | 前面オーバーレイがヒットテストを奪取 | InputTransparent 一時付与で切り分け |
| ウィンドウを小さくすると崩れる | 固定ピクセルと動的要素の混在 | Auto,* 設計へ移行、最小サイズ制約も検討 |
| 大量データで重い | ScrollView でラップして仮想化喪失 | ラップをやめて有限高さを確保、ページング導入 |
付録:ViewModel の雛形
public class ResultViewModel
{
public ObservableCollection<Item> Items { get; } = new();
public ResultViewModel()
{
// ダミーデータ
for (int i = 0; i < 200; i++)
{
Items.Add(new Item { Title = $"タイトル {i}", Summary = "説明テキスト..." });
}
}
}
public class Item
{
public string Title { get; set; }
public string Summary { get; set; }
}
データが十分に多いときにのみスクロールが発生するため、負荷検証では 100〜1000 件程度を用意して動作と性能を同時に確認しましょう。
終わりに
「WinUI だけスクロールしない」は、.NET MAUI の設計ミスではなくレイアウト制約の伝え方の違いが生む現象です。今日からは迷わず「CollectionView に有限の高さを与える」を合言葉に、Grid で領域を切るか HeightRequest を指定して、堅牢な一覧画面を作っていきましょう。

コメント