可观察链表

发布于 2024-11-29 01:35:55 字数 2796 浏览 1 评论 0原文

在我的 WPF 应用程序中,我有一个 ItemsControl,其项目值取决于显示的上一个项目。

ViewModel 是一个音频文件,分为可变长度的部分,我需要以这种方式显示它,右侧显示日期时间,这就是我需要计算的内容(我只知道每个部分的长度,我需要计算它开始和结束的实际时间以及 ItemsControl 上的位置)。

--
  ----
      ------------
                  --
                    --------------------

我的第一个方法是使用 ObservableCollection 但很快就发生了一些可怕的事情:

5 路多重绑定,其中 IMultiValueConverter 我会计算要返回的值并设置一个DataContext 的属性更改为该值,因为我在运行时只知道前一个元素。

前一个元素是使用 Relativesource.PreviousData 上的绑定发送的。

现在我的问题是,在从转换器设置一个值(这显然是一件坏事)并实际让它工作之后,常规集合在其元素中没有顺序的概念,所以当进一步前进时我想在其余部分的中间添加一个音频部分,显示混乱了。

此外,当我要实现更多业务逻辑时,我可能需要访问在此转换器中计算的音频部分的开始和结束,如果尚未显示怎么办......?

所以这种方法在几个层面上都是错误的。

这就是我开始谷歌搜索并发现LinkedList的地方。现在我正在尝试创建一个基本上是 Observable LinkedList 的类(我不需要它是通用的):

public class ObservableSegmentLinkedList : LinkedList<MyViewModel>, INotifyCollectionChanged
    {
        //Overrides ???

        #region INotifyCollectionChanged Members

        public event NotifyCollectionChangedEventHandler CollectionChanged;
        public void OnNotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
            {
                CollectionChanged(this, e);
            }
        }

        #endregion
    }

问题的核心是我无法重写修改集合的方法(Addfirst、AddLast等),所以我无法正确调用 OnNotifyCollectionChanged...

所以我想我可以为这些方法中的每一个进行重载,但这听起来很讨厌...

简而言之:我需要某种集合每个项目都知道前一个的详细信息,以便计算其自身的属性之一。

有任何线索吗?这是一个好的解决方案吗?

谢谢!

附录,ViewModel 看起来像:

public class MyViewModel : INotifyPropertyChanged
    {
        private DateTime m_SegmentLength;
        public DateTime SegmentLength
        {
            get { return m_SegmentLength; }
            set
            {
                m_SegmentLength = value;
                NotifyPropertyChanged("SegmentLength");
            }
        }

        private DateTime m_SegmentAdvert;
        public DateTime SegmentAdvert
        {
            get { return m_SegmentAdvert; }
            set
            {
                m_SegmentAdvert = value;
                NotifyPropertyChanged("SegmentAdvert");
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String prop)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }

        #endregion
    }

编辑:我想我会尝试结合托马斯和威尔的答案:我将使用组合(即我在自定义对象中保留 LinkedList 的实例而不是从中继承)并重新定义意味着的方法要使用的(AddAfter、AddFirst 等),我将在调用实际的 LinkedList 方法后调用 OnNotifyPropertychanged。这是一项工作,但我想我的问题不会有任何优雅的解决方案......

In my WPF app, I have an ItemsControl whose items values are dependant upon the previous item displayed.

The ViewModel is an audio file split into parts of variable length, and i need to display it in such manner, with a DateTime displayed on the right, and that's what i need to calculate (I only know each part's length, i need to calculate the actual time it starts and ends, and the position on the ItemsControl).

--
  ----
      ------------
                  --
                    --------------------

My first approach was to use an ObservableCollection<MyviewModel> but soon enough some horrors occured :

5-way multibinding in which's IMultiValueConverter I'd calculate the value to return and set a property of the DataContext to that value, because I only knew the previous element at runtime.

The previous element was sent using a binding on Relativesource.PreviousData.

Now my problem is that after setting a value from the Converter (which is obviously a bad thing), and actually getting it to work, a regular Collection doesn't have a notion of order in its elements, so when further down the road when i want to add an audio part in the middle of the rest, the display is messed up.

Furthermore, when I'll implement more business logic, I may need to access the audio parts's start and end that are calculated in this converter, and what if it's not displayed yet...?

So that approach was wrong on several levels.

That's where i started googling and found out about LinkedList. Now I'm trying to make a class that is basically an Observable LinkedList (I don't need it to be generic):

public class ObservableSegmentLinkedList : LinkedList<MyViewModel>, INotifyCollectionChanged
    {
        //Overrides ???

        #region INotifyCollectionChanged Members

        public event NotifyCollectionChangedEventHandler CollectionChanged;
        public void OnNotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
            {
                CollectionChanged(this, e);
            }
        }

        #endregion
    }

And the heart of the problem is that i can't override the methods that modify the collection (Addfirst, AddLast etc), so i can't call OnNotifyCollectionChanged properly...

So I'm thinking i could make overloads for each of these methods, but that sounds quite nasty...

In short: I need some kind of collection in which each item knows details of the previous one in order to calculate one of its own properties.

Any clues? is this even a good solution?

Thanks!

Appendix, the ViewModel looks like:

public class MyViewModel : INotifyPropertyChanged
    {
        private DateTime m_SegmentLength;
        public DateTime SegmentLength
        {
            get { return m_SegmentLength; }
            set
            {
                m_SegmentLength = value;
                NotifyPropertyChanged("SegmentLength");
            }
        }

        private DateTime m_SegmentAdvert;
        public DateTime SegmentAdvert
        {
            get { return m_SegmentAdvert; }
            set
            {
                m_SegmentAdvert = value;
                NotifyPropertyChanged("SegmentAdvert");
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String prop)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }

        #endregion
    }

EDIT: i think i will try to combine Thomas and Will's answers: I'll use composition (i.e I keep an instance of LinkedList in my custom object instead of inheriting from it) and redefine methods that are meant to be used (AddAfter, AddFirst etc) in which i'll just call OnNotifyPropertychanged after calling the actual LinkedList method. It's a bit of work but i guess there won't be any elegant solution to my problem...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

怂人 2024-12-06 01:35:55

现在,我创建了一个支持 IEnumerable 的自定义泛型类,并且将其用作 LinkedList,唯一的区别是 WPF 会收到更改通知。

请注意,此解决方案仅适用于相当小的集合,我最多只需管理大约 30 个元素,所以这对我来说很好,但每次修改此集合时,它都被视为“重置”。

解决方案如下:

    /// <summary>
    /// This class is a LinkedList that can be used in a WPF MVVM scenario. Composition was used instead of inheritance,
    /// because inheriting from LinkedList does not allow overriding its methods.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ObservableLinkedList<T> : INotifyCollectionChanged, IEnumerable
    {
        private LinkedList<T> m_UnderLyingLinkedList;

        #region Variables accessors
        public int Count
        {
            get { return m_UnderLyingLinkedList.Count; }
        }

        public LinkedListNode<T> First
        {
            get { return m_UnderLyingLinkedList.First; }
        }

        public LinkedListNode<T> Last
        {
            get { return m_UnderLyingLinkedList.Last; }
        }
        #endregion

        #region Constructors
        public ObservableLinkedList()
        {
            m_UnderLyingLinkedList = new LinkedList<T>();
        }

        public ObservableLinkedList(IEnumerable<T> collection)
        {
            m_UnderLyingLinkedList = new LinkedList<T>(collection);
        }
        #endregion

        #region LinkedList<T> Composition
        public LinkedListNode<T> AddAfter(LinkedListNode<T> prevNode, T value)
        {
            LinkedListNode<T> ret = m_UnderLyingLinkedList.AddAfter(prevNode, value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode)
        {
            m_UnderLyingLinkedList.AddAfter(node, newNode);
            OnNotifyCollectionChanged();
        }

        public LinkedListNode<T> AddBefore(LinkedListNode<T> node, T value)
        {
            LinkedListNode<T> ret = m_UnderLyingLinkedList.AddBefore(node, value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
        {
            m_UnderLyingLinkedList.AddBefore(node, newNode);
            OnNotifyCollectionChanged();
        }

        public LinkedListNode<T> AddFirst(T value)
        {
            LinkedListNode<T> ret = m_UnderLyingLinkedList.AddFirst(value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void AddFirst(LinkedListNode<T> node)
        {
            m_UnderLyingLinkedList.AddFirst(node);
            OnNotifyCollectionChanged();
        }

        public LinkedListNode<T> AddLast(T value)
        {
            LinkedListNode<T> ret = m_UnderLyingLinkedList.AddLast(value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void AddLast(LinkedListNode<T> node)
        {
            m_UnderLyingLinkedList.AddLast(node);
            OnNotifyCollectionChanged();
        }

        public void Clear()
        {
            m_UnderLyingLinkedList.Clear();
            OnNotifyCollectionChanged();
        }

        public bool Contains(T value)
        {
            return m_UnderLyingLinkedList.Contains(value);
        }

        public void CopyTo(T[] array, int index)
        {
            m_UnderLyingLinkedList.CopyTo(array, index);
        }

        public bool LinkedListEquals(object obj)
        {
            return m_UnderLyingLinkedList.Equals(obj);
        }

        public LinkedListNode<T> Find(T value)
        {
            return m_UnderLyingLinkedList.Find(value);
        }

        public LinkedListNode<T> FindLast(T value)
        {
            return m_UnderLyingLinkedList.FindLast(value);
        }

        public Type GetLinkedListType()
        {
            return m_UnderLyingLinkedList.GetType();
        }

        public bool Remove(T value)
        {
            bool ret = m_UnderLyingLinkedList.Remove(value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void Remove(LinkedListNode<T> node)
        {
            m_UnderLyingLinkedList.Remove(node);
            OnNotifyCollectionChanged();
        }

        public void RemoveFirst()
        {
            m_UnderLyingLinkedList.RemoveFirst();
            OnNotifyCollectionChanged();
        }

        public void RemoveLast()
        {
            m_UnderLyingLinkedList.RemoveLast();
            OnNotifyCollectionChanged();
        }
        #endregion

        #region INotifyCollectionChanged Members

        public event NotifyCollectionChangedEventHandler CollectionChanged;
        public void OnNotifyCollectionChanged()
        {
            if (CollectionChanged != null)
            {
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (m_UnderLyingLinkedList as IEnumerable).GetEnumerator();
        }

        #endregion
    }

正如 @AndrewS 在评论中提到的,LinkedListNode 应该替换为从其 List 属性返回 ObservableLinkedList 的自定义类。

Ok now, I made a custom generic class that supports IEnumerable and is used as if it was a LinkedList<T>, with the only difference that WPF gets notified of the changes.

Please note that this solution only works for a reasonably small collection, I only have to manage around 30 elements max, so it's fine for me, but everytime you modify this collection, it is considered "Reset".

Here goes the solution:

    /// <summary>
    /// This class is a LinkedList that can be used in a WPF MVVM scenario. Composition was used instead of inheritance,
    /// because inheriting from LinkedList does not allow overriding its methods.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ObservableLinkedList<T> : INotifyCollectionChanged, IEnumerable
    {
        private LinkedList<T> m_UnderLyingLinkedList;

        #region Variables accessors
        public int Count
        {
            get { return m_UnderLyingLinkedList.Count; }
        }

        public LinkedListNode<T> First
        {
            get { return m_UnderLyingLinkedList.First; }
        }

        public LinkedListNode<T> Last
        {
            get { return m_UnderLyingLinkedList.Last; }
        }
        #endregion

        #region Constructors
        public ObservableLinkedList()
        {
            m_UnderLyingLinkedList = new LinkedList<T>();
        }

        public ObservableLinkedList(IEnumerable<T> collection)
        {
            m_UnderLyingLinkedList = new LinkedList<T>(collection);
        }
        #endregion

        #region LinkedList<T> Composition
        public LinkedListNode<T> AddAfter(LinkedListNode<T> prevNode, T value)
        {
            LinkedListNode<T> ret = m_UnderLyingLinkedList.AddAfter(prevNode, value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode)
        {
            m_UnderLyingLinkedList.AddAfter(node, newNode);
            OnNotifyCollectionChanged();
        }

        public LinkedListNode<T> AddBefore(LinkedListNode<T> node, T value)
        {
            LinkedListNode<T> ret = m_UnderLyingLinkedList.AddBefore(node, value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)
        {
            m_UnderLyingLinkedList.AddBefore(node, newNode);
            OnNotifyCollectionChanged();
        }

        public LinkedListNode<T> AddFirst(T value)
        {
            LinkedListNode<T> ret = m_UnderLyingLinkedList.AddFirst(value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void AddFirst(LinkedListNode<T> node)
        {
            m_UnderLyingLinkedList.AddFirst(node);
            OnNotifyCollectionChanged();
        }

        public LinkedListNode<T> AddLast(T value)
        {
            LinkedListNode<T> ret = m_UnderLyingLinkedList.AddLast(value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void AddLast(LinkedListNode<T> node)
        {
            m_UnderLyingLinkedList.AddLast(node);
            OnNotifyCollectionChanged();
        }

        public void Clear()
        {
            m_UnderLyingLinkedList.Clear();
            OnNotifyCollectionChanged();
        }

        public bool Contains(T value)
        {
            return m_UnderLyingLinkedList.Contains(value);
        }

        public void CopyTo(T[] array, int index)
        {
            m_UnderLyingLinkedList.CopyTo(array, index);
        }

        public bool LinkedListEquals(object obj)
        {
            return m_UnderLyingLinkedList.Equals(obj);
        }

        public LinkedListNode<T> Find(T value)
        {
            return m_UnderLyingLinkedList.Find(value);
        }

        public LinkedListNode<T> FindLast(T value)
        {
            return m_UnderLyingLinkedList.FindLast(value);
        }

        public Type GetLinkedListType()
        {
            return m_UnderLyingLinkedList.GetType();
        }

        public bool Remove(T value)
        {
            bool ret = m_UnderLyingLinkedList.Remove(value);
            OnNotifyCollectionChanged();
            return ret;
        }

        public void Remove(LinkedListNode<T> node)
        {
            m_UnderLyingLinkedList.Remove(node);
            OnNotifyCollectionChanged();
        }

        public void RemoveFirst()
        {
            m_UnderLyingLinkedList.RemoveFirst();
            OnNotifyCollectionChanged();
        }

        public void RemoveLast()
        {
            m_UnderLyingLinkedList.RemoveLast();
            OnNotifyCollectionChanged();
        }
        #endregion

        #region INotifyCollectionChanged Members

        public event NotifyCollectionChangedEventHandler CollectionChanged;
        public void OnNotifyCollectionChanged()
        {
            if (CollectionChanged != null)
            {
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (m_UnderLyingLinkedList as IEnumerable).GetEnumerator();
        }

        #endregion
    }

As mentionned by @AndrewS in the comments, LinkedListNode should be replaced with a custom class that returns an ObservableLinkedList from its List property.

夜巴黎 2024-12-06 01:35:55

LinkedList 不是为继承而设计的:它的大多数方法都不是虚拟的,因此无需重写任何内容。如果您想重用其实现并实现 INotifyCollectionChanged,请使用组合,而不是继承。

但无论如何,实现一个可观察的链表是没有意义的,因为链表不支持通过索引进行随机访问,并且 CollectionChanged 通知仅在您指定索引时才有用(除非您只引发 NotifyCollectionChangedAction.Reset 通知,但效率不高)

LinkedList<T> isn't designed for inheritance: most of its methods are not virtual, so there is nothing to override. If you want to reuse its implementation and implement INotifyCollectionChanged, use composition, not inheritance.

But anyway, it wouldn't make sense to implement an observable linked list, because a linked list doesn't support random access by index, and CollectionChanged notifications are only useful if you specify an index (unless you only raise NotifyCollectionChangedAction.Reset notifications, but then it's not very efficient)

仙气飘飘 2024-12-06 01:35:55

这是一个很好的解决方案,您只需创建自己的 LinkedList 实现即可。

LinkedList 不实现任何链接列表接口,因此您需要自行决定方法/属性。我认为一个好的指南是复制 LinkedList 的公共方法和属性。这将允许您使用集合的实际实例作为后备存储。

It is a good solution, you'll just have to create your own implementation of a LinkedList.

LinkedList<T> does not implement any linky listy interface, so you are on your own as to the methods/properties. I suppose a good guide would be to duplicate the public methods and properties of LinkedList<T>. This would allow you to use an actual instance of the collection as your backing store.

妄想挽回 2024-12-06 01:35:55

我认为最终更简单的解决方案是预先计算开始和结束时间并将它们添加为 ViewModel 上的属性。特别是因为您说您的业务逻辑中可能需要这个值。

I think that eventually the simpler solution would be to calculate the start and end time beforehand and add them as properties on the ViewModel. Especially since you say that you may need this value in your business logic.

無心 2024-12-06 01:35:55

在我看来,你有两个不同的问题。一个是管理要显示的项目列表,另一个是允许项目访问其前面和后面的项目。

这就是我的处理方法:将 PreviousNext 属性添加到项目类中,在最初填充集合时设置它们,然后在插入和删除项目时更新它们从列表中。

如果您真的想疯狂地制定一个通用解决方案,您可以实现一个 ILinkedListNode 接口,然后将 ObservableCollection子类化。其中 T :ILinkedListNode,重写各种插入和删除方法以更新项目的 PreviousNext 属性。如果我需要可重复使用的解决方案,我就会这么做。

但如果没有,我只需创建一个视图模型类来包装该集合,将其公开为 Items 属性,然后实现 UI 可以绑定到的插入和删除命令。

It sounds to me like you have two different issues. One is managing a list of items for display, and the other is allowing an item to access its preceding and following items.

That's how I'd approach it: add Previous and Next properties to the item class, set them when initially populating the collection, and then updating them when I insert and remove items from the list.

If you really want to go nuts and make a generic solution, you could implement an ILinkedListNode interface, and then subclass ObservableCollection<T> where T : ILinkedListNode, overriding the various insert and remove methods to update the items' Previous and Next properties. That's what I'd do if I needed the solution to be reusable.

But if not, I'd just make a view model class that wrapped the collection, exposing it as an Items property, and then implemented insert and remove commands that the UI can bind to.

眼波传意 2024-12-06 01:35:55

如果你的类是这样的:

public class ObservableSegmentLinkedList<T> : LinkedList<T>, INotifyCollectionChanged 
{
...
public new void AddFirst(T value)
{
.. do something to make it your own - you can still call the base method so in effect you override it with the new keyword.
}
}

用 new 关键字覆盖方法就不会有任何问题。
请注意,类本身有一个与您的链接列表类型相匹配的类型说明符。

我在这里将其设为通用,但您可以在这些 < 之间放置任何您想要的内容。我的类型 >
通用只是意味着你可以在比 1 更多的地方使用这个东西,包括未来的项目。

If you had made your class like this:

public class ObservableSegmentLinkedList<T> : LinkedList<T>, INotifyCollectionChanged 
{
...
public new void AddFirst(T value)
{
.. do something to make it your own - you can still call the base method so in effect you override it with the new keyword.
}
}

You should not have any problem overriding the methods with new keyword.
Notice the Class itself has a TYPE Specifier that matches your Linked List Type.

I made it generic here but you can put whatever you want between those < MyType > .
Generic just means you can use this thing in a lot more places than 1 including a future project.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文