Sdílet prostřednictvím


Označení směrovaných událostí jako zpracovaných a jejich třídní zpracování

Obslužné rutiny pro směrovanou událost můžou označit událost jako zpracovanou v rámci dat události. Zpracování události efektivně zkracuje trasu. Správa tříd je programovací koncept podporovaný směrovanými událostmi. Obslužná rutina třídy má možnost zpracovat konkrétní směrovanou událost na úrovni třídy pomocí obslužné rutiny, která je vyvolána před jakoukoli obslužnou rutinou instance v jakékoli instanci třídy.

Požadavky

Toto téma popisuje koncepty představené v přehledu směrovaných událostí.

Kdy označit události jako zpracované

Když nastavíte hodnotu vlastnosti Handled na true v datech události pro směrovanou událost, označuje se to jako označení události zpracovávané. Neexistuje žádné absolutní pravidlo, kdy byste měli označit směrované události jako zpracovávané, buď jako autor aplikace, nebo jako autor ovládacího prvku, který reaguje na existující směrované události nebo implementuje nové směrované události. Ve většině případů by měl být koncept "zpracování", jak je specifikován v datech události směrované události, použit jako omezený protokol pro reakce vaší vlastní aplikace na různé směrované události, které jsou k dispozici v rozhraních API WPF, i pro jakékoliv vlastní směrované události. Dalším způsobem, jak zvážit problém "zpracovávaný", je, že byste obecně měli označit směrovanou událost zpracovávanou, pokud váš kód reagoval na směrovanou událost významným a relativně úplným způsobem. Obvykle by nemělo existovat více než jedna významná odpověď, která vyžaduje samostatné implementace obslužné rutiny pro všechny výskyty jedné směrované události. Pokud potřebujete více odpovědí, je potřeba implementovat potřebný kód prostřednictvím logiky aplikace, která je zřetězený v rámci jedné obslužné rutiny, a ne pomocí směrovaného systému událostí pro předávání. Koncept toho, co je "významné", je také subjektivní a závisí na vaší aplikaci nebo kódu. Mezi obecné pokyny patří některé příklady "významné odpovědi": nastavení fokusu, úprava veřejného stavu, nastavení vlastností, které ovlivňují vizuální reprezentaci, a vyvolávání dalších nových událostí. Mezi příklady nepodepsaných odpovědí patří: úprava soukromého stavu (bez vizuálního dopadu nebo programového vyjádření), protokolování událostí nebo zobrazení argumentů události a volba, že na ni neodpovědíte.

Chování směrovaného systému událostí posiluje model "významné odezvy" pro použití zpracovávaného stavu směrované události, protože obslužné rutiny přidané v XAML nebo společný podpis AddHandler se nevyvolávají v reakci na směrovanou událost, když jsou data událostí již označená jako zpracovaná. Pokud chcete zpracovávat směrované události označené předchozími účastníky v trase události, musíte projít další úsilí přidáním obslužné rutiny s verzí handledEventsToo parametru (AddHandler(RoutedEvent, Delegate, Boolean)).

V některých případech označují ovládací prvky určité směrované události jako zpracované. Řešená směrovaná událost představuje rozhodnutí autorů ovládacích prvků WPF, že reakce ovládacího prvku na směrovanou událost jsou významné nebo kompletní jako součást implementace ovládacího prvku a událost nevyžaduje další zpracování. Obvykle se to provádí přidáním obslužné rutiny třídy pro událost nebo přepsáním jedné z virtuálních rutin třídy, které existují v základní třídě. V případě potřeby můžete toto zpracování událostí obejít; viz Obcházení potlačení událostí ovládacími prvky v této části níže.

Události preview (tunelování) vs. události bublání a zpracování událostí

Směrované události ve verzi Preview jsou události, které sledují trasu tunelování přes strom elementů. "Preview" vyjádřený v názvové konvenci je indikací obecného principu pro vstupní události, kdy směrované události ve fázi Preview (tunelování) jsou vyvolány před ekvivalentními bublinovými směrovanými událostmi. Vstupní směrované události, které mají dvojici tunelování a bublinového propojení, mají také odlišnou logiku zpracování. Pokud je událost směrování tunelu nebo náhledu označena jako zpracovaná posluchačem událostí, pak bude bublající směrovaná událost označena jako zpracovaná ještě před tím, než ji obdrží všichni posluchači této směrované události. Technicky jsou tunelové a bublající směrované události samostatné, ale záměrně sdílejí stejnou instanci dat událostí, aby umožnily toto chování.

Propojení mezi tunelovými a bublajícími směrovanými událostmi je dosaženo interní implementací toho, jak každá daná třída WPF vyvolává své vlastní deklarované směrované události, a to platí pro spárované vstupní směrované události. Pokud však tato implementace na úrovni třídy neexistuje, neexistuje žádné spojení mezi tunelovou směrovanou událostí a bublinovou směrovanou událostí, které sdílí stejné pojmenování: bez takové implementace by byly dvě zcela samostatné směrované události a nebyly by spouštěny postupně ani by nesdílely data událostí.

Další informace o tom, jak implementovat dvojice tunelových/bublinových směrovaných událostí ve své uživatelské třídě, najdete v tématu Vytvoření vlastní směrované události.

Handlery tříd a handlery instancí

Směrované události zahrnují dva různé typy posluchačů událostí: posluchači tříd a posluchači instancí. Naslouchací procesy třídy existují, protože typy volají konkrétní rozhraní API EventManager ,RegisterClassHandler, v jejich statickém konstruktoru nebo přepsaly virtuální metodu obslužné rutiny třídy ze základní třídy elementu. Instance naslouchacího procesu jsou konkrétní instance třídy/prvky, ke kterým byly jedna nebo více obslužných rutin připojeny pro tuto směrovanou událost voláním AddHandler. Existující směrované události WPF volat AddHandler jako součást obálky událostí CLR (Common Language Runtime) přidat{} a odebrat{} implementace události, což je také způsob, jak je povolený jednoduchý mechanismus XAML připojení obslužných rutin událostí prostřednictvím syntaxe atributu. Proto i jednoduché použití XAML nakonec odpovídá AddHandler volání.

Prvky ve stromu vizuálu jsou ověřovány, zda mají registrované implementace obslužných rutin. Obslužné rutiny se dají v celé trase vyvolat v pořadí, v jakém je podstata typu strategie směrování pro danou směrovanou událost. Například směrované události s bublinovým chováním nejprve vyvolají ty obslužné rutiny, které jsou připojené ke stejnému prvku, který směrovanou událost vyvolal. Pak směrovaná událost postupuje k dalšímu nadřazenému elementu a pokračuje, dokud se nedosáhne kořenového elementu aplikace.

Z pohledu kořenového prvku v bublajícím přenosovém kanálu, pokud na úrovni třídy nebo jakýkoliv prvek blíže ke zdroji směrované události volají obslužné programy, které označují argumenty události jako zpracovávané, obslužné programy na kořenových prvcích nejsou vyvolány a trasa události je efektivně zkracována před dosažením tohoto kořenového prvku. Trasa však není úplně zastavená, protože obslužné rutiny je možné přidat pomocí speciální podmínky, kterou by měly být stále vyvolány, i když obslužná rutina třídy nebo obslužná rutina instance označila směrovanou událost jako zpracována. To je vysvětleno v Přidání obslužných rutin instance, které jsou vyvolány, i když jsou události označeny jako zpracované, dále v tomto tématu.

Na hlubší úrovni než trasa událostí existuje také několik obslužných rutin třídy fungujících na jakékoli dané instanci třídy. Důvodem je to, že model zpracování tříd pro směrované události umožňuje všem možným třídám v hierarchii tříd zaregistrovat vlastní obslužnou rutinu třídy pro každou směrovanou událost. Každá obslužná rutina třídy se přidá do interního úložiště a při vytvoření trasy události pro aplikaci se do trasy události přidají obslužné rutiny třídy. Obslužné rutiny třídy jsou přidány na trasu tak, aby byla obslužná rutina odvozené třídy vyvolána jako první a obslužné rutiny tříd z každé po sobě jdoucí základní třídy jsou vyvolány dále. Obecně platí, že obslužné rutiny tříd nejsou registrovány tak, aby také reagovaly na směrované události, které byly již označeny jako zpracovány. Tento mechanismus zpracování tříd proto umožňuje jednu ze dvou možností:

  • Odvozené třídy mohou doplnit zpracování události zděděné ze základní třídy přidáním obslužné rutiny, která neoznačuje směrovanou událost jako obslouženou, protože obslužná rutina základní třídy bude vyvolána někdy po obslužné rutině odvozené třídy.

  • Odvozené třídy mohou nahradit zpracování třídy ze základní třídy přidáním obslužné rutiny třídy, která označuje směrovanou událost jako zpracovanou. U tohoto přístupu byste měli být opatrní, protože potenciálně změní zamýšlený návrh základního ovládacího prvku v oblastech, jako je vzhled vizuálu, logika stavu, zpracování vstupu a zpracování příkazů.

Zpracování tříd směrovaných událostí podle základních tříd ovládacího prvku

Na každém daném uzlu elementu v trase události mají naslouchací procesy třídy možnost reagovat na směrovanou událost dříve, než může jakýkoli naslouchací proces instance prvku. Z tohoto důvodu se obslužné rutiny specifické pro třídu někdy používají k potlačení směrovaných událostí, které konkrétní implementace třídy ovládacího prvku nechce dále rozšířit, nebo k poskytnutí zvláštního zpracování této směrované události, která je součástí funkcí třídy. Třída může například vyvolat vlastní událost specifickou pro třídu, která obsahuje konkrétnější informace o tom, co určitá podmínka vstupu uživatele znamená v kontextu dané třídy. Implementace třídy pak může označit obecnější směrovanou událost jako zpracovanou. Obslužné rutiny třídy se obvykle přidávají tak, aby se nevyvolávaly pro směrované události, u nichž byla data sdílených událostí již označena jako zpracovaná. Pro neobvyklé případy však existuje podpis RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean), který registruje obslužné rutiny třídy k vyvolání, i když jsou směrované události označeny jako zpracované.

Virtuální funkce obslužné třídy

Některé prvky, zejména základní prvky, jako je UIElement, zpřístupňují prázdné virtuální metody On*Event a OnPreview*Event, které odpovídají jejich seznamu veřejných směrovaných událostí. Tyto virtuální metody lze přepsat, aby implementovaly obslužnou metodu třídy pro konkrétní směrovanou událost. Třídy základních elementů registrují tyto virtuální metody jako obslužnou rutinu třídy pro každou takovou směrovanou událost pomocí RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean), jak je popsáno výše. Virtuální metody On*Event usnadňují implementaci zpracování tříd pro příslušné směrované události bez nutnosti speciální inicializace statických konstruktorů pro každý typ. Můžete například přidat zpracování události DragEnter v libovolné UIElement odvozené třídě přepsáním virtuální metody OnDragEnter. V rámci přepsání můžete zpracovat směrovanou událost, vyvolat další události, zahájit logiku specifickou pro třídu, která může změnit vlastnosti elementu v instancích nebo jakoukoli kombinaci těchto akcí. Obecně byste v takových překrytích měli volat základní implementaci třídy, i když událost označíte jako zpracovanou. Volání implementace základní třídy se důrazně doporučuje, protože virtuální metoda je na základní třídě. Standardní chráněný virtuální vzor volání základních implementací z každé virtuální metody v podstatě nahrazuje a podobá se mechanismu, který je nativní pro zpracování směrovaných událostí v třídách, kde obslužné rutiny pro všechny třídy v hierarchii se volají na konkrétní instanci, počínaje obslužnou rutinou nejvíce odvozené třídy a postupující k obslužné rutině základní třídy. Základní volání implementace byste měli vynechat pouze v případě, že vaše třída má záměrný požadavek na změnu logiky zpracování základní třídy. Zda zavoláte základní implementaci před nebo po vašem přepsání kódu, bude záviset na povaze vaší implementace.

Zpracování tříd vstupních událostí

Virtuální metody obslužné rutiny třídy jsou registrovány tak, že jsou vyvolány pouze v případech, kdy žádná sdílená data událostí ještě nejsou označena jako zpracována. U vstupních událostí jsou také jedinečně vyvolány verze tunelování a bublání v posloupnosti a sdílení dat událostí. To znamená, že pro danou dvojici obslužných rutin tříd vstupních událostí, kde jedna je tunelovanou verzí a druhá je verzí bublání, možná nebudete chtít událost ihned označit jako zpracovanou. Pokud implementujete virtuální metodu zpracování třídy tunelování, která označí událost jako zpracovanou, zabráníte tím vyvolání obslužné rutiny třídy bublání (a také zabráníte vyvolání jakýchkoliv běžně registrovaných obslužných rutin instancí pro události tunelování nebo bublání).

Po dokončení zpracování tříd na uzlu se zohlední naslouchací procesy instance.

Přidání obslužných rutin instancí, které jsou vyvolány i v případě, že jsou události označeny jako zpracovávané

Metoda AddHandler poskytuje konkrétní přetížení, které umožňuje přidat obslužné rutiny, které budou vyvolány systémem událostí pokaždé, když událost dosáhne obslužného prvku v trase, a to i v případě, že některé jiné obslužné rutiny již upravily data události tak, aby tuto událost označil jako zpracována. Toto se obvykle nedělá. Obecně lze obslužné rutiny zapsat tak, aby upravovaly všechny oblasti kódu aplikace, které můžou být ovlivněny událostí, bez ohledu na to, kde byla zpracována ve stromu prvků, i když je žádoucí více koncových výsledků. Obvykle je opravdu pouze jeden prvek, který musí reagovat na danou událost, a příslušná logika aplikace již byla provedena. Přetížení handledEventsToo je však k dispozici pro výjimečné případy, kdy některé další prvky ve stromu elementů nebo ovládací prvek kompozice již označil událost jako zpracovanou, ale jiné prvky buď výše nebo níže ve stromu (podle trasy), stále chtějí mít své vlastní obslužné rutiny vyvolány.

Kdy označit zpracovávané události jako neošetřené

Obecně platí, že směrované události označené jako zpracované by neměly být označené jako nezpracované (Handled nastaveno zpět na false), a to ani obslužnými rutinami, které působí na handledEventsToo. Některé vstupní události však mají vysoké a nižší úrovně reprezentace událostí, které se můžou překrývat, když se událost vysoké úrovně zobrazí na jedné pozici ve stromu a událost nízké úrovně na jiné pozici. Představte si například případ, kdy podřízený prvek naslouchá klíčové události vysoké úrovně, jako je například TextInput, zatímco nadřazený prvek naslouchá události nízké úrovně, například KeyDown. Pokud nadřazený prvek zpracovává událost nízké úrovně, může být událost vyšší úrovně potlačena i v podřízeném prvku, který by intuitivně měl mít první příležitost zpracovat tuto událost.

V těchto situacích může být nutné přidat obslužné rutiny do nadřazených elementů i podřízených elementů pro událost nízké úrovně. Implementace obslužné rutiny podřízeného elementu může označit událost nízké úrovně jako zpracovanou, ale nadřazená implementace obslužné rutiny elementu ji znovu nastaví jako neošetřenou, aby další uzly ve stromu a také událost vysoké úrovně mohly mít možnost reagovat. Tato situace by měla být poměrně vzácná.

Záměrné potlačování vstupních událostí pro kompozitování ovládacích prvků

Hlavní scénář, ve kterém se používá zpracování směrovaných událostí, je určené pro vstupní události a složené ovládací prvky. Složený ovládací prvek se skládá z několika praktických ovládacích prvků nebo základních tříd ovládacích prvků. Autor ovládacího prvku často chce amalgamovat všechny možné vstupní události, které můžou každou dílčí součást vyvolat, aby se celý ovládací prvek hlásil jako jediný zdroj událostí. V některých případech může autor ovládacího prvku chtít potlačit události z komponent zcela nebo nahradit událost definovanou komponentou, která obsahuje více informací nebo naznačuje konkrétnější chování. Kanonický příklad, který je okamžitě viditelný každému autorovi komponenty, je způsob, jakým Windows Presentation Foundation (WPF Button) zpracovává jakoukoli událost myši, která se nakonec přeloží na intuitivní událost, kterou mají všechna tlačítka: Click událost.

Základní třída Button (ButtonBase) je odvozena od Control, která je odvozena z FrameworkElement a UIElementa velká část infrastruktury událostí potřebná pro zpracování vstupu řízení je k dispozici na úrovni UIElement. Konkrétně UIElement zpracovává obecné Mouse události, které se zabývají testováním zásahu kurzoru myši ve svých mezích, a poskytuje jedinečné události pro nejběžnější akce tlačítek, jako například MouseLeftButtonDown. UIElement také poskytuje prázdnou virtuální OnMouseLeftButtonDown jako předem registrovanou obslužnou rutinu třídy pro MouseLeftButtonDowna ButtonBase ji přepisuje. Podobně ButtonBase používá obslužné rutiny tříd pro MouseLeftButtonUp. V rámci přepsání, kterým jsou předávána data události, implementace označují, že instance RoutedEventArgs je zpracována tím, že Handled nastaví na true, a stejná data události pokračují až do ostatních obslužných rutin tříd a také na obslužné rutiny instancí nebo setterů událostí. Také přepsání OnMouseLeftButtonUp následně vyvolá událost Click. Konečným výsledkem pro většinu posluchačů bude, že události MouseLeftButtonDown a MouseLeftButtonUp "zmizí" a budou nahrazeny událostí Click, která má větší význam, protože je známo, že pochází z pravého tlačítka, nikoli z nějaké složené části tlačítka nebo zcela jiného prvku.

Obcházení potlačení událostí ovládacími prvky

Někdy může toto chování potlačení událostí v rámci jednotlivých ovládacích prvků ovlivnit některé obecnější záměry logiky zpracování událostí pro vaši aplikaci. Pokud například vaše aplikace z nějakého důvodu měla obslužnou rutinu pro MouseLeftButtonDown umístěnou v kořenovém prvku aplikace, všimli byste si, že jakékoli kliknutí myší na tlačítko by nevolal MouseLeftButtonDown nebo MouseLeftButtonUp obslužné rutiny na kořenové úrovni. Samotná událost skutečně probublala (opět nejsou trasy událostí skutečně ukončeny, ale systém směrovaných událostí změní chování vyvolání obslužné rutiny poté, co je událost označena jako zpracovaná). Když směrovaná událost dosáhla tlačítka, zpracování třídy ButtonBase označilo událost MouseLeftButtonDown jako zpracovanou, protože chtělo nahradit událost Click něčím smysluplnějším. Proto by nebyla vyvolána žádná standardní obslužná rutina MouseLeftButtonDown dále nahoru trasu. Existují dvě techniky, které můžete použít k zajištění, aby obslužné rutiny byly vyvolány v této situaci.

První technikou je úmyslně přidat obslužnou rutinu pomocí signatury handledEventsTooAddHandler(RoutedEvent, Delegate, Boolean). Omezení tohoto přístupu spočívá v tom, že technika připojení obslužné rutiny události je možná pouze z kódu, nikoli z markupu. Jednoduchá syntaxe zadání názvu obslužné rutiny události jako hodnoty atributu události prostřednictvím jazyka XAML (Extensible Application Markup Language) toto chování neumožňuje.

Druhá technika funguje pouze u vstupních událostí, kdy se spárují tunelové a bublinové verze směrované události. U těchto směrovaných událostí můžete místo toho přidat obslužné rutiny do ekvivalentní směrované události ve verzi Preview nebo tunelování. Tato směrovaná událost bude tunelovat trasu počínaje kořenem, takže kód zpracování třídy tlačítka by ho nezachytil. Předpokládá se, že jste obslužnou rutinu Preview připojili na určité úrovni nadřazeného prvku ve stromu elementů aplikace. Pokud použijete tento přístup, buďte opatrní při označování všech zpracovaných událostí ve verzi Preview. Příklad uvedený s PreviewMouseLeftButtonDown, zpracovávaný v kořenovém elementu: Pokud jste událost označili jako Handled v implementaci obslužné rutiny, skutečně byste potlačili událost Click. To obvykle není žádoucí chování.

Viz také