Marcando eventos roteados como manipulados e manipulação de classe

Os manipuladores de um evento roteado podem marcar o evento manipulado nos dados do evento. Efetivamente, a manipulação do evento reduzirá a rota. A manipulação de classes é um conceito de programação com suporte nos eventos roteados. Um manipulador de classes tem a oportunidade de manipular um evento roteado específico no nível de uma classe com um manipulador que é invocado antes de qualquer manipulador de instâncias em uma instância da classe.

Pré-requisitos

Este tópico fornece mais detalhes sobre os conceitos introduzidos na Visão geral dos eventos roteados.

Quando marcar eventos como manipulados

Quando você define o valor da propriedade como nos dados do Handled evento para um evento roteado, isso é conhecido como true "marcando o evento manipulado". Não há nenhuma regra absoluta para quando você deve marcar eventos roteados como manipulados, seja como autor de um aplicativo ou de um controle que responde aos eventos roteados existentes ou implementa novos eventos roteados. Na maioria das vezes, o conceito de "manipulado" como transportado nos dados de evento do evento roteado deve ser usado como um protocolo limitado para as respostas do seu próprio aplicativo aos vários eventos roteados expostos nas APIs do WPF, bem como para quaisquer eventos roteados personalizados. Outra maneira de considerar a questão de “manipulado” é que você geralmente deve marcar um evento roteado como manipulado se o código respondeu ao evento roteado de forma significativa e relativamente completa. Normalmente, não deve haver mais de uma resposta significativa que exige implementações de manipulador separadas para uma única ocorrência de evento roteado. Se mais respostas forem necessárias, o código necessário deverá ser implementado por meio da lógica do aplicativo que é encadeada em um único manipulador, em vez de usar o sistema de eventos roteados para o encaminhamento. O conceito do que é “significativo” também é subjetivo e depende do aplicativo ou do código. Como diretriz geral, alguns exemplos de “resposta significativa” incluem: configuração do foco, modificação do estado público, configuração de propriedades que afetam a representação visual e acionamento de outros novos eventos. Exemplos de respostas não significativas incluem: modificação do estado particular (sem impacto visual nem representação programática), log de eventos ou inspeção de argumentos de um evento e opção de não responder a ele.

O comportamento do sistema de eventos roteados reforça esse modelo de "resposta significativa" para usar o estado manipulado de um evento roteado, porque os manipuladores adicionados em XAML ou a assinatura comum de não são invocados em resposta a um evento roteado em que os dados do evento já estão marcados AddHandler como manipulados. Você deve passar pelo esforço extra de adicionar um manipulador com a versão do handledEventsToo parâmetro (AddHandler(RoutedEvent, Delegate, Boolean)) para manipular eventos roteados que são marcados manipulados por participantes anteriores na rota de eventos.

Em algumas circunstâncias, os próprios controles marcam alguns eventos roteados como manipulados. Um evento roteado manipulado representa uma decisão dos autores de controle do WPF de que as ações do controle em resposta ao evento roteado são significativas ou completas como parte da implementação do controle, e o evento não precisa de tratamento adicional. Geralmente, isso é feito com a adição de um manipulador de classes para um evento ou a substituição de um dos virtuais do manipulador de classes existentes em uma classe base. Você ainda pode resolver essa manipulação de eventos se necessário; consulte Resolvendo a supressão de eventos por controles mais adiante neste tópico.

Eventos de "visualização" (encapsulamento) vs. eventos borbulhantes e manipulação de eventos

Os eventos roteados de visualização são eventos que seguem uma rota de túnel pela árvore de elementos. A “Visualização” expressa na convenção de nomenclatura indica o princípio geral para eventos de entrada de que os eventos roteados de visualização (túnel) são acionados antes do evento roteado de propagação equivalente. Além disso, os eventos roteados de entrada que têm um par de túnel e de propagação têm uma lógica de manipulação distinta. Se o evento roteado de túnel/visualização for marcado como manipulado por um ouvinte de eventos, o evento roteado de propagação será marcado como manipulado mesmo antes de os ouvintes do evento roteado de propagação o receberem. Os eventos roteados de túnel e de propagação são tecnicamente eventos separados, mas, deliberadamente, compartilham a mesma instância de dados do evento para permitir esse comportamento.

A conexão entre o tunelamento e os eventos roteados borbulhantes é realizada pela implementação interna de como uma determinada classe WPF gera seus próprios eventos roteados declarados, e isso é verdadeiro para os eventos roteados de entrada emparelhados. Porém, a menos que essa implementação em nível de classe exista, não haverá nenhuma conexão entre um evento roteado de túnel e um evento roteado de propagação que compartilham o esquema de nomenclatura: sem essa implementação, eles serão dois eventos roteados completamente separados e não serão acionados em sequência nem compartilharão dados do evento.

Para obter mais informações sobre como implementar pares de eventos roteados de entrada de túnel/propagação em uma classe personalizada, consulte Criar um evento roteado personalizado.

Manipuladores de classe e de instância

Os eventos roteados consideram dois tipos diferentes de ouvintes para o evento: ouvintes de classes e ouvintes de instâncias. Os ouvintes de classe existem porque os tipos chamaram uma API específica EventManager ,, em seu construtor estático,RegisterClassHandler ou substituíram um método virtual de manipulador de classe de uma classe base de elemento. Os ouvintes de instância são instâncias/elementos de classe específicos em que um ou mais manipuladores foram anexados para esse evento roteado por uma chamada para AddHandler. Os eventos roteados WPF existentes fazem chamadas como parte do wrapper de eventos CLR (Common Language Runtime) adicionar{} e remover{} implementações do evento, que também é como AddHandler o mecanismo XAML simples de anexar manipuladores de eventos por meio de uma sintaxe de atributo é habilitado. Portanto, até mesmo o uso simples de XAML equivale, em última análise, a uma AddHandler chamada.

Elementos na árvore visual são verificados quanto a implementações de manipuladores registrados. Os manipuladores são potencialmente invocados em toda a rota, na ordem que é inerente ao tipo da estratégia de roteamento para esse evento roteado. Por exemplo, os eventos roteados de propagação primeiro invocarão os manipuladores anexados ao mesmo elemento que acionou o evento roteado. Em seguida, o evento roteado é “propagado” para o próximo elemento pai e assim por diante até chegar ao elemento raiz do aplicativo.

Da perspectiva do elemento raiz em uma rota de propagação, se a manipulação de classe ou qualquer elemento mais próximo à origem do evento roteado invocar manipuladores que marcam os argumentos do evento como tendo sido manipulados, os manipuladores nos elementos raiz não serão invocados e a rota de evento será reduzida efetivamente antes de chegar ao elemento raiz. No entanto, a rota não é completamente interrompida, porque os manipuladores podem ser adicionados com um condicional especial indicando que eles ainda devem ser invocados, mesmo se um manipulador de classes ou de instâncias marcou o evento roteado como manipulado. Isso é explicado em Adicionando manipuladores de instância que são acionados mesmo quando os eventos são marcados como manipulados, mais adiante neste tópico.

Em um nível mais profundo que a rota de evento, potencialmente, também há vários manipuladores de classe atuando em determinada instância de uma classe. Isso ocorre porque o modelo de manipulação de classe para eventos roteados permite que todas as classes possíveis em uma hierarquia de classe registrem, individualmente, seu próprio manipulador de classes para cada evento roteado. Cada manipulador de classes é adicionado a um repositório interno e quando a rota de evento de um aplicativo é construída, os manipuladores de classe são todos adicionados à rota de evento. Os manipuladores de classe são adicionados à rota de forma que o manipulador da classe derivada é invocado primeiro e os manipuladores de cada classe base sucessiva são invocados em seguida. Em geral, os manipuladores de classe não são registrados, para que também respondam aos eventos roteados que já foram marcados como manipulados. Portanto, esse mecanismo de manipulação de classe possibilita uma das duas opções:

  • As classes derivadas podem suplementar a manipulação de classe herdada da classe base adicionando um manipulador que não marca o evento roteado como manipulado, pois o manipulador da classe base será invocado algum tempo após o manipulador da classe derivada.

  • As classes derivadas podem substituir a manipulação de classe da classe base adicionando um manipulador de classes que marca o evento roteado como manipulado. Você deve ter cuidado com essa abordagem, pois ela potencialmente altera o design de controle base pretendido em áreas como aparência visual, lógica de estado, manipulação de entrada e manipulação de comandos.

Manipulação de classe de eventos roteados por classes base de controle

Em cada nó de elemento específico em uma rota de evento, os ouvintes de classes têm a oportunidade de responder ao evento roteado antes de qualquer ouvinte de instâncias do elemento. Por esse motivo, às vezes, os manipuladores de classe são usados para suprimir eventos roteados que uma implementação de classe de controle específica não pretende propagar adiante ou para fornecer uma manipulação especial desse evento roteado que é um recurso da classe. Por exemplo, uma classe pode acionar seu próprio evento específico à classe que contém mais especificações sobre o que uma condição de entrada do usuário significa no contexto dessa classe específica. A implementação de classe pode então marcar o evento roteado mais geral como manipulado. Os manipuladores de classe geralmente são adicionados de forma que não sejam invocados para eventos roteados em que os dados de eventos compartilhados já foram marcados como manipulados, mas para casos atípicos também há uma RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) assinatura que registra manipuladores de classe para invocar mesmo quando eventos roteados são marcados como manipulados.

Virtuais de manipulador de classes

Alguns elementos, particularmente os elementos base, como UIElement, expõem métodos virtuais vazios "On*Event" e "OnPreview*Event" que correspondem à sua lista de eventos públicos roteados. Esses métodos virtuais podem ser substituídos para implementar um manipulador de classes para esse evento roteado. As classes de elemento base registram esses métodos virtuais como seu manipulador de classe para cada evento roteado usando RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) conforme descrito anteriormente. Os métodos virtuais On*Event tornam muito mais simples implementar a manipulação de classes para os eventos roteados relevantes, sem exigir inicialização especial em construtores estáticos para cada tipo. Por exemplo, você pode adicionar manipulação de classe para o evento em qualquer UIElement classe derivada substituindo o DragEnterOnDragEnter método virtual. Na substituição, é possível manipular o evento roteado, acionar outros eventos, iniciar uma lógica específica à classe que pode alterar propriedades de elementos em instâncias ou qualquer combinação dessas ações. Em geral, você deverá chamar a implementação base nessas substituições, mesmo se marcar o evento como manipulado. É altamente recomendável chamar a implementação base, pois o método virtual está na classe base. Basicamente, o padrão virtual protegido de chamada das implementações base de cada virtual substitui e é análogo a um mecanismo semelhante nativo da manipulação de classe de evento roteado, pelo qual os manipuladores de classe de todas as classes em uma hierarquia de classe são chamados em determinada instância, começando com o manipulador da classe mais derivada e continuando para o manipulador da classe base. Você só deverá omitir a chamada da implementação base se a classe tiver um requisito intencional de alteração da lógica de manipulação da classe base. A decisão de chamar a implementação base antes ou após a substituição do código dependerá da natureza da implementação.

Manipulação de classe de evento de entrada

Os métodos virtuais de manipulador de classes são todos registrados para que só sejam invocados em casos em que os dados do evento compartilhados ainda não tenham sido marcados como manipulados. Além disso, exclusivamente para eventos de entrada, as versões de túnel e de propagação normalmente são acionadas em sequência e compartilham dados do evento. Isso indica que, para determinado par de manipuladores de classe de eventos de entrada em que um é a versão de túnel e o outro a versão de propagação, talvez você não deseje marcar o evento como manipulado imediatamente. Se você implementar o método virtual de manipulação de classe de túnel para marcar o evento como manipulado, isso impedirá que o manipulador de classes de propagação seja invocado (além de impedir que os manipuladores de instância normalmente registrados para o evento de túnel ou de propagação sejam invocados).

Após a conclusão da manipulação de classe em um nó, os ouvintes de instâncias são considerados.

Adicionando manipuladores de instância que são acionados mesmo quando os eventos são marcados como manipulados

O AddHandler método fornece uma sobrecarga específica que permite adicionar manipuladores que serão chamados pelo sistema de eventos sempre que um evento atingir o elemento de manipulação na rota, mesmo que algum outro manipulador já tenha ajustado os dados do evento para marcar esse evento como manipulado. Normalmente, isso não é feito. Em geral, os manipuladores podem ser escritos para ajustar todas as áreas do código do aplicativo que podem ser influenciadas por um evento, independentemente da localização em que foi manipulado em uma árvore de elementos, mesmo se vários resultados finais forem desejados. Além disso, por via de regra, há realmente apenas um elemento que precisa responder a esse evento e a lógica do aplicativo apropriada já ocorreu. No entanto, a sobrecarga handledEventsToo está disponível para os casos excepcionais em que algum outro elemento em uma árvore de elementos ou composição de controle já marcou um evento como manipulado, mas outros elementos acima ou abaixo da árvore de elementos (dependendo da rota) ainda desejam invocar seus próprios manipuladores.

Quando marcar eventos manipulados como não manipulados

Geralmente, os eventos roteados que são marcados manipulados não devem ser marcados como não manipulados (Handled set back to false) mesmo por manipuladores que atuam no handledEventsToo. No entanto, alguns eventos de entrada têm representações de evento de alto nível e de baixo nível que podem se sobrepor quando o evento de alto nível é visto em uma posição na árvore e o evento de baixo nível em outra posição. Por exemplo, considere o caso em que um elemento filho escuta um evento chave de alto nível, como enquanto um elemento pai escuta um evento de baixo nível, como TextInputKeyDown. Se o elemento pai manipular o evento de baixo nível, o evento de nível mais alto poderá ser suprimido mesmo no elemento filho, que intuitivamente deveria ter a primeira oportunidade de manipular o evento.

Nessas situações, pode ser necessário adicionar manipuladores aos elementos pai e filho no evento de baixo nível. A implementação do manipulador de elemento filho pode marcar o evento de baixo nível como manipulado, mas a implementação do manipulador de elemento pai o definirá como não manipulado novamente, para que os outros elementos acima na árvore (bem como o evento de alto nível) possam ter a oportunidade de responder. Essa situação deve ser bastante rara.

Suprimindo eventos de entrada propositalmente para composição de controle

O principal cenário em que a manipulação de classe de eventos roteados é usada é para eventos de entrada e controles compostos. Por definição, um controle composto consiste em vários controles práticos ou classes base de controle. Geralmente, o autor do controle deseja combinar todos os eventos de entrada possíveis que cada um dos subcomponentes pode acionar, para relatar o controle inteiro como uma única origem do evento. Em alguns casos, o autor do controle pode desejar suprimir por completo os eventos dos componentes ou substituir um evento definido por componente que carrega mais informações ou implica um comportamento mais específico. O exemplo canônico que é imediatamente visível para qualquer autor de componente é como um Windows Presentation Foundation (WPF) Button manipula qualquer evento de mouse que eventualmente será resolvido para o evento intuitivo que todos os botões têm: um Click evento.

A Button classe base (ButtonBase) deriva da Control qual, por sua vez, deriva de e , e UIElementgrande parte da infraestrutura de eventos necessária para o processamento de FrameworkElement entrada de controle está disponível no UIElement nível. Em particular, processa eventos gerais Mouse que manipulam o teste de ocorrências para o cursor do mouse dentro de seus limites e fornece eventos distintos para as ações de botão mais comuns, UIElement como MouseLeftButtonDown. UIElement também fornece um virtual OnMouseLeftButtonDown vazio como o manipulador de classe pré-registrado para MouseLeftButtonDown, e ButtonBase o substitui. Da mesma forma, ButtonBase usa manipuladores de classe para MouseLeftButtonUp. Nas substituições, que são passados os dados de evento, as implementações marcam essa RoutedEventArgs instância como manipulada definindo Handled como true, e esses mesmos dados de evento são o que continua ao longo do restante da rota para outros manipuladores de classe e também para manipuladores de instância ou setters de eventos. Além disso, a OnMouseLeftButtonUp substituição aumentará o Click evento em seguida. O resultado final para a maioria dos ouvintes será que os MouseLeftButtonDown eventos e "desaparecem" e são substituídos por Click, um evento que tem mais significado porque se sabe que esse evento se originou de um botão verdadeiro e MouseLeftButtonUp não de algum pedaço composto do botão ou de algum outro elemento inteiramente.

Resolvendo a supressão de eventos por controles

Às vezes, esse comportamento de supressão de eventos em controles individuais pode interferir em intenções mais gerais da lógica de manipulação de eventos do aplicativo. Por exemplo, se por algum motivo seu aplicativo tivesse um manipulador para MouseLeftButtonDown localizado no elemento raiz do aplicativo, você notaria que qualquer clique do mouse em um botão não invocaria MouseLeftButtonDown ou MouseLeftButtonUp manipularia no nível raiz. Na verdade, o próprio evento foi propagado (novamente, as rotas de evento não são realmente encerradas, mas o sistema de eventos roteados altera seu comportamento de invocação de manipulador depois de ser marcado como manipulado). Quando o evento roteado chegava ao botão, a manipulação de ButtonBase classe marcava o manipulado porque desejava substituir o MouseLeftButtonDownClick evento com mais significado. Portanto, qualquer manipulador padrão MouseLeftButtonDown mais acima da rota não seria invocado. Há duas técnicas que podem ser usadas para garantir que os manipuladores serão invocados nessa circunstância.

A primeira técnica é adicionar deliberadamente o manipulador usando a handledEventsToo assinatura de AddHandler(RoutedEvent, Delegate, Boolean). Uma limitação dessa abordagem é que essa técnica de anexar um manipulador de eventos só é possível por meio do código, não da marcação. A sintaxe simples de especificar o nome do manipulador de eventos como um valor de atributo de evento por meio de XAML (Extensible Application Markup Language) não habilita esse comportamento.

A segunda técnica funciona somente para eventos de entrada, nos quais as versões de túnel e de propagação do evento roteado são emparelhadas. Para esses eventos roteados, é possível adicionar manipuladores ao evento roteado de visualização/túnel equivalente. Esse evento roteado será encapsulado por meio da rota, começando na raiz, para que o código de manipulação de classes do botão não o intercepte, pressupondo que você anexou o manipulador de Visualização no nível de elemento de algum ancestral na árvore de elementos do aplicativo. Se você usar essa abordagem, tome cuidado ao marcar qualquer evento de Visualização como manipulado. Para o exemplo dado com PreviewMouseLeftButtonDown ser manipulado no elemento raiz, se você marcasse o evento como Handled na implementação do manipulador, você realmente suprimiria o Click evento. Geralmente, esse não é um comportamento desejável.

Confira também