トップ回答者
ListViewの画面に表示されていない領域の選択が解除されない

質問
-
お世話になっております。
ListView の SelectionMode を Multiple にして、ボタンで全ての列を選択、又は選択解除をしたいのですが、
ListView の ScrollViewer.CanContentScroll を true にすると、全選択解除をした時に、画面に表示されていない領域の列の選択が解除されない時があります。
ScrollViewer.CanContentScroll を false に設定するとこの現象は起こらないのですが、
実際のソフトだとパフォーマンスがかなり悪化します。
この現象を解決する方法はありませんでしょうか?
開発環境は
Visual studio professional 2015
.Net Framework Version 4.6
以下のサンプルプログラムで、次の手順を行うと現象が発生します。
①上から0番目、1番目、2番目の列を選択する。
②0~2番目までの列が表示されない所まで、リストを下にスクロールする。
③全選択解除ボタンを押し、Is Selected にバインドしているデータを全てfalseにする。
④画面を上にスクロールすると3番目の列のみ選択が解除され、1、2番目は選択が残っている。
①で選択する列を増やした場合でも、最後に選択した列のみが、選択解除されるようです。
また、選択状態の列を画面に表示させている状態で選択解除を行うと、問題なく選択は解除されます。
③で全選択解除後に、ModelのIs_Selected プロパティのSetにブレークを掛けると、④の画面スクロール時に、Value = True で値が変更される所までは確認できたのですが、なぜTrueが飛んでくるのかが分かりません。
以下、サンプルコードです。
MainWindow.xaml
<Window x:Class="LISTVIEW_TEST_SOFT.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:LISTVIEW_TEST_SOFT" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:ViewModel/> </Window.DataContext> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="10*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <ListView ItemsSource="{Binding Bind_List}" Foreground="Green" SelectionMode="Multiple" ScrollViewer.CanContentScroll="True"> <ListView.ItemContainerStyle> <Style TargetType="{x:Type ListViewItem}"> <Setter Property="IsSelected" Value="{Binding Is_Selected}" /> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Foreground" Value="Black"/> <Setter Property="Background" Value="Orange"/> </Trigger> </Style.Triggers> </Style> </ListView.ItemContainerStyle> <ListView.View> <GridView> <GridViewColumn Width="100" > <GridViewColumnHeader Content="No" /> <GridViewColumn.CellTemplate> <DataTemplate> <Viewbox Height="50"> <TextBlock Text="{Binding Path=No}" /> </Viewbox> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView> <Button Grid.Column="1" Content="全解除" Command="{Binding Rerease_Select_Command}"/> </Grid> </Window>
MainWindow.xaml.cs
using System.Windows; namespace LISTVIEW_TEST_SOFT { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } }
ViewModel.cs
using System.Collections.ObjectModel; namespace LISTVIEW_TEST_SOFT { class ViewModel : ViewModelBase { public ViewModel() { Bind_List = new ObservableCollection<Model>(); for(int i = 0;i < 200; i++) { Model add_model = new Model(); add_model.No = i; Bind_List.Add(add_model); } Rerease_Select_Command = new RelayCommand(Rerease_Select); Rerease_Select_Command.IsEnabled = true; } private ObservableCollection<Model> _bind_list; public ObservableCollection<Model> Bind_List { get { return _bind_list; } set { _bind_list = value; NotifyPropertyChange("Bind_List"); } } public RelayCommand Rerease_Select_Command { get; set; } private void Rerease_Select() { foreach(Model model in Bind_List) { model.Is_Selected = false; } } } class Model : ViewModelBase { public Model() { } private bool _is_selested; public bool Is_Selected { get { return _is_selested; } set { _is_selested = value; NotifyPropertyChange("Is_Selected"); } } private int _no; public int No { get { return _no; } set { _no = value; NotifyPropertyChange("No"); } } } }
ViewModelBase.cs
using System; using System.ComponentModel; namespace LISTVIEW_TEST_SOFT { public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void NotifyPropertyChange(string propName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } } }
RelayCommand.cs
using System; using System.Windows.Input; namespace LISTVIEW_TEST_SOFT { public class RelayCommand : ICommand { private readonly Action _handler; private bool _isEnabled; public RelayCommand(Action handler) { _handler = handler; } public bool IsEnabled { get { return _isEnabled; } set { try { if (value != _isEnabled) { _isEnabled = value; if (CanExecuteChanged != null) { CanExecuteChanged(this, EventArgs.Empty); } } } catch (Exception e) { Console.WriteLine(e); } } } public bool CanExecute(object parameter) { return IsEnabled; } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { try { _handler(); } catch (Exception e) { Console.WriteLine(e); } } } }
以上、宜しくお願い致します。
- 編集済み y_nakata 2016年11月1日 11:13
回答
-
http://qiita.com/wonderful_panda/items/36bf500094cc42f7ea97
ここに図入りで解説があります。
IsSelectedにBindingしなければ、SelectedItemsは信用できるとあるので、全解除時にVMの選択状態のリセットとSelectedItems.Clear()で解除してSelectionChangedでV->VMに反映するのも手ですね。
Bindingしないので、もちろん選択リストのVM->Vの反映は無しです。
- 回答としてマーク y_nakata 2016年11月1日 12:04
すべての返信
-
UI仮想化が有効になっているので、使用されなくなったコントロールが選択状態のまま放置されて、再割り当てされたときにコントロールの選択状態がBindingされているプロパティに反映されているのではないでしょうか?
https://msdn.microsoft.com/ja-jp/library/cc716879.aspx
* 試しに、IsVirtualizingをfalseにする
* 試しに、IsSelectedのBinding.ModeをOnewayにしてSelectionChangedイベントでVMに反映する (DataGridと同じですね)
でどうなるでしょうか?
-
http://qiita.com/wonderful_panda/items/36bf500094cc42f7ea97
ここに図入りで解説があります。
IsSelectedにBindingしなければ、SelectedItemsは信用できるとあるので、全解除時にVMの選択状態のリセットとSelectedItems.Clear()で解除してSelectionChangedでV->VMに反映するのも手ですね。
Bindingしないので、もちろん選択リストのVM->Vの反映は無しです。
- 回答としてマーク y_nakata 2016年11月1日 12:04