Gestire eventi mediante i delegati in C++/WinRTHandle events by using delegates in C++/WinRT

Questo argomento illustra come registrare e revocare delegati per la gestione degli eventi con C++/WinRT.This topic shows how to register and revoke event-handling delegates using C++/WinRT. Puoi gestire un evento con qualsiasi oggetto di tipo funzione C++ standard.You can handle an event using any standard C++ function-like object.

Nota

Per informazioni sull'installazione e sull'uso dell'Estensione C++/WinRT per Visual Studio (VSIX) e del pacchetto NuGet, che insieme forniscono il modello di progetto e il supporto della compilazione, vedi Supporto di Visual Studio per C++/WinRT.For info about installing and using the C++/WinRT Visual Studio Extension (VSIX) and the NuGet package (which together provide project template and build support), see Visual Studio support for C++/WinRT.

Uso di Visual Studio 2019 per aggiungere un gestore eventiUsing Visual Studio 2019 to add an event handler

Un modo pratico per aggiungere un gestore eventi al progetto consiste nell'usare l'interfaccia di utente (UI) della finestra di progettazione XAML in Visual Studio 2019.A convenient way of adding an event handler to your project is by using the XAML Designer user interface (UI) in Visual Studio 2019. Con la pagina XAML aperta nella finestra di progettazione XAML, seleziona il controllo di cui vuoi gestire l'evento.With your XAML page open in the XAML Designer, select the control whose event you want to handle. Nella pagina delle proprietà del controllo, fai clic sull'icona del lampo per elencare tutti gli eventi che hanno origine da tale controllo.Over in the property page for that control, click on the lightning-bolt icon to list all of the events that are sourced by that control. Fai quindi doppio clic sull'evento che vuoi gestire, ad esempio OnClicked.Then, double-click on the event that you want to handle; for example, OnClicked.

La finestra di progettazione XAML aggiunge il prototipo di funzione del gestore eventi appropriato (e un'implementazione dello stub) per i file di origine, che puoi sostituire subito con la tua implementazione.The XAML Designer adds the appropriate event handler function prototype (and a stub implementation) to your source files, ready for you to replace with your own implementation.

Nota

In genere non è necessario che i gestori eventi siano descritti nel file Midl (.idl).Typically, your event handlers don't need to be described in your Midl file (.idl). Pertanto, la finestra di progettazione XAML non aggiunge prototipi di funzione del gestore eventi nel file Midl.So, the XAML Designer doesn't add event handler function prototypes to your Midl file. Li aggiunge solo nei file .h e .cpp.It only adds them your .h and .cpp files.

Registrare un delegato per gestire un eventoRegister a delegate to handle an event

Un esempio molto semplice consiste nella gestione dell'evento Click di un pulsante.A simple example is handling a button's click event. Capita spesso di usare markup XAML per registrare una funzione membro allo scopo di gestire un evento come quello che segue.It's typical to use XAML markup to register a member function to handle the event, like this.

// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Windows::UI::Xaml::RoutedEventArgs const& args);

// MainPage.cpp
void MainPage::ClickHandler(
    IInspectable const& /* sender */,
    RoutedEventArgs const& /* args */)
{
    myButton().Content(box_value(L"Clicked"));
}

Il codice precedente è tratto dal progetto App vuota (C++/WinRT) in Visual Studio.The code above is taken from the Blank App (C++/WinRT) project in Visual Studio. Il codice myButton() chiama una funzione di accesso generata, che restituisce l'elemento Button denominato MyButton.The code myButton() calls a generated accessor function, which returns the Button that we named myButton. Se si modifica x:Name nell'elemento Button, viene modificato anche il nome della funzione di accesso generata.If you change the x:Name of that Button element, then the name of the generated accessor function changes, too.

Nota

In questo caso, l'origine evento, ovvero l'oggetto che genera l'evento, è l'elemento Button denominato myButton.In this case, the event source (the object that raises the event) is the Button named myButton. E il destinatario dell'evento, ovvero l'oggetto che gestisce l'evento, è un'istanza di MainPage.And the event recipient (the object handling the event) is an instance of MainPage. Più avanti in questo argomento sono disponibili altre informazioni sulla gestione della durata delle origini eventi e dei destinatari degli eventi.There's more info later in this topic about managing the lifetime of event sources and event recipients.

Anziché procedere in modo dichiarativo nel markup, puoi registrare in modo imperativo una funzione membro per gestire un evento.Instead of doing it declaratively in markup, you can imperatively register a member function to handle an event. Potrebbe non essere evidente dal codice riportato di seguito, ma l'argomento per la chiamata ButtonBase::Click è un'istanza del delegato RoutedEventHandler.It may not be obvious from the code example below, but the argument to the ButtonBase::Click call is an instance of the RoutedEventHandler delegate. In questo caso viene usato l'overload del costruttore RoutedEventHandler che accetta un oggetto e un puntatore alla funzione membro.In this case, we're using the RoutedEventHandler constructor overload that takes an object and a pointer-to-member-function.

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click({ this, &MainPage::ClickHandler });
}

Importante

Quando registri il delegato, l'esempio di codice precedente passa un puntatore non elaborato this che punta all'oggetto corrente.When registering the delegate, the code example above passes a raw this pointer (pointing to the current object). Per informazioni su come definire un riferimento sicuro o debole all'oggetto corrente, vedi Se usi una funzione membro come delegato.To learn how to establish a strong or a weak reference to the current object, see If you use a member function as a delegate.

Di seguito è riportato un esempio che usa una funzione membro statica. Si noti la sintassi più semplice.Here's an example that uses a static member function; note the simpler syntax.

// MainPage.h
static void ClickHandler(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Windows::UI::Xaml::RoutedEventArgs const& args);

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
    IInspectable const& /* sender */,
    RoutedEventArgs const& /* args */) { ... }

Esistono tuttavia altri modi per costruire un RoutedEventHandler.There are other ways to construct a RoutedEventHandler. Di seguito è riportato il blocco di sintassi estratto dall'argomento della documentazione per RoutedEventHandler (scegli C++/WinRT dall'elenco a discesa Linguaggio nell'angolo in alto a destra della pagina Web).Below is the syntax block taken from the documentation topic for RoutedEventHandler (choose C++/WinRT from the Language drop-down in the upper-right corner of the webpage). Osserva i vari costruttori: uno accetta una funzione lambda, un altro una funzione libera, un altro ancora (quello utilizzato sopra) un oggetto e un puntatore alla funzione membro.Notice the various constructors: one takes a lambda; another a free function; and another (the one we used above) takes an object and a pointer-to-member-function.

struct RoutedEventHandler : winrt::Windows::Foundation::IUnknown
{
    RoutedEventHandler(std::nullptr_t = nullptr) noexcept;
    template <typename L> RoutedEventHandler(L lambda);
    template <typename F> RoutedEventHandler(F* function);
    template <typename O, typename M> RoutedEventHandler(O* object, M method);
    void operator()(winrt::Windows::Foundation::IInspectable const& sender,
        winrt::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};

È utile vedere anche la sintassi dell'operatore di chiamata di funzione, cheThe syntax of the function call operator is also helpful to see. indica quali debbano essere i parametri del delegato.It tells you what your delegate's parameters need to be. Come appare evidente, in questo caso la sintassi dell'operatore di chiamata di funzione corrisponde ai parametri del nostro MainPage::ClickHandler.As you can see, in this case the function call operator syntax matches the parameters of our MainPage::ClickHandler.

Nota

Per qualsiasi evento, per determinare i dettagli del relativo delegato e i parametri del delegato, vedi prima l'argomento della documentazione per l'evento stesso.For any given event, to figure out the details of its delegate, and that delegate's parameters, go first to the documentation topic for the event itself. Vediamo come esempio l'evento UIElement.KeyDown.Let's take the UIElement.KeyDown event as an example. Accedi all'argomento e scegli C++/WinRT nell'elenco a discesa Linguaggio.Visit that topic, and choose C++/WinRT from the Language drop-down. Nel blocco di sintassi all'inizio dell'argomento saranno visualizzate queste informazioni.In the syntax block at the beginning of the topic, you'll see this.

// Register
event_token KeyDown(KeyEventHandler const& handler) const;

Le informazioni indicano che l'evento UIElement.KeyDown, l'argomento corrente, ha il tipo delegato KeyEventHandler, dal momento che quello è il tipo passato quando si registra un delegato con questo tipo di evento.That info tells us that the UIElement.KeyDown event (the topic we're on) has a delegate type of KeyEventHandler, since that's the type that you pass when you register a delegate with this event type. Ora fai clic sul collegamento relativo al tipo delegato KeyEventHandler.So, now follow the link on the topic to that KeyEventHandler delegate type. Qui il blocco di sintassi contiene un operatore di chiamata di funzione.Here, the syntax block contains a function call operator. Inoltre, come menzionato sopra, esso indica quali debbano essere i parametri del delegato.And, as mentioned above, that tells you what your delegate's parameters need to be.

void operator()(
  winrt::Windows::Foundation::IInspectable const& sender,
  winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;

Come puoi notare, il delegato deve essere dichiarato in modo tale che accetti un IInspectable come mittente e un'istanza della classe KeyRoutedEventArgs come argomenti.As you can see, the delegate needs to be declared to take an IInspectable as the sender, and an instance of the KeyRoutedEventArgs class as the args.

Per un altro esempio, esaminiamo l'evento Popup.Closed.To take another example, let's look at the Popup.Closed event. Il relativo tipo di delegato è EventHandler<IInspectable>.Its delegate type is EventHandler<IInspectable>. Pertanto il delegato accetterà un IInspectable come mittente e un altro IInspectable (perché questo è il parametro di tipo di EventHandler) come argomenti.So, your delegate will take an IInspectable as the sender, and another IInspectable (because that's the EventHandler's type parameter ) as the args.

Se non esegui molte attività nel gestore eventi, puoi usare una funzione lambda anziché una funzione membro.If you're not doing much work in your event handler, then you can use a lambda function instead of a member function. Anche in questo caso potrebbe non risultare ovvio dall'esempio di codice seguente, ma un delegato RoutedEventHandler viene creato da una funzione lambda che, di nuovo, deve corrispondere alla sintassi dell'operatore di chiamata di funzione trattato sopra.Again, it may not be obvious from the code example below, but a RoutedEventHandler delegate is being constructed from a lambda function which, again, needs to match the syntax of the function call operator that we discussed above.

MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
    {
        myButton().Content(box_value(L"Clicked"));
    });
}

Quando crei il delegato, puoi scegliere di essere un po' più esplicito.You can choose to be a little more explicit when you construct your delegate. Ad esempio, se desideri passarlo o usarlo più volte.For example, if you want to pass it around, or use it more than once.

MainPage::MainPage()
{
    InitializeComponent();

    auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
    {
        sender.as<winrt::Windows::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
    };
    myButton().Click(click_handler);
    AnotherButton().Click(click_handler);
}

Revocare un delegato registratoRevoke a registered delegate

Quando registri un delegato, ti viene generalmente restituito un token.When you register a delegate, typically a token is returned to you. Successivamente puoi usare tale token per revocare il delegato. Questo significa che la registrazione del delegato all'evento viene annullata e che, anche qualora venisse generato nuovamente l'evento, il delegato non verrà chiamato.You can subsequently use that token to revoke your delegate; meaning that the delegate is unregistered from the event, and won't be called should the event be raised again.

Per semplicità, nessuno degli esempi di codice precedenti illustra come eseguire questa operazione.For the sake of simplicity, none of the code examples above showed how to do that. Il prossimo esempio di codice invece archivia il token nel membro di dati privati dello struct e revoca il relativo gestore nel distruttore.But this next code example stores the token in the struct's private data member, and revokes its handler in the destructor.

struct Example : ExampleT<Example>
{
    Example(winrt::Windows::UI::Xaml::Controls::Button const& button) : m_button(button)
    {
        m_token = m_button.Click([this](IInspectable const&, RoutedEventArgs const&)
        {
            // ...
        });
    }
    ~Example()
    {
        m_button.Click(m_token);
    }

private:
    winrt::Windows::UI::Xaml::Controls::Button m_button;
    winrt::event_token m_token;
};

Invece di un riferimento sicuro, come nell'esempio precedente, puoi archiviare un riferimento debole al pulsante (vedi Riferimenti sicuri e deboli in C++/WinRT).Instead of a strong reference, as in the example above, you can store a weak reference to the button (see Strong and weak references in C++/WinRT).

Nota

Quando un'origine genera gli eventi in modo sincrono, puoi revocare il gestore e avere la certezza che non riceverai altri eventi.When an event source raises its events synchronously, you can revoke your handler and be confident that you won't receive any more events. Per gli eventi asincroni, tuttavia, anche dopo la revoca (e in particolare quando la revoca viene eseguita all'interno del distruttore), è possibile che un evento in corso raggiunga l'oggetto dopo l'avvio del processo di distruzione.But for asynchronous events, even after revoking (and especially when revoking within the destructor), an in-flight event might reach your object after it has started destructing. L'identificazione di una posizione in cui annullare la sottoscrizione prima della distruzione può attenuare il problema, ma vedi Accesso sicuro al puntatore this con un delegato di gestione degli eventi per una soluzione solida e affidabile.Finding a place to unsubscribe prior to destruction might mitigate the issue, or for a robust solution see Safely accessing the this pointer with an event-handling delegate.

In alternativa, quando registri un delegato, puoi specificare winrt::auto_revoke, ovvero un valore di tipo winrt::auto_revoke_t, per richiedere un revocatore di eventi (di tipo winrt::event_revoker).Alternatively, when you register a delegate, you can specify winrt::auto_revoke (which is a value of type winrt::auto_revoke_t) to request an event revoker (of type winrt::event_revoker). Il revocatore di eventi mantiene un riferimento debole all'origine dell'evento (l'oggetto che genera l'evento) automaticamente.The event revoker holds a weak reference to the event source (the object that raises the event) for you. Puoi eseguire la revoca manualmente chiamando la funzione membro event_revoker::revoke, tuttavia il revocatore eventi chiama automaticamente la funzione quando esce dall'ambito.You can manually revoke by calling the event_revoker::revoke member function; but the event revoker calls that function itself automatically when it goes out of scope. La funzione revoke verifica se l'origine evento esiste e, in caso affermativo, revoca il delegato.The revoke function checks whether the event source still exists and, if so, revokes your delegate. In questo esempio non c'è alcuna necessità di archiviare l'origine evento né di richiedere un distruttore.In this example, there's no need to store the event source, and no need for a destructor.

struct Example : ExampleT<Example>
{
    Example(winrt::Windows::UI::Xaml::Controls::Button button)
    {
        m_event_revoker = button.Click(
            winrt::auto_revoke,
            [this](IInspectable const& /* sender */,
            RoutedEventArgs const& /* args */)
        {
            // ...
        });
    }

private:
    winrt::Windows::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};

Di seguito è riportato il blocco di sintassi estratto dall'argomento della documentazione per l'evento ButtonBase::Click.Below is the syntax block taken from the documentation topic for the ButtonBase::Click event. Mostra le tre diverse funzioni di registrazione e revoca.It shows the three different registration and revoking functions. Puoi vedere esattamente il tipo di revocatore di eventi che devi dichiarare dal terzo overload.You can see exactly what type of event revoker you need to declare from the third overload. Inoltre, è possibile passare gli stessi tipi di delegati agli overload register e revoke with event_revoker.And you can pass the same kinds of delegates to both the register and the revoke with event_revoker overloads.

// Register
winrt::event_token Click(winrt::Windows::UI::Xaml::RoutedEventHandler const& handler) const;

// Revoke with event_token
void Click(winrt::event_token const& token) const;

// Revoke with event_revoker
Button::Click_revoker Click(winrt::auto_revoke_t,
    winrt::Windows::UI::Xaml::RoutedEventHandler const& handler) const;

Nota

Nell'esempio di codice precedente Button::Click_revoker è un alias di tipo per winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>.In the code example above, Button::Click_revoker is a type alias for winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>. Un modello simile si applica a tutti gli eventi C++/WinRT.A similar pattern applies to all C++/WinRT events. Ogni evento di Windows Runtime ha un overload della funzione revoke che restituisce un revocatore di eventi e il tipo del revocatore è un membro dell'origine evento.Each Windows Runtime event has a revoke function overload that returns an event revoker, and that revoker's type is a member of the event source. Per un altro esempio, l'evento CoreWindow::SizeChanged ha un overload di funzione di registrazione che restituisce un valore di tipo CoreWindow::SizeChanged_revoker.So, to take another example, the CoreWindow::SizeChanged event has a registration function overload that returns a value of type CoreWindow::SizeChanged_revoker.

Puoi valutare la possibilità di revocare gestori durante lo spostamento su una pagina.You might consider revoking handlers in a page-navigation scenario. Se esplori a più riprese una pagina e torni indietro, puoi revocare eventuali gestori al momento dell'uscita.If you're repeatedly navigating into a page and then back out, then you could revoke any handlers when you navigate away from the page. In alternativa, se riutilizzi la stessa istanza di pagina, controlla il valore del tuo token ed esegui la registrazione solo se non è ancora stato impostato (if (!m_token){ ... }).Alternatively, if you're re-using the same page instance, then check the value of your token and only register if it's not yet been set (if (!m_token){ ... }). Una terza opzione consiste nell'archiviare un revocatore di eventi nella pagina come membro dati.A third option is to store an event revoker in the page as a data member. Una quarta opzione, come descritto più avanti in questo argomento, consiste nell'acquisire un riferimento sicuro o debole all'oggetto this nella funzione lambda.And a fourth option, as described later in this topic, is to capture a strong or a weak reference to the this object in your lambda function.

Se la registrazione del delegato di revoca automatica ha esito negativoIf your auto-revoke delegate fails to register

Se provi a specificare winrt::auto_revoke durante la registrazione di un delegato e viene generata un'eccezione winrt::hresult_no_interface, ciò significa in genere che l'origine dell'evento non supporta riferimenti deboli.If you try to specify winrt::auto_revoke when registering a delegate, and the result is a winrt::hresult_no_interface exception, then that usually means that the event source doesn't support weak references. Questa è una situazione comune, ad esempio nello spazio dei nomi Windows.UI.Composition.That's a common situation in the Windows.UI.Composition namespace, for example. In una situazione di questo tipo non puoi usare la funzionalità di revoca automatica.In this situation, you can't use the auto-revoke feature. Dovrai eseguire il fallback e revocare manualmente i gestori eventi.You'll have to fall back to manually revoking your event handlers.

Tipi di delegati per operazioni e azioni asincroneDelegate types for asynchronous actions and operations

Gli esempi precedenti usano il tipo di delegato RoutedEventHandler, ma esistono molti altri tipi di delegati.The examples above use the RoutedEventHandler delegate type, but there are of course many other delegate types. Ad esempio, operazioni e azioni asincrone (con e senza stato) hanno eventi di completamento e/o avanzamento che prevedono delegati del tipo corrispondente.For example, asynchronous actions and operations (with and without progress) have completed and/or progress events that expect delegates of the corresponding type. Ad esempio, l'evento di avanzamento di un'operazione asincrona con stato, ovvero tutto ciò che implementa IAsyncOperationWithProgress, richiede un delegato di tipo AsyncOperationProgressHandler.For example, the progress event of an asynchronous operation with progress (which is anything that implements IAsyncOperationWithProgress) requires a delegate of type AsyncOperationProgressHandler. Di seguito è riportato un esempio di codice per la creazione di un delegato di tale tipo mediante una funzione lambda.Here's a code example of authoring a delegate of that type using a lambda function. L'esempio illustra anche come creare un delegato AsyncOperationWithProgressCompletedHandler.The example also shows how to author an AsyncOperationWithProgressCompletedHandler delegate.

#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void ProcessFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;

    auto async_op_with_progress = syndicationClient.RetrieveFeedAsync(rssFeedUri);

    async_op_with_progress.Progress(
        [](
            IAsyncOperationWithProgress<SyndicationFeed,
            RetrievalProgress> const& /* sender */,
            RetrievalProgress const& args)
        {
            uint32_t bytes_retrieved = args.BytesRetrieved;
            // use bytes_retrieved;
        });

    async_op_with_progress.Completed(
        [](
            IAsyncOperationWithProgress<SyndicationFeed,
            RetrievalProgress> const& sender,
            AsyncStatus const /* asyncStatus */)
        {
            SyndicationFeed syndicationFeed = sender.GetResults();
            // use syndicationFeed;
        });

    // or (but this function must then be a coroutine, and return IAsyncAction)
    // SyndicationFeed syndicationFeed{ co_await async_op_with_progress };
}

Come suggerisce il commento "coroutine" precedente, anziché usare un delegato con gli eventi di completamento di operazioni e azioni asincrone, potresti trovare più naturale usare coroutine.As the "coroutine" comment above suggests, instead of using a delegate with the completed events of asynchronous actions and operations, you'll probably find it more natural to use coroutines. Per informazioni dettagliate ed esempi di codice, vedi Concorrenza e operazioni asincrone con C++/WinRT.For details, and code examples, see Concurrency and asynchronous operations with C++/WinRT.

Nota

Non è corretto implementare più di un gestore di completamento per un'operazione o un'azione asincrona.It's not correct to implement more than one completion handler for an asynchronous action or operation. Puoi avere un solo delegato per l'evento completato oppure puoi usare co_await.You can have either a single delegate for its completed event, or you can co_await it. In presenza di entrambi, il secondo avrà esito negativo.If you have both, then the second will fail.

Se preferisci continuare a usare i delegati anziché una coroutine, puoi scegliere una sintassi più semplice.If you stick with delegates instead of a coroutine, then you can opt for a simpler syntax.

async_op_with_progress.Completed(
    [](auto&& /*sender*/, AsyncStatus const /* args */)
{
    // ...
});

Tipi di delegato che restituiscono un valoreDelegate types that return a value

Alcuni tipi di delegato devono restituire un valore.Some delegate types must themselves return a value. ListViewItemToKeyHandler, ad esempio, restituisce una stringa.An example is ListViewItemToKeyHandler, which returns a string. Di seguito è riportato un esempio di creazione di un delegato di quel tipo. Nota che la funzione lambda restituisca un valore.Here's an example of authoring a delegate of that type (note that the lambda function returns a value).

using namespace winrt::Windows::UI::Xaml::Controls;

winrt::hstring f(ListView listview)
{
    return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
    {
        return L"key for item goes here";
    });
}

Accesso sicuro al puntatore this con un delegato di gestione eventiSafely accessing the this pointer with an event-handling delegate

Ma se gestisci un evento con una funzione membro di un oggetto o dall'interno di una funzione lambda all'interno della funzione membro di un oggetto, devi considerare le durate relative del destinatario dell'evento (l'oggetto che gestisce l'evento) e dell'origine dell'evento (l'oggetto che genera l'evento).If you handle an event with an object's member function, or from within a lambda function inside an object's member function, then you need to think about the relative lifetimes of the event recipient (the object handling the event) and the event source (the object raising the event). Per altre informazioni ed esempi di codice, vedi Riferimenti sicuri e deboli in C++/WinRT.For more info, and code examples, see Strong and weak references in C++/WinRT.

API importantiImportant APIs