Prioridad de los valores de propiedades de dependencia (WPF .NET)

El funcionamiento del sistema de Windows Presentation Foundation (WPF) afecta al valor de una propiedad de dependencia. En este artículo se explica cómo la precedencia de las distintas entradas basadas en propiedades dentro del sistema de propiedades de WPF determina el valor efectivo de una propiedad de dependencia.

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 el artículo se da por supuesto un conocimiento básico de las propiedades de dependencia y que ha leído Información general sobre las propiedades de dependencia. Para seguir los ejemplos de este artículo, resultará útil que esté familiarizado con el lenguaje XAML y que sepa cómo escribir aplicaciones WPF.

Sistema de propiedades de WPF

El sistema de propiedades de WPF usa una variedad de factores para determinar el valor de las propiedades de dependencia, como la validación de propiedades en tiempo real, el enlace en tiempo de ejecución y las notificaciones de cambio de propiedad para las propiedades relacionadas. Aunque el orden y la lógica usados para determinar los valores de propiedad de dependencia son complejos, aprenderlo puede ayudarle a evitar configuraciones de propiedad innecesarias y también a averiguar por qué un intento de establecer una propiedad de dependencia no da como resultado el valor esperado.

Propiedades de dependencia establecidas en varios lugares

En el ejemplo XAML siguiente se muestra cómo tres operaciones «set» diferentes en la propiedad del botón Background pueden influir en su valor.

<StackPanel>
    <StackPanel.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" 
                    BorderBrush="{TemplateBinding BorderBrush}">
                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
            </Border>
        </ControlTemplate>
    </StackPanel.Resources>

    <Button Template="{StaticResource ButtonTemplate}" Background="Red">
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="Background" Value="Blue"/>
                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Background" Value="Yellow" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
        Which color do you expect?
    </Button>
</StackPanel>

En el ejemplo, la propiedad Background se establece localmente en Red. Sin embargo, el estilo implícito declarado en el ámbito del botón intenta establecer la propiedad Background en Blue. Y, cuando el mouse está sobre el botón, el desencadenador en el estilo implícito intenta establecer la propiedad Background en Yellow. Excepto por la coerción y la animación, un valor de propiedad establecido localmente tiene la prioridad más alta, por lo que el botón será rojo, incluso con el evento «mouseover». Sin embargo, si quita el valor establecido localmente del botón, recibirá su valor del estilo, Background. Dentro de un estilo los desencadenadores tienen prioridad, por lo que el botón será amarillo con el evento «mouseover» y azul en caso contrario. En el ejemplo se reemplaza el valor predeterminado ControlTemplate del botón porque la plantilla predeterminada tiene un valor Background para «mouseover» incluido en el código.

Lista de prioridades de las propiedades de dependencia

La lista siguiente es el orden de prioridad definitivo que usa el sistema de propiedades al asignar valores en tiempo de ejecución a las propiedades de dependencia. La precedencia más alta aparece primero.

  1. Coerción del sistema de propiedades. Para obtener más información sobre la coerción, consulte Coerción y animaciones.

  2. Animaciones activas o animaciones con un comportamiento en espera. Para tener un efecto práctico, un valor de animación debe tener prioridad sobre el valor base (no animado), incluso si el valor base se ha establecido localmente. Para obtener más información, consulte Coerción y animaciones.

  3. Valores locales. Puede establecer un valor local a través de una propiedad de «contenedor», lo que equivale a establecer un atributo o elemento de propiedad en XAML o mediante una llamada a la API SetValue utilizando una propiedad de una instancia específica. Un valor local establecido a través de un enlace o recurso tendrá la misma prioridad que un valor que se establece directamente.

  4. Valores de propiedad de plantilla TemplatedParent. Un elemento tiene un TemplatedParent si ha sido creado por una plantilla (ControlTemplate o DataTemplate). Para obtener más información, consulte TemplatedParent. Dentro de la plantilla especificada por el TemplatedParent el orden de prioridad es:

    1. Desencadenadores.

    2. Conjuntos de propiedades, normalmente a través de atributos XAML.

  5. Estilos implícitos. Solo se aplica a la propiedad Style. El valor Style es cualquier recurso de estilo con un valor TargetType que coincide con el tipo de elemento. El recurso de estilo debe existir dentro de la página o aplicación. La búsqueda de un recurso de estilo implícito no se extiende a los recursos de estilos de Temas.

  6. Desencadenadores de estilo. Un desencadenador de estilo es un desencadenador dentro de un estilo explícito o implícito. El estilo debe existir dentro de la página o aplicación. Los desencadenadores de los estilos predeterminados tienen una prioridad menor.

  7. Desencadenadores de plantilla. Un desencadenador de plantilla es un desencadenador de una plantilla aplicada directamente o de una plantilla dentro de un estilo. El estilo debe existir dentro de la página o aplicación.

  8. Valores de establecedores de estilo. Un valor de establecedor de estilo es un valor aplicado por un Setter dentro de un estilo. El estilo debe existir dentro de la página o aplicación.

  9. Estilos predeterminados, también conocidos como estilos de tema. Para obtener más información, consulte Estilos (de tema) predeterminados. Dentro de un estilo predeterminado el orden de prioridad es:

    1. Desencadenadores activos.

    2. Establecedores.

  10. Herencia. Algunas propiedades de dependencia de un elemento secundario heredan su valor del elemento primario. Por lo tanto, puede que no sea necesario establecer valores de propiedad en todos los elementos de la aplicación. Para obtener más información, consulte Herencia de valores de propiedad.

  11. Valor predeterminado de los metadatos de la propiedad de dependencia: una propiedad de dependencia puede tener un valor predeterminado establecido durante el registro del sistema de propiedades de esa propiedad. Las clases derivadas que heredan una propiedad de dependencia pueden invalidar los metadatos (incluido el valor predeterminado) de propiedad de dependencia por tipo. Para obtener más información, consulte Metadatos de propiedad de dependencia. Para una propiedad heredada, el valor predeterminado de un elemento primario tiene prioridad sobre el valor predeterminado de un elemento secundario. Por lo tanto, si no se establece una propiedad heredable, se usa el valor predeterminado de la raíz o del elemento primario en lugar del valor predeterminado del elemento secundario.

TemplatedParent

TemplatedParent la precedencia no se aplica a las propiedades de los elementos que se declaran directamente en el marcado de aplicación estándar. El concepto TemplatedParent solo existe para los elementos secundarios dentro de un árbol visual que existen a través de la aplicación de una plantilla. Cuando el sistema de propiedades busca en la plantilla especificada por el TemplatedParent para los valores de propiedad de un elemento, busca en la plantilla que ha creado al elemento. Los valores de propiedad de la plantilla TemplatedParent suelen actuar como si fueran valores establecidos localmente en el elemento, pero con menos prioridad que los valores locales reales porque las plantillas, potencialmente, se comparten. Para obtener más información, vea TemplatedParent.

La propiedad de estilo

El mismo orden de prioridad se aplica a todas las propiedades de dependencia, excepto a la propiedad Style. La propiedad Style es única en cuanto que no puede darse estilo a sí misma. No se recomienda la coerción o animación de la propiedad Style (y animar la propiedad Style requeriría una clase de animación personalizada). Como resultado, no se aplican todos los elementos de precedencia. Solo hay tres maneras de establecer la propiedad Style:

  • Estilo explícito. La propiedad Style de un elemento se establece directamente. El valor de la propiedad Style actúa como si fuera un valor local y tiene la misma prioridad que el elemento 3 de la lista de prioridad. En la mayoría de los escenarios los estilos explícitos no se definen en línea y, en su lugar, se hace referencia a ellos explícitamente como un recurso, por ejemplo, Style="{StaticResource myResourceKey}".

  • Estilo implícito. La propiedad Style de un elemento no se establece directamente. En su lugar, se aplica un estilo cuando existe en algún nivel dentro de la página o aplicación y tiene una clave de recurso que coincide con el tipo de elemento al que se aplica el estilo, por ejemplo, <Style TargetType="x:Type Button">. El tipo debe coincidir exactamente, por ejemplo, <Style TargetType="x:Type Button"> no se aplicará al tipo MyButton aunque MyButton se derive de Button. El valor de propiedad Style tiene la misma prioridad que el elemento 5 de la lista de prioridad. Se puede detectar un valor de estilo implícito llamando al método DependencyPropertyHelper.GetValueSource, pasando la propiedad Style y comprobando ImplicitStyleReference en los resultados.

  • Estilo predeterminado, también conocido como estilo del tema. La propiedad Style de un elemento no se establece directamente. En su lugar, procede de la evaluación de temas en tiempo de ejecución por parte del motor de presentación de WPF. Antes del tiempo de ejecución el valor de la propiedad Style es null. El valor de propiedad Style tiene la misma prioridad que el elemento 9 de la lista de prioridad.

Estilos (de tema) predeterminados

Todos los controles que se suministran con WPF tienen un estilo predeterminado que puede variar según el tema, que es la razón por la que el estilo predeterminado a veces se conoce como estilo de tema.

El ControlTemplate es un elemento importante dentro del estilo predeterminado para un control. ControlTemplate es un valor de establecedor para la propiedad del estilo Template. Si no hubiera ninguna plantilla en los estilos predeterminados, un control sin una plantilla personalizada como parte de un estilo personalizado no tendría ninguna apariencia visual. Una plantilla no solo define la apariencia visual de un control, sino que también define las conexiones entre las propiedades del árbol visual de la plantilla y la clase de control correspondiente. Cada control expone un conjunto de propiedades que puede influir en la apariencia visual del control sin reemplazar la plantilla. Por ejemplo, considere la apariencia visual predeterminada de un control Thumb, que es un componente ScrollBar.

Un control Thumb tiene ciertas propiedades personalizables. La plantilla predeterminada de un control Thumb crea una estructura básica, o árbol visual, con varios componentes Border anidados para crear un aspecto biselado. Dentro de la plantilla las propiedades diseñadas para ser personalizables por la clase Thumb se exponen a través de TemplateBinding. La plantilla predeterminada para el control Thumb tiene varias propiedades de borde que comparten un enlace de plantilla con propiedades como Background o BorderThickness. Pero donde los valores de propiedades o arreglos visuales están codificados de forma rígida en la plantilla, o están ligados a valores que proceden directamente del tema, solo se pueden cambiar esos valores reemplazando toda la plantilla. Por lo general, si una propiedad procede de un elemento primario con plantilla y no lo expone un elemento TemplateBinding, el valor de la propiedad no se puede cambiar mediante estilos porque no hay ninguna manera conveniente de establecerla como destino. No obstante, aún se podría influir en esa propiedad mediante la herencia de valores de propiedad de la plantilla aplicada o por un valor predeterminado.

Los estilos predeterminados especifican un TargetType en sus definiciones. La evaluación del tema en tiempo de ejecución asocia el TargetType de un estilo predeterminado con la propiedad DefaultStyleKey de un control. En cambio, el comportamiento de búsqueda de estilos implícitos usa el tipo real del control. El valor de DefaultStyleKey lo heredan las clases derivadas, por lo que los elementos derivados que de otro modo podrían no tener ningún estilo asociado obtengan una apariencia visual predeterminada. Por ejemplo, si deriva MyButton de Button, MyButton heredará la plantilla predeterminada de Button. Las clases derivadas pueden invalidar el valor predeterminado de DefaultStyleKey en los metadatos de la propiedad de dependencia. Por lo tanto, si desea una representación visual diferente para MyButton, puede invalidar los metadatos de la propiedad de dependencia para DefaultStyleKey en MyButton y, a continuación, definir el estilo predeterminado pertinente, incluida una plantilla, que empaquetará con el control MyButton. Para obtener más información, consulte Información general sobre la creación de controles.

Recurso dinámico

Recurso dinámico: las operaciones de enlace y referencias de recursos dinámicos tiene la precedencia de la ubicación en la que están establecidas. Por ejemplo, un recurso dinámico aplicado a un valor local tiene la misma prioridad que el elemento 3 de la lista de precedencia. Otro ejemplo es que un enlace de recursos dinámicos aplicado a un establecedor de propiedades dentro de un estilo predeterminado tiene la misma prioridad que el elemento 9 de la lista de precedencia. Dado que las referencias y el enlace de recursos dinámicos deben obtener valores del estado de tiempo de ejecución de la aplicación, el proceso para determinar la prioridad del valor de propiedad para cualquier propiedad determinada se extiende al tiempo de ejecución.

Las referencias de recursos dinámicos técnicamente no forman parte del sistema de propiedades y tienen su propio orden de búsqueda que interactúa con la lista de precedencia. Básicamente, la prioridad de las referencias de recursos dinámicos es: elemento a raíz de página, aplicación, tema y, a continuación, sistema. Para obtener más información, consulte Recursos XAML.

Aunque las referencias y enlaces de recursos dinámicos tienen la prioridad de la ubicación en la que se establecen, el valor se aplaza. Una consecuencia de esto es que, si establece un recurso dinámico o un enlace en un valor local, cualquier cambio en el valor local reemplazará completamente el recurso dinámico o el enlace. Aunque llame al método ClearValue para borrar el valor establecido localmente, el recurso dinámico o el enlace no se restaurarán. De hecho, si llama a ClearValue en una propiedad que tiene un recurso dinámico o enlace (sin ningún valor local literal), se borrará el recurso dinámico o el enlace.

SetCurrentValue

El método SetCurrentValue es otra manera de establecer una propiedad, pero no está en la lista de prioridad. SetCurrentValue permite cambiar el valor de una propiedad sin sobrescribir el origen de un valor anterior. Por ejemplo, si un desencadenador establece una propiedad y, a continuación, asigna otro valor mediante SetCurrentValue, la acción del siguiente desencadenador volverá a establecer la propiedad en el valor del desencadenador. Puede usar SetCurrentValue siempre que desee establecer un valor de propiedad sin dar a ese valor el nivel de precedencia de un valor local. De forma similar, puede usar SetCurrentValue para cambiar el valor de una propiedad sin sobrescribir un enlace.

Coerción y animación

Tanto la coerción como la animación actúan en un valor base. El valor base es el valor de la propiedad de dependencia con la prioridad más alta, determinado evaluando hacia arriba a través de la lista de precedencia hasta que se alcanza el elemento 2.

Si una animación no especifica los valores de propiedad From y To para determinados comportamientos, o si la animación revierte deliberadamente al valor base cuando se completa, el valor base puede afectar al valor animado. Para verlo en la práctica, ejecute la aplicación de ejemplo valores de destino. En el ejemplo, para la altura del rectángulo, pruebe a establecer valores locales iniciales que difieran de cualquier valor de From. Las animaciones de ejemplo comienzan a usar el valor From inmediatamente, en lugar del valor base. Al especificar Stop como el FillBehavior, al finalizar una animación se restablecerá un valor de propiedad a su valor base. La prioridad normal se usa para la determinación del valor base después de que finalice una animación.

Se pueden aplicar varias animaciones a una sola propiedad y que cada animación tenga una prioridad diferente. En lugar de aplicar la animación con la prioridad más alta, el motor de presentación de WPF podría componer los valores de animación en función de cómo se han definido las animaciones y el tipo de valores animados. Para obtener más información, vea Información general sobre animaciones.

La coerción está en la parte superior de la lista de precedencia. Incluso una animación que ya se está ejecutando está sujeta a la coerción de valores. Ciertas propiedades de dependencia existentes en WPF tienen coerción integrada. Para las propiedades de dependencia personalizadas, puede definir el comportamiento de coerción escribiendo un objeto CoerceValueCallback que se pasa como parte de los metadatos al crear una propiedad. También puede invalidar el comportamiento de la coerción de las propiedades existentes mediante la invalidación de los metadatos de esa propiedad en una clase derivada. La coerción interactúa con el valor base de forma que se aplican las restricciones en la coerción, ya que esas restricciones existen en el momento, pero el valor base se conserva. Por lo tanto, si las restricciones en la coerción se levantan posteriormente, la coerción devolverá el valor más próximo posible a ese valor base y la influencia de la coerción sobre una propiedad cesará potencialmente en cuanto se levanten todas las restricciones. Para obtener más información sobre el comportamiento de la coerción, consulte Devoluciones de llamada y validación de las propiedades de dependencia.

Comportamientos de los desencadenadores

Los controles suelen definir los comportamientos de los desencadenadores como parte de su estilo predeterminado. Establecer propiedades locales en controles podría entrar en conflicto con esos desencadenadores, lo que impediría que los desencadenadores respondan (ya sea visualmente o con su comportamiento) a eventos controlados por el usuario. Un uso común de un desencadenador de propiedad es controlar las propiedades de estado, como IsSelected o IsEnabled. Por ejemplo, de forma predeterminada, cuando se deshabilita un Button, un desencadenador de estilo de tema (IsEnabled es false) establece el valor Foreground para que el Button aparezca atenuado. Si ha establecido un valor local Foreground, el valor de propiedad local de precedencia superior anulará el valor Foreground del estilo de tema, incluso cuando Button esté deshabilitado. Al establecer valores de propiedad que invalidan los comportamientos de desencadenador de nivel de tema para un control, tenga cuidado de no interferir indebidamente con la experiencia de usuario prevista para ese control.

ClearValue

El método ClearValue borra cualquier valor aplicado localmente de una propiedad de dependencia para un elemento. Sin embargo, llamar a ClearValue no garantiza que el valor predeterminado establecido en los metadatos durante el registro de propiedades sea el nuevo valor efectivo. Todos los demás participantes de la lista de precedencia siguen activos y solo se quita el valor establecido localmente. Por ejemplo, si llama a ClearValue en una propiedad que tiene un estilo de tema, el valor de estilo de tema se aplicará como el nuevo valor, en lugar del valor predeterminado basado en metadatos. Si desea establecer un valor de propiedad en el valor predeterminado de metadatos registrados, obtenga el valor de metadatos predeterminado consultando los metadatos de la propiedad de dependencia y establezca localmente el valor de propiedad con una llamada a SetValue.

Vea también