Propiedades de dependencia personalizadas

En este tema se describen las razones por las que los creadores de componentes y los desarrolladores de aplicaciones de Windows Presentation Foundation (WPF) pueden beneficiarse de la creación de una propiedad de dependencia personalizada, y se describen los pasos de implementación y algunas opciones de implementación que pueden mejorar el rendimiento, la facilidad de uso o la versatilidad de la propiedad.

Requisitos previos

En este tema se da por hecho que conoce las propiedades de dependencia desde la perspectiva de un consumidor de propiedades de dependencia existentes en las clases de WPF y, asimismo, que ha leído el tema Información general sobre las propiedades de dependencia. Para seguir los ejemplos de este tema, también debe comprender el lenguaje XAML y saber cómo escribir aplicaciones de WPF.

¿Qué es una propiedad de dependencia?

Puede implementar como propiedad de dependencia lo que en otras circunstancias sería una propiedad de Common Language Runtime (CLR) para admitir estilos, enlace de datos, herencia, animaciones y valores predeterminados. Las propiedades de dependencia se registran con el sistema de propiedades de WPF llamando al método Register (o RegisterReadOnly) y están respaldadas por un campo de identificador de DependencyProperty. Solo pueden usar propiedades de dependencia los tipos DependencyObject, pero DependencyObject se encuentra en una posición bastante alta dentro de la jerarquía de clases de WPF, por lo que la mayoría de las clases disponibles en WPF puede admitir propiedades de dependencia. Para obtener más información sobre las propiedades de dependencia y algunos de los términos y convenciones que se usan para describirlas en este SDK, vea Información general sobre las propiedades de dependencia.

Ejemplos de propiedades de dependencia

Algunos ejemplos de propiedades de dependencia que se implementan en las clases de WPF son Background, Width y Text, entre muchas otras. Cada propiedad de dependencia expuesta por una clase tiene su correspondiente campo estático público de tipo DependencyProperty que se expone en esa misma clase. Es el identificador de la propiedad de dependencia. El identificador se denomina mediante una convención: el nombre de la propiedad de dependencia con la cadena Property anexada. Por ejemplo, el campo de identificador DependencyProperty correspondiente a la propiedad Background es BackgroundProperty. El identificador almacena la información sobre la propiedad de dependencia tal como se registró, y se usa posteriormente en otras operaciones relacionadas con la propiedad de dependencia, como llamar a SetValue.

Tal y como se menciona en Información general sobre las propiedades de dependencia, todas las propiedades de dependencia de WPF (excepto la mayoría de las propiedades adjuntas) también son propiedades de CLR debido a la implementación del "contenedor". Por lo tanto, desde el código, puede obtener o establecer propiedades de dependencia llamando a los descriptores de acceso de CLR que definen los contenedores, de la misma manera que usaría otras propiedades de CLR. Los consumidores de propiedades de dependencia establecidas no suelen usar los métodos de DependencyObjectGetValue y SetValue, que son el punto de conexión al sistema de propiedades subyacente. En su lugar, la implementación existente de propiedades de CLR ya habrá llamado a GetValue y a SetValue dentro de las implementaciones de contenedor get y set de la propiedad, usando el campo de identificador adecuadamente. Si se está implementando una propiedad de dependencia personalizada, a continuación, estará definiendo el contenedor de forma similar.

¿Cuándo se debe implementar una propiedad de dependencia?

Al implementar una propiedad en una clase, y siempre y cuando la clase derive de DependencyObject, existe la posibilidad de respaldar la propiedad con un identificador DependencyProperty, lo que la convierte en una propiedad de dependencia. Convertir su propiedad en una propiedad de dependencia no es siempre necesario ni adecuado y dependerá de las necesidades del escenario. A veces, la técnica típica de respaldar la propiedad con un campo privado es adecuada. Pese a ello, deberá implementar la propiedad como una propiedad de dependencia cada vez que quiera que esta admita una o varias de las siguientes funcionalidades de WPF:

  • Quiere que su propiedad se pueda establecer en un estilo. Para obtener más información, consulte Aplicar estilos y plantillas.

  • Quiere que su propiedad admita el enlace de datos. Para obtener más información sobre las propiedades de dependencia de enlace de datos, consulte Enlazar las propiedades de dos controles.

  • Quiere que su propiedad se pueda establecer con una referencia de recurso dinámico. Para obtener más información, consulte Recursos XAML.

  • Quiere heredar un valor de propiedad automáticamente de un elemento primario del árbol de elementos. En este caso, realice el registro con el método RegisterAttached, aunque también cree un contenedor de propiedad para el acceso de CLR. Para más información, vea Herencia de valores de propiedad.

  • Quiere que su propiedad se pueda animar. Para obtener más información, consulte Información general sobre animaciones.

  • Quiere que el sistema de propiedades notifique cualquier cambio en el valor anterior de la propiedad debido a acciones del sistema de propiedades, el entorno o el usuario, o a la lectura y el uso de estilos. Al usar los metadatos de la propiedad, la propiedad puede especificar un método de devolución de llamada que se invocará cada vez que el sistema de propiedades determine un cambio definitivo en el valor de la propiedad. Un concepto relacionado es la coerción del valor de propiedad. Para obtener más información, consulte Devoluciones de llamada y validación de las propiedades de dependencia.

  • Conviene usar convenciones de metadatos establecidas que también se usen en los procesos de WPF, como notificar si un cambio en un valor de propiedad debe exigir que el sistema de diseño recomponga los elementos visuales de un elemento. O bien quiere poder usar invalidaciones de metadatos para que las clases derivadas puedan cambiar las características basadas en metadatos, como el valor predeterminado.

  • Es aconsejable que las propiedades de un control personalizado cuenten con soporte técnico de WPF Designer de Visual Studio, como la edición de la ventana Propiedades. Para obtener más información, consulte Información general sobre la creación de controles.

Al examinar estos escenarios, también debe considerar si puede obtener su escenario mediante la invalidación de los metadatos de una propiedad de dependencia existente, en lugar de hacerlo a través de la implementación de una propiedad completamente nueva. El hecho de que una invalidación de metadatos sea práctica dependerá del escenario y de la similitud de ese escenario con la implementación de las clases y propiedades de dependencia de WPF existentes. Para obtener más información sobre cómo invalidar los metadatos de las propiedades existentes, consulte Metadatos de las propiedades de dependencia.

Lista de comprobación para definir una propiedad de dependencia

La definición de una propiedad de dependencia consta de cuatro conceptos distintos. Estos conceptos no son necesariamente procedimientos estrictos, ya que algunos de ellos acaban combinándose en una sola línea de código en la implementación:

  • (Opcional) Cree los metadatos de propiedad de la propiedad de dependencia.

  • Registre el nombre de propiedad con el sistema de propiedades, y especifique el tipo de propietario y el tipo de valor de propiedad. Especifique también los metadatos de la propiedad, si se usan.

  • Defina un identificador DependencyProperty como un campo publicstaticreadonly en el tipo de propietario.

  • Defina una propiedad de "contenedor" CLR cuyo nombre coincida con el nombre de la propiedad de dependencia. Implemente los descriptores de acceso get y set de la propiedad de "contenedor" CLR para establecer la conexión con la propiedad de dependencia que la respalda.

Registro de la propiedad con el sistema de propiedades

Para que la propiedad sea una propiedad de dependencia, debe registrarla en una tabla (de cuyo mantenimiento se encargue el sistema de propiedades) y asignarle un identificador único, que se usará como calificador en operaciones posteriores del sistema de propiedades. Estas operaciones podrían ser operaciones internas, o bien su propio código realizando llamadas a API de sistemas de propiedades. Para registrar la propiedad, debe llamar al método Register dentro del cuerpo de la clase (dentro de la clase, pero fuera de las definiciones de miembros). El campo de identificador también lo proporciona la llamada al método Register, como el valor devuelto. La razón por la que la llamada a Register se realiza fuera de otras definiciones de miembros es que este valor devuelto se usa para asignar y crear un campo publicstaticreadonly de tipo DependencyProperty como parte de la clase. Este campo se convierte en el identificador de la propiedad de dependencia.

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender,
      new PropertyChangedCallback(OnUriChanged)
  )
);
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))

Convenciones de nombres de las propiedades de dependencia

Existen convenciones de nomenclatura establecidas con respecto a las propiedades de dependencia que se deben seguir en todas las circunstancias, menos en las excepcionales.

La propiedad de dependencia tendrá un nombre básico, "AquariumGraphic" en este ejemplo, que se proporciona como el primer parámetro de Register. Este nombre debe ser único en cada tipo de registro. Las propiedades de dependencia heredadas a través de tipos base ya se consideran parte del tipo de registro; los nombres de las propiedades heredadas no se pueden volver a registrar. Sin embargo, existe una técnica para agregar una clase como propietaria de una propiedad de dependencia, aunque esa propiedad de dependencia no sea heredada. Para obtener más información, consulte Metadatos de las propiedades de dependencia.

Al crear el campo de identificador, asígnele el nombre de la propiedad tal como lo registró, seguido del sufijo Property. Este campo es el identificador de la propiedad de dependencia, que luego usarán como entrada de las llamadas a SetValue y GetValue en los contenedores cualquier otro acceso de código a la propiedad que realice su propio código, cualquier acceso de código externo que se permita, el sistema de propiedades y, posiblemente, los procesadores de XAML.

Nota:

La definición de la propiedad de dependencia en el cuerpo de la clase es la implementación típica, pero también es posible definir una propiedad de dependencia en el constructor estático de la clase. Este enfoque podría tener sentido si necesita más de una línea de código para inicializar la propiedad de dependencia.

Implementación del "contenedor"

La implementación del contenedor debería llamar a GetValue en la implementación de get y a SetValue en la implementación de set (el campo y la llamada de registro originales se muestran aquí para mayor claridad).

En todas las circunstancias, menos las excepcionales, las implementaciones de contenedores solo deben realizar las acciones GetValue y SetValue respectivamente. La razón se explica en el tema Carga de XAML y propiedades de dependencia.

Todas las propiedades de dependencia públicas existentes que se proporcionan en las clases de WPF usan este modelo de implementación de contenedor simple; gran parte de la complejidad del funcionamiento de las propiedades de dependencia es de manera inherente un comportamiento del sistema de propiedades, o bien se implementa a través de otros conceptos, como la coerción o las devoluciones de llamada de cambio de propiedad a través de los metadatos de la propiedad.


public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender,
      new PropertyChangedCallback(OnUriChanged)
  )
);
public Uri AquariumGraphic
{
  get { return (Uri)GetValue(AquariumGraphicProperty); }
  set { SetValue(AquariumGraphicProperty, value); }
}

Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
Public Property AquariumGraphic() As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set(ByVal value As Uri)
        SetValue(AquariumGraphicProperty, value)
    End Set
End Property

De nuevo, por convención, el nombre de la propiedad de contenedor debe coincidir con el elegido y asignado como primer parámetro de la llamada a Register que registró la propiedad. Si la propiedad no cumple esta convención, no deshabilitará necesariamente todos los usos posibles, pero encontrará varios problemas importantes:

  • Determinados aspectos de los estilos y las plantillas no funcionarán.

  • La mayoría de las herramientas y los diseñadores deben basarse en las convenciones de nomenclatura para serializar correctamente XAML, o para proporcionar al entorno del diseñador asistencia para cada propiedad.

  • La implementación actual del cargador de XAML de WPF omite completamente los contenedores y se basa en la convención de nomenclatura al procesar los valores de atributo. Para obtener más información, consulte Carga de XAML y propiedades de dependencia.

Metadatos de propiedad de una nueva propiedad de dependencia

Al registrar una propiedad de dependencia, el registro a través del sistema de propiedades crea un objeto de metadatos que almacena las características de la propiedad. Muchas de estas características tienen valores predeterminados que se establecen si la propiedad se registra con las signaturas simples de Register. Otras signaturas de Register permiten especificar los metadatos deseados al registrar la propiedad. La asignación de metadatos más común para las propiedades de dependencia consiste en asignarles un valor predeterminado, que se aplicará a las nuevas instancias que usen la propiedad.

Si está creando una propiedad de dependencia que existe en una clase derivada de FrameworkElement, puede usar la clase de metadatos más especializada FrameworkPropertyMetadata en lugar de la clase base PropertyMetadata. El constructor de la clase FrameworkPropertyMetadata tiene varias signaturas, donde puede especificar diversas características de los metadatos combinadas. Si quiere especificar solo el valor predeterminado, use la signatura que toma un único parámetro de tipo Object. Pase dicho parámetro de objeto como un valor predeterminado específico del tipo para la propiedad (el valor predeterminado proporcionado debe ser el tipo que se suministró como parámetro propertyType en la llamada a Register).

Respecto a FrameworkPropertyMetadata, también puede especificar marcas de opción de metadatos para la propiedad. Estas marcas se convierten en propiedades discretas en los metadatos de propiedad después del registro y se usan para comunicar ciertos determinantes a otros procesos, como el motor de diseño.

Establecimiento de marcas de metadatos adecuadas

  • Si su propiedad (o los cambios en el valor de esta) influye en la interfaz de usuario y, en particular, en cómo el diseño debe dimensionar o representar su elemento en una página, establezca una o varias de las marcas siguientes: AffectsMeasure, AffectsArrange, AffectsRender.

    • AffectsMeasure indica que un cambio en esta propiedad requiere un cambio en la representación de UI, donde el objeto contenedor puede necesitar más o menos espacio dentro del elemento primario. Por ejemplo, una propiedad "Width" debe tener establecida esta marca.

    • AffectsArrange indica que un cambio en esta propiedad requiere un cambio en la representación de UI, que por lo general no requiere un cambio en el espacio dedicado, pero indica un cambio en el posicionamiento dentro del espacio. Por ejemplo, una propiedad "Alignment" debe tener establecida esta marca.

    • AffectsRender indica que se produjo algún otro cambio que no afectará al diseño ni a la medida, pero no requiere ninguna otra representación. Un ejemplo sería una propiedad que cambia un color de un elemento existente, como "Background".

    • Estas marcas se usan a menudo como un protocolo en los metadatos para sus propias implementaciones de invalidación de las devoluciones de llamada de diseño o del sistema propiedades. Por ejemplo, es posible que tenga una devolución de llamada OnPropertyChanged que llamará a InvalidateArrange si cualquier propiedad de la instancia notifica un cambio de valor y tiene AffectsArrange establecido entrue sus metadatos.

  • Algunas propiedades pueden afectar a la representación de características del elemento primario contenedor, más allá de los cambios en el tamaño necesario mencionados anteriormente. Un ejemplo es la propiedad MinOrphanLines usada en el modelo de documentos dinámicos, donde los cambios en esa propiedad pueden cambiar la representación global del documento dinámico que contiene el párrafo. Use AffectsParentArrange o AffectsParentMeasure para identificar casos similares en sus propias propiedades.

  • De forma predeterminada, las propiedades de dependencia admiten el enlace de datos. Puede deshabilitar deliberadamente el enlace de datos en aquellos casos en que no exista ningún escenario realista para el enlace de datos, o en que el rendimiento del enlace de datos de un objeto de gran tamaño se reconozca como un problema.

  • De forma predeterminada, el objeto Mode de los enlaces de datos de las propiedades de dependencia tiene OneWay como valor predeterminado. Siempre puede cambiar el enlace para que sea TwoWay según la instancia de enlace. Para obtener más información, vea Especificar la dirección del enlace. No obstante, como autor de la propiedad de dependencia, puede hacer que la propiedad use el modo de enlace TwoWay de forma predeterminada. Un ejemplo de una propiedad de dependencia existente es MenuItem.IsSubmenuOpen. El escenario de esta propiedad es que la lógica de configuración de IsSubmenuOpen y la composición de MenuItem interactúan con el estilo de tema predeterminado. La lógica de la propiedad IsSubmenuOpen usa el enlace de datos de forma nativa para mantener el estado de la propiedad de acuerdo con otras propiedades de estado y llamadas a métodos. Otra propiedad de ejemplo que enlaza en el modo TwoWay de forma predeterminada es TextBox.Text.

  • También puede establecer la marca Inherits para habilitar la herencia de propiedades en una propiedad de dependencia personalizada. La herencia de propiedades resulta útil para un escenario en que los elementos primarios y secundarios tienen una propiedad en común, y tiene sentido para los elementos secundarios que tienen ese valor de propiedad concreto establecido en el mismo valor que el elemento primario. Un ejemplo de propiedad heredable es DataContext, que se usa para operaciones de enlace a fin de habilitar el escenario de maestro y detalle importante para la presentación de los datos. Al convertir DataContext en heredable, todos los elementos secundarios también heredan ese contexto de datos. Debido a la herencia de valores de propiedad, puede especificar un contexto de datos en la raíz de la aplicación o la página y no es necesario volver a especificarlo para los enlaces de todos los elementos secundarios posibles. DataContext es un buen ejemplo también para ilustrar que la herencia invalida el valor predeterminado, pero siempre se puede establecer localmente en cualquier elemento secundario determinado. Para obtener más información, vea Uso del patrón de maestro y detalle con datos jerárquicos. La herencia de valores de propiedad puede presentar un costo de rendimiento y, por tanto, debe usarse con moderación. Para obtener más información, consulte Herencia de valores de propiedad.

  • Establezca la marca Journal para indicar que la propiedad de dependencia debería ser detectada o usada por los servicios de registro en diario de navegación. Un ejemplo es la propiedad SelectedIndex: todos los elementos seleccionados en un control de selección deben mantenerse durante la navegación por el historial de registro en el diario.

Propiedades de dependencia de sólo lectura

Es posible definir una propiedad de dependencia que sea de solo lectura. Sin embargo, los escenarios para los que podría definir la propiedad como de solo lectura son algo diferentes, del mismo modo que lo es el procedimiento para registrarlos con el sistema de propiedades y exponer el identificador. Para obtener más información, consulte Propiedades de dependencia de solo lectura.

Propiedades de dependencia de tipo de colección

Las propiedades de dependencia de tipo de colección presentan algunos problemas de implementación adicionales que se deben tener en cuenta. Para obtener más información, consulte Propiedades de dependencia de tipo de colección.

Consideraciones de seguridad de las propiedades de dependencia

Las propiedades de dependencia deben declararse como propiedades públicas. Los campos de identificador de las propiedades de dependencia deben declararse como campos estáticos públicos. Aunque intente declarar otros niveles de acceso (como, por ejemplo, protegido), siempre se puede acceder a una propiedad de dependencia a través del identificador en combinación con las API del sistema de propiedades. Incluso un campo de identificador protegido es potencialmente accesible debido a las API de determinación de valores o notificación de metadatos que forman parte del sistema de propiedades, como LocalValueEnumerator. Para obtener más información, consulte Seguridad de las propiedades de dependencia.

Propiedades de dependencia y constructores de clase

Existe un principio general en la programación de código administrado (que suelen aplicar las herramientas de análisis de código, como FxCop), según el cual los constructores de clase no deben llamar a métodos virtuales. Esto es debe a que los constructores se pueden llamar como inicialización base de un constructor de clase derivada y la especificación del método virtual a través del constructor puede producirse en un estado de inicialización incompleto de la instancia del objeto que se está construyendo. Al derivar de cualquier clase que ya deriva de DependencyObject, debe tener en cuenta que el propio sistema de propiedades llama a métodos virtuales y los expone internamente. Estos métodos virtuales forman parte de los servicios del sistema de propiedades de WPF. La invalidación de los métodos permite que las clases derivadas participen en la determinación del valor. Para evitar posibles problemas con la inicialización en tiempo de ejecución, no debe establecer valores de propiedades de dependencia dentro de los constructores de clase, a menos que siga un patrón de constructor muy específico. Para obtener más información, consulte Modelos de constructores seguros para objetos DependencyObject.

Vea también