Making a Two-Way binding for SelectedItems on a multi-select WPF ListBox

Similar to the TreeView, there is yet another WPF control that is severely limited in its functionality for no obvious reason – the ListBox. Inheriting from ListView, the ListBox implements support for a very important feature – multiple selection, which makes it possible for the user to select multiple items using either of the two SelectionModes:

  1. Extended – where you can hold the Shift key to select multiple items
  2. Multiple – where you can select multiple items by just clicking them.

The ListBox control even has a property, SelectedItems, where you can see what models were selected, but it doesn't have a public setter. Besides that, despite ListBox having support for value selectors (via SelectedValuePath and SelectedValue), it does not expose a similar property for multiple selected items, meaning that there is no SelectedValues, as one might expect.


I haven’t looked very far, but there were two ways of setting SelectedItems from code that I found:

  • ListBox.SetSelectedItems(…) protected method that takes a non-generic IEnumerable parameter
  • Accessing the SelectedItems property, which is a non-generic IList, to clear the existing items and add new ones as necessary

Both of them are essentially equivalent, but I chose the second one because it appeared more trivial.

Here’s how my basic behavior looked:

The main logic is placed inside the SelectItems() method which just boils down to clearing and populating the SelectedItems property of the ListBox. It’s also worth to note that, ListBox.SelectedItems.Add(…) will just silently return without adding anything if given an invalid parameter (non-existent item, for example).

Make sure to not forget the _XXXhandled flags, as they prevent endless recursions that would otherwise occur – a property changed from view will trigger an event that changes the property in source, which in turn triggers an event that changes the property in view and so on (or the other way around).


However, the ListBox in my project was actually utilizing SelectedValuePath so I’d also need to implement SelectedValues to make full use of that. For those unfamiliar – SelectedValue and SelectedValuePath is a pretty convenient mechanic to bind to object’s properties without losing binding to the object reference itself. You can read a bit more on it here.

The way SelectedValue works in WPF controls is actually pretty simple – when an item is selected, its property value is populated to SelectedValue using the property path stored in SelectedValuePath; and vice versa - when a SelectedValue is changed from source, a respective item, whose property value is equal to it, is selected.

To get the value of a property by its path I used reflection in the following way:

And then I added SelectedValues dependency property to complement the other one, as well as methods to convert between SelectedItems and SelectedValues.

At this point it can get really confusing. Important thing to remember is that SelectedValues takes precedence over SelectedItems, because in the worst case scenario (when SelectedValuePath is not set), it will be equal to SelectedItems. That means when we’re not sure which of them changed last, we assume SelectedValues to be the most up to date.

Besides that, SelectedValues is not actually changed in any of the event handlers (OnListBoxSelectionChanged, OnListBoxItemsChanged), because it’s updated in the dependency property callback (OnSelectedItemsChanged). In other words, when the view changes – an event is raised (OnListBoxSelectionChanged), which in turn updates SelectedItems, which raises the callback event of the dependency property (OnSelectedItemsChanged), which converts SelectedItems to SelectedValues using SelectedValuePath, setting the property to the new value.

The _XXXhandled flags also play an important role here, avoiding even more possible unwanted recursions (or event loops).

Finally, this is how you use it:


This behavior, among others, is implemented in my WPF extension library.

Comments