Search code examples
c#wpfdata-bindingdatagrid

DataGrid MultiSelect bound to ObservableCollection unstable when ObservableCollection is modified in the ViewModel


I’m building a WPF C# application that has multiple DataGrids bound to their respective ObservableCollections that contain objects.

I will focus on the DataGrid that binds to the Conduits ObservableCollection to keep things simple.

The DataGrids are set to multi select SelectionMode="Extended". The data in the DataGrids is also represented in a 2D view via a Canvas and drawing elements.

The idea is the user can select the objects in 2D or the DataGrids, as a single item, or multiple items/rows, and the DataGrid row, or object in 2D will be highlighted.

This is producing some unstable results. Too many to list, so I will focus on deleting items. I can delete objects in the 2D without problem when the DataGrid ViewModels have not been initialized. Once they are initialized I get the following error when deleting in 2D.

`System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'`

Objects are deleted in 2D as follows:

foreach (object _conduit in SelectedConduitList)
    {
        if (_conduit is Conduit conduit)
        {
                Conduits.Remove(conduit);
        }
    }

The associated DataGrid is bound to the objects, and selected objects as follows:

<custom:ConduitDataGrid
    ItemsSource="{Binding Path=NetworkMain.Conduits}" 
    SelectionMode="Extended"
    SelectedItemsList="{Binding NetworkMain.SelectedConduitList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

Here is the ObservableCollection for the Conduit DataGrids and the list for the selected Conduits

public ObservableCollection<Conduit> Conduits { get; set; } = new();

private IList _selectedConduitList = new ArrayList();
    public IList SelectedConduitList
    {
        get { return _selectedConduitList; }
        set
        {
            _selectedConduitList = value;
            //changes the IsSelected property of all objects in the ObserbservableCollection to false 
            DeselectAll();
            //changes the IsSelected property of all objects in the ObserbservableCollection to true if the object exists in the SelectedConduitList
            SelectConduits();
            NotifyOfPropertyChange(nameof(SelectedConduitList));
        }
    }

In order to get the DataGrids to bind multiple selected rows to the SelectedConduitList a custom datagrid was used as follows:

public class ConduitDataGrid : DataGrid
{
    public ConduitDataGrid()
    {
        this.SelectionChanged += CustomDataGrid_SelectionChanged;
    }

    void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        this.SelectedItemsList = this.SelectedItems;
    }

    #region SelectedItemsList

    public IList SelectedItemsList
    {
        get { return (IList)GetValue(SelectedItemsListProperty); }
        set 
        { 
            SetValue(SelectedItemsListProperty, value); 
        
        }
    }

    public static readonly DependencyProperty SelectedItemsListProperty = 
        DependencyProperty.Register(nameof(SelectedItemsList), typeof(IList), typeof(ConduitDataGrid), new PropertyMetadata(null));

    #endregion
}

Does anybody know why I can’t modify (for example delete) an object form the SelectedConduitList from within my 2D Layout ViewModel without throwing an error once the DataGrid ViewModels have been initialised?


Solution

  • I can delete objects in the 2D without problem when the DataGrid ViewModels have not been initialized. Once they are initialized I get the following error when deleting in 2D.

    Deleting from the collection to which the source is attached leads to the deletion from the collection of selected elements, on which you have a foreach loop. Changing the foreach source while it is running is not allowed.
    This is a common problem and the simplest solution is to make a copy of the list and then loop over it.

        foreach (Conduit conduit in SelectedConduitList.OfType<Conduit>().ToList())
        {
            Conduits.Remove(conduit);
        }