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