问题 在没有索引的集合上实现INotifyCollectionChanged


在ASP.Net专门工作了几年之后,我刚刚在WPF中弄湿了脚趾。我目前正在努力解决的问题是我有一个自定义集合类,我需要绑定到列表框。除了从集合中删除项目之外,一切似乎都在起作用。当我试图得到错误时: “Collection Remove event must specify item position.”  问题是这个集合没有使用索引,所以我没有看到指定位置的方法,到目前为止谷歌没有向我展示一个可行的解决方案......

该类被定义为实现 ICollection<> 和 INotifyCollectionChanged。我的内部物品容器是 Dictionary 它使用项目的名称(字符串)值作为键。除了这两个接口定义的方法之外,此集合还有一个索引器,允许通过Name访问项目,并覆盖 Contains 和 Remove 方法,以便也可以使用项目Name调用它们。这适用于添加和编辑,但在我尝试删除时会抛出上述异常。

以下是相关代码的摘录:

class Foo
{
    public string Name
    {
        get;
        set;
    }
}
class FooCollection : ICollection<Foo>, INotifyCollectionChanged
{
    Dictionary<string, Foo> Items;

    public FooCollection()
    {
        Items = new Dictionary<string, Foo>();
    }

    #region ICollection<Foo> Members

    //***REMOVED FOR BREVITY***

    public bool Remove(Foo item)
    {
        return this.Remove(item.Name);
    }
    public bool Remove(string name)
    {
        bool Value = this.Contains(name);
        if (Value)
        {
            NotifyCollectionChangedEventArgs E = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, Items[name]);
            Value = Items.Remove(name);
            if (Value)
            {
                RaiseCollectionChanged(E);
            }
        }
        return Value;
    }
    #endregion

    #region INotifyCollectionChanged Members
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    private void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
        {
            CollectionChanged(this, e);
        }
    }
    #endregion
}

6788
2018-06-06 16:56


起源

你是否尝试过始终给出-1的位置? - user7116
根据调试,它默认为-1。我发现其他人收到类似错误的一些帖子表明该位置必须是int> = 0和<collection size。我只是不确定如何找到字典中的值的索引... - Rozwel
由于这个确切的问题,我可以告诉你,我们的团队已经“禁止”ObservableDictionary的尝试,而是使用派生的ObservableCollection,它添加了确保唯一性所需的业务逻辑(通过某些属性/函数)。 - user7116
您是否尝试使用.ToList()(在LINQ中)将字典转换为字典并以此方式获取索引? - CamronBute
@Camron - 想到在这些方面做某些事情已经发生在我身上,并且可能会奏效,但我希望有一些更优雅/更有效的东西。 - Rozwel


答案:


您的自定义集合似乎是重新发明的 KeyedCollection<TKey,TItem>,内部使用字典,  有索引。索引器 int 索引可以隐藏起来 TKey 是 int 要么 int基于enum,但是 这可以修复

至于制作 KeyedCollection 我发现,与WPF合作 本文,他基本上是一个 ObservableKeyedCollection<TKey,TItem> 通过实施 INotifyCollectionChanged 并压倒一切 SetItem()InsertItem()ClearItems(),和 RemoveItem(),以及添加 AddRange() 并通过 Func<TItem,TKey> 得到的构造函数 TKey 从一个 TItem


6
2018-06-08 19:50



不知怎的,我从来没有意识到那个班级。是的,它与我正在做的非常相似,看起来它应该也能正常工作。我可以想到很多地方我编写了这种类型的结构,这可能有利于使用KeyedCollection作为基础,但在这种情况下,我认为SortedList稍微适合。 - Rozwel
所以我在我的应用程序的另一部分遇到了一些问题,导致我回溯并改变了我的框架设计。在这个过程中,我继续前进,转而使用KeyedCollection,其实现与第二个链接中的实现非常相似。最后,这似乎是我正在做的更好的解决方案。 - Rozwel


需要一点间接,但你可以用Linq来做。不包括错误处理你可以这样做:

var items = dict.Keys.Select((k, i) => new { idx = i, key = k });
var index = items.FirstOrDefault(f => f.key == name).idx;

只要保持一致,您也可以使用值而不是键。


2
2018-06-06 17:28



我对LINQ的经验有限。我将不得不做一些研究来跟踪你在这里做的事情。 - Rozwel
这令人惊讶地直截了当。 Select只是转换集合中的项目并返回一个新的集合,所以在这种情况下,我(有效地)为密钥中的每个密钥说,返回一个带有密钥及其索引的新对象(在第一行)。第二行只是说,'给我一个键等于name的对象,然后返回该对象的idx属性)。 - Paul
因为键/值的顺序不可靠并且可能改变,这可能导致意外/不一致的结果 - mike


所以我通过将remove事件更改为重置来暂时破解,并开始处理我的代码的其他一些区域。当我回到这个问题时,我发现/意识到了 排序列表 class将满足我的要求,并允许我正确地实现Collection Changed事件,只需对现有代码进行最少的更改。

对于那些不熟悉这个课程的人(我之前从未使用过),这里有一个基于我迄今为止所做的阅读的快速总结。在大多数情况下,它看起来像字典,尽管内部结构不同。此集合维护键和值的排序列表,而不是哈希表。这意味着将数据输入和输出集合会产生更多的开销,但其内存消耗较低。这种差异的显着程度似乎取决于您需要存储多少数据以及您为密钥使用的数据类型。

由于我在这个实例中的数据量相对较低,并且我需要将列表框中的项目按其名称值排序,因此在我的情况下使用此类似乎是一个很好的答案。如果有人争论为什么不应该使用这个课程,请告诉我。

感谢所有人的建议和意见,希望这个主题可以帮助其他人。


2
2018-06-08 19:39





我能够使用 NotifyCollectionChangedAction.Replace 一个空的行动 NewItems 列表提高 CollectionChanged 事件已成功用于非索引集合。


0
2017-10-18 21:33



它不起作用。该项目已从集合中删除,但在UI上不受影响。你能否分享一个关于你如何实现这个目标的例子? - Shimmy