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.
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 true
wartość . Chociaż możliwe jest przywrócenie tej wartości do false
wartoś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:
- 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.
- 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
handledEventsToo
sątrue
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 outerStackPanel
StackPanel 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:
- Program obsługi wystąpień dołączony do
componentWrapper
programu jest wyzwalany przezPreviewKeyDown
zdarzenie kierowane. - Program obsługi klas statycznych dołączony do
componentWrapper
jest wyzwalany przezKeyDown
zdarzenie kierowane. - Program obsługi klas statycznych dołączony do
componentWrapperBase
jest wyzwalany przezKeyDown
zdarzenie kierowane. - Procedura obsługi klasy zastąpienia dołączona do
componentWrapper
programu jest wyzwalana przezKeyDown
zdarzenie kierowane. - Procedura obsługi klasy zastąpienia dołączona do
componentWrapperBase
programu jest wyzwalana przezKeyDown
zdarzenie kierowane. - Zdarzenie
KeyDown
kierowane jest oznaczone jako obsługiwane. - Program obsługi wystąpień dołączony do
componentWrapper
programu jest wyzwalany przezKeyDown
zdarzenie kierowane. Procedura obsługi została zarejestrowana przy użyciu parametru ustawionegohandledEventsToo
natrue
.
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
natrue
. 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ługiwanegoPreviewMouseLeftButtonDown
, 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ż
.NET Desktop feedback