Keeping the Selected Item Selected When Changing a ListView’s ItemsSource

, , ,

A ListView’s data display needs to be updated. The revised dataset, retrieved from the service layer, is contained in a new collection containing new object instances. Setting this IList<T> as the control’s new ItemsSource causes the control to display the revised data and…whoa…clears the currently selected item.

From the technical perspective, this clearing makes sense. A new collection has been displayed making the selected item from the previous collection irrelevant. However, from the user’s perspective, the same collection is displayed both before and after the refresh (albeit with revised data), so the current item selection is still pertinent and should be maintained.

Imagine that the ListView displays a list of bank account names and balances (“Checking – $250.25”, “Savings – $3,539.22”, etc.). The user selects account Savings. Moments later, as a result of automatically retrieving updated information from the bank, a background process increases Savings’ balance to $4,001.18. From the user’s perspective, does it make sense that this balance revision should clear their selection of account Savings? No!

How can we keep the currently selected item selected when changing ItemsSource’s value?

Selecting the Equivalent Item in the New Collection

Before pointing ItemsSource to the revised data collection, set the current selected item to a local variable. After making the ItemsSource change, look through the new collection to see if it contains an item equivalent to the old selected item. If it does, set this item as the new selected item.

(This example view model code assumes that the ListView’s ItemsSource and SelectedItem properties are data-bound to equivalently-named properties on the view model and that the view model implements INotifyPropertyChanged.)

// Method called whenever the data display needs to be updated.
private void UpdateAccountsList() {
    var currentSelectedItem = SelectedItem;
    ItemsSource = service.GetList();
    SelectedItem = ItemsSource.FirstOrDefault(x => x.Id == currentSelectedItem.Id);
}

If the new collection does not contain an entry equivalent to the old selected item, SelectedItem will be set to null because ItemsSource.FirstOrDefault(…) returns null when no match is found.

Now, let’s apply a refactoring. In the above code, the view model knows the criteria for determining if two Account object instances represent the same account (“x.Id == currentSelectedItem.Id”). This responsibility would more appropriately reside in the Account class. If we override Account’s Equals() method to indicate value equivalence, we can remove this knowledge from the view model by changing the search and set line of code to:

    SelectedItem = ItemsSource.FirstOrDefault(x => x.Equals(currentSelectedItem));

From the user’s perspective, this approach maintains the selected item. So, the problem is solved, correct? Yes—if our only concern is what the user can see.

Other Code Dependent on SelectedItem’s Value

However, if other code takes action when SelectedItem’s value changes, we still have a problem. Within the three lines of UpdateAccountsList(), SelectedItem changes from the old selected item, to null, to the new selected item. Suppose that each time SelectedItem is changed, the view model populates an edit form (say, the detail part of a master-detail form). Do you see where there’d be a problem? Would you be interested in a blog post on how to resolve this issue?

Note: What we’ve discussed applies not only to ListView, but also to other Selector-based controls, including ListBox, TreeView, and WPF Toolkit’s DataGrid.

Leave a Reply

Your email address will not be published. Required fields are marked *