首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >带缓存的VirtualizingStackPanel

带缓存的VirtualizingStackPanel
EN

Stack Overflow用户
提问于 2022-04-15 23:55:25
回答 1查看 161关注 0票数 2

我们必须要求使用VirtualizingStackPanel来虚拟化ListView/ItemsControl。尽管一切都按预期工作,但控件的ItemTemplate在初始化阶段坚持一个复杂的控件,需要大量的计算--这必须在UI线程上完成。换句话说,滚动会导致UI冻结--如果只需要做一次就可以了。由于我们不能使用VirtualizingStackPanel.VirtualizationMode="Recycle" (由于其他几个限制),我们必须尝试一些不同的东西。

我想到了一个“缓存”virtualizingStackPanel,它不实际释放ItemTemplate的模板,而是‘冻结’控件。当用户回滚到以前加载的模板时,我们可以简单地“解冻”控件。

可以通过覆盖OnCleanUpVirtualizedItem来实现“冻结”,例如:

代码语言:javascript
运行
复制
    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,这可能解决我的问题,如下所示:

代码语言:javascript
运行
复制
     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属性:

代码语言:javascript
运行
复制
    public new IItemContainerGenerator ItemContainerGenerator => generator;

在不需要VirtualizingStackPanel重新创建实例的情况下,当控件回滚回视图时,是否有获得信息的方法?

我也考虑过虚拟化数据源本身。然而,即使我们要虚拟化数据源,全局用户输入也将导致控件执行CPU和UI线程密集型操作。因此,无论我们选择哪种方式,我们都必须“冻结”和“解冻”特定的、非视点相关的控件。换句话说,我们仍然需要UI虚拟化。

编辑:“冻结”和“解冻”不指.NET对象冻结。我用词不当可能会造成混乱。对于“冻结”和“解冻”,我确实是指一些内部逻辑,它订阅或取消订阅来自各种事件处理程序,例如控制,在视图端口之外,不需要处理该输入。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-04-23 10:10:15

您可以使用下面的示例实现来扩展StackPanel以跟踪其宿主容器的可见性(根据父滚动查看器的视图)。只需将自定义Panel设置为ItemsPanelListBox

重要的是父ScrollViewerCanContentScroll属性设置为true (这是ListBox的默认设置)。

因为StackPanel已经实现了IScrollInfo,所以观察滚动事件和视图是非常直接的。

将实际实现添加到OnHiddenContainersChanged方法中,以处理更改的容器和/或其宿主模型,以完成Panel

代码语言:javascript
运行
复制
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.
  }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71889956

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档