Managing multiple selection in View Model ( .net Metro style app)

I was into refactoring of some WPF code (of metro application) into MVVM design pattern that has heavily used code behind.

I was stumbled upon a requirement, to bind to Selected Items property of GridView.  There was a list view in snapped mode and grid view in other modes (full/fill).

Requirement:

All I was trying to do is below

  1. To define SelectedItems in ViewModel
  2. Bind GridView.ItemsSource, ListView.ItemsSource to Items
  3. Somehow bind SelectedItems of GridView and ListView to SelectedItems
  4. After step 3, hoping that any selection change in GridView should be reflected to ListView and vice versa.

Challenges:

Never faced this scenario before, I found following challenges

  1. SelectedItems property is read only, and cannot be set directly
  2. SelectedItems cannot be used for binding expression, hence cannot be retrieved in View Model
  3. WinRT has no support for Behaviors. (For some unknown reasons, I wanted to use Attached Behavior). Thankfully there exists WinRTBehaviors on codeplex.

Note – Behaviors are not supported in WinRT natively. WinRTBehaviors is an open source for providing behavior support. This library is excellent, and provides behavior extension, exactly similar to WPF framework.

Behavior outline

  1. Behavior name -> MultiSelectBehavior. It will target ListViewBase (why -> Base class that provides Multiple selection mechanism)
  2. Add SelectedItems Dependency property to Behavior. This property will track of selected items of associated UI Element (List view derived class is referred as UI Element from hereafter).
  3. Hookup SelectionChanged event of UI element in  OnAttached, OnDetached event of Behavior. In the OnSelectionChanged event, sync up the changes to SelectedItems (of Behavior). It will propatege UI selection changes to SelectedItems in MultiBehavior.
  4. In the Property changed callback of SelectedItems (in Behavior), listen to CollectionChanged of bound object. Propagate changes in CollectionChanged event to UI Element.
  5. Add Behavior to UI elements in XAML
  6. Define data binding from SelectedItems (in Behavior) to SelectedItems in view model.

Code walkthrough

ListView, GridView are inherited from ListViewBase. ListViewBase provides multiple selection mechanism (SelectedItems, SelectionMode properties).

MultiSelectBehavior class is defined that is targeting ListViewBase.


public class MultiSelectBehavior : Behavior

SelectedItems dependency property is created in MultiSelectBehavior class. It internally holds all the selected items in the ListViewBase derived class.

public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
 "SelectedItems",
 typeof(ObservableCollection</pre>
SelectionChanged event is hooked up in the Behavior


protected override void OnAttached()
 {
 base.OnAttached();
 AssociatedObject.SelectionChanged += OnSelectionChanged;
 }

protected override void OnDetaching()
 {
 base.OnDetaching();
 AssociatedObject.SelectionChanged -= OnSelectionChanged;
 }

When SelectionChanged is triggered on element, SelectedItems is populated. _selectionChangedInProgress flag indicates, the selection change is in process. If this flag is set, no further handling is done (as it would trigger to infinite loop and stackoverflow exception).

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
 {
 if (_selectionChangedInProgress) return;
 _selectionChangedInProgress = true;
 foreach (var item in e.RemovedItems)
 {
 if (SelectedItems.Contains(item))
 {
 SelectedItems.Remove(item);
 }
 }

foreach (var item in e.AddedItems)
 {
 if (!SelectedItems.Contains(item))
 {
 SelectedItems.Add(item);
 }
 }
 _selectionChangedInProgress = false;
 }

Hook the collection change event of bound ObservableCollection. Propagate any change to List view base derived ui elements. This is done in PropertyChangedCallback handler.

 private static void PropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
 {
 NotifyCollectionChangedEventHandler handler = (s, e) => SelectedItemsChanged(sender, e);
 if (args.OldValue is ObservableCollection<object>)
 {
 (args.OldValue as ObservableCollection<object>).CollectionChanged -= handler;
 }

if (args.NewValue is ObservableCollection<object>)
 {
 (args.NewValue as ObservableCollection<object>).CollectionChanged += handler;
 }
 }

private static void SelectedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
 {
 if (sender is MultiSelectBehavior)
 {
 var listViewBase = (sender as MultiSelectBehavior).AssociatedObject;

var listSelectedItems = listViewBase.SelectedItems;
 if (e.OldItems != null)
 {
 foreach (var item in e.OldItems)
 {
 if (listSelectedItems.Contains(item))
 {
 listSelectedItems.Remove(item);
 }
 }
 }

if (e.NewItems != null)
 {
 foreach (var item in e.NewItems)
 {
 if (!listSelectedItems.Contains(item))
 {
 listSelectedItems.Add(item);
 }
 }
 }
 }
 }
 

MultiSelectBehavior is now completed.

Apply the behavior to UI elements.
Import the namespaces in XAML ( i -> behavior framework library, custom -> MultiSelectBehavior class)

 xmlns:i ="using:WinRtBehaviors"
 xmlns:custom="using:WinRtExt.Behavior"

Add behavior, and attach bind SelectedItems of behavior to SelectedItems of ViewModel


<i:Interaction.Behaviors>
 <custom:MultiSelectBehavior SelectedItems="{Binding SelectedItems, Mode=TwoWay}">

 </custom:MultiSelectBehavior>
 </i:Interaction.Behaviors>

Following XAML is for 2 controls (one list view, other grid view)


<GridView SelectionMode="Multiple" ItemsSource="{Binding Items}" BorderBrush="White" BorderThickness="2" ItemTemplate="{StaticResource textBlockDataTemplate}">
 <i:Interaction.Behaviors>
 <custom:MultiSelectBehavior SelectedItems="{Binding SelectedItems, Mode=TwoWay}">

 </custom:MultiSelectBehavior>
 </i:Interaction.Behaviors>
 </GridView>
 <Rectangle Width="20"></Rectangle>
 <ListView SelectionMode="Multiple" ItemsSource="{Binding Items}" BorderBrush="White" BorderThickness="2" ItemTemplate="{StaticResource textBlockDataTemplate}">
 <i:Interaction.Behaviors>
 <custom:MultiSelectBehavior SelectedItems="{Binding SelectedItems, Mode=TwoWay}">
 </custom:MultiSelectBehavior>
 </i:Interaction.Behaviors>
 </ListView>

Both list view and grid view are multi select enabled and in sync. Any selection change in any control is propagated to other control.

MultiselectSample is attached code. (Rename extension from doc to zip. It is a wordpress limitation).

The code is written in VS2012 RC in Win 8 Release preview. (It is incompatible with older versions, Win 8 Consumer preview, Win 8 developer preview, and may get broken in future versions).

Advertisements

About funatlearn

Hi i am a software developer working in noida, india. I have created this blog to jolt down my daily learnings at a single place.

Posted on June 28, 2012, in metro-ui, WPF. Bookmark the permalink. 5 Comments.

  1. I came across this blog post via codeproject.com as I was searching for a solution for the exact issue this code attempts to address. After downloading and testing it I noticed that SelectedItems is never getting propogated to the ViewModel. As a test, add a button to mainpage.xaml and hook it up to a command on the ViewModel. Set a breakpoint inside the command then start the app and select a few items. When you click the button and hit the breakpoint you’ll notice that SelectedItems is null in the ViewModel. You can take it a step further and remove the SelectedItems property all together from the ViewModel and the example still appears to function. My guess is the binding to the ViewModel is never picked up and the DependencyProperty on MultiSelectBehavior is defaulting to an empty list per the PropertyMetadata.

    I’ve tried some different ways of coaxing the behavior to properly bind to the ViewModel to no avail. Any thoughts on what would be required to pull this off?

    • Thanks for your reply and letting me know the issues.
      1. SelectedItems is null in ViewModel -> Yes it is until initialization is complete.

      2. Removing SelectedItems property from ViewModel will make it work just one way. Not bi directional sync.

      Note that this is my first attempt to the problem of synchronization of multiple item controls. Lots of enhancement can be made. Feel free to do so.

  2. This is exactly the functionality I was looking. Do you have Win 8 Port?

    • No I dont have.
      Very little modification is required to use in other Win 8 versions. You can just copy the code library (except demo app) and compile. Let me know if you face any issue.

  3. I got this to work by making sure SelectedItems was instaintiated in my viewmodel constructor. Now it works between controls and with the view model.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: