読者です 読者をやめる 読者になる 読者になる

Code for final

ふぁいなる向けのコード置き場です。すでにコードじゃないこともいっぱい。

WPFでDataGridのソート後にSelectedIndexでデータを取得すると違うものがとれるとき。

WPFのDataGridはヘッダクリックのソートが実装されており、非常に便利なのですが、
一つ気をつけないとならないことがあります。

DataGridには通常、リストとかのItemsSourceをバインドすると思います。 そしてその選択行に対して何かしたいときに選択行を取得するため、「SelectedIndex」を使用してリストの値を取得していると思わぬ罠にはまります。
わたしもはまりました。

ヘッダクリックでソートしていないときは問題ないのですが、ソート後に「SelectedIndex」を使用してリストの値を取得すると期待しているもの(画面で選択しているもの)と違うものがとれるのです。
ヘッダにNoがあって、デフォルトで昇順で表示しているものがあります。
これをヘッダクリックで降順でソートし、一番上の行を「SelectedIndex」で取得するとNoの一番小さいデータがとれるのです。
そうです、ヘッダクリックでソートしてもバインドしているデータまではソートされないのです。

では、どうするかというと一番簡単なのは、「SelectedIndex」でなく、「SelectedItem」を使用するのです。
そうするとIndexではないので、画面で選択したとおりのデータが取得できます。

ただ、「SelectedItem」も万能ではないです。
単一選択のみのリストであれば問題ありませんが、複数行選択だと一番上のものしか取れないのです。

そこでなんかいいのないかとMSDNを見るわけですよ。
で、「SelectedItems」というドンピシャなものを見つけたわけです。それでBindingしようとしたら怒られるわけですよ。
セッターはありませんって。
そうです、「SelectedItems」はゲッターしかないのです。

じゃあどうするかというとMVVMだと、Modelに選択状態を持たせてDataGridRowのIsSelectedをバインディングするのです。
そしてVMでIsSelectedがtrueのものを取得すれば複数選択は取得できます。
が、先ほども書きましたが、バインドしているデータはソートに連動しないので順番は期待通りにならないのです。
順番を意識しないのであれば以下の処理は不要ですが。

順番を意識したい場合はCollectionViewSource.GetDefaultView()を使用してビュー(画面で表示している順)のリストを取得できます。
サンプルです。

/// <summary>
/// 選択行を取得して処理を行うなにか。
/// </summary>
private void Button_Click(object sender, RoutedEventArgs e)
{
    var views = CollectionViewSource.GetDefaultView(Libraries).OfType<LibraryViewModel>(); // LibrariesはItemsSourceにバインドしているデータ。
    
    // 以降はviewsに画面の表示順でデータが入っているのであとは好きにできる。
    // 選択行のみ取得する場合は以下のような感じで。(IsSelectedはDataGridRowとバインディングしている場合)
    var selectedViews = views.Where(x => x.IsSelected).Select();
}

ちなみに「SelectedIndex」は使用する機会がないかというと、そうでもありません。
選択行の位置から10行取得とかそういった場合に使用します。Movselexでもそういうケースで使用しています。
もちろん、GetDefaultViewで取得したコレクションに対して行ってくださいねー。