【WPF】SelectedItemsとSelectedCellsの違い。使い分けが重要

C#

DataGridには選択している要素にアクセスできるプロパティが沢山あります。
その中でも以下の3つは違いが分かりにくいです。

  • SelectedItem
  • SelectedItems
  • SelectedCells

また、DataGridの設定によっては迂闊にアクセスすると、
例外になることもあるため、気を付けて使う必要があります。

本記事では、どんな時に何を使うべきなのか解説します。

スポンサーリンク

早見表

いきなり結論ですが、3つの観点があります。

  • 複数選択時に取得できる要素、順番が異なる
  • SelectionUnitプロパティによる機能差
  • SelectedCellsはBindingできない

複数選択時に取得できる要素、順番が異なる

SelectedItem(およびSelectedItems)と、SelectedCellsとでは、
要素の取得できる順番が違います。

SelectionUnitプロパティによる機能差

DataGridにはSelectinUnitというプロパティがあり、
このプロパティに設定した値によって、
何を使うかを変えるべきです。

SelectedCellsはBindingできない

SelectedCellsに対応するDependencyPropertyは存在しないため、
Bindingすることができません。
MVVMでDataGridを使いたい人には致命的です。

忙しい人のために、
それぞれの早見表を用意しましたのでご覧ください。

選択肢取得プロパティ 複数選択時の取得の挙動使用可能なSelectionUnitBinding
SelectedItemユーザーが最初に選んだ行FullRow or CellOrRowHeader可能
SelectedItemsユーザーが選んだ順序FullRow or CellOrRowHeader可能
SelectedCells左上から行のセル優先で並ぶCell or CellOrRowHeader不可能

さすがになんのこっちゃ、って感じですね。
以下詳しい解説をしていくのでご安心ください。

SelectedItemの特徴・使い方

SelectedItemは、
「現在選んでいる行のDataContext」にアクセスできるプロパティです。
セッターも公開されており、
セットした場合は選択している行が切り替わります。

Bindingを用いることでViewModelに選択中の要素を流すことが可能です。

…こう書くと特にそれ以上気にすることは無いように思えますね。
ところが、深く掘ると
以下のような特徴があります。

  • 複数セル選択している場合は最初に選んだセルのDataContextが取得される
  • DataGrid.SelectionUnitがFullRow以外の時は何も取得できないことがある
  • DataGrid.SelectionUnitがCellの時にセットしようとすると例外

後ろのやつほどなんだかまずそうですね。
さらに詳しく見ていきましょう。

複数セル選択している場合は最初に選んだセルのDataContextが取得される

これはつまり、上の要素からドラッグして範囲選択したら、
最初にクリックした上の要素が取得されるし、
逆に下の要素から上に向かって範囲選択されたら、
最初にクリックした、下の要素が取得される。
ということです。

「必ず上側の要素が取れるんでしょ?」
と思っていたら失敗しましたそれは勘違いなので注意しましょう。

また、複数のDataContextを取得したい場合は、
SelectedItemsを使いましょう。
SelectedItemでは先頭要素のみしか取得できません。

とはいえ、SelectedItemプロパティは
Selectorクラスから継承されたプロパティであり、
Selector継承クラスならほぼほぼこの挙動なので、
迷うケースは少ないと思います。

DataGrid.SelectionUnitがFullRow以外の時は何も取得できないことがある

DataGrid.SelectionUnitは、
そのDataGridがどのように選択状態になるのかを
設定するプロパティです。

たとえば、既定値であるFullRowの場合は、
どのセルをクリックしても行全体が選択状態になります。

一方で、CellやCellOrRowHeaderの場合は、
セルをクリックすると、そのセルのみが選択状態になります。

セルのみが選択されている状態では、
SelectedItemプロパティは常にnullを返します。

つまり、SelectionUnitをFullRow以外にした時は、
SelectedItemは何も取得できない場合がある
ということになります。

取得できない場合があるだけならまだ良いのですが、
値をセットしようとするとさらにまずいことになります。

DataGrid.SelectionUnitがCellの時にセットしようとすると例外

SelectionUnitがCellの時に
セッターを呼び出してしまうと例外になります。
(CellOrRowHeaderの時は大丈夫です)

幸い、例外メッセージが分かりやすいので、
対処は簡単ですが、
思わぬ落とし穴なので注意しましょう。

SelectedItemを使うなら覚えておきたいこと

というわけで、SelectedItemを使うなら
以下の項目を押さえておきましょう。

  • 複数行選択されていた場合は、ユーザーが最初に選んだ行のDataContextが対象
  • 単純に複数要素ほしいならSelectedItemsの方を使いましょう
  • SelectionUnitをFullRow以外に変えてはいけない

SelectedItemsの特徴・使い方

SelectedItemsは、
「 現在選んでいる行のDataContext のリスト」
にアクセスできるプロパティです。

SelectedItemの複数版と捉えてもらえればほぼ問題ありません。

Bindingも可能で、ViewModel側では、
IEnumerable<T>などで受ければ取得できます。

SelectedItemの複数版ということで、
似た点に気を付ける必要があります。

  • 複数行選択している場合は、選んだ順番にDataContextが格納される
  • DataGrid.SelectionUnitがFullRow以外の時は要素が0のことがある
  • DataGrid.SelectionUnitがCellの時に追加をしようとすると例外

ほとんど一緒ですね。
SelectedItemと気を付けることもやはり一緒なので詳細は割愛します。

SelectedCellsの特徴・使い方

SelectedCellsは
「現在選んでいるセル(DataGridCellInfo構造体)のリスト」
にアクセスできるプロパティです。

以下の特徴があります。

  • SelectionUnitの設定に関わらず値が取得可能
  • 複数セル選択時の順序は選択順とは限らない
  • Bindingできない

最後のせいですべて台無しな気がしますが、
詳しく見ていきます。

SelectionUnitの設定に関わらず値が取得可能

SelectionUnitが何に設定されていても、
同一のものが取得できます。

SelectionUnitがFullRowだと、
ユーザーからすると不便な点があります。
たとえば、以下のような点です。

  • フォーカスしているセルが分かりにくい
    • 行全体にハイライトされてしまうため
  • 狙ったセルの値だけコピーができない
    • 行全体が選択されているため、行全体の値が文字コピーされてしまう

この辺りをケアするなら、
SelectionUnitはCellやCellOrRowHeaderにすべきです。
SelectedCellsはこれらの時でも常に値が取得できます。

(DataGridCellInfo構造体という形で取得するので、
DataContextをとりたい場合は重複解消などがいりますが)

ちなみに、取得に関しては同一のものが取得できますが、
SelectionUnitがFullRowの時に追加や削除をしようとすると、
それは例外になるので気を付けましょう。

複数セル選択時の順序は必ず左上からで固定

SelectedCellsではリストの格納順が、
選択順とは異なることがあります。

ここはSelectedItem(s)とは明確に異なります。

ドラッグにより複数セルを範囲選択した場合は、
必ず左上から行優先で格納されます。

下から上へドラッグしようが、右から左にドラッグしようが、
結果は必ず左上から行優先です。

ただし、Ctrlを押しながらクリックして複数セル選択した場合は、
選んだ順序が維持されたりされなかったりします。

さんさめ
さんさめ

なんか内部で色々最適化されてるのかなぁ…

そのため、順序に意味を持たせるような
取得の仕方をしてはいけません。
何も考えずにFirstOrDefaultとか使っちゃダメですね。

Bindingできない

一方、SelectedCells最大の問題点は、
DependencyPropertyではないので、
Bindingができないことです。

セッターすらないのでそもそもxaml上では記述もできません。

ユーザー体験としてはSelectionUnitはセル単位の方が圧倒的に便利なのに、
SelectedCellsの仕様が壁となってMVVMなどでは扱いづらいものとなってます。

さんさめ
さんさめ

なんとかSelectionUnitをCellにしつつ、
確実にDataContextを取れないものか…

SelectedCellsの情報をもとに、
BindingでDataContextにアクセスできれば、
SelectionUnitの設定に依らずにMVVMを実現できそうです。

まとめ

本記事では、
SelectedItem(s)とSelectedCellsの違いについて
解説しました。

次回DataGridの話題を出すときには、
SelectionUnitをCell(やCellOrRowHeader)にしつつ、
DataContextをいい感じに取得する方法を
紹介したいと思います。

追記:書きました。以下の記事をご覧ください。
DataGridのSelectedItemsをどんな時でも取得する方法

最後までお読みいただき、ありがとうございました。

コメント

タイトルとURLをコピーしました