我们必须要求使用VirtualizingStackPanel来虚拟化ListView/ItemsControl。尽管一切都按预期工作,但控件的ItemTemplate在初始化阶段坚持一个复杂的控件,需要大量的计算--这必须在UI线程上完成。换句话说,滚动会导致UI冻结--如果只需要做一次就可以了。由于我们不能使用VirtualizingStackPanel.VirtualizationMode="Recycle"
(由于其他几个限制),我们必须尝试一些不同的东西。
我想到了一个“缓存”virtualizingStackPanel,它不实际释放ItemTemplate的模板,而是‘冻结’控件。当用户回滚到以前加载的模板时,我们可以简单地“解冻”控件。
可以通过覆盖OnCleanUpVirtualizedItem
来实现“冻结”,例如:
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);
}
}
效果很好。该控件停留在VisualTree中,它只是“冻结”,避免了任何用户输入和可能产生的工作负载。然而,当控件返回视图时,我不知道该如何“解冻”该控件。我在参考源中搜索并找到了BringIndexIntoView
,这可能解决我的问题,如下所示:
protected override void BringIndexIntoView(int index)
{
if (_buffer.ContainsKey(index))
{
FindChild<HeavyStuff>(_buffer[index]).UnFreeze();
}
else
{
base.BringIndexIntoView(index);
}
}
但是,该方法从未被内部VirtualizingStackPanel逻辑调用。我的第二个想法是重写IItemContainerGenerator
,因为生成器确实按需提供DependencyObjects
。但又一次没有运气。不能继承ItemContainerGenerator
,因为它是密封的。其次,定义代理和覆盖ItemContainerGenerator
属性也没有帮助,因为基类根本不调用VirtualizingStackPanel的ItemContainerGenerator属性:
public new IItemContainerGenerator ItemContainerGenerator => generator;
在不需要VirtualizingStackPanel重新创建实例的情况下,当控件回滚回视图时,是否有获得信息的方法?
我也考虑过虚拟化数据源本身。然而,即使我们要虚拟化数据源,全局用户输入也将导致控件执行CPU和UI线程密集型操作。因此,无论我们选择哪种方式,我们都必须“冻结”和“解冻”特定的、非视点相关的控件。换句话说,我们仍然需要UI虚拟化。
编辑:“冻结”和“解冻”不指.NET对象冻结。我用词不当可能会造成混乱。对于“冻结”和“解冻”,我确实是指一些内部逻辑,它订阅或取消订阅来自各种事件处理程序,例如控制,在视图端口之外,不需要处理该输入。
发布于 2022-04-23 10:10:15
您可以使用下面的示例实现来扩展StackPanel
以跟踪其宿主容器的可见性(根据父滚动查看器的视图)。只需将自定义Panel
设置为ItemsPanel
为ListBox
。
重要的是父ScrollViewer
将CanContentScroll
属性设置为true
(这是ListBox
的默认设置)。
因为StackPanel
已经实现了IScrollInfo
,所以观察滚动事件和视图是非常直接的。
将实际实现添加到OnHiddenContainersChanged
方法中,以处理更改的容器和/或其宿主模型,以完成Panel
。
public class ScrollWatcherPanel : StackPanel
{
public ScrollWatcherPanel()
{
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (!this.ScrollOwner.CanContentScroll)
{
throw new InvalidOperationException("ScrollViewer.CanContentScroll must be enabled.");
}
this.ScrollOwner.ScrollChanged += OnScrollChanged;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
HandleAllContainers();
}
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
=> HandleContainerVisibilityChanges((int)e.VerticalChange);
private void HandleAllContainers()
{
int containersBeforeViewportStartCount = (int)this.VerticalOffset;
int containersBeforeViewportEndCount = containersBeforeViewportStartCount + (int)this.ViewportHeight + 1;
var newHiddenContainers = new List<FrameworkElement>();
var newVisibleContainers = new List<FrameworkElement>();
for (int childContainerIndex = 0; childContainerIndex < this.InternalChildren.Count; childContainerIndex++)
{
bool isContainerHiddenBeforeViewport = childContainerIndex < containersBeforeViewportStartCount;
bool isContainerVisibleInViewport = childContainerIndex < containersBeforeViewportEndCount;
var childContainer = (FrameworkElement)this.InternalChildren[childContainerIndex];
if (isContainerHiddenBeforeViewport)
{
newHiddenContainers.Add(childContainer);
}
else if (isContainerVisibleInViewport)
{
newVisibleContainers.Add(childContainer);
}
else // Container is hidden after viewport
{
newHiddenContainers.Add(childContainer);
}
}
OnHiddenContainersChanged(newHiddenContainers, newVisibleContainers);
}
private void HandleContainerVisibilityChanges(int verticalChange)
{
int containersBeforeViewportStartCount = (int)this.VerticalOffset;
int containersBeforeViewportEndCount = containersBeforeViewportStartCount + (int)this.ViewportHeight + 1;
int newHiddenContainerCount = Math.Abs(verticalChange);
int newVisibleContainerCount = Math.Abs(verticalChange);
bool isScrollingDown = verticalChange > 0;
int changeCount = Math.Abs(verticalChange);
var newHiddenContainers = new List<FrameworkElement>();
var newVisibleContainers = new List<FrameworkElement>();
int changesIndex = Math.Max(0, containersBeforeViewportStartCount - changeCount);
for (int childContainerIndex = changesIndex; childContainerIndex < this.InternalChildren.Count; childContainerIndex++)
{
bool isContainerHiddenBeforeViewport = childContainerIndex < containersBeforeViewportStartCount;
bool isContainerVisibleInViewport = childContainerIndex < containersBeforeViewportEndCount;
var childContainer = (FrameworkElement)this.InternalChildren[childContainerIndex];
if (isContainerHiddenBeforeViewport)
{
if (isScrollingDown)
{
bool isContainerNewHidden = childContainerIndex >= containersBeforeViewportStartCount - changeCount
&& newHiddenContainerCount > 0;
if (isContainerNewHidden)
{
newHiddenContainers.Add(childContainer);
newHiddenContainerCount--;
}
}
}
else if (isContainerVisibleInViewport)
{
if (isScrollingDown)
{
bool isContainerNewVisible = childContainerIndex >= containersBeforeViewportEndCount - changeCount
&& newVisibleContainerCount > 0;
if (isContainerNewVisible)
{
newVisibleContainers.Add(childContainer);
newVisibleContainerCount--;
}
}
else
{
bool isContainerNewVisible = childContainerIndex >= containersBeforeViewportStartCount
&& newVisibleContainerCount > 0;
if (isContainerNewVisible)
{
newVisibleContainers.Add(childContainer);
newVisibleContainerCount--;
}
}
}
else // Container is hidden after viewport (on scroll up)
{
if (!isScrollingDown)
{
bool isContainerNewHidden = childContainerIndex >= containersBeforeViewportEndCount
&& newHiddenContainerCount > 0;
if (isContainerNewHidden)
{
newHiddenContainers.Add(childContainer);
newHiddenContainerCount--;
if (newHiddenContainerCount == 0)
{
break;
}
}
}
}
}
OnHiddenContainersChanged(newHiddenContainers, newVisibleContainers);
}
protected virtual void OnHiddenContainersChanged(IEnumerable<FrameworkElement> newHiddenContainers, IEnumerable<FrameworkElement> newVisibleContainers)
{
// TODO::Handle "hidden"/"visible" item containers, that are just scrolled out of/into the viewport.
// You can access the DataContext of the containers to get a reference to the underlying data model.
}
}
https://stackoverflow.com/questions/71889956
复制相似问题