Desenvolvimento - WCF/WPF

Introdução aos Routed Events

Assim como as Dependency Properties, uma outra grande inovação no WPF foram os Routed Events.

por Israel Aéce



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 emarcada comoreadonly 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 voidbutton1_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 oenumerador 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 voidMeuControle_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.

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.