C# WPF ScrollViewer 滚动锁定帧速率为 30fps,线程利用率非常低 UI

C# WPF ScrollViewer scrolling locks a framerate at 30fps with a very low UI thread utilization percentage

我很困惑,我看到UI线程几乎没有加载,时间线分析告诉我我的fps至少应该等于60,但真正的fps不稳定。

所以,我有两个 VirtualizingStackPanel,它们都使用 DoubleAnimation 用于平滑滚动。唯一(我知道)它们不同的是,第一个是用 xaml 创建的,并且是 ListView.ItemsPanelTemplate并由ListView的ItemSource属性管理;我在代码中手动创建的第二个面板,手动将其设置为手动创建的 ScrollViewer[ 的 Content =44=] object 并手动将每个 child 添加到此面板。

创建代码:

private void InitializeItemsOwner()
{
    ItemsOwner = new VirtualizingStackPanel
    {
        Width = this.Width,
        Height = this.Height            
    };
    VirtualizingPanel.SetIsVirtualizing(ItemsOwner, true);
    VirtualizingPanel.SetCacheLengthUnit(ItemsOwner, VirtualizationCacheLengthUnit.Item);
    VirtualizingPanel.SetCacheLength(ItemsOwner, new VirtualizationCacheLength(2));
    VirtualizingPanel.SetVirtualizationMode(ItemsOwner, VirtualizationMode.Recycling);
    UpdateFrameworkElement(ItemsOwner, RenderSize, new Rect(RenderSize));
    PreviewMouseWheel += Managers.AnimationManager.ListView_PreviewMouseWheel;
}

private void InitializeScrollOwner()
{
    ScrollOwner = new ScrollViewer()
    {
        VerticalScrollBarVisibility = ScrollBarVisibility.Disabled,
        HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled
    };
    ScrollOwner.Content = ItemsOwner;
    ScrollOwner.InvalidateScrollInfo();
    UpdateFrameworkElement(ScrollOwner, RenderSize, new Rect(RenderSize));
    this.Content = ScrollOwner;
}

/// <summary>
/// Updates layout of FrameworkElement object
/// </summary>
/// <param name="element">Framework element to update</param>
/// <param name="availableSize">Meashre with availableSize</param>
/// <param name="finalRect">Arrange with finalRect</param>
private void UpdateFrameworkElement(FrameworkElement element, Size availableSize, Rect finalRect)
{
    element.Measure(availableSize);
    element.Arrange(finalRect);
    element.UpdateLayout();
}

Here is a Profiler screenshot 我希望它能显示所有重要信息。 第一个面板滚动位于分析屏幕截图的左侧,第二个面板位于右侧。我们可以看到,由于布局和代码操作有点昂贵,第一个面板加载 UI 线程更多,但它具有稳定的 60 fps;第二个面板是在代码加载 UI 中创建的,线程比第一个面板滚动要少得多,但是它会将帧速率降至 30,因为它将锁定在 30 fps。我认为这种简单的行为是非常奇怪的。重要的是两个stackPanelobjects都不到40children,我在Live Visual Treewindow.

里查了一下

正如我上面提到的,我将 children 添加到代码中的第二个面板,每个 child 都是一个带有 Source 属性 集的图像控件。这是child添加方法的代码:

private void AddChild(BitmapSource image)
{
    Image imageControl = new Image
    {
        Source = image,
        SnapsToDevicePixels = true,
        UseLayoutRounding = true
    };
    RenderOptions.SetBitmapScalingMode(imageControl, BitmapScalingMode.NearestNeighbor);
    imageControl.Effect = new System.Windows.Media.Effects.DropShadowEffect()
    {
        Color = Colors.Black
    };
    imageControl.CacheMode = new BitmapCache();
    Size imageSize = new Size(image.Width, image.Height);
    ItemsOwner.Children.Add(imageControl);

    UpdateFrameworkElement(imageControl, imageSize, new Rect(imageSize));
    ItemsOwner.UpdateLayout();
}

所以对我来说一件非常有趣的事情是,如果用一些 Rectangle 替换这个 Image 控件,任何其他简单的控件都会以 30fps 锁定消失,我在滚动动画期间有一个平滑的 60fps。问题出在这个 Image 控件中,出于某种原因,此类实例的排列很重,但为什么 Profiler 没有说明任何相关信息?我可以在 Image 实例创建时错过一些重要的东西吗?如果是,我可以尝试解决这个问题吗? 谢谢,如果您需要更多详细信息,请告诉我。等待你的想法..

所以,问题出在DropShadowEffect。第二个面板项目的排列导致它们的阴影效果为 recalculated/redrawn 或类似的东西。所以解决方案是完全摆脱 DropShadowEffect,或者您可以使用 RenderTargetBitmap 将具有阴影效果的整个控件渲染到 BitmapSource 对象,就像描述的那样 here。所以问题结束了!