Maio de 2019

Volume 34 – Número 5

[XAML]

Controles XAML personalizados

Por Jerry Nixon

Sendo um desenvolvedor empresarial, você conhece o SQL Server. Você conhece os serviços Web do .NET. E, para você, criar belas interfaces XAML (provavelmente com o Windows Presentation Foundation [WPF]) é brincadeira de criança. Assim como milhares de outros desenvolvedores de carreira, as tecnologias da Microsoft preenchem seu currículo e você recorta artigos da MSDN Magazine como este e os afixa em seu quadro Kanban. Pegue sua tesoura, este artigo é garantido.

Chegou a hora de melhorar sua experiência com controles XAML. A estrutura XAML oferece uma ampla biblioteca de controles para o desenvolvimento de interface do usuário, mas para fazer o que deseja, você precisa de mais. Neste artigo, mostrarei como obter os resultados desejados usando controles XAML personalizados.

Controles personalizados

Há duas abordagens para criar controles personalizados no XAML: controles de usuário e controles modelo. Os controles de usuário são uma abordagem fácil para o designer para criar um layout reutilizável. Controles modelo oferecem um layout flexível com uma API personalizável para desenvolvedores. Como acontece com qualquer linguagem, layouts sofisticados podem produzir milhares de linhas de XAML que podem ser difíceis de navegar de forma produtiva. Os controles personalizados são uma estratégia eficaz para reduzir o código de layout.

Escolher a abordagem correta afetará como você usará e reutilizará os controles em seu aplicativo. Aqui temos algumas opções para ajudá-lo a começar.

Simplicidade. Fácil nem sempre é simples, mas simples sempre é fácil. Controles de usuário são simples e fáceis. Desenvolvedores de qualquer nível podem fornecê-los com pouco alcance na documentação.

Experiência de design. Muitos desenvolvedores adoram o designer XAML. Layouts de controle modelo podem ser criados no designer, mas é o controle de usuário que engloba a experiência de tempo de design.

Superfície da API. A criação de uma superfície de API intuitiva permite aos desenvolvedores utilizá-la facilmente. Os controles de usuário oferecem suporte a propriedade, eventos e métodos personalizados, mas os controles modelo são os mais flexíveis.

Visuais flexíveis. Fornecer uma experiência padrão excelente permite que os desenvolvedores utilizem controles com facilidade. Mas os controles flexíveis dão suporte para visuais remodelados. Somente os controles modelo dão suporte a remodelagem.

Para resumir, controles de usuário são ideais para experiência de design e simplicidade, enquanto os controles modelo oferecem a melhor superfície de API e os visuais mais flexíveis.

Se você decidir começar com um controle de usuário e migrar para um controle modelo, terá algum trabalho pela frente. Mas, não é o fim do mundo. Este artigo começa com um controle de usuário e migra para um controle modelo. É importante reconhecer que vários layouts reutilizáveis exigem apenas controles de usuário. Também é razoável abrir uma solução de linha de negócios e localizar controles de usuário e controles modelo.

Uma nova história de usuário

Você precisa que os novos usuários aceitem o contrato de licença de usuário final (EULA) em seu novo aplicativo. Como eu e você bem sabemos, nenhum usuário quer aceitar o EULA. Ainda assim, o jurídico precisa garantir que os usuários marquem "Eu concordo" antes de continuar. Portanto, mesmo que o EULA seja confuso, você vai garantir que a interface do XAML seja clara e intuitiva. Comece pela criação de protótipo; adicione um TextBlock, uma CheckBox e um Button, conforme mostrado na Figura 1, e, em seguida, comece a pensar.

Protótipo da interface do usuário
Figura 1 - Protótipo da interface do usuário

A criação de protótipos no designer XAML é rápida. E é fácil, pois você aproveitou o tempo para aprender as ferramentas. Mas e quanto aos outros formulários em seu aplicativo? Você pode precisar dessa funcionalidade em outro lugar. Encapsulamento é um padrão de design usado para ocultar do consumidor uma lógica complexa. Don’t repeat yourself (DRY, não ser repetitivo) é outro, concentrando-se na reutilização de código.  O XAML oferece ambos por meio de controles de usuário e controles modelo. Como um desenvolvedor XAML, você sabe que controles modelo são mais eficientes do que controles de usuário, mas eles não são tão simples. Você decide começar por um controle de usuário. Talvez ele possa realizar o trabalho. Alerta de spoiler: ele não pode.

Controles de usuário

Controles de usuário são fáceis. Eles fornecem interfaces consistentes e reutilizáveis e um codebehind personalizado e encapsulado. Para criar um controle de usuário, selecione User Control no diálogo Add New Item, conforme mostrado na Figura 2.

Diálogo Add New Item
Figura 2 - Diálogo Add New Item

Normalmente, os controles de usuário são filhos de outro controle. No entanto, o ciclo de vida deles é tão semelhante ao de janelas e páginas, que um controle de usuário pode ser o valor definido na propriedade Window.Current.Content. Controles de usuário são recursos completos, internos e que dão suporte a gerenciamento de estado visual, e um ou outro elemento da estrutura XAML. Criá-los não é um compromisso de funcionalidade disponível. Meu objetivo é reutilizá-los em uma página, aproveitando o suporte para gerenciamento de estado visual, os recursos, os estilos e a associação de dados. A implementação de XAML deles é simples e fácil para o designer:

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

Este XAML renderiza meu protótipo anterior e mostra apenas a simplicidade de um controle de usuário. Obviamente, ainda não há um comportamento personalizado, apenas o comportamento interno dos controles que eu declaro.

Os EULAs de Text Fast Path são longos, portanto, vamos abordar o desempenho do texto. O TextBlock (e apenas o TextBlock) foi otimizado para usar caminho rápido, pouca memória e renderização de CPU. Ele foi criado para ser rápido, mas posso dar um spoiler:

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

Usar controles de embutidos do TextBlock, como <Run/> e <LineBreak />, interrompe a otimização. Propriedades como CharacterSpacing, LineStackingStrategy e TextTrimming podem fazer o mesmo. Confuso? Há um teste fácil:

Application.Current.DebugSettings
  .IsTextPerformanceVisualizationEnabled = true;

IsTextPerformanceVisualizationEnabled é uma configuração de depuração pouco conhecida que permite ver qual texto em seu aplicativo é otimizado durante a depuração. Se o texto não estiver verde, é hora de investigar.

A cada versão do Windows, menos propriedades afetam o caminho rápido. No entanto, ainda há várias que afetam o desempenho de maneira negativa e inesperada. Com um pouco de depuração intencional, isso não é problema.

Regras imutáveis Há tantas opiniões quanto há opções para onde a lógica de negócios deve residir. Uma regra geral coloca regras menos mutáveis mais próximas ao controle. Geralmente, é mais fácil e rápido e otimiza a facilidade de manutenção.

Regras de negócio, a propósito, são diferentes de validação de dados. Responder à entrada de dados e verificar tamanho do texto e intervalos numéricos é simplesmente validação. As regras controlam os tipos de comportamento do usuário.

Por exemplo, um banco tem como regra de negócio não fornecer empréstimo a clientes com uma classificação de crédito abaixo de um valor específico. Um encanador tem como regra não ir para um cliente fora de um determinado CEP. As regras são sobre comportamento. Em alguns casos, as regras mudam todos os dias, como quais classificações de crédito influenciam empréstimos novos. Em outros casos, as regras nunca mudam, como a regra de um mecânico de nunca querer trabalhar com Subaru anterior a 2014.

Agora, considere estes critérios de aceitação: Um usuário não pode clicar no botão enquanto a caixa de seleção não for marcada. Essa é uma regra e está tão próxima do imutável quanto possível. Vou implementá-la próxima aos meus 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>

Nesse código, a associação de dados satisfaz perfeitamente meu requisito. O botão de Submit1 usa a associação clássica de dados do WPF (e UWP). O botão de Submit2 usa a associação moderna de dados do UWP.

Observe na Figura 3 que Submit2 está habilitado. Está certo? Bem, no Designer do Visual Studio, a associação clássica de dados tem a vantagem de renderização no tempo de design. Por enquanto, a associação compilada de dados (x:BIND) só ocorre no tempo de execução. Escolher entre associação de dados compilada e clássica é a decisão fácil mais difícil que você vai tomar. Por um lado, a associação compilada é rápida. Mas, por outro, a associação clássica é simples. A associação compilada existe para resolver o problema difícil de desempenho do XAML: associação de dados. Como a associação clássica exige reflexão de tempo de execução, é inerentemente mais lenta, esforçando-se para se expandir.

Implementar uma regra de negócios com associação de dados
Figura 3 - Implementar uma regra de negócios com associação de dados

Muitos recursos novos foram adicionados à associação clássica, como a associação assíncrona, e surgiram vários padrões para ajudar os desenvolvedores. Mas, como o UWP assumiu a postura de ser o sucessor do WPF, sofria do mesmo problema de arrastamento. Eis aqui algo para se pensar: a capacidade de usar a associação clássica em um modo assíncrono não foi migrada do WPF para o UWP. Tire as suas conclusões, mas ele encoraja os desenvolvedores corporativos a investir em associação compilada. A associação compilada utiliza o gerador de código do XAML, criando o codebehind automaticamente e acoplando as instruções de associação a propriedades e datatypes reais esperados no tempo de execução.

Devido a esse acoplamento, tipos incompatíveis podem criar erros, pois podem tentar se associar a objetos anônimos ou a objetos JSON dinâmicos. Muitos desenvolvedores não sentem saudade desses casos extremos, mas eles deram o fora:

  • A associação compilada resolve os problemas de desempenho da associação de dados e, ao mesmo tempo, introduz determinadas restrições.
  • A compatibilidade com versões anteriores mantém o suporte à associação clássica, oferecendo aos desenvolvedores de UWP uma opção melhor.
  • Inovação e melhorias na associação de dados são investidas na associação compilada e não na associação clássica.
  • Recursos como associação de função estão disponíveis apenas com associação compilada em que a estratégia de associação da Microsoft esteja claramente focada.

Embora a simplicidade e o suporte para tempo de design da associação clássica mantenham o argumento ativo, há uma pressão sobre a equipe de ferramentas do desenvolvedor da Microsoft para continuar a melhorar a associação compilada e sua experiência do desenvolvedor. Neste artigo, escolher um ou outro terá um impacto quase imensurável. Alguns dos exemplos demonstrarão a associação clássica, enquanto outros mostrarão a associação compilada. Cabe a você decidir. A decisão, obviamente, é mais significativa em aplicativos grandes.

Eventos personalizados Eventos personalizados não podem ser declarados no XAML; portanto, você pode manipulá-los no seu codebehind. Por exemplo, eu posso encaminhar o evento de clique do botão de envio para um evento de clique personalizado em meu controle de usuário:

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

Aqui, o código gera os eventos personalizados, encaminhando o RoutedEventArgs do botão. Os desenvolvedores consumidores podem manipular esses eventos declarativamente, como todos os outros eventos no XAML:

<controls:MyUserControl1 Click="MyUserControl1_Click" />

O valor disso é que os desenvolvedores consumidores não precisam aprender um novo paradigma; controles personalizados e controles primários prontos para uso se comportam funcionalmente da mesma maneira.

Propriedades personalizadas Para permitir que os desenvolvedores consumidores forneçam seus próprios EULAs, posso definir o atributo X:FieldModifier em TextBlock. Isso modifica o comportamento de compilação do XAML do valor particular padrão:

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

Mas, fácil não significa bom. Esse método oferece pouca abstração e exige que os desenvolvedores entendam a estrutura interna. Ele também exige o codebehind. Portanto, evitarei usar a abordagem de atributo neste caso:

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));

Igualmente fácil, e sem as advertências, é uma associação de dados de propriedade de dependência com a propriedade Text do TextBlock. Isso permite que o desenvolvedor consumidor leia, grave ou associe a propriedade personalizada Text:

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

A propriedade de dependência é necessária para dar suporte à associação de dados. Controles robustos dão suporte a casos de uso básicos, como vinculação de dados. Além disso, a propriedade de dependência adiciona uma única linha à minha base de código:

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

A associação de dados bidirecional das propriedades personalizadas em controles de usuário tem suporte sem INotifyPropertyChanged. Isso ocorre porque as propriedades de dependência geram eventos internos alterados que a estrutura da associação monitora. É seu próprio tipo de INotifyPropertyChanged.

O código anterior nos lembra que UpdateSourceTrigger determina quando as alterações são registradas. Os valores possíveis são Explicit, LostFocus e PropertyChanged. O último ocorre conforme as alterações são feitas.

Obstáculos O desenvolvedor consumidor talvez queira definir a propriedade de conteúdo do controle de usuário. Essa é uma abordagem intuitiva, mas os controles de usuário não dão suporte. A propriedade já está definida para o XAML que eu declarei:

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

Essa sintaxe substitui a propriedade de conteúdo: TextBlock, CheckBox e Button. Se eu considerar a remodelagem de um caso de uso básico do XAML, meu controle de usuário não oferecerá uma experiência robusta e completa. Os controles de usuário são fáceis, mas oferecem pouco controle ou extensibilidade. Uma sintaxe intuitiva e suporte a remodelagem fazem parte de uma experiência comum. É hora de considerar um controle modelo.

Controles modelo

O XAML obteve grandes melhorias em consumo de memória, desempenho, acessibilidade e consistência visual. Os desenvolvedores adoram o XAML porque ele é flexível. Controles modelo são um bom exemplo.

Controles modelo podem definir algo completamente novo; mas, normalmente, são uma composição de vários controles existentes. O exemplo aqui de TextBlock, CheckBox e Button juntos é um cenário clássico.

A propósito, não confunda controles modelo com controles personalizados. Um controle modelo é um layout personalizado. Um controle personalizado é simplesmente uma classe herdando um controle existente sem qualquer estilo personalizado. Às vezes, se tudo o que você precisa é um método ou propriedade adicional em um controle existente, os controles personalizados são uma ótima opção. Os visuais e lógica dele já estão em vigor e você está simplesmente estendendo-os.

Modelo de controle Um layout de um controle é definido por um ControlTemplate. Esse recurso especial é aplicado no tempo de execução. Cada botão e caixa residem no ControlTemplate. Um modelo de controle pode ser facilmente acessado pela propriedade Template. Essa propriedade Template não é somente leitura. Os desenvolvedores podem defini-la como um ControlTemplate personalizado, transformando os visuais e o comportamento de um controle para atender às suas necessidades específicas. Esse é o poder de remodelagem:

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

O XAML ControlTemplate se parece com qualquer outra declaração de layout. No código anterior, observe a extensão de marcação especial TemplateBinding. Essa associação especial é ajustada para operações de modelo unidirecional. Desde o Windows 10 versão 1809, a sintaxe de x:Bind tem suporte das definições de ControlTemplate do UWP. Isso permite associações de desempenho, compiladas, bidirecionais e de função em modelos. TemplateBinding funciona muito bem na maioria dos casos.

Generic.xaml Para criar um controle modelo, selecione Template Control no diálogo Add New Item. Isso introduz três arquivos: o arquivo XAML; seu codebehind; e themes/generic.xaml, que mantém o ControlTemplate. O arquivo themes/generic.xaml é idêntico ao WPF. Ele é especial. A estrutura mescla-o automaticamente com os recursos do seu aplicativo. Os recursos definidos aqui têm escopo no nível de aplicativo:

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

ControlTemplates são aplicados usando estilos implícitos: estilos sem uma chave. Os estilos explícitos têm uma chave usada para aplicá-los aos controles; estilos implícitos são aplicados com base em TargetType. Assim, você deve definir DefaultStyleKey:

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

Esse código define DefaultStyleKey, determinando qual estilo é aplicado implicitamente ao seu controle. Estou definindo-o como o valor correspondente de TargetType em 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 associa a propriedade Text do TextBlock à propriedade de dependência personalizada copiada do controle de usuário para o controle modelo. TemplateBinding é unidirecional, muito eficiente e, em geral, a melhor opção.

A Figura 4 mostra o resultado do meu trabalho no designer. O layout personalizado declarado em um ControlTemplate é aplicado ao meu controle personalizado e a associação é executada e renderizada no tempo de design:

 

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

Visualização transparente usando propriedades internas
Figura 4 - Visualização transparente usando propriedades internas

A sintaxe para usar meu controle personalizado é simples. Posso torná-la melhor, permitindo que o desenvolvedor use texto embutido. É a sintaxe mais intuitiva para definir o conteúdo de um elemento. O XAML fornece um atributo de classe para me ajudar a fazer isso, como mostra a Figura 5.

Figura 5 - Usar o atributo de classe para definir a propriedade de conteúdo

[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)));
}

Observe o atributo ContentProperty, que vem do namespace Windows.UI.Xaml.Markup. Ele indica em qual propriedade direta o conteúdo embutido declarado em XAML deve ser gravado. Agora, eu posso declarar meu conteúdo como este:

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

É lindo. Os controles modelo oferecem a você a flexibilidade para projetar a interação do controle e a sintaxe da maneira que lhe parecer mais intuitiva. A Figura 6 mostra o resultado da introdução de ContentProperty ao controle.

Visualização de design
Figura 6 - Visualização de design

O ContentControl Onde anteriormente eu precisei criar uma propriedade personalizada e mapeá-la até o conteúdo do controle, o XAML fornece um controle já criado para isso. Ele é conhecido como ContentControl e sua propriedade é chamada de Content. O ContentControl também fornece as propriedades ContentTemplate e ContentTransition para manipular visualização e transições. Button, CheckBox, Frame e vários controles XAML padrão herdam o ContentControl. O meu poderia ter herdado também; eu usaria apenas Content no lugar de Text:

public sealed class MyControl2 : ContentControl
{
  // Empty
}

Nesse código, observe a sintaxe concisa para criar um controle personalizado com uma propriedade Content. ContentControl renderiza automaticamente como um ContentPresenter quando declarado. É uma solução rápida e fácil. No entanto, há uma limitação: ContentControl não dá suporte a cadeias de caracteres literais em XAML. Como ele viola o meu objetivo de fazer o controle dar suporte a cadeias de caracteres literais, continuarei com o Control, considerando ContentControl em outro momento.

Acessar controles internos A diretiva x:Name declara o nome do campo gerado automaticamente em um codebehind. Atribua uma caixa de seleção a um x:Name de MyCheckBox e o gerador criará um campo em sua classe chamada MyCheckBox.

Ao contrário, x:Key (somente para recursos) não cria um campo. Ele adiciona recursos a um dicionário de tipos não resolvidos até que eles sejam usados pela primeira vez. Para recursos, x:Key oferece melhorias de desempenho.

Como X:Name cria um campo de suporte, ele não pode ser usado em um ControlTemplate; modelos são desassociados de qualquer classe de suporte. Em vez disso, você pode usar a propriedade Name.

Name é uma propriedade de dependência no FrameworkElement, um ancestral do Control. Use-a quando não for possível usar x:Name. Name e x:Name são mutuamente exclusivos em um determinado escopo devido ao FindName.

FindName é um método XAML usado para localizar objetos por nome e ele funciona com Name ou x:Name. Ele é confiável no codebehind, mas não em controles modelo em que você deva usar GetTemplateChild:

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

GetTemplateChild é um método auxiliar usado na substituição de OnApplyTemplate para localizar controles criados por um ControlTemplate. Use-o para localizar referências a controles internos.

Alterar o modelo Remodelar o controle é simples, mas eu criei a classe para esperar controles com determinados nomes. Devo garantir que o novo modelo mantém essa dependência. Vamos criar um novo ControlTemplate:

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

Pequenas alterações em um ControlTemplate são normais. Você não precisa começar do zero. No painel Estrutura de Tópicos de Documentos do Visual Studio, clique com o botão direito do mouse em qualquer controle e extraia uma cópia do seu modelo atual (confira a Figura 7).

Extrair um modelo de controle
Figura 7 - Extrair um modelo de controle

Se eu mantiver suas dependências, meu novo ControlTemplate poderá alterar totalmente os visuais e comportamentos de um controle. Declarar um estilo explícito no controle informa a estrutura para ignorar o estilo implícito padrão:

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

Mas, essa recriação do ControlTemplate vem com um aviso. Os desenvolvedores e designers de controle devem ter o cuidado de dar suporte às funcionalidades de acessibilidade e localização. É fácil removê-las por engano.

TemplatePartAttribute Seria útil se um controle personalizado pudesse comunicar os elementos nomeados que ele espera. Alguns elementos nomeados podem ser necessários somente em casos extremos. No WPF, você tem TemplatePartAttribute:

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

Essa sintaxe mostra como o controle pode comunicar dependências internas a desenvolvedores externos. Nesse caso, posso esperar um TextBlock com o nome EulaTextBlock em meu ControlTemplate. Também posso especificar os estados visuais que espero em meu controle personalizado:

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

O TemplatePart é usado pelo Blend com TemplateVisualState para orientar os desenvolvedores em relação às expectativas durante a criação de modelos personalizados. Um ControlTemplate pode ser validado em relação a essas atribuições. Desde 10240, o WinRT inclui esses atributos. O UWP pode usá-los, mas o Blend para Visual Studio não. Essa prática continua sendo boa e progressiva, mas a documentação ainda é a melhor abordagem.

Acessibilidade Controles XAML primários são meticulosamente projetados e testados para serem bonitos, compatíveis e acessíveis. Requisitos de acessibilidade agora são cidadãos de primeira classe e são requisitos de versão de cada controle.

Quando remodela um controle primário, você coloca em risco os recursos de acessibilidade cuidadosamente adicionados pelas equipes de desenvolvimento. Eles são difíceis de acertar e fáceis de errar. Ao optar por remodelar um controle, você deve conhecer bem as funcionalidades de acessibilidade da estrutura e as técnicas para implementá-las. Caso contrário, perderá uma parte considerável do seu valor.

Adicionar acessibilidade como um requisito de versão ajuda não apenas aqueles com deficiências permanentes, mas também os que estão temporariamente incapacitados. Também reduz o risco ao remodelar controles primários.

Você conseguiu.

Após a atualização de um controle de usuário para um controle modelo, introduzi pouquíssimo código novo. Mas adicionei muita funcionalidade. Vamos considerar o que foi realizado no geral.

Encapsulamento. O controle é uma coleção de vários controles, agrupados com visuais e comportamentos personalizados que os desenvolvedores consumidores podem reutilizar com facilidade através de um aplicativo.

Lógica de negócios. O controle incorpora regras de negócio que atendem aos critérios de aceitação da história de usuário. Eu coloquei regras imutáveis perto do controle e também dei suporte a uma experiência avançada de tempo de design.

Eventos personalizados. O controle expõe eventos personalizados específicos do controle, como "clique" que ajuda os desenvolvedores a interoperar com o controle, sem a necessidade de compreender a estrutura interna do layout.

Propriedades personalizadas. O controle expõe propriedades para permitir que o desenvolvedor consumidor influencie o conteúdo do layout. Isso tem sido feito de maneira a dar suporte total à associação de dados do XAML.

Sintaxe de API. O controle dá suporte a uma abordagem intuitiva que permite aos desenvolvedores declarar o próprio conteúdo com cadeias de caracteres literais e de maneira simples. Utilizei o atributo ContentProperty para isso.

Modelos. O controle é fornecido com um ControlTemplate padrão que dispõe de uma interface intuitiva. No entanto, a remodelagem do XAML tem suporte para permitir que os desenvolvedores consumidores personalizem os visuais, conforme necessário.

Ainda tem mais a fazer

Meu controle precisa de mais, mas não muito mais, apenas um pouco de atenção ao layout (como a necessidade de rolar texto grande) e a algumas propriedades (como o conteúdo da caixa de seleção). Estou incrivelmente perto.

Os controles podem dar suporte a gerenciamento de estado visual, um recurso nativo do XAML que permite que propriedades mudem com base em eventos de dimensionamento ou de estrutura (como mouseover). Controles maduros têm estados visuais.

Os controles podem dar suporte à localização. A funcionalidade nativa no UWP usa os controles de associação de diretiva x:Uid com cadeias de caracteres RESW, que são filtradas pela localidade ativa. Controles maduros dão suporte à localização.

Os controles podem dar suporte às definições de estilo externo para ajudar a atualizar os visuais delas, sem a necessidade de um novo modelo; isso pode envolver visuais compartilhados e utilização de temas e de estilos BasedOn. Controles maduros compartilham e reutilizam estilos.

Conclusão

A criação de protótipos de uma interface do usuário em XAML é rápida. Controles de usuário criam layouts simples e reutilizáveis com facilidade. Controles de modelo exigem um pouco mais de trabalho para criar layouts simples e reutilizáveis, com funcionalidades mais sofisticadas. A abordagem correta é responsabilidade do desenvolvedor, com base em um pouco de conhecimento e em muita experiência. Experimento. Quanto mais você aprende as ferramentas, mais produtivo se torna.

O Windows Forms substituiu o Visual Basic 6 em 2002, assim como o WPF substituiu o Windows Forms em 2006. O WPF trouxe com ele o XAML: uma linguagem declarativa da interface do usuário. Os desenvolvedores da Microsoft nunca tinham visto nada como o XAML. Hoje, Xamarin e UWP trazem o XAML para iOS, Android, HoloLens, Surface Hub, Xbox, IoT e o computador moderno. Na verdade, XAML agora é a tecnologia de criação do próprio sistema operacional Windows.

Os desenvolvedores em todo o mundo adoram o XAML porque ele é muito produtivo e flexível. Os engenheiros da Microsoft sentem o mesmo; estamos criando nossos próprios aplicativos e até mesmo o Windows 10 com o XAML. O futuro é brilhante, as ferramentas são poderosas e a tecnologia está mais acessível do que nunca.


Jerry Nixoné engenheiro de software sênior e arquiteto-chefe do departamento de Engenharia de Software Comercial da Microsoft. Há duas décadas ele desenvolve e projeta softwares. Palestrante, organizador, professor e autor, Nixon também é o anfitrião da DevRadio. A maioria de seus dias é gasto ensinando às suas três filhas eventos anteriores e tramas dos episódios de "Star Trek".

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Daniel Jacobson, Dmitry Lyalin, Daren May, Ricardo Minguez Pablos


Discuta esse artigo no fórum do MSDN Magazine