Označení směrovaných událostí jako zpracovaných a zpracování tříd (WPF .NET)

I když neexistuje žádné absolutní pravidlo, kdy označit směrovanou událost jako zpracovávanou, zvažte označení události jako zpracovávané, pokud váš kód reaguje na událost významným způsobem. Směrovaná událost označená jako popisovaná bude pokračovat podél své trasy, ale vyvolá se pouze obslužné rutiny nakonfigurované tak, aby reagovaly na zpracovávané události. Označení směrované události v podstatě omezuje jeho viditelnost na naslouchací procesy podél trasy události.

Směrované obslužné rutiny událostí mohou být obslužné rutiny instance nebo obslužné rutiny třídy. Obslužné rutiny instancí zpracovávají směrované události u objektů nebo elementů XAML. Obslužné rutiny třídy zpracovávají směrovanou událost na úrovni třídy a jsou vyvolány před jakoukoli obslužnou rutinou instance reagující na stejnou událost v jakékoli instanci třídy. Při označení směrovaných událostí jako popisovaných událostí se často označují jako takové v rámci obslužných rutin tříd. Tento článek popisuje výhody a potenciální nástrahy označení směrovaných událostí jako zpracovaných, různé typy směrovaných událostí a obslužných rutin trasovaných událostí a potlačení událostí ve složených ovládacích prvcích.

Důležité

Dokumentace k desktopové příručce pro .NET 7 a .NET 6 se právě připravuje.

Předpoklady

V článku se předpokládá základní znalost směrovaných událostí a že jste si přečetli přehled směrovaných událostí. Pokud chcete postupovat podle příkladů v tomto článku, pomůže vám to, pokud znáte jazyk XAML (Extensible Application Markup Language) a víte, jak psát aplikace WINDOWS Presentation Foundation (WPF).

Kdy označit směrované události jako zpracovávané

Obvykle by pro každou směrovanou událost měla poskytovat významnou odpověď pouze jedna obslužná rutina. Nepoužívejte směrovaný systém událostí k zajištění významné odpovědi napříč několika obslužné rutinami. Definice toho, co představuje významnou odpověď, je subjektivní a závisí na vaší aplikaci. Obecné pokyny:

  • Mezi významné odpovědi patří nastavení fokusu, úprava veřejného stavu, vlastnosti nastavení, které ovlivňují vizuální reprezentaci, vyvolávání nových událostí a úplné zpracování události.
  • Mezi nevýznamné odpovědi patří změna privátního stavu bez vizuálního nebo programového dopadu, protokolování událostí a zkoumání dat událostí bez reakce na událost.

Některé ovládací prvky WPF potlačí události na úrovni komponent, které nepotřebují další zpracování tím, že je označí jako zpracovávané. Pokud chcete zpracovat událost, která byla označena jako zpracována ovládacím prvku, přečtěte si téma Práce s potlačením událostí ovládacími prvky.

Pokud chcete událost označit jako zpracovanou, nastavte Handled hodnotu vlastnosti v datech události na truehodnotu . I když je možné vrátit se k této hodnotě false, nutnost to udělat, by měla být vzácná.

Náhled a bublinové dvojice směrovaných událostí

Dvojice směrovaných událostí ve verzi Preview a bublání jsou specifické pro vstupní události. Několik vstupních událostí implementuje tunelování a bublající dvojici směrovaných událostí, například PreviewKeyDown a KeyDown. Předpona Preview označuje, že událost bublání se spustí po dokončení události náhledu. Každý pár událostí verze Preview a bublání sdílí stejnou instanci dat událostí.

Obslužné rutiny směrovaných událostí se vyvolávají v pořadí, které odpovídá strategii směrování události:

  1. Událost preview se přesune z kořenového elementu aplikace dolů do elementu, který vyvolal směrovanou událost. Obslužné rutiny událostí náhledu připojené k kořenovému prvku aplikace se vyvolávají jako první a obslužné rutiny připojené k následným vnořeným prvkům.
  2. Po dokončení události ve verzi Preview se spárovaná bublinová událost přesune z elementu, který vyvolal směrovanou událost do kořenového prvku aplikace. Bublinové obslužné rutiny událostí připojené ke stejnému prvku, který vyvolal směrovanou událost, se vyvolá jako první, následované obslužnými rutinami připojenými k následným nadřazeným prvkům.

Spárované události náhledu a bublání jsou součástí interní implementace několika tříd WPF, které deklarují a vyvolávají vlastní směrované události. Bez interní implementace na úrovni třídy jsou směrované události verze Preview a bublinové směrované události zcela oddělené a nebudou sdílet data událostí – bez ohledu na pojmenování událostí. Informace o tom, jak implementovat bublování nebo tunelování vstupních směrovaných událostí ve vlastní třídě, naleznete v tématu Vytvoření vlastní směrované události.

Vzhledem k tomu, že každá dvojice událostí náhledu a bublání sdílí stejnou instanci dat událostí, pokud je událost směrovaná ve verzi Preview označená jako zpracována, zpracuje se také její spárovaná událost bublání. Pokud je událost směrovaná nasměrovaná bublinou označená jako zpracována, nebude mít vliv na spárovanou událost náhledu, protože událost náhledu byla dokončena. Při označování náhledu a vybuchování párů vstupních událostí při zpracování buďte opatrní. Obslužná událost vstupu ve verzi Preview nevolá žádné běžně zaregistrované obslužné rutiny událostí pro zbytek trasy tunelování a spárovaná událost bublování se nevyvolá. Obslužná vstupní událost bublování nevyvolá žádné obvykle registrované obslužné rutiny událostí pro zbytek trasy bublání.

Obslužné rutiny událostí směrovaných instancí a tříd

Směrované obslužné rutiny událostí mohou být obslužné rutiny instance nebo obslužné rutiny třídy . Obslužné rutiny třídy pro danou třídu jsou vyvolány před jakoukoli obslužnou rutinou instance reagující na stejnou událost u jakékoli instance této třídy. Vzhledem k tomuto chování jsou směrované události označeny jako popisované, jsou často označeny jako takové v rámci obslužných rutin třídy. Existují dva typy obslužných rutin tříd:

  • Obslužné rutiny událostí statické třídy, které jsou registrovány voláním RegisterClassHandler metody v rámci konstruktoru statické třídy.
  • Přepsat obslužné rutiny událostí třídy, které jsou registrovány přepsáním metod virtuální události základní třídy. Metody virtuálních událostí základní třídy primárně existují pro vstupní události a mají názvy začínající na název> události On<a OnPreview<název> události.

Obslužné rutiny událostí instance

Obslužné rutiny instance můžete připojit k objektům nebo elementům XAML přímo voláním AddHandler metody. Směrované události WPF implementují obálku událostí CLR (Common Language Runtime), která používá metodu AddHandler pro připojení obslužných rutin událostí. Vzhledem k tomu, že syntaxe atributu XAML pro připojení obslužných rutin událostí vede k volání obálky událostí CLR, dokonce i připojení obslužných rutin v XAML překládá na AddHandler volání. Pro zpracovávané události:

  • Obslužné rutiny, které jsou připojeny pomocí syntaxe atributu XAML nebo společný podpis AddHandler , se nevyvolají.
  • Obslužné rutiny připojené pomocí AddHandler(RoutedEvent, Delegate, Boolean) přetížení se handledEventsToo sadou parametrů jsou true vyvolány. Toto přetížení je k dispozici pro vzácné případy, kdy je nutné reagovat na zpracovávané události. Například některý prvek ve stromu elementu označil událost jako zpracována, ale další prvky podél trasy události musí reagovat na zpracovávanou událost.

Následující ukázka XAML přidá vlastní ovládací prvek s názvem componentWrapper, který zabalí pojmenovaný TextBoxcomponentTextBox, do pojmenovaného StackPanelouterStackPanel. Obslužná rutina události instance pro PreviewKeyDown událost je připojena k syntaxi atributu componentWrapper XAML. V důsledku toho obslužná rutina instance bude reagovat pouze na neošetřené PreviewKeyDown události tunelování vyvolané .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 připojí obslužnou rutinu instance pro KeyDown událost bublání k componentWrapper použití UIElement.AddHandler(RoutedEvent, Delegate, Boolean) přetížení s parametrem nastaveným handledEventsToo na true. V důsledku toho obslužná rutina události instance odpoví na neošetřené i zpracovávané události.

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

Implementace kódu se ComponentWrapper zobrazí v další části.

Obslužné rutiny událostí statické třídy

Obslužné rutiny událostí statické třídy můžete připojit voláním RegisterClassHandler metody ve statickém konstruktoru třídy. Každá třída v hierarchii tříd může pro každou směrovanou událost zaregistrovat vlastní obslužnou rutinu statické třídy. V důsledku toho může být vyvoláno více obslužných rutin statických tříd pro stejnou událost na libovolném daném uzlu v trase události. Při vytvoření trasy události události se do trasy události přidají všechny obslužné rutiny statických tříd pro každý uzel. Pořadí vyvolání obslužných rutin statických tříd na uzlu začíná nejvýraznější obslužnou rutinou statické třídy následované obslužnými rutinami statických tříd z každé po sobě jdoucí základní třídy.

Obslužné rutiny událostí statické třídy zaregistrované pomocí RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) přetížení se handledEventsToo sadou true parametrů budou reagovat na neošetřené i zpracovávané směrované události.

Obslužné rutiny statických tříd jsou obvykle registrovány tak, aby reagovaly pouze na neošetřené události. V takovém případě, pokud obslužná rutina odvozené třídy na uzlu označí událost jako zpracována, obslužné rutiny základní třídy pro tuto událost nebudou vyvolány. V tomto scénáři je obslužná rutina základní třídy účinně nahrazena odvozenou obslužnou rutinou třídy. Obslužné rutiny základní třídy často přispívají k návrhu ovládacích prvků v oblastech, jako je vzhled vizuálu, logika stavu, zpracování vstupu a zpracování příkazů, takže buďte opatrní při jejich nahrazení. Odvozené obslužné rutiny třídy, které neoznačí událost jako zpracovávané, končí doplněním obslužných rutin základní třídy místo jejich nahrazení.

Následující ukázka kódu ukazuje hierarchii tříd pro ComponentWrapper vlastní ovládací prvek, na který byl odkazován v předchozím kódu XAML. Třída ComponentWrapper je odvozena od ComponentWrapperBase třídy, která je zase odvozena od StackPanel třídy. Metoda RegisterClassHandler použitá ve statickém konstruktoru ComponentWrapper a ComponentWrapperBase třídách zaregistruje obslužnou rutinu události statické třídy pro každou z těchto tříd. Systém událostí WPF vyvolá obslužnou rutinu ComponentWrapper statické třídy před obslužnou rutinou ComponentWrapperBase statické třídy.

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

Implementace obslužných rutin událostí přepsání třídy v této ukázce kódu je popsána v další části.

Přepsání obslužných rutin událostí třídy

Některé základní třídy vizuálních prvků zpřístupňují prázdné on<název> události a OnPreview<název> události virtuální metody pro každou z jejich veřejných směrovaných vstupních událostí. Například UIElement implementuje obslužné rutiny OnKeyDown virtuálních OnPreviewKeyDown událostí a mnoho dalších. Můžete přepsat virtuální obslužné rutiny virtuálních událostí základní třídy, aby implementovaly obslužné rutiny událostí přepsání třídy pro odvozené třídy. Můžete například přidat obslužnou rutinu přepsání třídy pro DragEnter událost v jakékoli UIElement odvozené třídě přepsáním OnDragEnter virtuální metody. Přepsání virtuálních metod základní třídy je jednodušší způsob implementace obslužných rutin třídy než registrace obslužných rutin tříd ve statickém konstruktoru. V rámci přepsání můžete vyvolat události, zahájit logiku specifickou pro třídu a změnit vlastnosti elementu u instancí, označit událost jako zpracovávanou nebo provést jinou logiku zpracování událostí.

Na rozdíl od obslužných rutin událostí statické třídy vyvolá systém událostí WPF pouze přepsání obslužných rutin událostí třídy pro nejvíce odvozenou třídu v hierarchii třídy. Nejvýraznější třída v hierarchii tříd pak může použít základní klíčové slovo k volání základní implementace virtuální metody. Ve většině případů byste měli volat základní implementaci bez ohledu na to, jestli událost označíte jako zpracovanou. Volání základní implementace byste měli vynechat pouze v případě, že vaše třída má požadavek na nahrazení základní logiky implementace, pokud existuje. Bez ohledu na to, jestli voláte základní implementaci před nebo po přepsání kódu, závisí na povaze implementace.

V předchozí ukázce kódu se virtuální metoda základní třídy OnKeyDown přepíše v obou třídách ComponentWrapperComponentWrapperBase . Vzhledem k tomu, že systém událostí WPF vyvolá pouze obslužnou rutinu ComponentWrapper.OnKeyDown události přepsání třídy, tato obslužná rutina používá base.OnKeyDown(e) k volání ComponentWrapperBase.OnKeyDown obslužné rutiny události přepsání třídy, která zase používá base.OnKeyDown(e) k volání StackPanel.OnKeyDown virtuální metody. Pořadí událostí v předchozí ukázce kódu je:

  1. Obslužná rutina instance připojená PreviewKeyDown je componentWrapper aktivována směrovanou událostí.
  2. Obslužná rutina statické třídy připojená KeyDown je componentWrapper aktivována směrovanou událostí.
  3. Obslužná rutina statické třídy připojená KeyDown je componentWrapperBase aktivována směrovanou událostí.
  4. Obslužná rutina přepsání třídy připojená KeyDown je componentWrapper aktivována směrovanou událostí.
  5. Obslužná rutina přepsání třídy připojená KeyDown je componentWrapperBase aktivována směrovanou událostí.
  6. Směrovaná KeyDown událost je označena jako zpracována.
  7. Obslužná rutina instance připojená KeyDown je componentWrapper aktivována směrovanou událostí. Obslužná rutina byla zaregistrována s parametrem nastaveným handledEventsToo na true.

Potlačení vstupní události ve složených ovládacích prvcích

Některé složené ovládací prvky potlačí vstupní události na úrovni komponenty, aby je nahradily přizpůsobenou událostí vysoké úrovně, která obsahuje více informací nebo implikuje konkrétnější chování. 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ů. Klasickým příkladem je Button ovládací prvek, který transformuje různé události myši na Click směrovanou událost. Základní Button třída je ButtonBase, která nepřímo odvozena od UIElement. Velká část infrastruktury událostí potřebná ke zpracování vstupu řízení je k dispozici na UIElement úrovni. UIElement zveřejňuje několik Mouse událostí, jako MouseLeftButtonDownMouseRightButtonDownje a . UIElement také implementuje prázdné virtuální metody OnMouseLeftButtonDown a OnMouseRightButtonDown jako předregistrované obslužné rutiny třídy. ButtonBase přepíše tyto obslužné rutiny třídy a v rámci obslužné rutiny přepsání nastaví Handled vlastnost na true a vyvolá Click událost. Konečným výsledkem většiny naslouchacích procesů je, že MouseLeftButtonDown události a MouseRightButtonDown události jsou skryté a událost vysoké úrovně Click je viditelná.

Práce s potlačením vstupních událostí

Někdy může potlačení událostí v rámci jednotlivých ovládacích prvků kolidovat s logikou zpracování událostí ve vaší aplikaci. Pokud například vaše aplikace použila syntaxi atributu XAML k připojení obslužné rutiny události MouseLeftButtonDown v kořenovém elementu XAML, nebude tato obslužná rutina vyvolána, protože Button ovládací prvek označí MouseLeftButtonDown událost jako zpracována. Pokud chcete, aby se elementy směrem ke kořenovému adresáři vaší aplikace vyvolaly pro zpracovávanou směrovanou událost, můžete:

  • Připojte obslužné rutiny voláním UIElement.AddHandler(RoutedEvent, Delegate, Boolean) metody s parametrem nastaveným handledEventsToo na true. Tento přístup vyžaduje připojení obslužné rutiny události za kódem po získání odkazu na objekt pro prvek, ke kterému se připojí.

  • Pokud je událost označená jako popisovaná jako vstupní událost, připojte obslužné rutiny pro spárovanou událost náhledu( pokud je k dispozici). Pokud například ovládací prvek potlačí MouseLeftButtonDown událost, můžete místo toho připojit obslužnou rutinu PreviewMouseLeftButtonDown události. Tento přístup funguje jenom u párů vstupních událostí ve verzi Preview a bublání, které sdílejí data událostí. Dávejte pozor, abyste neoznačili PreviewMouseLeftButtonDown popisovač, protože by to událost zcela potlačí Click .

Příklad, jak obejít potlačení vstupní události, naleznete v tématu Práce s potlačením událostí ovládacími prvky.

Viz také