Thursday, June 23, 2011

Silverlight 4 ComboBox SelectedItem vanishes on ItemsSource update

In Silverlight 4, the SelectedItem is set to null when we update / change (new instance set) the ItemsSource.

Let us take a case of having a readonly property that builds a new list(by performing operations like filtering , sortings, etc... on an existing data) each time the property is read(get).

Snippet 1
 
ObservableCollection<ICheckItem> checkItemCollection;

public ObservableCollection<ICheckItem> CheckItemCollection
{
    get
    {
        return checkItemCollection;
    }
    set
    {
        checkItemCollection = value;
        checkItemCollection.CollectionChanged += (s, e) =>
        {
            NotifyPropertyChanged("SortedCheckItemCollection");
        };
        NotifyPropertyChanged("CheckItemCollection");
        NotifyPropertyChanged("SortedCheckItemCollection");
    }
}

public List<ICheckItem> SortedCheckItemCollection
{
    get
    {
        return CheckItemCollection.OrderBy(ci => ci.Name).ToList();
    }
}
 
Also consider another property that holds the selected item.
 
Snippet 2
 
ICheckItem selectedCheckItem;
public ICheckItem SelectedCheckItem
{
    get
    {
        return selectedCheckItem;
    }
    set
    {
        selectedCheckItem = value;
        NotifyPropertyChanged("SelectedCheckItem");
    }
}
 
Now if we bind the above properties to the ComboBox, initially the ComboBox will hold the list and displays the selected item correctly, provided the SelectedItem property holds a valid object(item) from the ItemsSource(IEnumerable).
 
Snippet 3

<ComboBox ItemsSource="{Binding Path=SortedCheckItemCollection}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedCheckItem, Mode=TwoWay}" />
<br />

But after that, the next time when the property change notification for "SortedCheckItemCollection" is raised(by some means), the ItemsSource is provided with a new List instance by the "get" accessor(see Snippet 1). At this point of time, the SelectedItem will become null and no item will be displayed selected in the ComboBox.
 
To overcome this issue(in my real case), I planned to go with the custom control derived from the ComboBox. In the beginning, I thought of providing a dependency property that works in sync with the SelectedItem property and make workaround for the scenario where the issue arises. But while developing code for that, I identified(by debugging) that only after the execution of "OnItemsChanged" handler, the SelectedItem is lost.
So I revamped my logic by overriding the "OnItemsChanged".
In that I'll,
  • Persist the SelectedItem locally,
  • Call the base.OnItemsChanged and
  • Then reset the SelectedItem with the locally persisted object.

Snippet 4

/// <summary>
/// PersistenceComboBox is same as ComboBox in addition will handle the SelectedItem when the ItemSource is changed.
/// Workaround for issue: ComboBox unable to handle the SelectedItem on ItemSource change. 
/// </summary>
public class PersistenceComboBox : ComboBox
{
    /// <summary>
    /// Updates the current selected item when the <see cref="P:System.Windows.Controls.ItemsControl.Items"/> collection has changed.
    /// </summary>
    /// <param name="e">Contains data about changes in the items collection.</param>
    protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        object persistenceSelectedItem = null;

        foreach (object item in this.ItemsSource)
        {
            if (item.Equals(this.SelectedItem))
            {
                persistenceSelectedItem = item;
            }
        }

        base.OnItemsChanged(e);

        if (this.SelectedItem != persistenceSelectedItem)
        {
            this.SelectedItem = persistenceSelectedItem;
        }
    }

}

So the change in the XAML(Snippet 3) will be,
 
Snippet 5 
 
<customControl:PersistenceComboBox ItemsSource="{Binding Path=SortedCheckItemCollection}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedCheckItem, Mode=TwoWay}" />
<br /> 

Now, the PersistenceComboBox(custom control) can be used in places where we require the SelectedItem to be retained even after updating the ItemsSource.

4 comments:

Creative Commons License
This work by Tito is licensed under a Creative Commons Attribution 3.0 Unported License.