Счетчик SelectedItems в WPF ListView изменяется при изменении ItemsPanel

Кажется, есть проблема с SelectedItems ListView при динамическом изменении ItemsPanel. Я реализовал MVVM в ListView, ItemsSource которого привязан к коллекции моделей. Модель имеет 2 свойства: DisplayName(string) и Selected(bool). И DataContext для списка содержит свойство ViewMode (bool).

Настройка заключается в том, что свойство IsSelected ListViewItem привязано к свойству Selected модели, а ItemsPanel ListView изменяется, когда я изменил ViewMode, нажав кнопку.

Проблема заключается в том, что когда в ListView есть выбранный элемент и изменяется ViewMode, счетчик SelectedItems в ListView увеличивается, даже если выбранные элементы не меняются.

Примечание. В моей настройке в ListView есть только 1 элемент, но количество SelectedItems увеличивается каждый раз, когда я меняю ViewMode.

Вот часть xaml приложения для проверки проблемы. Я думаю, что вы, эксперты, можете сделать часть ViewModel/Model.

<Window x:Class="WpfApplication5.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Width="525"
        Height="350">
    <StackPanel>
        <Button Command="{Binding ChangeViewModeCommand}"
                Content="Change ViewMode" />
        <ListView x:Name="list" ItemsSource="{Binding Models}">
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="IsSelected" Value="{Binding Path=Selected, Mode=TwoWay}" />
                    <Setter Property="Content" Value="{Binding DisplayName}" />
                </Style>
            </ListView.ItemContainerStyle>
            <ListView.Style>
                <!--  Default ItemsPanel  -->
                <Style TargetType="ListView">
                    <Setter Property="ItemsPanel">
                        <Setter.Value>
                            <ItemsPanelTemplate>
                                <StackPanel />
                            </ItemsPanelTemplate>
                        </Setter.Value>
                    </Setter>

                    <Style.Triggers>
                        <!--  Change ItemsPanel  -->
                        <DataTrigger Binding="{Binding ViewMode}" Value="true">
                            <Setter Property="ItemsPanel">
                                <Setter.Value>
                                    <ItemsPanelTemplate>
                                        <WrapPanel />
                                    </ItemsPanelTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ListView.Style>
        </ListView>
        <TextBlock Text="{Binding Path=SelectedItems.Count, ElementName=list, StringFormat=Selected Items Count:{0}}" />
    </StackPanel>
</Window>

Изменить
Я добавляю код для ViewModel и класса Model. Как видите, это так же просто, как и получается.

public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ObservableCollection<Model> Models { get; private set; }

        private bool viewMode;
        public bool ViewMode
        {
            get { return viewMode; }
            set 
            {
                if (viewMode != value)
                {
                    viewMode = value;
                    OnPropertyChanged("ViewMode");
                }
            }
        }

        public ICommand ChangeViewModeCommand
        {
            get { return new DelegateCommand(() => ViewMode = ViewMode ? false : true); }
        }

        public ViewModel()
        {
            Models = new ObservableCollection<Model>();
            Models.Add(new Model() { DisplayName = "Model1" });
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

Класс модели

public class Model : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private bool isSelected;
    public bool Selected
    {
        get { return isSelected; }
        set 
        { 
            isSelected = value; OnPropertyChanged("Selected"); 
        }
    }

    private string display;
    public string DisplayName
    {
        get { return display; }
        set { display = value; OnPropertyChanged("Display"); }
    }


    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Странно то, что хотя в коллекции Models всего 1 элемент, ListView.SelectedItems.Count увеличивается.

Спасибо


person ajravago    schedule 09.09.2015    source источник
comment
ваш XAML верен, текстовый блок, показывающий ваш SelectedItems.Count, делает то, что должен. Я предполагаю, что проблема должна заключаться в том, как вы привязываетесь к своему свойству Selected(bool).   -  person Cadogi    schedule 09.09.2015


Ответы (1)


Я думаю, вы нашли ошибку в реализации Microsoft ListView. После явной установки свойства в вашем триггере данных.

 <Setter Property="IsSelected" Value="{Binding Path=Selected, Mode=TwoWay}" />

Когда ListView уничтожает представление и повторно привязывает панель, он не очищает свой внутренний кеш от выбранных элементов. После перестроения представления новые ListViewItems получают вашу привязку и вставляют ее в список SelectedItems. Таким образом, почему вы будете продолжать увеличиваться, когда вы переворачиваете свои панели. Если вы не заметили, что если вы нажмете на элементы после их повторной визуализации, ListView удалит элементы, по одному на каждый ваш щелчок, до тех пор, пока он снова не заработает правильно.

Я бы посоветовал, если вы хотите узнать количество выбранных элементов, не занимаясь этой проблемной областью, я бы предложил конвертер, привязанный к вашим моделям, который просто подсчитывал бы их.

У меня есть очень хакерская работа, если вы просто хотите, чтобы она генерировалась правильно. Вам нужно будет вручную заменить список и отключить привязку, чтобы компенсировать эту ошибку. Это очень уродливый и неправильный способ обращения с ним, но вы можете это сделать.

  public ICommand ChangeViewModeCommand
    {
        get
       { 
           return new DelegateCommand(() =>
           { 
             ViewMode = ViewMode ? false : true;
             //this is crap that you shouldn't have to do
             var m = Models;
             Models = null;
             OnPropertyChanged("Models");
             Models = m;
             OnPropertyChanged("Models");
             return viewMode;
           }); 
       }
    }

Это позволит обойти вашу ошибку и правильно связать список.

person Matt Wilkinson    schedule 10.09.2015
comment
Я думаю, это действительно ошибка. Я сделал что-то похожее на ваше предложение, но вместо того, чтобы установить для моделей значение null, я установил для свойства Selected всех моделей значение false. Куда я могу сообщить об этой проблеме, чтобы ее можно было исправить? - person ajravago; 11.09.2015