Friday, 16 July 2010

MVVM and Multiple Selection – Part I

When using the MVVM pattern in the Windows Presentation Foundation you’ll often want to display a list of items to your user in a control such as a ListBox. To do this, you can expose a collection of child items in your ViewModel:

And in xaml, bind your ListBox’s ItemsSource property to that collection:

<ListBox ItemsSource="{Binding Items}"/>

If you want to do anything useful with this list, your ViewModel will usually want to be able to find out what the ListBox’s current selection is. To do this, WPF provides the ICollectionView mechanism. Instead of exposing “Items” as an ObservableCollection, you’ll expose it as an ICollectionView, set ListBox.IsSynchronizedWithCurrentItem="True" and the user’s selection magically becomes available to your ViewModel through the ICollectionView.CurrentItem property.

However, one major omission from ICollectionView is that it doesn’t support multiple selections. The usual solution to this is to add an IsSelected property to your ItemViewModel, and use a style to keep the IsSelected property on the View in sync with the IsSelected property in the ViewModel:

Here, we’re setting up a two-way binding between the ListBoxItem.IsSelected property and the ItemViewModel.IsSelected property. If you download the sample project, you’ll see that this works: select 3 items in the list, click the “Display Selection Count” button and you’ll see that the ViewModel now knows what the user has selected:

(Yes, I know we shouldn’t be calling MessageBox.Show from our ViewModel, but this is for illustration purposes only.)

But hold on, what’s this? There are 200 items in that ListBox, but if you press Ctrl+A to select all items, and then click “Display Selection Count” you’ll see this:

19 Items? The problem we’re seeing is related to a WPF feature called UI virtualization. This feature basically means that WPF only creates ListBoxItems for the items currently visible. Because only visible ListBoxItems are being created, the style we set up to keep the View and ViewModel in sync fails.

Now, we can turn off UI virtualization, but it’s actually a very useful feature, particularly when working with large amounts of complex data, so we need another solution. I’ll explain that (and its associated problems) in my next blog post.