Desenvolvimento de UIs

Por mais simples e fina que ela seja, uma das partes mais importantes de um sistema deskop, web ou mobile, é a interface gráfica. Por mais complexo e tecnológico que seja as camadas mais baixas da aplicação, independentemente dos padrões de arquitetura e projeto que utilize, se é uma base de dados relacional ou No-SQL, etc., é a interface com o usuário que receberá a maior parte das interações e é ela que inicialmente é apresentada quando se propõe ou vendo uma solução.

Além da interface ser a principal responsável por capturar informações e devolver respostas (de sucesso ou falha), é ela também quem exibe os dados para que os usuários realizem a análise gerencial e operacional da empresa para qual o sistema foi desenvolvido.

Apesar de existir uma porção de artigos e documentações que descrevem boas práticas para a construção de interfaces, o bom entendimento do negócio garantirá aos desenvolvedores a criação e otimização de telas para que em pouco espaço, sejamos capazes de apresentar poucos dados, mas com muitas informações.

A ideia é repensar a forma como as construímos, tendo em mente a atividade (ou tarefa) que o usuário irá desempenhar naquela tela. Entender a essência do problema é crucial para o sucesso da criação das telas, e o usuário que é o responsável por executar a tal atividade dentro da empresa é a melhor pessoa com quem se pode falar para extrair as dificuldades/exigências. Em seguida, temos que tentar abstrair tudo de importante que foi colhido, e por fim, implementar.

Abaixo estão algumas técnicas que utilizamos na construção de aplicações para a empresa em que trabalho. A ideia aqui é exibir alguns conceitos, dificuldades e soluções que foram encontradas e implementadas. Apesar dos exemplos serem aplicações desktop (WPF) e web, eles não se limitam a estes cenários; nada impede de utilizar estes conceitos na construção de um aplicativo móvel.

Observação: clique nas imagens para visualiza-las em um tamanho maior.

Dashboards

Todos os funcionários da empresa têm atividades diárias. Algumas pessoas estão condicionadas a executar sempre as mesmas tarefas, analisar os mesmos dados. Se conseguirmos mapear tudo o que ele deve olhar e monitorar, podemos criar um painel que concentra todas as informações que coordenarão o seu dia.

Bem-vindo aos dashboards. Assim como o painel de um carro, que centraliza todas as informações relevantes para a condução e monitoramento do mesmo, os dashboards em uma aplicação tem o mesmo sentido. Se começar a setorizar a sua empresa, irá perceber que cada departamento terá ações distintas (muitas vezes, sobre os mesmos dados), e antes dele sair analisando um a um para saber se há problemas, porque já não apontar as divergências para que ele resolva?

Além disso, os dashboards muitas vezes exibem informações e índices sobre a saúde de algum recurso da empresa, sobre dados financeiros, etc., para que pessoas de nível gerencial possam acompanhar e tomar as decisões cabíveis para cada situação. Reportar periodicamente tais informações garante que ele possa reagir assim que detecta algo suspeito.

Análises

Além dos dashboards que dão uma visão mais panorâmica sobre certos aspectos, há telas que podem concentrar informações mais detalhadas a respeito de algo ou alguém. Por exemplo, qual o risco atual do cliente XYZ? Qual a posição do meu estoque?

Quando colocamos várias informações lado a lado, temos que de alguma forma evidenciar algumas delas, separando muitas vezes por severidade. Existem índices que servem como histórico, enquanto outros indicam a quantidade e total de títulos vencidos que a empresa possui. Saber diferenciar entre essas duas informações garantirá o sucesso da tela, pois é com essa segmentação do que é ou não crítico que vamos utilizar para desenhar e destinar os espaços necessários para cada seção.

O tamanho da fonte é um importante aliado no momento em que precisamos destacar certos valores. A parte superior da tela sempre me pareceu o primeiro ponto em que olhamos quando a mesma é aberta. E aproveitando este instinto, podemos concentrar em uma linha horizontal as informações mais críticas do cliente, enquanto o restante da tela exibe informações de menor severidade, mas não tão menos importantes.

É claro que isso não é uma regra. Você pode optar por utilizar o canto direito, canto esquerdo, etc., para concentrar informações de análise acerca do que está olhando naquele momento. Abaixo está um outro cenário, agora web, que apresenta a situação atual de uma empresa na mesma tela em que exibe os seus títulos em aberto e algumas outras informações de histórico.

Por fim, é importante dizer que nada é estático. A maioria dos índices podem ser (e na maioria das vezes serão) clicáveis. O que quero dizer é que ao clicar no total de vencidos, será apresentado uma tela contendo os títulos que compõem aquele valor.

Repare que essa forma muda como o usuário pensa. Algumas vezes, tudo o que o usuário quer saber é quanto a empresa XYZ está devendo; não importa quantos títulos são. Em geral, o que se apresenta é uma tela contendo vários tipos de filtros, onde um deles é o código/CNPJ da empresa em questão, que ao clicar em filtrar, os 4822 títulos são listados.

Neste outro modelo proposto, basta ele chegar até a empresa e já terá uma sumarização, as pendências não concluídas, sendo possível apontar os títulos que estão vencidos (que ele precisa atuar).

Cores

O tamanho da fonte não é o seu único aliado no momento de destacar informações. As cores também podem representar muita coisa. Além do verde (positivo), vermelho (negativo), azul (indiferente) e amarelo (alerta), podemos abusar das mais diversas cores para indicar algum comportamento, evolução ou tendência de alguma informação.

As cores não se restringem apenas aos índices. Você pode optar por colorir diversas partes do sistema, como por exemplo, o contorno de uma janela que exibe a nota daquele cliente. Repare que a nota, o número propriamente dito, começa a ser irrelevante, já que todo o contexto gráfico indica que aquele é um cliente ruim, independente da escala utilizada (1 a 5, 1 a 10, 10 a 100, etc.).

Gráficos

Gráficos são recursos que cabem na maioria das aplicações. O importante é sempre ter um bom componente à mão para que seja fácil a criação, já que gráficos podem ser criados aos montes, cruzando diversos tipos de informações.

O maior desafio não é a parte técnica, mas sim as informações que compõem o gráfico. Com o conhecimento do negócio é possível que você sugira algumas opções predefinidas, mas é o usuário, com o conhecimento e experiência, que pode indicar quais os melhores gráficos para enriquecer a aplicação.

Sempre há uma discussão em torno disso, que é: criar manualmente cada gráfico ou disponibilizar os dados para que alguma ferramenta externa (Excel, Power BI, etc.)? É uma decisão difícil, pois a construção de gráficos é estática, ou seja, se quiser um novo gráfico, precisamos criar uma nova tela, extrair os dados e exibir, compilar e redistribuir, ao contrário dessas ferramentas específicas, que recebem um conjunto de dados e você molda como preferir.

Ao meu ver, a vantagem de ter o gráfico dentro da aplicação é a possibilidade de inserir o mesmo em um contexto específico, como por exemplo, colocar lado a lado com outras informações que estão sendo analisadas no momento da aprovação de alguma operação.

Contexto

Informações de contexto são sempre bem-vindas quando está analisando ou tomando alguma decisão. São informações que estão conectadas de alguma forma com o que está sendo analisado, e não estou me referindo à banco de dados aqui. Situações onde você está avaliando o crédito para uma empresa e vê que ela está devendo R$ 200.000,00 no mercado, e com apenas um clique, você consegue chegar rapidamente a lista de credores e seus respectivos valores.

Elas podem estar na mesma tela de análise, mas mesmo que haja espaço suficiente para ela, sacar a informação de algum outro lugar pode ser mais interessante, pois evita o risco de poluir demasiadamente a tela. Temos que ponderar entre a discrição e a facilidade de acesso à informação. Considere o exemplo abaixo, onde há uma operação de crédito sendo analisada, qual já possui diversos parâmetros, mas se o usuário desejar, pode acessar as últimas operações realizadas para definir a taxa que será aplicada à operação corrente. A janela que aparece é apenas uma tooltip.

Interação

Nem sempre as aplicações têm uma área de ajuda, pois não é algo fácil de fazer e, principalmente, de manter. Muitas vezes ela nunca é utilizada, pois é mais fácil ligar para alguém e perguntar sobre a dificuldade em fazer alguma atividade dentro do sistema.

Ao invés de criar uma área de ajuda, que concentra 100% das informações e detalhamento de como utilizar seções do sistema, a forma mais simples e intuitiva seria espalhar pelas telas da aplicação informações que ajudem o usuário a esclarecer possíveis dúvidas que ele tenha no momento da realização de uma determinada tarefa.

Repare que com poucas palavras é possível esclarecer um termo que à primeira vista parece ser complexo e confunde o usuário na escolha. A mesma regra pode se aplicada na descrição de um link que ele pode estar na dúvida em qual deles escolher:

Feedback

Reportar falha de execução de alguma atividade (seja de infraestrutura ou de negócio) é fácil, pois basta indicar o motivo do erro e pedir para o usuário tentar novamente. Mas o grande desafio é notificar, de forma simples e objetiva, a conclusão da mesma.

Não estou falando aqui de “cadastro realizado com sucesso”, “operação enviada para análise”, etc., mas sim de exibir detalhes precisos da atividade que o usuário acabou de realizar. Quando você dá entrada em algum órgão, é comum eles darem um protocolo indicando o número, a data, o prazo de conclusão, o nome, etc. Poderíamos utilizar a mesma regra aqui, ou seja, quando a atividade for concluída, podemos exibir na tela detalhes que o deixem seguro de que a mesma foi executada com sucesso.

É claro que isso nem sempre é possível ou viável. Processos simples também demandam mensagens mais simples. Mas considere mesmo estes casos simples para ser mais esclarecedor com o usuário, indicando com mais ênfase o que acabou de ser realizado. Opte por “A empresa teve a sua razão social alterada de Israel Aece-ME para Israel Aece Ltda.” ao invés de “Alteração realizada com sucesso.”.

Foco em Tarefas

No nível mais baixo, sabemos que tudo o que um sistema faz em uma base de dados é inserir, atualizar, excluir e ler os dados(CRUD). Só que se ficarmos preso a este modelo de trabalho, vamos construir telas que simplesmente farão estas quatro operações básicas, orientando elas a serem voltadas para a estrutura do nosso banco de dados.

Só que as telas podem dizer muita coisa sobre o comportamento e as atividades que devemos fazer com os dados, que nem sempre se referem à apenas uma única entidade. Muitas vezes a atividade é composta de diversas outras subatividades, que contribuem para a totalidade de uma tarefa a ser executada.

Imagine que você tem um cliente e ele está bloqueado de operar. A implementação mais trivial possível no banco de dados é ter uma tabela chamada “Cliente” com um flag chamado “Bloqueado”. Como seria a construção da tela que desbloqueia o cliente?

Geralmente temos um formulário que você edita todos os dados, e nele está incluído um checkbox que está associado à coluna “Bloqueado”. Ao clicar no botão atualizar, os dados são persistidos na base de dados e o cliente está apto a operar novamente. Mas essencialmente será que o desbloqueio seria apenas isso? Será que o desbloqueio não seria algo muito maior, que depende que alguém solicite e outra pessoa seja responsável por analisar o motivo e autorizar o não o desbloqueio? Será que não precisamos de histórico de alteração? Qual a justificativa para o desbloqueio? Qual a razão de aceitar o desbloqueio?

Isso é um exemplo de uma tarefa que você executa contra o seu cliente, ou seja, solicitar o seu desbloqueio. Não é um simples formulário de edição. Se você pensar orientando à tarefas que podem ser executadas naquela entidade, é provável que nem precise de um formulário para isso. Aos poucos você começa a perceber que as telas da aplicação desencadeiam ações.

Suas telas deixam de serem formulários baseados na base de dados. Elas possam a ser desenhadas de acordo com a atividade que aquele usuário deve desempenhar. Se ele é responsável por aprovar a alteração de vencimento de boletos, então podemos relacionar os que estão atualmente na sua alçada e permitir com que ele possa selecionar os que deseja aprovar e escolher a opção de conclusão.

Relatórios

É uma tentação por parte dos usuários. Sempre quando eles querem visualizar alguma massa de dados, eles falam em “relatórios”. Para mim, relatório é o que se imprime, ou seja, aquilo que se manda para a impressora. Mesmo que relatórios sejam indispensáveis para aplicações, não quer dizer que eles devem ser utilizados como forma de exibir um conjunto de dados em tela.

Algumas aplicações, por questões de simplicidade, utilizam relatórios para exibição, por exemplo: listar todos os títulos vencidos do Israel, clientes com divergências cadastrais, empresas localizadas em São Paulo, etc. Apesar de isso ser possível, torna a relação estática, ou seja, não é possível clicar no cliente que está sem endereço e ajustar. Teria que imprimir, abrir a tela de cadastro de clientes, localizar o mesmo, e em seguida, fazer a alteração.

Como provavelmente existe uma pessoa responsável pelo cadastro dos clientes, então no dashboard dela poderia existir um índice que indica a quantidade de clientes com divergências, e ao clicar, uma tela seria apresentada com esta relação; bastaria dar um clique e já ajustar o endereço e ao sair, a relação já seria sensibilizada e o cliente recém alterado iria desaparecer. As telas tem um grande poder, pois pequenas sumarizações servem também de filtro para uma listagem, sem a necessidade de ter um formulário de pesquisa para isso.

Outro ponto importante a ser considerado é segurança. A impressão de relatórios permite com que algum funcionário mal-intencionado imprima e entregue a relação de clientes para um concorrente. Não que com a tela seja impossível de acontecer, mas chama mais atenção alguém fotografando a tela do computador.

Os relatórios precisam ser pensados com cautela, pois como disse acima, é uma tentação para os usuários. É importante entender a essência do problema, focando na atividade que vem após a impressão. Isso nos dará a oportunidade de construir uma tela que atenda a necessidade deles sem a impressão física da relação de dados.

E claro, não há como escapar de desenvolver relatórios. E sem querer se contraditório, eles são sempre bem-vindos.

A ideia deste artigo foi apenas mostrar algumas técnicas que utilizamos no desenvolvimento das aplicações para empresa onde trabalho. Não há nenhuma regra que deve ser assegurada aqui. São apenas experiências que foram vivenciadas e que a escolha, no meu ambiente, se fez válida. Use como quiser.

Assincronismo no WPF

Uma grande necessidade que existe ao escrevermos aplicações, é a necessidade de executar tarefas custosas e/ou periódicas (polling). Muitas vezes, se elas forem escritas em sua forma tradicional, vamos nos deparar com uma experiência ruim durante o uso do software, justamente porque essas tarefas serão executadas de forma síncrona, ou seja, até que elas não sejam concluídas, o usuário não poderá acessar qualquer outra área do sistema.

O .NET Framework fornece desde as primeiras versões suporte para escrita de código assíncrono. O problema é que a escrita deste código não é lá muito trivial, tendo que lidar com muitos detalhes de baixa nível, tais como asyncs results, callbacks, sincronização, etc. Para facilitar isso, a Microsoft trabalha na criação de novos recursos que serão incorporados na própria linguagem (C# e VB.NET), tornando a programação assíncrona muito mais simples de ser realizada, tornando-a tão intuitiva quanto a programação síncrona.

Além dessa mudança nas linguagens, ainda temos o Reactive Extensions, que uma das suas funcionalidades, é prover uma forma diferente de lidar com a programação assíncrona, que ao invés de “puxarmos” o resultado de algum lugar, podemos fazer com que esse resultado seja “empurrado” para a aplicação, o que lhe permitirá trabalhar de forma reativa. A finalidade deste artigo é apresentar como podemos proceder para trabalhar de forma assíncrona em uma aplicação Windows, e para o exemplo, vamos recorrer a um projeto baseado em WPF em conjunto com o Reactive Extensions.

Como disse anteriormente, podemos executar uma tarefa de forma assíncrona ou executar uma ação periodicamente. No primeiro caso, em que precisamos executar uma tarefa de forma assíncrona, podemos recorrer ao método de extensão ToAsync da classe Observable. Esse método possui centenas de overloads, qual contempla grande parte (senão todas) das versões dos delegates Action e Func. Sendo assim, podemos vincular diretamente um método que leva um tempo longo para executar, e com isso, reagir quando o resultado for retornado. A implementação deste código pode variar dependendo se você está ou não utilizando o padrão MVVM. Abaixo temos o exemplo de como podemos proceder para executar um cálculo custoso de forma assíncrona, utilizando Reactive Extensions, sem MVVM:

private void Operacao_Click(object sender, RoutedEventArgs e)
{
    int v1 = int.Parse(this.Valor1.Text);
    int v2 = int.Parse(this.Valor2.Text);

    Observable
        .ToAsync<int, int, int>(Somar)(v1, v2)
        .ObserveOnDispatcher()
        .Subscribe<int>(r => this.Resultado.Text = r.ToString());
}

private int Somar(int v1, int v2)
{
    Thread.Sleep(4000);

    return v1 + v2;
}

O único comentário relevante ao método Somar é que ele simula um processamento custoso através do método Sleep. Já no evento Click do botão, invocamos o método ToAsync, informando qual o método que deve ser disparado de forma assíncrona, incluindo logo na sequência, os parâmetros exigido pelo método Somar. Quando chamamos o método Subscribe, ele passa a monitorar qualquer resultado (de sucesso) gerado pelo método Somar, e neste caso, estamos apresentando-o em um terceiro TextBox.

Um detalhe extremamente importante é sobre o método ObserveOnDispatcher. O WPF possui uma classe chamada Dispatcher, que serve como um gerenciador de tarefas para serem executadas, e está sempre associada com uma determinada thread de UI. Isso quer dizer que qualquer notificação enviada pelo método Somar será enviada e executada pela própria thread que criou os controles de UI, já que aplicações Windows (WPF e Windows Forms) possuem essa afinidade. Se não nos atentarmos a este método, uma exceção do tipo InvalidOperationException será disparada, contendo a seguinte mensagem: The calling thread cannot access this object because a different thread owns it.

Já quando utilizamos MVVM, a implentação é um pouco diferente por conta da estrutura imposta pelo padrão. Os botões da View (Xaml) são executados através de um comando que deve implementar a interface ICommand. Neste caso, é muito comum recorrer à criação de um comando chamado de RelayCommand, que permite você injetar a lógica do comando a ser executado através de delegates. Só que é importante dizer que a execução deste comando é realizado de forma síncrona. Precisamos realizar uma pequena implementação para conseguir executar este mesmo comando de forma assíncrona. O método que representa a lógica a ser executada, pode ser executado de forma síncrona ou assíncrona, sem a necessidade de qualquer alteração no mesmo.

Abaixo temos o código que representa o comando que define que a execução de alguma tarefa seja realizada de forma assíncrona. Note que continuamos utilizando o Reactive Extensions, e no método ToAsync definimos o método que é informado no construtor desta mesma classe.

public class AsyncRelayCommand : RelayCommand
{
    public AsyncRelayCommand(Action execute)
        : base(execute) { }

    public AsyncRelayCommand(Action execute, Func<bool> canExecute)
        : base(execute, canExecute) { }

    public override void Execute(object parameter)
    {
        Observable
            .ToAsync(base.execute)()
            .Subscribe();
    }
}

Depois de criado esta classe, podemos fazer o uso da mesma diretamente em nossa ViewModel. Aqui optei por variar o método Somar, pois ao invés de receber os parâmetros e devolver o resultado, estou optando por acessar diretamente as propriedades no interior do método. E aqui cabe comentar um detalhe interessante: note que não usamos o método ObserveOnDispatcher na classe AsyncRelayCommand. Isso se deve ao fato de que o padrão MVVM faz com que a ViewModel seja completamente independente da View, e com isso, não conseguimos acessar seus respectivos controles e, consequentemente, não corremos risco nos depararmos novamente com aquela exceção que vimos acima.

public class CalculoViewModel : INotifyPropertyChanged
{
    public CalculoViewModel()
    {
        this.Calculo = new AsyncRelayCommand(Somar);
    }

    public ICommand Calculo { get; private set; }

    public string Valor1 { get; set;}

    public string Valor2 { get; set; }

    public string Resultado { get; set; }

    private void Somar()
    {
        Thread.Sleep(4000);

        this.Resultado =
            Convert.ToString(int.Parse(this.Valor1) + int.Parse(this.Valor2));
    }
}

Observação: Por questões de espaço eu preferi omitir a implementação necessária para notificar a alteração das propriedades (INotifyPropertyChanged). Isso continua sendo necessário, pois é assim que a View monitora toda e qualquer alteração que é realizada no interior da ViewModel para assim atualizar a UI.

Além das opções que vimos acima, ainda podemos necessitar que tenhamos um consumo periódico de alguma informação. Por exemplo, necessitamos monitorar serviço de cotação de valores, notícias de algum site (RSS), empregos, etc. Com isso, haverá uma tarefa sendo executando a cada X segundos, buscando as informações e, consequentemente, apresentando-as na tela para que o usuário possa visualizá-la.

Abaixo temos a ViewModel criada para atender este cenário. Temos uma propriedade chamada Noticias que retorna uma coleção do tipo ObservableCollection<Noticia>, qual será definida como fonte de dados de um controle ListBox da View (Xaml).

public class NoticiasViewModel
{
    public NoticiasViewModel()
    {
        this.Noticias = new ObservableCollection<Noticia>();
        this.MonitorarNoticias();
    }

    private void MonitorarNoticias()
    {
        Observable
            .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(3))
            .Select(_ => BuscarNoticiasViaWeb())
            .ObserveOnDispatcher()
            .Subscribe(noticias => noticias.ForEach(n => this.Noticias.Add(n)));
    }

    private static List<Noticia> BuscarNoticiasViaWeb()
    {
        // Buscar notícias via Http
    }

    public ObservableCollection<Noticia> Noticias { get; set; }
}

Alguns novos operadores entram em cena aqui. O método Timer retorna uma sequência produzida (pelo método Select) a cada X segundos/minutos/horas. Note que voltamos a necessitar do método ObserveOnDispatcher, mesmo aqui, onde estamos utilizando MVVM. A questão é que quando definimos uma coleção como fonte de dados de algum controle, como é o caso do ListBox, ele envolve essa coleção em uma classe do tipo CollectionView, e esta herda diretamente da classe DispatcherObject, o que determina que ela tenha afinidade com a thread (Dispatcher) em que a mesma foi criada. Sendo assim, a não utilização do método ObserveOnDispatcher, vamos nos deparar com uma exceção do tipo NotSuppertedException, com a seguinte mensagem: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

Eis aqui algumas opções que temos para trabalharmos de forma assíncrona no WPF. Como disse antes, temos algumas mudanças que estão acontecendo nas linguagens que tendem a facilitar ainda mais a criação de código assíncrono, sem depender de qualquer recurso extra. De qualquer forma, essas opções já tornam o código bem mais expressivo e de fácil manutenção.

Revisão do Livro: Silverlight 4.0 Curso Completo

Eu tenho acompanhado a evolução do Silverlight ao longo de sua existência, ainda quando ele se chamava WPF/E. Eu tenho me preocupado em estudar as stacks de comunicação para o consumo de serviços WCF, deixando alguns detalhes interessantes para focar especificamente na área de comunicação.

Para explorar um pouco melhor as capacidades do Silverlight, decidi ler um livro sobre o assunto. Como eu conheço os trabalhos do Luis Abreu, eu optei por utilizar o livro que ele escreveu sobre a versão 4.0 do Silverlight. O livro inicia com uma visão do que é o XAML e detalhes de sua estrutura. Depois dá uma volta pela customização e análise dos controles fornecidos pela tecnologia. DataBinding que também é um recurso extremamente rico em funcionalidades e facilidades, ele aborda com um carinho especial. Além disso, é falado sobre as opções de comunicação com serviços WCF. Há ainda também alguns recursos para aqueles que desejam explorar as possibilidades relacionadas à multimídia e animações. E como se não bastasse, ainda há alguns outros temas interessantes, que são essenciais para a execução e customização deste tipo de aplicação, e que possuem capítulos exclusivos, como é o caso do Out Of Browser e interação com o Javascript.

Mesmo para aqueles que usam WPF, podem tirar proveito deste livro, onde algumas seções são comuns para ambas tecnologias. Um livro curto, fácil de ler e bem ilustrado, que conta com uma didática simples, e que ao mesmo tempo, não deixa de abordar detalhes interessantes de mais baixo nível, para aqueles que gostam de entender como as coisas funcionam nos bastidores. Minha avaliação final é 9/10.

Debug em DataBinding do WPF

Como eu escrevi aqui, o WPF fornece várias funcionalidades para databinding. Quando efetuamos a configuração de forma declarativa, a verificação para se certificar de que a propriedade que serve como origem das informações realmente existe, somente acontecerá durante a execução, mas o WPF não irá disparar qualquer exceção se ela não for encontrada. O binding simplesmente não funciona.

Para exemplificar, o código abaixo define a propriedade Content do controle Button com uma propriedade que não existe no TextBox. Esse código compilará sem problemas, mas durante a execução, o texto do botão sempre ficará vazio.

<TextBox Name=”textBox1″ />
<Button Name=”button1″ Content=”{Binding ElementName=textBox1, Path=PropriedadeQueNaoExiste}” />

Assim como já acontece com o WCF, o Microsoft disponibilizou uma série de elementos de tracing que nos permitirá escolher aqueles que nos interessam para começar a monitorar eventuais informações que eles geram. Entre as várias opções, uma delas é a chamada System.Windows.Data, que é utilizada para catalogar mensagens relacionadas ao processo de databinding, incluindo casos como este, onde não é possível resolução do binding.

Para habilitá-lo, basta configurar o TraceSource como é mostrado abaixo, definindo qual nível de severidade das informações está interessado (TraceSwitch) e, finalmente, o local onde deseja salvar essas informações (TraceListener). O exemplo abaixo ilustra a configuração feita através do arquivo App.config da aplicação:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <system.diagnostics>
    <sources>
      <source name=”System.Windows.Data” switchName=”SourceSwitch”>
        <listeners>
          <add name=”textListener” />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name=”SourceSwitch” value=”All”/>
    </switches>
    <sharedListeners>
      <add name=”console”
           type=”System.Diagnostics.ConsoleTraceListener”
           initializeData=”false”/>
    </sharedListeners>
  </system.diagnostics>
</configuration>

O ConsoleTraceListener faz com que as informações sejam enviadas para a janela de Output do Visual Studio. Ao rodar o código XAML acima, vamos visualizar a seguinte mensagem:

System.Windows.Data Error: 39 : BindingExpression path error: ‘PropriedadeQueNaoExiste’ property not found on ‘object’ ”TextBox’ (Name=’textBox1′)’. BindingExpression:Path=PropriedadeQueNaoExiste; DataItem=’TextBox’ (Name=’textBox1′); target element is ‘Button’ (Name=’button1′); target property is ‘Content’ (type ‘Object’)

Utilizando Resources no WPF

Qualquer tipo de aplicação geralmente precisa de arquivos adicionais, que servem como recursos extras para a mesma. Esses arquivos muitas vezes são do tipo Xml, simples textos, imagens, ícones, etc. Esses arquivos são colocados dentro do mesmo diretório da aplicação, e muitas vezes organizados através de sub-diretórios. Geralmente esse tipo de técnica define esses arquivos como arquivos de conteúdo, e são distribuídos juntamente com o assembly, mas estão fisicamente separados.

Desde as primeiras versões do .NET, para as aplicações construídas em cima da plataforma, podemos embutir um arquivo qualquer em um determinado assembly, fazendo com que este assembly contenha tudo o que ele precise. Um exemplo disso é o assembly System.Web.dll. Dentro dele há toda a infraestrutura do ASP.NET, incluindo os controles. Quando estamos desenvolvendo uma aplicação deste tipo, a ToolBox do Visual Studio exibe os controles disponíveis (novamente, aqueles que estão dentro do System.Web.dll), e ao lado uma imagem que representa o controle. Mas de onde vem essa imagem? Elas estão embutidas neste mesmo assembly, como você pode visualizar na imagem abaixo:

O WPF também possibilita embutir arquivos extras à nossa aplicação, compilando – ou não – juntamente com o assembly (exe). Imagine que você adiciona a referência para uma imagem qualquer no Solution Explorer. Se for até as propriedades da mesma, verá que existe uma propriedade chamada Build Action, que determina justamente o comportamento desta imagem durante a compilação. Entre as várias opções voltadas para o WPF, vamos analisar duas delas: a Resource e Content.

A primeira opção faz com que o arquivo em questão seja embutido no assembly, enquanto a segunda opção faz com que o arquivo seja mantido como um arquivo de conteúdo da aplicação, mas não o compila para dentro do assembly. Independentemente da forma que utiliza para o arquivo, sempre utilizaremos uma URI para acessá-lo, dependendo apenas do local que ela se encontra (embutida ou não). Por exemplo, se temos uma imagem chamada Logo.png e ela está marcada como Resource, você pode simplesmente fazer:

<Image Source=”Logo.png” />

Como disse acima, é comum utilizar sub-diretórios para organizar os arquivos que fazem parte da aplicação. Quando cria uma pasta para armazenar esses arquivos, você deverá também mencionar o caminho ao referenciá-lo. Isso é necessário mesmo quando embutimos o arquivo no assembly, pois o WPF nomeia o recurso utilizando a estrutura de pastas. Abaixo temos o resultado da compilação dos recursos, onde acima temos um sub-diretório chamado Imagens e a imagem está dentro dele, enquanto abaixo temos a imagem colocada na raiz da aplicação.

A utilização das imagens que estão em sub-diretórios, pode ser feita da seguinte forma:

<Image Source=”ImagensLogo.png” />

É importante dizer que a forma de acesso aos recursos que utilizamos acima, é apenas uma espécie de atalho, pois isso é transformado em uma URI muito mais complexa, para que assim possa acessar o respectivo recurso. A URI completa para acessar o recurso deve ser: pack://packageUri/partPath. Transformando os exemplos anteriores nesta sintaxe, teríamos o seguinte:

<Image Source=”pack://application:,,,/Logo.png” />
<Image Source=”pack://application:,,,/Imagens/Logo.png” />

Caso você desejar acessar recursos que estão embutidos em outros assemblies, você deverá referenciá-lo com esta sintaxe, definindo explicitamente o nome do assembly que deseja extrair a informação. O exemplo ilustra como proceder para acessar a mesma imagem, mas que estará em um segundo assembly:

pack://application:,,,/BoletosBancarios;component/Imagens/Logo.png

Quando você não deseja embutir o arquivo no assembly, então o arquivo vinculado à aplicação deverá ser marcado como Content. Para acessar esse tipo de recurso, dependerá também de onde ele estará armazenado. Por exemplo, se ele estiver armazenado em algum diretório do disco, podemos referenciar diretamente:

<Image Source=”C:TempLogo.png” />

Quando Content é definido para o arquivo, você deve definir a propriedade Copy To Output Directory para Copy Always, para que o arquivo seja copiado para o mesmo diretório onde a aplicação está sendo gerada, para assim facilitar as referências relativas à ele.

Você ainda pode referenciar também outros tipos de arquivos que estão externos a sua aplicação, como por exemplo, aqueles que estão em algum ponto da internet, como por exemplo:

<Image Source=”http://www.site.com.br/Imagens/Logo.png&#8221; />

O tipo de distribuição da aplicação influencia no caminho de acesso aos recursos, e ao invés de você deixar em hard-code, o WPF facilita isso com o uso de uma opção chamada de siteOfOrigin. Essa opção retorna valores diferentes, de acordo com a forma com que a aplicação foi originalmente instalada. Por exemplo, se a aplicação foi instalada através do Click Once, essa opção retornará a URL de onde a aplicação foi distribuída; se utilizar Windows Installer (MSI), essa opção refletirá o diretório raiz da aplicação.

Essa opção facilita bastante, já que independentemente da forma que a aplicação é entregue, você sempre conseguirá chegar até o recurso que está sendo solicitado. A sua utilização é simples, basta alterar o packageUri de application para siteOfOrigin, como mostrado no exemplo abaixo:

<Image Source=”pack://siteOfOrigin:,,,/Logo.png” />
<Image Source=”pack://siteOfOrigin:,,,/Imagens/Logo.png” />

E, finalmente, esse tipo de sintaxe também é a mesma para acesso via código:

this.image1.Source = new BitmapImage(new Uri(“pack://application:,,,/Imagens/Logo.png”));
//Ou
this.image1.Source = new BitmapImage(new Uri(“Logo.png”));

Conclusão: Os recursos que são adicionados à aplicação não estão resumidos ao que vimos aqui. Há também a possibilidade de embutir arquivos como MP3, AVI, etc., e tudo isso dependerá do quanto isso é viável. Além disso, os recursos também são largamente utilizados quando estamos construindo uma aplicação localizada, aquela que dá suporte à múltiplos idiomas, mas isso é matéria para um próximo artigo.

DataBinding em WPF

Já ouvimos falar muito sobre o termo DataBinding. Como sabemos, trata-se de um mecanismo para associar a informação de uma determinada origem à um determinado destino, criando uma dependência unidirecional ou bidirecional. Muitas vezes este termo está associado à alguma fonte de dados, que desejamos exibir suas respectivas informações na tela de uma aplicação qualquer.

Cada tecnologia implementa isso de uma forma diferente, com seus benefícios e possíveis limitações. O ASP.NET traz essa funcionalidade, onde você pode facilmente ligar uma fonte de dados, mas tem algumas limitações por conta de ser HTTP, que não mantém estado. Já o Windows Forms, fornece uma forma de databinding muito mais rico em termos de funcionalidades. A finalidade deste artigo é apresentar o databinding no WPF, exibindo suas funcionalidades, que facilitarão a forma com que lidamos com manipulações de UI.

A Microsoft incorporou no WPF uma forma muito mais evoluída para efetuar databinding, não se limitando apenas a vincular uma fonte de dados à controles, mas também permitindo que qualquer objeto preencha outro, incluindo controles. Isso quer dizer que poderemos definir o valor de uma propriedade de um controle com o valor de uma outra propriedade e de um outro controle. Tudo isso eliminando grande parte do código imperativo requerido pelo Windows Forms, pois a partir de agora, poderemos recorrer a código declarativo (XAML) para especificar essas “amarrações”.

Grande parte da responsabilidade para fazer tudo isso funcionar, é a classe Binding, que está debaixo do namespace System.Windows.Data. Ela é responsável por manter o “canal de comunicação” entre a origem e o destino, e além disso, expõe uma série de propriedades que nos permite customizar o comportamento dessa comunicação. Entre as principais propriedades, temos:

  • ElementName: define o nome do elemento que servirá como fonte. Utilize esta propriedade quando desejar preencher uma outra propriedade com o valor de um controle do WPF.
  • Mode: determina a direção das informações.
  • NotifyOnSourceUpdated: valor boleano indicando se o evento SourceUpdated é disparado quando alguma atualização na fonte das informações ocorrer.
  • NotifyOnTargetUpdated: valor boleano indicando se o evento SourceUpdated é disparado quando alguma atualização no destino das informações ocorrer.
  • Path: espefica o nome da propriedade que será exibida.
  • RelativeSource: especifica uma fonte de forma relativa à posição do objeto atual.
  • Source: define o nome do objeto que servirá como fonte. Utilize esta propriedade quando desejar preencher com uma instância de um objeto.
  • XPath: a mesma finalidade da propriedade Path, mas define uma expressão XPath quando a fonte de informações for um arquivo Xml.

Note que nas propriedades acima, nós não temos uma propriedade que especifica qual propriedade no destino será carregada. Isso se deve, porque você aplicará a sintaxe de binding diretamente dentro da propriedade que você quer preencher. O exemplo abaixo mostra como podemos proceder para preencher um conteúdo de um controle Label com o texto de um Button:

<Button Name=”button1″ Content=”Texto do Botão” />
<Label Name=”label1″ Content=”{Binding ElementName=button1, Path=Content}” />

Alternativamente, você pode achar essa sintaxe um pouco ilegível, principalmente quando você tiver situações mais complexas. Se desejar, você pode recorrer à uma segunda forma de configurar o databinding, de forma hierárquica, onde você irá aninhar as configurações como um Xml tradicional, através de sub-elementos. O código abaixo ilustra esta segunda técnica:

<Button Name=”button1″ Content=”Texto do Botão” />
<Label Name=”label1″>
    <Label.Content>
        <Binding ElementName=”button1″ Path=”Content” />
    </Label.Content>
</Label>

Quando o modelo declarativa não é uma solução, pois você precisa dinamicamente determinar os databindings, você pode ainda utilizar o código C#/VB.NET para configurá-los. Tudo o que precisamos fazer é instanciar a classe Binding que falamos acima, e configurar as propriedades necessárias para que isso funcione, e que neste exemplo simples serão ElementName e Path. ElementName vai receber uma string com o nome do controle que servirá como origem, enquanto a propriedade Path, receberá a instância da classe PropertyPath, que em seu construtor você deverá especificar o nome da propriedade no objeto de origem, que quer que seja enviado para o destino.

Binding b = new Binding();
b.ElementName = “button1”;
b.Path = new PropertyPath(“Content”);

this.label1.SetBinding(Label.ContentProperty, b);

Depois da instância da classe Binding configurada, utilizamos o método SetBinding do controle de destino, que no caso do exemplo é o Label. Além do Binding, esse método recebe a dependency property que receberá o valor da origem.

Carregando um Objeto

Acima vimos como podemos utilizar o databinding de uma forma diferente da qual estamos acostumado, que é através de controles de UI. Mas, o cenário mais comum é quando precisamos preencher um, ou vários controles de UI, com propriedades de um objeto. Eventualmente você tem um objeto que foi construído pela aplicação, e você precisa exibí-lo no formuário, distribuindo suas propriedades pelos controles do mesmo. O databinding também ajuda nisso, onde você pode especificar o tipo da classe, e o próprio WPF o instancia e, consequentemente, preenche os controles interessados, e tudo isso sendo feito declarativamente.

Ao invés de utilizar a propriedade ElementName, vamos agora recorrer à propriedade Source, que deve ser utilizada quando a origem se tratar de um objeto. Para exemplificar, vamos criar a instância da classe dentro dos resources do formulário, que nada mais é que uma coleção que pode armazenar qualquer tipo de objeto. Teremos uma classe simples chamada de Configuracao, contendo uma propriedade chamada Url. A instância desta classe será criada pelo WPF e estará armazenada estaticamente dentro dos recursos locais daquele formulário.

A sintaxe de binding agora consiste em configurar a propriedade Source com a instância criada e nomeada como “config”. Continuamos a utilizar a propriedade Path, mas agora ela deverá refletir a propriedade do objeto que será preenchida pelo controle. Como podemos perceber, a propriedade Text do TextBox irá exibir a propriedade Url:

<Window x:Class=”Teste.Window2″
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <local:Configuracao x:Key=”config” />
    </Window.Resources>
    <Grid name=”grid1″>
        <TextBox Name=”textBox1″ Text=”{Binding Source={StaticResource config}, Path=Url}” />
    </Grid>
</Window>

Suponhamos que temos vários controles e cada um receberá o valor de uma propriedade diferente. Ao invés de ficar repetindo a instância do objeto (config), podemos utilizar a propriedade DataContext. Essa propriedade nos permite compartilhar a mesma fonte por vários controles, e cada controle que o utilizará, apenas deve indicar qual propriedade ele estará vinculado. Repare que vinculamos o config à propriedade DataContext do controle Grid, e os controles inerentes à eles apenas mencionam qual propriedade cada um deles quer utilizar, sem a necessidade de especificar a propriedade Source.

<Window x:Class=”Teste.Window2″
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <local:Configuracao x:Key=”config” />
    </Window.Resources>
    <Grid name=”grid1″ DataContext=”{StaticResource config}”>
        <TextBox Name=”textBox1″ Text=”{Binding Path=Url}” />
        <TextBox Name=”textBox2″ Text=”{Binding Path=Timeout}”/>
    </Grid>
</Window>

Um detalhe importante aqui, é que se um controle não especificar nenhuma das propriedades de Binding (Source, RelativeSource ou Element), o WPF procura por algum elemento que possui a propriedade DataContext definida na árvore visual do formulário, e encontrando-o, tentará extrair o valor dele. Essa propriedade também pode ser configurada de via código, caso a instância do objeto precise de alguma manipulação adicional antes de ser usada pelo WPF.

this.grid1.DataContext = new Configuracao();

ObjectDataProvider

Como vimos acima, podemos criar a instância do objeto via código. Geralmente recorremos a essa técnica quando precisamos customizar a criação deste objeto. Mas o WPF fornece uma opção, que nos permite customizar de forma declarativa, algumas opções que podem ser utilizadas durante a criação deste objeto.

Entre essas opções, podemos definir alguns parâmetros para um construtor, fazer o databinding através de um método que retorna a instância do objeto, entre outras opções. Suponhamos que a partir de agora o construtor da classe Configuracao recebe como parâmetro uma string com a Url. Se tentar rodar a aplicação sem qualquer alteração, uma exceção será lançada dizendo que a classe não possui nenhum construtor público sem parâmetros. O ObjectDataProvider irá nos ajudar, permitindo especificar o valor deste parâmetro durante a criação:

<Window x:Class=”Teste.Window2″
    xmlns:system=”clr-namespace:System;assembly=mscorlib”
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <ObjectDataProvider x:Key=”config” ObjectType=”{x:Type local:Configuracao}”>
            <ObjectDataProvider.ConstructorParameters>
                <system:String>http://wwww.israelaece.com</system:String&gt;
            </ObjectDataProvider.ConstructorParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid DataContext=”{StaticResource config}”>
        <TextBox Name=”textBox1″ Text=”{Binding Path=Url}” />
        <TextBox Name=”textBox2″ Text=”{Binding Path=Timeout}”/>
    </Grid>
</Window>

RelativeSource

Já falamos das formas de databinding ElementName e Source, mas ainda nos resta uma terceira forma: a RelativeSource. Essa forma permite especificar como fonte, um objeto que está relativamente posicionado em relação ao objeto corrente. Essa opção traz três propriedades: Mode, AncestorType e AncestorLevel. A primeira delas recebe uma entre as seguintes opções:

  • FindAncestor: refere-se à um ancestral na árvore visual, que está acima em relação ao controle corrente. Essa opção exige que as propriedades AncestorType e AncestorLevel sejam definidas. A propriedade AncestorType define o tipo de elemento que será procurado, enquanto a propriedade AncestorLevel determina o nível de profundidade da busca.
  • PreviousData: permite referenciar o item anterior de uma coleção que está sendo exibida. Útil em templates.
  • Self: permite referenciar qualquer propriedade do objeto corrente.
  • TemplatedParent: permite referenciar um objeto que está envolvido em uma template.

O exemplo abaixo ilustra o uso do FindAncestor. Note que especificamos através do AncestorType o tipo de controle que desejamos procurar e o AncestorLevel, um número inteiro que determina até quantos níveis acima a busca será realizada. Neste caso, estamos interessados em exibir como o texto do botão o valor da propriedade Name de um Grid, e como estamos definindo o nível 2, ele irá apresentar o valor “g1” no botão.

<Window>
    <Grid Name=”g1″>
        <Grid Name=”g2″>
            <Button Name=”button1″>
                <Button.Content>
                    <Binding Path=”Name”>
                        <Binding.RelativeSource>
                            <RelativeSource Mode=”FindAncestor” AncestorType=”Grid” AncestorLevel=”2″ />
                        </Binding.RelativeSource>
                    </Binding>
                </Button.Content>
            </Button>
        </Grid>
    </Grid>
</Window>

Binding.Mode e Binding.UpdateSourceTrigger

Vimos até agora como podemos efetuar a ligação entre a origem e o destino das informações, mas não se resume a isso. Uma das característica do databinding permite também customizar que possíveis alterações sejam efetuadas para refletí-las tanto na origem quanto no destino. A propriedade Mode da classe Binding nos permite configurar como responder à essas ações, onde devemos escolher uma entre as cinco opções expostas pelo enumerador BindingMode:

  • Default: especifica que o binding utilizará o modo padrão estipulado pelo destino.
  • OneTime: especifica que o binding deve atualizar o destino quando a aplicação inicia ou quando os dados mudam, mas não deve atualizar o alvo quando subsequentes alterações são feitas na origem.
  • OneWay: especifica que o binding atualizará o destino quando a origem mudar. Alterações no destino não terão efeito na origem.
  • OneWayToSource: especifica que o binding atualizará a origem quando o destino mudar. Alterações na origem não terão efeito no destino.
  • TwoWay: especifica que as alterações feitas tanto na origem quanto no destino serão atualizadas automaticamente.

Outra propriedade que também é exposta pela classe Binding é a UpdateSourceTrigger, e que é utilizada quando a propriedade Mode é definida como OneWayToSource ou TwoWay. Essa propriedade determina como e quando a atualização das informações será realizada. Essa propriedade também receberá a informação oriunda de um enumerador, chamado UpdateSourceTrigger, onde as possíveis opções são:

  • Default: indica que a atualização será de acordo com o valor definido pela propriedade de destino, que muitas vezes é PropertyChanged. Propriedades que são editáveis pelo usuário, como a propriedade Text do TextBox, define o padrão como sendo LostFocus.
  • PropertyChanged: a fonte é atualizada quando a propriedade do destino é alterada.
  • LostFocus: a fonte é atualizada quando a propriedade de destino é alterada e quando o objeto perde o foco.
  • Explicit: a atualização da fonte será realizada quando você invocar explicitamente o método UpdateSource da classe Binding.

Coleções

Muitas vezes temos coleções de objetos que desejamos exibir através de controles, como por exemplo, ListBox. Da mesma forma que vimos anteriormente, para coleções, podemos proceder de forma semelhante, mas por se tratar de coleções, temos algumas novas propriedades que são exclusivas para esse cenário.

Controles que são considerados databound, expõe as seguintes propriedades DisplayMemberPath, ItemsSource e ItemTemplate. A primeira delas define o nome da propriedade do objeto que será exibida. Já a segunda representa a coleção que contém os itens que serão definidos com fonte das informações. Finalmente, a propriedade ItemTemplate, como o próprio nome diz, nos permite criar uma forma diferenciada para exibição de cada item da coleção. Dado uma coleção de clientes, onde cada elemento é representado por uma instância da classe Cliente, podemos fazer o seguinte:

<Window x:Class=”Teste.Window3″
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <local:ColecaoDeClientes x:Key=”cc” />
    </Window.Resources>
    <Grid>
        <ListBox
            Name=”listBox1″
            ItemsSource=”{Binding Source={StaticResource cc}}”
            DisplayMemberPath=”Nome” />
    </Grid>
</Window>

O binding de coleções não está restrito à controles databound. Você pode também vincular uma coleção à um controle do tipo Label, mas como já era de se esperar, apenas o primeiro elemento será exibido. Para que se consiga navegar pelos elementos da coleção, você precisa recorrer à um mecanismo exposto pelo WPF que permite essa navegação.

Quando uma coleção é utilizada através do databinding, o WPF cria nos bastidores um objeto que implementa a interface ICollectionView (namespace System.ComponentModel). Essa interface disponibiliza membros que permite gerenciar a navegação, ordenação e agrupamento das informações. Para extrair este objeto, podemos utilizar o seguinte código:

ColecaoDeClientes cc = new ColecaoDeClientes();
ICollectionView view = CollectionViewSource.GetDefaultView(cc);

Entre os vários membros expostos por essa interface, temos: MoveCurrentToNext, MoveCurrentToPrevious, CurrentItem, etc. Não há o que comentar sobre cada um deles, pois são autoexplicativos. De posse da instância deste navegador, podemos navegar pelos registros de forma simples, sem precisar manualmente armazenar e incrementar ou decrementar índices na medida que o usuário for solicitando a visualização de um novo registro.

Data Templates

Muitas vezes, a visualização padrão fornecida por um controle databound não nos atende, ou por questões visuais ou porque a informação precisa ser customizada/formatada para cada item. Felizmente o WPF separa a funcionalidade do controle da sua visualização, permitindo que se customize completamente a aparência, sem perder ou ter que reescrever a funcionalidade de iteração de elementos.

Como o próprio nome diz, as data templates permite customizar a aparência de um controle, configurando como queremos que ele seja exibido. Tradicionalmente o ListBox exibe cada item um abaixo do outro, sem qualquer customização. Mas e se quisermos que cada elemento seja exibido como um TextBox, e dentro da propriedade Text termos a propriedade Nome vinculada? Abaixo podemos atingir esse objetivo com as data templates, onde customizamos o ListBox e para cada item, exibimos o valor da propriedade Nome dentro da propriedade Text de um TextBox. Repare que neste caso não utilizamos a propriedade DisplayMemberPath do ListBox, pois isso foi delegado ao template, para que ele determine onde e como mostrará o valor. Para quem já trabalhou com ASP.NET, mais precisamente com os controles DataList e Repeater, notará uma grande semelhança aqui.

<Window x:Class=”Teste.Window3″
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <local:ColecaoDeClientes x:Key=”cc” />
    </Window.Resources>
    <Grid>
        <ListBox Name=”listBox1″ ItemsSource=”{Binding Source={StaticResource cc}}”>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBox Text=”{Binding Path=Nome}” />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

ADO.NET

Para preencher os controles do formulário com dados de um banco de dados, você pode recorrer as mesmas técnicas apresentadas aqui. DataSets possuem uma forma tranquila para uso em databinding, mesmo no WPF. Você pode utilizar a propriedade DataContext que vimos acima, definindo para ela a instância do DataSet/DataTable com os dados carregados de um banco de dados qualquer, e a partir daí, utiliza-se a mesma sintaxe para exibição dessas informações no formulário.

Já quando trabalhamos com tecnologias mais recentes, como o LINQ To SQL ou Entity Framework, eles sempre geram coleções, e estas estariam vinculadas aos controles que fossem exibí-las.

Conclusão: Este artigo demonstrou superficialmente todo o poder do databinding do WPF. Vimos como podemos utilizar as mais variadas formas de preencher um controle, não somente com dados de um banco de dados, mas também com informações que são geradas por outros controles ou até mesmo por outros objetos. Esse novo modelo de trabalho facilita bastante a forma como efetuamos a ligação das informações, algo que é um pouco mais complicado em seu principal concorrente, o Windows Forms.

Introdução aos Commands

Entre as várias novidades que o WPF introduziu, uma das que mais vieram para nos ajudar são os Commands, que é semelhante ao popular design pattern Command do GoF. A Microsoft implementou este padrão no WPF para permitir uma melhor organização do código, já que ações são executadas quando o usuário manuseia algum controle ou quando alguma tecla (ou uma combinação delas) for pressionada.

Para ilustrar o problema que temos atualmente, vamos imaginar que temos um controle ListBox com o nome dos clientes e um Button que em seu evento Click, recupera o item selecionado, e exibe os detalhes cadastrais deste em um Label mais abaixo. Além disso, há um item na ToolBar da aplicação que tem a mesma funcionalidade, mas além, ainda possibilita que pressionando as teclas Ctrl + D, também deverá ter o mesmo efeito, ou seja, de mostrar os detalhes do cliente selecionado. Basicamente o código seria qualquer coisa próximo disso:

private void butto1_Click(object sender, EventArgs e)
{
    this.ExibirDetalhesDoCliente();
}

private void menuItem1_Click(object sender, EventArgs e)
{
    this.ExibirDetalhesDoCliente();
}

private void ExibirDetalhesDoCliente()
{
    if (this.Clientes.SelectedItem != null)
    {
        this.DetalhesDoCliente.Text = this.Clientes.SelectedItem.ToString();
    }
}

A imagem abaixo ilustra o formulário já em funcionamento:

Imagine também que gostaria de algo mais interativo, como por exemplo, fazer com que o botão que exibe os detalhes do cliente altere entre o estado de habilitado e desabilitado, de acordo com a seleção (ou não) de um item do ListBox. Isso faria com que eu espalhasse pelo código, verificações e checagens para garantir este resultado. Dependendo de como faz isso, pode resultar em um código ruim e propício a muitas redundâncias. Além disso, muitas vezes você tem várias outras ações, que são executadas por um formulário, e podem ser reutilizadas por vários controles dentro do mesmo, ou até mesmo em outros formulários dentro da mesma aplicação.

Como há muitas ações que são acessadas através de vários controles do formulário, pode também ter condicionais que estão ligados a elas, que permite a execução de acordo com alguma regra (como ter um item selecionado no ListBox). Outro ponto importante é a execução em batch, onde você pode elencar várias ações, adicionando cada uma delas na medida que vai clicando ou manipulando algum controle.

Essa funcionalidade fornecida pelo WPF tem a finalidade de conseguir controlar, de uma forma mais elegante e eficaz, esse tipo de situação, que é muito comum em aplicações Windows. A ideia é que esse padrão separa a execução da ação (chamada daqui para frente de comando) daquele que a invoca, ou seja, no evento Click por exemplo, você não terá mais o código, mas o comando será vinculado ao controle que o executará. Dessa forma, esta técnica permitirá que você vincule o mesmo comando à vários controles, mas a implementação somente acontecerá uma única vez.

O primeiro tipo que vamos analisar, é a interface ICommand. Essa interface fornece três membros autoexplicativos: Execute, CanExecute e CanExecuteChanged. O que chama mais a atenção é o método CanExecute, que retorna um valor boleano indicando se o comando poderá ou não ser executado (através do método Execute). No nosso exemplo, o comando deverá avaliar se o ListBox possui algum item selecionado, para que assim conseguimos visualizar os detalhes do mesmo.

Atualmente no WPF somente existe uma única classe que implementa esta interface, que é a RoutedCommand, e por tabela, a RoutedUICommand, que herda da RoutedCommand. Esse tipo de comando leva esse nome porque trabalha de forma semelhante aos routed events, ou seja, se o controle atual não for capaz de executar o comando, ele delega para o próximo elemento da árvore, até que alguém o execute. A única diferença entre as classes RoutedCommand e RoutedUICommand é que a segunda fornece uma propriedade chamada Text, que nada mais é do que a descrição do comando, que é usado para ser exibido por algum controle de UI.

Para começarmos a modificar o código acima, aquele para exibição de detalhes do cliente, vamos criar uma instância da classe RoutedUICommand, configurando em seu construtor o nome do comando e o tipo da classe onde ela é criada. Note que o modelo de criação segue mais ou menos a mesma forma de criação das dependency properties e dos routed events. Logo após a criação, note que estamos adicionando a possibilidade de utilizar uma combinação de teclas para executar o comando, através da coleção de gestures:

public static class MeusComandos
{
    public static readonly RoutedUICommand ExibirDetalhesDoCliente;

    static MeusComandos()
    {
        ExibirDetalhesDoCliente = 
            new RoutedUICommand(“Exibir Cliente”, “ExibirDetalhesDoCliente”, typeof(MeusComandos));
        ExibirDetalhesDoCliente.InputGestures.Add(new KeyGesture(Key.D, ModifierKeys.Control));
    }
}

É importante dizer que o controle por si só não faz nada. Depois de decidir onde irá usá-lo, você precisa determinar o que ele irá fazer e se ele pode ou não executar esse comando. Para vincular o comando à algum controle, você precisará fazer uso dos CommandBindings. CommandBindings é uma espécie de listeners que aguardam um determinado comando ser executado, e utilizará os métodos já conhecidos (CanExecute e Execute) quando este comando for acionado. Repare no código abaixo, que em momento nenhum o código relacionado ao comando está vinculado ao controle; o que vinculamos no(s) controle(s) é o comando que ele deverá executar, através de um command binding.

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        this.ConfigurarComandos();
    }

    private void ConfigurarComandos()
    {
        this.CommandBindings.Add(
            new CommandBinding(
                MeusComandos.ExibirDetalhesDoCliente,
                cb_Executed,
                cb_CanExecute));

        this.button1.Command = MeusComandos.ExibirDetalhesDoCliente;
        this.menuItem1.Command = MeusComandos.ExibirDetalhesDoCliente;
    }

    private void cb_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        this.label1.Content =
            ((ListBoxItem)this.listBox1.SelectedItem).Content.ToString();
    }

    private void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = this.listBox1.SelectedItem != null;
    }
}

A classe CommandBinding fornece vários eventos, e entre eles os eventos CanExecute e Executed, que é exatamente onde colocamos a regra para determinar se o comando pode ser executado, e o código referente ao comando propriamente dito, que no nosso caso, é mostrar os detalhes. Note que vinculamos a instância no comando criado (ExibirDetalhesDoCliente) na propriedade Command (exposta pela interface ICommandSource) do controle Button e do Menu, que por sua vez, recebe a instância de alguma classe que implemente a interface ICommand. Repare que não estamos nos vinculamos ao evento Click dos controles, pois internamente, depois deste evento (Click) disparado, o WPF avalia se a propriedade Command está definida com algum comando, e estando, o executará, extraindo dos command bindings quais são os eventos correspondentes ao comando. Como estamos lidando com eventos aqui, a propriedade CanExecute defina no parâmetro CanExecuteRoutedEventArgs, é que recebe essa resposta e encaminha para o WPF avaliar, e se estiver True, o executará. O código acima fica mais simples de ler, centraliza as regras e facilita a manutenção.

Algo interessante a se notar é que os controles que estão com o comando vinculado, monitoram o retorno do método/evento CanExecute, definindo a sua respectiva propriedade Enabled com o resultado deste método, ou seja, deixará o controle desabilitado até que ele retorna True. A imagem abaixo ilustra esse comportamento. Do lado esquerdo, a imagem está com os dois controles (Menu e Button) desabilitados até o momento que seleciono um dos itens do ListBox, que é a imagem da direita. Ao clicar no Menu, no Button ou pressionar Ctrl + D, o nome selecionado aparecerá no Label.

É interessante dizer também que os command bindings podem ser definidos de forma declarativa, através do XAML, eliminando assim todo o código que está definido dentro do método ConfigurarComandos.

<Window … xmlns:local=”clr-namespace:WpfApplication1″>
    <Window.CommandBindings>
        <CommandBinding
            Command=”local:MeusComandos.ExibirDetalhesDoCliente”
            CanExecute=”b_CanExecute”
            Executed=”b_Executed” />
    </Window.CommandBindings>

    <Grid>
        <Button Command=”local:MeusComandos.ExibirDetalhesDoCliente” … />
        <Menu Name=”menu1″>
            <MenuItem Header=”Cadastro”>
                <MenuItem
                    Name=”menuItem1″
                    Command=”local:MeusComandos.ExibirDetalhesDoCliente” />
            </MenuItem>
        </Menu>
    </Grid>
</Window>

Comandos Predefinidos

Há alguns comandos que já são bastante conhecidos, como é caso do Copiar, Colar, Recortar, etc., e a Microsoft mapeou eles e já embutiu no WPF, distribuindo-os em cinco categorias: ApplicationCommands, ComponentCommands, MediaCommands, NavigationCommands e EditingCommands. Cada categoria nada mais é do que uma classe estática e, consequentemente, todos os comandos também são declarados como estático, o que siginifica que haverá apenas uma única instância compartilhada de cada um com toda a aplicação.

Pelo fato dos controles serem estáticos (mesmo aquele que criamos acima), quem o “traz” para o controle atual é o CommandBinding, evitando conflitos entre outras regiões que fazem uso deste mesmo comando. Abaixo temos um exemplo simples de como podemos utilizar o comando Copy/Paste, expostos pela classe ApplicationCommands:

<Window …>
    <Window.CommandBindings>
        <CommandBinding
            Command=”ApplicationCommands.Copy”
            CanExecute=”CommandBinding_CanExecute” />
    </Window.CommandBindings>

    <Grid>
        <TextBox Name=”textBox1″ />

        <Button Name=”button1″ Command=”ApplicationCommands.Copy” />
        <Button Name=”button2″ Command=”ApplicationCommands.Paste” />
    </Grid>
</Window>

E no C#, você precisa apenas testar se há algo digitado no TextBox e se ele está selecionado:

private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = !string.IsNullOrEmpty(this.textBox1.SelectedText);
}

É interessante notar que em momento nenhum eu escrevi código para mandar o conteúdo selecionado para o ClipBoard ou para ler o conteúdo de lá. Esse código já está criado em outro lugar (no próprio sistema operacional), e o que o WPF faz é uma forma de interceptar e me permitir decidir de devo ou não continuar.

Conclusão: Eis mais uma grande funcionalidade que facilita muito a escrita de código no desenvolvimento de aplicações Windows. Além disso, torna o código mais fácil para dar manutenção, já que a centralização é um dos principais trunfos desta técnica.

Introdução aos Routed Events

Assim como as Dependency Properties, uma outra grande inovação no WPF foram os Routed Events. As dependency properties adicionam às propriedades uma série de recursos adicionais, quando comparadas às propriedades tradicionais do .NET Framework, e o mesmo acontece com os routed events, fornecendo recursos bastantes interessantes, trazendo uma riqueza maior para os eventos, e que o WPF faz uso intenso disso.

Antes de efetivamente falar sobre os routed events, primeiro precisamos conhecer dois conceitos: logical e visual trees. Ambos tem a finalidade de descrever a interface que foi criada de forma declarativa ou imperativa, mas diferem na forma como visualizamos esses elementos. No primeiro caso, logical, cada elemento dentro desta árvore será visualizado de forma “macro”, por exemplo, se tiver um controle do tipo Button, ele será apenas um Button com uma string que representa o seu texto; já o segundo conceito, visual, faz com que ao invés de visualizarmos o controle como um todo, ele “explode” o mesmo, permitindo o acesso a cada objeto que compõe a construção dele, e no caso do Button, teríamos o Button -> ButtonChrome -> ContentPresenter -> TextBlock.

Os conceitos que vimos acima é importante, pois tanto para dependency properties quanto para os routed events, eles utilizam a árvore lógica para a propagação de informações (dependency properties) e de ações (routed events). Como vimos acima, o Button é composto de vários outros sub-elementos, mas quando o usuário efetua o clique no botão, muito provavelmente ele poderá clicar em cima do TextBlock, e sendo assim, como o WPF traduzirá, ou melhor, entregará este evento para o evento Click do botão correspondente? Tudo isso graças ao recurso de propagação de eventos através da árvore visual.

A implementação de um routed event é bem semelhante a forma que criamos uma dependency property, ou seja, o evento é definido através de uma classe chamada RoutedEvent, declarada como estática e marcada como readonly no topo da classe onde ele será exposto. No construtor estático desta mesma classe, utilizamos o método estático RegisterRoutedEvent da classe EventManager, onde especificamos o nome do evento, a estratégia de roteamento, o tipo do event handler e o tipo da classe onde ele está sendo criado. Abaixo temos uma implementação de um evento em um controle:

public partial class MeuControle : UserControl
{
    public static readonly RoutedEvent ChangedStateEvent;

    static MeuControle()
    {
        ChangedStateEvent =
            EventManager.RegisterRoutedEvent(
                “ChangedState”,
                RoutingStrategy.Tunnel, 
                typeof(RoutedEventHandler), 
                typeof(MeuControle));
    }

    public event RoutedEventHandler ChangedState
    {
        add { AddHandler(MeuControle.ChangedStateEvent, value); }
        remove { RemoveHandler(MeuControle.ChangedStateEvent, value); }
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        this.RaiseEvent(new RoutedEventArgs(MeuControle.ChangedStateEvent, this));
    }
}

O primeiro ponto importante a se notar, é a nomenclatura do evento. Por convenção, adotou-se sufixar com a palavra “Event”, mas isso fica coerente quando estamos criando tudo em inglês; se o nome do evento for “EstadoAlterado”, eu considero desnecessário este sufixo. Além disso, temos um evento tradicional do .NET, chamado de ChangedState. Isso serve apenas como um wrapper para o routed event que foi criado acima (lembre-se que as dependency properties também são expostas através de um wrapper), interceptando a adição e remoção do delegate que tratará o evento, recorrendo aos métodos AddHandler e RemoveHandler, respectivamente, que foram herdados da classe UIElement. E, finalmente, chega o momento de disparar o evento, e para isso utilizamos o método RaiseEvent, que dado os argumentos (falaremos mais abaixo sobre eles) correspondente ao delegate do evento, ele irá dispará-lo e os interessados poderão se anexar ao mesmo.

Os routed events nos permite ainda vincular à um evento não necessariamente em cima do próprio controle, mas através de qualquer controle dentro da hierarquia. Por exemplo, se tivermos um formulário WPF, e dentro dele um StackPanel, e dois botões dentro deste painel, o XAML correspondente seria:

<Window …>
    <Grid>
        <StackPanel>
            <Button Name=”btn1″ Click=”btn1_Click”>Botao 1</Button>
            <Button Name=”btn2″ Click=”btn2_Click”>Botao 2</Button>
        </StackPanel>
    </Grid>
</Window>

A pergunta é: onde tratar o evento Click de cada botão? Se eu tratar o evento diretamente no botão (que é o mais comum), isso fará com que eu crie um event handler para cada botão dentro do StackPanel. Então porque não centralizar isso, onde independemente de qual botão for clicado, eu redirecionará sempre para o mesmo event handler? Poderia resolver isso vinculando o evento Click de todos os botões para o mesmo event handler, mas uma forma mais simples de fazer isso, é vinculando o evento ao controle que está acima na hierarquia do XAML, por exemplo:

<Window …>
    <Grid>
        <StackPanel ButtonBase.Click=”AlgumBotaoFoiClicado”>
            <Button Name=”btn1″>Botao 1</Button>
            <Button Name=”btn2″>Botao 2</Button>
        </StackPanel>
    </Grid>
</Window>

Mas e se haverem mais do que um StackPanel dentro do meu formulário? Podemos então elevar o tratamento do evento para nível de formulário, como é mostrado abaixo:

<Window … ButtonBase.Click=”AlgumBotaoFoiClicado”>
    <Grid>
        <StackPanel>
            <Button Name=”btn1″>Botao 1</Button>
            <Button Name=”btn2″>Botao 2</Button>
        </StackPanel>
        <StackPanel>
            <Button Name=”btn3″>Botao 3</Button>
            <Button Name=”btn4″>Botao 4</Button>
        </StackPanel>
    </Grid>
</Window>

O que determina como esses eventos serão disparados/propagados, é a estratégia de roteamento que você define quando cria o evento. Para isso, utilizamos o enumerador RoutingStrategy, que podemos escolher uma entre as três opções abaixo:

  • Tunnel: O evento é primeiramente disparado no controle raiz e depois em cima de todos os elementos até chegar no controle que efetivamente disparou o evento.
  • Bubble: O evento é primeiramente disparado no controle que efetivamente disparou o evento, subindo até a raiz.
  • Direct: O evento somente é disparado em cima do elemento de origem, assim como acontece com os eventos tradicionais do .NET.

Para utilizar o controle que criamos acima e consumir o evento, teremos um código semelhante a este:

<Window …
    xmlns:app=”clr-namespace:WpfApplication1″
    app:MeuControle.ChangedState=”MeuControle_ChangedState”>
    <Grid>
        <app:MeuControle ChangedState=”MeuControle_ChangedState” />
    </Grid>
</Window>

private void MeuControle_ChangedState(object sender, RoutedEventArgs e)
{
    MessageBox.Show(“Sender: ” + sender.ToString());
}

Se o evento for criado com a opção Tunnel, as mensagens serão: “Window1” e “MeuControle”; se definir a opção como Bubble, as mensagens serão “MeuControle” e “Window1” e, finalmente, se definir como Direct, somente aparecerá “MeuControle”.

A assinatura dos event handlers dos routed events seguem, basicamente, o mesmo padrão de eventos tradicionais do .NET. O primeiro parâmetro (sender) é do tipo System.Object, que informa o objeto que disparou o evento. Já o segundo parâmetro (e) é uma classe do tipo RoutedEventArgs, que expõe quatro propriedades que trazem informações contextuais:

  • Source: System.Object que representa o elemento na árvore lógica que originalmente invocou o evento.
  • OriginalSource: System.Object que representa o elemento na árvore visual que originalmente disparou o evento.
  • Handled: Valor boleano que indica se o evento deverá prosseguir com a propagação para os elementos subsequentes da árvore. Se definí-lo como False, o próximo não será disparado.
  • RoutedEvent: A instância do routed event que foi disparado, que pode ser usado quando você direciona vários eventos para o mesmo event handler.

Conclusão: Assim como já acontece com as dependency properties, em um primeiro momento, esse tipo de evento torna o código um pouco mais complicado de ler, mas por outro lado, traz uma série de benefícios que você não consegue com os eventos tradicionais do .NET.

Introdução às Dependency Properties

Quando desenvolvemos classes e/ou controles, geralmente expomos as suas características através de propriedades. Na maioria das vezes, essas propriedades encapsulam o acesso à membros privados internos, interceptando a escrita e leitura dos mesmos. Os controles ASP.NET geralmente não possuem esses membros internos, e armazenam o conteúdo diretamente no ViewState ou no ControlState da respectiva página.

As propriedades também são utilizadas por controles Windows Forms, seguindo a mesma estratégia das propriedades tradicionais que as linguagens disponibilizam. Mas aplicações Windows geralmente tem algumas características próprias, como validação e, principalmente, databinding, que é a possibilidade de vincular um objeto à algum controle de interface (UI). Há também algumas tarefas que são comuns em aplicações deste tipo, como por exemplo, a notificação da alteração de um valor, que fará eventuais mudanças nos demais controles.

Para tornar esse trabalho mais suave, a Microsoft introduziu, desde a primeira versão do WPF, as Dependency Properties. Esse tipo especial de propriedade é semelhante as propriedades das linguagens, mas com muito mais poder. A inteligência envolvida neste novo tipo de propriedade, permite trabalharmos com o código puramente declarativo (XAML), sem precisar de qualquer código procedural para efetuar alguma mudança na aparência quando algo acontecer.

Além disso, temos uma série de outros benefícios ao utilizá-las. O primeiro deles é a capacidade de sermos notificados quando o valor dessa propriedade for alterado, e o próprio sistema de Triggers do WPF faz uso desta funcionalidade, onde você consegue associar um valor à uma propriedade, e quando ela receber este valor, você poderá tomar alguma decisão, como alterar a cor de outro controle, disparo de outros eventos, etc. Há também outros pontos positivos que vamos analisar mais tarde, ainda neste artigo.

A sua utilização é relativamente simples, e não dispensa completamente o uso das propriedades tradicionais fornecidas pelas linguagens. As classes (ou controles) que querem fazer uso destas propriedades, deverão herdar direta ou indiretamente da classe DependencyObject, pois essa classe permitirá ao objeto participar do serviço de propriedades que o WPF disponibiliza. Além disso, para que uma propriedade seja registrada e, consequentemente, poder fazer uso de toda essa infraestrutura, cada propriedade exposta deverá manter um campo dentro da classe onde ela será declarada, definindo o tipo deste campo como sendo DependencyProperty. Abaixo temos a estrutura de uma dependency property:

public static readonly DependencyProperty TitleProperty =
    DependencyProperty.Register(
        “Title”, 
        typeof(string), 
        typeof(MeuControle));

public string Title
{
    get
    {
        return this.GetValue(TitleProperty).ToString();
    }
    set
    {
        this.SetValue(TitleProperty, value);
    }
}

O primeiro ponto que vemos é que a classe DependencyProperty não é instanciada diretamente. Para registrá-la, você precisa invocar o método estático Register (dessa mesma classe), que em sua versão mais básica, receberá o nome da propriedade, o tipo da propriedade e o tipo onde essa propriedade está sendo criada. Outro ponto bastante curioso é que esse campo é declarado como estático. Mas e se houverem várias instâncias dessa classe, o valor da propriedade será compartilhado? Não. Na verdade, as propriedades são salvas em uma espécie de dicionário, onde a chave deverá ser única dentro do tipo. Aqui é a grande diferença, ou seja, ao invés de termos um campo privado para cada propriedade exposta, que muitas vezes ficavam com o seu valor padrão, as dependency properties resolvem este problema armazenando somente os valores que são modificados pela instância, enquanto as outras compartilham um – mesmo – valor padrão, reutilizando o valor padrão por todas as instâncias.

A seguir temos a propriedade Title, que por sua vez, serve apenas como um wrapper para o objeto que criamos acima, expondo o tipo efetivo da propriedade, que no caso acima é string. Note que em momento nenhum você acessa o objeto TitleProperty diretamente; toda a manipulação é realizada pelos métodos GetValue e SetValue, para ler e escrever, respectivamente. Como vimos, esses métodos estão disponíveis para todas as classes que derivam da classe DependencyObject, que realiza todas as etapas necessárias para determinar se o valor já foi sobrescrito pela instância corrente, e caso tenha sido, este será retornado ao invés de seu valor padrão.

Ainda há a possibilidade de registrar uma dependency property como somente leitura, pois pode haver propriedades que apenas reportam o estado interno da classe, como por exemplo, a propriedade IsMouseOver da classe Control, que retorna um valor boleano indicando se o ponteiro está ou não posicionado em cima daquele controle. Neste caso, o valor é definido internamente pelo WPF.

A classe DependencyProperty fornece para essa finalidade, um método também estático, chamado RegisterReadOnly, que depois de registrado, retornará uma instância da classe DependencyPropertyKey, representando a chave para a propriedade recém registrada.

private static readonly DependencyPropertyKey TitlePropertyKey =
      DependencyProperty.RegisterReadOnly(
          “Title”, 
          typeof(string),
          typeof(MeuControle));
 
public static readonly DependencyProperty TitleProperty =
      TitlePropertyKey.DependencyProperty;

public string Title
{
    get
    {
        return this.GetValue(TitleProperty).ToString();
    }
    private set
    {
        this.SetValue(TitlePropertyKey, value);
    }
}

Neste caso, veja que a chave é declarada de forma privada, para que somente o interior da classe onde ela é declarada tenha acesso. A criação do objeto DependencyProperty ainda é necessário, mas não será através do método Register. A classe DependencyPropertyKey define uma propriedade chamada DependencyProperty, que retorna a propriedade para acessá-la através do método GetValue, seguindo o mesmo esquema que vimos acima. Na propriedade Title, a escrita também está protegida pelo modificador private, e note que a alteração está sendo feita, passando o chave e não a instância da DependencyProperty como fizemos no primeiro exemplo.

Metadados

Como vimos acima, a classe DependencyProperty ainda fornece outras versões (overloads) do método Register. O que veremos a seguir, é aquele que recebe os metadados. Através destes metadados, poderemos configurar uma série de características destas propriedades, como por exemplo: valor padrão, notificação quando o valor for alterado, coerção, etc. A principal classe que podemos utilizar para definição destes metadados, é a FrameworkPropertyMetadata.

Entre as principais funcionalidades que esta classe fornece, temos a possibilidade de informar o valor padrão da propriedade, e enquanto você não definir um novo valor para uma propriedade, será este valor que será retornado. Além disso, podemos definir através de delegates, ações que desejamos executar quando o valor for alterado ou alguma ação para efetuar uma coerção no valor. Abaixo temos o exemplo de como utilizar esses metadados:

public static readonly DependencyProperty TitleProperty =
    DependencyProperty.Register(
        “Title”,
        typeof(string),
        typeof(MeuControle),
        new FrameworkPropertyMetadata(
            “Título Indefinido”,
            (o, e) => MessageBox.Show(e.NewValue.ToString()),
            (d, e) => string.Format(“*** {0} ***”, e)));

O primeiro parâmetro informado no construtor da classe FrameworkPropertyMetadata, é o valor padrão da propriedade. Esse parâmetro é do tipo System.Object, pois a propriedade poderá ser de qualquer tipo. Todas as instâncias desta classe sempre compartilharão este mesmo valor, a menos que uma instância específica o sobrescreva, e a partir daí, esta instância terá o seu próprio valor.

Os dois parâmetros seguintes são delegates, onde o primeiro deles (PropertyChangedCallback) representa o método que será disparado quando o valor desta propriedade for alterado. O argumento “e” é do tipo DependencyPropertyChangedEventArgs, e entre suas propriedades, temos OldValue e NewValue, que são autoexplicativas. Já o terceiro parâmetro (CoerceValueCallback), define um método de coerção, que tem a finalidade de customizar/formatar a entrada da informação de acordo com alguma regra, que acontecerá antes de armazenar o valor definitivamente. O “e” que vemos sendo passado para este método, traz o valor atual; a assinatura deste delegate também retorna um System.Object, que é justamente o valor depois de alterado.

Validação

Outro ponto importante quando falamos sobre propriedades, é a questão de validação. As validações geralmente são avaliadas antes do valor ser efetivamente armazenado. As dependency properties também já trazem a validação nativamente, que podemos configurar e vincular durante a criação de uma propriedade.

Para isso, o método Register ainda fornece em um de seus overloads, um parâmetro do tipo ValidateValueCallback, que também é um delegate e que recebe o valor que está sendo passado para a propriedade e retorna um valor boleano, indicando se o parâmetro está válido ou não. O seu uso está sendo exibido abaixo, e note que antes da propriedade receber o valor, a consistência para verificar se ele está correta será avaliada, e caso retorne False, uma exceção será disparada.

public static readonly DependencyProperty TitleProperty =
    DependencyProperty.Register(
        “Title”,
        typeof(string),
        typeof(MeuControle),
        new FrameworkPropertyMetadata(
            “Sem Título Definido”,
            (o, e) => MessageBox.Show(e.NewValue.ToString()),
            (d, e) => string.Format(“*** {0} ***”, e)),
        p => p != null && p.ToString().Length > 0);

Conclusão: Apesar de no primeiro momento tornar o código um pouco ilegível, percebermos no decorrer deste artigo o poder deste tipo de propriedade. Grande parte das tarefas de manipulação de UI, e que antes ficavam espalhadas pelo código, agora podemos centralizar, tornando a manutenção e reutilização muito mais simples. Isso sem contar que utilizar esse tipo de propriedade habilita grande parte dos recursos exclusivos do WPF, como estilos, databinding, etc.

Threading em WPF

Quando desenvolvemos aplicações Windows, é muito comum em algum ponto da mesma, que algumas tarefas mais complicadas e custosas sejam realizadas, que podem levar um tempo maior até que seja concluída. Independentemente do que ela faça, seja um cálculo, uma consulta em uma base de dados ou uma chamada para um serviço, se você executar esse código de forma síncrona, o usuário deverá esperar até que essa tarefa seja finalizada, para a partir daí, conseguir acessar outras áreas do sistema.

Ao rodar uma aplicação Windows, um processo é criado dentro do sistema operacional. Processo não executa nenhum código; são as threads que fazem isso. Quando o processo é criado, uma thread é criada juntamente com ele, e esta é muitas vezes chamada de main-thread (thread principal). Essa thread nasce e morre com o término do processo, ou seja, enquanto ela estiver executando alguma tarefa, o processo continuará ativo.

As aplicações Windows que conhecemos, como Windows Forms, Console, Windows Services e WPF trabalham nesta mesma linha. Aplicações que possuem gráficos, como é o caso do Windows Forms e do WPF, tem um agravante: a afinidade que os controles tem com a thread principal. Quando a aplicação é iniciada, a thread principal é quem cria os controles (Form, TextBox, Label, TextBlock, etc.), e quando dizemos que há uma afinidade, isso quer dizer que podemos somente manipular esses controles, através da mesma thread que os criaram, e qualquer tentativa de fazer isso através de uma segunda thread, uma exceção do tipo InvalidOperationException será disparada.

Quando essas tarefas são finalizadas, é normal queremos exibir o resultado para o usuário, que na maioria das vezes, implica em alterar a propriedade Text de algum controle, exibir uma MessageBox, etc. Em Windows Forms, todos os controles herdam direta ou indiretamente da classe Control, que fornece um método chamado Invoke, e que dado um delegate, executa o método relacionado na mesma thread do criador. Mais tarde, com o .NET Framework 2.0, surgiu o SynchronizationContext, que facilitou bastante a atualização dos controles a partir de uma thread secundária.

Agora temos o WPF, que traz novas funcionalidades e uma forma um pouco diferente para lidar com esse tipo de problema. Vamos a partir deste artigo, explorar um pouco mais sobre o modelo de threading do WPF. A Microsoft introduziu uma série de novos tipos, espalhados por vários namespaces e que serão utilizados para conseguir atingir o nosso principal objetivo. Para iniciar, o primeiro tipo que temos que conhecer é a classe Dispatcher. Essa classe serve como um gerenciador de tarefas para serem executadas, e está sempre associada com uma determinada thread de UI. Ela mantém uma fila de tarefas que são executadas utilizando a thread qual está relacionada.

A fila que é mantida pela classe Dispatcher é priorizada, que permite especificar uma prioridade antes de enfileirar a tarefa (mais detalhes abaixo). Para alistar uma tarefa nesta fila, você poderá utilizar o método Invoke ou BeginInvoke. A diferença entre eles é clara: o primeiro executa a tarefa de forma síncrona, enquanto a segunda alista a tarefa para ser executada de forma assíncrona. E para sedimentar, ambas sempre executarão na thread ao qual o Dispatcher está vinculado.

Grande parte das classes que compõem o framework do WPF, incluindo os controles, herda direta ou indiretamente da classe abstrata DispatcherObject, que possui uma estrutura simples, ou seja, fornece uma propriedade chamada Dispatcher que retorna a instância de uma classe Dispatcher, e como já era de esperar, fornece a instância do Dispatcher que está vinculado com aquele classe/controle.

A classe DispatcherObject ainda fornece dois métodos importantes: CheckAccess e VerifyAccess. A diferença entre eles é que o primeiro retorna um valor boleano, indicando se a thread que está chamando tem direito de acesso ao Dispatcher correspondente. Já o segundo método, VerifyAccess, dispara uma exceção do tipo InvalidOperationException, caso a thread que está chamando não tiver direito de acesso ao Dispatcher. Como pudemos perceber, esses métodos vão nos auxiliar para determinar se há ou não a necessidade de atualizar o controle através da thread atual, sem que seja necessário utilizar o Dispatcher para chegar até o controle.

Para exemplificar, imagine que temos uma thread que executará algum cálculo complexo, e depois de calculado, deverá exibir o resultado em um TextBox. Como comentado acima, dentro desta thread não podemos alterar qualquer propriedade do TextBox, e para solucionar isso no WPF, vamos recorrer a propriedade Dispatcher do TextBox (“txt”), que foi herdada de DispatcherObject. Ao passar um delegate para o método Invoke, ele será executado (de forma síncrona) na mesma thread que criou o controle.

new Thread(() =>
{
    int result = 2 ^ 4 * 2 + 3 / 3;
    Thread.Sleep(3000); //Simula Processo Complexo

    txt.Dispatcher.Invoke(new Action<int>(r => txt.Text = r.ToString()), result);
}).Start();

Se desejar, podemos trocar o método Invoke por BeginInvoke, e a atualização do controle será feita em background. A vantagem desta técnica é que você pode executar a atualização do controle enquanto faz outras tarefas. É importante que você mantenha tarefas “leves” dentro do Dispatcher, pois tudo o que ele deveria fazer ali é atualizar a UI; colocar tarefas mais complexas, voltará a ter concorrência com os eventos dos controles e, consequentemente, o usuário voltará a ter os travamentos das telas do sistema, que acontecia quando trabalhávamos de forma síncrona.

Quando você opta por utilizar o método BeginInvoke, ele retorna uma instância da classe DispatcherOperation. Basicamente, este objeto representa uma espécie de “ponteiro” para a operação que está sendo executada. Essa classe fornece uma série de membros interessantes, e entre eles temos:

  • Dispatcher: O Dispatcher relacionado.
  • Priority: Uma das opções definidas no enumerador DispatcherPriority, que define a prioridade da operação.
  • Result: Retorna um System.Object com o resultado da tarefa (isso quando ela retornar algum resultado).
  • Status: Uma das opções definidas no enumerador DispatcherOperationStatus, que define o status atual da operação (Pending, Aborted, Completed ou Execution).
  • Abort: Método que aborta a operação que está sendo executada.
  • Wait: Quando invocado, fará um “join” na thread atual, aguardando até o término da operação. Opcionalmente você pode especificar um timeout.
  • Aborted: Evento que é disparado quando a operação é abortada.
  • Completed: Evento que é disparado quando a operação foi finalizada.

Com a instância do DispatcherOperation em mãos, podemos utilizar duas formas para chegar até o resultado, que é via eventos ou através de polling. Utilizando o modelo de eventos, podemos nos vincular ao evento Completed, e quando a operação for finalizada, esse evento será automaticamente disparado. Já o polling consiste em testar, de tempo em tempo, se a operação finalizou ou não. Abaixo temos os dois exemplos de utilização:

new Thread(() =>
{
    int result = 2 ^ 4 * 2 + 3 / 3;
    Thread.Sleep(3000); //Simula Processo Complexo

    DispatcherOperation op =
        txt.Dispatcher.BeginInvoke(new Action<int>(r => txt.Text = r.ToString()), result);

    op.Completed += (o, args) => MessageBox.Show(“Finalizou Tudo”);
}).Start();

new Thread(() =>
{
    int result = 2 ^ 4 * 2 + 3 / 3;
    Thread.Sleep(3000); //Simula Processo Complexo

    DispatcherOperation op =
        txt.Dispatcher.BeginInvoke(new Action<int>(r => txt.Text = r.ToString()), result);

    //Faz Algo…

    while (op.Status != DispatcherOperationStatus.Completed)
    {
        if (op.Wait(TimeSpan.FromSeconds(5)) == DispatcherOperationStatus.Completed)
        {
            //Finalizou a Atualização da UI
            break;
        }
    }
}).Start();

Tanto o método Invoke quanto o BeginInvoke possui versões (overloads) destes métodos que permitem especificar uma prioridade, e para isso, utilizamos uma das doze opções definidas pelo enumerador DispatcherPriority. Há algumas opções interessantes, como por exemplo Inactive, que permite você alistar a operação, mas que não será processada. De qualquer forma, na maioria das vezes a opção Normal (que também é o padrão), já será o suficiente. Abaixo um exemplo de como podemos proceder para especificar a prioridade de uma operação:

txt.Dispatcher.BeginInvoke(new Action<int>(r => txt.Text = r.ToString()), DispatcherPriority.Normal, result);

Uma vez que você tem operações alistadas no Dispatcher, o runtime irá determinar quando executá-las, dependendo da prioridade definida. A classe Dispatcher também fornece métodos para abortar todas as operações pendentes de processamento. Para isso, recorremos aos métodos InvokeShutdown ou BeginInvokeShutdown (a diferença entre eles já é sabida). A classe Dispatcher ainda fornece um evento chamado ShutdownFinished, que é disparado quando shutdown do Dispatcher for completamente finalizado, e com isso, você poderá tomar alguma decisão.

Conclusão: Como vimos, ambas tecnologias (Windows Forms e WPF) possuem os mesmos comportamentos. Como trabalhar com ambientes multi-threading não é uma tarefa fácil, a Microsoft introduziu no WPF uma forma diferente de se trabalhar para a atualizar de UI através de uma segunda thread. Além de um modelo suavemente diferente, há algumas melhorias internas que garantem que isso acabe sendo executado de uma forma melhor. E para finalizar, essa técnica visa sempre tornar a aplicação mais amigável para o usuário, sem que eles tenha experiências ruins.