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!