Marcando eventos roteados como manipulados e manipulação de classe (WPF .NET)

Embora não haja uma regra absoluta para quando marcar um evento roteado como manipulado, considere marcar um evento como manipulado se seu código responder ao evento de maneira significativa. Um evento roteado marcado como manipulado continuará ao longo de sua rota, mas somente os manipuladores configurados para responder a eventos manipulados serão chamados. Basicamente, marcar um evento roteado como manipulado limita sua visibilidade para os ouvintes ao longo da rota do evento.

Os manipuladores de eventos roteados podem ser manipuladores de instância ou manipuladores de classe. Os manipuladores de instância manipulam eventos roteados em objetos ou elementos XAML. Os manipuladores de classe manipulam um evento roteado em um nível de classe e são chamados antes de qualquer manipulador de instância que responda ao mesmo evento em qualquer instância da classe. Quando os eventos roteados são marcados como manipulados, eles geralmente são marcados como tal nos manipuladores de classe. Este artigo discute os benefícios e possíveis armadilhas de marcar eventos roteados como manipulados, os diferentes tipos de eventos roteados e manipuladores de eventos roteados e a supressão de eventos em controles compostos.

Importante

A documentação do Guia da Área de Trabalho para .NET 7 e .NET 6 está em construção.

Pré-requisitos

O artigo pressupõe um conhecimento básico de eventos roteados e que você leu Visão geral de eventos roteados. Para seguir os exemplos neste artigo, é útil se você estiver familiarizado com XAML (Extensible Application Markup Language) e souber como escrever aplicativos Windows Presentation Foundation (WPF).

Quando marcar eventos roteados como manipulados

Normalmente, apenas um manipulador deve fornecer uma resposta significativa para cada evento roteado. Evite usar o sistema de eventos roteado para fornecer uma resposta significativa em vários manipuladores. A definição do que constitui uma resposta significativa é subjetiva e depende da sua aplicação. Como orientação geral:

  • As respostas significativas incluem definir o foco, modificar o estado público, definir propriedades que afetam a representação visual, gerar novos eventos e manipular completamente um evento.
  • Respostas insignificantes incluem modificação de estado privado sem impacto visual ou programático, log de eventos e exame de dados de eventos sem responder ao evento.

Alguns controles WPF suprimem eventos no nível do componente que não precisam de manipulação adicional, marcando-os como manipulados. Se você quiser manipular um evento que foi marcado como manipulado por um controle, consulte Trabalhando em torno da supressão de eventos por controles.

Para marcar um evento como manipulado, defina o valor da propriedade em seus dados de Handled evento como true. Embora seja possível reverter esse valor para false, a necessidade de fazê-lo deve ser rara.

Visualizar e borbulhar pares de eventos roteados

Os pares de eventos roteados de visualização e borbulhamento são específicos para eventos de entrada. Vários eventos de entrada implementam um par de eventos roteados de encapsulamento e borbulhamento, como PreviewKeyDown e KeyDown. O Preview prefixo significa que o evento borbulhante é iniciado assim que o evento de visualização é concluído. Cada par de eventos de visualização e borbulhamento compartilha a mesma instância de dados de evento.

Os manipuladores de eventos roteados são chamados em uma ordem que corresponde à estratégia de roteamento de um evento:

  1. O evento de visualização viaja do elemento raiz do aplicativo até o elemento que gerou o evento roteado. Os manipuladores de eventos de visualização anexados ao elemento raiz do aplicativo são chamados primeiro, seguidos pelos manipuladores anexados a elementos aninhados sucessivos.
  2. Após a conclusão do evento de visualização, o evento borbulhante emparelhado viaja do elemento que gerou o evento roteado para o elemento raiz do aplicativo. Os manipuladores de eventos borbulhantes anexados ao mesmo elemento que gerou o evento roteado são chamados primeiro, seguidos pelos manipuladores anexados a elementos pai sucessivos.

Visualização emparelhada e eventos borbulhantes fazem parte da implementação interna de várias classes WPF que declaram e geram seus próprios eventos roteados. Sem essa implementação interna em nível de classe, os eventos roteados de visualização e borbulhamento são totalmente separados e não compartilharão dados de eventos, independentemente da nomeação do evento. Para obter informações sobre como implementar eventos roteados de entrada borbulhante ou encapsulamento em uma classe personalizada, consulte Criar um evento roteado personalizado.

Como cada par de eventos de visualização e borbulhamento compartilha a mesma instância de dados de evento, se um evento roteado de visualização for marcado como manipulado, seu evento borbulhante emparelhado também será manipulado. Se um evento roteado borbulhante for marcado como manipulado, ele não afetará o evento de visualização emparelhado porque o evento de visualização foi concluído. Tenha cuidado ao marcar a visualização e os pares de eventos de entrada borbulhantes como manipulados. Um evento de entrada de visualização manipulado não invocará nenhum manipulador de eventos normalmente registrado para o restante da rota de encapsulamento, e o evento borbulhante emparelhado não será gerado. Um evento de entrada borbulhante manipulado não invocará nenhum manipulador de eventos normalmente registrado para o restante da rota borbulhante.

Manipuladores de eventos roteados de instância e classe

Os manipuladores de eventos roteados podem ser manipuladores de instância ou manipuladores de classe . Os manipuladores de classe para uma determinada classe são chamados antes de qualquer manipulador de instância responder ao mesmo evento em qualquer instância dessa classe. Devido a esse comportamento, quando eventos roteados são marcados como manipulados, eles geralmente são marcados como tal dentro de manipuladores de classe. Há dois tipos de manipuladores de classe:

  • Manipuladores de eventos de classe estática, que são registrados chamando o RegisterClassHandler método dentro de um construtor de classe estática.
  • Substitua manipuladores de eventos de classe, que são registrados substituindo métodos de evento virtual de classe base. Os métodos de evento virtual de classe base existem principalmente para eventos de entrada e têm nomes que começam com On event name e OnPreview<<event name>>.

Manipuladores de eventos de instância

Você pode anexar manipuladores de instância a objetos ou elementos XAML chamando diretamente o AddHandler método. Os eventos roteados do WPF implementam um wrapper de eventos CLR (Common Language Runtime) que usa o AddHandler método para anexar manipuladores de eventos. Como a sintaxe do atributo XAML para anexar manipuladores de eventos resulta em uma chamada para o wrapper de eventos CLR, até mesmo anexar manipuladores em XAML resolve uma AddHandler chamada. Para eventos manipulados:

  • Os manipuladores anexados usando a sintaxe de atributo XAML ou a assinatura comum de AddHandler não são invocados.
  • Os manipuladores anexados usando a AddHandler(RoutedEvent, Delegate, Boolean) sobrecarga com o handledEventsToo parâmetro definido como true são chamados. Essa sobrecarga está disponível para os raros casos em que é necessário responder a eventos manipulados. Por exemplo, algum elemento em uma árvore de elementos marcou um evento como manipulado, mas outros elementos ao longo da rota de eventos precisam responder ao evento manipulado.

O exemplo XAML a seguir adiciona um controle personalizado chamado componentWrapper, que encapsula um nome componentTextBox, a um TextBoxStackPanel nome outerStackPanel. Um manipulador de eventos de instância para o PreviewKeyDown evento é anexado à sintaxe de componentWrapper atributo XAML usando. Como resultado, o manipulador de instância responderá somente a eventos de encapsulamento não manipulados PreviewKeyDown gerados pelo componentTextBox.

<StackPanel Name="outerStackPanel" VerticalAlignment="Center">
    <custom:ComponentWrapper
        x:Name="componentWrapper"
        TextBox.PreviewKeyDown="HandlerInstanceEventInfo"
        HorizontalAlignment="Center">
        <TextBox Name="componentTextBox" Width="200" />
    </custom:ComponentWrapper>
</StackPanel>

O MainWindow construtor anexa um manipulador de instância para o evento borbulhante ao uso da componentWrapperUIElement.AddHandler(RoutedEvent, Delegate, Boolean) sobrecarga, com o handledEventsTooKeyDown parâmetro definido como true. Como resultado, o manipulador de eventos de instância responderá a eventos não manipulados e manipulados.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler.InstanceEventInfo),
            handledEventsToo: true);
    }

    // The handler attached to componentWrapper in XAML.
    public void HandlerInstanceEventInfo(object sender, KeyEventArgs e) => 
        Handler.InstanceEventInfo(sender, e);
}
Partial Public Class MainWindow
    Inherits Window

    Public Sub New()
        InitializeComponent()

        ' Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.
        componentWrapper.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf InstanceEventInfo),
                                      handledEventsToo:=True)
    End Sub

    ' The handler attached to componentWrapper in XAML.
    Public Sub HandlerInstanceEventInfo(sender As Object, e As KeyEventArgs)
        InstanceEventInfo(sender, e)
    End Sub

End Class

A implementação code-behind de ComponentWrapper é mostrada na próxima seção.

Manipuladores de eventos de classe estática

Você pode anexar manipuladores de eventos de classe estática chamando o RegisterClassHandler método no construtor estático de uma classe. Cada classe em uma hierarquia de classe pode registrar seu próprio manipulador de classe estático para cada evento roteado. Como resultado, pode haver vários manipuladores de classe estática invocados para o mesmo evento em qualquer nó específico na rota de eventos. Quando a rota de evento para o evento é construída, todos os manipuladores de classe estática para cada nó são adicionados à rota de evento. A ordem de invocação de manipuladores de classe estática em um nó começa com o manipulador de classe estática mais derivada, seguido por manipuladores de classe estática de cada classe base sucessiva.

Os manipuladores de eventos de classe estática registrados usando a sobrecarga com o handledEventsToo parâmetro definido como true responderão a RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) eventos roteados não manipulados e manipulados.

Os manipuladores de classe estática são normalmente registrados para responder somente a eventos não manipulados. Nesse caso, se um manipulador de classe derivado em um nó marcar um evento como manipulado, os manipuladores de classe base para esse evento não serão chamados. Nesse cenário, o manipulador de classe base é efetivamente substituído pelo manipulador de classe derivada. Os manipuladores de classe base geralmente contribuem para o design de controle em áreas como aparência visual, lógica de estado, manipulação de entrada e manipulação de comando, portanto, tenha cuidado ao substituí-los. Os manipuladores de classe derivada que não marcam um evento como manipulado acabam complementando os manipuladores de classe base em vez de substituí-los.

O exemplo de código a seguir mostra a hierarquia de classe para o ComponentWrapper controle personalizado que foi referenciado no XAML anterior. A classe ComponentWrapper deriva da classe ComponentWrapperBase, que, por sua vez, deriva da classe StackPanel. O RegisterClassHandler método, usado no construtor estático das classes e ComponentWrapperBase , registra um manipulador de eventos de ComponentWrapper classe estática para cada uma dessas classes. O sistema de eventos WPF invoca o ComponentWrapper manipulador de classe estático à frente do ComponentWrapperBase manipulador de classe estático.

public class ComponentWrapper : ComponentWrapperBase
{
    static ComponentWrapper()
    {
        // Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(typeof(ComponentWrapper), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfo_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfo_Override(this, e);

        // Call the base OnKeyDown implementation on ComponentWrapperBase.
        base.OnKeyDown(e);
    }
}

public class ComponentWrapperBase : StackPanel
{
    // Class event handler implemented in the static constructor.
    static ComponentWrapperBase()
    {
        EventManager.RegisterClassHandler(typeof(ComponentWrapperBase), KeyDownEvent, 
            new RoutedEventHandler(Handler.ClassEventInfoBase_Static));
    }

    // Class event handler that overrides a base class virtual method.
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Handler.ClassEventInfoBase_Override(this, e);

        e.Handled = true;
        Debug.WriteLine("The KeyDown routed event is marked as handled.");

        // Call the base OnKeyDown implementation on StackPanel.
        base.OnKeyDown(e);
    }
}
Public Class ComponentWrapper
    Inherits ComponentWrapperBase

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapper), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfo_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfo_Override(Me, e)

        ' Call the base OnKeyDown implementation on ComponentWrapperBase.
        MyBase.OnKeyDown(e)
    End Sub

End Class

Public Class ComponentWrapperBase
    Inherits StackPanel

    Shared Sub New()
        ' Class event handler implemented in the static constructor.
        EventManager.RegisterClassHandler(GetType(ComponentWrapperBase), KeyDownEvent,
                                          New RoutedEventHandler(AddressOf ClassEventInfoBase_Static))
    End Sub

    ' Class event handler that overrides a base class virtual method.
    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
        ClassEventInfoBase_Override(Me, e)

        e.Handled = True
        Debug.WriteLine("The KeyDown event is marked as handled.")

        ' Call the base OnKeyDown implementation on StackPanel.
        MyBase.OnKeyDown(e)
    End Sub

End Class

A implementação code-behind dos manipuladores de eventos de classe de substituição neste exemplo de código é discutida na próxima seção.

Substituir manipuladores de eventos de classe

Algumas classes base de elementos visuais expõem métodos virtuais On event name> e OnPreview><< para cada um de seus eventos públicos de entrada roteados. Por exemplo, UIElement implementa os OnKeyDown manipuladores de eventos virtuais e OnPreviewKeyDown muitos outros. Você pode substituir manipuladores de eventos virtuais de classe base para implementar manipuladores de eventos de classe de substituição para suas classes derivadas. Por exemplo, você pode adicionar um manipulador de classe de substituição para o evento em qualquer UIElement classe derivada substituindo o DragEnterOnDragEnter método virtual. Substituir métodos virtuais de classe base é uma maneira mais simples de implementar manipuladores de classe do que registrar manipuladores de classe em um construtor estático. Dentro da substituição, você pode gerar eventos, iniciar lógica específica de classe para alterar propriedades de elemento em instâncias, marcar o evento como manipulado ou executar outra lógica de manipulação de eventos.

Ao contrário dos manipuladores de eventos de classe estática, o sistema de eventos WPF só invoca manipuladores de eventos de classe de substituição para a classe mais derivada em uma hierarquia de classe. A classe mais derivada em uma hierarquia de classe, pode então usar a palavra-chave base para chamar a implementação base do método virtual. Na maioria dos casos, você deve chamar a implementação base, independentemente de marcar um evento como manipulado. Você só deve omitir a chamada da implementação base se sua classe tiver um requisito para substituir a lógica de implementação base, se houver. Se você chamar a implementação base antes ou depois do código de substituição, depende da natureza da implementação.

No exemplo de código anterior, o método virtual de classe OnKeyDown base é substituído nas ComponentWrapper classes e ComponentWrapperBase . Como o sistema de eventos WPF invoca apenas o manipulador de eventos de classe de substituição, esse manipulador usa para chamar o manipulador de eventos de classe de substituição, que, por sua vez, usa base.OnKeyDown(e) para chamar o ComponentWrapperBase.OnKeyDownComponentWrapper.OnKeyDownStackPanel.OnKeyDown método virtual.base.OnKeyDown(e) A ordem dos eventos no exemplo de código anterior é:

  1. O manipulador de instância anexado PreviewKeyDown é componentWrapper acionado pelo evento roteado.
  2. O manipulador de classe estático anexado KeyDown ao componentWrapper é acionado pelo evento roteado.
  3. O manipulador de classe estático anexado KeyDown ao componentWrapperBase é acionado pelo evento roteado.
  4. O manipulador de classe de substituição anexado KeyDown ao componentWrapper é acionado pelo evento roteado.
  5. O manipulador de classe de substituição anexado KeyDown ao componentWrapperBase é acionado pelo evento roteado.
  6. O KeyDown evento roteado é marcado como manipulado.
  7. O manipulador de instância anexado KeyDown é componentWrapper acionado pelo evento roteado. O manipulador foi registrado com o handledEventsToo parâmetro definido como true.

Supressão de eventos de entrada em controles compostos

Alguns controles compostos suprimem eventos de entrada no nível do componente para substituí-los por um evento de alto nível personalizado que carrega mais informações ou implica um comportamento mais específico. Um controle composto é, por definição, composto de múltiplos controles práticos ou classes de base de controle. Um exemplo clássico é o Button controle, que transforma vários eventos do mouse em um Click evento roteado. A Button classe base é ButtonBase, que indiretamente deriva de UIElement. Grande parte da infraestrutura de eventos necessária para o processamento de entrada de controle está disponível no UIElement nível. UIElement expõe vários Mouse eventos como MouseLeftButtonDown e MouseRightButtonDown. UIElement também implementa os métodos OnMouseLeftButtonDown virtuais vazios e OnMouseRightButtonDown como manipuladores de classe pré-registrados. ButtonBase substitui esses manipuladores de classe e, dentro do manipulador de substituição, define a Handled propriedade como true e gera um Click evento. O resultado final para a maioria dos ouvintes é que os MouseLeftButtonDown eventos e estão ocultos e MouseRightButtonDown o evento de alto nível Click está visível.

Trabalhando em torno da supressão de eventos de entrada

Às vezes, a supressão de eventos em controles individuais pode interferir na lógica de manipulação de eventos em seu aplicativo. Por exemplo, se seu aplicativo usou a sintaxe de atributo XAML para anexar um manipulador para o evento no elemento raiz XAML, esse manipulador não será chamado porque o controle marca o MouseLeftButtonDownButtonMouseLeftButtonDown evento como manipulado. Se quiser que os elementos em direção à raiz do seu aplicativo sejam chamados para um evento roteado manipulado, você pode:

  • Anexe manipuladores chamando o método com o UIElement.AddHandler(RoutedEvent, Delegate, Boolean)handledEventsToo parâmetro definido como true. Essa abordagem requer anexar o manipulador de eventos em code-behind, depois de obter uma referência de objeto para o elemento ao qual ele será anexado.

  • Se o evento marcado como manipulado for um evento de entrada borbulhante, anexe manipuladores para o evento de visualização emparelhado, se disponível. Por exemplo, se um controle suprime o evento, você pode anexar um manipulador para o MouseLeftButtonDownPreviewMouseLeftButtonDown evento. Essa abordagem só funciona para pares de eventos de entrada de visualização e borbulhamento, que compartilham dados de eventos. Tenha cuidado para não marcar o como manipulado, pois isso suprimiria completamente o PreviewMouseLeftButtonDownClick evento.

Para obter um exemplo de como contornar a supressão de eventos de entrada, consulte Trabalhando em torno da supressão de eventos por controles.

Confira também