Хорошо, это уже некоторое время подтачивало меня. И мне интересно, как другие обрабатывают следующий случай:
<ComboBox ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem}"/>
Объектный код DataContext:
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;
}
}
Очевидно, что когда метод RefreshMyItems()
вызывается, поле со списком принимает события Collection Changed, обновляет его элементы и не находит SelectedItem в обновленной коллекции = > устанавливает для SelectedItem значение null
. Но мне понадобится поле со списком, чтобы использовать метод Equals
, чтобы выбрать правильный элемент в новой коллекции.
Другими словами - коллекция ItemsSource по-прежнему содержит правильный MyItem
, но это объект new
. И я хочу, чтобы поле со списком использовало что-то вроде Equals
для его автоматического выбора (это делается еще сложнее, потому что сначала коллекция источников вызывает Clear()
, которая сбрасывает коллекцию, и уже в этот момент значение SelectedItem установлено на null
)..
ОБНОВЛЕНИЕ 2 Перед тем, как скопировать код ниже, обратите внимание, что он далеко не совершенен! И обратите внимание, что по умолчанию он не связывает два пути.
ОБНОВЛЕНИЕ На всякий случай у кого-то такая же проблема (вложенное свойство, предложенное Павлом Глазковым в его ответе):
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]);
}
};
}
}
}