ComboBox ItemsSource 已更改 =>SelectedItem 已损坏 [英] ComboBox ItemsSource changed => SelectedItem is ruined

查看:30
本文介绍了ComboBox ItemsSource 已更改 =>SelectedItem 已损坏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好的,这已经困扰我一段时间了.我想知道其他人如何处理以下情况:

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}"/>

DataContext 对象的代码:

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;
    }
}

显然,当调用 RefreshMyItems() 方法时,组合框接收到 Collection Changed 事件,更新其项目并且在刷新的集合中找不到 SelectedItem => 将 SelectedItem 设置为 .但我需要组合框使用 Equals 方法在新集合中选择正确的项目.

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.

换句话说 - ItemsSource 集合仍然包含正确的 MyItem,但它是一个 new 对象.我希望组合框使用类似 Equals 的东西来自动选择它(这变得更加困难,因为首先源集合调用 Clear() 重置集合并且已经此时 SelectedItem 设置为 null).

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 在复制粘贴下面的代码之前,请注意它远非完美!并注意它默认不绑定两种方式.

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.

更新以防万一有人遇到同样的问题(Pavlo Glazkov 在他的回答中提出的附加财产):

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]);
                                           }
                                       };
        }

    }
}

推荐答案

标准 ComboBox 没有这种逻辑.正如你提到的 SelectedItem 在你调用 Clear 后已经变成 null,所以 ComboBox 不知道你的意图稍后添加相同的项目,因此它不会选择它.话虽如此,您必须手动记住之前选择的项目,并且在更新您的收藏后也手动恢复选择.通常这样做是这样的:

The standard ComboBox doesn't have that logic. And as you mentioned SelectedItem becomes null already after you call Clear, so the ComboBox has 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:

public void RefreshMyItems()
{
    var previouslySelectedItem = SelectedItem;

    MyItems.Clear();
    foreach(var myItem in LoadItems()) MyItems.Add(myItem);

    SelectedItem = MyItems.SingleOrDefault(i => i.Id == previouslySelectedItem.Id);

}

如果你想对所有ComboBoxes(或者可能是所有Selector控件)应用相同的行为,你可以考虑创建一个Behavior(附加属性混合行为).此行为将订阅 SelectionChangedCollectionChanged 事件,并在适当时保存/恢复所选项目.

If you want to apply the same behavior to all ComboBoxes (or perhaps all Selector controls), you can consider creating a Behavior(an attached property or blend behavior). This behavior will subscribe to the SelectionChanged and CollectionChanged events and will save/restore the selected item when appropriate.

这篇关于ComboBox ItemsSource 已更改 =>SelectedItem 已损坏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆