Search code examples
c#wpfmvvmdatagrid

WPF Select multiple items in DataGrid from ViewModel with twoway binding


I'm trying to create a twoway binding to the selected items in a WPF DataGrid. I want to be able to select multiple items from the ViewModel and also from the actual UserControl. I know I cannot directly set the selected items since this property is readonly.

I'm thinking of binding to a DependencyProperty and subscribing to the SelectionChanged event of the DataGrid in the code behind.

<UserControl.Resources>
    <Style TargetType="local:ListView">
        <Setter Property="SelectedItems" Value="{Binding SelectedItemsVM, Mode=TwoWay}"/>
    </Style>
</UserControl.Resources>

<DataGrid Name="ObjectListDataGrid" SelectionChanged="OnSelectionChanged">

In the code behind I create the DependencyProperty. When this is set, I subscribe to the CollectionChanged event.

public ObservableCollection<object> SelectedItems
{
    get { return (ObservableCollection<object>)GetValue(SelectedItemsProperty); }
    set { SetValue(SelectedItemsProperty, value); }
}

public static readonly DependencyProperty SelectedItemsProperty =
    DependencyProperty.Register("SelectedItems", typeof(ObservableCollection<object>), typeof(ListView), new PropertyMetadata(default(ObservableCollection<object>), OnSelectedItemsChanged));

private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var collection = e.NewValue as ObservableCollection<object>;
    if (collection != null)
    {
        collection.CollectionChanged -= OnSelectedItemsCollectionChanged;
        collection.CollectionChanged += OnSelectedItemsCollectionChanged;
    }
}

I use the EventHandler to the SelectionChanged event of the DataGrid to add/remove the items in the collection.

private void OnSelectionChanged(object sender, SelectionChangedEventArgs args)
{
    SelectedItems.Remove(args.RemovedItems);
    SelectedItems.Add(args.AddedItems);
}

Now I want to select the rows needed in the OnSelectedItemsCollectionChanged method when the collection changes.

private static void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{   
    foreach (DataGridRow row in ObjectListDataGrid.Rows())
    {
        if(ObjectListDataGrid.SelectedItems.Contains(row.Item))
            row.IsSelected = true;
        else
            row.IsSelected = false;
    }
}

The problem: Since the OnSelectedItemsCollectionChanged method is static, I do not have access to the ObjectListDataGrid. Is there any way to overcome this, or do it in a different way?

For completeness, the DataGrid.Rows() method is an extension method to get a list of the rows:

public static IEnumerable<DataGridRow> Rows(this DataGrid grid)
{
    var itemsSource = grid.ItemsSource as IEnumerable;
    if (null == itemsSource) yield return null;
    foreach (var item in itemsSource)
    {
        var row = grid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
        if (null != row) yield return row;
    }
}

Solution

  • Your OnSelectedItemsCollectionChanged method must not be static.

    Cast the DependencyObject argument of the OnSelectedItemsChanged method to your ListView class. Also make sure to detach the handler from the old value of the SelectedItems property.

    private static void OnSelectedItemsChanged(
        DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var listView = (ListView)d;
        var oldCollection = (INotifyCollectionChanged)e.OldValue;
        var newCollection = (INotifyCollectionChanged)e.NewValue;
    
        if (oldCollection != null)
        {
            oldCollection.CollectionChanged -= listView.OnSelectedItemsCollectionChanged;
        }
    
        if (newCollection != null)
        {
            newCollection.CollectionChanged += listView.OnSelectedItemsCollectionChanged;
        }
    }
    
    private void OnSelectedItemsCollectionChanged(
        object sender, NotifyCollectionChangedEventArgs e)
    {
        foreach (var row in ObjectListDataGrid.Rows())
        {
            row.IsSelected = ObjectListDataGrid.SelectedItems.Contains(row.Item);
        }
    }