.NET MAUIでGridにContextActionsは付かない?XLS0415エラーの原因とListView/SwipeView/CollectionView対処法

.NET MAUIのXAMLでGridにContextActionsを付けようとしてXLS0415が出る、ListViewにSwipeViewを入れたらスクロールで落ちる――そんなつまずきポイントを、原因→正しい書き方→代替UI→切り分け手順の順にまとめます。

目次

.NET MAUIでGridにContextActionsを付けるとエラーになる理由

まず結論から言うと、Grid(やStackLayoutなどの一般的なレイアウト要素)にはContextActionsプロパティがありません。そのため、XAMLで次のように「Gridにコンテキストメニューを生やす」書き方をすると、ビルド時(XAMLコンパイル時)にエラーになります。

発生する代表的なエラー

XLS0415 The attachable property ‘ContextActions’ was not found in type ‘Grid’

このメッセージが示しているのは、「Gridに対して添付プロパティ(Grid.Rowのように “外から付けられるプロパティ”)としてContextActionsを探したけれど見つからない」ということです。つまり、書き方のミスというより、設計としてその機能がGridには用意されていないのが根本原因です。

やりたいことつい書きたくなるXAML実際の状況
Gridを右クリック/長押ししたらメニューを出したいGrid.ContextActions に MenuItem を定義GridにContextActionsが無いのでXLS0415で失敗
ListViewの行に「削除」「編集」を付けたい行の中のGridにContextActionsを付ける正攻法はViewCell.ContextActions(GridではなくViewCell側)

ここで重要なのは、ContextActionsは「どのViewにも付けられる万能コンテキストメニュー」ではないという点です。MAUI(そして前身のXamarin.Forms)では、ContextActionsは主にリストの1行(Cell)に紐づく操作として用意されています。

ContextActionsが使えるのは「Cell」系

ListViewの中身は「Cell」で構成されます。カスタム表示に使うのがViewCellで、他にもTextCellやImageCellなどがあり、ContextActionsはこのCellに対して付与する機能です。つまり、GridのようなView要素に直接付けられないのは自然な設計です。

カテゴリ代表的な型ContextActions向いている用途
CellViewCell / TextCell / ImageCell など使えるListViewの「行」に対する操作(削除/編集など)
View(表示要素)Grid / StackLayout / Label / Image など使えない画面部品としての表示・入力

「右クリック/長押しで出る“純粋なコンテキストメニュー”」をGrid等に付けたい場合は、MAUI標準だけでは完結しにくく、SwipeViewやActionSheet、Popup、プラットフォーム別実装で代替する設計が現実的になります。

ListViewでContextActionsをやりたい場合の正しい書き方

ListViewの各行にメニュー(削除・編集など)を付けたい場合は、ItemTemplate内のViewCellに対してViewCell.ContextActionsを定義します。要点は「GridではなくViewCell側」です。

NG例(GridにContextActionsを付けようとして失敗する)

<Grid>
  <Grid.ContextActions>
    <MenuItem Text="Delete" />
  </Grid.ContextActions>
</Grid>

OK例(ViewCell.ContextActionsを使う)

<ListView ItemsSource="{Binding Items}">
  <ListView.ItemTemplate>
    <DataTemplate>
      <ViewCell>


    <ViewCell.ContextActions>
      <MenuItem Text="削除"
                IsDestructive="True"
                Clicked="OnDeleteClicked"
                CommandParameter="{Binding .}" />
      <MenuItem Text="編集"
                Clicked="OnEditClicked"
                CommandParameter="{Binding .}" />
    </ViewCell.ContextActions>

    <Grid Padding="12">
      <Label Text="{Binding Title}" FontSize="16" />
    </Grid>

  </ViewCell>
</DataTemplate>



イベントハンドラ側では、CommandParameterで渡した行データを取り出して処理します。

private async void OnDeleteClicked(object sender, EventArgs e)
{
    if (sender is MenuItem mi && mi.CommandParameter is MyItem item)
    {
        var ok = await DisplayAlert("確認", $"{item.Title} を削除しますか?", "削除", "キャンセル");
        if (ok)
        {
            await _viewModel.DeleteAsync(item);
        }
    }
}

MVVMでCommandに寄せたい場合は、MenuItemのCommandを親のBindingContext(ViewModel)へバインドし、CommandParameterに行のモデルを渡すのが定石です。DataTemplate内部は「行のモデル」がBindingContextになるため、参照元を明示します(x:ReferenceやRelativeSourceなど)。

観点イベント(Clicked)MVVM(Command)
実装の簡単さ簡単(コードビハインドで完結)少し手間(参照元の指定が必要)
テストしやすさUIに寄りがちViewModel中心にテストしやすい
規模が大きい場合肥大化しやすい整理しやすい

表示方法はプラットフォームで変わる

ContextActionsは「行に対する操作」を提供しますが、操作の出し方(スワイプ/長押し/メニュー)はプラットフォームやテーマで違います。仕様を固定的に決め打ちせず、「操作が実行できること」を優先し、必要ならUIで補助します。

プラットフォーム例ありがちな見え方設計上の注意
モバイル(iOS/Android)スワイプで露出、または長押しメニューチュートリアルやヒント表示で発見性を補う
デスクトップ(Windows/Mac)右クリックを期待されるが、必ずしも標準提供されない「…ボタン」など明示的な入口を用意すると迷われにくい

Gridなど「ListView以外」に操作メニューを付けたい場合の現実的な選択肢

GridやStackLayoutなど、ListViewのCellではない一般のViewに対して「右クリック/長押しでコンテキストメニュー」を付けたいケースは多いです。しかし、MAUI標準の範囲だけでデスクトップの右クリックメニューのような体験を完全に再現するのは簡単ではありません。

そこで、実装と運用のしやすさを優先して、次のいずれかに寄せるのが現場では現実的です。

目的おすすめ理由
「行スワイプで削除・編集」などモバイルらしい操作を入れたいSwipeViewMAUI標準で使え、視覚的に分かりやすい
どのViewでも共通に「選択肢を出して実行」したいDisplayActionSheet(またはPopup)UIの自由度より安定性を優先できる
Windowsの右クリックに寄せたいプラットフォーム別実装OSのUI規約に合わせやすいが、保守は増える

選択肢1:SwipeViewで「操作ボタンを出す」

スレッドの案内でもよく推奨されるのがSwipeViewです。左右スワイプでボタンを出すUIは、モバイルでは「削除」「アーカイブ」などの定番で、ListView以外の要素でもラップして使えます。

<SwipeView>

  <SwipeView.RightItems>
    <SwipeItems Mode="Reveal">
      <SwipeItem Text="削除"
                 Command="{Binding DeleteCommand}"
                 CommandParameter="{Binding .}" />
      <SwipeItem Text="編集"
                 Command="{Binding EditCommand}"
                 CommandParameter="{Binding .}" />
    </SwipeItems>
  </SwipeView.RightItems>

  <Grid Padding="12">
    <Label Text="{Binding Title}" />
  </Grid>

</SwipeView>

SwipeViewの使い分けポイントとして、SwipeItemsのModeがあります。挙動が変わるため、意図に合わせて選びます。

Mode挙動向いているケース
Revealスワイプでボタンが露出し、タップで実行複数操作(編集/削除など)を安全に提示したい
Execute一定量スワイプすると即実行されるアーカイブなど誤操作が起きにくい単一操作

ただしSwipeViewは、デスクトップ(Windows/Mac)では「スワイプ」という概念自体が直感的でない場合があります。その場合は、行の右端に「…(その他)」ボタンを置いてActionSheetやPopupを開くなど、操作の入口を明示する設計にすると迷われにくくなります。

選択肢2:ActionSheet/Popupで「疑似コンテキストメニュー」に寄せる

「右クリック/長押しでメニューを出したい」の本質が「複数の操作候補を出して選ばせたい」であれば、最も安定しやすいのはActionSheetです。見た目はOSごとに変わりますが、動作は揃えやすいです。

例えば、Gridをタップ(またはメニューボタン)したタイミングで、次のように選択肢を出せます。

private async Task ShowRowMenuAsync(MyItem item)
{
    var action = await DisplayActionSheet(
        "操作を選択",
        "キャンセル",
        null,
        "編集",
        "削除"
    );

    switch (action)
    {
        case "編集":
            await _viewModel.EditAsync(item);
            break;
        case "削除":
            await _viewModel.DeleteAsync(item);
            break;
    }
}

長押しを厳密に実装したい場合は、MAUI標準だけで頑張るよりも、Community ToolkitのPopupやタッチ拡張、またはプラットフォーム別のジェスチャ検知で補完する方が、結果的に安定します。特に「Windowsでは右クリック、スマホでは長押し」といった要件は、単一のXAML機能で揃えきれないことが多いです。

追加相談:ListViewにSwipeViewを入れるとスクロール時にクラッシュする場合

ここからは、質問で追加相談として出ていた「ListView.ItemTemplateの中にSwipeViewを入れると、スクロール時にクラッシュする」問題についてです。やり取りの中では追加コメント段階で止まっており、確定原因や決定打の解決回答が提示されていない状況でした。

そのため、ここでは「現場で効くことが多い切り分け」と「事故りにくい回避策」を、優先度が高い順に整理します。

まず押さえたい前提:ListViewはレガシー寄り、複雑なUIと相性問題が出やすい

.NET MAUIでは、コレクション表示の推奨コントロールとしてCollectionViewが位置づけられており、ListViewは互換性のために残っている側面があります。ListViewはセルの再利用(仮想化)やプラットフォーム差の影響を強く受けるため、SwipeViewのようなジェスチャ+状態管理を持つコントロールを入れると、特定条件で不安定になるケースがあります。

項目ListViewCollectionView
位置づけ互換性・既存資産向け新しい推奨コントロール
カスタマイズ性できるが制約も多い柔軟で拡張しやすい
スワイプ系との相性環境によって不安定になる報告が出やすい比較的相性が良いケースが多い

切り分けチェックリスト(クラッシュを最短で絞る)

チェック項目狙い具体例
どのOSで落ちるかプラットフォーム固有不具合を疑うAndroidだけ / iOSだけ / Windowsだけ、など
スクロール量と発生条件セル再利用のタイミングを疑う高速スクロール、指を離した慣性スクロールで落ちる
ItemTemplateが複雑すぎないか描画負荷・状態保持の問題を減らすネストが深い、画像が多い、Bindingが重い
SwipeViewをどこに置いているか再利用時の状態破綻を避けるテンプレートの最上位をSwipeViewにする
例外ログ(スタックトレース)NullReference/Disposeなどの傾向を見るDebug出力、クラッシュレポート、ADBログ

実務的な回避策

MAUI/SDKを最新の修正版へ更新する

UI系のクラッシュは、フレームワーク側の修正で直っていることが珍しくありません。特にListView周りは古い資産との互換性もあり、バージョン差の影響を受けやすい領域です。プロジェクトの制約が許すなら、まずは.NET(SDK)とMAUI関連パッケージを最新の安定版へ寄せることをおすすめします。

可能ならListViewからCollectionViewへ移行する

「行スワイプで削除・編集」をやりたいだけなら、CollectionView+SwipeViewの組み合わせに寄せるのが堅実です。移行のコストはありますが、将来の保守性も上がります。

次はイメージしやすい最小例です。CommandをViewModel側で受ける場合は、DataTemplate内部から参照できるように、ページやルート要素にx:Nameを付けて参照元を固定します。

<ContentPage x:Name="PageRoot" ...>

  <CollectionView ItemsSource="{Binding Items}">
    <CollectionView.ItemTemplate>
      <DataTemplate>

        <SwipeView>
          <SwipeView.RightItems>
            <SwipeItems Mode="Reveal">
              <SwipeItem Text="削除"
                         Command="{Binding BindingContext.DeleteCommand, Source={x:Reference Name=PageRoot}}"
                         CommandParameter="{Binding .}" />
            </SwipeItems>
          </SwipeView.RightItems>

          <Grid Padding="12">
            <Label Text="{Binding Title}" />
          </Grid>

        </SwipeView>

      </DataTemplate>
    </CollectionView.ItemTemplate>
  </CollectionView>

</ContentPage>

CollectionView移行時は、次の差分を押さえるとスムーズです。

よく使う機能ListViewCollectionView
行クリックItemSelected / ItemTappedSelectionChanged またはGestureRecognizer
引っ張って更新IsPullToRefresh / RefreshCommandRefreshViewと組み合わせる
ヘッダー/フッターHeader / FooterHeader / Footer(または周辺レイアウト)

「スワイプしたいだけ」ならContextActionsに戻す

もし、やりたいことが「ListViewの行に削除などの操作を付けたい」だけで、SwipeViewの見た目に強いこだわりがない場合は、ViewCell.ContextActionsに戻すのも有効です。SwipeViewを外すだけでクラッシュが止まるなら、まずは安定性を優先してContextActionsに寄せ、後からUIを再検討する方がトータルでは早く解決することもあります。

セル再利用が怪しい場合は「再利用を弱める」暫定策もある

クラッシュが「セル再利用のタイミングで起きている」疑いが強い場合、ListViewのキャッシュ戦略を変えると症状が改善することがあります。例えば、要素再利用を行わない(または弱める)戦略にすると、状態破綻が起きにくくなります。

ただしこの手は、メモリ消費やスクロール性能に影響しやすいので、あくまで暫定回避として扱い、可能ならCollectionView移行へ繋げるのがおすすめです。

// 例:コード側でListViewのキャッシュ戦略を指定する(暫定回避)
var listView = new ListView(ListViewCachingStrategy.RetainElement)
{
    ItemsSource = viewModel.Items,
    ItemTemplate = new DataTemplate(typeof(MyRowViewCell))
};

最小再現を作って原因を切り分ける

「特定の画面だけ落ちる」「特定の行データで落ちる」など再現条件が絞れそうなら、最小構成(ListView+SwipeView+簡単な表示)を作り、段階的に要素を足していくのが近道です。最小再現が作れれば、フレームワーク側の不具合としてIssue化しやすく、修正や回避策に辿り着く確率が上がります。

設計の考え方:コンテキストメニューを「UI要件」に合わせて選ぶ

最後に、混乱しやすいポイントを整理します。「コンテキストメニュー」と言ったとき、開発者が思い浮かべるものが複数あります。

呼び方ユーザーが期待する操作MAUIでの現実解
デスクトップ的な右クリックメニュー右クリックでメニューが出るプラットフォーム別(Windows)+共通はPopup/ActionSheetで代替
リスト行の操作(削除/編集)行スワイプや長押しで操作が出るListViewならViewCell.ContextActions、推奨はCollectionView+SwipeView
どこでも使える操作メニュータップで選択肢が出るActionSheetやPopupで共通化

「GridにContextActionsが付かない」という問題は、裏を返すと“どんな体験を提供したいか”を先に決める必要があるというサインでもあります。モバイル中心ならSwipeView、リスト行の操作ならViewCell.ContextActions、デスクトップの右クリックに寄せたいならプラットフォーム別――と、要件に合わせて割り切ることで、実装も保守も安定します。

まとめ:今回のポイント

  • Grid(一般のView)にはContextActionsが無いので、XLS0415は「仕様どおり」のエラー
  • ListViewの行に付けるなら、ViewCell.ContextActionsにMenuItemを定義するのが正攻法
  • Gridなどに操作を付けたいなら、SwipeViewやActionSheet/Popupで目的に合うUIへ寄せる
  • ListView+SwipeViewでスクロールクラッシュするなら、更新・CollectionView移行・ContextActions回帰・最小再現で切り分けが現実的

この記事を書いた人

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

コメント

コメントする

目次