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:
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#
alt text http://virtualwrappanel.codeplex.com/Project/Download/AttachmentDownload.ashx?ProjectName=virtualwrappanel&WorkItemId=1&FileAttachmentId=138959
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:
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;
}
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.
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:
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With