Clases XAML y personalizadas para WPF

XAML implementado en marcos de Common Language Runtime (CLR) admite la capacidad de definir una clase o estructura personalizada en cualquier lenguaje de Common Language Runtime (CLR), y después, tiene acceso a esa clase mediante marcado XAML. Puede usar una mezcla de tipos definidos por Windows Presentation Foundation (WPF) y sus tipos personalizados dentro del mismo archivo de marcado, normalmente asignando los tipos personalizados a un prefijo de espacio de nombres de XAML. En este tema se describen los requisitos que debe satisfacer una clase personalizada para que se pueda usar como elemento XAML.

Clases personalizadas en aplicaciones o ensamblados

Las clases personalizadas que se usan en XAML pueden definirse de dos maneras distintas: dentro del código subyacente o de otro código que genera la aplicación primaria de Windows Presentation Foundation (WPF), o como una clase en un ensamblado independiente, como una aplicación ejecutable o DLL usado como una biblioteca de clases. Cada uno de estos enfoques tiene ventajas y desventajas determinadas.

  • La ventaja de crear una biblioteca de clases es que cualquiera de esas clases personalizadas puede compartirse entre muchas posibles aplicaciones diferentes. Una biblioteca independiente también facilita que las versiones de las aplicaciones sean más fáciles de controlar, y simplifica la creación de una clase donde el uso previsto de la clase es como un elemento raíz en una página XAML.

  • La ventaja de definir las clases personalizadas en la aplicación consiste en que esta técnica es relativamente ligera, y minimiza los problemas de implementación y pruebas que se encuentran al especificar ensamblados independientes además del ejecutable principal de la aplicación.

  • Si se define en el mismo ensamblado o en uno diferente, las clases personalizadas deben asignarse entre el espacio de nombres CLR y el espacio de nombres XML para usarlo en XAML como elementos. Vea Espacios de nombres y asignación de espacios de nombres XAML para WPF.

Requisitos para una clase personalizada como elemento XAML

Para permitir la creación de instancias como elementos de objeto, la clase debe cumplir los requisitos siguientes:

  • La clase personalizada debe ser pública y admitir un constructor público predeterminado (sin parámetros). (Vea en la siguiente sección las notas relativas a las estructuras).

  • Su clase personalizada no debe ser una clase anidada. Las clases anidadas y el "punto" en su sintaxis de uso de CLR general interfieren con otras características de WPF o XAML como las propiedades adjuntas.

Además de permitir la sintaxis de elemento de objeto, su definición del objeto también debe habilitar la sintaxis de elemento de propiedad para cualquier otra propiedad pública que admita el objeto como el tipo de valor. Esto se debe a que ahora es posible crear instancias de un objeto como un elemento de objeto y a que puede rellenar el valor de elemento de propiedad de dicha propiedad.

Estructuras

Las estructuras que se definen como tipos personalizados siempre pueden construirse en XAML en WPF. Esto es porque los compiladores de CLR crean implícitamente un constructor sin parámetros para una estructura que inicializa todos los valores de propiedad a sus valores predeterminados. En algunos casos, no es deseable para una estructura el comportamiento predeterminado de construcción o el uso de elementos de objeto. Esto se puede deber a que la estructura está pensada para rellenar valores y funcionar, desde el punto de vista conceptual, como una unión, cuyos valores contenidos pueden tener interpretaciones mutuamente excluyentes, por lo que ninguna de sus propiedades se podría establecer. Un ejemplo de WPF de este tipo de estructura es GridLength. Generalmente, tales estructuras deberían implementar un convertidor de tipos para que los valores se puedan expresar en forma de atributo y usar las convenciones de cadena que crean las diferentes interpretaciones o modos de los valores de la estructura. La estructura también debería exponer un comportamiento similar por la construcción del código mediante un constructor sin parámetros.

Requisitos para las propiedades de una clase personalizada como atributos XAML

Las propiedades deben hacer referencia a un tipo por valor (como un tipo primitivo) o usar una clase para tipos que tenga un constructor sin parámetros o un convertidor de tipos dedicado al que pueda obtener acceso un procesador XAML. En la implementación de CLR XAML, los procesadores de XAML buscan estos convertidores a través de la compatibilidad nativa con los primitivos del lenguaje o a través de la aplicación de TypeConverterAttribute a un tipo o miembro en las definiciones de tipo de respaldo.

De manera alternativa, la propiedad puede hacer referencia a un tipo de clase abstracta o a una interfaz. Para las clases abstractas o las interfaces, la expectativa del análisis de XAML es que el valor de propiedad debe rellenarse con instancias de clase concreta que implementen la interfaz, o con instancias de tipos derivadas de la clase abstracta.

Las propiedades se pueden declarar en una clase abstracta, pero solo se pueden establecer en las clases concretas derivadas de la clase abstracta. Esto se debe a que crear el elemento de objeto para la clase requiere un constructor sin parámetros público en la clase.

Sintaxis de atributo con convertidor de tipos

Si proporciona un convertidor de tipos dedicado, con atributos, en el nivel de clase, la conversión de tipos aplicada habilita la sintaxis de atributo para cualquier propiedad que necesita crear instancias de ese tipo. Un convertidor de tipos no permite el uso de elemento de objeto del tipo; solamente la presencia de un constructor sin parámetros para ese tipo habilita el uso de elemento de objeto. Por consiguiente, las propiedades habilitadas con convertidor de tipos, en general, no se usan en sintaxis de propiedad, a menos que el propio tipo admita también la sintaxis de elemento de objeto. La excepción consiste en que se puede especificar una sintaxis de elemento de propiedad, pero el elemento de propiedad debe contener una cadena. Realmente, ese uso es esencialmente el equivalente a un uso de sintaxis de atributo, y dicho uso no es común a menos que se necesite un control más sólido del espacio en blanco en el valor del atributo. Por ejemplo, lo siguiente es un uso de elemento de propiedad que toma una cadena y el uso de atributo equivalente:

<Button>Hallo!
  <Button.Language>
    de-DE
  </Button.Language>
</Button>
<Button Language="de-DE">Hallo!</Button>

Los ejemplos de propiedades donde se permite la sintaxis de atributo, pero la sintaxis de elementos de propiedad que contiene un elemento de objeto no se permite con XAML son varias propiedades que toman el tipo Cursor. La clase Cursor tiene un objeto CursorConverter convertidor de tipos dedicado, pero no expone un constructor sin parámetros, por lo que la propiedad Cursor solamente se puede establecer mediante sintaxis de atributo aunque el tipo Cursor real sea un tipo de referencia.

Convertidores de tipo por propiedad

De manera alternativa, la propia propiedad puede declarar un convertidor de tipos en el nivel de propiedad. Esto habilita un "minilenguaje" que crea instancias de objetos del tipo de la propiedad insertada, procesando valores de cadena de entrada del atributo como entrada para una operación ConvertFrom basada en el tipo correspondiente. Normalmente esto se realiza para proporcionar un descriptor de acceso adecuado, y no como único medio para habilitar el establecimiento de una propiedad en XAML. En cambio, también es posible usar convertidores de tipos para atributos cuando quiera usar tipos CLR existentes que no proporcionen un constructor sin parámetros ni un convertidor de tipos con atributos. Los ejemplos de la API de WPF son propiedades determinadas que toman el tipo CultureInfo. En este caso, WPF ha usado el tipo CultureInfo existente de Microsoft .NET Framework para abordar mejor los escenarios de compatibilidad y migración que se han usado en versiones anteriores de marcos, pero el tipo CultureInfo no admitía los constructores necesarios ni la conversión de tipos en el nivel del tipo que se pueda usar como un valor de propiedad XAML directamente.

Siempre que exponga una propiedad que se pueda usar en XAML, en particular si es un creador de controles, deberá considerar la posibilidad de respaldar esa propiedad con una propiedad de dependencia. Esto es especialmente cierto si se usa la implementación existente de Windows Presentation Foundation (WPF) del procesador XAML, porque puede mejorar el rendimiento usando el soporte de DependencyProperty. Una propiedad de dependencia expondrá para la propiedad las características del sistema de propiedades que los usuarios esperan de una propiedad accesible a través de XAML. Esto incluye características tales como la animación, el enlace de datos y la compatibilidad con estilos. Para obtener más información, vea Propiedades de dependencia personalizadas y Carga de XAML y propiedades de dependencia.

Escribir y asignar atributos a un convertidor de tipos

A veces, necesitará escribir una clase personalizada derivada de TypeConverter para proporcionar conversión de tipos a un tipo de propiedad. Para obtener instrucciones sobre cómo derivar y crear un convertidor de tipos que admita usos XAML y cómo aplicar el objeto TypeConverterAttribute, vea Clases TypeConverter y XAML.

Requisitos para la sintaxis de atributo del controlador de eventos XAML en los eventos de una clase personalizada

Para que se pueda usar como un evento CLR, el evento se debe exponer como un evento público en una clase que admita un constructor sin parámetros o en una clase abstracta donde se pueda tener acceso al evento en clases derivadas. Para poder usarse cómodamente como un evento enrutado, el evento de CLR debe implementar métodos add y remove explícitos que agreguen y quiten controladores para la firma de evento de CLR y reenvíen esos controladores a los métodos AddHandler y RemoveHandler. Estos métodos agregan o quitan controladores del almacén del controlador de eventos enrutados en la instancia a la que está asociado el evento.

Nota:

Es posible registrar controladores directamente para eventos enrutados usando AddHandler, así como no definir deliberadamente un evento de CLR que exponga el evento enrutado. Esto no suele ser recomendable porque el evento no habilitará la sintaxis de atributo de XAML para asociar controladores, y la clase resultante ofrecerá una vista menos transparente XAML de las capacidades de ese tipo.

Escribir propiedades de colección

Las propiedades cuyo tipo es una colección tienen una sintaxis XAML que le permite especificar los objetos que se agregan a la colección. Esta sintaxis tiene dos características notables.

  • El objeto que es el objeto de colección no necesita especificarse en la sintaxis de elemento de objeto. La presencia de ese tipo de colección es implícita cuando se especifica una propiedad en XAML que toma un tipo de colección.

  • Los elementos secundarios de la propiedad de colección del marcado se procesan para convertirse en miembros de la colección. Normalmente, el acceso del código a los miembros de una colección se realiza a través de métodos de lista y diccionario, tales como Add, o mediante un indexador. Pero la sintaxis de XAML no admite los métodos o indexadores (excepción: XAML 2009 puede admitir métodos, pero con XAML 2009 limita los usos posibles de WPF; vea Características del lenguaje XAML 2009). Las colecciones son obviamente un requisito muy común para compilar un árbol de elementos y necesita alguna manera de rellenarlas en XAML declarativo. Por consiguiente, los elementos secundarios de una propiedad de colección se procesan agregándolos a la colección que es el valor de tipo de la propiedad de colección.

La implementación de los servicios XAML de.NET Framework y el procesador XAML de WPF usan así la siguiente definición de lo que constituye una propiedad de colección. El tipo de propiedad de la propiedad debe implementar una de las opciones siguientes:

Cada uno de estos tipos de CLR tiene un método de Add, usado por el procesador XAML para agregar elementos a la colección subyacente al crear el gráfico de objetos.

Nota:

Las interfaces List y Dictionary genéricas (IList<T> y IDictionary<TKey,TValue>) no se admiten para la detección de colecciones mediante el procesador XAML de WPF. Pero puede usar la clase List<T> como una clase base, porque implementa IList directamente, o Dictionary<TKey,TValue> como una clase base, porque implementa IDictionary directamente.

Al declarar una propiedad que toma una colección, tenga cuidado con el modo en el que se inicializa ese valor de propiedad en las nuevas instancias del tipo. Si no está implementando la propiedad como una propiedad de dependencia, es aconsejable hacer que la propiedad use un campo de respaldo que llame al constructor del tipo de colección. Si la propiedad es una propiedad de dependencia, quizá necesite inicializar la propiedad de colección como parte del constructor de tipo predeterminado. Esto se debe a que una propiedad de dependencia toma su valor predeterminado de los metadatos y, normalmente, no es conveniente que el valor inicial de una propiedad de colección sea una colección estática compartida. Debería haber una instancia de colección por cada instancia del tipo contenedor. Para más información, consulte Propiedades de dependencia personalizadas.

Puede implementar un tipo de colección personalizado para la propiedad de colección. Debido al tratamiento de propiedad de colección implícito, no es necesario que el tipo de colección personalizado proporcione un constructor sin parámetros para que se use implícitamente en XAML. En cambio, puede proporcionar de manera opcional un constructor sin parámetros para el tipo de colección. Esta puede ser una práctica útil. A menos que proporcione un constructor sin parámetros, no puede declarar explícitamente la colección como un elemento de objeto. Es posible que algunos autores de marcado prefieran considerar la colección explícita como una cuestión de estilo de marcado. Además, un constructor sin parámetros puede simplificar los requisitos de inicialización al crear nuevos objetos que usen el tipo de colección como un valor de propiedad.

Declarar propiedades de contenido XAML

El lenguaje XAML define el concepto de una propiedad de contenido de XAML. Cada clase que se puede usar en la sintaxis de objeto puede tener exactamente una propiedad de contenido XAML. Para declarar una propiedad como propiedad de contenido XAML de la clase, aplique ContentPropertyAttribute como parte de la definición de clase. Especifique el nombre de la propiedad de contenido XAML prevista como el Name en el atributo. La propiedad se especifica como una cadena por nombre, no como una construcción de reflexión como PropertyInfo.

Puede especificar una propiedad de colección para que sea la propiedad de contenido XAML. El resultado es un uso para esa propiedad mediante la cual el elemento de objeto puede tener uno o más elementos secundarios, sin elementos de objeto de colección intermedios ni etiquetas de elemento de propiedad. Estos elementos se tratan entonces como el valor de la propiedad de contenido XAML y se agregan a la instancia de respaldo de la colección.

Algunas propiedades existentes de contenido de XAML usan el tipo de propiedad de Object. Esto permite que una propiedad de contenido XAML tome valores primitivos, como un valor String, así como un valor de objeto de referencia único. Si sigue este modelo, el tipo será responsable de la determinación de tipo, así como del control de los tipos posibles. La razón típica para un tipo de contenido Object es admitir tanto un medio simple para agregar contenido de objetos en forma de cadena (que recibe un tratamiento de presentación predeterminado) como un medio avanzado de agregar contenido de objetos que especifique una presentación no predeterminada o datos adicionales.

Serializar XAML

En determinados escenarios, como si es un creador de controles, quizá también quiera asegurarse de que cualquier representación de objeto de la que se pueda crear una instancia en XAML también pueda serializarse de nuevo al marcado equivalente de XAML. Los requisitos de serialización no se describen en este tema. Vea Información general sobre la creación de controles y Árbol de elementos y serialización.

Vea también