Download the source for this article.
Previous posts in this series:
- http://grokys.blogspot.com/2010/07/mvvm-and-multiple-selection-part-iii.html
- http://grokys.blogspot.com/2010/07/mvvm-and-multiple-selection-part-ii.html
- http://grokys.blogspot.com/2010/07/mvvm-and-multiple-selection-part-i.html
Please note, this is WPF only - I don't use Silverlight!
It's been a while since my previous post in this series, MVVM and Multiple Selection – Part III. In the comments to that post, I had a number of people asking about the DataGrid control, however by that point I was no longer in WPF-land and didn't have the time or inclination to investigate properly.
Recently though, I needed to track multiple selections on a DataGrid and as the commenters pointed out, it didn't work. The problem came down to the method I was using to track changes to the ItemsSource property on a control:
static MultiSelect()
{
Selector.ItemsSourceProperty.OverrideMetadata(typeof(Selector), new FrameworkPropertyMetadata(ItemsSourceChanged));
}
This doesn't work for DataGrid as the ItemsSource metadata on that control is already overridden, so I needed to find a different approach.
Here's what I came up with:
static void IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Selector selector = sender as Selector;
bool enabled = (bool)e.NewValue;
if (selector != null)
{
DependencyPropertyDescriptor itemsSourceProperty =
DependencyPropertyDescriptor.FromProperty(Selector.ItemsSourceProperty, typeof(Selector));
IMultiSelectCollectionView collectionView = selector.ItemsSource as IMultiSelectCollectionView;
if (enabled)
{
if (collectionView != null) collectionView.AddControl(selector);
itemsSourceProperty.AddValueChanged(selector, ItemsSourceChanged);
}
else
{
if (collectionView != null) collectionView.RemoveControl(selector);
itemsSourceProperty.RemoveValueChanged(selector, ItemsSourceChanged);
}
}
}
Instead of overriding the property metadata globally for all Selectors, I attach a listener to the PropertyDescriptor. Now, the only problem here is that the event handler for PropertyDescriptor changed events are plain old EventHandler delegates, so by the time the property has changed we can't get hold of the old IMultiSelectCollectionView in order to remove the control from it. I get round this by storing all attached controls in a static Dictionary.
This leaves us the problem that we're going to be keeping controls alive when after they're gone, but that's a post for another day. In the meantime, download the code!
Have you overcome the problem where you bind the IsSelected property of the DateGridRow to a boolean IsSelected property to your viewmodel. Then in a select all command, i.e. add a new button which on the click event you go through the items view model and set the IsSelected property to true, this highlights the row in the grid automatically. But you still run into an issue where if RowVirtualization is enabled not all the rows are populated in the SelectedItems. I was able to reproduce this with your sample and was wondering if you had a workaround or solution. Thanks for the great blog posts, very informative.
ReplyDeleteHi NEO,
DeleteI'm not sure I understand your question correctly, but it sounds like you're referring to the problem outlined in part 1 of this series? If that's the case then the workaround is to use MultiSelectCollectionView - that's the whole purpose of this class, and indeed this series of blog posts!
This comment has been removed by the author.
ReplyDeleteI tried your great solution. It was just wath I needed. Thank you but ...
ReplyDeleteTrying to Unselect all the selected items with a ICommand like this ...
Items.SelectedItems.Clear();
Items.Refresh();
... one Item remain selected.
What is your opinion about that ?
Is there another way to unselect all items ?
thank you
(Luca from Italy)
I found this solution my self.
DeleteIn MultiSelectCollectionView.cs added this two methods.
It seems to work fine.
public void DeselectAll() {
// Remove all the selected items
SelectedItems.Clear();
// Update the UI controls.
foreach( Control control in controls )
SetSelection( (Selector)control );
}
public void SelectAll() {
// Reload all the elements in the selected collection
SelectedItems.Clear();
foreach( T item in SourceCollection )
SelectedItems.Add( item );
// Update the UI controls.
foreach( Control control in controls )
SetSelection( control as Selector );
}
Hi Luca,
DeleteYes as you have noticed I haven't implemented setting the selection from code as I've never needed it for my own purposes. Please let me know if Barna's solution works for you!
Yes it works fine.
Delete(Barna is my nick name)
Bye Luca+
This is a very good solution.
ReplyDeletePreviously I'd been either turning off virtualization for small lists (and taking the performance hit), or rolling my own multi-select in the view models using the Mediator pattern.
I've been doing this a while, and I'm rarely impressed enough to leave a comment! Good work.
Pete