mcpField
M
maui-collectionview

maui-collectionview

github.com/dotnet開発5.8k3.3k 回インストール1 日前に更新
リポジトリ

maui-collectionview について

maui-collectionviewは、.NET MAUIのCollectionViewでスクロール可能なリストやグリッドを作る開発者向けのリファレンススキルです。APIの選択やバインディング・レイアウト上の問題の回避、必要に応じたListViewからの移行を扱います。

主な機能

  1. 01リストとグリッドのレイアウト指針
  2. 02選択・グループ化・更新のパターン
  3. 03インクリメンタル読み込みとスワイプ操作
  4. 04バインディングと性能の注意点
  5. 05ListViewからの移行ガイド

活用シーン

  1. 01データバインドされたリストやグリッドの作成
  2. 02選択・グループ化・プル更新の追加
  3. 03インクリメンタル読み込みやスワイプ操作の実装

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dotnet/skills --skill maui-collectionview

インストール後、Claude Code で/maui-collectionviewを入力して読み込まれたか確認します。

同梱ファイルreferences/collectionview-api.md

詳細

CollectionView — .NET MAUI

CollectionView は、.NET MAUI でスクロール可能なデータのリストやグリッドを表示するための主要なコントロールです。パフォーマンスが向上し、柔軟なレイアウトに対応しており、ViewCell も不要なため、ListView に代わるコントロールです。

使用する場面

  • スクロール可能なデータ項目のリストまたはグリッドを表示する
  • オブジェクトのコレクションを、テンプレート化された項目レイアウトにバインドする
  • 選択(単一または複数)、グループ化、またはプルツーリフレッシュを追加する
  • 無限スクロール/インクリメンタル読み込みを実装する
  • リスト項目にスワイプ操作を表示する
  • データがない場合に空の状態を表示する

使用しない場面

  • 項目数が固定された静的なレイアウト — Grid または StackLayout を直接使用してください
  • 地図のピンのリスト — Microsoft.Maui.Controls.Maps NuGet パッケージを使用してください
  • テーブル形式のデータ入力フォーム — 標準のフォームコントロールを使用してください
  • 操作のない、テキストのみのシンプルなリスト — StackLayout 上の BindableLayout を検討してください

スコープの制御 — 質問されたことだけに答える

このスキルは参照資料であり、すべてを適用するチェックリストではありません。ほとんどの質問では、ここにあるセクションのうち1つか2つだけが必要です。残りまで持ち出すと、回答の質が下がります。

中止条件 — 次の場合は対応しないでください:

  • ユーザーの質問が限定的な場合。 その質問だけに答えてください。質問されていないグループ化、スワイプ操作、空のビュー、スナップポイント、パフォーマンスのヒントを付け加えないでください。
  • ユーザーの既存コードがすでに動作している場合。 ここにある例に合わせるためだけに、動作するマークアップを書き換えないでください。具体的な問題があれば指摘し、なければ問題がないことを伝えて、質問された内容に答えてください。
  • 変更がスタイル上のものにすぎない場合。 名前の変更、属性の並べ替え、すでに正しく動作するテンプレートの構成変更は、修正ではなく無用な変更です。
  • 対象のコントロールが CollectionView ではない場合。 CarouselView、BindableLayout、および保守中のコードにある ListView には、それぞれ異なるルールがあります。ユーザーが求めていない ListView のコードを書き換えないでください。ただし、どのコントロールを使うべきか尋ねられた場合や、Xamarin.Forms からの移行の場合は、CollectionView を推奨してください(ListView からの移行を参照)。
  • 問題の本質がバインディング、DI、またはナビゲーションの問題で、リストが関係しているにすぎない場合。 maui-data-binding、maui-dependency-injection、または maui-shell-navigation を参照してください。

以下の API セクションは参照資料であり、チェックリストではありません。関連する場合にのみ提示してください。 違反すると動作しないコードになったり、コンパイル時チェックがひそかに失われたりするため、譲れないルールが4つあります:

  1. CollectionView で DataTemplate のルートに ViewCell を使用しないでください。
  2. 初回レンダリング後にリストを変更する場合は、ObservableCollection<T> を使用してください。
  3. バインドされたコレクションは UI スレッド上で変更してください。
  4. コンパイル済みバインディングを使用するには、すべての DataTemplate とページのルートに x:DataType を設定してください。

それ以外のすべて(サイズ設定方法、スナップポイント、ヘッダー/フッター、空のビューなど)は任意であり、ユーザーの実際の問題に対応する場合にのみ提示してください。

入力

  • ItemsSource にバインドするデータソース(通常は ObservableCollection<T>)
  • 各項目の表示方法を定義する DataTemplate
  • 任意:レイアウト設定、選択モード、グループ化モデル、空のビュー

基本セットアップ

そのままコピーして貼り付けられる完全なページです。重要なのは 2 点です。このスキル内のすべての x:DataType="models:Item" が前提とする xmlns:models 宣言と、ルートの x:DataType です。これがないと、外側の ItemsSource バインディングがコンパイルされません。

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:MyApp.Models"
             xmlns:vm="clr-namespace:MyApp.ViewModels"
             x:DataType="vm:ItemsViewModel"
             x:Class="MyApp.ItemsPage">
    <ContentPage.BindingContext>
        <vm:ItemsViewModel />
    </ContentPage.BindingContext>
    <CollectionView ItemsSource="{Binding Items}">
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="models:Item">
                <HorizontalStackLayout Padding="8" Spacing="8">
                    <Image Source="{Binding Icon}" WidthRequest="40" HeightRequest="40" />
                    <Label Text="{Binding Name}" VerticalOptions="Center" />
                </HorizontalStackLayout>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

以降のスニペットでは、CollectionView 要素のみを示します。スニペットをユーザーに渡す際は、使用する各プレフィックスに対応する xmlns: 宣言を含めてください。宣言がないと XAML がコンパイルされません。

上記のインラインの <ContentPage.BindingContext> により、例が単独で完結します。依存性注入を使用するアプリでは、代わりに ViewModel を登録し、コンストラクターインジェクションを通じて割り当ててください(BindingContext = vm;)。詳しくは maui-dependency-injection スキルを参照してください。

主なルール:

  • 追加や削除時に UI が更新されるよう、ItemsSource は ObservableCollection<T> にバインドします。
  • 各アイテムテンプレートのルートは Layout または View にします。ViewCell は使用しないでください。
  • コンパイル済みバインディングを使用するには、DataTemplate に必ず x:DataType を設定します。

レイアウト

配置を制御するには ItemsLayout を設定します。既定値は VerticalList です。

レイアウトXAML 値
垂直リストVerticalList(既定)
水平リストHorizontalList
垂直グリッドOrientation="Vertical" を指定した GridItemsLayout
水平グリッドOrientation="Horizontal" を指定した GridItemsLayout
グリッドレイアウト
<CollectionView ItemsSource="{Binding Items}">
    <CollectionView.ItemsLayout>
        <GridItemsLayout Orientation="Vertical"
                         Span="2"
                         VerticalItemSpacing="8"
                         HorizontalItemSpacing="8" />
    </CollectionView.ItemsLayout>
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="models:Item">
            <Border Padding="8" StrokeThickness="0">
                <VerticalStackLayout>
                    <Image Source="{Binding Image}" HeightRequest="120" Aspect="AspectFill" />
                    <Label Text="{Binding Name}" FontAttributes="Bold" />
                </VerticalStackLayout>
            </Border>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>
水平リスト
<CollectionView ItemsSource="{Binding Items}"
                ItemsLayout="HorizontalList" />

選択

選択モード
モードバインドするプロパティバインディングモード
None——
SingleSelectedItemTwoWay
MultipleSelectedItemsOneWay
<CollectionView ItemsSource="{Binding Items}"
                SelectionMode="Single"
                SelectedItem="{Binding CurrentItem, Mode=TwoWay}"
                SelectionChangedCommand="{Binding ItemSelectedCommand}" />

Multiple 選択では、SelectedItems(型は IList<object>)にバインドします。

<CollectionView SelectionMode="Multiple"
                SelectedItems="{Binding ChosenItems, Mode=OneWay}" />
選択時のビジュアル状態

VisualStateManager を使って選択したアイテムをハイライト表示します。

<CollectionView.ItemTemplate>
    <DataTemplate x:DataType="models:Item">
        <Grid Padding="8">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup Name="CommonStates">
                    <VisualState Name="Normal">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor" Value="Transparent" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState Name="Selected">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor"
                                    Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Label Text="{Binding Name}" />
        </Grid>
    </DataTemplate>
</CollectionView.ItemTemplate>

グループ化

  1. List<T> を継承するグループクラスを作成します。
public class AnimalGroup : List<Animal>
{
    public string Name { get; }
    public AnimalGroup(string name, List<Animal> animals) : base(animals)
    {
        Name = name;
    }
}
  1. ObservableCollection<AnimalGroup> にバインドし、IsGrouped="True" を設定します。
<CollectionView ItemsSource="{Binding AnimalGroups}"
                IsGrouped="True">
    <CollectionView.GroupHeaderTemplate>
        <DataTemplate x:DataType="models:AnimalGroup">
            <Label Text="{Binding Name}"
                   FontAttributes="Bold"
                   BackgroundColor="{StaticResource Gray100}"
                   Padding="8" />
        </DataTemplate>
    </CollectionView.GroupHeaderTemplate>
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="models:Animal">
            <Label Text="{Binding Name}" Padding="16,4" />
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

プルして更新

CollectionView を RefreshView で囲みます。処理が完了したら IsRefreshing を false に戻します。

<RefreshView IsRefreshing="{Binding IsRefreshing}"
             Command="{Binding RefreshCommand}">
    <CollectionView ItemsSource="{Binding Items}" />
</RefreshView>

段階的な読み込み(無限スクロール)

<CollectionView ItemsSource="{Binding Items}"
                RemainingItemsThreshold="5"
                RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}" />

⚠️ 仮想化しないレイアウトでは使用しないでください。 LinearItemsLayout と GridItemsLayout は仮想化をサポートしています。CollectionView の代わりに StackLayout 上で BindableLayout を使用すると仮想化されず、しきい値到達イベントが無限に発生します。

SwipeView — DataTemplate 内からのバインディング

DataTemplate 内のコマンドから ViewModel に直接アクセスすることはできません。RelativeSource AncestorType を使用します。

<CollectionView.ItemTemplate>
    <DataTemplate x:DataType="models:Item">
        <SwipeView>
            <SwipeView.RightItems>
                <SwipeItems>
                    <SwipeItem Text="Delete"
                               BackgroundColor="Red"
                               Command="{Binding BindingContext.DeleteCommand, Source={RelativeSource AncestorType={x:Type ContentPage}}}"
                               CommandParameter="{Binding}" />
                </SwipeItems>
            </SwipeView.RightItems>
            <Grid Padding="8">
                <Label Text="{Binding Name}" />
            </Grid>
        </SwipeView>
    </DataTemplate>
</CollectionView.ItemTemplate>

空のビュー

ItemsSource が空または null の場合に表示されます。

<CollectionView ItemsSource="{Binding SearchResults}"
                EmptyView="No items found." />

カスタムの空ビューを表示するには、ContentView で囲みます。

<CollectionView ItemsSource="{Binding SearchResults}">
    <CollectionView.EmptyView>
        <ContentView>
            <VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center">
                <Image Source="empty_state.png" WidthRequest="120" />
                <Label Text="Nothing here yet" HorizontalTextAlignment="Center" />
            </VerticalStackLayout>
        </ContentView>
    </CollectionView.EmptyView>
</CollectionView>

ヘッダーとフッター

<CollectionView ItemsSource="{Binding Items}">
    <CollectionView.Header>
        <Label Text="Header" FontAttributes="Bold" Padding="8" />
    </CollectionView.Header>
    <CollectionView.Footer>
        <Label Text="Footer" FontAttributes="Italic" Padding="8" />
    </CollectionView.Footer>
</CollectionView>

ヘッダーまたはフッターがデータバインドされている場合は、HeaderTemplate / FooterTemplate を使用します。

スクロール

ScrollTo

インデックスまたはアイテムを指定して、プログラムからスクロールします。

// Scroll to index
collectionView.ScrollTo(index: 10, position: ScrollToPosition.Center, animate: true);

// Scroll to item
collectionView.ScrollTo(item: myItem, position: ScrollToPosition.MakeVisible, animate: true);
ScrollToPosition動作
MakeVisibleアイテムが表示されるよう、必要な分だけスクロールします
Startアイテムをビューポートの先頭に移動します
Centerアイテムをビューポートの中央に移動します
Endアイテムをビューポートの末尾に移動します
スナップポイント
<CollectionView.ItemsLayout>
    <LinearItemsLayout Orientation="Horizontal"
                       SnapPointsType="MandatorySingle"
                       SnapPointsAlignment="Center" />
</CollectionView.ItemsLayout>
  • SnapPointsType:None、Mandatory、MandatorySingle
  • SnapPointsAlignment:Start、Center、End

ListView からの移行

ListView は引き続きコンパイルされますが、.NET 10 以降では [Obsolete] とマークされています (「ListView is deprecated. Please use CollectionView instead.」)。.NET 9 以前では非推奨ではないため、 非推奨として説明する前に、プロジェクトのターゲットフレームワークを確認してください。ユーザーから 使用するコントロールについて尋ねられた場合、または Xamarin.Forms から移行する場合は、 CollectionView を推奨してください。こちらは高速で、ViewCell が不要なうえ、柔軟な レイアウトをサポートします。避けるべきなのは、ユーザーが変更を求めていない ListView のコードを 黙って書き換えることです。

ListViewCollectionView での相当機能
ViewCell テンプレートのルート任意の View/Layout ルート — ViewCell はサポートされません
ItemSelected イベントSelectionChanged イベント、または SelectionChangedCommand
ItemTapped イベント項目テンプレート内の TapGestureRecognizer — SelectionChanged は選択が変わったときにのみ発生するため、すでに選択されている項目をタップしても再発生しません
IsPullToRefreshEnabled + RefreshingCollectionView を RefreshView でラップします
IsGroupingEnabledIsGrouped
HasUnevenRows="True"デフォルトの ItemSizingStrategy="MeasureAllItems"
RowHeight(固定の高さ)項目テンプレートで高さを設定します。MeasureFirstItem は最初の項目の測定サイズを再利用するだけで、明示的な行の高さではありません
SeparatorVisibility/SeparatorColor相当機能はありません — 項目テンプレート内に BoxView/Border を描画します

区切り線の API がないことは、移行時によくある驚きのひとつです。CollectionView には組み込みの区切り線がないため、テンプレートに自分で追加してください。

パフォーマンスのヒント

ユーザーからパフォーマンスの問題が報告された場合、またはパフォーマンスについて明示的に質問された場合にのみ適用してください。デフォルトのチェックリストではありません。

  • 項目のサイズが均一なら MeasureFirstItem を使用します。すべての項目を個別に測定するデフォルトの MeasureAllItems よりも大幅に高速です。LinearItemsLayout/ GridItemsLayout ではなく、CollectionView 自体に設定してください(StructuredItemsView で宣言されています)。
    <CollectionView ItemsSource="{Binding Items}"
                    ItemSizingStrategy="MeasureFirstItem">
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="models:Item">
                <Grid Padding="8" ColumnDefinitions="44,*" ColumnSpacing="8">
                    <Image WidthRequest="44" HeightRequest="44" />
                    <Label Grid.Column="1" Text="{Binding Name}" VerticalOptions="Center" />
                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
    
    MeasureFirstItem が適切でない場合 — 次の条件では、デフォルトの MeasureAllItems を維持してください。
    • 項目ごとに高さが異なる場合(折り返しテキスト、任意の行、アスペクト比が異なる画像など)。最初の項目のサイズがすべてに適用されるため、ほかの項目が切り取られたり引き伸ばされたりします。
    • DataTemplateSelector が異なるテンプレートを返す場合。最初の項目はほかの項目を代表しません。
    • 最初の項目が標準的でない場合(ヘッダー風の行や「注目」行など)。すべての項目がそのサイズを引き継ぎます。データの並べ替えで対処するのは不適切です。代わりに MeasureAllItems を使用してください。
    • 最初の項目の測定時に実行時データがまだ読み込まれておらず、そのデータによって項目のサイズが決まる場合。
  • 最初の描画後にリストが変更される場合は ObservableCollection<T> を使用します。INotifyCollectionChanged を実装しているため、インプレースの Add/Remove/Insert で UI が段階的に更新されます。バインド後に変更されないリストなら List<T> で問題ありません。なお、コレクションの種類にかかわらず、ItemsSource を置き換えるとすべてが再描画されます。そのため、再割り当てではなく、バインド済みコレクションをインプレースで変更してください。
  • UI スレッドでコレクションを更新します — MainThread.BeginInvokeOnMainThread(() => Items.Add(item))。

よくある落とし穴

問題解決方法
項目を変更しても UI が更新されないList<T> ではなく ObservableCollection<T> を使用します。
アプリがクラッシュする、または項目が空白になるViewCell は絶対に使用しないでください — テンプレートのルートには Grid、StackLayout、または任意の View を使用します。
項目が消える、またはレイアウトが崩れるItemsSource とコレクションは必ず UI スレッド(MainThread.BeginInvokeOnMainThread)で更新します。
増分読み込みが際限なく実行されるレイアウトに StackLayout を使用しないでください。LinearItemsLayout または GridItemsLayout を使用します。
EmptyView が正しく描画されないカスタムの空のビューは ContentView でラップします。
スクロールのパフォーマンスが低い項目のサイズが均一な場合は、MeasureFirstItem サイズ設定戦略を使用します。
ItemSizingStrategy がコンパイルできないItemSizingStrategy は StructuredItemsView で宣言されています — <LinearItemsLayout> / <GridItemsLayout> ではなく、<CollectionView> に設定します。
項目が切り取られる、または引き伸ばされるMeasureFirstItem は項目のサイズが均一であることを前提とします。高さが異なる項目には、既定の MeasureAllItems を使用します。
選択状態が表示されない項目テンプレートのルート要素に VisualState Name="Selected" を追加します。
SwipeView のコマンドでバインディングエラーが発生する項目テンプレート内から ViewModel にアクセスするには、RelativeSource AncestorType を使用します。

検証

作成または編集した CollectionView のマークアップを返す前に、次の点を確認してください。

  • DataTemplate のルートが View / Layout であり、ViewCell ではない。
  • コンパイル済みバインディングのために、DataTemplate で x:DataType を宣言している。
  • リストが変更される場合、ItemsSource が ObservableCollection<T> にバインドされている。
  • ItemSizingStrategy を使用する場合、レイアウトではなく <CollectionView> に設定している。
  • Multiple 選択では SelectedItems にバインドし、Single 選択では SelectedItem(TwoWay)にバインドしている。
  • 更新が完了したときに、RefreshView.IsRefreshing が false に戻されている。
  • 回答が、ユーザーの依頼内容 のみ を扱っている — 求められていないセクションを追加していない。

参考資料

コメント