2010-07-28 165 views
8

虛擬化包裝面板在WPF中使用的選項並不多。由於某種原因,MS決定不在標準庫中發貨。虛擬化WPF包裝面板問題

如果任何人都可以大膽地提供以下CodePlex項目人羣源的答案(和交代)的第一項工作,我將不勝感激:

http://virtualwrappanel.codeplex.com/workitem/1

謝謝!


總結問題:

我已經使用從該項目中的虛擬化wrappanel最近想和所遇到的錯誤。

重現步驟:

  1. 創建列表框。
  2. 將虛擬化wrappanel設置爲listboxpanel模板中的itemhost。
  3. 將列表框的itemsource綁定到可觀察集合。
  4. 從後臺可觀察集合中移除一個項目。

的Debug.Assert的失敗(Debug.Assert的(子== _children [childIndex], 「錯誤的子生成」);)中的MeasureOverride,並在清理方法一個空的異常持續執行結果[見附加截圖]。

請讓我知道,如果你能糾正這一點。

感謝,

AO


代碼:

http://virtualwrappanel.codeplex.com/SourceControl/list/changesets#

alt text http://virtualwrappanel.codeplex.com/Project/Download/AttachmentDownload.ashx?ProjectName=virtualwrappanel&WorkItemId=1&FileAttachmentId=138959

回答

4

OnItemsChanged方法需要正確處理args參數。請參閱此question以獲取更多信息。複製從這個問題的代碼,你將需要更新OnItemsChanged像這樣:

protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args) { 
    base.OnItemsChanged(sender, args); 
    _abstractPanel = null; 
    ResetScrollInfo(); 

    // ...ADD THIS... 
    switch (args.Action) { 
     case NotifyCollectionChangedAction.Remove: 
     case NotifyCollectionChangedAction.Replace: 
      RemoveInternalChildRange(args.Position.Index, args.ItemUICount); 
      break; 
     case NotifyCollectionChangedAction.Move: 
      RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount); 
      break; 
    } 
} 
0

首先,要注意,在一般情況下,如果你是從集合移除對象你沒有它的參考,那對象在移除時死亡。所以RemoveInternalChildRange調用至少在移除後是非法的,但這不是核心問題。

其次,你可能有一個小的競爭條件,即使它不是嚴格的多線程。必須檢查(帶有斷點)該事件處理程序是否過於熱切地反應 - 即使它是單個項目,也不希望事件處理程序正在運行,但仍處於刪除過程中。

三,檢查空後:

UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement; 

和一審修改代碼,有一個體面退出,在這種情況下意味着gracefull繼續 - 已在循環中使用for循環和增量能夠繼續下去。

另外檢查InternalChildren,當你看到null,看看該訪問路徑是否給出與你的_children相同的結果(如大小,內部數據,null在同一地點)。

如果只是跳過一個null生存(渲染沒有例外),在調試器中立即停止它,並檢查這些數組/集合是否已解決(內部沒有空值)。

另外,發佈完全可編譯的示例項目,它可以在某處提供repro(作爲zip文件) - 減少隨機assumprions並允許ppl構建/運行並查看。

說到假設 - 檢查你的「可觀察集合」在做什麼。 如果您要從集合中刪除項目,那麼來自該集合之前狀態的任何和每個迭代器/枚舉器都有權拋出或給出空值,並且在嘗試過於智能的UI中,可能會發生過時的迭代器容易。

8

問題

你問的什麼錯誤,以及說明如何解決的解釋說明它。到目前爲止,沒有人解釋這個問題。我會照辦的。

在具有VirtualizingWrapPanel列表框有跟蹤項目,每個項目以不同的方式五個獨立的數據結構:

  1. 的ItemsSource:原來的集合(在這種情況下的ObservableCollection)
  2. 的CollectionView:保持一個單獨的列表排序/過濾/分組項目(僅當使用這些功能中的任何一個時)
  3. ItemContainerGenerator:跟蹤項目和容器之間的映射
  4. InternalChildren:跟蹤當前可見的容器勒
  5. WrapPanelAbstraction:跟蹤哪些容器出現在哪一行

當物品從的ItemsSource去除,該去除必須通過所有的數據結構被傳播。下面是它如何工作的:

  1. 您呼籲的ItemsSource
  2. 的ItemsSource刪除()移除該項目,並觸發其CollectionChanged這是由的CollectionView
  3. 的CollectionView處理刪除的項目(如排序/過濾/分組是在使用中),並觸發其CollectionChanged其由ItemContainerGenerator
  4. ItemContainerGenerator更新其映射處理,觸發其ItemsChanged其通過VirtualizingPanel處理
  5. VirtualizingPanel調用其虛擬OnItemsChanged方法whic h由VirtualizingWrapPanel實現
  6. VirtualizingWrapPanel放棄其WrapPanelAbstraction所以將建成,但它永遠不會更新InternalChildren

正因爲如此,在InternalChildren收集了與其他四個集合同步,導致經歷過的錯誤。

解決問題

要解決此問題,任意位置添加以下代碼VirtualizingWrapPanel的OnItemsChanged方法中:

switch(args.Action) 
{ 
    case NotifyCollectionChangedAction.Remove: 
    case NotifyCollectionChangedAction.Replace: 
     RemoveInternalChildRange(args.Position.Index, args.ItemUICount); 
     break; 
    case NotifyCollectionChangedAction.Move: 
     RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount); 
     break; 
} 

這樣可以使InternalChildren集合中同步與其他數據結構。

爲什麼AddInternalChild/InsertInternalChild這裏不叫

你可能想知道爲什麼在上面的代碼InsertInternalChild或AddInternalChild沒有電話,特別是爲什麼處理更換和移動並不需要我們添加一個OnItemsChanged期間的新項目。

理解這一點的關鍵在於ItemContainerGenerator的工作方式。

當ItemContainerGenerator接收它處理的一切立即刪除事件:

  1. ItemContainerGenerator立即從自己的數據結構
  2. ItemContainerGenerator將觸發ItemChanged事件中刪除的項目。預計該小組將立即移除該容器。
  3. ItemContainerGenerator「都會使得準備」通過去除其的DataContext

在另一方面,容器,ItemContainerGenerator得知項目被添加一切通常推遲:

  1. ItemContainerGenerator立即增加了一個「槽」爲該項目在其數據結構中但不創建容器
  2. ItemContainerGenerator觸發ItemChanged事件。面板調用InvalidateMeasure()[這是由基類完成的 - 你不必這樣做]
  3. 稍後當調用MeasureOverride時,Generator.StartAt/MoveNext用於生成項容器。任何新生成的容器當時都會被添加到InternalChildren中。

因此,從InternalChildren集合(包括那些是移動的部位或更換)全部清除裏面必須要OnItemsChanged做,但增加的部分可以(也應該)被推遲,直到下一次的MeasureOverride。

+0

看起來像Tom Goff在輸入我的答案時提供了必要的代碼。他的回答也是正確的,如果沒有詳細的解釋,基本上和我一樣。 – 2010-08-18 21:15:07

+0

嗨雷 - 尼斯總結,你有我的投票。答案的一個問題是,問題不在於「InternalChildren集合與其他四個集合不同步」,但我相信它沒有幫助。 潛在的問題是,實現的孩子沒有「清理」。如果您刪除了索引爲10的項目,那麼索引爲11的項目將被移至索引10.當您在索引10(先前爲11)處實現該項目時,您將最終斷言「錯誤的孩子被生成「,因爲另一個孩子從未被實現。 – CodeNaked 2010-08-19 13:23:06