Markera rutthändelser som hanterade och hantering av klasser (WPF .NET)
Även om det inte finns någon absolut regel för när en dirigerad händelse ska markeras som hanterad bör du överväga att markera en händelse som hanterad om koden svarar på händelsen på ett betydande sätt. En dirigerad händelse som är markerad som hanterad fortsätter längs vägen, men endast hanterare som är konfigurerade för att svara på hanterade händelser anropas. I grund och botten begränsar märkningen av en dirigerad händelse som hanterad dess synlighet till lyssnare längs händelsevägen.
Routade händelsehanterare kan vara antingen instanshanterare eller klasshanterare. Instanshanterare hanterar dirigerade händelser på objekt eller XAML-element. Klasshanterare hanterar en dirigerad händelse på klassnivå och anropas innan någon instanshanterare svarar på samma händelse på någon instans av klassen. När registrerade händelser markeras som hanterade, görs detta ofta i klasshanterare. I den här artikeln beskrivs fördelarna och potentiella fallgropar med att markera dirigerade händelser som hanterade, de olika typerna av dirigerade händelser och routade händelsehanterare och händelseundertryckning i sammansatta kontroller.
Förutsättningar
Artikeln förutsätter grundläggande kunskaper om routade händelser och att du har läst Översikt över routade händelser. Om du vill följa exemplen i den här artikeln hjälper det om du är bekant med XAML (Extensible Application Markup Language) och vet hur du skriver WPF-program (Windows Presentation Foundation).
När routade händelser ska markeras som hanterade
Vanligtvis bör endast en hanterare ge ett betydande svar för varje dirigerad händelse. Undvik att använda det dirigerade händelsesystemet för att ge ett betydande svar mellan flera hanterare. Definitionen av vad som utgör ett betydande svar är subjektiv och beror på ditt program. Som allmän vägledning:
- Viktiga svar är att ställa in fokus, ändra offentligt tillstånd, ange egenskaper som påverkar visuell representation, skapa nya händelser och helt hantera en händelse.
- Obetydliga svar omfattar ändring av privat tillstånd utan visuell eller programmässig påverkan, händelseloggning och undersökning av händelsedata utan att svara på händelsen.
Vissa WPF-kontroller förhindrar händelser på komponentnivå som inte behöver hanteras ytterligare genom att markera dem som hanterade. Om du vill hantera en händelse som har markerats som hanterad av en kontroll, se Åtgärda händelseblockering hos kontroller.
Om du vill markera en händelse som hanteradanger du egenskapsvärdet Handled i händelsedata till true
. Även om det är möjligt att återställa värdet till false
bör behovet av att göra det vara sällsynt.
Förhandsgranskning och förflyttning av routade händelsepar
Förhandsversion och bubblande routade händelsepar är specifika för indatahändelser. Flera indatahändelser implementerar en tunneltrafik och bubblande dirigerade händelsepar, till exempel PreviewKeyDown och KeyDown. Prefixet Preview
betyder att den bubblande händelsen startar när förhandsgranskningshändelsen har slutförts. Varje förhandsgransknings- och bubblande händelsepar delar samma instans av händelsedata.
Routade händelsehanterare anropas i en ordning som motsvarar en händelses routningsstrategi:
- Förhandsgranskningshändelsen överförs från programmets rotelement ned till elementet som aktiverade den dirigerade händelsen. Förhandsgranskningshändelsehanterare som är kopplade till programmets rotelement anropas först, följt av hanterare som är kopplade till efterföljande kapslade element.
- När förhandsgranskningshändelsen har slutförts överförs den kopplade bubbelhändelsen från elementet som höjde den dirigerade händelsen till programmets rotelement. Bubblande händelsehanterare kopplade till samma element som aktiverade den dirigerade händelsen anropas först, följt av hanterare kopplade till efterföljande överordnade element.
Parade förhandsgransknings- och bubbelhändelser utgör en del av den interna implementeringen i flera WPF-klasser som deklarerar och utlöser sina egna dirigerade händelser. Utan den interna implementeringen på klassnivå är förhandsversioner och bubblande dirigerade händelser helt separata och delar inte händelsedata – oavsett namngivning av händelser. Information om hur du implementerar bubbel eller tunnlande inmatning för dirigerade händelser i en anpassad klass finns i Skapa en anpassad dirigerad händelse.
Eftersom varje förhandsgransknings- och bubbelhändelsepar delar samma instans av händelsedata, hanteras även den parkopplade bubbelhändelsen om en dirigerad förhandsgranskningshändelse markeras som hanterad. Om en bubblande händelse i en dirigerad sekvens markeras som hanterad, påverkar det inte den tillhörande förhandsgranskningshändelsen eftersom den redan har slutförts. Var försiktig när du markerar förhandsgranskning och bubblar indatahändelsepar enligt hanteringen. En hanterad indatahändelse för visning anropar inte några normalt registrerade händelsehanterare för resten av tunnelrutten, och den parade bubbelhändelsen aktiveras inte. En hanterad bubblande indatahändelse anropar inte några normalt registrerade händelsehanterare för resten av den bubblande vägen.
Instans- och klassroutade händelsehanterare
Routade händelsehanterare kan antingen vara instans-hanterare eller klass--hanterare. Klasshanterare för en viss klass anropas innan någon instanshanterare svarar på samma händelse på någon instans av den klassen. På grund av detta beteende, när dirigerade händelser markeras som hanterade, markeras de ofta så i hanterarfunktioner för klasser. Det finns två typer av klasshanterare:
- statiska klasshändelsehanterare, som registreras genom att anropa metoden RegisterClassHandler i en statisk klasskonstruktor.
- Åsidosätt klassens händelsehanteraresom registreras genom att åsidosätta basklassens virtuella eventmetoder. Virtuella händelsemetoder för basklass finns främst för inmatningshändelser och har namn som börjar med On<händelsenamn> och OnPreview<händelsenamn>.
Händelsehanterare för instanser
Du kan koppla instanshanterare till objekt eller XAML-element genom att anropa metoden AddHandler direkt. WPF-routade händelser implementerar en CLR-händelseomslutning (Common Language Runtime) som använder AddHandler
-metoden för att koppla händelsehanterare. Eftersom XAML-attributsyntaxen för att fästa händelsehanterare resulterar i ett anrop till CLR event-omslutare, motsvarar även att fästa hanterare i XAML ett AddHandler
-anrop. För hanterade händelser:
- Hanterare som är kopplade med XAML-attributsyntax eller den gemensamma signaturen för
AddHandler
anropas inte. - Hanterare som är anslutna med hjälp av AddHandler(RoutedEvent, Delegate, Boolean) överlagring med parametern
handledEventsToo
inställd påtrue
anropas. Den här överlagringen är tillgänglig för sällsynta fall när det är nödvändigt att svara på hanterade händelser. Vissa element i ett elementträd har till exempel markerat en händelse som hanterad, men andra element längre längs händelsevägen måste svara på den hanterade händelsen.
Följande XAML-exempel lägger till en anpassad kontroll med namnet componentWrapper
, som omsluter en TextBox med namnet componentTextBox
till en StackPanel med namnet outerStackPanel
. En instanshändelsehanterare för PreviewKeyDown-händelsen är kopplad till componentWrapper
med hjälp av XAML-attributsyntax. Därför svarar instanshanteraren bara på ohanterade PreviewKeyDown
tunnelhändelser som genereras av componentTextBox
.
<StackPanel Name="outerStackPanel" VerticalAlignment="Center">
<custom:ComponentWrapper
x:Name="componentWrapper"
TextBox.PreviewKeyDown="HandlerInstanceEventInfo"
HorizontalAlignment="Center">
<TextBox Name="componentTextBox" Width="200" />
</custom:ComponentWrapper>
</StackPanel>
Konstruktorn MainWindow
kopplar en instanshanterare för bubbelhändelsen KeyDown
till componentWrapper
med hjälp av överlagringen UIElement.AddHandler(RoutedEvent, Delegate, Boolean), där parametern handledEventsToo
är inställd på true
. Därför svarar instanshändelsehanteraren på både ohanterade och hanterade händelser.
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
Koden bakom implementeringen av ComponentWrapper
visas i nästa avsnitt.
Händelsehanterare för statisk klass
Du kan koppla händelsehanterare för statisk klass genom att anropa metoden RegisterClassHandler i den statiska konstruktorn för en klass. Varje klass i en klasshierarki kan registrera sin egen statiska klasshanterare för varje dirigerad händelse. Därför kan det finnas flera statiska klasshanterare som anropas för samma händelse på en viss nod i händelsevägen. När händelsevägen för händelsen har konstruerats läggs alla statiska klasshanterare för varje nod till i händelsevägen. Ordningen på anrop av statiska klasshanterare på en nod börjar med den mest härledda statiska klasshanteraren, följt av statiska klasshanterare från varje efterföljande basklass.
Statisk klasshändelsehanterare som registrerats med RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean)-överlagring där parametern handledEventsToo
är inställd på true
svarar på både ohanterade och hanterade dirigerade händelser.
Statiska klasshanterare är vanligtvis registrerade för att endast svara på ohanterade händelser. I så fall, om en härledd klasshanterare på en nod markerar en händelse som hanterad, anropas inte basklasshanterare för den händelsen. I det scenariot ersätts basklasshanteraren effektivt av den härledda klasshanteraren. Basklasshanterare bidrar ofta till kontrolldesign inom områden som visuellt utseende, tillståndslogik, indatahantering och kommandohantering, så var försiktig med att ersätta dem. Härledda klasshanterare som inte markerar en händelse som hanterad slutar med att komplettera basklassens hanterare i stället för att ersätta dem.
Följande kodexempel visar klasshierarkin för den ComponentWrapper
anpassade kontroll som refererades till i föregående XAML. Klassen ComponentWrapper
härleds från klassen ComponentWrapperBase
, som i sin tur härleds från klassen StackPanel. Metoden RegisterClassHandler
, som används i den statiska konstruktorn för klasserna ComponentWrapper
och ComponentWrapperBase
, registrerar en händelsehanterare för statisk klass för var och en av dessa klasser. WPF-händelsesystemet anropar den statiska klasshanteraren ComponentWrapper
före den statiska klasshanteraren ComponentWrapperBase
.
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
Koden bakom implementeringen av åsidosättningsklasshändelsehanterare i det här kodexemplet beskrivs i nästa avsnitt.
Åsidosätt händelsehanterare för klass
Vissa basklasser för visuella element exponerar tomma På<händelsenamn> och OnPreview<händelsenamn> virtuella metoder för var och en av deras offentliga dirigerade indatahändelser. Till exempel implementerar UIElementOnKeyDown och OnPreviewKeyDown virtuella händelsehanterare och många andra. Du kan åsidosätta virtuella händelsehanterare för basklass för att implementera åsidosättningsklasshändelsehanterare för dina härledda klasser. Du kan till exempel lägga till en åsidosättningsklasshanterare för DragEnter händelse i valfri UIElement
härledd klass genom att åsidosätta den virtuella OnDragEnter-metoden. Att åsidosätta virtuella basklassmetoder är ett enklare sätt att implementera klasshanterare än att registrera klasshanterare i en statisk konstruktor. Inom åsidosättningen kan du skapa händelser, initiera klassspecifik logik för att ändra elementegenskaper på instanser, markera händelsen som hanterad eller utföra annan logik för händelsehantering.
Till skillnad från händelsehanterare för statisk klass anropar WPF-händelsesystemet endast åsidosättningshändelsehanterare för den mest härledda klassen i en klasshierarki. Den mest härledda klassen i en klasshierarki kan sedan använda nyckelordet grundläggande för att anropa basimplementeringen av den virtuella metoden. I de flesta fall bör du anropa basimplementeringen, oavsett om du markerar en händelse som hanterad. Du bör bara utelämna att anropa basimplementeringen om din klass har ett krav på att ersätta den eventuella grundläggande implementeringslogik. Om du anropar basimplementeringen före eller efter den övergripande koden beror på vilken typ av implementering du har.
I föregående kodexempel åsidosätts basklassens OnKeyDown
virtuella metod i både ComponentWrapper
- och ComponentWrapperBase
-klasserna. Eftersom WPF-händelsesystemet bara anropar ComponentWrapper.OnKeyDown
överskridningsklassens händelsehanterare, använder den hanteraren base.OnKeyDown(e)
för att anropa ComponentWrapperBase.OnKeyDown
överskridningshändelsehanteraren, som i sin tur använder base.OnKeyDown(e)
för att anropa StackPanel.OnKeyDown
virtuella metoden. Händelseordningen i föregående kodexempel är:
- Instanshanteraren som är ansluten till
componentWrapper
utlöses av den routed event-händelsenPreviewKeyDown
. - Den statiska klasshanteraren som är kopplad till
componentWrapper
utlöses av denKeyDown
dirigerade händelsen. - Den statiska klasshanteraren som är kopplad till
componentWrapperBase
utlöses av denKeyDown
routade händelsen. - Åsidosättningsklasshanteraren som är kopplad till
componentWrapper
utlöses av denKeyDown
dirigerade händelsen. - Åsidosättningsklasshanteraren som är kopplad till
componentWrapperBase
utlöses av denKeyDown
routade händelsen. - Den
KeyDown
dirigerade händelsen markeras som hanterad. - Instanshanteraren som är kopplad till
componentWrapper
utlöses av denKeyDown
dirigerade händelsen. Hanteraren registrerades med parameternhandledEventsToo
inställd påtrue
.
Undertryckning av indatahändelser i sammansatta kontroller
Vissa sammansatta kontroller utelämnar indatahändelser på komponentnivå för att ersätta dem med en anpassad högnivåhändelse som innehåller mer information eller innebär ett mer specifikt beteende. En sammansatt kontroll består per definition av flera praktiska kontroller eller kontrollbasklasser. Ett klassiskt exempel är kontrollen Button, som omvandlar olika mushändelser till en Click dirigerad händelse. Basklassen Button
är ButtonBase, som indirekt härleds från UIElement. En stor del av händelseinfrastrukturen som krävs för kontrollindatabearbetning är tillgänglig på UIElement
nivå.
UIElement
exponerar flera Mouse händelser, till exempel MouseLeftButtonDown och MouseRightButtonDown.
UIElement
implementerar även de tomma virtuella metoderna OnMouseLeftButtonDown och OnMouseRightButtonDown som förregistrerade klasshanterare.
ButtonBase
åsidosätter dessa klasshanterare, och i åsidosättningshanteraren ställs egenskapen Handled in på true
och utlöser en Click
-händelse. Slutresultatet för de flesta lyssnare är att händelserna MouseLeftButtonDown
och MouseRightButtonDown
är dolda och att händelsen Click
på hög nivå är synlig.
Arbeta runt undertryckning av inmatningshändelser
Ibland kan händelseundertryckning i enskilda kontroller störa logiken för händelsehantering i ditt program. Om ditt program till exempel använde XAML-attributsyntax för att koppla en hanterare för händelsen MouseLeftButtonDown på XAML-rotelementet anropas inte den hanteraren eftersom Button-kontrollen markerar MouseLeftButtonDown
händelsen som hanterad. Om du vill att element mot rotpunkten i din applikation ska anropas för en hanterad dirigerad händelse kan du antingen:
Koppla hanterare genom att anropa metoden UIElement.AddHandler(RoutedEvent, Delegate, Boolean) med parametern
handledEventsToo
inställd påtrue
. Den här metoden kräver att du kopplar händelsehanteraren i code-behind när du har hämtat en objektreferens för elementet som den ska koppla till.Om händelsen som markerats som hanterad är en bubblande indatahändelse bifogar du hanterare för den kopplade förhandsgranskningshändelsen, om det är tillgängligt. Om en kontroll till exempel undertrycker händelsen
MouseLeftButtonDown
kan du koppla en hanterare för händelsen PreviewMouseLeftButtonDown i stället. Den här metoden fungerar bara för förhandsgransknings- och bubbel-indatahändelsepar som delar händelsedata. Var noga med att inte markeraPreviewMouseLeftButtonDown
som hanterats eftersom det helt skulle undertrycka händelsen Click.
Ett exempel på hur du kan kringgå undertryckning av indatahändelser finns i Kringgå händelseundertryckning med hjälp av kontroller.
Se även
.NET Desktop feedback