Marcação de eventos roteados como manipulados e manipulação de classe
Os manipuladores de um evento roteado podem marcar o evento manipulado nos dados do evento. Lidar com o evento efetivamente irá encurtar a rota. A manipulação de classes é um conceito de programação suportado por eventos roteados. Um manipulador de classe tem a oportunidade de manipular um determinado evento roteado em um nível de classe com um manipulador que é invocado antes de qualquer manipulador de instância em qualquer instância da classe.
Pré-requisitos
Este tópico desenvolve os conceitos introduzidos no Routed Events Overview.
Quando marcar eventos como processados
Quando você define o valor da propriedade Handled para true
nos dados do evento para um evento roteado, isso é chamado de "marcação do evento manipulado". Não há uma regra absoluta para quando você deve marcar eventos roteados como manipulados, seja como um autor de aplicativo ou como um autor de controle que responde a eventos roteados existentes ou implementa novos eventos roteados. Na maioria das vezes, o conceito de "manipulado", conforme utilizado nos dados de evento roteado, deve ser usado como um protocolo restrito para as respostas do seu próprio aplicativo aos diversos eventos roteados expostos nas APIs do WPF, bem como para quaisquer eventos roteados personalizados. Outra maneira de considerar a questão "tratada" é que deve-se geralmente marcar um evento encaminhado como tratado se o seu código respondeu a este evento de maneira significativa e relativamente completa. Normalmente, não deve haver mais de uma resposta significativa que exija implementações de manipulador separadas para qualquer ocorrência de evento roteado único. Se forem necessárias mais respostas, o código necessário deve ser implementado por meio da lógica do aplicativo encadeada em um único manipulador, em vez de usar o sistema de eventos roteado para encaminhamento. O conceito do que é "significativo" também é subjetivo e depende da sua aplicação ou código. Como orientação geral, alguns exemplos de "resposta significativa" incluem: definir o foco, modificar o estado público, definir propriedades que afetam a representação visual e gerar outros novos eventos. Exemplos de respostas não significativas incluem: modificar o estado privado (sem impacto visual ou representação programática), registrar eventos ou examinar argumentos de um evento e optar por não respondê-lo.
O comportamento do sistema de eventos roteado 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 AddHandler não são invocados em resposta a um evento roteado onde os dados do evento já estão marcados como manipulados. Você deve fazer o esforço adicional de adicionar um manipulador com a versão do parâmetro handledEventsToo
(AddHandler(RoutedEvent, Delegate, Boolean)) para tratar eventos roteados que são considerados como já tratados por participantes anteriores na rota do evento.
Em algumas circunstâncias, os próprios controles marcam determinados 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 de controle, e o evento não precisa de manipulação adicional. Normalmente, isto é feito adicionando um manipulador de classes para um evento ou substituindo um dos virtuais do manipulador de classes existentes numa classe base. Você ainda pode contornar essa manipulação de eventos, se necessário; consulte Trabalhando em torno da supressão de eventos por controles mais adiante neste tópico.
Eventos de "visualização" (tunelamento) vs. eventos borbulhantes e manipulação de eventos
Os eventos roteados de visualização são eventos que seguem uma rota de tunelamento através da árvore de elementos. A "Visualização" expressa na convenção de nomenclatura é indicativa do princípio geral para eventos de entrada, em que os eventos roteados de visualização (tunelamento) são gerados antes do evento roteado equivalente de propagação para cima. Além disso, os eventos roteados de entrada que têm um par de tunelamento e borbulhamento têm uma lógica de manipulação distinta. Se o evento roteado de tunelamento/visualização for considerado processado por um ouvinte de eventos, o evento roteado de propagação será considerado processado mesmo antes de qualquer ouvinte do evento roteado de propagação recebê-lo. Os eventos roteados de tunelamento e borbulhamento são eventos tecnicamente separados, mas eles deliberadamente compartilham a mesma instância de dados de evento para habilitar esse comportamento.
A ligação entre os eventos roteados de tunneling e bubbling é realizada pela implementação interna de como qualquer classe WPF específica gera os seus próprios eventos roteados declarados, e isto também se aplica aos eventos roteados de entrada emparelhados. Mas, a menos que essa implementação de nível de classe exista, não há conexão entre um evento roteado de tunelamento e um evento roteado borbulhante que compartilha o esquema de nomenclatura: sem essa implementação, eles seriam dois eventos roteados totalmente separados e não seriam gerados em sequência ou compartilhariam dados de eventos.
Para obter mais informações sobre como implementar pares de eventos roteados de entrada de túnel/bolha em uma classe personalizada, consulte Criar um evento roteado personalizado.
Manipuladores de classe e manipuladores de instância
Os eventos roteados consideram dois tipos diferentes de ouvintes para o evento: ouvintes de classe e ouvintes de instância. Os ouvintes de classe existem porque os tipos chamaram uma API de EventManager específica,RegisterClassHandler, em seu construtor estático, 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 a que um ou mais manipuladores foram anexados para esse evento encaminhado através de uma chamada para AddHandler. Os eventos roteados WPF existentes fazem chamadas para AddHandler como parte do wrapper de eventos CLR (Common Language Runtime), adicionando as implementações{} e removendo as implementações{} do evento, o que também habilita o mecanismo simples do XAML de anexar manipuladores de eventos através da sintaxe de atributos. Portanto, mesmo o simples uso de XAML acaba equivalendo a uma chamada AddHandler.
Os elementos dentro da árvore visual são verificados quanto a implementações de manipuladores registrados. Os manipuladores são potencialmente invocados em toda a rota, na ordem inerente ao tipo da estratégia de roteamento para esse evento roteado. Por exemplo, eventos roteados borbulhantes primeiro invocarão os manipuladores que estão anexados ao mesmo elemento que gerou o evento roteado. Em seguida, o evento propagado propaga-se para o próximo elemento pai e assim por diante até que o elemento raiz da aplicação seja alcançado.
Da perspetiva do elemento raiz em uma rota borbulhante, se a manipulação de classe ou qualquer elemento mais próximo da origem do evento roteado invocar manipuladores que marcam os argumentos de evento como sendo manipulados, os manipuladores nos elementos raiz não serão invocados e a rota do evento será efetivamente encurtada antes de alcançar esse elemento raiz. No entanto, a rota não é completamente interrompida, porque os manipuladores podem ser adicionados usando uma condicional especial que eles ainda devem ser invocados, mesmo que um manipulador de classe ou manipulador de instância tenha marcado o evento roteado como manipulado. Isso é explicado em Adicionar manipuladores de instância que são ativados mesmo quando os eventos estão marcados como manipulados, mais adiante neste tópico.
Em um nível mais profundo do que a rota de evento, também há potencialmente vários manipuladores de classe atuando em qualquer instância específica 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 seu próprio manipulador de classe para cada evento roteado. Cada manipulador de classe é adicionado a um armazenamento interno e, quando a rota de evento para um aplicativo é construída, os manipuladores de classe são todos adicionados à rota de evento. Os manipuladores de classe são adicionados à rota de modo que o manipulador de classe mais derivado seja invocado primeiro, e os manipuladores de classe de cada classe base sucessiva sejam invocados em seguida. Geralmente, os manipuladores de classe não são registrados de forma que também respondam a eventos roteados que já foram marcados como manipulados. Portanto, esse mecanismo de manipulação de classe permite uma das duas opções:
As classes derivadas podem complementar a manipulação de classe herdada da classe base adicionando um manipulador que não marca o evento roteado manipulado, porque o manipulador de classe base será invocado algum tempo depois do manipulador de classe derivada.
As classes derivadas podem substituir a manipulação de classe da classe base adicionando um manipulador de classe que marca o evento roteado manipulado. Você deve ser cauteloso com essa abordagem, porque ela potencialmente alterará o design de controle de 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 em uma rota de evento, os ouvintes de classe têm a oportunidade de responder ao evento roteado antes que qualquer ouvinte de instância no elemento possa. Por esse motivo, os manipuladores de classe às vezes são usados para suprimir eventos roteados que uma implementação de classe de controle específica não deseja propagar ainda mais, ou para fornecer tratamento especial desse evento roteado que é um recurso da classe. Por exemplo, uma classe pode gerar seu próprio evento específico de classe que contém mais detalhes sobre o que alguma 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 assinatura RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) que registra manipuladores de classe para invocar mesmo quando os eventos roteados são marcados como manipulados.
Métodos Virtuais do Manipulador de Classe
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 classe 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 DragEnter em qualquer classe derivada UIElement substituindo o método virtual OnDragEnter. Dentro da substituição, você pode manipular o evento roteado, gerar outros eventos, iniciar lógica específica de classe que pode alterar as propriedades do elemento em instâncias ou qualquer combinação dessas ações. Geralmente, deves chamar a implementação base em tais casos, mesmo que marques o evento como manipulado. Chamar a implementação base é altamente recomendado porque o método virtual está na classe base. O padrão virtual protegido padrão de chamar as implementações base de cada virtual essencialmente substitui e paraleliza um mecanismo semelhante que é nativo para manipulação de classe de evento roteado, pelo qual manipuladores de classe para todas as classes em uma hierarquia de classe são chamados em qualquer instância específica, começando com o manipulador de classe mais derivado e continuando com o manipulador de classe base. Você só deve omitir a chamada de implementação base se sua classe tiver um requisito deliberado para alterar a lógica de manipulação da classe base. Se decidir chamar a implementação base antes ou depois do seu código de substituição, isso dependerá da natureza da sua implementação.
Manipulação de classe de evento de entrada
Os métodos virtuais do manipulador de classe são todos registrados de forma que só são invocados nos casos em que os dados de eventos compartilhados ainda não estão marcados como manipulados. Além disso, exclusivamente para os eventos de entrada, as versões de tunelamento e borbulhamento normalmente são geradas em sequência e compartilham os dados dos eventos. Isso implica que, para um determinado par de manipuladores de classe de eventos de entrada, em que um é a versão de encapsulamento e o outro é a versão borbulhante, talvez você não queira marcar o evento manipulado imediatamente. Se você implementar o método virtual de manipulação de classe de encapsulamento para marcar o evento manipulado, isso impedirá que o manipulador de classe borbulhante seja invocado (bem como impedirá que quaisquer manipuladores de instância normalmente registrados para o evento de encapsulamento ou borbulhante sejam invocados).
Quando a manipulação de classes em um nó estiver concluída, são considerados os ouvintes da instância.
Adicionar manipuladores de instâncias que são invocados mesmo quando os eventos são marcados como manipulados
O método AddHandler fornece uma sobrecarga específica que permite adicionar manipuladores que serão invocados 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. Geralmente, os manipuladores podem ser gravados para ajustar todas as áreas do código do aplicativo que podem ser influenciadas por um evento, independentemente de onde ele foi manipulado em uma árvore de elementos, mesmo que vários resultados finais sejam desejados. Além disso, normalmente há realmente apenas um elemento que precisa responder a esse evento, e a lógica de aplicativo apropriada já aconteceu. Mas a sobrecarga de handledEventsToo
está disponível para os casos excecionais em que algum outro elemento numa árvore de elementos ou numa composição de controlo já tenha marcado um evento como tratado, mas outros elementos, mais altos ou mais baixos na árvore de elementos (dependendo da rota), ainda desejam ver os seus próprios manipuladores invocados.
Quando marcar eventos manipulados como não tratados
Geralmente, os eventos enrutados marcados como manipulados não devem ser marcados como não manipulados (Handled retornados a false
) mesmo por manipuladores que atuam em handledEventsToo
. No entanto, alguns eventos de entrada têm representações de eventos de alto e 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 ouve um evento-chave de alto nível, como TextInput, enquanto um elemento pai ouve um evento de baixo nível, como KeyDown. Se o elemento pai manipular o evento de baixo nível, o evento de nível superior poderá ser suprimido mesmo no elemento filho que, intuitivamente, deve ter a primeira oportunidade de manipular o evento.
Em situações como estas, pode ser necessário adicionar manipuladores tanto aos elementos pai quanto aos elementos filho para o 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 definiria como não manipulado novamente, para que elementos adicionais na árvore (bem como o evento de alto nível) possam ter a oportunidade de responder. Esta situação deve ser bastante rara.
Supressão deliberada de eventos de entrada para composição de controle
O cenário principal em que a manipulação de classe de eventos roteados é usada é para eventos de entrada e controles compostos. Um controle composto é, por definição, composto de vários controles práticos ou classes base de controle. Muitas vezes, o autor do controle deseja amalgamar todos os possíveis eventos de entrada que cada um dos subcomponentes pode gerar, a fim de relatar todo o controle como a fonte de evento singular. Em alguns casos, o autor do controle pode querer suprimir totalmente os eventos dos componentes ou substituir um evento definido por componente que carregue mais informações ou implique um comportamento mais específico. O exemplo canônico imediatamente visível para qualquer autor de componente é como um Button no Windows Presentation Foundation (WPF) manipula qualquer evento de mouse que eventualmente se transforma no evento intuitivo que todos os botões têm: um evento Click.
A classe base Button (ButtonBase) deriva de Control que, por sua vez, deriva de FrameworkElement e UIElement, e grande parte da infraestrutura de eventos necessária para o processamento de entrada de controle está disponível no nível UIElement. Em especial, o UIElement processa eventos gerais de Mouse que gerem o teste de colisão para o cursor do mouse dentro dos seus limites e fornece eventos distintos para as ações de botão mais comuns, como MouseLeftButtonDown.
UIElement também fornece um OnMouseLeftButtonDown virtual vazio como o manipulador de classe pré-registrado para MouseLeftButtonDowne ButtonBase o substitui. Da mesma forma, ButtonBase usa manipuladores de classe para MouseLeftButtonUp. Nas substituições, que são passadas os dados do evento, as implementações marcam essa instância RoutedEventArgs 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 substituição de OnMouseLeftButtonUp irá acionar o evento Click a seguir. O resultado final para a maioria dos ouvintes será que os eventos MouseLeftButtonDown e MouseLeftButtonUp "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 não de algum pedaço composto do botão ou de algum outro elemento inteiramente.
Trabalhando em torno da supressão de eventos por controles
Às vezes, esse comportamento de supressão de eventos dentro de controles individuais pode interferir com algumas intenções mais gerais da lógica de manipulação de eventos para seu 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 manipuladores MouseLeftButtonDown ou MouseLeftButtonUp no nível raiz. O evento em si realmente propagou-se (novamente, as rotas de eventos não são realmente encerradas, mas o sistema de eventos roteados altera o seu comportamento de invocação do manipulador depois de ser marcado como tratado). Quando o evento roteado atingiu o botão, a manipulação de classe ButtonBase marcou o MouseLeftButtonDown manipulado porque desejava substituir o evento Click com mais significado. Portanto, qualquer manipulador de MouseLeftButtonDown padrão mais acima na rota não seria invocado. Há duas técnicas que você pode usar para garantir que seus manipuladores sejam invocados nessa circunstância.
A primeira técnica é adicionar deliberadamente o manipulador usando a assinatura handledEventsToo
de AddHandler(RoutedEvent, Delegate, Boolean). Uma limitação dessa abordagem é que essa técnica para anexar um manipulador de eventos só é possível a partir 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 apenas para eventos de entrada, onde as versões de tunelamento e borbulhamento do evento roteado são emparelhadas. Para esses eventos roteados, você pode adicionar manipuladores ao evento roteado equivalente de visualização/encapsulamento. Este evento roteado percorrerá a rota a partir da raiz, de modo que o código de manipulação da classe botão não intercetaria, presumindo que você colocou o manipulador Preview em algum nível de um elemento antecessor na árvore de elementos do aplicativo. Se usar esta abordagem, tenha cautela ao indicar qualquer evento de pré-visualização como tratado. Para o exemplo dado com PreviewMouseLeftButtonDown sendo manipulado no elemento raiz, se você marcasse o evento como Handled na implementação do manipulador, na verdade suprimiria o evento Click. Isso normalmente não é um comportamento desejável.
Ver também
.NET Desktop feedback