I'm attempting to create a Graph control using a WPF ListBox. I created my own Canvas which derives from a VirtualizingPanel and I handle the realization and virtualization of items myself.
The listbox' item panel is then set to be my custom virtualized canvas.
The problem I am encountering occurs in the following scenario:
What causes the creation of this "DisconnectedItem" ? If I were to virtualize B first, followed by A, this item would not be created. My theory is that virtualizing items that precedes other items in a ListBox causes children to be disconnected.
The problem is even more apparent using a graph with hundreds of nodes, as I end up with hundreds of disconnected items as I pan around.
Here is a portion of the code for the canvas:
/// <summary>
/// Arranges and virtualizes child element positionned explicitly.
/// </summary>
public class VirtualizingCanvas : VirtualizingPanel
{
(...)
protected override Size MeasureOverride(Size constraint)
{
ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this);
// For some reason you have to "touch" the children collection in
// order for the ItemContainerGenerator to initialize properly.
var necessaryChidrenTouch = Children;
IItemContainerGenerator generator = ItemContainerGenerator;
IDisposable generationAction = null;
int index = 0;
Rect visibilityRect = new Rect(
-HorizontalOffset / ZoomFactor,
-VerticalOffset / ZoomFactor,
ActualWidth / ZoomFactor,
ActualHeight / ZoomFactor);
// Loop thru the list of items and generate their container
// if they are included in the current visible view.
foreach (object item in itemsOwner.Items)
{
var virtualizedItem = item as IVirtualizingCanvasItem;
if (virtualizedItem == null ||
visibilityRect.IntersectsWith(GetBounds(virtualizedItem)))
{
if (generationAction == null)
{
GeneratorPosition startPosition =
generator.GeneratorPositionFromIndex(index);
generationAction = generator.StartAt(startPosition,
GeneratorDirection.Forward, true);
}
GenerateItem(index);
}
else
{
GeneratorPosition itemPosition =
generator.GeneratorPositionFromIndex(index);
if (itemPosition.Index != -1 && itemPosition.Offset == 0)
{
RemoveInternalChildRange(index, 1);
generator.Remove(itemPosition, 1);
}
// The generator needs to be "reseted" when we skip some items
// in the sequence...
if (generationAction != null)
{
generationAction.Dispose();
generationAction = null;
}
}
++index;
}
if (generationAction != null)
{
generationAction.Dispose();
}
return default(Size);
}
(...)
private void GenerateItem(int index)
{
bool newlyRealized;
var element =
ItemContainerGenerator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
if (index >= InternalChildren.Count)
{
AddInternalChild(element);
}
else
{
InsertInternalChild(index, element);
}
ItemContainerGenerator.PrepareItemContainer(element);
element.RenderTransform = _scaleTransform;
}
element.Measure(new Size(double.PositiveInfinity,
double.PositiveInfinity));
}
I'm 6 years late, but the problem is still not fixed in WPF. Here is the solution (workaround).
Make a self-binding to the DataContext, eg.:
<Image DataContext="{Binding}" />
This worked for me, even for a very complex xaml.
It's used whenever a container is removed from the visual tree, either because the corresponding item was deleted, or the collection was refreshed, or the container was scrolled off the screen and re-virtualized.
This is a known bug in WPF 4
See this link for known bug, it also has a workaround you may be able to apply.
"You can make your solution a little more robust by saving a reference to the sentinel object {DisconnectedItem} the first time you see it, then comparing against the saved value after that.
We should have made a public way to test for {DisconnectedItem}, but it slipped through the cracks. We'll fix that in a future release, but for now you can count on the fact that there's a unique {DisconnectedItem} object."
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