Share via


Slabé vzory událostí (WPF .NET)

V aplikacích je možné, že obslužné rutiny připojené ke zdrojům událostí nebudou zničeny v koordinaci s objektem naslouchacího procesu, který připojil obslužnou rutinu ke zdroji. Tato situace může vést k nevracení paměti. Windows Presentation Foundation (WPF) zavádí vzor návrhu, který lze použít k vyřešení tohoto problému. Vzor návrhu poskytuje vyhrazenou třídu manažera pro konkrétní události a implementuje rozhraní pro naslouchací procesy pro danou událost. Tento vzor návrhu se označuje jako slabý vzor události.

Důležité

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

Požadavky

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).

Proč implementovat slabý vzor událostí?

Naslouchání událostem může vést k nevracení paměti. Obvyklou technikou naslouchání události je použití syntaxe specifické pro jazyk k připojení obslužné rutiny k události ve zdroji. Například příkaz source.SomeEvent += new SomeEventHandler(MyEventHandler) jazyka C# nebo příkaz AddHandler source.SomeEvent, AddressOf MyEventHandlerVB . Tato technika však vytvoří silný odkaz ze zdroje událostí na naslouchací proces události. Pokud není obslužná rutina události explicitně zrušena, bude životnost objektu naslouchacího procesu ovlivněna životností objektu zdroje. Za určitých okolností můžete chtít, aby životnost naslouchacího procesu byla řízena jinými faktory, například jestli aktuálně patří do vizuálního stromu aplikace. Kdykoli životnost objektu zdroje přesahuje dobu životnosti užitečného objektu naslouchacího procesu, naslouchací proces zůstane naživu déle, než je potřeba. V tomto případě se nepřidělená paměť rovná nevracení paměti.

Slabý vzor událostí je navržen tak, aby vyřešil problém s nevrácenou pamětí. Slabý vzor události lze použít, když naslouchací proces potřebuje zaregistrovat událost, ale naslouchací proces explicitně neví, kdy se má zrušit registrace. Slabý vzor událostí lze použít také v případě, že životnost objektu zdroje překračuje užitečnou životnost objektu naslouchacího procesu. V tomto případě je užitečné určit vás. Model slabých událostí umožňuje naslouchacímu procesu zaregistrovat a přijmout událost, aniž by to ovlivnilo vlastnosti životnosti objektu naslouchacího procesu jakýmkoli způsobem. Implicitní odkaz ze zdroje v podstatě nezjistí, jestli je naslouchací proces způsobilý pro uvolňování paměti. Odkaz je slabý odkaz, takže pojmenování slabého vzoru událostí a souvisejících rozhraní API. Naslouchací proces může být uvolněný z paměti nebo jinak zničen a zdroj může pokračovat bez zachování neschválených odkazů obslužné rutiny na nyní zničený objekt.

Kdo by měl implementovat slabý vzor událostí?

Slabý vzor událostí je primárně relevantní pro autory řízení. Jako autor ovládacího prvku jste z velké části zodpovědní za chování a omezení vašeho ovládacího prvku a dopad, který má na aplikace, do kterých se vkládá. To zahrnuje chování životnosti objektu ovládacího prvku, zejména zpracování popsaného problému nevracení paměti.

Některé scénáře se ze své podstaty hodí k použití slabého vzoru události. Jedním z takových scénářů je datová vazba. V datové vazbě je běžné, že zdrojový objekt je nezávislý na objektu naslouchacího procesu, což je cíl vazby. Mnoho aspektů datové vazby WPF již má použitý slabý vzor událostí v tom, jak se události implementují.

Implementace slabého vzoru události

Existují čtyři způsoby implementace slabého vzoru událostí a každý přístup používá jiného správce událostí. Vyberte manažera událostí, který nejlépe vyhovuje vašemu scénáři.

  • Stávající slabý správce událostí:

    Použijte existující slabou třídu správce událostí, pokud událost, kterou chcete přihlásit k odběru, má odpovídající WeakEventManager. Seznam slabých správců událostí, které jsou součástí WPF, naleznete v hierarchii dědičnosti ve WeakEventManager třídě. Vzhledem k tomu, že zahrnutí slabí správci událostí jsou omezeni, budete pravděpodobně muset zvolit jeden z dalších přístupů.

  • Obecný slabý správce událostí:

    Použijte obecný, WeakEventManager<TEventSource,TEventArgs> pokud existující WeakEventManager není k dispozici a hledáte nejjednodušší způsob implementace slabých událostí. Obecný je WeakEventManager<TEventSource,TEventArgs> však méně efektivní než existující nebo vlastní slabý správce událostí, protože používá reflexi ke zjištění události z jejího názvu. Kód potřebný k registraci události pomocí obecného WeakEventManager<TEventSource,TEventArgs> kódu je také více podrobný než použití existující nebo vlastní WeakEventManager.

  • Vlastní slabý správce událostí:

    Vytvořte vlastní, WeakEventManager pokud existující WeakEventManager není k dispozici a efektivita je zásadní. I když je efektivnější než obecný WeakEventManager, vlastní WeakEventManager vyžaduje, abyste napsali více počátečního kódu.

  • Správce slabých událostí třetích stran:

    Pokud potřebujete funkce, které nejsou poskytované jinými přístupy, použijte správce slabých událostí třetí strany. NuGet má některé slabé správce událostí. Model podporuje také mnoho architektur WPF.

Následující části popisují, jak implementovat slabý vzor událostí pomocí různých typů správce událostí. Pro obecné a vlastní slabé příklady správce událostí má událost, která se přihlásí k odběru, následující charakteristiky.

  • Název události je SomeEvent.
  • Událost je vyvolána SomeEventSource třídou.
  • Obslužná rutina události má typ EventHandler<SomeEventArgs>.
  • Událost předává parametr typu SomeEventArgs obslužným rutinům událostí.

Použití existující slabé třídy správce událostí

  1. Najděte existujícího slabého správce událostí. Seznam slabých správců událostí zahrnutých ve WPF najdete v hierarchii dědičnosti WeakEventManager třídy.

  2. Místo normálního připojení událostí použijte nového slabého správce událostí.

    Pokud například váš kód používá k přihlášení k odběru události následující vzor:

    source.LostFocus += new RoutedEventHandler(Source_LostFocus);
    
    AddHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    Změňte ho na následující vzor:

    LostFocusEventManager.AddHandler(source, Source_LostFocus);
    
    LostFocusEventManager.AddHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

    Podobně platí, že pokud váš kód používá následující vzor k odhlášení odběru události:

    source.LostFocus -= new RoutedEventHandler(Source_LostFocus);
    
    RemoveHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    Změňte ho na následující vzor:

    LostFocusEventManager.RemoveHandler(source, Source_LostFocus);
    
    LostFocusEventManager.RemoveHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

Použití obecné slabé třídy správce událostí

Místo normálního připojení událostí použijte obecnou WeakEventManager<TEventSource,TEventArgs> třídu.

Při registraci WeakEventManager<TEventSource,TEventArgs> naslouchacích procesů událostí zadáte zdroj události a EventArgs zadáte jako parametry typu do třídy. Volání AddHandler , jak je znázorněno v následujícím kódu:

WeakEventManager<SomeEventSource, SomeEventArgs>.AddHandler(source, "SomeEvent", Source_SomeEvent);
WeakEventManager(Of SomeEventSource, SomeEventArgs).AddHandler(
    source, "SomeEvent", New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))

Vytvoření vlastní slabé třídy správce událostí

  1. Zkopírujte do projektu následující šablonu třídy. Následující třída dědí z WeakEventManager třídy:

    class SomeEventWeakEventManager : WeakEventManager
    {
        private SomeEventWeakEventManager()
        {
        }
    
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(SomeEventSource source,
                                      EventHandler<SomeEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (handler == null)
                throw new ArgumentNullException(nameof(handler));
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(SomeEventSource source,
                                         EventHandler<SomeEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (handler == null)
                throw new ArgumentNullException(nameof(handler));
    
            CurrentManager.ProtectedRemoveHandler(source, handler);
        }
    
        /// <summary>
        /// Get the event manager for the current thread.
        /// </summary>
        private static SomeEventWeakEventManager CurrentManager
        {
            get
            {
                Type managerType = typeof(SomeEventWeakEventManager);
                SomeEventWeakEventManager manager =
                    (SomeEventWeakEventManager)GetCurrentManager(managerType);
    
                // at first use, create and register a new manager
                if (manager == null)
                {
                    manager = new SomeEventWeakEventManager();
                    SetCurrentManager(managerType, manager);
                }
    
                return manager;
            }
        }
    
        /// <summary>
        /// Return a new list to hold listeners to the event.
        /// </summary>
        protected override ListenerList NewListenerList()
        {
            return new ListenerList<SomeEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            SomeEventSource typedSource = (SomeEventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            SomeEventSource typedSource = (SomeEventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
    Class SomeEventWeakEventManager
        Inherits WeakEventManager
    
        Private Sub New()
        End Sub
    
        ''' <summary>
        ''' Add a handler for the given source's event.
        ''' </summary>
        Public Shared Sub [AddHandler](source As SomeEventSource,
                                       handler As EventHandler(Of SomeEventArgs))
            If source Is Nothing Then Throw New ArgumentNullException(NameOf(source))
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            CurrentManager.ProtectedAddHandler(source, handler)
        End Sub
    
        ''' <summary>
        ''' Remove a handler for the given source's event.
        ''' </summary>
        Public Shared Sub [RemoveHandler](source As SomeEventSource,
                                          handler As EventHandler(Of SomeEventArgs))
            If source Is Nothing Then Throw New ArgumentNullException(NameOf(source))
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            CurrentManager.ProtectedRemoveHandler(source, handler)
        End Sub
    
        ''' <summary>
        ''' Get the event manager for the current thread.
        ''' </summary>
        Private Shared ReadOnly Property CurrentManager As SomeEventWeakEventManager
            Get
                Dim managerType As Type = GetType(SomeEventWeakEventManager)
                Dim manager As SomeEventWeakEventManager =
                    CType(GetCurrentManager(managerType), SomeEventWeakEventManager)
    
                If manager Is Nothing Then
                    manager = New SomeEventWeakEventManager()
                    SetCurrentManager(managerType, manager)
                End If
    
                Return manager
            End Get
        End Property
    
        ''' <summary>
        ''' Return a new list to hold listeners to the event.
        ''' </summary>
        Protected Overrides Function NewListenerList() As ListenerList
            Return New ListenerList(Of SomeEventArgs)()
        End Function
    
        ''' <summary>
        ''' Listen to the given source for the event.
        ''' </summary>
        Protected Overrides Sub StartListening(source As Object)
            Dim typedSource As SomeEventSource = CType(source, SomeEventSource)
            AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent)
        End Sub
    
        ''' <summary>
        ''' Stop listening to the given source for the event.
        ''' </summary>
        Protected Overrides Sub StopListening(source As Object)
            Dim typedSource As SomeEventSource = CType(source, SomeEventSource)
            AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent)
        End Sub
    
        ''' <summary>
        ''' Event handler for the SomeEvent event.
        ''' </summary>
        Private Sub OnSomeEvent(sender As Object, e As SomeEventArgs)
            DeliverEvent(sender, e)
        End Sub
    End Class
    
  2. Přejmenujte SomeEventWeakEventManager, SomeEventSomeEventSourcea SomeEventArgs podle názvu události.

  3. Nastavte modifikátory přístupu pro slabou třídu správce událostí tak, aby odpovídaly přístupnosti události, která spravuje.

  4. Místo normálního připojení událostí použijte nového slabého správce událostí.

    Pokud například váš kód používá k přihlášení k odběru události následující vzor:

    source.SomeEvent += new EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    AddHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    Změňte ho na následující vzor:

    SomeEventWeakEventManager.AddHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.AddHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

    Podobně platí, že pokud váš kód používá následující vzor k odhlášení odběru události:

    source.SomeEvent -= new EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    RemoveHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    Změňte ho na následující vzor:

    SomeEventWeakEventManager.RemoveHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.RemoveHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

Viz také