虚拟化 WPF WPF 包裹面板问题
在 WPF 中使用虚拟化环绕面板的选项并不多。由于某种原因,MS 决定不在标准库中提供一个。
如果有人能够如此大胆地为以下 Codeplex 项目的第一个工作项目提供众包答案(和解释),我将不胜感激:
http://virtualwrappanel.codeplex.com/workitem/1
谢谢!
问题摘要:
我最近尝试使用此项目中的虚拟化包装面板,但遇到了一个错误。
重现步骤:
- 创建列表框。
- 将虚拟化包装面板设置为列表框面板模板中的 itemhost。
- 将列表框的 itemsource 绑定到可观察集合。
- 从支持的可观察集合中删除一个项目。
MeasureOverride 中的 Debug.Assert 失败 (Debug.Assert(child == _children[childIndex], "Wrong child was generated");),继续执行会导致 Cleanup 方法中出现空异常 [请参阅随附的屏幕截图]。
如果您能够纠正此问题,请告诉我。
谢谢,
AO
代码:
http://virtualwrappanel.codeplex.com/SourceControl/list/changesets#< /a>
There are not very many options for a virtualizing wrap panel for use in WPF. For one reason or another MS decided to not ship one in the standard library.
If anyone could be so bold as to provide a crowd source answer (and explaination) to the first work item on the following codeplex project, I would greatly appreciate it:
http://virtualwrappanel.codeplex.com/workitem/1
Thanks!
Summary of issue:
I've recently tried using the virtualizing wrappanel from this project and have encountered a bug.
Steps to reproduce:
- Create listbox.
- Set the virtualizing wrappanel as the itemhost in a listboxpanel template.
- Bind the itemsource of the listbox to an observable collection.
- Remove an item from the backing observable collection.
The Debug.Assert fails (Debug.Assert(child == _children[childIndex], "Wrong child was generated");) in MeasureOverride, and continued execution results in a null exception in the Cleanup method [see attached screenshot].
Please let me know if you are able to correct this.
Thanks,
AO
Code:
http://virtualwrappanel.codeplex.com/SourceControl/list/changesets#
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
问题说明
您要求对出现的问题进行说明以及如何解决该问题的说明。到目前为止还没有人解释这个问题。我会这样做。
在带有 VirtualizingWrapPanel 的 ListBox 中,有五个独立的数据结构用于跟踪项目,每个数据结构都以不同的方式进行跟踪:
当从 ItemsSource 中删除项目时,此删除必须传播到所有数据结构。它的工作原理如下:
因为其中,InternalChildren 集合与其他四个集合不同步,导致出现错误。
问题的解决方案
要解决该问题,请在 VirtualizingWrapPanel 的 OnItemsChanged 方法中的任意位置添加以下代码:
这使 InternalChildren 集合与其他数据结构保持同步。
为什么这里没有调用 AddInternalChild/InsertInternalChild
你可能想知道为什么上面的代码中没有调用 InsertInternalChild 或 AddInternalChild,特别是为什么处理 Replace 和 Move 不需要我们在执行过程中添加新项项目已更改。
理解这一点的关键在于 ItemContainerGenerator 的工作方式。
当 ItemContainerGenerator 收到删除事件时,它会立即处理所有内容:
。 另一方面,ItemContainerGenerator 了解到添加项目后,所有内容通常都会被推迟:
因此,InternalChildren 集合中的所有删除(包括属于 Move 或 Replace 的部分)都必须在 OnItemsChanged 内完成,但添加可以(并且应该)推迟到下一个 MeasureOverride。
Explanation of the problem
You asked for an explanation of what is going wrong as well as instructions how to fix it. So far nobody has explained the problem. I will do so.
In ListBox with a VirtualizingWrapPanel there are five separate data structures that track items, each in different ways:
When an item is removed from ItemsSource, this removal must be propagated through all data structures. Here is how it works:
Because of this, the InternalChildren collection is out of sync with the other four collections, leading to the errors that were experienced.
Solution to the problem
To fix the problem, add the following code anywhere within VirtualizingWrapPanel's OnItemsChanged method:
This keeps the InternalChildren collection in sync with the other data structures.
Why AddInternalChild/InsertInternalChild is not called here
You may wonder why there are no calls to InsertInternalChild or AddInternalChild in the above code, and especially why handling Replace and Move don't require us to add a new item during OnItemsChanged.
The key to understanding this is in the way ItemContainerGenerator works.
When ItemContainerGenerator receives a remove event it handles everything immediately:
On the other hand, ItemContainerGenerator learns that an item is added everything is typically deferred:
Thus, all removals from the InternalChildren collection (including ones that are part of a Move or Replace) must be done inside OnItemsChanged, but additions can (and should) be deferred until the next MeasureOverride.
OnItemsChanged 方法需要正确处理 args 参数。请参阅此问题了解更多信息。复制该问题的代码,您需要像这样更新 OnItemsChanged:
The OnItemsChanged method needs to properly handle the args parameters. Please see this question for more information. Copying the code from that question, you would need to update OnItemsChanged like so:
首先,请注意,一般来说,如果要从集合中删除对象并且没有它的引用,则该对象在删除时就已死亡。因此,至少在删除后,RemoveInternalChildRange 调用是非法的,但这不是核心问题。
其次,即使不是严格的多线程,您也可能会遇到一些竞争条件。必须检查(使用断点)该事件处理程序是否反应过于急切 - 您不希望事件处理程序在您仍处于删除过程中时运行,即使它是单个项目。
第三,检查 null after:
并且对于第一次尝试,更改代码以正常退出,在本例中这意味着正常继续 - 必须使用 for 循环和循环中的增量才能完全继续。
当您看到 null 时,还要检查 InternalChildren 以查看该访问路径是否给出与您的 _children 相同的结果(如大小、内部数据、null 在同一位置)。
如果只是跳过一个空值仍然存在(渲染无异常),则立即在调试器中停止它并检查这些数组/集合是否已解决(内部没有空值)。
另外,发布完全可编译的示例项目,该项目在某处提供重现(作为 zip 文件) - 减少随机假设并允许 ppl 构建/运行并查看。
说到假设 - 检查你的“可观察集合”在做什么。 如果要从集合中删除项目,则该集合先前状态中的任何迭代器/枚举器都有权抛出或给出 null,并且在试图过于智能的 UI 中,具有过时的迭代器很容易发生。
First, beware that in general, if you are removing an object from a collection and you don't have it's reference, that object is dead at the point of removal. So at the very least RemoveInternalChildRange call is illegal after removal but that's not the core issue.
Second, you might be having a little race condition, even if it's not strictly multi-threaded. Have to check (with breakpoint) if that event handler is reacting too eagerly - you don't want event handler running while you are still in the middle of a removal even if it's a single item.
Third, check for null after:
and for the first trial change the code to have a graceful exit, which in this case means gracefull continue - have to use for loop and increments in the loop to be able to do continue at all.
Also check InternalChildren whne you see that null to see if that access path gives the same result as your _children (as in size, internal data, null in the same place).
If just skipping a null survives (renders without exceptions) stop it in debugger right after that and check if these arrays/collections got settled (no nulls inside).
Also, post the fully compilable sample project that gives the repro (as a zip file) somewhere - reduces random assumprions and allows ppl to just build/run and see.
Speaking of assumptions - check what's your "observable collection" doing. If you are removing an item from a collection, any and every iterator/enumerator from a prior state of that collection has the right to throw or give nulls and in a UI that tries to be too smart, having a stale iterator can happen easily.