Adventures in MVVM – Commanding with List Boxes

Shout it kick it on DotNetKicks.com

Continuing in my series of “Adventures in MVVM”, I want to talk about a few different approaches to working with List Boxes with the MVVM pattern.  What I am writing here is generally true of all controls that derive from Selector, including ListBox and ComboBox.  This example was developed in Silverlight, but the same concepts also apply to WPF.

The Problem

You have a list box in your view, and you want your ViewModel to do something when an item in the ListBox is selected. You want to do this without any code-behind, using the MVVM pattern.  There are three methods that I have come up with, and I will outline them here.  In this post, I will be using a VERY simple data class in my ListBox called Person with a First and Last name.  It is so simple, in fact, that I have chosen not to include the source for this class.

Method 1: Quick and Dirty SelectedItem binding

This method sets up a SelectedPerson property in the view model and does something when the property is changed. 

public class ViewModel : INotifyPropertyChanged
{
    public ObservableCollection<Person> People { get; private set; }

    private Person _selectedPerson = null;
    public Person SelectedPerson
    {
        get { return _selectedPerson; }
        set
        {
            _selectedPerson = value;
            OnPropertyChanged("SelectedPerson");
            DoSomething(value);
        }
    }
    // ... rest of ViewModel
}

<ListBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" />

Pros: This method is quick and simple to get going
Cons: You are introducing side effects in your property code.  If you are OK with this, then read no further.  If this bothers you the way it does for me, then lets look at our next option.

Method 2: Button Command

There are plenty of commanding libraries out there to choose from.  I will take advantage of the Prism commanding system (Microsoft.Practices.Composite.Presentation.Commands).  They have implemented bindable commands for ButtonBase.  The only problem: ListBox is not a ButtonBase.  To get around this, replace the ItemTemplate with a Button that has a template of textblock.

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        PersonSelected = new DelegateCommand<Person>(DoSomething);
        // ... rest of constructor
    }

    public ObservableCollection<Person> People { get; private set; }
    public ICommand PersonSelected { get; private set; }

    // ... rest of ViewModel    
}

<ListBox ItemsSource="{Binding People}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Button Commands:Click.Command="{Binding PersonSelected, Source={StaticResource ViewModel}}" Commands:Click.CommandParameter="{Binding}" >
                <Button.Template>
                    <ControlTemplate>
                        <TextBlock Text="{Binding}" />
                    </ControlTemplate>
                </Button.Template>
            </Button>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Pros: The ViewModel is much more simple with no side effects. 
Cons: The XAML is ugly as sin.  It also changes the behavior of the ListBox in a subtle way.  Every time you select an item, the command fires, not just when it changes.  This is my LEAST favorite approach.  We can do better

Method 3: Bind Commands to the ListBox

The final mechanism is my favorite.  Even though Prism doesn’t give us the ability to bind commands to ListBoxes, we can extend their attached behavior infrastructure such that all ListBoxes and ComboBoxes (or anything that derives from Selector) can take advantage of it.  The ViewModel doesn’t change from “Method 2”, but the XAML does:

<ListBox ItemsSource="{Binding People}" Commands:Selected.Command="{Binding PersonSelected}" />

Pros: Best of both worlds.  Simple ViewModel.  Simple XAML
Cons: You have to write some extensions to the Prism infrastructure.  This code is boilerplate.  I have written some generics that can reduce the boilerplate code somewhat, but not completely, due to the static properties.

The Winner Is….

I like “Method 3” the best.  With a bit of some infrastructure code that you can tuck away, you get to bind the selected items to a command in any case.  It plays well, and it is easy to follow.

But wait… you want the Prism extensions?  Here they are:

public class SelectorSelectedCommandBehavior : CommandBehaviorBase<Selector>
{
    public SelectorSelectedCommandBehavior(Selector selectableObject)
        : base(selectableObject)
    {
        selectableObject.SelectionChanged += OnSelectionChanged;
    }

    void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        CommandParameter = TargetObject.SelectedItem;
        ExecuteCommand();
    }
}public static class Selected
{
    private static readonly DependencyProperty SelectedCommandBehaviorProperty = DependencyProperty.RegisterAttached(
        "SelectedCommandBehavior",
        typeof(SelectorSelectedCommandBehavior),
        typeof(Selected),
        null);

    public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
        "Command",
        typeof(ICommand),
        typeof(Selected),
        new PropertyMetadata(OnSetCommandCallback));

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only works for selector")]
    public static void SetCommand(Selector selector, ICommand command)
    {
        selector.SetValue(CommandProperty, command);
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only works for selector")]
    public static ICommand GetCommand(Selector selector)
    {
        return selector.GetValue(CommandProperty) as ICommand;
    }

    private static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var selector = dependencyObject as Selector;
        if (selector != null)
        {
            GetOrCreateBehavior(selector).Command = e.NewValue as ICommand;
        }
    }

    private static SelectorSelectedCommandBehavior GetOrCreateBehavior(Selector selector)
    {
        var behavior = selector.GetValue(SelectedCommandBehaviorProperty) as SelectorSelectedCommandBehavior;
        if (behavior == null)
        {
            behavior = new SelectorSelectedCommandBehavior(selector);
            selector.SetValue(SelectedCommandBehaviorProperty, behavior);
        }

        return behavior;
    }
}


                            
                                                                    
                            
            
                        

6 Responses to “Adventures in MVVM – Commanding with List Boxes”

  1. Vivek says:

    Hi,
    I am using Commands:Selected.Command for my ComboBox. In XAML I have set IsEnabled = false for the control. But when the following line gets executed,
    “GetOrCreateBehavior(selector).Command = e.NewValue as ICommand;”
    the control’s IsEnabled property changed back to true.

    How can we avoid this behavior?
    Thanks,
    Vivek

  2. Chris says:

    Brian,
    Thanks a lot for this sample code – helped me keep event handlers out of my view code-behind. I’m trying to figure out the best place to put this type of code in my project. I’ve got view namespaces, view model namespaces, etc. Where do you put this type of code in your projects?

    Thanks,
    Chris

  3. admin says:

    I created a folder and namespace called AttachedBehaviors. I often get several of them put away by the end of a project, and they usually follow the same pattern.

  4. sri says:

    Hi,

    I have a listbox which shows checkbox items. Whenever i do select the checkbox, listbox item selectedItem is not triggering which is very mych required for me. How to handle this. Note that the SelectedItem is triggering when i click the listbox item but not on checkbox click event.

    Regards,Sri

  5. bydt says:

    Great! Thank you very much, it works like charm

Leave a Reply