0
votes

There seems to be a problem with the SelectedItems of ListView when changing the ItemsPanel dynamically. I have implemented MVVM on a ListView whose ItemsSource is binded to a collection of Models. The Model has 2 properties, DisplayName(string) and Selected(bool). And the DataContext for the listview contains a ViewMode(bool) property.

The setup is that the IsSelected property of the ListViewItem is binded to the Selected property of the Model and the ListView's ItemsPanel changes when I changed the ViewMode by click a button.

The problem is that when there is a selected item in the ListView and the ViewMode is changed, the ListView's SelectedItems count increases, even if the selected items do not change.

Note: In my setup, there is only 1 item in the ListView but the SelectedItems count increaes everytime I changed the ViewMode.

Here's the xaml part of the application to test the problem. I think you experts can do the ViewModel/Model part.

<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>

Edit
I'm adding the code for the ViewModel and the Model class. As you can see, it is as simple as it gets.

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));
        }

Model Class

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));
        }
    }
}

What is weird is that eventhough there is only 1 item in the Models collection, the ListView.SelectedItems.Count increases.

Thanks

1
your XAML is correct, the textblock showing your SelectedItems.Count is doing what it should. I would assume the problem must be in how you are binding to your Selected(bool) property.Cadogi

1 Answers

0
votes

I think you found a bug in Microsoft implementation of the ListView. Upon setting the property explictly in your on the data trigger.

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

When the ListView is destroying the view and rebinding the panel it is not clearing its internal cache of selected items. Once the view is rebuilt the new ListViewItems then receive your binding and insert it into the SelectedItems list. Thus why you will continue to increment when you flip flop your panels. If you hadn't noticed if you click on the items once they are re-rendered the ListView will remove the items, one for every time you click, until it works properly again.

I would suggest if you want to know the number of selected items without dealing with this problem area, I would suggest a converter that is bound to your Models that would simply count them.

I have a very hackish work around if you just want to it to generate correctly. You will need to manually replace the list and fire off the binding to compensate for this bug. This is a very ugly and improper way of handling it but you could do this.

  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;
           }); 
       }
    }

This will get around your bug and correctly bind the list properly.