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.

Anúncios

2 comentários sobre “Introdução aos Commands

  1. Israel,

    iniciei meus estudos no WCF graças a você e seus vídeos. Muito obrigado (mais uma vez).
    Gostaria de saber se você pensa em criar vídeos para WPF também. Eles são extremamente úteis, e em português, acredito ser o único material que teremos.

    Abraços
    Leandro Fagundes

  2. Boas Leandro,

    Em princípio eu não penso em fazer vídeos de WPF, porque no meu backlog há coisas que eu vou dar mais prioridade. Sabendo que há interesse, eu penso nisso mais pra frente.

    É importante dizer que, se você notar, eu estou escrevendo somente sobre características do WPF e SL, não sobre design. Se eu vier a fazer vídeos, será seguindo essa mesma linha.

    Obrigado.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s