Compartir a través de



Mayo de 2019

Volumen 34, número 5

[XAML]

Controles XAML personalizados

Por Jerry Nixon

Como desarrollador empresarial, ya sabe cómo moverse por SQL Server. Conoce los servicios web de .NET. Y, para usted, diseñar atractivas interfaces XAML (probablemente con Windows Presentation Foundation [WPF]) es un juego de niños. Al igual que miles de otros desarrolladores profesionales, su currículum está lleno de tecnologías de Microsoft y recorta artículos de MSDN Magazine como este para anclarlos en su panel kanban. Prepare las tijeras: este artículo contiene recursos fundamentales.

Ha llegado el momento de mejorar su experiencia con los controles XAML. El marco XAML ofrece una amplia biblioteca de controles para el desarrollo de la interfaz de usuario, pero, para conseguir lo que quiere, se necesitan más. En este artículo, mostraré cómo obtener los resultados que quiera mediante controles XAML personalizados.

Controles personalizados

A la hora de crear controles personalizados en XAML, existen dos enfoques: controles de usuario y controles basados en modelo. Los controles de usuario son un enfoque sencillo y compatible con el diseñador que permite crear un diseño reutilizable. Los controles basados en modelo ofrecen un diseño flexible con una API personalizable para los desarrolladores. Como sucede en cualquier lenguaje, los diseños sofisticados pueden generar miles de líneas de XAML que pueden resultar difíciles de navegar de forma productiva. Los controles personalizados son una estrategia eficaz para reducir el código del diseño.

Elegir el enfoque correcto afectará al grado de satisfacción con el que puede utilizar y reutilizar los controles de la aplicación. Estas son algunas consideraciones que le ayudarán a empezar.

Simplicidad. Lo fácil no es siempre sencillo, pero lo sencillo siempre es fácil. Los controles de usuario son sencillos y fáciles. Los desarrolladores de cualquier nivel pueden proporcionarlos sin apenas tener que recurrir a la documentación.

Experiencia de diseño. A muchos desarrolladores les encanta el diseñador XAML. Los diseños de control basado en modelo se pueden compilar en el diseñador, pero es el control de usuario el que aprovecha la experiencia en tiempo de diseño.

Superficie de API. La creación de una superficie de API intuitiva permite a los desarrolladores usarla fácilmente. Los controles de usuario admiten propiedades, eventos y métodos personalizados, pero los controles basados en modelo son los más flexibles.

Objetos visuales flexibles. Proporcionar una gran experiencia predeterminada permite a los desarrolladores consumir controles con facilidad. Pero los controles flexibles admiten objetos visuales basados en modelos redefinidos. Solo los controles basados en modelo admiten la redefinición de modelos.

En resumen, los controles de usuario son ideales para la simplicidad y la experiencia de diseño, mientras que los controles basados en modelo ofrecen la mejor superficie de API y los objetos visuales más flexibles.

Si decide empezar con un control de usuario y migrar a un control basado en modelo, tiene mucho trabajo por delante. Pero no es el fin. En este artículo, se empieza por un control de usuario y se pasa a un control basado en modelo. Es importante tener en cuenta que muchos diseños reutilizables solo requieren controles de usuario. También es razonable abrir una solución de línea de negocio y buscar tanto controles de usuario como controles basados en modelo.

Nuevo caso de usuario

Necesita obtener el consentimiento de los nuevos usuarios del contrato de licencia del usuario final (CLUF) en la nueva aplicación. Ambos sabemos que ningún usuario quiere aceptar el CLUF. Aun así, el departamento legal debe asegurarse de que los usuarios marquen "Acepto" antes de continuar. Así que, aunque el CLUF sea confuso, debe asegurarse de que la interfaz XAML sea limpia e intuitiva. Para crear el prototipo, agregue los elementos TextBlock, CheckBox y Button, tal como se muestra en la figura 1. Después, empiece a pensar.

Prototipo de interfaz de usuario
Figura 1 Prototipo de interfaz de usuario

Crear prototipos en el diseñador XAML es un proceso rápido. Además, es fácil, porque dedicó tiempo a conocer las herramientas. Pero, ¿qué sucede con otras formas de la aplicación? Puede que necesite esta funcionalidad en cualquier otro sitio. La encapsulación es un modelo de diseño que se usa para ocultar una lógica compleja del consumidor. Una vez y solo una (DRY) es otro, centrado en la reutilización del código.  XAML ofrece ambos modelos mediante controles de usuario y controles personalizados. Como desarrollador XAML, sabe que los controles personalizados son más eficaces que los controles de usuario, pero no son tan simples. Para empezar, decide usar un control de usuario. Puede que sirva para hacer el trabajo. Alerta de spoiler: no sirve.

Controles de usuario

Los controles de usuario son sencillos. Proporcionan interfaces coherentes y reutilizables, y un código subyacente personalizado y encapsulado. Para crear un control de usuario, seleccione Control de usuario en el cuadro de diálogo Agregar nuevo elemento, como se muestra en la figura 2.

Cuadro de diálogo Agregar nuevo elemento
Figura 2 Cuadro de diálogo Agregar nuevo elemento

Los controles de usuario suelen ser un elemento secundario de otro control. Sin embargo, su ciclo de vida es tan similar al de las ventanas y las páginas que un control de usuario puede ser el valor definido en la propiedad Window.Current.Content. Los controles de usuario son muy completos: admiten la administración del estado visual, recursos internos y el resto de componentes del marco XAML. Crearlos no supone ningún riesgo para la funcionalidad disponible. Mi objetivo es volver a usarlos en una página aprovechando su compatibilidad con la administración del estado visual, los recursos, los estilos y el enlace de datos. Su implementación XAML es sencilla y compatible con el diseñador:

<UserControl>
  <StackPanel Padding="20">
    <TextBlock>Lorem ipsum.</TextBlock>
    <CheckBox>I agree!</CheckBox>
    <Button>Submit</Button>
  </StackPanel>
</UserControl>

este XAML representa el prototipo anterior y muestra lo fácil que puede ser un control de usuario. Por supuesto, aún no hay ningún comportamiento personalizado, solo el comportamiento integrado de los controles que declaro.

El camino rápido del texto Los CLUF son largos, así que es necesario abordar el rendimiento del texto. TextBlock (y solo TextBlock) se ha optimizado para usar el camino rápido, poca memoria y representación de CPU. Está pensado para ser rápido, pero puedo anticipar que:

<TextBlock Text="Optimized" />
<TextBlock>Not optimized</TextBlock>

El uso de controles insertados como <Run/> y <LineBreak /> interrumpe la optimización. Algunas propiedades, como CharacterSpacing, LineStackingStrategy y TextTrimming, pueden hacer lo mismo. ¿Está confundido? Existe una prueba sencilla:

Application.Current.DebugSettings
  .IsTextPerformanceVisualizationEnabled = true;

IsTextPerformanceVisualizationEnabled es una configuración de depuración poco conocida que le permite ver qué texto de la aplicación está optimizado a medida que realiza la depuración. Si el texto no es verde, ha llegado la hora de investigar.

Con cada versión de Windows, menos propiedades afectan al camino rápido. Sin embargo, aún existen algunas que afectan al rendimiento de forma negativa e inesperada. Pero, con un poco de depuración intencionada, esto no supone un problema.

Reglas inmutables Hay tantas opiniones como opciones acerca de dónde debe residir la lógica de negocio. Una regla general coloca las reglas menos mutables más cerca del control. Esto suele ser más fácil y rápido, y optimiza la facilidad de mantenimiento.

Por cierto, las reglas de negocio son diferentes de la validación de datos. Responder a la entrada de datos y comprobar la longitud del texto y los rangos numéricos es, simplemente, una validación. Las reglas controlan los tipos de comportamiento del usuario.

Por ejemplo, un banco tiene una regla de negocio que no permite conceder crédito a los clientes con una puntuación de crédito por debajo de un valor determinado. Un fontanero tiene la regla de no desplazarse hasta un cliente ubicado fuera de un determinado código postal. Las reglas se centran en el comportamiento. En algunos casos, las reglas cambian cada día; por ejemplo, qué puntuaciones de crédito influyen sobre los nuevos préstamos. En otros casos, las reglas nunca cambian; por ejemplo, un mecánico nunca trabajará en un Subaru anterior a 2014.

Ahora, considere estos criterios de aceptación: Un usuario no puede hacer clic en el botón hasta que se active CheckBox. Se trata de una regla y no podría ser más inmutable. Voy a implementarla cerca de mis controles:

<StackPanel Padding="20">
  <TextBlock>Lorem ipsum.</TextBlock>
  <CheckBox x:Name="AgreeCheckBox">I agree!</CheckBox>
  <Button IsEnabled="{Binding Path=IsChecked,
    ElementName=AgreeCheckBox}">Submit1</Button>
  <Button IsEnabled="{x:Bind Path=AgreeCheckBox.IsChecked.Value,
    Mode=OneWay}">Submit2</Button>
</StackPanel>

En este código, el enlace de datos satisface mi requisito a la perfección. El botón Submit1 usa el enlace de datos WPF (y UWP) clásico. El botón Submit2 usa el enlace de datos UWP moderno.

Observe en la figura 3 que Submit2 está habilitado. ¿Es correcto? Bueno, en el diseñador de Visual Studio, el enlace de datos clásico tiene la ventaja de la representación en tiempo de diseño. Por ahora, el enlace de datos compilados (x: Bind) solo se produce en tiempo de ejecución. Elegir entre el enlace de datos compilado y clásico es la decisión sencilla más difícil que tendrá que tomar. Por un lado, el enlace compilado es rápido. Pero, por otro lado, el enlace clásico es sencillo. El enlace compilado existe para solucionar el difícil problema de rendimiento de XAML: el enlace de datos. Dado que el enlace clásico requiere reflexión en tiempo de ejecución, es intrínsecamente más lento, con dificultades para escalar.

Implementar una regla de negocios con enlace de datos
Figura 3 Implementar una regla de negocios con enlace de datos

Se han agregado muchas características nuevas para el enlace clásico, como el enlace asincrónico, y han surgido varios patrones para ayudar a los desarrolladores. Sin embargo, aunque UWP se posicionó para reemplazar WPF, sufría el mismo problema de ralentización. Esto es algo que se debe considerar: la capacidad de usar el enlace clásico en modo asincrónico no se portó de WPF a UWP. Interprételo como quiera, pero esto anima a los desarrolladores empresariales a invertir en enlaces compilados. Los enlaces compilados aprovechan el generador de código XAML para crear automáticamente el código subyacente y acoplar las instrucciones de enlace con propiedades y tipos de datos reales que se esperan en tiempo de ejecución.

Debido a este acoplamiento, los tipos no coincidentes pueden generar errores, como puede hacerlo intentar crear enlaces con objetos anónimos o con objetos JSON dinámicos. Estos casos extremos no pasan desapercibidos para muchos desarrolladores, pero han desaparecido:

  • Los enlaces compilados resuelven los problemas de rendimiento de enlace de datos al introducir determinadas restricciones.
  • La compatibilidad con versiones anteriores mantiene la compatibilidad de enlace clásica al mismo tiempo que ofrece una opción mejor a los desarrolladores de UWP.
  • Las innovaciones y mejoras para el enlace de datos se invierten en el enlace compilado, no en el enlace clásico.
  • Las características como el enlace de función solo están disponibles con el enlace compilado donde la estrategia de enlace de Microsoft está claramente centrada.

Sin embargo, la simplicidad y la compatibilidad en tiempo de diseño del enlace clásico mantienen vivo el argumento y presionan al equipo de herramientas de desarrollo de Microsoft para que siga mejorando el enlace compilado y su experiencia de desarrollo. Tenga en cuenta que, en este artículo, elegir uno u otro supone una enorme diferencia. Algunos ejemplos demuestran el enlace clásico, mientras que otros muestran el enlace compilado. Usted decide. La decisión, por supuesto, tiene mayor impacto en grandes aplicaciones.

Eventos personalizados Los eventos personalizados no se pueden declarar en XAML, por lo que debe controlarlos en el código subyacente. Por ejemplo, puedo desviar el evento de clic del botón de enviar a un evento de clic personalizado de mi control de usuario:

public event RoutedEventHandler Click;
public MyUserControl1()
{
  InitializeComponent();
  SubmitButton.Click += (s, e)
    => Click?.Invoke(this, e);
}

En este caso, el código genera los eventos personalizados y desvía RoutedEventArgs desde el botón. Los desarrolladores de consumo pueden controlar estos eventos mediante declaración, como cualquier otro evento en XAML:

<controls:MyUserControl1 Click="MyUserControl1_Click" />

El valor de esto es que los desarrolladores de consumo no tienen que aprender un nuevo paradigma: los controles personalizados y los controles predeterminados propios se comportan igual, desde el punto de vista funcional.

Propiedades personalizadas Para que los desarrolladores de consumo tengan su propio CLUF, puedo establecer el atributo x:FieldModifier en TextBlock. Esto modifica el comportamiento de compilación de XAML del valor predeterminado privado:

<TextBlock x:Name="EulaTextBlock" x:FieldModifier="public" />

pero que sea fácil no significa que sea bueno. Este método ofrece poco nivel de abstracción y requiere que los desarrolladores comprendan la estructura interna. También se requiere código subyacente. Por lo tanto, en este caso, evitaré el uso del enfoque de atributo:

public string Text
{
  get => (string)GetValue(TextProperty);
  set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty =
  DependencyProperty.Register(nameof(Text), typeof(string),
    typeof(MyUserControl1), new PropertyMetadata(string.Empty));

Igual de fácil, y sin las advertencias, es un enlace de datos de propiedad de dependencia con la propiedad Text de TextBlock. Esto permite al desarrollador de consumo leer, escribir o enlazar con la propiedad Text personalizada:

<StackPanel Padding="20">
  <TextBlock Text="{x:Bind Text, Mode=OneWay}" />
  <CheckBox>I agree!</CheckBox>
  <Button>Submit</Button>
</StackPanel>

La propiedad de dependencia es necesaria para admitir el enlace de datos. Los controles sólidos admiten casos de uso básicos como enlace de datos. Además, la propiedad de dependencia solo agrega una línea a mi base de código:

<TextBox Text="{x:Bind Text, Mode=TwoWay,
  UpdateSourceTrigger=PropertyChanged}" />

El enlace de datos bidireccional para las propiedades personalizadas en los controles de usuario se admite sin INotifyPropertyChanged. El motivo es que las propiedades de dependencia generan eventos cambiados internos que supervisa el marco del enlace. Es su propio tipo de INotifyPropertyChanged.

El código anterior nos recuerda que UpdateSourceTrigger determina cuándo se registran los cambios. Los valores posibles son Explicit, LostFocus y PropertyChanged. Esto último se produce cuando se realizan cambios.

Obstáculos El desarrollador de consumo puede querer establecer la propiedad de contenido del control de usuario. Se trata de un enfoque intuitivo, pero no es compatible con los controles de usuario. La propiedad ya está establecida en el XAML que declaré:

<Controls:MyUserControl>
  Lorem Ipsum
</Controls:MyUserControl>

Esta sintaxis sobrescribe la propiedad de contenido: TextBlock, CheckBox y Button. Si considero la redefinición de modelos un caso de uso de XAML básico, mi control de usuario no puede ofrecer una experiencia sólida y completa. Los controles de usuario son sencillos, pero ofrecen poco control o extensibilidad. Una sintaxis intuitiva y compatibilidad con la redefinición de modelos forman parte de una experiencia común. Ha llegado el momento de considerar un control basado en modelo.

Controles basados en modelo

XAML ha experimentado grandes mejoras en cuanto a consumo de memoria, rendimiento, accesibilidad y coherencia visual. A los desarrolladores les encanta XAML porque es flexible. Los controles basados en modelo son un caso puntual.

Los controles basados en modelo pueden definir algo totalmente nuevo, pero suelen estar formados por varios controles existentes. Este ejemplo, con los elementos TextBlock, CheckBox y Button juntos, es un escenario clásico.

Por cierto, no confunda los controles basados en modelo con los controles personalizados. Un control basado en modelo es un diseño personalizado. Un control personalizado es, simplemente, una clase que hereda un control existente sin ningún estilo personalizado. A veces, si solo necesita un método o propiedad adicional en un control existente, los controles personalizados son una excelente opción. Sus objetos visuales y la lógica ya están instalados y basta con ampliarlos.

Plantilla de control ControlTemplate define un diseño de control. Este recurso especial se aplica en tiempo de ejecución. Cada cuadro y botón reside en ControlTemplate. La propiedad Template puede acceder fácilmente a la propiedad de plantilla. Esa propiedad de plantilla no es de solo lectura. Los desarrolladores pueden establecerla en un elemento ControlTemplate personalizado a fin de transformar los objetos visuales y el comportamiento de un control para satisfacer sus necesidades concretas. Es el punto fuerte de la redefinición de modelos:

<ControlTemplate>
  <StackPanel Padding="20">
    <ContentControl Content="{TemplateBinding Content}" />
    <CheckBox>I agree!</CheckBox>
    <Button>Submit1</Button>
  </StackPanel>
</ControlTemplate>

el código XAML de ControlTemplate es similar al de cualquier otra declaración de diseño. En el código anterior, observe la extensión de marcado especial de TemplateBinding. Este enlace especial está optimizado para operaciones de plantilla unidireccionales. Desde Windows 10 versión 1809, se admite la sintaxis x:Bind en las definiciones de ControlTemplate de UWP. Esto permite enlaces funcionales de alto rendimiento, compilados y bidireccionales en las plantillas. TemplateBinding funciona muy bien en la mayoría de los casos.

Generic.XAML Para crear un control basado en modelo, seleccione el control basado en modelo en el cuadro de diálogo Agregar nuevo elemento. Esto introduce tres archivos: el archivo XAML; el código subyacente y themes/generic.xaml, que contiene ControlTemplate. El archivo themes/generic.xaml es idéntico a WPF. Es especial. El marco de trabajo lo combina con los recursos de la aplicación automáticamente. Los recursos que se definen aquí tienen como ámbito el nivel de aplicación:

<Style TargetType="controls:MyControl">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="controls:MyControl" />
    </Setter.Value>
  </Setter>
</Style>

Se aplican ControlTemplates mediante estilos implícitos: estilos sin clave. Los estilos explícitos tienen una clave que se usa para aplicarlos a controles, mientras que los estilos implícitos se aplican en función de TargetType. Por lo tanto, debe establecer DefaultStyleKey:

public sealed class MyControl : Control
{
  public MyControl() => DefaultStyleKey = typeof(MyControl);
}

Este código establece DefaultStyleKey, que determina qué estilo se aplica implícitamente al control. Lo estoy estableciendo en el valor correspondiente de TargetType en ControlTemplate:

<ControlTemplate TargetType="controls:MyControl">
  <StackPanel Padding="20">
    <TextBlock Text="{TemplateBinding Text}" />
    <CheckBox Name="AgreeCheckbox">I agree!</CheckBox>
    <Button IsEnabled="{Binding IsChecked,
      ElementName=AgreeCheckbox}">Submit</Button>
  </StackPanel>
</ControlTemplate>

TemplateBinding enlaza la propiedad Text de TextBlock con la propiedad de dependencia personalizada copiada desde el control de usuario al control basado en modelo. TemplateBinding es unidireccional, muy eficaz y, generalmente, la mejor opción.

En la figura 4 se muestra el resultado de mi trabajo en el diseñador. El diseño personalizado declarado en ControlTemplate se aplica a mi control personalizado, y el enlace se ejecuta y representa en tiempo de diseño:

 

<controls:MyControl Text="My outside value." />

Obtener una vista previa de forma transparente mediante propiedades internas
Figura 4 Obtener una vista previa de forma transparente mediante propiedades internas

La sintaxis para usar mi control personalizado es sencilla. Para mejorarla, permitiré al desarrollador usar texto insertado. Esta es la sintaxis más intuitiva para establecer el contenido de un elemento. XAML proporciona un atributo de clase que me ayuda a hacerlo, como se muestra en la figura 5.

Figura 5 Uso de atributos de clase para definir la propiedad de contenido

[ContentProperty(Name = "Text")]
public sealed class MyControl : Control
{
  public MyControl() => DefaultStyleKey = typeof(MyControl);
  public string Text
  {
    get => (string)GetValue(TextProperty);
    set => SetValue(TextProperty, value);
  }
  public static readonly DependencyProperty TextProperty =
    DependencyProperty.Register(nameof(Text), typeof(string),
      typeof(MyControl), new PropertyMetadata(default(string)));
}

Tenga en cuenta el atributo ContentProperty, que procede del espacio de nombres Windows.UI.Xaml.Markup. Indica en qué propiedad se debe escribir el contenido directo insertado en XAML. Ahora ya puedo declarar mi contenido como se indica a continuación:

<controls:MyControl>
  My inline value!
</controls:MyControl>

Qué bonito. Los controles basados en modelo proporcionan la flexibilidad necesaria para diseñar la interacción y la sintaxis del control de la forma que le parezca más intuitiva. En la figura 6 se muestra el resultado de la introducción de ContentProperty en el control.

Vista previa de diseño
Figura 6 Vista previa de diseño

ContentControl Donde, anteriormente, tenía que crear una propiedad personalizada y asignarla al contenido del control, XAML proporciona un control incorporado para eso. Se conoce como ContentControl y su propiedad se denomina Content. ContentControl también proporciona las propiedades ContentTemplate y ContentTransition para controlar la visualización y las transiciones. Button, CheckBox, Frame y muchos controles XAML estándares heredan ContentControl. El mío también podría haberlo hecho, pero preferí usar Content en lugar de Text:

public sealed class MyControl2 : ContentControl
{
  // Empty
}

En este código, observe la sintaxis concisa para crear un control personalizado con una propiedad Content. ContentControl se representa automáticamente como ContentPresenter cuando se declara. Es una solución rápida y sencilla. Pero cuidado: ContentControl no es compatible con las cadenas literales en XAML. Dado que infringe mi objetivo de que mi control admita cadenas literales, me ceñiré a Control y tendré en cuenta ContentControl en otra ocasión.

Acceso a controles internos La directiva x:Name declara el nombre del campo generado automáticamente en un código subyacente. Asigne a una casilla un valor x:Name de MyCheckBox y el generador creará un campo en la clase denominado MyCheckBox.

Por el contrario, x:Key (solo para recursos) no crea ningún campo. Agrega recursos a un diccionario de tipos no resueltos hasta que se usan por primera vez. Para los recursos, x:Key ofrece mejoras de rendimiento.

Dado que x:Name crea un campo de respaldo, no se puede usar en ControlTemplate; las plantillas se desacoplan de cualquier clase de respaldo. En su lugar, debe usar la propiedad Name.

Name es una propiedad de dependencia en FrameworkElement, un antecesor de Control. Úselo cuando no pueda usar x:Name. Name y x:Name son mutuamente excluyentes en un ámbito determinado debido a FindName.

FindName es un método XAML que se usa para buscar objetos por nombre; funciona con Name o x:Name. Es confiable en el código subyacente, pero no en controles basados en modelo, donde debe usar GetTemplateChild:

protected override void OnApplyTemplate()
{
  if (GetTemplateChild("AgreeCheckbox") is CheckBox c)
  {
    c.Content = "I really agree!";
  }
}

GetTemplateChild es un método auxiliar que se usa en la invalidación de OnApplyTemplate para buscar los controles que ha creado ControlTemplate. Úselo para buscar referencias a controles internos.

Cambiar la plantilla La redefinición del modelo de control es sencilla, pero he compilado la clase para esperar controles con determinados nombres. Debo asegurarme de que la nueva plantilla mantenga esta dependencia. Vamos a crear un nuevo elemento ControlTemplate:

<ControlTemplate
  TargetType="controls:MyControl"
  x:Key="MyNewStyle">

Los cambios pequeños en ControlTemplate son normales. No es necesario empezar desde cero. En el panel Esquema de documento de Visual Studio, haga clic con el botón derecho en cualquier control y extraiga una copia de su plantilla actual (consulte la figura 7).

Extraer una plantilla de control
Figura 7 Extraer una plantilla de control

Si mantengo sus dependencias, el nuevo elemento ControlTemplate puede cambiar completamente los objetos visuales y los comportamientos de un control. Declarar un estilo explícito en el control indica al marco que debe ignorar el estilo implícito predeterminado:

<controls:MyControl Style="{StaticResource MyControlNewStyle}">

Pero la reescritura de ControlTemplate conlleva una advertencia. Los desarrolladores y diseñadores de controles deben tener cuidado y admitir funcionalidades de localización y accesibilidad. Es fácil quitarlas por error.

TemplatePartAttribute Sería útil que un control personalizado pudiera comunicar los elementos con nombre que espera. Algunos elementos con nombre pueden ser necesarios solo en casos extremos. En WPF, tiene TemplatePartAttribute:

[TemplatePart (
  Name = "EulaTextBlock",
  Type = typeof(TextBlock))]
public sealed class MyControl : Control { }

Esta sintaxis muestra cómo el control puede comunicar dependencias internas a desarrolladores externos. En este caso, espero un elemento TextBlock con el nombre EulaTextBlock en mi elemento ControlTemplate. También puedo especificar los estados visuales que espero en mi control personalizado:

[TemplateVisualState(
  GroupName = "Visual",
  Name = "Mouseover")]
public sealed class MyControl : Control { }

TemplatePart se usa en Blend con TemplateVisualState para orientar a los desarrolladores en relación con las expectativas al crear plantillas personalizadas. ControlTemplate se puede validar con estas atribuciones. Desde 10240, WinRT incluye estos atributos. UWP puede usarlos, pero Blend para Visual Studio, no. Aún son una buena práctica directa, pero la documentación sigue siendo el mejor enfoque.

Accesibilidad Los controles XAML propios se han diseñado y probado meticulosamente para ser atractivos, compatibles y accesibles. Ahora, los requisitos de accesibilidad son ciudadanos de primera y son requisitos de versión para cada control.

Cuando redefine el modelo de un control propio, pone en riesgo las características de accesibilidad que agregan atentamente los equipos de desarrollo. Ponerlas en su sitio es difícil, pero quitarlas es fácil. Para redefinir el modelo de un control, debe estar muy familiarizado con las funcionalidades de accesibilidad del marco y las técnicas para implementarlas. De lo contrario, pierde una parte considerable de su valor.

Agregar accesibilidad como requisito de versión ayuda no solo a aquellas personas con discapacidades permanentes, sino también a los usuarios incapacitados temporalmente. También reduce el riesgo al redefinir el modelo de los controles propios.

Bien hecho.

Después de actualizar de un control de usuario a un control basado en modelo, he agregado poco código nuevo. Pero he agregado una gran cantidad de funcionalidad. Veamos qué se ha logrado en general.

Encapsulación. El control es una colección de varios controles enlazados con objetos visuales y comportamientos personalizados que los desarrolladores de consumo pueden reutilizar con facilidad en una aplicación.

Lógica de negocios. El control incorpora reglas de negocio que cumplen los criterios de aceptación del caso de usuario. He colocado reglas inmutables cerca del control y también he admitido una experiencia en tiempo de diseño avanzada.

Eventos personalizados. El control expone eventos personalizados específicos del control, como clics, que ayudan a los desarrolladores a interactuar con el control sin necesidad de entender la estructura interna del diseño.

Propiedades personalizadas. El control expone las propiedades para permitir que el desarrollador de consumo influya en el contenido del diseño. Esto se ha hecho de forma que se admita completamente el enlace de datos XAML.

Sintaxis de API. El control admite un enfoque intuitivo que permite a los desarrolladores declarar su contenido con cadenas literales y de una manera sencilla. He aprovechado el atributo ContentProperty para hacer esto.

Plantillas. El control se suministra con un valor de ControlTemplate predeterminado que presenta una interfaz intuitiva. Sin embargo, se admite la redefinición de modelos XAML para que los desarrolladores puedan personalizar los objetos visuales según sea necesario.

Aún queda mucho por hacer

Mi control necesita más, pero no mucho más: un poco de atención al diseño (por ejemplo, la necesidad de desplazarse por texto grande) y algunas propiedades (por ejemplo, el contenido de la casilla). Estoy increíblemente cerca.

Los controles pueden admitir la administración del estado visual, una característica nativa de XAML que permite a las propiedades cambiar en función de eventos de cambio de tamaño o marco (por ejemplo, mouseover). Los controles maduros tienen estados visuales.

Los controles pueden admitir la localización; la funcionalidad nativa de UWP usa los controles de asociación de la directiva x:Uid con cadenas RESW que filtra la configuración regional activa. Los controles maduros admiten la localización.

Los controles pueden admitir definiciones de estilo externas para ayudar a actualizar sus objetos visuales sin necesidad de requerir una nueva plantilla. Esto puede implicar objetos visuales compartidos y aprovechar temas y estilos BasedOn. Los controles maduros comparten y reutilizan estilos.

Resumen

La creación de prototipos de UI en XAML es rápida. Los controles de usuario crean diseños sencillos y reutilizables fácilmente. Los controles basados en modelo requieren un poco más de trabajo para crear diseños sencillos y reutilizables con funcionalidades más sofisticadas. El enfoque adecuado depende del desarrollador y se basa en algunos conocimientos y mucha experiencia. Experimente. Cuanto más conozca las herramientas, más productivo será.

Windows Forms reemplazó a Visual Basic 6 en 2002, igual que WPF reemplazó a Windows Forms en 2006. WPF trajo consigo XAML: un lenguaje declarativo para la interfaz de usuario. Los desarrolladores de Microsoft nunca habían visto algo como XAML. En la actualidad, Xamarin y UWP llevan XAML a iOS, Android, HoloLens, Surface Hub, Xbox, IoT y el escritorio moderno. De hecho, ahora, la tecnología base del propio sistema operativo Windows es XAML.

A los desarrolladores de todo el mundo les encanta XAML porque es muy productivo y flexible. Los ingenieros de Microsoft lo ven así, también: estamos creando nuestras propias aplicaciones e incluso Windows 10 con XAML. El futuro es brillante, las herramientas son eficaces y la tecnología es más accesible que nunca.


Jerry Nixones ingeniero de software sénior y arquitecto principal en la sección de ingeniería de software comercial en Microsoft. Ha desarrollado y diseñado software durante dos décadas. Además de ponente, organizador, profesor y autor, Nixon también es anfitrión de DevRadio. Dedica la mayoría de sus días a enseñar a sus tres hijas tramas de episodios y trasfondos de "Star Trek".

Gracias a los siguientes expertos técnicos de Microsoft por revisar este artículo: Daniel Jacobson, Dmitry Lyalin, Daren May, Ricardo Minguez Pablos


Comente este artículo en el foro de MSDN Magazine