Share via


WPF: How To Manage Available/Selected Lists using MVVM Or Code Behind

 

 

Introduction

This example shows how to handle two lists for selecting items. One showing selected items, the other showing remaining options available (minus the selected options).

 http://code.msdn.microsoft.com/site/view/file/84003/1/SelectSelected.png

As you select items from the ComboBox, they are moved to the "Selected" listbox. Selected items have a remove button, which returns the item to the "select" Combo.

  

Download

You can download the sample project here : http://code.msdn.microsoft.com/How-to-manage-availablesele-175b1682
 

Description

This sample project shows two methods of coding this solution, firstly using MVVM architecture, then using plain old code behind.

 

MVVM

Firstly, let's look at the nicer way to do this. Example 1 has just one line in the code-behind:

 

DataContext = new  ViewModel.Example1ViewModel();

 

Then the ViewModel takes over. Everything is therefore done with bindings in Example1Window.xaml, as the ViewModel does not know of any controls. Here is the 'available' listbox:

 

<ComboBox ItemsSource="{Binding UnselectedPeople}" DisplayMemberPath="Name" Margin="0,0,10,0"
                  helpers:EventToCommand.Event="ComboBox.SelectionChanged"
                  helpers:EventToCommand.CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource Self}}"
                  helpers:EventToCommand.Command="{Binding SelectPersonCommand}" />
 
Notice I am using the SelectionChanged event rather than binding to SelectedItem. This is because in practice, SelectedItem is not cleared when the actual item is removed from the underlying collection, which caused binding issues in later tasks. Instead I deploy an EventToCommand behaviour to convert SelectionChanged into a Command from the ViewModel.

 

The EventToCommand and RelayCommand shown in this project are from my MVVMXL sample project.

 

The 'selected' listbox is as follows:

<ListBox ItemsSource="{Binding SelectedPeople}" Grid.Row="1" Background="{x:Null}" BorderThickness="0" FontWeight="Bold" ItemTemplate="{StaticResource SelectedPersonItemTemplate}" ItemContainerStyle="{StaticResource StretchedItemContainer}"/>
 
 
SelectedPeople is an ObservableCollection, so any changes are reflected back in the UI, but UnselectedPeople is a calculated property, that needs triggering (manually calling RaisepropertyChanged) every time an option is selected or removed.
private void  DoSelectPerson(object parameter)
{
    var person = parameter as  Person;
    if (person == null) return;
 
    SelectedPeople.Add(person);
    RaisePropertyChanged("UnselectedPeople"); //Regenerate the list
}
 
void DoUnselectPerson(object parameter)
{
    var person = parameter as  Person;
    SelectedPeople.Remove(person);
    RaisePropertyChanged("UnselectedPeople"); //Regenerate the list
}
 
 

Raising the PropertyChanged event on UnselectedPeople triggers the bindings to update:

 

public List<Person> UnselectedPeople
{
    get
    {
        if (SelectedPeople.Count == 0) return AllPeople;
 
        return AllPeople.Except(SelectedPeople).ToList();
    }
}
 
 

Code Behind

Many would say this is simpler to code in code-behind rather than MVVM, and they are right, but coupling the code directly to the controls leads to a mess when scaled up to a full page's worth of functionality. However here is an example of how to do this:

 

public List<Person> UnselectedPeople
{
    get
    {
        if (SelectedPeople.Count == 0) return AllPeople;
 
        return AllPeople.Except(SelectedPeople).ToList();
    }
}
 
public Example2Window()
{
    InitializeComponent();
 
    AllPeople = new  List<Person>();
    for (var i = 0; i < 10; i++)
        AllPeople.Add(new Person(i, "Person_" + i));
 
    lstSelected.ItemsSource = SelectedPeople = new  ObservableCollection<Person>();
    LoadUnselectedCombo();
}
 
private void  ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var person = cmbUnselected.SelectedItem as  Person;
    if (person != null)
    {
        SelectedPeople.Add(person);
        LoadUnselectedCombo();
    }
}
 
private void  Button_Click(object  sender, RoutedEventArgs e)
{
    var button = sender as  Button;
    var person = button.DataContext as  Person;
 
    if (button != null && person != null)
    {
        SelectedPeople.Remove(person);
        LoadUnselectedCombo();
    }
}
 
void LoadUnselectedCombo()
{
    cmbUnselected.ItemsSource = UnselectedPeople;
}
 
public List<Person> UnselectedPeople 
{ 
    get
    { 
        if (SelectedPeople.Count == 0) return AllPeople; 
  
        return AllPeople.Except(SelectedPeople).ToList(); 
    } 
} 
  
public Example2Window() 
{ 
    InitializeComponent(); 
  
    AllPeople = new  List<Person>(); 
    for (var i = 0; i < 10; i++) 
        AllPeople.Add(new Person(i, "Person_" + i)); 
  
    lstSelected.ItemsSource = SelectedPeople = new  ObservableCollection<Person>(); 
    LoadUnselectedCombo(); 
} 
  
private void  ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    var person = cmbUnselected.SelectedItem as  Person; 
    if (person != null) 
    { 
        SelectedPeople.Add(person); 
        LoadUnselectedCombo(); 
    } 
} 
  
private void  Button_Click(object  sender, RoutedEventArgs e) 
{ 
    var button = sender as  Button; 
    var person = button.DataContext as  Person; 
  
    if (button != null && person != null) 
    { 
        SelectedPeople.Remove(person); 
        LoadUnselectedCombo(); 
    } 
} 
  
void LoadUnselectedCombo() 
{ 
    cmbUnselected.ItemsSource = UnselectedPeople; 
}

 

 

 

Hope this helps you!