Freezable 对象概述

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

什么是 Freezable?

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

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

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

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

例如,假设创建一个 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

Try

    ' 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;
}
else
{
    // 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
Else
    ' 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 声明为页面资源并将其冻结。 然后,使用它来设置按钮背景。

<Page 
  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 属性PresentationOptions: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 方法。 如果类对其所有数据使用依赖属性,则表示操作已完成。

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

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

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

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

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

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

注意

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

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

另请参阅