Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Preserve the Selected Item of a WPF List Box

0.00/5 (No votes)
1 Aug 2014 9  
A behavior to attach to a ListBox to keep WPF from clearing the selected item

When you filter or sort the items in a ListBox, WPF will sometimes set the SelectedItem property to null. I’ll show you a behavior that you can attach to your ListBox to preserve that selection.

The problem

You’ve data bound the items source of a ListBox to an observable collection. Maybe you’ve also data bound the SelectedItem property to your view model. Then at some point you need to update that collection to sort or filter that collection.

It could be simply to move an item up or down in the collection. you would do this by calling Move(oldIndex, newIndex), or by a RemoveItem followed by a InsertItem. Or it could be to apply a completely different sort order.

As you sort the collection, you will remove items from one place and insert them into another. But WPF jumps the gun. It will immediately react when you remove the item. If the item you just removed happens to be the selected item, it will set the SelectedItem property of the ListBox to null.

Even if you prevent the null from coming into your view model, the ListBox will no longer display the item as selected.

The events

If you subscribe to SelectionChanged events on the ListBox, and CollectionChanged events on the observable collection, you can observe the following sequence of events:

First, you will receive the SelectionChanged event from the ListBox, indicating one item in the RemovedItems collection. This is WPF responding to the second event, which you are just about to receive.

Second, the observable collection fires CollectionChanged, with the action being Remove. There will be one item in the OldItems collection. WPF received this event first, and deselected the item. Now it’s your turn to see this event.

Third, the observable collection fires CollectoinChanged, with the action being Add. There will be one item in the NewItems collection. What you want to do is set the SelectedItem on the ListBox, fixing the mistake that WPF made in the first place.

The fix

To fix this problem, you need to keep track of the above events. Since you get the CollectionChanged event after WPF does, the SelectedItem has already been set to null. So you have to know what item used to be selected to see if you are in this situation.

The following code handles the SelectionChanged event by storing the _lastSelection. Then it sees the CollectionChanged event with the Remove action, and finds that the removed item is the one that was just selected. It moves this forward to the _lastRemoved. Finally, when it sees the CollectionChanged event with the Add action, and determines that the item that was just removed has been re-added, it sets the SelectedItem back again.

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.RemovedItems != null && (e.AddedItems == null || e.AddedItems.Count == 0))
    {
        _lastSelection = e.RemovedItems.OfType<object>().FirstOrDefault();
    }
}

private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        var lostItem = e.OldItems.OfType<object>().FirstOrDefault();
        if (lostItem != null && Object.Equals(_lastSelection, lostItem))
        {
           _lastRemoved = _lastSelection;
           _lastSelection = null;
        }
    }
    else if (e.Action == NotifyCollectionChangedAction.Add)
    {
        var newItem = e.NewItems.OfType<object>().FirstOrDefault();
        if (newItem != null && Object.Equals(_lastRemoved, newItem))
        {
             AssociatedObject.SelectedItem = newItem;
            _lastRemoved = null;
        }
    }
}

I’ve packaged the entire thing in a behavior so you can just attach it to a ListBox to fix this issue. Get it from NuGet.

Install-Package Itzben

Or download the source code from CodePlex.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here