.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 | 向いている用途 |
|---|---|---|---|
| Cell | ViewCell / 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標準の範囲だけでデスクトップの右クリックメニューのような体験を完全に再現するのは簡単ではありません。
そこで、実装と運用のしやすさを優先して、次のいずれかに寄せるのが現場では現実的です。
| 目的 | おすすめ | 理由 |
|---|---|---|
| 「行スワイプで削除・編集」などモバイルらしい操作を入れたい | SwipeView | MAUI標準で使え、視覚的に分かりやすい |
| どの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のようなジェスチャ+状態管理を持つコントロールを入れると、特定条件で不安定になるケースがあります。
| 項目 | ListView | CollectionView |
|---|---|---|
| 位置づけ | 互換性・既存資産向け | 新しい推奨コントロール |
| カスタマイズ性 | できるが制約も多い | 柔軟で拡張しやすい |
| スワイプ系との相性 | 環境によって不安定になる報告が出やすい | 比較的相性が良いケースが多い |
切り分けチェックリスト(クラッシュを最短で絞る)
| チェック項目 | 狙い | 具体例 |
|---|---|---|
| どの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移行時は、次の差分を押さえるとスムーズです。
| よく使う機能 | ListView | CollectionView |
|---|---|---|
| 行クリック | ItemSelected / ItemTapped | SelectionChanged またはGestureRecognizer |
| 引っ張って更新 | IsPullToRefresh / RefreshCommand | RefreshViewと組み合わせる |
| ヘッダー/フッター | Header / Footer | Header / 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回帰・最小再現で切り分けが現実的

コメント