2011-08-09 82 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更多的地方使用這個東西,包括未來的項目。