【问题标题】:Implementing INotifyCollectionChanged on a collection without indexes在没有索引的集合上实现 INotifyCollectionChanged
【发布时间】:2011-06-06 16:56:26
【问题描述】:

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

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

以下是相关代码的摘录:

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
}

【问题讨论】:

  • 您是否尝试过始终给出 -1 的位置?
  • 根据调试默认为-1。我发现其他人收到类似错误的一些帖子指出该位置必须是 int >= 0 和
  • 由于这个确切的问题,我可以告诉你,我们小组在 ObservableDictionary 上的尝试是“非法的”,而是使用派生的 ObservableCollection 添加确保唯一性所需的业务逻辑(通过某些属性/功能) .
  • 您是否尝试过使用 .ToList()(在 LINQ 中)将字典转换为列表并以这种方式获取索引?
  • @Camron - 我想到了按照这些思路做一些事情,并且可能会奏效,但我希望有一些更优雅/高效的东西。

标签: c# wpf


【解决方案1】:

您的自定义集合似乎是KeyedCollection&lt;TKey,TItem&gt; 的重新发明,它在内部使用字典,并且 具有索引。如果TKey 是基于intint 的枚举,则int 索引的索引器可以隐藏,但this can be fixed

至于让KeyedCollection 与WPF 一起工作,我发现this article,其中他基本上通过实现INotifyCollectionChanged 并覆盖SetItem()InsertItem()ClearItems() 和@987654334 来创建ObservableKeyedCollection&lt;TKey,TItem&gt; @,以及添加 AddRange() 并将 Func&lt;TItem,TKey&gt; 传递给构造函数,以便从 TItem 获取 TKey

【讨论】:

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

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

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

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

【讨论】:

  • 我对 LINQ 的经验有限。我将不得不做一些研究来了解你在这里所做的事情。
  • 这非常简单。 Select 只是转换集合中的项目并返回一个新集合,所以在这种情况下,我说(有效地)'对于键中的每个键,返回一个带有键及其索引的新对象'(在第一行中)。第二行只是说,'给我一个 key 等于 name 的对象,然后返回该对象的 idx 属性)。
  • 因为键/值的顺序不可靠并且可能会改变这可能导致意外/不一致的结果
【解决方案3】:

所以我通过将删除事件更改为重置来进行临时修改,然后开始处理我的代码的其他一些区域。当我回到这个问题时,我发现/意识到 SortedList 类将满足我的要求,并允许我正确实现 Collection Changed 事件,而对现有代码的更改最少。

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

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

感谢大家的建议和 cmets,希望这个帖子可以帮助其他人。

【讨论】:

    【解决方案4】:

    我能够使用NotifyCollectionChangedAction.Replace 操作和一个空的NewItems 列表为非索引集合成功引发CollectionChanged 事件。

    【讨论】:

    • 它不起作用。该项目已从集合中移除,但在 UI 上不受影响。你能分享一个你是如何做到这一点的例子吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-31
    • 1970-01-01
    • 1970-01-01
    • 2015-05-14
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    相关资源
    最近更新 更多