Gerouteerde gebeurtenissen markeren als verwerkt en klasseafhandeling (WPF .NET)
Hoewel er geen absolute regel is voor wanneer u een gerouteerde gebeurtenis als afgehandeld moet markeren, kunt u overwegen een gebeurtenis als afgehandeld te markeren als uw code op een aanzienlijke manier op de gebeurtenis reageert. Een gerouteerde gebeurtenis die als afgehandeld is gemarkeerd, gaat verder op zijn route, maar alleen handlers die zijn geconfigureerd om te reageren op afgehandelde gebeurtenissen, worden geactiveerd. In principe beperkt het markeren van een gerouteerde gebeurtenis als behandeld de zichtbaarheid voor luisteraars langs de gebeurtenisroute.
Gerouteerde eventhandlers kunnen exemplaarhandlers of klassehandlers zijn. Instantiehandlers verwerken gerouteerde gebeurtenissen op objecten of XAML-elementen. Klassehandlers verwerken een gerouteerde gebeurtenis op klasseniveau en worden aangeroepen voordat een instantiehandler reageert op dezelfde gebeurtenis bij een instantie van de klasse. Wanneer gerouteerde gebeurtenissen als afgehandeld worden gemarkeerd, worden ze vaak op die manier gemarkeerd binnen klassehandlers. In dit artikel worden de voordelen en mogelijke valkuilen besproken van het markeren van gerouteerde gebeurtenissen zoals verwerkt, de verschillende typen gerouteerde gebeurtenissen en gerouteerde gebeurtenis-handlers en onderdrukking van gebeurtenissen in samengestelde besturingselementen.
Voorwaarden
In het artikel wordt ervan uitgegaan dat u basiskennis hebt van gerouteerde gebeurtenissen en dat u overzicht van gerouteerde gebeurtenissen hebt gelezen. Als u de voorbeelden in dit artikel wilt volgen, helpt dit als u bekend bent met Extensible Application Markup Language (XAML) en weet hoe u WPF-toepassingen (Windows Presentation Foundation) schrijft.
Wanneer gerouteerde gebeurtenissen als verwerkt gemarkeerd moeten worden
Normaal gesproken moet slechts één handler een aanzienlijk antwoord bieden voor elke gerouteerde gebeurtenis. Vermijd het gebruik van het routed event-systeem om een aanzienlijk antwoord te bieden voor meerdere handlers. De definitie van wat een significant antwoord vormt, is subjectief en is afhankelijk van uw toepassing. Als algemene richtlijnen:
- Belangrijke antwoorden omvatten het instellen van de focus, het wijzigen van de openbare status, het instellen van eigenschappen die van invloed zijn op visuele weergave, het genereren van nieuwe gebeurtenissen en het volledig verwerken van een gebeurtenis.
- Onbelangrijke antwoorden omvatten het wijzigen van de privéstatus zonder visuele of programmatische impact, gebeurtenislogboekregistratie en het onderzoeken van gebeurtenisgegevens zonder te reageren op de gebeurtenis.
Sommige WPF-besturingselementen onderdrukken gebeurtenissen op onderdeelniveau die niet verder hoeven te worden verwerkt door ze als afgehandeld te markeren. Als u een gebeurtenis wilt verwerken die als afgehandeld door een besturingselement is gemarkeerd, raadpleegt u Gebeurtenisonderdrukking door besturingselementen omzeilen.
Als u een gebeurtenis wilt markeren als verwerkt, stelt u de waarde van de eigenschap Handled in de gebeurtenisgegevens in op true
. Hoewel het mogelijk is om die waarde terug te keren naar false
, moet de noodzaak om dit te doen zeldzaam zijn.
Preview en gerouteerde gebeurtenisparen bekijken
Voorbeeld van en gebobbelde gebeurtenisparen zijn specifiek voor invoergebeurtenissen. Verschillende invoergebeurtenissen implementeren een "tunneling" en "bubbling" gerouteerd gebeurtenispaar, zoals PreviewKeyDown en KeyDown. Het Preview
voorvoegsel geeft aan dat de bubblinggebeurtenis wordt gestart zodra de preview-gebeurtenis is voltooid. Elk voorbeeld- en bubbling-gebeurtenispaar deelt hetzelfde exemplaar van gebeurtenisgegevens.
Gerouteerde gebeurtenis-handlers worden aangeroepen in een volgorde die overeenkomt met de routeringsstrategie van een gebeurtenis:
- De preview-gebeurtenis gaat van het hoofdelement van de toepassing naar het element dat de gerouteerde gebeurtenis heeft gegenereerd. Vooraf worden de gebeurtenishandlers die gekoppeld zijn aan het hoofdelement van de toepassing aangeroepen, gevolgd door de handlers die aan achtereenvolgende geneste elementen zijn gekoppeld.
- Nadat de preview-gebeurtenis is voltooid, reist de gekoppelde bubbling-gebeurtenis van het element dat de gerouteerde gebeurtenis heeft geactiveerd, naar het hoofdelement van de toepassing. Bubbelgebeurtenishandlers die zijn gekoppeld aan hetzelfde element dat het gerouteerde evenement heeft geactiveerd, worden als eerste aangeroepen. Daarna volgen de handlers die zijn gekoppeld aan de opeenvolgende bovenliggende elementen.
Gekoppelde preview- en bubblinggebeurtenissen maken deel uit van de interne implementatie van verschillende WPF-klassen die hun eigen gerouteerde gebeurtenissen declareren en genereren. Zonder die interne implementatie op klasseniveau zijn preview- en bubblinggebeurtenissen volledig gescheiden en worden er geen gebeurtenisgegevens gedeeld, ongeacht de naamgeving van gebeurtenissen. Zie Een aangepaste gerouteerde gebeurtenis makenvoor meer informatie over het implementeren van 'bubbling' of 'tunneling' invoergebeurtenissen in een aangepaste klasse.
Omdat elk paar van preview- en bubbling-gebeurtenissen hetzelfde exemplaar van gebeurtenisgegevens deelt, zal de gekoppelde bubbling-gebeurtenis ook als afgehandeld worden beschouwd wanneer een gerouteerde preview-gebeurtenis als afgehandeld wordt gemarkeerd. Als een bubbelende gerouteerde gebeurtenis als afgehandeld is gemarkeerd, heeft dit geen invloed op de gekoppelde voorbeeldgebeurtenis omdat de voorbeeldgebeurtenis is voltooid. Wees voorzichtig bij het markeren van preview- en bubblinginvoergebeurtenisparen zoals verwerkt. Een handled preview-invoergebeurtenis roept geen normaal geregistreerde gebeurtenis-handlers aan voor de rest van de tunnelingroute en de gekoppelde bubblinggebeurtenis wordt niet gegenereerd. Een afgehandelde invoergebeurtenis roept geen normaal geregelde gebeurtenisafhandelaars aan voor de rest van de bubbelingsroute.
Voor instanties en klassen gedistribueerde gebeurtenishandlers.
Gerouteerde gebeurtenis-handlers kunnen exemplaar handlers of klasse handlers zijn. Klassehandlers voor een specifieke klasse worden aangeroepen voordat een instantiehandler reageert op hetzelfde evenement bij een willekeurige instantie van die klasse. Vanwege dit gedrag worden gerouteerde gebeurtenissen gemarkeerd als verwerkt, worden ze vaak als zodanig gemarkeerd binnen klasse-handlers. Er zijn twee typen klasse-handlers:
- Statische classeventhandlers, die zijn geregistreerd door de RegisterClassHandler methode aan te roepen binnen een statische klasseconstructor.
- Overschrijf klassegebeurtenishandlers, die zijn geregistreerd door virtuele gebeurtenismethoden van de basisklasse te overschrijven. Basisklasse-methoden voor virtuele gebeurtenissen bestaan voornamelijk voor invoergebeurtenissen en hebben namen die beginnen met On<gebeurtenis> en OnPreview<gebeurtenis>.
Gebeurtenishandlers van exemplaren
U kunt exemplaarhandlers koppelen aan objecten of XAML-elementen door de AddHandler methode rechtstreeks aan te roepen. WPF-gerouteerde gebeurtenissen implementeren een Common Language Runtime-gebeurteniswikkelaar (CLR) die gebruikmaakt van de AddHandler
-methode om gebeurtenishandlers te koppelen. Omdat de syntaxis van het XAML-kenmerk voor het koppelen van evenement-handlers resulteert in een aanroep van de CLR-evenement-wrapper, wordt zelfs het koppelen van handlers in XAML omgezet in een AddHandler
aanroep. Voor afgehandelde gebeurtenissen:
- Handlers die zijn gekoppeld met de syntaxis van het XAML-kenmerk of de algemene handtekening van
AddHandler
worden niet aangeroepen. - Handlers die zijn gekoppeld met behulp van de AddHandler(RoutedEvent, Delegate, Boolean) overload met de
handledEventsToo
parameter die is ingesteld optrue
worden aangeroepen. Deze overbelasting is beschikbaar voor zeldzame gevallen wanneer het nodig is om te reageren op afgehandelde gebeurtenissen. Een element in een elementstructuur heeft bijvoorbeeld een gebeurtenis gemarkeerd als afgehandeld, maar andere elementen verder langs de gebeurtenisroute moeten reageren op de afgehandelde gebeurtenis.
In het volgende XAML-voorbeeld wordt een aangepast besturingselement met de naam componentWrapper
toegevoegd, waarmee een TextBox met de naam componentTextBox
wordt verpakt in een StackPanel met de naam outerStackPanel
. Een gebeurtenisafhandelaar van een instantie voor de PreviewKeyDown gebeurtenis wordt met behulp van de XAML-attribuutsyntaxis aan de componentWrapper
gekoppeld. Als gevolg hiervan reageert de instantiehandler alleen op niet-verwerkte PreviewKeyDown
tunneling gebeurtenissen die door de componentTextBox
zijn gegenereerd.
<StackPanel Name="outerStackPanel" VerticalAlignment="Center">
<custom:ComponentWrapper
x:Name="componentWrapper"
TextBox.PreviewKeyDown="HandlerInstanceEventInfo"
HorizontalAlignment="Center">
<TextBox Name="componentTextBox" Width="200" />
</custom:ComponentWrapper>
</StackPanel>
De MainWindow
constructor koppelt een instantiehandler voor de KeyDown
bubbling-gebeurtenis aan de componentWrapper
met behulp van de UIElement.AddHandler(RoutedEvent, Delegate, Boolean)-overload, waarbij de parameter handledEventsToo
is ingesteld op true
. Als gevolg hiervan reageert de gebeurtenissenbeheerder van het exemplaar op zowel niet-afgehandelde als afgehandelde gebeurtenissen.
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
De code-behind-implementatie van ComponentWrapper
wordt weergegeven in de volgende sectie.
Gebeurtenishandlers van statische klassen
U kunt statische gebeurtenishandlers koppelen door de methode RegisterClassHandler aan te roepen in de statische constructor van een klasse. Elke klasse in een klassehiërarchie kan een eigen statische klasse-handler registreren voor elke gerouteerde gebeurtenis. Als gevolg hiervan kunnen er meerdere statische klasse-handlers worden aangeroepen voor dezelfde gebeurtenis op elk knooppunt in de gebeurtenisroute. Wanneer de gebeurtenisroute voor de gebeurtenis wordt samengesteld, worden alle statische klasse-handlers voor elk knooppunt toegevoegd aan de gebeurtenisroute. De volgorde van aanroepen van statische klasse-handlers op een knooppunt begint met de meest afgeleide statische klassehandler, gevolgd door statische klasse-handlers van elke opeenvolgende basisklasse.
Statische gebeurtenis-handlers die in de klasse zijn geregistreerd met behulp van de RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) overload, waarbij de parameter handledEventsToo
is ingesteld op true
, reageren op zowel niet-verwerkte als verwerkte gerouteerde gebeurtenissen.
Statische klasse-handlers worden doorgaans alleen geregistreerd om te reageren op niet-verwerkte gebeurtenissen. In een geval waarin een handler van een afgeleide klasse op een knooppunt een gebeurtenis als verwerkt markeert, worden de handlers van de basisklasse voor die gebeurtenis niet aangeroepen. In dat scenario wordt de basisklassehandler effectief vervangen door de afgeleide klassehandler. Basisklassehandlers dragen vaak bij aan het ontwerp van besturingselementen in gebieden zoals het visuele uiterlijk, toestandlogica, invoerafhandeling en commandoverwerking, dus wees voorzichtig met het vervangen ervan. Afgeleide klassehandlers die een gebeurtenis niet aangeven als afgehandeld, vullen uiteindelijk de basisklassehandlers aan, in plaats van ze te vervangen.
In het volgende codevoorbeeld ziet u de klassehiërarchie voor het aangepaste ComponentWrapper
besturingselement waarnaar in de voorgaande XAML is verwezen. De ComponentWrapper
klasse is afgeleid van de ComponentWrapperBase
klasse, die op zijn beurt is afgeleid van de StackPanel-klasse. De methode RegisterClassHandler
, die wordt gebruikt in de statische constructor van de ComponentWrapper
- en ComponentWrapperBase
-klassen, registreert een statische gebeurtenis-handler voor elke klasse. Het WPF-gebeurtenissysteem roept de ComponentWrapper
statische klasse-handler aan voor de ComponentWrapperBase
statische klasse-handler.
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
De code-behind-implementatie van de override-class evenementhandlers in dit codevoorbeeld wordt in de volgende sectie besproken.
Klasse gebeurtenishandlers overschrijven
Sommige basisklassen van het visuele element bevatten lege Voor<gebeurtenisnaam> en OnPreview<gebeurtenisnaam> virtuele methoden voor elk van hun openbare gerouteerde invoergebeurtenissen.
UIElement implementeert bijvoorbeeld de OnKeyDown en OnPreviewKeyDown virtuele gebeurtenis-handlers en nog veel meer. U kunt virtuele gebeurtenishandlers van de basisklasse overschrijven om override-gebeurtenishandlers voor uw afgeleide klassen te implementeren. U kunt bijvoorbeeld een override-klassehandler toevoegen voor het event DragEnter in een UIElement
-afgeleide klasse door de OnDragEnter virtuele methode te overschrijven. Het overschrijven van virtuele basisklassemethoden is een eenvoudigere manier om klasse-handlers te implementeren dan het registreren van klasse-handlers in een statische constructor. Binnen de overschrijving kunt u gebeurtenissen activeren, specifieke klasselogica initiëren om elementeigenschappen op instanties te wijzigen, de gebeurtenis als verwerkt markeren, of andere logica voor gebeurtenisafhandeling uitvoeren.
In tegenstelling tot statische klasse-gebeurtenishandlers roept het WPF-gebeurtenissysteem alleen override klasse-gebeurtenishandlers aan voor de meest afgeleide klasse in een klassehiërarchie. De meest afgeleide klasse in een klassehiërarchie kan vervolgens het basis- trefwoord gebruiken om de basis-implementatie van de virtuele methode aan te roepen. In de meeste gevallen moet u de basis-implementatie aanroepen, ongeacht of u een gebeurtenis markeert als afgehandeld. U moet alleen het aanroepen van de basis-implementatie weglaten als uw klasse een vereiste heeft om de basis-implementatielogica te vervangen, indien van toepassing. Of u de basisimplementatie vóór of na uw overschrijvingscode aanroept, is afhankelijk van de aard van uw implementatie.
In het voorgaande codevoorbeeld wordt de virtuele methode van de basisklasse OnKeyDown
overschreven in zowel de ComponentWrapper
- als de ComponentWrapperBase
-klassen. Omdat het WPF-gebeurtenissysteem alleen de gebeurtenishandler van de override-klasse ComponentWrapper.OnKeyDown
aanroept, gebruikt die handler base.OnKeyDown(e)
om de gebeurtenishandler van de override-klasse ComponentWrapperBase.OnKeyDown
aan te roepen, die op zijn beurt gebruikmaakt van base.OnKeyDown(e)
om de virtuele methode StackPanel.OnKeyDown
aan te roepen. De volgorde van gebeurtenissen in het voorgaande codevoorbeeld is:
- De exemplaarhandler die aan
componentWrapper
is gekoppeld, wordt geactiveerd door het gerouteerde evenementPreviewKeyDown
. - De statische klassehandler die is gekoppeld aan
componentWrapper
wordt geactiveerd door hetKeyDown
-routeringsgebeurtenis. - De statische klassehandler die is gekoppeld aan
componentWrapperBase
wordt geactiveerd door deKeyDown
gerouteerde gebeurtenis. - De override klassehandler die is gekoppeld aan
componentWrapper
wordt geactiveerd door hetKeyDown
gerichte evenement. - De override-klassenhandler die is gekoppeld aan
componentWrapperBase
wordt geactiveerd door deKeyDown
doorgegeven gebeurtenis. - De
KeyDown
gerouteerde gebeurtenis is gemarkeerd als afgehandeld. - De exemplaarhandler die is gekoppeld aan
componentWrapper
wordt getriggerd door de gerouteerde gebeurtenisKeyDown
. De handler is geregistreerd met de parameterhandledEventsToo
ingesteld optrue
.
Onderdrukking van invoergebeurtenissen in samengestelde besturingselementen
Sommige samengestelde besturingselementen onderdrukken invoergebeurtenissen op onderdeelniveau om ze te vervangen door een aangepaste gebeurtenis op hoog niveau die meer informatie bevat of een specifieker gedrag impliceert. Een samengesteld besturingselement bestaat per definitie uit meerdere praktische besturingselementen of basisklassen voor besturingselementen. Een klassiek voorbeeld is het Button besturingselement, waarmee verschillende muisgebeurtenissen worden omgezet in een Click gerouteerde gebeurtenis. De Button
basisklasse is ButtonBase, die indirect is afgeleid van UIElement. Veel van de gebeurtenisinfrastructuur die nodig is voor het beheren van invoerverwerking is beschikbaar op UIElement
niveau.
UIElement
maakt verschillende Mouse gebeurtenissen beschikbaar, zoals MouseLeftButtonDown en MouseRightButtonDown.
UIElement
implementeert ook de lege virtuele methoden OnMouseLeftButtonDown en OnMouseRightButtonDown als vooraf geregistreerde klassehandlers.
ButtonBase
overschrijft deze klassehandlers en binnen de override-handler wordt de eigenschap Handled ingesteld op true
en veroorzaakt een Click
gebeurtenis. Het eindresultaat voor de meeste listeners is dat de gebeurtenissen MouseLeftButtonDown
en MouseRightButtonDown
verborgen zijn en dat de gebeurtenis op hoog niveau Click
zichtbaar is.
Het omzeilen van de onderdrukking van invoerevenementen
Soms kan het onderdrukken van gebeurtenissen binnen afzonderlijke besturingselementen de logica voor gebeurtenisafhandeling in uw toepassing verstoren. Als uw toepassing bijvoorbeeld de syntaxis van het XAML-kenmerk heeft gebruikt om een handler toe te voegen voor de MouseLeftButtonDown gebeurtenis op het XAML-hoofdelement, wordt die handler niet aangeroepen omdat het besturingselement Button de MouseLeftButtonDown
gebeurtenis markeert als verwerkt. Als u wilt dat elementen dichter bij de wortel van uw toepassing worden aangesproken voor een afgehandelde gerouteerde gebeurtenis, kunt u:
Koppel handlers door de methode UIElement.AddHandler(RoutedEvent, Delegate, Boolean) aan te roepen met de parameter
handledEventsToo
ingesteld optrue
. Voor deze aanpak moet de event handler in code-behind worden gekoppeld, nadat voor het element een objectverwijzing is verkregen waaraan deze wordt gekoppeld.Als de gebeurtenis die is gemarkeerd als verwerkt, een invoergebeurtenis is die bubbling is, voegt u handlers toe voor de gekoppelde preview-gebeurtenis, indien beschikbaar. Als een besturingselement bijvoorbeeld de
MouseLeftButtonDown
gebeurtenis onderdrukt, kunt u in plaats daarvan een handler koppelen voor de PreviewMouseLeftButtonDown gebeurtenis. Deze benadering werkt alleen voor preview- en bubblinginvoergebeurtenisparen, die gebeurtenisgegevens delen. Zorg ervoor dat u dePreviewMouseLeftButtonDown
niet aanduidt als afgehandeld, omdat dat de Click-gebeurtenis volledig zou onderdrukken.
Zie voor een voorbeeld van hoe je om kunt gaan met het onderdrukken van invoerevenementen, Het omzeilen van gebeurtenisonderdrukking door besturingselementen.
Zie ook
.NET Desktop feedback