如何在WPF中绑定SortedSet

本文关键字:绑定 SortedSet WPF | 更新日期: 2023-09-27 18:20:20

我正在构建一个WPF应用程序,它包含三个绑定到ISet、SortedSet或自定义类的ListBox元素(不确定)。我是根据以前的Java经验得出这个结论的,因为我需要以下约束:

  • 需要对添加进行排序,同时考虑性能
  • 不应包含重复项

在访问MSDN查看ObservableCollection使用的接口后,我想我会做这样的事情:

public class ObservableSortedSet<T> : SortedSet<T>, INotifyCollectionChanged
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    public override bool Add(T item)
    {
        bool result = base.Add(item);
        OnCollectionChanged(NotifyCollectionChangedAction.Add);
        return true;
    }
    private void OnCollectionChanged(NotifyCollectionChangedAction action)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(action));
    }
}

.NET中的ObservableCollection类激发了我制作ObservableSortedSet的灵感,但显然在C#中,你使用了virtual、override和new关键字,所以由于SortedSet没有虚拟添加方法,我不能像上面那样做所以我现在的问题是,我该如何制作一个SortedSet,或者什么.NET类可以提供我需要的功能我想我可以创建自己的SortedSet类,但这似乎应该在.NET框架中。

更新如果有人想要像我上面描述的那样的东西,我就去做了一个。我已经在我的项目中使用它一段时间了,它看起来相当稳定。我采用了一个很久以前制作的链表实现,并将其作为一个排序集进行了定制。这让我可以快速地进行分类。

这是代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Conquest.Collections {
    public class OrderedSet<T> : IEnumerable<T>, INotifyCollectionChanged where T : IComparable<T> {
        private Node<T> head;
        private Node<T> tail;
        public OrderedSet() {
            Count = 0;
        }
        public OrderedSet(IEnumerable<T> enumerable) {
            Count = 0;
            foreach (T element in enumerable)
                Add(element);
        }
        public T First {
            get { return (head == null) ? default(T) : head.Datum; }
        }
        public T Last {
            get { return (tail == null) ? default(T) : tail.Datum; }
        }
        public int Count { get; private set; }
        public IEnumerator<T> GetEnumerator() {
            return new OrderedSetEnumerator<T>(this);
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return new OrderedSetEnumerator<T>(this);
        }
        private void InsertNodeBefore(Node<T> insertingNode, Node<T> here) {
            insertingNode.Next = here;
            insertingNode.Previous = here.Previous;
            if (here == head)
                head = insertingNode;
            else
                here.Previous.Next = insertingNode;
            here.Previous = insertingNode;
            insertingNode.Index = here.Index;
            for (Node<T> n = here; n != null; n = n.Next)
                n.Index++;
        }
        private void InsertNodeAfter(Node<T> insertNode, Node<T> here) {
            insertNode.Previous = here;
            insertNode.Next = here.Next;
            if (here == tail)
                tail = insertNode;
            else
                here.Next.Previous = insertNode;
            here.Next = insertNode;
            insertNode.Index = here.Index + 1;
            for (Node<T> n = insertNode.Next; n != null; n = n.Next)
                n.Index++;
        }
        private bool IsNodeSorted(Node<T> node) {
            if (Count == 1)
                return true;
            return (node == head && node.Next.Datum.CompareTo(node.Datum) > 0) ||
                   (node == tail && node.Previous.Datum.CompareTo(node.Datum) < 0) ||
                   (node != tail && node != head && node.Next.Datum.CompareTo(node.Datum) > 0 &&
                    node.Previous.Datum.CompareTo(node.Datum) < 0);
        }
        private void RemoveNode(Node<T> node) {
            if (node == head)
                head = node.Next;
            else
                node.Previous.Next = node.Next;
            if (node == tail)
                tail = node.Previous;
            else
                node.Next.Previous = node.Previous;
            for (Node<T> n = node.Next; n != null; n = n.Next)
                n.Index--;
            Count--;
        }
        private void SortNodeLeft(Node<T> node) {
            // Unlink
            RemoveNode(node);
            Count++;
            for (Node<T> currentNode = node.Previous; currentNode != null; currentNode = currentNode.Previous) {
                int compareResult = currentNode.Datum.CompareTo(node.Datum);
                if (compareResult < 0) {
                    // Link
                    InsertNodeAfter(node, currentNode);
                    return;
                }
            }
            InsertNodeBefore(node, head);
        }
        private void SortNodeRight(Node<T> node) {
            // Unlink
            RemoveNode(node);
            Count++;
            for (Node<T> currentNode = node.Next; currentNode != null; currentNode = currentNode.Next) {
                int compareResult = currentNode.Datum.CompareTo(node.Datum);
                if (compareResult > 0) {
                    // Link
                    InsertNodeBefore(node, currentNode);
                    return;
                }
            }
            InsertNodeAfter(node, tail);
        }
        public bool Add(T item) {
            var node = new Node<T>(item);
            int index = 0;
            if (head == null) {
                head = node;
                tail = head;
            }
            else {
                for (Node<T> currentNode = head; currentNode != null; currentNode = currentNode.Next, index++) {
                    int compareResult = currentNode.Datum.CompareTo(item);
                    if (compareResult == 0)
                        return false;
                    else if (compareResult > 0) {
                        InsertNodeBefore(node, currentNode);
                        break;
                    }
                        // if so, make node the new tail
                    else if (currentNode == tail) {
                        InsertNodeAfter(node, tail);
                        index++;
                        break;
                    }
                }
            }
            Count++;
            NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
            node.Index = index;
            node.PropertyChanged += Sort;
            return true;
        }
        /// <summary>
        ///     If the nodes datum changes state, this function ensures
        ///     the set remains sorted.
        /// </summary>
        /// <param name="sender">The node</param>
        /// <param name="e">The property that changed</param>
        private void Sort(object sender, PropertyChangedEventArgs e) {
            var node = (Node<T>) sender;
            int index = node.Index;
            // Check if node is still in correct spot
            if (IsNodeSorted(node))
                return;
            if (node.Previous == null || (node.Previous.Datum.CompareTo(node.Datum) < 0))
                SortNodeRight(node);
            else
                SortNodeLeft(node);
            NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, node.Datum,
                                                                         node.Index, index));
        }
        public bool Remove(T item) {
            if (head == null)
                return false;
            for (Node<T> node = head; node != null; node = node.Next) {
                if (node.Datum.CompareTo(item) == 0) {
                    RemoveNode(node);
                    NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
                                                                                 item, node.Index));
                    return true;
                }
            }
            return false;
        }
        #region INotifyCollectionChanged
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs args) {
            if (CollectionChanged != null)
                CollectionChanged(this, args);
        }
        #endregion
        #region Enumerator
        private class OrderedSetEnumerator<U> : IEnumerator<U> where U : IComparable<U> {
            private readonly OrderedSet<U> set;
            private OrderedSet<U>.Node<U> current;
            public OrderedSetEnumerator(OrderedSet<U> set) {
                this.set = set;
                current = null;
            }
            public U Current {
                get { return (current == null) ? default(U) : current.Datum; }
            }
            Object IEnumerator.Current {
                get { return (current == null) ? null : (Object) current.Datum; }
            }
            public bool MoveNext() {
                current = current == null ? set.head : current.Next;
                return (current != null);
            }
            public void Reset() {
                current = null;
            }
            public void Dispose() {
                current = null;
            }
        }
        #endregion
        private class Node<U> : INotifyPropertyChanged {
            public Node(U datum) {
                Datum = datum;
                var flagObserveChanges = datum as INotifyPropertyChanged;
                if (flagObserveChanges != null)
                    flagObserveChanges.PropertyChanged += NotifySet;
            }
            public Node<U> Next { get; set; }
            public Node<U> Previous { get; set; }
            public int Index { get; set; }
            public U Datum { get; set; }
            public event PropertyChangedEventHandler PropertyChanged;
            private void NotifySet(object sender, PropertyChangedEventArgs e) {
                if (PropertyChanged != null)
                    PropertyChanged(this, e);
            }
        }
    }
}

当然,我做了一些简单的测试用例,所以你可能想扩展:)

using System.Linq;
using Conquest.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.ComponentModel;
using System;
namespace Conquest.Test.Collections
{
    [TestClass]
    public class OrderedSetTest
    {
        private class Department : INotifyPropertyChanged, IComparable<Department> {
            private string name;
            public string Name {
                get { return name; }
                set {
                    name = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Name"));
                }
            }
            public event PropertyChangedEventHandler PropertyChanged;
            public int CompareTo(Department other) {
                return String.Compare(this.Name, other.Name, StringComparison.OrdinalIgnoreCase);
            }
        }
        [TestMethod]
        public void TestAddSingleItem()
        {
            var set = new OrderedSet<int> {1};
            Assert.AreEqual(set.First, 1, "Head points to correct location");
            Assert.AreEqual(set.Count, 1, "Correct Size");
            Assert.AreEqual(set.Last, 1, "Tail points to correct location");
        }
        [TestMethod]
        public void TestAddHead()
        {
            var set = new OrderedSet<int> {2, 1};
            Assert.AreEqual(set.First, 1, "Head points to correct value");
        }
        [TestMethod]
        public void TestAddTail()
        {
            var set = new OrderedSet<int> {1, 2};
            Assert.AreEqual(set.Last, 2, "Tail points to correct value");
            Assert.AreEqual(set.Count, 2, "Correct Size");
        }
        [TestMethod]
        public void TestAddDuplicationItems()
        {
            var set = new OrderedSet<int> {1, 1};
            Assert.IsTrue(1 == set.Count);
        }
        [TestMethod]
        public void TestAddMultipleItems()
        {
            var set = new OrderedSet<int> {3, 2, 4, 1, 5};
            int expected = 1;
            bool worked = true;
            foreach (int i in set)
            {
                if (i != expected)
                {
                    worked = false;
                    break;
                }
                expected++;
            }
            Assert.IsTrue(worked, "Multiple Items");
        }
        [TestMethod]
        public void TestRemoveSingleItem()
        {
            var set = new OrderedSet<int> {1};
            set.Remove(1);
            Assert.AreEqual(set.Count, 0, "Removed single item");
            Assert.AreEqual(set.First, 0, "Head does not point to anything");
            Assert.AreEqual(set.Last, 0, "Tail does not point to anything");
        }
        [TestMethod]
        public void TestRemoveHead()
        {
            var set = new OrderedSet<int> {1, 2};
            set.Remove(1);
            Assert.AreEqual(set.Count, 1, "Removed head with more than one item in set");
            Assert.AreEqual(set.First, 2, "Head points to the next value in set");
        }
        [TestMethod]
        public void TestRemoveTail()
        {
            var set = new OrderedSet<int> {1, 2};
            set.Remove(2);
            Assert.AreEqual(set.Count, 1, "Removed tail with more than one item in set");
            Assert.AreEqual(set.Last, 1, "Tail points to the previous value in set");
        }
        [TestMethod]
        public void TestRemoveItem()
        {
            var set = new OrderedSet<int> {1, 2, 3};
            Assert.IsTrue(set.Remove(2), "Remove correct value");
            Assert.IsTrue(set.Count == 2, "Removed item");
            int sum = set.Sum();
            Assert.AreEqual(4, sum, "Remove correctly relinked adjacent nodes");
        }
        [TestMethod]
        public void TestSortOnAdd()
        {
            var test = new Queue<int>();
            for (int i = 0; i < 10; i++)
                test.Enqueue(i);
            var set = new OrderedSet<int> {5, 4, 3, 7, 2, 8, 1, 9, 0, 6};
            var worked = set.All(i => i == test.Dequeue());
            Assert.IsTrue(worked, "Sorted on Add");
        }
        [TestMethod]
        public void TestSortOnEdit()
        {
            var dep1 = new Department
            {
                Name = "Test"
            };
            var dep2 = new Department
            {
                Name = "Test2"
            };
            var set = new OrderedSet<Department> {dep1, dep2};
            dep2.Name = "Hello";
            var e = set.GetEnumerator();
            e.MoveNext();
            Assert.AreEqual("Hello", e.Current.Name, "Swaped tail to head on edit");
            dep1.Name = "Abc";
            e = set.GetEnumerator();
            e.MoveNext();
            Assert.AreEqual("Abc", e.Current.Name, "Verified integrity of node linkage");
            var dep3 = new Department
            {
                Name = "Test3"
            };
            set.Add(dep3);
            dep3.Name = "Cat";
            e = set.GetEnumerator();
            e.MoveNext();
            bool correctOrder = e.Current.Name == "Abc";
            e.MoveNext();
            correctOrder = correctOrder && e.Current.Name == "Cat";
            Assert.IsTrue(correctOrder, "Moved item to the left");
            dep1.Name = "Dad";
            e = set.GetEnumerator();
            e.MoveNext();
            correctOrder = e.Current.Name == "Cat";
            e.MoveNext();
            correctOrder = correctOrder && e.Current.Name == "Dad";
            Assert.IsTrue(correctOrder, "Moved item to the right");
        }
    }
}

如何在WPF中绑定SortedSet

您可以更改您的类以具有此声明。。。

public class ObservableSortedSet<T> : INotifyCollectionChanged, ISet<T>
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    private readonly SortedSet<T> _sortedSet;
    public ObservableSortedSet()
    {
        _sortedSet = new SortedSet<T>();
    }
    public bool Add(T item)
    {
        bool result = _sortedSet.Add(item);
        OnCollectionChanged(NotifyCollectionChangedAction.Add);
        return true;
    }
    private void OnCollectionChanged(NotifyCollectionChangedAction action)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(action));
    }
    // all the rest of ISet implementation goes here...
}

这种方法虽然更为冗长,而且可以说是夸大其词,但会给你带来想要的行为。编码演练大约需要20分钟。