2011-08-09 35 views
4

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

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

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

我的第一个方法是使用一个ObservableCollection<MyviewModel>但很快一些恐怖发生:

5路multibinding其中的IMultiValueConverter我计算的价值回归和DataContext的的属性设置为值,因为我只知道运行时的前一个元素。

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

现在我的问题是,从转换器(这显然是一件坏事)设置一个值,并真正让它工作后,一个常规集合没有在其元素的顺序的概念,所以当进一步当我想在其余的中间添加一个音频部分的时候,显示器会混乱。另外,当我实现更多业务逻辑时,我可能需要访问在此转换器中计算出的音频部分的开始和结束,以及如果尚未显示音频部分的开始和结束......?????????????

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

这就是我开始使用Google进行搜索并找到约LinkedList。现在,我想做一个类,基本上是一个可观察的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 ...

所以我想我可以使重载每种方法,但听起来很讨厌...

简而言之:我需要某种集合,其中每个物品都知道前一个物品的细节,以便计算其中的一个物品自己的属性。

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

谢谢!

附录,视图模型是这样的:

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。这是一个工作,但我想没有任何优雅的解决方案,我的问题...

回答

4

好了,现在我做了一个支持IEnumerable的自定义泛型类,并且好像它是LinkedList<T>一样使用,唯一不同的是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 
    } 

正如mentionned在评论@AndrewS,一个LinkedListNode应该返回从列表属性的ObservableLinkedList一个自定义类来代替。

+0

您可能想要排除返回LinkedListNode 的方法,因为它公开公开了要包装的内部LinkedList (通过其List属性)。或者创建你自己的班级,你可以改为返回。 – AndrewS

+0

@AndrewS我为什么要这样做?访问节点时要做什么? –

+0

因为它公开地将内部LinkedList暴露给observablelinkedlist的任何用户,所以有人可以直接操纵它,并且不会导致收集更改通知。 – AndrewS

2

LinkedList<T>不是为继承而设计的:它的大多数方法都不是虚拟的,所以没有什么可以重写。如果您想重新使用其实现并实施INotifyCollectionChanged,请使用组合,而不是继承。

但无论如何,实现一个可观察的链表是不合理的,因为链表不支持索引的随机访问,并且CollectionChanged通知仅在您指定索引时才有用(除非您只提出NotifyCollectionChangedAction.Reset通知,但它不是很有效)

+0

NotifyCollectionChangedAction.Reset做的事情,无论如何,这是非常不可能的,我得到超过20段。还有其他什么缺点?我其实只是想在我订阅的CollectionChanged事件上调用OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset)。 –

+0

我想不出任何其他的缺点......只要收集很小,它不应该有性能的主要影响 –

1

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

LinkedList<T>没有实现任何链接listy接口,所以你是在你自己的方法/属性。我想一个好的指南是复制LinkedList<T>的公共方法和属性。这将允许您将集合的实际实例用作后备存储。

+0

我只是不想手动每次使用手动调用OnNotifyCollectionChanged LinkedList的方法,但在我看来,这是不好的设计,留下不应该使用的方法... –

+0

@Baboon:简单。创建一个自定义属性,详细说明方法导致的更新类型,然后使用T4模板为其成员用这些属性装饰的类型生成部分类。好吧,我猜不是那么简单(尽管我已经为类创建了自定义的TypeDescriptors,这是一个苦差事)。不幸的是,我认为没有任何简单的解决办法。但是一旦你写完了,你可以反复使用它! – Will

0

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

+0

开始时间和结束时间取决于零件的放置位置,它高度可变。 –

+0

当您移动零件时,您可以重新计算它。我的意思是......当你使用转换器和绑定时,你会怎么想?特别是如果你要使用重置事件? :)我会说一种方法遍历你的列表,并设置开始和结束时间比添加一个新的集合类和使用几个转换器要简单得多。 – Bubblewrap

0

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

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

如果你真的想要去坚果和做通用的解决方案,你可以实现一个ILinkedListNode接口,然后继承ObservableCollection<T> where T : ILinkedListNode,覆盖不同的插入和删除方法来更新项目PreviousNext性能。如果我需要解决方案是可重用的,那我就会这么做。

但是,如果没有,我只需制作一个包含集合的视图模型类,将其暴露为Items属性,然后实现插入和删除UI可以绑定到的命令。

0

如果你做了你的类是这样的:

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

你不应该有覆盖新的关键字的方法的任何问题。
注意类本身有一个类型说明符匹配您的链接列表类型。

我在这里通用,但你可以把你想要的任何东西放在那些< MyType>。 泛型只是意味着你可以在比1更多的地方使用这个东西,包括未来的项目。