Oznaczanie zdarzeń trasowanych zgodnie z obsługą i obsługą klas (WPF .NET)

Chociaż nie ma reguły bezwzględnej, kiedy oznaczyć zdarzenie kierowane jako obsługiwane, rozważ oznaczenie zdarzenia jako obsługiwanego, jeśli kod reaguje na zdarzenie w znaczący sposób. Zdarzenie kierowane oznaczone jako obsługiwane będzie kontynuowane wzdłuż trasy, ale wywoływane są tylko programy obsługi skonfigurowane do reagowania na obsługiwane zdarzenia. Zasadniczo oznaczanie zdarzenia kierowanego jako obsługiwane ogranicza widoczność odbiorników wzdłuż trasy zdarzeń.

Programy obsługi zdarzeń trasowanych mogą być procedurami obsługi wystąpień lub procedurami obsługi klas. Programy obsługi wystąpień obsługują zdarzenia kierowane w obiektach lub elementach XAML. Programy obsługi klas obsługują zdarzenie kierowane na poziomie klasy i są wywoływane przed każdym procedurą obsługi wystąpień odpowiadającą na to samo zdarzenie w dowolnym wystąpieniu klasy. Gdy zdarzenia kierowane są oznaczone jako obsługiwane, są one często oznaczone jako takie w programach obsługi klas. W tym artykule omówiono korzyści i potencjalne pułapki oznaczania zdarzeń trasowanych zgodnie z procedurą obsługi, różne typy kierowanych zdarzeń i procedury obsługi zdarzeń kierowanych oraz pomijanie zdarzeń w złożonych kontrolkach.

Ważne

Dokumentacja przewodnika dla komputerów dla platform .NET 7 i .NET 6 jest w budowie.

Wymagania wstępne

W tym artykule przyjęto założenie, że masz podstawową wiedzę na temat zdarzeń kierowanych i zapoznasz się z omówieniem zdarzeń trasowanych. Aby postępować zgodnie z przykładami w tym artykule, warto zapoznać się z językiem Extensible Application Markup Language (XAML) i wiedzieć, jak pisać aplikacje programu Windows Presentation Foundation (WPF).

Kiedy oznaczyć zdarzenia kierowane jako obsługiwane

Zazwyczaj tylko jedna procedura obsługi powinna zapewniać znaczącą odpowiedź dla każdego zdarzenia kierowanego. Unikaj używania systemu zdarzeń kierowanych w celu zapewnienia znaczącej odpowiedzi w wielu programach obsługi. Definicja tego, co stanowi znaczącą reakcję, jest subiektywna i zależy od twojej aplikacji. Ogólne wskazówki:

  • Znaczące odpowiedzi obejmują ustawianie fokusu, modyfikowanie stanu publicznego, ustawianie właściwości wpływających na reprezentację wizualizacji, podnoszenie nowych zdarzeń i całkowite obsługiwanie zdarzenia.
  • Nieistotne odpowiedzi obejmują modyfikowanie stanu prywatnego bez wpływu wizualnego lub programowego, rejestrowania zdarzeń i badania danych zdarzeń bez reagowania na zdarzenie.

Niektóre kontrolki WPF pomijają zdarzenia na poziomie składnika, które nie wymagają dalszej obsługi, oznaczając je jako obsługiwane. Jeśli chcesz obsłużyć zdarzenie oznaczone jako obsługiwane przez kontrolkę, zobacz Praca nad pomijaniem zdarzeń przez kontrolki.

Aby oznaczyć zdarzenie jako obsłużone, ustaw Handled wartość właściwości w danych zdarzenia na truewartość . Chociaż możliwe jest przywrócenie tej wartości do falsewartości , należy to zrobić rzadko.

Podgląd i bubbling trasowane pary zdarzeń

Pary zdarzeń kierowanych w wersji zapoznawczej i bubbling są specyficzne dla zdarzeń wejściowych. Kilka zdarzeń wejściowych implementuje tunelowanie i bubbling kierowane pary zdarzeń, takich jak PreviewKeyDown i KeyDown. Prefiks Preview oznacza, że zdarzenie bubbling rozpoczyna się po zakończeniu zdarzenia podglądu. Każda para zdarzeń w wersji zapoznawczej i bubbling współudzieli to samo wystąpienie danych zdarzenia.

Programy obsługi zdarzeń kierowanych są wywoływane w kolejności odpowiadającej strategii routingu zdarzenia:

  1. Zdarzenie podglądu jest przesyłane z elementu głównego aplikacji w dół do elementu, który wzbudził zdarzenie kierowane. Podgląd procedur obsługi zdarzeń dołączonych do elementu głównego aplikacji jest wywoływany jako pierwszy, a następnie programy obsługi dołączone do kolejnych zagnieżdżonych elementów.
  2. Po zakończeniu zdarzenia podglądu sparowane zdarzenie bubbling jest przesyłane z elementu, który podniósł zdarzenie kierowane do elementu głównego aplikacji. Programy obsługi zdarzeń bubbling dołączone do tego samego elementu, który wzbudził wywołanie zdarzenia kierowanego najpierw, a następnie programy obsługi dołączone do kolejnych elementów nadrzędnych.

Sparowane zdarzenia w wersji zapoznawczej i bubbling są częścią wewnętrznej implementacji kilku klas WPF, które deklarują i zgłaszają własne zdarzenia kierowane. Bez tej wewnętrznej implementacji na poziomie klasy zdarzenia kierowane w wersji zapoznawczej i bubbling są całkowicie oddzielne i nie będą udostępniać danych zdarzeń — niezależnie od nazewnictwa zdarzeń. Aby uzyskać informacje na temat implementowania zdarzeń kierowanych przez wypychanie lub tunelowanie danych wejściowych w klasie niestandardowej, zobacz Create a custom routed event (Tworzenie niestandardowego zdarzenia trasowanego).

Ponieważ każda para zdarzeń w wersji zapoznawczej i bubbling współudzieli to samo wystąpienie danych zdarzeń, jeśli zdarzenie kierowane w wersji zapoznawczej jest oznaczone jako obsługiwane, będzie również obsługiwane sparowane zdarzenie bubbling. Jeśli zdarzenie rozsyłane jest oznaczone jako obsługiwane, nie będzie miało to wpływu na sparowane zdarzenie podglądu, ponieważ zdarzenie podglądu zostało ukończone. Należy zachować ostrożność podczas oznaczania podglądu i bubbling wejściowych par zdarzeń zgodnie z obsługą. Obsługiwane zdarzenie wejściowe podglądu nie będzie wywoływać żadnych zwykle zarejestrowanych programów obsługi zdarzeń dla pozostałej części trasy tunelowania, a sparowane zdarzenie bubbling nie zostanie podniesione. Obsługiwane zdarzenie wejściowe bubbling nie wywoła żadnych normalnie zarejestrowanych procedur obsługi zdarzeń dla pozostałej części trasy bubbling.

Programy obsługi zdarzeń kierowanych do wystąpień i klas

Programy obsługi zdarzeń trasowanych mogą być procedurami obsługi wystąpień lub procedurami obsługi klas . Programy obsługi klas dla danej klasy są wywoływane przed każdym procedurą obsługi wystąpień odpowiadającą na to samo zdarzenie w dowolnym wystąpieniu tej klasy. Ze względu na to zachowanie, gdy zdarzenia kierowane są oznaczone jako obsługiwane, są one często oznaczone jako takie w programach obsługi klas. Istnieją dwa typy procedur obsługi klas:

  • Programy obsługi zdarzeń klasy statycznej, które są rejestrowane przez wywołanie RegisterClassHandler metody w konstruktorze klasy statycznej.
  • Zastąpić programy obsługi zdarzeń klasy, które są rejestrowane przez zastępowanie metod zdarzeń wirtualnych klasy bazowej. Metody zdarzeń wirtualnych klasy bazowej istnieją głównie dla zdarzeń wejściowych i mają nazwy rozpoczynające się od w polu Nazwa> zdarzenia i Nazwa> zdarzenia OnPreview<.<

Programy obsługi zdarzeń wystąpienia

Programy obsługi wystąpień można dołączać do obiektów lub elementów XAML, wywołując metodę AddHandler bezpośrednio. Zdarzenia kierowane WPF implementują otokę zdarzeń środowiska uruchomieniowego języka wspólnego (CLR), która używa AddHandler metody do dołączania procedur obsługi zdarzeń. Ponieważ składnia atrybutów XAML do dołączania programów obsługi zdarzeń powoduje wywołanie otoki zdarzeń CLR, nawet dołączanie programów obsługi w języku XAML jest rozpoznawane jako AddHandler wywołanie. W przypadku zdarzeń obsługiwanych:

  • Programy obsługi dołączone przy użyciu składni atrybutów AddHandler XAML lub wspólnego podpisu nie są wywoływane.
  • Procedury obsługi dołączone przy użyciu AddHandler(RoutedEvent, Delegate, Boolean) przeciążenia z ustawionym parametrem handledEventsTootrue wywoływane. To przeciążenie jest dostępne w rzadkich przypadkach, gdy konieczne jest reagowanie na obsługiwane zdarzenia. Na przykład niektóre elementy w drzewie elementów oznaczyły zdarzenie jako obsługiwane, ale inne elementy wzdłuż trasy zdarzenia muszą odpowiadać na obsłużone zdarzenie.

Poniższy przykład XAML dodaje niestandardową kontrolkę o nazwie componentWrapper, która opakowuje TextBox nazwę componentTextBox, do nazwanej outerStackPanelStackPanel klasy . Procedura obsługi zdarzeń wystąpienia dla PreviewKeyDown zdarzenia jest dołączana do składni atrybutu componentWrapper XAML. W rezultacie program obsługi wystąpień będzie reagować tylko na nieobsługiwane PreviewKeyDown zdarzenia tunelowania zgłaszane przez program componentTextBox.

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

Konstruktor MainWindow dołącza program obsługi wystąpień dla KeyDown zdarzenia bubbling do componentWrapper przy użyciu UIElement.AddHandler(RoutedEvent, Delegate, Boolean) przeciążenia, z parametrem ustawionym handledEventsToo na true. W rezultacie program obsługi zdarzeń wystąpienia będzie reagować zarówno na nieobsługiwane, jak i obsługiwane zdarzenia.

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

Implementacja kodu jest wyświetlana ComponentWrapper w następnej sekcji.

Programy obsługi zdarzeń klasy statycznej

Programy obsługi zdarzeń klasy statycznej można dołączyć, wywołując metodę RegisterClassHandler w konstruktorze statycznym klasy. Każda klasa w hierarchii klas może zarejestrować własną procedurę obsługi klas statycznych dla każdego zdarzenia kierowanego. W związku z tym może istnieć wiele programów obsługi klas statycznych wywoływanych dla tego samego zdarzenia w dowolnym węźle w trasie zdarzeń. Po utworzeniu trasy zdarzeń dla zdarzenia wszystkie programy obsługi klas statycznych dla każdego węzła są dodawane do trasy zdarzeń. Kolejność wywołania programów obsługi klas statycznych w węźle rozpoczyna się od najbardziej pochodnej procedury obsługi klas statycznych, a następnie statycznych procedur obsługi klas z każdej kolejnej klasy bazowej.

Programy obsługi zdarzeń klasy statycznej zarejestrowane przy użyciu RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) przeciążenia z ustawionym parametrem handledEventsToo będą reagować true zarówno na nieobsługiwane, jak i obsługiwane zdarzenia kierowane.

Programy obsługi klas statycznych są zwykle rejestrowane w celu reagowania tylko na nieobsługiwane zdarzenia. W takim przypadku, jeśli program obsługi klas pochodnych w węźle oznacza zdarzenie jako obsługiwane, programy obsługi klas bazowych dla tego zdarzenia nie będą wywoływane. W tym scenariuszu program obsługi klas bazowych jest skutecznie zastępowany przez program obsługi klas pochodnych. Programy obsługi klas bazowych często przyczyniają się do kontrolowania projektu w obszarach, takich jak wygląd wizualny, logika stanu, obsługa danych wejściowych i obsługa poleceń, dlatego należy zachować ostrożność podczas ich zastępowania. Programy obsługi klas pochodnych, które nie oznaczają zdarzenia jako obsługiwane, uzupełniają programy obsługi klas bazowych zamiast ich zastępowania.

Poniższy przykładowy kod przedstawia hierarchię klas dla kontrolki niestandardowej ComponentWrapper , do którego odwołuje się poprzedni kod XAML. Klasa ComponentWrapper pochodzi z ComponentWrapperBase klasy, która z kolei pochodzi z StackPanel klasy . Metoda RegisterClassHandler używana w konstruktorze statycznym ComponentWrapper klas i ComponentWrapperBase rejestruje program obsługi zdarzeń klasy statycznej dla każdej z tych klas. System zdarzeń WPF wywołuje ComponentWrapper program obsługi klas statycznych przed ComponentWrapperBase programem obsługi klas statycznych.

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

Implementacja za pomocą kodu procedur obsługi zdarzeń klasy zastępowania w tym przykładzie kodu została omówiona w następnej sekcji.

Zastąpić programy obsługi zdarzeń klasy

Niektóre klasy bazowe elementów wizualizacji uwidaczniają puste metody wirtualne w nazwach> zdarzeń> OnPreview<<dla każdego z publicznych zdarzeń wejściowych kierowanych. Na przykład UIElement implementuje OnKeyDown programy obsługi zdarzeń wirtualnych i OnPreviewKeyDown oraz wiele innych. Możesz zastąpić programy obsługi zdarzeń wirtualnych klasy bazowej, aby zaimplementować programy obsługi zdarzeń klasy zastępowania dla klas pochodnych. Można na przykład dodać program obsługi klasy zastąpienia dla DragEnter zdarzenia w dowolnej UIElement klasie pochodnej, przesłaniając metodę wirtualną OnDragEnter . Zastępowanie metod wirtualnych klasy bazowej to prostszy sposób implementowania programów obsługi klas niż rejestrowanie programów obsługi klas w konstruktorze statycznym. W ramach przesłonięcia można zgłaszać zdarzenia, inicjować logikę specyficzną dla klasy, aby zmienić właściwości elementu w wystąpieniach, oznaczyć zdarzenie jako obsługiwane lub wykonać inną logikę obsługi zdarzeń.

W przeciwieństwie do programów obsługi zdarzeń klasy statycznej system zdarzeń WPF wywołuje tylko programy obsługi zdarzeń klasy przesłonięć dla najbardziej pochodnej klasy w hierarchii klas. Najbardziej pochodna klasa w hierarchii klas może następnie użyć podstawowego słowa kluczowego, aby wywołać podstawową implementację metody wirtualnej. W większości przypadków należy wywołać implementację podstawową, niezależnie od tego, czy oznaczysz zdarzenie jako obsługiwane. Należy pominąć wywoływanie implementacji podstawowej tylko wtedy, gdy klasa ma wymóg zastąpienia podstawowej logiki implementacji, jeśli istnieje. Niezależnie od tego, czy implementacja podstawowa jest wywoływana przed lub po zastąpieniu kodu, zależy od charakteru implementacji.

W poprzednim przykładzie kodu metoda wirtualna klasy OnKeyDown bazowej jest zastępowana zarówno w klasach , jak ComponentWrapper i ComponentWrapperBase . Ponieważ system zdarzeń WPF wywołuje ComponentWrapper.OnKeyDown tylko program obsługi zdarzeń klasy zastąpienia, ta procedura obsługi używa base.OnKeyDown(e) do wywoływania ComponentWrapperBase.OnKeyDown programu obsługi zdarzeń klasy zastąpienia, który z kolei używa base.OnKeyDown(e) do wywoływania StackPanel.OnKeyDown metody wirtualnej. Kolejność zdarzeń w poprzednim przykładzie kodu to:

  1. Program obsługi wystąpień dołączony do componentWrapper programu jest wyzwalany przez PreviewKeyDown zdarzenie kierowane.
  2. Program obsługi klas statycznych dołączony do componentWrapper jest wyzwalany przez KeyDown zdarzenie kierowane.
  3. Program obsługi klas statycznych dołączony do componentWrapperBase jest wyzwalany przez KeyDown zdarzenie kierowane.
  4. Procedura obsługi klasy zastąpienia dołączona do componentWrapper programu jest wyzwalana przez KeyDown zdarzenie kierowane.
  5. Procedura obsługi klasy zastąpienia dołączona do componentWrapperBase programu jest wyzwalana przez KeyDown zdarzenie kierowane.
  6. Zdarzenie KeyDown kierowane jest oznaczone jako obsługiwane.
  7. Program obsługi wystąpień dołączony do componentWrapper programu jest wyzwalany przez KeyDown zdarzenie kierowane. Procedura obsługi została zarejestrowana przy użyciu parametru ustawionego handledEventsToo na true.

Pomijanie zdarzeń wejściowych w kontrolkach złożonych

Niektóre złożone kontrolki pomijają zdarzenia wejściowe na poziomie składnika, aby zastąpić je niestandardowym zdarzeniem wysokiego poziomu, które niesie ze sobą więcej informacji lub implikuje bardziej szczegółowe zachowanie. Kontrolka złożona składa się z wielu praktycznych kontrolek lub klas bazowych kontrolek. Klasycznym przykładem jest kontrolka Button , która przekształca różne zdarzenia myszy w Click zdarzenie kierowane. Klasa Button bazowa to ButtonBase, która pośrednio pochodzi z klasy UIElement. Znaczna część infrastruktury zdarzeń potrzebnej do sterowania przetwarzaniem wejściowym jest dostępna na UIElement poziomie. UIElement uwidacznia kilka Mouse zdarzeń, takich jak MouseLeftButtonDown i MouseRightButtonDown. UIElement Implementuje również puste metody OnMouseLeftButtonDown wirtualne i OnMouseRightButtonDown jako wstępnie wyrejestrowane programy obsługi klas. ButtonBase zastępuje te programy obsługi klas, a w ramach programu obsługi przesłonięcia ustawia Handled właściwość na true i zgłasza Click zdarzenie. Wynikiem końcowym dla większości odbiorników jest to, że MouseLeftButtonDown zdarzenia i MouseRightButtonDown są ukryte, a zdarzenie wysokiego poziomu Click jest widoczne.

Obejście pomijania zdarzeń wejściowych

Czasami pomijanie zdarzeń w ramach poszczególnych kontrolek może zakłócać logikę obsługi zdarzeń w aplikacji. Jeśli na przykład aplikacja użyła składni atrybutu XAML w celu dołączenia procedury obsługi dla MouseLeftButtonDown zdarzenia w elemecie głównym XAML, ta procedura obsługi nie zostanie wywołana, ponieważ Button kontrolka oznacza MouseLeftButtonDown zdarzenie jako obsługiwane. Jeśli chcesz, aby elementy w katalogu głównym aplikacji mogły być wywoływane dla obsłużonego zdarzenia kierowanego, możesz wykonać następujące czynności:

  • Dołączanie procedur obsługi przez wywołanie UIElement.AddHandler(RoutedEvent, Delegate, Boolean) metody z parametrem ustawionym handledEventsToo na true. Takie podejście wymaga dołączenia procedury obsługi zdarzeń w kodzie po uzyskaniu odwołania do obiektu dla elementu, do którego zostanie dołączony.

  • Jeśli zdarzenie oznaczone jako obsłużone jest zdarzeniem wejściowym bubbling, dołącz programy obsługi dla sparowanego zdarzenia podglądu, jeśli są dostępne. Jeśli na przykład kontrolka pomija MouseLeftButtonDown zdarzenie, możesz dołączyć program obsługi dla PreviewMouseLeftButtonDown zdarzenia. Takie podejście działa tylko w przypadku par zdarzeń wejściowych w wersji zapoznawczej i bubbling, które udostępniają dane zdarzenia. Należy zachować ostrożność, aby nie oznaczyć go jako obsługiwanego PreviewMouseLeftButtonDown , ponieważ spowoduje to całkowite pominięcie Click zdarzenia.

Aby zapoznać się z przykładem obejścia pomijania zdarzeń wejściowych, zobacz Praca nad pomijaniem zdarzeń przez kontrolki.

Zobacz też