Freezable 对象概述
本主题介绍如何高效使用和创建 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:
如果这些条件不成立,并且你不打算修改 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/options
。 PresentationOptions
是推荐用于映射此命名空间的前缀:
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 就永远无法修改或解冻;但是,可以使用 Clone 或 CloneCurrentValue 方法创建解冻克隆。
以下示例使用画笔设置按钮背景,然后冻结该画笔。 使用 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。
Clone 和 CloneCurrentValue 方法可生成 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 类的示例,请参阅自定义动画示例。