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。所以问题结束了!
我很困惑,我看到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。所以问题结束了!