Condividi tramite


Gestire gli eventi usando delegati in C++/WinRT

Questo argomento illustra come registrare e revocare delegati di gestione degli eventi usando C++/WinRT. È possibile gestire un evento usando qualsiasi oggetto di tipo funzione C++ standard.

Annotazioni

Per informazioni sull'installazione e l'uso dell'estensione di Visual Studio C++/WinRT (VSIX) e del pacchetto NuGet (che insieme forniscono il modello di progetto e il supporto per la compilazione), vedere supporto di Visual Studio per C++/WinRT.

Uso di Visual Studio per aggiungere un gestore eventi

Un modo pratico per aggiungere un gestore eventi al progetto consiste nell'usare l'interfaccia utente della finestra di progettazione XAML in Visual Studio. Con la pagina XAML aperta nella finestra di progettazione XAML, selezionare il controllo di cui si vuole gestire l'evento. Nella pagina delle proprietà per tale controllo fare clic sull'icona a forma di fulmine per elencare tutti gli eventi originati da tale controllo. Quindi, fai doppio clic sull'evento che vuoi gestire; ad esempio OnClicked.

La finestra di progettazione XAML aggiunge il prototipo di funzione del gestore eventi appropriato (e un'implementazione stub) ai file di origine, pronti per la sostituzione con la propria implementazione.

Annotazioni

In genere, i gestori eventi non devono essere descritti nel file Midl (.idl). La finestra di progettazione XAML non aggiunge quindi prototipi di funzioni del gestore eventi al file Midl. Esso aggiunge solo i file .h e .cpp.

Registrare un delegato per gestire un evento

Un semplice esempio è la gestione dell'evento click di un pulsante. È comune usare il markup XAML per registrare una funzione membro per gestire l'evento, in questo modo.

// 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 sopra è tratto dal progetto Blank App (C++/WinRT) in Visual Studio. Il codice myButton() chiama una funzione di accesso generata, che restituisce il pulsante che abbiamo chiamato myButton. Se si modifica il x:Name di tale elemento Button, allora anche il nome della funzione accessor generata cambia.

Annotazioni

In questo caso, l'origine evento (l'oggetto che genera l'evento) è il Button denominato myButton. E il destinatario dell'evento (l'oggetto che gestisce l'evento) è un'istanza di MainPage. Più avanti in questo argomento troverai ulteriori informazioni sulla gestione della durata delle origini eventi e dei destinatari degli eventi.

Invece di farlo in modo dichiarativo nel markup, è possibile registrare in modo imperativo una funzione membro per gestire un evento. L'esempio di codice riportato di seguito potrebbe non essere ovvio, ma l'argomento della chiamata ButtonBase::Click è un'istanza del delegato RoutedEventHandler. In questo caso viene usato l'overload del costruttore RoutedEventHandler che accetta un oggetto e un puntatore a una funzione membro.

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

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

Importante

Quando si registra il delegato, l'esempio di codice sopra passa un puntatore grezzo questa (che punta all'oggetto corrente). Per informazioni su come stabilire un riferimento forte o debole all'oggetto corrente, vedere se si utilizza una funzione membro come delegato.

Ecco un esempio che usa una funzione membro statica; si noti la sintassi più semplice.

// 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 altri modi per costruire un RoutedEventHandler. Di seguito è riportato il blocco di sintassi tratto dall'argomento della documentazione per RoutedEventHandler (scegliere C++/WinRT dall'elenco a discesa Language nell'angolo superiore destro della pagina Web). Si notino i vari costruttori: uno accetta un'espressione lambda; un'altra funzione libera; e un altro (quello usato in precedenza) accetta un oggetto e un puntatore a una funzione membro.

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);
    /* ... other constructors ... */
    void operator()(winrt::Windows::Foundation::IInspectable const& sender,
        winrt::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};

La sintassi dell'operatore di chiamata di funzione è anche utile da osservare. Indica quali devono essere i parametri del tuo delegato. Si nota che, in questo caso, la sintassi dell'operatore di chiamata di funzione corrisponde ai parametri del MainPage::ClickHandler.

Annotazioni

Per un determinato evento, per determinare i dettagli del relativo delegato e i parametri del delegato, consultare prima la sezione della documentazione per l'evento stesso. Si prenda l'evento UIElement.KeyDown come esempio. Visita questo argomento e scegli C++/WinRT dall'elenco a discesa Linguaggio. Nel blocco di sintassi all'inizio dell'argomento, vedrai questo.

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

Queste informazioni ci dicono che l'evento UIElement.KeyDown (l'argomento di cui ci stiamo occupando) ha un tipo delegato di KeyEventHandler, poiché è il tipo che si passa quando si registra un delegato con questo tipo di evento. Quindi, segui il collegamento relativo a quell'argomento al tipo di delegato KeyEventHandler . In questo caso, il blocco di sintassi contiene un operatore di chiamata di funzione. E, come accennato in precedenza, spiega quali devono essere i parametri del delegato.

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

Come si può notare, il delegato deve essere dichiarato per accettare un IInspectable come mittente e un'istanza della classe KeyRoutedEventArgs come argomento.

Per un altro esempio, esaminiamo l'evento Popup.Closed. Il suo tipo di delegato è EventHandler<IInspectable>. Quindi, il tuo delegato accetterà un IInspectable come mittente e un altro IInspectable (perché questo è il parametro di tipo dell'EventHandler) come argomento.

Se non si esegue molto lavoro nel gestore eventi, è possibile usare una funzione lambda anziché una funzione membro. Anche in questo caso, potrebbe non essere ovvio dall'esempio di codice seguente. Tuttavia, un delegato RoutedEventHandler sta venendo costruito da una funzione lambda che, ancora una volta, deve rispettare la sintassi dell'operatore di chiamata di funzione che abbiamo discusso sopra.

MainPage::MainPage()
{
    InitializeComponent();

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

Puoi scegliere di essere un po' più esplicito quando costruisci il delegato. Ad esempio, se vuoi condividerlo o usarlo più di una volta.

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 registrato

Quando si registra un delegato, in genere viene restituito un token. Successivamente, è possibile usare tale token per revocare il delegato; significa che il delegato non è più registrato all'evento e non verrà chiamato se l'evento verrà generato di nuovo.

Per motivi di semplicità, nessuno degli esempi di codice precedenti ha mostrato come eseguire questa operazione. Tuttavia, questo esempio di codice successivo archivia il token nel membro dati privato dello struct e revoca il relativo gestore nel distruttore.

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

Annotazioni

Quando un'origine di eventi genera gli eventi in modo sincrono, puoi revocare il gestore ed essere sicuro di non ricevere altri eventi. Tuttavia, per gli eventi asincroni, anche dopo la revoca (e soprattutto quando si revoca all'interno del distruttore), un evento in corso potrebbe raggiungere l'oggetto dopo l'inizio della sua distruzione. Trovare una posizione in cui annullare la sottoscrizione prima della distruzione potrebbe attenuare il problema o per una soluzione affidabile vedere Accedere in modo sicuro al questo puntatore con un delegato di gestione degli eventi.

In alternativa, quando si registra un delegato, è possibile specificare winrt::auto_revoke (ovvero un valore di tipo winrt::auto_revoke_t) per richiedere un revocatore di eventi (di tipo winrt::event_revoker). Il revocatore di eventi contiene un riferimento debole all'origine evento (l'oggetto che genera l'evento) per l'utente. È possibile revocare manualmente chiamando la funzione membro event_revoker::revoke; ma il revocatore di eventi chiama automaticamente tale funzione quando esce dall'ambito. La funzione revoca verifica se l'origine dell'evento esiste ancora e, in tal caso, revoca il tuo delegato. In questo esempio non è necessario archiviare l'origine evento e non è necessario un distruttore.

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 tratto dall'argomento della documentazione per l'evento ButtonBase::Click. Mostra le tre diverse funzioni di registrazione e revoca. È possibile vedere esattamente quale tipo di revocatore di eventi è necessario dichiarare dalla terza sovraccarica. È inoltre possibile passare gli stessi tipi di delegati sia al registro che al revocare con gli overload di event_revoker.

// 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;

Annotazioni

Nell'esempio di codice precedente, Button::Click_revoker è un alias di tipo per winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>. Un modello simile si applica a tutti gli eventi C++/WinRT. Ogni evento di Windows Runtime ha una funzione di revoca sovraccaricata che restituisce un gestore di revoca, e il tipo di questo gestore è membro della sorgente dell'evento. Quindi, per fare un altro esempio, l'evento CoreWindow::SizeChanged ha una sovraccarica della funzione di registrazione che restituisce un valore di tipo CoreWindow::SizeChanged_revoker.

Potresti considerare di revocare i gestori in uno scenario di navigazione tra le pagine. Se si passa ripetutamente a una pagina e quindi si torna indietro, è possibile revocare eventuali gestori quando si esce dalla pagina. In alternativa, se si riutilizza la stessa istanza di pagina, controllare il valore del token e registrarsi solo se non è ancora stato impostato (if (!m_token){ ... }). Una terza opzione consiste nell'archiviare un revocatore di eventi nella pagina come membro di dati. E una quarta opzione, come descritto più avanti in questo argomento, consiste nell'acquisire un riferimento sicuro o debole al questo oggetto nella funzione lambda.

Se il delegato di revoca automatica non riesce a registrarsi

Se si tenta di specificare winrt::auto_revoke durante la registrazione di un delegato e il risultato è un'eccezione winrt::hresult_no_interface, in genere significa che l'origine evento non supporta riferimenti deboli. Si tratta di una situazione comune nello spazio dei nomi Windows.UI.Composition, ad esempio. In questo caso, non è possibile usare la funzionalità di revoca automatica. Dovrai ricorrere a revocare manualmente i tuoi gestori di eventi.

Tipi delegati per azioni e operazioni asincrone

Gli esempi precedenti usano il RoutedEventHandler tipo delegato, ma esistono naturalmente molti altri tipi di delegato. Ad esempio, le azioni e le operazioni asincrone (con e senza avanzamento) hanno eventi di completamento e/o di avanzamento che prevedono delegati del tipo corrispondente. Ad esempio, l'evento di avanzamento di un'operazione asincrona con avanzamento (ovvero qualsiasi elemento che implementa IAsyncOperationWithProgress) richiede un delegato di tipo AsyncOperationProgressHandler. Di seguito è riportato un esempio di codice per la creazione di un delegato di tale tipo usando una funzione lambda. L'esempio mostra anche come creare un delegato AsyncOperationWithProgressCompletedHandler .

#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" sopra, invece di utilizzare un delegato con gli eventi di completamento delle azioni e operazioni asincrone, probabilmente troverai più naturale usare le coroutine. Per informazioni dettagliate ed esempi di codice, vedere Concorrenza e Operazioni Asincrone con C++/WinRT.

Annotazioni

Non è corretto implementare più di un gestore di completamento per un'azione o un'operazione asincrona. È possibile avere un singolo delegato per il suo evento completato, oppure eseguire un'operazione con co_await. Se si dispone di entrambi, il secondo fallirà.

Se si utilizzano delegati invece di una coroutine, è possibile optare per una sintassi più semplice.

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

Tipi delegati che restituiscono un valore

Alcuni tipi di delegati devono essi stessi restituire un valore. Un esempio è ListViewItemToKeyHandler, che restituisce una stringa. Di seguito è riportato un esempio di creazione di un delegato di tale tipo (si noti che la funzione lambda restituisce un valore).

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 con un delegato di gestione degli eventi

Se gestisci un evento con la funzione membro di un oggetto o con una funzione lambda all'interno della funzione membro di un oggetto, è necessario considerare la durata relativa del destinatario dell'evento (l'oggetto che gestisce l'evento) e dell'origine dell'evento (l'oggetto che genera l'evento). Per ulteriori informazioni ed esempi di codice, vedi riferimenti forti e deboli in C++/WinRT.

API importanti