Freezable 对象概述

本主题说明如何有效地使用和创建 Freezable 对象,这些对象提供可帮助改进应用程序性能的特殊功能。 Freezable 对象的示例包括画笔、钢笔、变换、几何图形和动画。

本主题包含以下各节:

什么是 Freezable?

Freezable 是一种特殊的对象类型,具有两个状态:解冻和冻结。 当处于解冻状态时,Freezable 的行为与任何其他对象的行为一样。 Freezable 一旦冻结,便无法修改。

Freezable 提供了一个 Changed 事件以将对对象所做的任何修改通知给观察程序。 冻结 Freezable 可以改进其性能,因为它不再需要因更改通知而消耗资源。 冻结的 Freezable 也可以在线程之间共享,而解冻的 Freezable 则不能。

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

Freezable 类使您在使用某些图形系统对象时更加轻松,并且有助于改进应用程序性能。 从 Freezable 继承的类型示例包括 BrushTransformGeometry 类。 由于它们包含非托管资源,因此系统必须监视这些对象的修改情况,然后在对原始对象进行了更改的情况下,更新其相应的非托管资源。 即使实际上您并没有修改图形系统对象,但系统也必须花费一些资源来监视该对象,以防您对其进行更改。

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

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

呈现按钮时,WPF 图形子系统使用您提供的信息来绘制一组像素,以创建按钮的外观。 虽然您使用了纯色画笔来描写应如何绘制按钮,但您的纯色画笔并未真正绘制。 图形系统为该按钮和画笔生成快速、低级别的对象,而屏幕上实际显示的正是这些对象。

如果要修改画笔,则必须重新生成这些低级别对象。 通过 Freezable 类,画笔可以找到相应的已生成低级别对象,并在画笔更改时更新这些对象。 当启用该功能时,画笔便被认为是“解冻的”。

使用 Freezable 对象的 Freeze 方法可以禁用该自行更新功能。 可以使用该方法使画笔变为“冻结”或不可修改。

注意注意

并不是每个 Freezable 对象都可以冻结。若要避免引发 InvalidOperationException,请在尝试冻结 Freezable 对象之前,查看其 CanFreeze 属性的值,以确定它是否可以被冻结。

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

当不再需要修改某个 Freezable 时,冻结它可以改进性能。 如果您在该示例中冻结画笔,则图形系统将不再需要监视它的更改情况。 图形系统还可以进行其他优化,因为它知道画笔不会更改。

注意注意

出于方便,除非您显式冻结 Freezable 对象,否则它们保持解冻状态。

使用 Freezable

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

            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
Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;  


// Changes the button's background to red.
myBrush.Color = Colors.Red;

冻结 Freezable

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

如果下列任一情况属实,则无法冻结 Freezable:

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

  • 它有由动态资源设置的属性 (有关动态资源的更多信息,请参见资源概述)。

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

如果不存在这些情况,并且您不希望修改 Freezable,则应当像前面介绍的那样冻结该对象以改进性能。

一旦调用某个 Freezable 的 Freeze 方法,便不能再修改该对象。 尝试修改冻结的对象会导致引发 InvalidOperationException。 由于尝试修改已冻结的画笔,下面的代码将引发一个异常。


            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


            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());
            }

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


            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



            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;
            }


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

注意   由于无法对冻结的 Freezable 进行动画处理,因此当您尝试使用 Storyboard 对冻结的 Freezable 对象进行动画处理时,动画系统将自动创建这些对象的可修改复本。 为了消除由于克隆而导致的性能系统开销,当您希望对某个对象进行动画处理时,请使其保持解冻状态。 有关使用演示图板进行动画处理的更多信息,请参见演示图板概述

从标记冻结

若要冻结在标记中声明的 Freezable 对象,请使用 PresentationOptions:Freeze 特性。 在下面的示例中,将一个 SolidColorBrush 声明为页资源,并冻结它。 随后将它用于设置按钮的背景。

<Page 
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:PresentationOptions="https://schemas.microsoft.com/winfx/2006/xaml/presentation/options" 
  xmlns:mc="https://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 特性,必须映射到表示选项命名空间:https://schemas.microsoft.com/winfx/2006/xaml/presentation/options。 PresentationOptions 是用于映射此命名空间时的建议前缀:

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

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

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

有关更多信息,请参见 mc:Ignorable 特性页。

“解冻”Freezable

Freezable 一旦冻结,便不能再修改或解冻;不过,您可以使用 CloneCloneCurrentValue 方法创建一个解冻的复本。

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

            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
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;
注意注意

无论使用哪一种克隆方法,都不会将动画复制到新的 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 类的示例,请参见 Custom Animation Sample(自定义动画示例)。

请参见

参考

Freezable

概念

依赖项属性概述

自定义依赖项属性

其他资源

Custom Animation Sample