We have to requirement to virtualize a ListView/ItemsControl with a VirtualizingStackPanel. Although everything works as expected, the Control's ItemTemplate adheres a complex control with a lot of computation during its initialization phase - which has to be done on the UI thread. In other words, scrolling leads to UI freezes - which is fine if it only has to be done once. As we can't use the VirtualizingStackPanel.VirtualizationMode="Recycle" (due to several other restrictions) we have to try something different.
I thought of a "cached" virtualizingStackPanel which doesn't actually dispose the ItemTemplate's Template, but rather 'freezes' the control. When the user scrolls back to a - previously loaded template - we could simply 'unfreeze' the control.
The 'freeze' can be implemented by overwriting OnCleanUpVirtualizedItem, such as:
protected override void OnCleanUpVirtualizedItem(CleanUpVirtualizedItemEventArgs args)
{
var stuff = FindChild<HeavyStuff>(args.UIElement);
if (stuff != null)
{
int idx = Children.IndexOf(args.UIElement);
if (!_buffer.ContainsKey(idx))
_buffer.Add(idx, args.UIElement);
stuff.Freeze();
args.Handled = true;
args.Cancel = true;
}
else
{
base.OnCleanUpVirtualizedItem(args);
}
}
That works pretty well. The control stays within the VisualTree and it simply 'freezes' and avoids any user-input and the potential resulting workload. However, I couldn't figure out on howto 'unfreeze' the control when it comes back into view. I dug through the reference-source and found the BringIndexIntoView, which could potentially solve my issue like the following:
protected override void BringIndexIntoView(int index)
{
if (_buffer.ContainsKey(index))
{
FindChild<HeavyStuff>(_buffer[index]).UnFreeze();
}
else
{
base.BringIndexIntoView(index);
}
}
However, that method never gets called by the internal VirtualizingStackPanel logic. My second thought was to override the IItemContainerGenerator, as the generator does provide the DependencyObjects on demand. But again without any luck. One can't inherit the ItemContainerGenerator, because it is sealed. Secondly, defining a proxy and overwriting the ItemContainerGenerator properties doesn't help either, as the base class doesn't call that VirtualizingStackPanel's ItemContainerGenerator property at all:
public new IItemContainerGenerator ItemContainerGenerator => generator;
Is there any way to obtain the information when a control scrolls back into the view, without the VirtualizingStackPanel re-creating an instance?
Addon: I also thought about virtualizing the data-source itself. However, even if we would virtualize the data source, the global user input would lead the controls to perform CPU and UI-thread intensive operations. Hence, it doesn't matter which way we choose, we do have to 'freeze' and 'unfreeze' certain, non-viewport-related controls. In other words, we need UI virtualization nevertheless.
EDIT: "Freeze" and "Unfreeze" does not refer to the .NET object freezing. My poor choice of words may cause that confusion. With "freeze" and "unfreeze" I do refer to some internal logic which subscribes or unsubscribes from various event handlers, such that controls, beeing out of the viewport, don't require to process that input.