MVVM: IsSelected list binding when tracking IsSynchronizedWithCurrentItem

I track changes to ListView selection in an MVVM project, binding to IsSelected. I also need to track the current item by including IsSynchronizedWithCurrentItem.

I found that when I have two ListView bindings to the same collection, I get an InvalidOperationException : "The collection was changed, the enumeration operation may not be executed." This seems to be a synchronous error between two ListViews; one fires a PropertyChanged event while the other fires a selector, perhaps?

I can’t figure out how to get around this, except to refuse to use IsSynchronizedWithCurrentItem and manage it myself. Any ideas?

Thanks.

ViewModel and code behind:

public class Item : INotifyPropertyChanged
{        
    public string Name{ get; set; }

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

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

public class ViewModel
{
    public ViewModel()
    {
        Items = new ObservableCollection<Item>()
                {
                    new Item(){Name = "Foo"},
                    new Item(){Name = "Bar"}
                };
    }
    public ObservableCollection<Item> Items { get; private set; }
}

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }
}

XAML:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="100" Width="100">
    <StackPanel>
        <ListView DataContext="{Binding Items}" ItemsSource="{Binding}" 
                  IsSynchronizedWithCurrentItem="True" SelectionMode="Single">
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
                </Style>
            </ListView.ItemContainerStyle>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=Name, Mode=OneWay}"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <ListView DataContext="{Binding Items}" ItemsSource="{Binding}" 
              IsSynchronizedWithCurrentItem="True" SelectionMode="Single">
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
                </Style>
            </ListView.ItemContainerStyle>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=Name, Mode=OneWay}"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackPanel>
</Window>
+3
2

. , .

, "SelectedItem", , ListView. , PropertyChanged. IsSelected, SelectedItem (, IsSelected = true). SelectedItem ListView ViewModel.

ViewModel .

public class ViewModel : INotifyPropertyChanged
{
    private Item _selectedItem;

    public ViewModel()
    {
        Items = new ObservableCollection<Item>()
            {
                new Item {Name = "Foo"},
                new Item {Name = "Bar"}
            };

        foreach ( Item anItem in Items )
        {
            anItem.PropertyChanged += OnItemIsSelectedChanged;
        }
    }

    public ObservableCollection<Item> Items { get; private set; }

    public Item SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            // only update if the value is difference, don't
            // want to send false positives
            if ( _selectedItem == value )
            {
                return;
            }

            _selectedItem = value;
            OnPropertyChanged("SelectedItem");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnItemIsSelectedChanged(object sender, PropertyChangedEventArgs e)
    {
        if ( e.PropertyName != "IsSelected" )
        {
            return;
        }

        SelectedItem = sender as Item;
    }

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

, IsSelected SelectionMode='Single'

, SelectionMode = 'Multiple', ViewModel, , - IsSelected true.

0

Source: https://habr.com/ru/post/1710324/


All Articles