Información general sobre objetos Freezable

En este tema se describe cómo usar y crear eficazmente objetos Freezable, que proporcionan características especiales que pueden ayudar a mejorar el rendimiento de la aplicación. Algunos ejemplos de objetos Freezable son los pinceles, los lápices, las transformaciones, las geometrías y las animaciones.

¿Qué es un objeto Freezable?

Un objeto Freezable es un tipo especial de objeto que tiene dos estados: no inmovilizado e inmovilizado. Cuando no está inmovilizado, Freezable parece comportarse como cualquier otro objeto. Cuando está inmovilizado, Freezable ya no se puede modificar.

Un objeto Freezable proporciona un evento Changed para avisar a quienes lo estén viendo de cualquier modificación en el objeto. Inmovilizar un objeto Freezable puede mejorar su rendimiento, ya que no necesita dedicar recursos a mantener notificaciones de cambios. Un objeto Freezable inmovilizado se puede compartir entre subprocesos, cosa que no es posible con un objeto Freezable no inmovilizado.

Aunque la clase Freezable tiene muchos usos, la mayoría de los objetos Freezable de Windows Presentation Foundation (WPF) están relacionados con el subsistema de gráficos.

La clase Freezable facilita el uso de determinados objetos del sistema de gráficos y puede ayudar a mejorar el rendimiento de la aplicación. Entre los ejemplos de tipos que heredan de Freezable están las clases Brush, Transform y Geometry. Dado que contienen recursos no administrados, el sistema debe supervisar estos objetos para saber si existen modificaciones y, tras ello, actualizar los recursos no administrados correspondientes cuando haya un cambio en el objeto original. Aun cuando realmente no se modifique un objeto del sistema de gráficos, el sistema debe seguir gastando algunos de sus recursos en supervisarlo, por si cambiara.

Por ejemplo, supongamos que crea un pincel SolidColorBrush y lo usa para representar el fondo de un botón.

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

Cuando el botón se representa, el subsistema de gráficos de WPF usa la información proporcionada para representar un grupo de píxeles para crear la apariencia de un botón. Aunque ha usado un pincel de color sólido para describir cómo debe representarse el botón, el pincel de color sólido no se encarga de representarlo de facto, sino que es el sistema de gráficos el que genera objetos rápidos y de bajo nivel para el botón y el pincel, y esos objetos son los que realmente aparecen en la pantalla.

Si modificara el pincel, esos objetos de bajo nivel tendrían que volver a generarse. La clase Freezable es la que proporciona a un pincel la capacidad de encontrar sus objetos generados y de bajo nivel correspondientes y actualizarlos cuando cambie. Cuando esta capacidad está habilitada, se dice que el pincel "no está inmovilizado".

El método Freeze de una clase Freezable permite deshabilitar esta capacidad de actualización automática. Puede usar este método para "inmovilizar" el pincel; es decir, para que no se pueda modificar.

Nota:

No todos los objetos Freezable se pueden inmovilizar. Para evitar iniciar una excepción InvalidOperationException, compruebe el valor de la propiedad CanFreeze del objeto Freezable para saber si se puede inmovilizar antes de intentar inmovilizarlo.

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

Cuando ya no necesite modificar un objeto Freezable, inmovilizarlo mejora su rendimiento. Si se inmovilizara el pincel de nuestro ejemplo, el sistema de gráficos ya no tendría que supervisarlo para ver si hay cambios. Aparte, el sistema de gráficos también podría realizar otras optimizaciones, ya que sabe que el pincel no va a cambiar.

Nota:

Por comodidad, los objetos Freezable no están inmovilizados, a menos que se inmovilicen expresamente.

Uso de objetos Freezable

Usar un objeto Freezable que no está inmovilizado es como usar cualquier otro tipo de objeto. En el ejemplo siguiente, el color de un objeto SolidColorBrush cambia de amarillo a rojo después de haberlo usado para representar el fondo de un botón. El sistema de gráficos hace su trabajo en segundo plano para cambiar automáticamente el botón de amarillo a rojo la próxima vez que se actualice la pantalla.

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

Inmovilización de un objeto Freezable

Para que un objeto Freezable no se pueda modificar, llame a su método Freeze. Cuando se inmoviliza un objeto que contiene objetos Freezable, estos también se inmovilizan. Por ejemplo, si inmovilizara un objeto PathGeometry, las figuras y los segmentos que contiene también se inmovilizarían.

Un objeto Freezable no se puede inmovilizar si se cumple alguna de las siguientes condiciones:

  • Tiene propiedades animadas o enlazadas a datos.

  • Tiene propiedades establecidas por un recurso dinámico (vea Recursos de XAML para obtener más información sobre los recursos dinámicos).

  • Contiene subobjetos Freezable que no se pueden inmovilizar.

Si no se cumple ninguna de estas condiciones y no tiene intención de modificar el objeto Freezable, conviene entonces inmovilizarlo para obtener las ventajas de rendimiento descritas anteriormente.

Una vez que se llama al método Freeze de un objeto Freezable, ya no se puede modificar. Si se intenta modificar un objeto inmovilizado, se produce una excepción InvalidOperationException. El código siguiente produce una excepción, porque intentamos modificar el pincel después de que se haya inmovilizado.


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

Para evitar iniciar esta excepción, puede usar el método IsFrozen para determinar si un objeto Freezable está inmovilizado.


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


En el ejemplo de código anterior, se realizó una copia modificable de un objeto inmovilizado mediante el método Clone. En la sección siguiente se describe la clonación con más detalle.

Nota:

Un objeto Freezable inmovilizado no se puede animar, con lo cual el sistema de animación creará automáticamente clones modificables de objetos Freezable inmovilizados cuando intente animarlos con un Storyboard. Para eliminar la sobrecarga de rendimiento causada por la clonación, deje un objeto sin inmovilizar si piensa animarlo. Para obtener más información sobre cómo animar con guiones gráficos, vea Información general sobre guiones gráficos.

Congelación desde el marcado

Para inmovilizar un objeto Freezable declarado en el marcado, use el atributo PresentationOptions:Freeze. En el ejemplo siguiente, un objeto SolidColorBrush se declara como un recurso de página y se inmoviliza. Luego, se usa para establecer el fondo de un botón.

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

Para usar el atributo Freeze, debe asignar el espacio de nombres de las opciones de presentación: http://schemas.microsoft.com/winfx/2006/xaml/presentation/options. PresentationOptions es el prefijo recomendado para asignar este espacio de nombres:

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

Dado que no todos los lectores de XAML reconocen este atributo, se recomienda usar el atributo mc:Ignorable para marcar el atributo PresentationOptions:Freeze como susceptible de omitirse:

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

Para obtener más información, vea la página del atributo mc:Ignorable.

"Anulación de la inmovilización" de un objeto Freezable

Una vez inmovilizado, un objeto Freezable nunca se puede modificar o poner en un estado no inmovilizado, pero sí se puede crear un clon que no esté inmovilizado mediante los métodos Clone o CloneCurrentValue.

En el ejemplo siguiente, el fondo del botón se establece con un pincel, tras lo cual ese pincel se inmoviliza. Se hace una copia no inmovilizada del pincel mediante el método Clone. Este clon se modifica y se usa para cambiar el fondo del botón de amarillo a rojo.

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

Nota:

Independientemente del método de clonación usado, las animaciones nunca se copian en el nuevo objeto Freezable.

Los métodos Clone y CloneCurrentValue generan copias en profundidad del objeto Freezable. Si el objeto Freezable contiene otros objetos Freezable inmovilizados, también se clonarán y podrán modificarse. Por ejemplo, si clona un objeto PathGeometry inmovilizado para que pueda modificarse, las figuras y segmentos que contiene también se copian y se pueden modificar.

Creación de su propia clase Freezable

Una clase que deriva de Freezable se beneficia de lo siguiente:

  • Estados especiales: un estado de solo lectura (inmovilizado) y un estado grabable.

  • Seguridad para subprocesos: un objeto Freezable inmovilizado se puede compartir entre varios subprocesos.

  • Notificación de cambios detallada: a diferencia de otros objetos DependencyObject, los objetos Freezable muestran notificaciones de cambio cuando los valores de la subpropiedad cambian.

  • Clonación sencilla: la clase Freezable ya ha implementado varios métodos que producen clones profundos.

Un objeto Freezable es de tipo DependencyObject y, por tanto, usa el sistema de propiedades de dependencia. Las propiedades de clase no tienen que ser propiedades de dependencia, pero el uso de propiedades de dependencia reducirá la cantidad de código que tiene que escribir, ya que la clase Freezable se ha diseñado teniendo en cuenta las propiedades de dependencia. Para obtener más información sobre el sistema de propiedades de dependencia, vea Introducción a las propiedades de dependencia.

Cada subclase Freezable debe invalidar el método CreateInstanceCore. Si la clase usa propiedades de dependencia en todos sus datos, no tendrá que hacer nada más.

Si la clase contiene miembros de datos de propiedad que no son de dependencia, deberá invalidar también los métodos siguientes:

También debe respetar las siguientes reglas para poder acceder a miembros de datos que no son propiedades de dependencia y escribir en ellos:

  • Al principio de cualquier API que lee miembros de datos de propiedad que no son de dependencia, llame al método ReadPreamble.

  • Al principio de cualquier API que escribe en miembros de datos de propiedad que no son de dependencia, llame al métodoWritePreamble. (una vez que haya llamado a WritePreamble en una API, no es necesario realizar una llamada adicional a ReadPreamble si también lee miembros de datos de propiedad que no son de dependencia).

  • Llame al método WritePostscript antes de salir de los métodos que escriben en miembros de datos de propiedad que no son de dependencia.

Si la clase contiene miembros de datos que no son de propiedad de dependencia y que son objetos DependencyObject, también deberá llamar al método OnFreezablePropertyChanged cada vez que cambie uno de sus valores, aunque el miembro esté establecido en null.

Nota:

Es muy importante comenzar cada método Freezable que invalide con una llamada a la implementación base.

Para obtener un ejemplo de una clase Freezable personalizada, vea este ejemplo de animación personalizada.

Vea también