Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

Managing Multiple selection in View Model (.NET Metro Style Apps)

Rate me:
Please Sign up or sign in to vote.
4.44/5 (6 votes)
29 Jun 2012CPOL3 min read 73.3K   3K   11   13
This article provides Attached Behavior based approach to manage Multiple Selection in Collection Based UI control from the View Model. All the code in this article is strictly applicable to Win 8 metro style apps. Though the Behaviors can be easily adapted to WPF/Silverlight.

Introduction 

This article shows how to manage multiple selection in a View Model using Attached Behaviors.

Why

I was into refactoring some WPF code (of a Metro application) into MVVM design pattern that has heavily used code behind. I stumbled upon a requirement to bind to the SelectedItems property of a GridView.  There was a list view in snapped mode and grid view in other modes (full/fill).

Requirement

  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 

  1. SelectedItems property is read only, and cannot be set directly. 
  2. SelectedItems cannot be used for binding expressions, hence cannot be retrieved in View Model.
  3. WinRT has no support for Behaviors. (For 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 a behavior extension, exactly similar to the WPF framework.

Attached Behavior outline 

  1. Behavior name -> MultiSelectBehavior. It will target ListViewBase (why -> Base class that provides multiple selection mechanism). 
  2. Add the SelectedItems Dependency Property to Behavior. This property will track selected items of the associated UI Element (ListView derived class is referred as UI Element from hereafter). 
  3. Hookup the SelectionChanged event of the UI element in the OnAttached, OnDetached events of the Behavior. In the OnSelectionChanged event, sync up the changes to SelectedItems (of Behavior). It will propagate UI selection changes to SelectedItems in MultiBehavior. 
  4. In the PropertyChanged callback of SelectedItems (in Behavior), listen to CollectionChanged of the bound object. Propagate changes in the CollectionChanged event to the 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).

The MultiSelectBehavior class is defined targeting ListViewBase.

C#
public class MultiSelectBehavior : Behavior<ListViewBase>

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

C#
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
    "SelectedItems",
    typeof(ObservableCollection<object>),
    typeof(MultiSelectBehavior),
    new PropertyMetadata(new ObservableCollection<object>(), PropertyChangedCallback));

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

The SelectionChanged event is hooked up in the Behavior.

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

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

When SelectionChanged is triggered on an element, SelectedItems is populated. The _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).

C#
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 the bound ObservableCollection. Propagate any change to the ListView base derived UI elements. This is done in the PropertyChangedCallback handler.

C#
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 the UI elements. 

Import the namespaces in XAML ( i -> behavior framework library, custom -> MultiSelectBehavior class):

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

Add behavior, and attach bound SelectedItems of behavior to SelectedItems of ViewModel:

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

Following XAML is for two controls (one ListView, the other GridView):

XML
<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 the other control.

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.)

To use the code, copy MultiSelectBehavior.cs.

To execute the app, open in VS2012RC in Win 8 release preview. Build the app, deploy app, and launch the app.  

Points of Interest 

Kudos to WinRTBehaviors that made me mange multi-selection from View Model and share the information in the form of an article. 

History 

This is the first version of the article/source code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
Software Engineer based out in Noida.

Technology skillset – .NET, WPF, WCF, LINQ, XAML.

Started blogging on http://1wpf.wordpress.com/


Stackoverflow Profile -> http://stackoverflow.com/users/649524/tilak

Comments and Discussions

 
QuestionI can't print selected Items. Pin
Member 1104554621-Feb-15 6:25
Member 1104554621-Feb-15 6:25 
QuestionAdding SelectedItems from ViewModel Pin
Apar Mathur28-May-14 1:27
Apar Mathur28-May-14 1:27 
AnswerRe: Adding SelectedItems from ViewModel Pin
Member 1253132919-May-16 6:37
Member 1253132919-May-16 6:37 
BugPropertyMetadata should not contain reference as default initial value Pin
Akash Kava8-May-14 18:00
Akash Kava8-May-14 18:00 
QuestionProblem type conversion Pin
LasseL6-Aug-13 23:29
LasseL6-Aug-13 23:29 
GeneralMy vote of 4 Pin
imgen12-Nov-12 23:19
imgen12-Nov-12 23:19 
GeneralMy vote of 3 Pin
Wieser Software Ltd18-Oct-12 23:03
Wieser Software Ltd18-Oct-12 23:03 
QuestionThis code doesn't quite work Pin
Wieser Software Ltd18-Oct-12 21:35
Wieser Software Ltd18-Oct-12 21:35 
AnswerRe: This code doesn't quite work Pin
Fun@learn19-Oct-12 22:48
Fun@learn19-Oct-12 22:48 
GeneralRe: This code doesn't quite work Pin
Wieser Software Ltd19-Oct-12 23:11
Wieser Software Ltd19-Oct-12 23:11 
GeneralRe: This code doesn't quite work Pin
Fun@learn20-Oct-12 1:40
Fun@learn20-Oct-12 1:40 
QuestionThanks Pin
cbordeman15-Oct-12 18:37
cbordeman15-Oct-12 18:37 
AnswerRe: Thanks Pin
Fun@learn15-Oct-12 19:21
Fun@learn15-Oct-12 19:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.