本主题介绍如何高效使用和创建 Freezable 对象,这些对象提供有助于提高应用程序性能的特殊功能。 Freezable 对象的示例包括画笔、笔、转换、几何图形和动画。

什么是 Freezable?

Freezable 是一种特殊类型的对象,具有两种状态:解冻和冻结。 解冻后, Freezable 的行为看起来与任何其他对象一样。 冻结后,不能再修改 Freezable

Freezable 提供 Changed 事件来通知观察者对象发生的任何修改。 冻结 Freezable 可以提高其性能,因为它不再需要在更改通知上消耗资源。 冻结的 Freezable 还可以跨线程共享,解冻的 Freezable 则不能。

尽管 Freezable 类有许多应用程序,但 Windows Presentation Foundation (WPF) 中的大多数 Freezable 对象都与图形子系统相关。

借助 Freezable 类,可以更轻松地使用某些图形系统对象,并有助于提高应用程序性能。 从 Freezable 继承的类型示例包括 Brush Transform Geometry 类。 由于它们包含非托管资源,因此系统必须监视这些对象发生的修改,然后在原始对象发生更改时更新对应的非托管资源。 即使实际上并未修改图形系统对象,系统仍必须消耗一些资源来监视该对象,以防你更改它。

例如,假设创建一个 SolidColorBrush 画笔并用它来绘制按钮的背景。

Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;
Dim myButton As New Button()
Dim myBrush As New SolidColorBrush(Colors.Yellow)
myButton.Background = myBrush

呈现按钮时,WPF 图形子系统使用你提供的信息来绘制一组像素,以创建按钮的外观。 尽管使用纯色画笔来描述按钮的绘制方式,但纯色画笔实际上并没有进行绘制。 图形系统为按钮和画笔生成快速、低级别的对象,实际显示在屏幕上的就是这些对象。

如果要修改画笔,则必须重新生成这些低级别对象。 Freezable 类使画笔能够找到生成的相应低级别对象并在更改时更新它们。 启用此功能后,画笔即“解冻”。

使用 Freezable 的 Freeze 方法可以禁用此自更新功能。 你可以使用此方法使画笔“冻结”或无法修改。

并非每个 Freezable 对象都可以冻结。 为避免引发 InvalidOperationException,请在尝试冻结 Freezable 对象之前检查该对象的 CanFreeze 属性值,以确定是否可以将其冻结。

if (myBrush.CanFreeze)
    // Makes the brush unmodifiable.
    myBrush.Freeze();
If myBrush.CanFreeze Then
    ' Makes the brush unmodifiable.
    myBrush.Freeze()
End If

当你不再需要修改 Freezable 对象时,冻结它会带来性能优势。 如果冻结此示例中的画笔,图形系统将不再需要监视其更改。 图形系统还可以进行其他优化,因为它知道画笔不会更改。

为方便起见,除非显式冻结 Freezable 对象,否则它们将保持解冻状态。

使用 Freezable

使用解冻的 Freezable 就像使用任何其他类型的对象一样。 在以下示例中,在使用 SolidColorBrush 绘制按钮背景后,其颜色从黄色变为红色。 图形系统在后台工作,以在下次刷新屏幕时自动将按钮从黄色更改为红色。

Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;
// Changes the button's background to red.
myBrush.Color = Colors.Red;
Dim myButton As New Button()
Dim myBrush As New SolidColorBrush(Colors.Yellow)
myButton.Background = myBrush
' Changes the button's background to red.
myBrush.Color = Colors.Red

冻结 Freezable

若要使 Freezable 不可修改,请调用其 Freeze 方法。 冻结包含 Freezable 对象的对象时,这些对象也会被冻结。 例如,如果冻结 PathGeometry,它所包含的图形和段也会被冻结。

如果满足以下任一条件,则无法冻结 Freezable:

  • 它具有动画属性或数据绑定属性。

  • 它具有由动态资源设置的属性。 (有关动态资源的详细信息,请参阅 XAML 资源。)

  • 它包含无法冻结的 Freezable 子对象。

    如果这些条件不成立,并且你不打算修改 Freezable,则应冻结它,以获得前面所述的性能优势。

    一旦调用了 Freezable 的 Freeze 方法,就无法再对其进行修改。 尝试修改冻结对象会导致引发 InvalidOperationException。 以下代码会引发异常,因为我们试图在冻结画笔后对其进行修改。

    Button myButton = new Button(); SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow); if (myBrush.CanFreeze) // Makes the brush unmodifiable. myBrush.Freeze(); myButton.Background = myBrush; try { // Throws an InvalidOperationException, because the brush is frozen. myBrush.Color = Colors.Red; }catch(InvalidOperationException ex) MessageBox.Show("Invalid operation: " + ex.ToString()); Dim myButton As New Button() Dim myBrush As New SolidColorBrush(Colors.Yellow) If myBrush.CanFreeze Then ' Makes the brush unmodifiable. myBrush.Freeze() End If myButton.Background = myBrush ' Throws an InvalidOperationException, because the brush is frozen. myBrush.Color = Colors.Red Catch ex As InvalidOperationException MessageBox.Show("Invalid operation: " & ex.ToString()) End Try

    为避免引发此异常,可以使用 IsFrozen 方法来确定 Freezable 是否已冻结。

    Button myButton = new Button(); SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow); if (myBrush.CanFreeze) // Makes the brush unmodifiable. myBrush.Freeze(); myButton.Background = myBrush; if (myBrush.IsFrozen) // Evaluates to true. // If the brush is frozen, create a clone and // modify the clone. SolidColorBrush myBrushClone = myBrush.Clone(); myBrushClone.Color = Colors.Red; myButton.Background = myBrushClone; // If the brush is not frozen, // it can be modified directly. myBrush.Color = Colors.Red; Dim myButton As New Button() Dim myBrush As New SolidColorBrush(Colors.Yellow) If myBrush.CanFreeze Then ' Makes the brush unmodifiable. myBrush.Freeze() End If myButton.Background = myBrush If myBrush.IsFrozen Then ' Evaluates to true. ' If the brush is frozen, create a clone and ' modify the clone. Dim myBrushClone As SolidColorBrush = myBrush.Clone() myBrushClone.Color = Colors.Red myButton.Background = myBrushClone ' If the brush is not frozen, ' it can be modified directly. myBrush.Color = Colors.Red End If

    前面的代码示例使用 Clone 方法创建了冻结对象的可修改副本。 下一部分将更详细地讨论克隆。

    由于无法对冻结的 Freezable 进行动画处理,因此当你尝试使用 Storyboard 对其进行动画处理时,动画系统会自动创建冻结的 Freezable 对象的可修改克隆。 为了消除克隆导致的性能开销,如果你打算对对象进行动画处理,请让其保持解冻状态。 有关使用情节提要进行动画处理的详细信息,请参阅情节提要概述

    从标记冻结

    若要冻结标记中声明的 Freezable 对象,请使用 PresentationOptions:Freeze 属性。 以下示例将 SolidColorBrush 声明为页面资源并将其冻结。 然后,使用它来设置按钮背景。

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="PresentationOptions"> <Page.Resources> <!-- This resource is frozen. --> <SolidColorBrush x:Key="MyBrush" PresentationOptions:Freeze="True" Color="Red" /> </Page.Resources> <StackPanel> <Button Content="A Button" Background="{StaticResource MyBrush}"> </Button> </StackPanel> </Page>

    若要使用 Freeze 属性,必须映射到呈现选项命名空间:http://schemas.microsoft.com/winfx/2006/xaml/presentation/optionsPresentationOptions 是推荐用于映射此命名空间的前缀:

    xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
    

    由于并非所有 XAML 读取器都能识别此属性,因此建议使用 mc:Ignorable 属性Presentation:Freeze 属性标记为可忽略:

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="PresentationOptions"
    

    有关详细信息,请参阅 mc:Ignorable 属性页面。

    “解冻”Freezable

    一旦冻结,Freezable 就永远无法修改或解冻;但是,可以使用 CloneCloneCurrentValue 方法创建解冻克隆。

    以下示例使用画笔设置按钮背景,然后冻结该画笔。 使用 Clone 方法创建画笔的解冻副本。 修改克隆并使用它将按钮的背景从黄色更改为红色。

    Button myButton = new Button();
    SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
    // Freezing a Freezable before it provides
    // performance improvements if you don't
    // intend on modifying it.
    if (myBrush.CanFreeze)
        // Makes the brush unmodifiable.
        myBrush.Freeze();
    myButton.Background = myBrush;
    // If you need to modify a frozen brush,
    // the Clone method can be used to
    // create a modifiable copy.
    SolidColorBrush myBrushClone = myBrush.Clone();
    // Changing myBrushClone does not change
    // the color of myButton, because its
    // background is still set by myBrush.
    myBrushClone.Color = Colors.Red;
    // Replacing myBrush with myBrushClone
    // makes the button change to red.
    myButton.Background = myBrushClone;
    
    Dim myButton As New Button()
    Dim myBrush As New SolidColorBrush(Colors.Yellow)
    ' Freezing a Freezable before it provides
    ' performance improvements if you don't
    ' intend on modifying it. 
    If myBrush.CanFreeze Then
        ' Makes the brush unmodifiable.
        myBrush.Freeze()
    End If
    myButton.Background = myBrush
    ' If you need to modify a frozen brush,
    ' the Clone method can be used to
    ' create a modifiable copy.
    Dim myBrushClone As SolidColorBrush = myBrush.Clone()
    ' Changing myBrushClone does not change
    ' the color of myButton, because its
    ' background is still set by myBrush.
    myBrushClone.Color = Colors.Red
    ' Replacing myBrush with myBrushClone
    ' makes the button change to red.
    myButton.Background = myBrushClone
    

    无论使用哪种克隆方法,动画都不会复制到新的 Freezable

    CloneCloneCurrentValue 方法可生成 Freezable 的深层副本。 如果 Freezable 包含其他已冻结的 Freezable 对象,它们也会被克隆并变为可修改。 例如,如果克隆冻结的 PathGeometry 以使其可修改,则它包含的图形和段也会被复制并变为可修改。

    创建自己的 Freezable 类

    派生自 Freezable 的类具有以下特性。

  • 特殊状态:只读(冻结)和可写状态。

  • 线程安全:冻结的 Freezable 可以跨线程共享。

  • 详细的更改通知:与其他 DependencyObject 不同,Freezable 对象在子属性值更改时提供更改通知。

  • 轻松克隆:Freezable 类已经实现了几种生成深层克隆的方法。

    Freezable 是一种 DependencyObject,因此使用依赖属性系统。 类属性不必是依赖属性,但使用依赖属性可减少必须编写的代码量,因为 Freezable 类的设计考虑了依赖属性。 有关依赖属性系统的详细信息,请参阅依赖属性概述

    每个 Freezable 子类都必须替代 CreateInstanceCore 方法。 如果类对其所有数据使用依赖属性,则表示操作已完成。

    如果类包含非依赖属性数据成员,则还必须替代以下方法:

  • CloneCore

  • CloneCurrentValueCore

  • GetAsFrozenCore

  • GetCurrentValueAsFrozenCore

  • FreezeCore

    访问和写入不是依赖属性的数据成员时,还必须遵守以下规则:

  • 在读取非依赖属性数据成员的任何 API 的开头,调用 ReadPreamble 方法。

  • 在写入非依赖属性数据成员的任何 API 的开头,调用 WritePreamble 方法。 (在 API 中调用 WritePreamble 后,如果还读取了非依赖属性数据成员,则无需额外调用 ReadPreamble。)

  • 在退出写入非依赖属性数据成员的方法之前调用 WritePostscript 方法。

    如果类包含属于 DependencyObject 对象的非依赖属性数据成员,那么每次更改其中一个值时还必须调用 OnFreezablePropertyChanged 方法,即使将该成员设置为 null 也是如此。

    在替代的每个 Freezable 方法开始时都要调用基本实现,这一点很重要。

    有关自定义 Freezable 类的示例,请参阅自定义动画示例

  • Freezable
  • 自定义动画示例
  • 依赖项属性概述
  • 自定义依赖属性
  •