Compartilhar via


Marcação de 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 o 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 invocados. 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 lidam com eventos roteados em objetos ou elementos XAML. Os manipuladores de classe manipulam um evento roteado em um nível de classe e são invocados 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 as 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.

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 WPF (Windows Presentation Foundation).

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 roteados 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 modificar o estado privado sem impacto visual ou programático, log de eventos e examinar dados de eventos sem responder ao evento.

Alguns controles do WPF suprimem eventos no nível do componente que não precisam de tratamento 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 Handled propriedade em seus dados de evento como true. Embora seja possível reverter esse valor para false, a necessidade de fazer isso deve ser rara.

Pares de eventos roteados de visualização e propagação

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

Os manipuladores de eventos roteados são invocados 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 invocados primeiro, seguidos pelos manipuladores anexados a elementos aninhados sucessivos.
  2. Após a conclusão do evento de visualização, o evento de propagação emparelhado viaja do elemento que gerou o evento roteado para o elemento raiz do aplicativo. Os manipuladores de eventos de propagação anexados ao mesmo elemento que gerou o evento roteado são invocados primeiro, seguidos pelos manipuladores anexados a elementos pai sucessivos.

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

Como cada par de eventos de visualização e propagação compartilha a mesma instância de dados de evento, se um evento roteado de visualização for marcado como manipulado, seu evento de propagação emparelhado também será manipulado. Se um evento roteado de propagação 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 pares de eventos de entrada de visualização e propagação 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 túnel, e o evento de propagação emparelhado não será gerado. Um evento de entrada de propagação manipulado não invocará nenhum manipulador de eventos normalmente registrado para o restante da rota de propagação.

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 invocados antes de qualquer manipulador de instância que responda ao mesmo evento em qualquer instância dessa classe. Devido a esse comportamento, quando os eventos roteados são marcados como manipulados, eles geralmente são marcados como tal nos 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 os 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 o nome> do evento On<e o nome> do evento OnPreview<.

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 de atributo XAML para anexar manipuladores de eventos resulta em uma chamada para o wrapper de eventos CLR, até mesmo anexar manipuladores em XAML é resolvido para 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 invocados. 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 mais adiante na rota do evento precisam responder ao evento manipulado.

O exemplo XAML a seguir adiciona um controle personalizado chamado componentWrapper, que encapsula um TextBox , nomeado componentTextBoxa um StackPanel .outerStackPanel Um manipulador de eventos de instância para o PreviewKeyDown evento é anexado à sintaxe componentWrapper de atributo XAML using. Como resultado, o manipulador de instâncias responderá apenas a eventos de encapsulamento sem tratamento 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 KeyDown evento de propagação ao componentWrapper usando a UIElement.AddHandler(RoutedEvent, Delegate, Boolean) sobrecarga, com o handledEventsToo parâmetro definido como true. Como resultado, o manipulador de eventos da instância responderá a eventos não tratados 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 é mostrada ComponentWrapper 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 classes pode registrar seu próprio manipulador de classes estáticas para cada evento roteado. Como resultado, pode haver vários manipuladores de classe estática invocados para o mesmo evento em qualquer nó na rota do evento. Quando a rota de eventos para o evento é construída, todos os manipuladores de classe estática para cada nó são adicionados à rota de eventos. A ordem de chamada de manipuladores de classe estática em um nó começa com o manipulador de classe estática mais derivado, seguido por manipuladores de classe estática de cada classe base sucessiva.

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

Os manipuladores de classe estáticos normalmente são registrados para responder apenas a eventos sem tratamento. Nesse caso, se um manipulador de classe derivada em um nó marcar um evento como manipulado, os manipuladores de classe base para esse evento não serão invocados. 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 andComponentWrapperBase, registra um manipulador de ComponentWrapper eventos de classe estática para cada uma dessas classes. O sistema de eventos do WPF invoca o ComponentWrapper manipulador de classe estática antes do ComponentWrapperBase manipulador de classe estática.

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 elemento visual expõem métodos virtuais vazios de nome> de evento On<e nome de evento> OnPreview<para cada um de seus eventos de entrada roteados públicos. 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 DragEnter substituindo o OnDragEnter 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. Na substituição, você pode gerar eventos, iniciar uma lógica específica da classe para alterar as propriedades do 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 do WPF invoca apenas manipuladores de eventos de classe de substituição para a classe mais derivada em uma hierarquia de classes. A classe mais derivada em uma hierarquia de classes pode 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ê chama 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 da classe OnKeyDown base é substituído nas ComponentWrapper classes e ComponentWrapperBase . Como o sistema de eventos do WPF invoca apenas o manipulador de eventos de ComponentWrapper.OnKeyDown classe de substituição, esse manipulador usa base.OnKeyDown(e) para chamar o manipulador de eventos de ComponentWrapperBase.OnKeyDown classe de substituição, que, por sua vez, usa base.OnKeyDown(e) para chamar o StackPanel.OnKeyDown método virtual. A ordem dos eventos no exemplo de código anterior é:

  1. O manipulador de instâncias anexado PreviewKeyDown a componentWrapper é acionado pelo evento roteado.
  2. O manipulador de classe estático anexado é componentWrapper disparado KeyDown pelo evento roteado.
  3. O manipulador de classe estático anexado é componentWrapperBase disparado KeyDown pelo evento roteado.
  4. O manipulador de classe de substituição anexado é componentWrapper disparado KeyDown pelo evento roteado.
  5. O manipulador de classe de substituição anexado é componentWrapperBase disparado KeyDown pelo evento roteado.
  6. O KeyDown evento roteado é marcado como manipulado.
  7. O manipulador de instâncias anexado KeyDown a 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 vários controles práticos ou classes base de controle. Um exemplo clássico é o controle, que transforma Button vários eventos do mouse em um Click evento roteado. A Button classe base é ButtonBase, que deriva indiretamente de UIElement. Grande parte da infra-estrutura 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 e true gera um Click evento. O resultado final para a maioria dos ouvintes é que os MouseLeftButtonDown eventos and MouseRightButtonDown estão ocultos e o evento de alto nível Click é 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 o aplicativo usou a sintaxe de atributo XAML para anexar um manipulador para o MouseLeftButtonDown evento no elemento raiz XAML, esse manipulador não será invocado porque o Button controle marca o MouseLeftButtonDown evento como manipulado. Se você quiser que os elementos em direção à raiz do seu aplicativo sejam invocados para um evento roteado manipulado, você pode:

  • Anexe manipuladores chamando o UIElement.AddHandler(RoutedEvent, Delegate, Boolean) método com o handledEventsToo parâmetro definido como true. Essa abordagem requer anexar o manipulador de eventos no 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 de propagação, anexe manipuladores para o evento de visualização emparelhado, se disponível. Por exemplo, se um controle suprimir o MouseLeftButtonDown evento, você poderá anexar um manipulador para o PreviewMouseLeftButtonDown evento. Essa abordagem só funciona para pares de eventos de entrada de visualização e propagação, que compartilham dados de eventos. Tenha cuidado para não marcar o como manipulado, PreviewMouseLeftButtonDown pois isso suprimiria completamente o Click evento.

Para obter um exemplo de como contornar a supressão de eventos de entrada, consulte Contornar a supressão de eventos por controles.

Confira também