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]);
}
};
}
}
}
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…