Freezable Objects Overview
This topic describes how to effectively use and create Freezable objects, which provide special features that can help improve application performance. Examples of freezable objects include brushes, pens, transformations, geometries, and animations.
This topic contains the following sections:
- What is a Freezable?
- Using Freezables
- Creating Your Own Freezable Class
- Related Topics
What is a Freezable?
A Freezable is a special type of object that has two states: unfrozen and frozen. When unfrozen, a Freezable appears to behave like any other object. When frozen, a Freezable can no longer be modified.
A Freezable provides a Changed event to notify observers of any modifications to the object. Freezing a Freezable can improve its performance, because it no longer needs to spend resources on change notifications. A frozen Freezable can also be shared across threads, while an unfrozen Freezable cannot.
Although the Freezable class has many applications, most Freezable objects in Windows Presentation Foundation (WPF) are related to the graphics sub-system.
The Freezable class makes it easier to use certain graphics system objects and can help improve application performance. Examples of types that inherit from Freezable include the Brush, Transform, and Geometry classes. Because they contain unmanaged resources, the system must monitor these objects for modifications, and then update their corresponding unmanaged resources when there is a change to the original object. Even if you don't actually modify a graphics system object, the system must still spend some of its resources monitoring the object, in case you do change it.
For example, suppose you create a SolidColorBrush brush and use it to paint the background of a button.
Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;
When the button is rendered, the WPF graphics sub-system uses the information you provided to paint a group of pixels to create the appearance of a button. Although you used a solid color brush to describe how the button should be painted, your solid color brush doesn't actually do the painting. The graphics system generates fast, low-level objects for the button and the brush, and it is those objects that actually appear on the screen.
If you were to modify the brush, those low-level objects would have to be regenerated. The freezable class is what gives a brush the ability to find its corresponding generated, low-level objects and to update them when it changes. When this ability is enabled, the brush is said to be "unfrozen."
A freezable's Freeze method enables you to disable this self-updating ability. You can use this method to make the brush become "frozen," or unmodifiable.
Note: |
---|
Not every Freezable object can be frozen. To avoid throwing an InvalidOperationException, check the value of the Freezable object's CanFreeze property to determine whether it can be frozen before attempting to freeze it. |
if (myBrush.CanFreeze)
{
// Makes the brush unmodifiable.
myBrush.Freeze();
}
When you no longer need to modify a freezable, freezing it provides performance benefits. If you were to freeze the brush in this example, the graphics system would no longer need to monitor it for changes. The graphics system can also make other optimizations, because it knows the brush won't change.
Note: |
---|
For convenience, freezable objects remain unfrozen unless you explicitly freeze them. |
Using Freezables
Using an unfrozen freezable is like using any other type of object. In the following example, the color of a SolidColorBrush is changed from yellow to red after it's used to paint the background of a button. The graphics system works behind the scenes to automatically change the button from yellow to red the next time the screen is refreshed.
Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;
// Changes the button's background to red.
myBrush.Color = Colors.Red;
Freezing a Freezable
To make a Freezable unmodifiable, you call its Freeze method. When you freeze an object that contains freezable objects, those objects are frozen as well. For example, if you freeze a PathGeometry, the figures and segments it contains would be frozen too.
A Freezable can't be frozen if any of the following are true:
It has animated or data bound properties.
It has properties set by a dynamic resource. (See the Resources Overview for more information about dynamic resources.)
It contains Freezable sub-objects that can't be frozen.
If these conditions are false, and you don't intend to modify the Freezable, then you should freeze it to gain the performance benefits described earlier.
Once you call a freezable's Freeze method, it can no longer be modified. Attempting to modify a frozen object causes an InvalidOperationException to be thrown. The following code throws an exception, because we attempt to modify the brush after it's been frozen.
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());
}
To avoid throwing this exception, you can use the IsFrozen method to determine whether a Freezable is frozen.
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;
}
In the preceding code example, a modifiable copy was made of a frozen object using the Clone method. The next section discusses cloning in more detail.
Note Because a frozen freezable cannot be animated, the animation system will automatically create modifiable clones of frozen Freezable objects when you try to animate them with a Storyboard. To eliminate the performance overhead caused by cloning, leave an object unfrozen if you intend to animate it. For more information about animating with storyboards, see the Storyboards Overview.
Freezing from Markup
To freeze a Freezable object declared in markup, you use the PresentationOptions:Freeze
attribute. In the following example, a SolidColorBrush is declared as a page resource and frozen. It is then used to set the background of a button.
<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>
To use the Freeze
attribute, you must map to the presentation options namespace: https://schemas.microsoft.com/winfx/2006/xaml/presentation/options
. PresentationOptions
is the recommended prefix for mapping this namespace:
xmlns:PresentationOptions="https://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
Because not all XAML readers recognize this attribute, it's recommended that you use the mc:Ignorable Attribute to mark the Presentation:Freeze
attribute as ignorable:
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="PresentationOptions"
For more information, see the mc:Ignorable Attribute page.
"Unfreezing" a Freezable
Once frozen, a Freezable can never be modified or unfrozen; however, you can create an unfrozen clone using the Clone or CloneCurrentValue method.
In the following example, the button's background is set with a brush and that brush is then frozen. An unfrozen copy is made of the brush using the Clone method. The clone is modified and used to change the button's background from yellow to red.
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;
Note: |
---|
Regardless of which clone method you use, animations are never copied to the new Freezable. |
The Clone and CloneCurrentValue methods produce deep copies of the freezable. If the freezable contains other frozen freezable objects, they are also cloned and made modifiable. For example, if you clone a frozen PathGeometry to make it modifiable, the figures and segments it contains are also copied and made modifiable.
Creating Your Own Freezable Class
A class that derives from Freezable gains the following features.
Special states: a read-only (frozen) and a writable state.
Thread safety: a frozen Freezable can be shared across threads.
Detailed change notification: Unlike other DependencyObjects, Freezable objects provide change notifications when sub-property values change.
Easy cloning: the Freezable class has already implemented several methods that produce deep clones.
A Freezable is a type of DependencyObject, and therefore uses the dependency property system. Your class properties don't have to be dependency properties, but using dependency properties will reduce the amount of code you have to write, because the Freezable class was designed with dependency properties in mind. For more information about the dependency property system, see the Dependency Properties Overview.
Every Freezable subclass must override the CreateInstanceCore method. If your class uses dependency properties for all its data, you're finished.
If your class contains non-dependency property data members, you must also override the following methods:
You must also observe the following rules for accessing and writing to data members that are not dependency properties:
At the beginning of any API that reads non-dependency property data members, call the ReadPreamble method.
At the beginning of any API that writes non-dependency property data members, call the WritePreamble method. (Once you've called WritePreamble in an API, you don't need to make an additional call to ReadPreamble if you also read non-dependency property data members.)
Call the WritePostscript method before exiting methods that write to non-dependency property data members.
If your class contains non-dependency-property data members that are DependencyObject objects, you must also call the OnFreezablePropertyChanged method each time you change on of their values, even if you're setting the member to null.
Note It's very important that you begin each Freezable method you override with a call to the base implementation.
For an example of a custom Freezable class, see the Custom Animation Sample.
See Also
Reference
Concepts
Dependency Properties Overview
Custom Dependency Properties