Ok, this has been bugging me for a while now. And I wonder how others handle the following case:
<ComboBox ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem}"/>
The DataContext object’s code:
public ObservableCollection<MyItem> MyItems { get; set; }
public MyItem SelectedItem { get; set; }
public void RefreshMyItems()
{
MyItems.Clear();
foreach(var myItem in LoadItems()) MyItems.Add(myItem);
}
public class MyItem
{
public int Id { get; set; }
public override bool Equals(object obj)
{
return this.Id == ((MyItem)obj).Id;
}
}
Obviously when the RefreshMyItems() method is called the combo box receives the Collection Changed events, updates its items and does not find the SelectedItem in the refreshed collection => sets the SelectedItem to null. But I would need the combo box to use Equals method to select the correct item in the new collection.
In other words – the ItemsSource collection still contains the correct MyItem, but it is a new object. And I want the combo box to use something like Equals to select it automatically (this is made even harder because first the source collection calls Clear() which resets the collection and already at that point the SelectedItem is set to null).
UPDATE 2 Before copy-pasting the code below please note that it is far from perfection! And note that it does not bind two ways by default.
UPDATE Just in case someone has the same problem (an attached property as proposed by Pavlo Glazkov in his answer):
public static class CBSelectedItem
{
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedIte. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(CBSelectedItem), new UIPropertyMetadata(null, SelectedItemChanged));
private static List<WeakReference> ComboBoxes = new List<WeakReference>();
private static void SelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComboBox cb = (ComboBox) d;
// Set the selected item of the ComboBox since the value changed
if (cb.SelectedItem != e.NewValue) cb.SelectedItem = e.NewValue;
// If we already handled this ComboBox - return
if(ComboBoxes.SingleOrDefault(o => o.Target == cb) != null) return;
// Check if the ItemsSource supports notifications
if(cb.ItemsSource is INotifyCollectionChanged)
{
// Add ComboBox to the list of handled combo boxes so we do not handle it again in the future
ComboBoxes.Add(new WeakReference(cb));
// When the ItemsSource collection changes we set the SelectedItem to correct value (using Equals)
((INotifyCollectionChanged) cb.ItemsSource).CollectionChanged +=
delegate(object sender, NotifyCollectionChangedEventArgs e2)
{
var collection = (IEnumerable<object>) sender;
cb.SelectedItem = collection.SingleOrDefault(o => o.Equals(GetSelectedItem(cb)));
};
// If the user has selected some new value in the combo box - update the attached property too
cb.SelectionChanged += delegate(object sender, SelectionChangedEventArgs e3)
{
// We only want to handle cases that actually change the selection
if(e3.AddedItems.Count == 1)
{
SetSelectedItem((DependencyObject)sender, e3.AddedItems[0]);
}
};
}
}
}
The standard
ComboBoxdoesn’t have that logic. And as you mentionedSelectedItembecomesnullalready after you callClear, so theComboBoxhas no idea about you intention to add the same item later and therefore it does nothing to select it. That being said, you will have to memorize the previously selected item manually and after you’ve updated you collection restore the selection also manually. Usually it is done something like this:If you want to apply the same behavior to all
ComboBoxes(or perhaps allSelectorcontrols), you can consider creating aBehavior(an attached property or blend behavior). This behavior will subscribe to theSelectionChangedandCollectionChangedevents and will save/restore the selected item when appropriate.