WPF性能优化

图形渲染层

来源文档
影响渲染级别的因素:

  1. Video RAM:视频内存量决定了可用于渲染图形的缓冲器的大小和数量
  2. 像素阴影:像素阴影需要根据分辨率对每一帧的所有像素进行计算,每个显示帧可能需要处理数百万像素
  3. 顶点着色:顶点着色是一种图形处理函数,对对象的顶点数据进行数学计算
  4. 多纹理支持:在3D图形对象的混合操作中应用两种或多种不同纹理。多纹理支持程度取决于图形硬件上的多纹理单元的数量

WPF中渲染层有三层:

  1. Rendering Tier 0 :没有图形硬件加速度。所有图形功能都使用软件加速度。directx 版本级别小于版本 9.0
  2. Rendering Tier 1 :某些图形功能使用图形硬件加速度。directx 版本级别大于或等于版本 9.0
  3. Rendering Tier 2 :大多数图形功能使用图形硬件加速度。directx 版本级别大于或等于版本 9.0

RenderCapability.Tier属性可以在运行时检索渲染层,以动态决定部分渲染逻辑

布局

布局是一个数学密集型过程,布局系统将为集合中的每个子成员完成两次传递:测量传递、排列传递。
每个子对象都提供了自己的 Measure 和Arrange 方法的重写实现,以提供自己特定的布局行为。
最简单的说,布局是一个递归系统,它导致元素在屏幕上被调整大小、定位和绘制。
子 UIElement 对象首先测量其核心属性,从而开始布局过程。评估对象的与大小相关的 FrameworkElement 属性,例如 Width、Height 和 Margin。
应用特定于面板的逻辑,例如 DockPanel 的 Dock 属性或 StackPanel 的 Orientation 属性。
在测量完所有子对象后,将排列或定位内容。子对象的集合被绘制到屏幕上。
如果发生以下任何操作,将再次调用布局传递过程:

  1. 一个子对象被添加到集合中。

  2. LayoutTransform 应用于子对象。

  3. 为子对象调用 UpdateLayout 方法。

  4. 当使用影响度量或排列传递的元数据标记的依赖属性的值发生更改时。

  5. 尽可能使用Canvas等简单布局,少使用grid、stackpanel

  6. 创建视觉树或逻辑树时遵循Top-Down原则
    性能对照

Action Tree building (in ms) Render—includes tree building (in ms)
Bottom-up 366 454
Top-down 11 96

Top-Down示例

private void OnBuildTreeTopDown(object sender, RoutedEventArgs e)
{
    TextBlock textBlock = new TextBlock();
    textBlock.Text = "Default";

    DockPanel parentPanel = new DockPanel();
    DockPanel childPanel;

    myCanvas.Children.Add(parentPanel);
    myCanvas.Children.Add(textBlock);

    for (int i = 0; i < 150; i++)
    {
        textBlock = new TextBlock();
        textBlock.Text = "Default";
        parentPanel.Children.Add(textBlock);

        childPanel = new DockPanel();
        parentPanel.Children.Add(childPanel);
        parentPanel = childPanel;
    }
}

使用Drawing而不是Sharp

Shape 派生自 FrameworkElement,使用将会显著增加内存消耗,建议考虑使用轻量级的 Drawing

图像

默认情况下,WPF 会加载图像并将其解码为全尺寸。若只需要缩略图,可以通过设置图像模式为低质量模式

RenderOptions.SetBitmapScalingMode(MyImage, BitmapScalingMode.LowQuality);
  1. 考虑使用低质量模式
  2. 始终将图像解码为所需的大小
  3. 将图像组合成单个图像

其他注意点

  1. 释放对象之前必须删除委托
  2. 定义资源然后共享引用,而不是在XAML中定义内联引用
  3. 尽可能使用静态资源
  4. 使用Run而不是Textblock !!!如果使用run来显示文本,则需要注意尽量不要对run设置属性,而是设置在textblock上!!!
<!-- Run is used to set text properties. -->
<TextBlock>
  <Run FontWeight="Bold">Hello, world</Run>
</TextBlock>

<!-- TextBlock is used to set text properties, which is more efficient. -->
<TextBlock FontWeight="Bold">
  Hello, world
</TextBlock>
文本块类型 创建时间(ms) 渲染时间(ms)
设置文本属性 146 540
设置文本属性 43 453
  1. 文本频繁更新时避免数据绑定到Label.Content属性而是使用Textblock.Text
数据绑定属性 更新时间(ms)
Label.Content 835
Textblock.Text 242
  1. 避免直接绑定IList而是使用ObservableCollection
  2. 绑定ItemsControl时使用IList而不是IEnumerable,使用IEnumerable将创建包装器IList 对象

虚拟化

使用列表型控件如ListView、ComboBox时如果列表数据较多,则应该考虑使用虚拟化。
上文中提及到子元素将需要计算布局大小和位置,通常这些元素并不是一次性显示在页面上,所以延迟计算是有意义的,
使用虚拟化即可实现延迟这些计算,数据虚拟化仅将屏幕上可见的数据项存储在内存中。

默认情况下,当 ListView 和 ListBox 控件的列表项绑定到数据时,会为它们启用 UI 虚拟化。
可以通过将 VirtualizingStackPanel.IsVirtualizing 附加属性设置为True来启用 TreeView 虚拟化。
如果要为派生自 ItemsControl 的自定义控件或使用 StackPanel 类的现有项目控件(例如 ComboBox)启用 UI 虚拟化,
可以将 ItemsPanel 设置为 VirtualizingStackPanel 并将 IsVirtualizing 设置为True。

如果应用程序将 ListBoxItem 对象显式添加到 ListBox,则 ListBox 不会虚拟化 ListBoxItem 对象。

容器回收

填充使用 UI 虚拟化的 ItemsControl 时,它会为滚动到视图中的每个项目创建一个项目容器,并为每个滚动出视图的项目销毁该项目容器。
容器回收使控件能够为不同的数据项重用现有的项容器,以便在用户滚动 ItemsControl 时不会不断地创建和销毁项容器。
您可以通过将 VirtualizationMode 附加属性设置为 Recycling 来选择启用项目回收。任何支持虚拟化的 ItemsControl 都可以使用容器回收。
VirtualizingStackPanel 在一个方向(水平或垂直)提供对 UI 虚拟化的内置支持。如果要对控件使用双向虚拟化,
则必须实现扩展 VirtualizingStackPanel 类的自定义面板。 VirtualizingStackPanel 类公开了诸如
OnViewportSizeChanged、LineUp、PageUp 和 MouseWheelUp 之类的虚拟方法。这些虚拟方法使您能够检测列表可见部分的更改并相应地处理它。

默认情况下,当用户在滚动条上拖动拇指时,内容视图会不断更新。如果控件中的滚动速度较慢,请考虑使用延迟滚动。在延迟滚动中,内容仅在用户松开拇指时更新。
要实现延迟滚动,请将 IsDeferredScrollingEnabled 属性设置为true。 IsDeferredScrollingEnabled 是一个附加属性,可以在 ScrollViewer 和任何在其控件模板中具有 ScrollViewer 的控件上设置。

WPF中usercontrol的销毁

  1. 使用Dispatcher.ShutdownStarted和Dispatcher.ShutdownFinished事件,将在主窗口关闭时候触发
public TestUsercontrol()
{
  InitializeComponent();

  Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;

  Dispatcher.ShutdownFinished += Dispatcher_ShutdownFinished;

}

private void Dispatcher_ShutdownFinished(object? sender, EventArgs e)
{
  throw new NotImplementedException();
}

private void Dispatcher_ShutdownStarted(object? sender, EventArgs e)
{
  Trace.WriteLine("shutdown");
}
  1. 通过重写OnVisualParentChanged和OnVisualChildrenChanged,将在父级或子集变化时候触发
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
  base.OnVisualParentChanged(oldParent);
}

protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
  base.OnVisualChildrenChanged(visualAdded, visualRemoved);  
}
  1. 获取父窗口后使用窗口的卸载相关事件
var parentWindows=Window.GetWindow(usercontrol);

留待后查,同时方便他人
联系我:renhanlinbsl@163.com
原文地址:https://www.cnblogs.com/ives/p/wpfperformance.html