Introducción a las propiedades de dependencia

En este tema se explica el sistema de propiedades de dependencia que está disponible al escribir una aplicación de Windows Runtime mediante C++, C# o Visual Basic junto con definiciones xaml para la interfaz de usuario.

¿Qué es una propiedad de dependencia?

Una propiedad de dependencia es un tipo especializado de propiedad. En concreto, es una propiedad en la que se realiza un seguimiento del valor de la propiedad e influye en un sistema de propiedades dedicado que forma parte de Windows Runtime.

Para admitir una propiedad de dependencia, el objeto que define la propiedad debe ser DependencyObject (es decir, una clase que tenga la clase base DependencyObject en algún lugar de su herencia). Muchos de los tipos que usas para las definiciones de interfaz de usuario de una aplicación para UWP con XAML serán una subclase DependencyObject y admitirán propiedades de dependencia. Sin embargo, cualquier tipo que proceda de un espacio de nombres de Windows Runtime que no tenga "XAML" en su nombre no admitirá propiedades de dependencia; las propiedades de estos tipos son propiedades normales que no tendrán el comportamiento de dependencia del sistema de propiedades.

El propósito de las propiedades de dependencia es proporcionar una manera sistémica de calcular el valor de una propiedad en función de otras entradas (otras propiedades, eventos y estados que se producen dentro de la aplicación mientras se ejecuta). Estas otras entradas pueden incluir:

  • Entrada externa, como la preferencia del usuario
  • Mecanismos de determinación de propiedades Just-In-Time, como el enlace de datos, animaciones y guiones gráficos
  • Patrones de 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 objetos

Una propiedad de dependencia representa o admite una característica específica del modelo de programación para definir una aplicación de Windows Runtime con XAML para la interfaz de usuario y C#, Las extensiones de componentes de Microsoft Visual Basic o Visual C++ (C++/CX) para el código. Entre ellas se incluyen:

  • Enlace de datos
  • Estilos
  • Animaciones con guion gráfico
  • Comportamiento "PropertyChanged"; se puede implementar una propiedad de dependencia para proporcionar devoluciones de llamada que puedan propagar los cambios a otras propiedades de dependencia.
  • Uso de un valor predeterminado que procede de los metadatos de propiedad
  • Utilidad del sistema de propiedades general, como ClearValue y búsqueda de metadatos

Propiedades de dependencia y propiedades de Windows Runtime

Las propiedades de dependencia amplían la funcionalidad básica de propiedades de Windows Runtime proporcionando un almacén de propiedades interno global que respalda todas las propiedades de dependencia de una aplicación en tiempo de ejecución. Se trata de una alternativa al patrón estándar de respaldo de una propiedad con un campo privado que es privado en la clase property-definition. Puede pensar en este almacén de propiedades interno como un conjunto de identificadores de propiedad y valores que existen para cualquier objeto determinado (siempre que sea un DependencyObject). En lugar de identificarse por nombre, cada propiedad del almacén se identifica mediante una instancia de DependencyProperty. Sin embargo, el sistema de propiedades oculta principalmente este detalle de implementación: normalmente puede acceder a las propiedades de dependencia mediante un nombre simple (el nombre de la propiedad mediante programación en el lenguaje de código que usa o un nombre de atributo al escribir XAML).

El tipo base que proporciona los fundamentos del sistema de propiedades de dependencia es DependencyObject. DependencyObject define métodos que pueden tener acceso a la propiedad de dependencia y las instancias de una clase derivada DependencyObject admiten internamente el concepto de almacén de propiedades mencionado anteriormente.

Esta es una suma de la terminología que usamos en la documentación al analizar las propiedades de dependencia:

Término Descripción
Propiedad de dependencia Propiedad que existe en un identificador DependencyProperty (consulte a continuación). Normalmente, este identificador está disponible como miembro estático de la clase derivada DependencyObject que define.
Identificador de propiedad de dependencia Valor constante para identificar la propiedad, normalmente es público y de solo lectura.
Contenedor de propiedades Las implementaciones get y set invocables para una propiedad de Windows Runtime. O bien, la proyección específica del lenguaje de la definición original. Una implementación del contenedor de propiedades get llama a GetValue y pasa el identificador de propiedad de dependencia correspondiente.

El contenedor de propiedades no solo es cómodo para los autores de llamadas, sino que también expone la propiedad de dependencia a cualquier proceso, herramienta o proyección que use definiciones de Windows Runtime para las propiedades.

En el ejemplo siguiente se define una propiedad de dependencia personalizada tal como se define para C#, y se muestra la relación del identificador de propiedad de dependencia con el contenedor de propiedades.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(string),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);


public string Label
{
    get { return (string)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}

Nota:

El ejemplo anterior no está pensado como ejemplo completo para crear una propiedad de dependencia personalizada. Está pensado para mostrar conceptos de propiedades de dependencia para cualquier persona que prefiera conceptos de aprendizaje a través del código. Para obtener una explicación más completa de este ejemplo, consulte Propiedades de dependencia personalizadas.

Prioridad de los valores de propiedades de dependencia

Cuando obtienes el valor de una propiedad de dependencia, obtienes un valor determinado para esa propiedad a través de cualquiera de las entradas que participan en el sistema de propiedades de Windows Runtime. La prioridad del valor de propiedad de dependencia existe para que el sistema de propiedades de Windows Runtime pueda calcular valores de forma predecible y es importante que también esté familiarizado con el orden de precedencia básico. De lo contrario, es posible que se encuentre en una situación en la que está intentando establecer una propiedad en un nivel de precedencia, pero algo más (el sistema, los autores de llamadas de terceros, algunos de su propio código) lo está estableciendo en otro nivel, y se frustrará intentando averiguar de qué valor de propiedad se usa y de dónde procede ese valor.

Por ejemplo, los estilos y las plantillas están diseñados para ser un punto de partida compartido para establecer valores de propiedad y, por tanto, apariencias de un control. Sin embargo, en una instancia de control determinada es posible que desee cambiar su valor frente al valor de plantilla común, como proporcionar ese control a un color de fondo diferente o a una cadena de texto diferente como contenido. El sistema de propiedades de Windows Runtime tiene en cuenta los valores locales con mayor prioridad que los valores proporcionados por estilos y plantillas. Esto permite que el escenario de tener valores específicos de la aplicación sobrescriba las plantillas para que los controles sean útiles para su propio uso en la interfaz de usuario de la aplicación.

Lista de prioridades de las propiedades de dependencia

A continuación se muestra el orden definitivo que usa el sistema de propiedades al asignar el valor en tiempo de ejecución para una propiedad de dependencia. La precedencia más alta aparece primero. Encontrará explicaciones más detalladas justo después de esta lista.

  1. Valores animados: animaciones activas, animaciones de estado visual o animaciones con un comportamiento HoldEnd. Para tener cualquier efecto práctico, una animación aplicada a una propiedad debe tener prioridad sobre el valor base (unánime), incluso si ese valor se estableció localmente.
  2. Valor local: un valor local se puede establecer a través de la comodidad del contenedor de propiedades, que también equivale a establecer como un atributo o elemento de propiedad en XAML, o mediante una llamada al método SetValue mediante una propiedad de una instancia específica. Si establece un valor local mediante un enlace o un recurso estático, cada uno actúa en la precedencia como si se hubiera establecido un valor local y se borran los enlaces o las referencias de recursos si se establece un nuevo valor local.
  3. Propiedades con plantilla: un elemento tiene estos elementos si se creó como parte de una plantilla (desde controlTemplate o DataTemplate).
  4. Establecedores de estilo: valores de un establecedor dentro de estilos de los recursos de la página o de la aplicación.
  5. Valor predeterminado: una propiedad de dependencia puede tener un valor predeterminado como parte de sus metadatos.

Propiedades con plantilla

Las propiedades con plantilla como elemento de precedencia no se aplican a ninguna propiedad de un elemento que declares directamente en el marcado de página XAML. El concepto de propiedad con plantilla solo existe para los objetos que se crean cuando Windows Runtime aplica una plantilla XAML a un elemento de interfaz de usuario y, por tanto, define sus objetos visuales.

Todas las propiedades que se establecen desde una plantilla de control tienen valores de algún tipo. Estos valores son casi como un conjunto extendido de valores predeterminados para el control y a menudo están asociados a valores que se pueden restablecer más adelante estableciendo los valores de propiedad directamente. Por lo tanto, los valores del conjunto de plantillas deben distinguirse de un valor local true, de modo que cualquier nuevo valor local pueda sobrescribirlo.

Nota:

En algunos casos, la plantilla podría invalidar incluso los valores locales, si la plantilla no pudo exponer las referencias de extensión de marcado {TemplateBinding} para las propiedades que deben haberse establecido en instancias. Normalmente, esto solo se hace si la propiedad no está pensada para establecerse en instancias, por ejemplo, si solo es relevante para los objetos visuales y el comportamiento de la plantilla y no para la función prevista o la lógica en tiempo de ejecución del control que usa la plantilla.

Enlaces y precedencia

Las operaciones de enlace tienen la prioridad adecuada para cualquier ámbito para el que se usen. Por ejemplo, un {Binding} aplicado a un valor local actúa como valor local y una extensión de marcado {TemplateBinding} para un establecedor de propiedades se aplica como establecedor de estilo. Dado que los enlaces deben esperar hasta el tiempo de ejecución para obtener valores de orígenes de datos, el proceso de determinar la prioridad del valor de propiedad para cualquier propiedad se extiende también en tiempo de ejecución.

No solo los enlaces funcionan con la misma prioridad que un valor local, en realidad son un valor local, donde el enlace es el marcador de posición de un valor aplazado. Si tiene un enlace en su lugar para un valor de propiedad y establece un valor local en él en tiempo de ejecución, que reemplaza el enlace por completo. Del mismo modo, si llamas a SetBinding para definir un enlace que solo exista en tiempo de ejecución, reemplazas cualquier valor local que hayas aplicado en XAML o con código ejecutado previamente.

Animaciones con guion gráfico y valor base

Las animaciones con guion gráfico actúan sobre un concepto de valor base. El valor base es el valor determinado por el sistema de propiedades mediante su precedencia, pero se omite ese último paso de búsqueda de animaciones. Por ejemplo, un valor base puede provenir de la plantilla de un control, o puede provenir de establecer un valor local en una instancia de un control. En cualquier caso, la aplicación de una animación sobrescribirá este valor base y aplicará el valor animado mientras la animación continúe ejecutándose.

Para una propiedad animada, el valor base todavía puede tener un efecto en el comportamiento de la animación, si esa animación no especifica explícitamente From y To, o si la animación revierte la propiedad a su valor base cuando se completa. En estos casos, una vez que una animación ya no se está ejecutando, se vuelve a usar el resto de la prioridad.

Sin embargo, una animación que especifica un objeto To con un comportamiento HoldEnd puede invalidar un valor local hasta que se quite la animación, incluso cuando parezca que se detiene visualmente. Conceptualmente esto es como una animación que se ejecuta para siempre incluso si no hay una animación visual en la interfaz de usuario.

Se pueden aplicar varias animaciones a una sola propiedad. Es posible que cada una de estas animaciones se haya definido para reemplazar los valores base procedentes de distintos puntos de la precedencia de valor. Sin embargo, todas estas animaciones se ejecutarán simultáneamente en tiempo de ejecución y, a menudo, significa que deben combinar sus valores porque cada animación tiene la misma influencia en el valor. Esto depende de la exactitud con la que estén definidas las animaciones y del tipo de valor que se esté animando.

Para obtener más información, consulta Animaciones con guion gráfico.

Valores predeterminados

El establecimiento del valor predeterminado para una propiedad de dependencia con un valor PropertyMetadata se explica con más detalle en el tema Propiedades de dependencia personalizadas.

Las propiedades de dependencia siguen teniendo valores predeterminados incluso si esos valores predeterminados no se definieron explícitamente en los metadatos de esa propiedad. A menos que los metadatos los hayan cambiado, los valores predeterminados para las propiedades de dependencia de Windows Runtime suelen ser uno de los siguientes:

  • Una propiedad que usa un objeto en tiempo de ejecución o el tipo de objeto básico (un tipo de referencia) tiene un valor predeterminado de NULL. Por ejemplo, DataContext es NULL hasta que se establece deliberadamente o se hereda.
  • Una propiedad que usa un valor básico como números o un valor booleano (un tipo de valor) usa un valor predeterminado esperado para ese valor. Por ejemplo, 0 para enteros y números de punto flotante, false para un valor booleano.
  • Una propiedad que usa una estructura de Windows Runtime tiene un valor predeterminado que se obtiene llamando al constructor predeterminado implícito de esa estructura. Este constructor usa los valores predeterminados para cada uno de los campos de valor básico de la estructura. Por ejemplo, un valor predeterminado para un valor Point se inicializa con sus valores X e Y como 0.
  • Una propiedad que usa una enumeración tiene un valor predeterminado del primer miembro definido en esa enumeración. Compruebe la referencia de enumeraciones específicas para ver cuál es el valor predeterminado.
  • Una propiedad que usa una cadena (System.String para .NET, Platform::String para C++/CX) tiene un valor predeterminado de una cadena vacía (""").
  • Normalmente, las propiedades de la colección no se implementan como propiedades de dependencia, por motivos que se describen más adelante en este tema. Sin embargo, si implementa una propiedad de colección personalizada y quiere que sea una propiedad de dependencia, asegúrese de evitar un singleton involuntaria, tal como se describe cerca del final de las propiedades de dependencia personalizadas.

Funcionalidad de propiedad proporcionada por una propiedad de dependencia

Enlace de datos

Una propiedad de dependencia puede tener su valor establecido mediante la aplicación de un enlace de datos. El enlace de datos usa la sintaxis de extensión de marcado {Binding} en XAML, extensión de marcado {x:Bind} o la clase Binding en el código. Para una propiedad de entrada de datos, la determinación final del valor de propiedad se aplaza hasta el tiempo de ejecución. En ese momento, el valor se obtiene de un origen de datos. El rol que desempeña el sistema de propiedades de dependencia aquí es habilitar un comportamiento de marcador de posición para operaciones como cargar XAML cuando el valor aún no se conoce y, a continuación, proporcionar el valor en tiempo de ejecución mediante la interacción con el motor de enlace de datos de Windows Runtime.

En el ejemplo siguiente se establece el valor Text de un elemento TextBlock mediante un enlace en XAML. El enlace usa un contexto de datos heredado y un origen de datos de objeto. (Ninguno de estos se muestra en el ejemplo abreviado; para obtener un ejemplo más completo que muestre el contexto y el origen, consulte Enlace de datos en profundidad).

<Canvas>
  <TextBlock Text="{Binding Team.TeamName}"/>
</Canvas>

También puedes establecer enlaces mediante código en lugar de XAML. Consulte SetBinding.

Nota:

Los enlaces como este se tratan como un valor local con fines de prioridad de valor de propiedad de dependencia. Si establece otro valor local para una propiedad que originalmente tenía un valor binding , sobrescribirá completamente el enlace, no solo el valor en tiempo de ejecución del enlace. {x:Bind} Los enlaces se implementan mediante código generado que establecerá un valor local para la propiedad . Si establece un valor local para una propiedad que usa {x:Bind}, ese valor se reemplazará la próxima vez que se evalúe el enlace, como cuando observe un cambio de propiedad en su objeto de origen.

Orígenes de enlace, destinos de enlace, rol de FrameworkElement

Para ser el origen de un enlace, una propiedad no necesita ser una propiedad de dependencia; Por lo general, puede usar cualquier propiedad como origen de enlace, aunque esto depende del lenguaje de programación y cada uno tiene determinados casos perimetrales. Sin embargo, para ser el destino de una extensión de marcado {Binding} o Binding, esa propiedad debe ser una propiedad de dependencia. {x:Bind} no tiene este requisito, ya que usa código generado para aplicar sus valores de enlace.

Si va a crear un enlace en el código, tenga en cuenta que la API SetBinding solo se define para FrameworkElement. Sin embargo, puede crear una definición de enlace mediante BindingOperations en su lugar y, por tanto, hacer referencia a cualquier propiedad DependencyObject.

Para código o XAML, recuerde que DataContext es una propiedad FrameworkElement. Al usar una forma de herencia de propiedades primarias y secundarias (normalmente establecida en marcado XAML), el sistema de enlace puede resolver un DataContext que existe en un elemento primario. Esta herencia puede evaluarse incluso si el objeto secundario (que tiene la propiedad de destino) no es frameworkElement y, por tanto, no contiene su propio valor DataContext. Sin embargo, el elemento primario que se hereda debe ser FrameworkElement para establecer y contener DataContext. Como alternativa, debe definir el enlace de modo que pueda funcionar con un valor NULL para DataContext.

El cableado del enlace no es lo único que se necesita para la mayoría de los escenarios de enlace de datos. Para que un enlace unidireccional o bidireccional sea efectivo, la propiedad de origen debe admitir notificaciones de cambio que se propagan al sistema de enlace y, por tanto, el destino. En el caso de los orígenes de enlace personalizados, esto significa que la propiedad debe ser una propiedad de dependencia o el objeto debe admitir INotifyPropertyChanged. Las colecciones deben admitir INotifyCollectionChanged. Algunas clases admiten estas interfaces en sus implementaciones para que sean útiles como clases base para escenarios de enlace de datos; Un ejemplo de esta clase es ObservableCollection<T>. Para obtener más información sobre el enlace de datos y cómo se relaciona el enlace de datos con el sistema de propiedades, consulte Enlace de datos en profundidad.

Nota:

Los tipos enumerados aquí admiten orígenes de datos de Microsoft .NET. Los orígenes de datos de C++/CX usan interfaces diferentes para la notificación de cambios o el comportamiento observable, consulte Enlace de datos en profundidad.

Estilos y plantillas

Los estilos y las plantillas son dos de los escenarios para las propiedades que se definen como propiedades de dependencia. Los estilos son útiles para establecer propiedades que definen la interfaz de usuario de la aplicación. Los estilos se definen como recursos en XAML, ya sea como entrada en una colección Resources o en archivos XAML independientes, como diccionarios de recursos de tema. Los estilos interactúan con el sistema de propiedades porque contienen establecedores para las propiedades. La propiedad más importante aquí es la propiedad Control.Template de un Control: define la mayoría de la apariencia visual y el estado visual de un control. Para obtener más información sobre los estilos y un ejemplo de XAML que define un estilo y usa establecedores, consulta Controles de estilo.

Los valores que proceden de estilos o plantillas son valores diferidos, similares a los enlaces. Esto es para que los usuarios controle los controles de nueva plantilla o vuelvan a definir estilos. Y por eso los establecedores de propiedades en estilos solo pueden actuar en propiedades de dependencia, no en propiedades normales.

Animaciones con guion gráfico

Puede animar el valor de una propiedad de dependencia mediante una animación con guion gráfico. Las animaciones con guion gráfico en Windows Runtime no son simplemente decoraciones visuales. Es más útil pensar en animaciones como una técnica de máquina de estado que puede establecer los valores de propiedades individuales o de todas las propiedades y objetos visuales de un control y cambiar estos valores a lo largo del tiempo.

Para animarse, la propiedad de destino de la animación debe ser una propiedad de dependencia. Además, para animarse, el tipo de valor de la propiedad de destino debe ser compatible con uno de los tipos de animación derivados de la escala de tiempo existentes. Los valores de Color, Double y Point se pueden animar mediante técnicas de interpolación o fotograma clave. La mayoría de los demás valores se pueden animar mediante fotogramas clave de objeto discretos.

Cuando se aplica y ejecuta una animación, el valor animado funciona con una prioridad mayor que cualquier valor (como un valor local) que tenga la propiedad de otro modo. Las animaciones también tienen un comportamiento HoldEnd opcional que puede hacer que las animaciones se apliquen a los valores de propiedad incluso si la animación parece detenida visualmente.

El principio de la máquina de estado está incorporado por el uso de animaciones con guion gráfico como parte del modelo de estado de VisualStateManager para los controles. Para obtener más información sobre las animaciones con guion gráfico, consulta Animaciones con guion gráfico. Para obtener más información sobre VisualStateManager y definir estados visuales para controles, consulta Animaciones con guion gráfico para estados visuales o plantillas de control.

Comportamiento cambiado de propiedad

El comportamiento cambiado por propiedades es el origen de la parte "dependencia" de la terminología de la propiedad de dependencia. Mantener valores válidos para una propiedad cuando otra propiedad puede influir en el valor de la primera propiedad es un problema de desarrollo difícil en muchos marcos. En el sistema de propiedades de Windows Runtime, cada propiedad de dependencia puede especificar una devolución de llamada que se invoca cada vez que cambia su valor de propiedad. Esta devolución de llamada se puede usar para notificar o cambiar los valores de propiedad relacionados, de forma general sincrónica. Muchas propiedades de dependencia existentes tienen un comportamiento cambiado por propiedades. También puede agregar un comportamiento de devolución de llamada similar a las propiedades de dependencia personalizadas e implementar sus propias devoluciones de llamada modificadas por propiedades. Consulte Propiedades de dependencia personalizadas para obtener un ejemplo.

Windows 10 presenta el método RegisterPropertyChangedCallback. Esto permite que el código de la aplicación se registre para recibir notificaciones de cambios cuando se cambie la propiedad de dependencia especificada en una instancia de DependencyObject.

Valor predeterminado y ClearValue

Una propiedad de dependencia puede tener un valor predeterminado definido como parte de sus metadatos de propiedad. Para una propiedad de dependencia, su valor predeterminado no pasa a ser irrelevante después de que la propiedad se haya establecido por primera vez. El valor predeterminado puede volver a aplicarse en tiempo de ejecución cada vez que desaparece algún otro determinante en la precedencia del valor. (La prioridad del valor de la propiedad de dependencia se describe en la sección siguiente). Por ejemplo, puede quitar deliberadamente un valor de estilo o una animación que se aplique a una propiedad, pero desea que el valor sea un valor predeterminado razonable después de hacerlo. El valor predeterminado de la propiedad de dependencia puede proporcionar este valor, sin necesidad de establecer específicamente el valor de cada propiedad como un paso adicional.

Puede establecer deliberadamente una propiedad en el valor predeterminado incluso después de haberla establecido con un valor local. Para restablecer un valor de nuevo como predeterminado, y también para habilitar a otros participantes en prioridad que podrían invalidar el valor predeterminado, pero no un valor local, llame al método ClearValue (haga referencia a la propiedad para borrar como parámetro de método). No siempre quieres que la propiedad use literalmente el valor predeterminado, pero borrar el valor local y revertir al valor predeterminado podría habilitar otro elemento en prioridad que quieras actuar ahora, como usar el valor que procede de un establecedor de estilo en una plantilla de control.

DependencyObject y subprocesos

Todas las instancias de DependencyObject deben crearse en el subproceso de interfaz de usuario que está asociado a la ventana actual que muestra una aplicación de Windows Runtime. Aunque se debe crear cada DependencyObject en el subproceso principal de la interfaz de usuario, se puede tener acceso a los objetos mediante una referencia del distribuidor desde otros subprocesos, accediendo a la propiedad Dispatcher. A continuación, puede llamar a métodos como RunAsync en el objeto CoreDispatcher y ejecutar el código dentro de las reglas de restricciones de subprocesos en el subproceso de la interfaz de usuario.

Los aspectos de subproceso de DependencyObject son relevantes porque, por lo general, significa que solo el código que se ejecuta en el subproceso de la interfaz de usuario puede cambiar o incluso leer el valor de una propiedad de dependencia. Normalmente, los problemas de subproceso se pueden evitar en código de interfaz de usuario típico que hace un uso correcto de patrones asincrónicos y subprocesos de trabajo en segundo plano. Normalmente, solo se producen problemas de subprocesos relacionados con DependencyObject si define sus propios tipos dependencyObject e intenta usarlos para orígenes de datos u otros escenarios en los que dependencyObject no es necesariamente adecuado.

Material conceptual