Introducción a las propiedades de dependencia (WPF .NET)

Windows Presentation Foundation (WPF) proporciona un conjunto de servicios que se pueden usar para ampliar la funcionalidad de la propiedad de un tipo. Colectivamente, estos servicios se conocen como el sistema de propiedades de WPF. Una propiedad que está respaldada por el sistema de propiedades de WPF se conoce como propiedad de dependencia. En esta información general se describe el sistema de propiedades de WPF y las funciones de una propiedad de dependencia, incluido cómo usar las propiedades de dependencia existentes en XAML y en el código. Esta información general también presenta aspectos especializados de las propiedades de dependencia, como los metadatos de las propiedades de dependencia y el proceso de creación de una propiedad de dependencia propia en una clase personalizada.

Importante

La documentación de la Guía de escritorio para .NET 6 y .NET 5 (incluido .NET Core 3.1) está en elaboración.

Requisitos previos

En este artículo se da por supuesto el conocimiento básico del sistema de tipos de .NET y la programación orientada a objetos. Comprender XAML y saber cómo escribir aplicaciones WPF ayudará a seguir los ejemplos de este artículo. Para obtener más información, vea Tutorial: Mi primera aplicación de escritorio WPF.

Propiedades de dependencia y propiedades CLR

Las propiedades de WPF suelen exponerse como propiedadesestándar de .NET. Puede interactuar con estas propiedades en un nivel básico y no saber nunca que se implementan como una propiedad de dependencia. Sin embargo, la familiaridad con algunas o todas las características del sistema de propiedades de WPF le ayudará a aprovechar esas características.

El propósito de las propiedades de dependencia es proporcionar una manera de calcular el valor de una propiedad en función del valor de otras entradas, como:

  • Propiedades del sistema, como temas y preferencias de usuario.
  • Mecanismos de determinación de propiedades Just-In-Time, como enlaces de datos y animaciones o guiones gráficos.
  • Plantillas de varios usos, como recursos y estilos.
  • Valores conocidos a través de relaciones de elementos primarios y secundarios con otros elementos del árbol de elementos.

Además, una propiedad de dependencia puede proporcionar:

  • Validación autocontenida.
  • Valores predeterminados.
  • Devoluciones de llamada que supervisan los cambios en otras propiedades.
  • Un sistema que puede convertir los valores de propiedad en función de la información del tiempo de ejecución.

Las clases derivadas pueden cambiar algunas características de una propiedad existente invalidando los metadatos de una propiedad de dependencia, en lugar de invalidar la implementación real de las propiedades existentes o creando nuevas propiedades.

En la referencia del SDK puede identificar una propiedad de dependencia mediante la presencia de una sección Información de propiedad de dependencia en la página de referencia administrada de esa propiedad. La sección Información de propiedad de dependencia incluye un vínculo al campo DependencyProperty de identificador de esa propiedad de dependencia. También incluye la lista de opciones de metadatos para esa propiedad, información de invalidación por clase y otros detalles.

Las propiedades de dependencia respaldan las propiedades CLR

Las propiedades de dependencia y el sistema de propiedades de WPF amplían la funcionalidad de propiedad proporcionando un tipo que hace una copia de seguridad de una propiedad, como alternativa al patrón estándar de respaldo de una propiedad con un campo privado. El nombre de este tipo es DependencyProperty. El otro tipo importante que define el sistema de propiedades de WPF es DependencyObject, que define la clase base que puede registrar y poseer una propiedad de dependencia.

Esta es una terminología de uso frecuente:

  • Propiedad de dependencia, que es una propiedad que está copiada por un DependencyProperty.

  • Identificador de propiedad de dependencia, que es una instancia de DependencyProperty obtenida como un valor devuelto al registrar una propiedad de dependencia y, a continuación, almacenada como miembro estático de una clase. Muchas de las API que interactúan con el sistema de propiedades de WPF usan el identificador de propiedad de dependencia como parámetro.

  • «Contenedor» de CLR, que son las implementaciones de get y set para la propiedad. Estas implementaciones incorporan el identificador de propiedad de dependencia mediante su uso en las llamadas GetValue y SetValue. De esta manera, el sistema de propiedades de WPF proporciona el respaldo para la propiedad.

En el ejemplo siguiente se define la propiedad de dependencia IsSpinningpara mostrar la relación del identificador DependencyProperty con la propiedad que respalda.

public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register(
    "IsSpinning", typeof(bool),
    typeof(MainWindow)
    );

public bool IsSpinning
{
    get => (bool)GetValue(IsSpinningProperty);
    set => SetValue(IsSpinningProperty, value);
}
Public Shared ReadOnly IsSpinningProperty As DependencyProperty =
    DependencyProperty.Register("IsSpinning", GetType(Boolean), GetType(MainWindow))

Public Property IsSpinning As Boolean
    Get
        Return GetValue(IsSpinningProperty)
    End Get
    Set(value As Boolean)
        SetValue(IsSpinningProperty, value)
    End Set
End Property

La convención de nomenclatura de la propiedad y su campo de respaldo DependencyProperty es importante. El nombre del campo siempre es el nombre de la propiedad, con el sufijo Property anexado. Para obtener más información acerca de esta convención y las razones para usarla, consulte Propiedades de dependencia personalizadas.

Establecimiento de valores de propiedad

Puede establecer propiedades en el código o en XAML.

Establecimiento de valores de propiedad en XAML

En el ejemplo XAML siguiente se establece el color de fondo de un botón en rojo. El analizador XAML de WPF convierte el valor de cadena del atributo XAML en un tipo WPF. En el código generado, el tipo de WPF es un Color por medio de un SolidColorBrush.

<Button Content="I am red" Background="Red"/>

XAML admite varios formas de sintaxis para establecer propiedades. La sintaxis que se va a usar para una propiedad determinada depende del tipo de valor que usa una propiedad y de otros factores, como la presencia de un convertidor de tipos. Para obtener más información sobre la sintaxis XAML para establecer propiedades, consulte XAML en WPF y Sintaxis XAML en detalle.

En el ejemplo XAML siguiente se muestra otro fondo de botón que usa la sintaxis de elemento de propiedad en lugar de la sintaxis de atributo. En lugar de establecer un color sólido simple, el XAML establece la propiedad Background del botón en una imagen. Un elemento representa esa imagen y un atributo del elemento anidado especifica el origen de la imagen.

<Button Content="I have an image background">
    <Button.Background>
        <ImageBrush ImageSource="stripes.jpg"/>
    </Button.Background>
</Button>

Establecimiento de propiedades en el código

Establecer valores de propiedad de dependencia en el código suele ser simplemente una llamada a la implementación set expuesta por el «contenedor» de CLR:

Button myButton = new();
myButton.Width = 200.0;
Dim myButton As New Button With {
    .Width = 200.0
}

Obtener un valor de propiedad es básicamente una llamada a la implementación del «contenedor» de get:

double whatWidth = myButton.Width;
Dim whatWidth As Double = myButton.Width

También puede llamar a las API del sistema de propiedades GetValue y SetValue directamente. Llamar directamente a las API es adecuado para algunos escenarios, pero normalmente no cuando se usan propiedades existentes. Normalmente los contenedores son más convenientes y proporcionan una mejor exposición de la propiedad para las herramientas de desarrollo.

Las propiedades también se pueden establecer en XAML y se puede acceder a ellas más adelante en el código a través del código subyacente. Para obtener información detallada, consulte Código subyacente y XAML en WPF.

Funcionalidad de propiedad proporcionada por una propiedad de dependencia

A diferencia de una propiedad que está copiada por un campo, una propiedad de dependencia amplía la funcionalidad de una propiedad. A menudo la funcionalidad agregada representa o admite una de estas características:

Recursos

Puede establecer un valor de propiedad de dependencia haciendo referencia a un recurso. Normalmente los recursos se especifican como el valor de propiedad Resources de un elemento raíz de página o de la aplicación, ya que estas ubicaciones ofrecen un acceso cómodo al recurso. En este ejemplo se define un recurso SolidColorBrush:

<StackPanel.Resources>
    <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
</StackPanel.Resources>

Ahora que el recurso está definido, podemos hacer referencia al recurso para proporcionar un valor para la propiedad Background:

<Button Background="{DynamicResource MyBrush}" Content="I am gold" />

En XAML de WPF puede usar una referencia de recursos estáticos o dinámicos. Se hace referencia a este recurso concreto como una DynamicResource. Una referencia de recursos dinámicos solo se puede usar para establecer una propiedad de dependencia, por lo que es específicamente el uso de referencia de recursos dinámicos habilitado por el sistema de propiedades de WPF. Para obtener más información, consulte Recursos XAML.

Nota:

Los recursos se tratan como un valor local, lo que significa que si establece otro valor local, eliminará la referencia de recursos. Para obtener más información, consulte Prioridad de los valores de propiedades de dependencia.

Enlace de datos

Una propiedad de dependencia puede hacer referencia a un valor mediante el enlace de datos. El enlace de datos funciona mediante una sintaxis de extensión de marcado específica en XAML, o bien mediante el objeto Binding en el código. Con el enlace de datos la determinación del valor de propiedad final se aplaza hasta el tiempo de ejecución, momento en el que el valor se obtiene de un origen de datos.

En el ejemplo siguiente se establece la propiedad Content para un Button mediante un enlace declarado en XAML. El enlace usa un contexto de datos heredado y un origen de datos XmlDataProvider (no se muestra). El propio enlace especifica la propiedad de origen dentro del origen de datos mediante XPath.

<Button Content="{Binding Source={StaticResource TestData}, XPath=test[1]/@text}"/>

Nota:

Los enlaces se tratan como un valor local, lo que significa que si establece otro valor local, eliminará el enlace. Para obtener más información, consulte Prioridad de los valores de propiedades de dependencia.

Ni las propiedades de dependencia ni la clase DependencyObject admiten INotifyPropertyChanged de forma nativa para la notificación de cambios en el valor de propiedad de origen DependencyObject para las operaciones de enlace de datos. Para obtener más información sobre cómo crear propiedades para su uso en el enlace de datos que pueden notificar cambios en un destino de enlace de datos, consulte Información general sobre el enlace de datos.

Estilos

Los estilos y las plantillas son razones atractivas para usar las propiedades de dependencia. Los estilos son especialmente útiles para establecer propiedades que definen la interfaz de usuario de la aplicación. Los estilos se definen normalmente como recursos en XAML. Los estilos interactúan con el sistema de propiedades porque normalmente contienen «establecedores» para propiedades concretas y «desencadenadores» que cambian un valor de propiedad basado en el valor en tiempo de ejecución de otra propiedad.

En el ejemplo siguiente se crea un estilo simple que se definiría dentro de un diccionario Resources (que no se muestra). A continuación, ese estilo se aplica directamente a la propiedad Style para un Button. El establecedor dentro del estilo establece la propiedad Background de un Button con estilo en el color verde.

<Style x:Key="GreenButtonStyle">
    <Setter Property="Control.Background" Value="Green"/>
</Style>
<Button Style="{StaticResource GreenButtonStyle}" Content="I am green"/>

Para obtener más información, vea Aplicar estilos y plantillas.

Animaciones

Las propiedades de dependencia se pueden animar. Cuando se ejecuta una animación aplicada, el valor animado tiene mayor prioridad que cualquier otro valor de propiedad, incluido un valor local.

En el ejemplo siguiente se anima la propiedad Background de un Button. Técnicamente, la sintaxis del elemento de propiedad establece un espacio en blanco SolidColorBrush como Background y la propiedad Color del SolidColorBrush se anima.

<Button Content="I am animated">
    <Button.Background>
        <SolidColorBrush x:Name="AnimBrush"/>
    </Button.Background>
    <Button.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation
                        Storyboard.TargetName="AnimBrush" 
                        Storyboard.TargetProperty="(SolidColorBrush.Color)"
                        From="Blue" To="White" Duration="0:0:1" 
                        AutoReverse="True" RepeatBehavior="Forever" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

Para obtener más información sobre la animación de propiedades, consulte Información general sobre animaciones e Información general sobre guiones gráficos.

Invalidaciones de metadatos

Puede cambiar comportamientos específicos de una propiedad de dependencia invalidando sus metadatos cuando derive de la clase que ha registrado originalmente la propiedad de dependencia. La invalidación de metadatos se basa en el identificador DependencyProperty y no requiere volver a implementar la propiedad. El sistema de propiedades controla de forma nativa el cambio de metadatos. Cada clase puede contener metadatos individuales para todas las propiedades heredadas de las clases base, por tipo.

En el ejemplo siguiente se invalidan los metadatos de una propiedad de dependencia DefaultStyleKey. La invalidación de metadatos para esta propiedad de dependencia determinada forma parte de un patrón de implementación para crear controles que pueden usar estilos predeterminados de temas.

public class SpinnerControl : ItemsControl
{
    static SpinnerControl() => DefaultStyleKeyProperty.OverrideMetadata(
            typeof(SpinnerControl),
            new FrameworkPropertyMetadata(typeof(SpinnerControl))
        );
}
Public Class SpinnerControl
    Inherits ItemsControl
    Shared Sub New()
        DefaultStyleKeyProperty.OverrideMetadata(GetType(SpinnerControl), New FrameworkPropertyMetadata(GetType(SpinnerControl)))
    End Sub
End Class

Para obtener más información sobre cómo invalidar u obtener acceso a los metadatos de las propiedades de dependencia, consulte Invalidar metadatos para una propiedad de dependencia.

Herencia de valores de propiedad

Un elemento puede heredar el valor de una propiedad de dependencia de su elemento primario en el árbol de objetos.

Nota:

El comportamiento de herencia de valores de propiedad no está habilitado globalmente para todas las propiedades de dependencia, porque el tiempo de cálculo de la herencia afecta al rendimiento. La herencia de valores de propiedad normalmente solo se habilita en escenarios que sugieren aplicabilidad. Para comprobar si una propiedad de dependencia hereda, consulte la sección Información de la propiedad de dependencia para esa propiedad de dependencia en la referencia del SDK.

En el ejemplo siguiente se muestra un enlace que incluye la propiedad DataContext para especificar el origen del enlace. Por lo tanto, los enlaces de objetos secundarios no necesitan especificar el origen y pueden usar el valor heredado de DataContext en el objeto primario StackPanel. O bien, un objeto secundario puede especificar directamente su propio DataContext o un Source en el Binding y no usar el valor heredado.

<StackPanel Canvas.Top="50" DataContext="{Binding Source={StaticResource TestData}}">
    <Button Content="{Binding XPath=test[2]/@text}"/>
</StackPanel>

Para más información, consulte Herencia de valores de propiedad.

Integración de WPF Designer

Los controles personalizados con propiedades implementadas como propiedades de dependencia se integran bien con WPF Designer para Visual Studio. Un ejemplo es la capacidad de editar propiedades de dependencia directas y adjuntas en la ventana Propiedades. Para obtener más información, consulte Información general sobre la creación de controles.

Prioridad de los valores de propiedades de dependencia

Cualquiera de las entradas basadas en propiedades dentro del sistema de propiedades de WPF puede establecer el valor de una propiedad de dependencia. La prioridad de los valores de propiedad de dependencia existe para que los distintos escenarios de cómo obtienen las propiedades sus valores interactúen de una manera predecible.

Nota:

La documentación del SDK a veces usa el término «valor local» o «valor establecido localmente» al analizar las propiedades de dependencia. Un valor establecido localmente es un valor de propiedad que se establece directamente en una instancia de objeto en el código o como un atributo de elemento en XAML.

En el ejemplo siguiente se incluye un estilo que se aplica a la propiedad Background de cualquier botón, pero especifica un botón con una propiedad Background establecida localmente. Técnicamente, ese botón tiene su propiedad Background establecida dos veces, aunque solo se aplica un valor: el valor con la prioridad más alta. Un valor establecido localmente tiene la prioridad más alta, excepto para una animación en ejecución, que no existe aquí. Por lo tanto, el primer botón usa el valor establecido localmente para la propiedad Background, en lugar del valor del establecedor de estilo. El segundo botón no tiene ningún valor local u otro valor con mayor precedencia que un establecedor de estilo, por lo que usa el valor de establecedor de estilo para la propiedad Background.

<StackPanel>
    <StackPanel.Resources>
        <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
            <Setter Property="Background" Value="Orange"/>
        </Style>
    </StackPanel.Resources>
    <Button>I am styled orange</Button>
    <Button Background="Pink">I am locally set to pink (not styled orange)</Button>
</StackPanel>

¿Por qué existe la precedencia de las propiedades de dependencia?

Los valores establecidos localmente tienen prioridad sobre los valores de establecedor de estilo, que admite el control local de las propiedades de elemento. Para obtener más información, consulte Prioridad de los valores de propiedades de dependencia.

Nota:

Algunas propiedades definidas en elementos WPF no son propiedades de dependencia, porque las propiedades de dependencia se implementaban normalmente solo cuando se requería una característica del sistema de propiedades de WPF. Las características incluyen el enlace de datos, el estilo, la animación, la compatibilidad con valores predeterminados, la herencia, las propiedades adjuntas y la invalidación.

Más información sobre las propiedades de dependencia

  • Es posible que los desarrolladores de componentes o desarrolladores de aplicaciones quieran crear su propia propiedad de dependencia para agregar funcionalidades, como compatibilidad con estilos o enlaces de datos, o compatibilidad con la invalidación y la coerción de valores. Para obtener más información, consulte Propiedades de dependencia personalizadas.

  • Considere las propiedades de dependencia como propiedades públicas, accesibles o detectables por cualquier autor de llamada con acceso a una instancia. Para obtener más información, consulte Seguridad de las propiedades de dependencia.

  • Una propiedad adjunta es un tipo de propiedad que admite una sintaxis especializada en XAML. A menudo una propiedad adjunta no tiene una correspondencia 1:1 con una propiedad de Common Language Runtime ni es necesariamente una propiedad de dependencia. El propósito principal de una propiedad adjunta es permitir que los elementos secundarios informen de los valores de propiedad a un elemento primario, incluso si el elemento primario y el elemento secundario no incluyen esa propiedad como parte de las listas de miembros de clase. Un escenario principal es habilitar un elemento secundario para informar a los elementos primarios de cómo presentarlos en la interfaz de usuario. Como ejemplos, vea Dock y Left. Para obtener más información, consulte Información general sobre propiedades adjuntas.

Vea también