49
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ListViewの選択項目を取得/設定する

Posted at

※前からWPFやってる人には常識なんだろうなあとは思うけどそういうことは気にしない

問題

ListViewの選択項目をプログラムから操作するには、VMにIsSelectedプロパティを用意して、ListViewItem.IsSelected にバインドすればよい

<ListView.ItemContainerStyle>
  <Style TargetType="ListViewItem">
    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
  </Style>
</ListView.ItemContainerStyle>

という記述がぐぐるとよく見つかるのだけど、これは正しくない。

ListViewはデフォルトで仮想化されるため、見えている範囲+αの分しかListViewItemのインスタンスは生成されない。ListViewItemが存在しなければバインディングも働かないので、範囲外の項目に対してはVとVMでの同期は行われない。

実際どういう動きになるのか、簡単なテストプログラムで確認してみた。

テストプログラム

説明

画面はこんな感じ。
SelectedItemsTest.png

item.cs
public class Item : INotifyPropertyChanged
{
    public string Name { get; }
    public bool IsSelected { get; set; }
}

こんな感じのViewModelがあって、そのコレクション(要素数50、Name=00~49)が左上のListViewと右のItemsControlのItemsSourceに指定されている。

ListViewの方では ListViewItem.IsSelectedItem.IsSelected が双方向でBindingされていて、ItemsControlの方では Item.IsSelected がtrueなら背景色が変わるようになっている。

ソースは こちら

動かしてみる

ListView上で適当なアイテムを選択してみると、ViewModel側のIsSelectedもtrueになる。
SelectedItemsTest-1.png

Ctrl+AでListView上のアイテムを全選択してみる。
ListView.SelectedItems には50項目すべてちゃんと追加されるけれど、ViewModel側のIsSelectedは、画面に見えているもの+αしか変わっていない。
SelectedItemsTest-2.png

そのままListViewをスクロールしていくと、それに合わせてViewModel側のIsSelectedも順番にtrueに変わっていく。
SelectedItemsTest-3.png

すべてのViewModelのIsSelectedがtrueになったところで、またListView上の適当な項目をクリックしてみる。
全選択が解除されてその項目だけが選択された状態になるので SelectedItems.Count は1になるけれど、画面に見えていない部分のViewModelのIsSelectedはtrueのまま。
SelectedItemsTest-4.png


ViewModel側で IsSelected を操作した時の挙動も確認する。
ListView上に見えているItem-01のIsSelectedをtrueに変更すると、SelectedItems にも反映されるが、
SelectedItemsTest-5.png

見えていないItem-49のIsSelectedをtrueにしても SelectedItems には反映されない。
SelectedItemsTest-6.png

解決策

  • ListViewItem.IsSelected に対するバインディングは OneWay (VM -> Vのみ)にする
  • ListView.SelectedItems の内容は一切信用しない
  • VからVMへの反映は、SelectionChanged イベントで自力でやる
void lvw_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    foreach (var item in e.RemovedItems.Cast<Item>())
        item.IsSelected = false;
    foreach (var item in e.AddedItems.Cast<Item>())
        item.IsSelected = true;
}

とのこと。
IsSelectedに対するバインディングをしないならSelectedItemsを見ても大丈夫っぽいけど。

(参考) VirtualizingStackPanel + MVVM + multiple selection

49
39
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?