Udostępnij przez


Oznaczanie zdarzeń trasowanych jako obsłużonych oraz obsługa klas

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ść dla odbiorników wzdłuż trasy zdarzenia.

Obsługiwacze zdarzeń przekierowywanych mogą być obsługiwaczami instancji lub obsługiwaczami klas. Programy obsługi wystąpień obsługują zdarzenia kierowane w obiektach lub elementach XAML. Obsługiwacze klas obsługują zdarzenie routowane na poziomie klasy i są wywoływane przed jakimkolwiek obsługiwaczem instancji odpowiadającym 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 jako obsłużone, różne typy zdarzeń trasowanych i obsługi zdarzeń trasowanych oraz pomijanie zdarzeń w kontrolkach złożonych.

Wymagania wstępne

Artykuł zakłada podstawową znajomość zdarzeń trasowanych i że przeczytałeś Przegląd 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 jeden obsługujący powinien zapewniać istotną reakcję 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 skupienia uwagi, modyfikowanie stanu publicznego, ustawianie właściwości wpływających na reprezentację wizualną, wywoływanie 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 Ominięcie tłumienia zdarzeń przez kontrolki.

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

Podglądowe i rozchodzące się pary zdarzeń trasowanych

Pary zdarzeń kierowanych w wersji zapoznawczej i bubbling są specyficzne dla zdarzeń wejściowych. Kilka zdarzeń wejściowych implementuje parę zdarzeń typu tunelowanie i propagacja, takich jak PreviewKeyDown i KeyDown. Prefiks Preview oznacza, że zdarzenie bubbling rozpoczyna się po zakończeniu zdarzenia podglądu. Każda para zdarzeń w fazie podglądu i propagacji dzieli ten sam zestaw danych zdarzenia.

Obsługiwacze zdarzeń routowanych są wywoływane w kolejności odpowiadającej strategii routingu zdarzenia.

  1. Zdarzenie przeglądowe jest przesyłane z elementu głównego aplikacji w dół do elementu, który wzbudził zdarzenie kierowane. Procedury obsługi zdarzeń podglądu dołączone do elementu głównego aplikacji są wywoływane jako pierwsze, a następnie procedury 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 podglądowe i bubblingowe zdarzenia są częścią wewnętrznej implementacji kilku klas WPF, które deklarują i zgłaszają własne routowane zdarzenia. 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żde wstępne i bubbling routowane zdarzenie dzieli to samo wystąpienie danych zdarzeń, jeśli wstępne routowane zdarzenie jest oznaczone jako obsługiwane, to powiązane zdarzenie bubbling również zostanie obsłużone. 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.

Procedury obsługi zdarzeń trasowanych dla 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 <> 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ą osłonę zdarzenia środowiska uruchomieniowego języka wspólnego (CLR), która używa metody AddHandler do obsługi zdarzeń. Ponieważ składnia atrybutów XAML do przypisywania obsługi zdarzeń powoduje wywołanie opakowania zdarzeń CLR, nawet przypisywanie obsługi w języku XAML jest rozpoznawane jako AddHandler wywołanie. W przypadku obsługiwanych zdarzeń:

  • Programy obsługi, które są dołączone przy użyciu składni atrybutów XAML lub wspólnego podpisu AddHandler, nie są wywoływane.
  • Procedury obsługi, które są dołączone przy użyciu przeciążenia, gdzie parametr AddHandler(RoutedEvent, Delegate, Boolean) jest ustawiony na handledEventsToo, są 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 o nazwie componentTextBox, do StackPanel o nazwie outerStackPanel. Procedura obsługi zdarzeń wystąpienia dla zdarzenia PreviewKeyDown jest dołączana do componentWrapper za pomocą składni atrybutu XAML. W rezultacie obsługiwacz instancji będzie reagować tylko na nieobsługiwane PreviewKeyDown zdarzenia tunelowania zgłaszane przez 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 bąbelkowania do componentWrapper z przeciążeniem UIElement.AddHandler(RoutedEvent, Delegate, Boolean), z parametrem handledEventsToo ustawionym na true. W rezultacie program obsługujący zdarzenia 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 w tle ComponentWrapper jest pokazana 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łasny statyczny handler klasy dla każdego zdarzenia trasowanego. 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 przeciążenia RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) z parametrem handledEventsToo ustawionym na true będą reagować 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 program obsługi ComponentWrapper klasy statycznej przed programem obsługi ComponentWrapperBase klasy statycznej.

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ąp procedury obsługi zdarzeń klasy

Niektóre klasy bazowe elementów wizualnych udostępniają puste metody wirtualne w On<nazwie zdarzenia> i OnPreview<nazwie zdarzenia> dla każdego z ich publicznych zdarzeń wejściowych kierowanych. Na przykład UIElement implementuje wirtualne handlery zdarzeń OnKeyDown i OnPreviewKeyDown oraz wiele innych. Możesz przesłonić wirtualne procedury obsługi zdarzeń klasy bazowej, aby zaimplementować zastępujące procedury obsługi zdarzeń dla klas pochodnych. Na przykład można dodać procedurę obsługi klasy zastąpienia dla DragEnter zdarzenia w dowolnej UIElement klasie pochodnej, poprzez przesłonięcie metody wirtualnej 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 przesłonięte programy obsługi zdarzeń klas dla najbardziej pochodnej klasy w hierarchii. 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. To, czy podstawowa implementacja zostanie wywołana przed lub po nadpisaniu kodu, zależy od charakteru Twojej implementacji.

W poprzednim przykładzie kodu metoda wirtualna klasy OnKeyDown bazowej jest przesłaniana zarówno w klasie ComponentWrapper jak i w klasie ComponentWrapperBase. Ponieważ system zdarzeń WPF wywołuje tylko ComponentWrapper.OnKeyDown procedurę obsługi zdarzeń klasy nadpisanej, ta procedura używa base.OnKeyDown(e) do wywołania ComponentWrapperBase.OnKeyDown procedury obsługi zdarzeń klasy nadpisanej, która z kolei wykorzystuje base.OnKeyDown(e) do wywołania StackPanel.OnKeyDown metody wirtualnej. Kolejność zdarzeń w poprzednim przykładzie kodu to:

  1. Program obsługi wystąpień dołączony do componentWrapper jest wyzwalany przez zdarzenie kierowane PreviewKeyDown.
  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 jest wyzwalany przez zdarzenie kierowane KeyDown. Procedura obsługi została zarejestrowana przy użyciu parametru ustawionego handledEventsToo na true.

Blokowanie 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 zdarzenie kierowane Click. Klasa Button bazowa to ButtonBase, która pośrednio pochodzi z klasy UIElement. Znaczna część infrastruktury systemu zdarzeń potrzebnej do przetwarzania sygnałów wejściowych 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 obsługiwacze klas, a w ramach procedury przesłaniania ustawia właściwość Handled na true i wywołuje zdarzenie Click. Wynikiem końcowym dla większości słuchaczy jest to, że zdarzenia MouseLeftButtonDown i MouseRightButtonDown są ukryte, a zdarzenie wysokiego poziomu Click jest widoczne.

Obejście tłumienia zdarzeń wejściowych

Czasami tłumienie 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 bliżej korzenia twojej aplikacji mogły być wywoływane dla zdarzenia kierowanego, które zostało obsłużone, możesz wykonać jedną z następujących 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. Uważaj, aby nie oznaczyć PreviewMouseLeftButtonDown jako obsłużonego, ponieważ spowodowałoby to całkowite pominięcie zdarzenia Click.

Aby zapoznać się z przykładem sposobu obejścia tłumienia zdarzeń wejściowych, zobacz Obejście tłumienia zdarzeń przez kontrolki.

Zobacz także