Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo argomento si basa sul componente Windows Runtime e sull'applicazione che usa, che l'argomento relativo ai componenti Windows Runtime con C++/WinRT illustra come compilare.
Ecco le nuove funzionalità aggiunte in questo argomento.
- Aggiornare la classe runtime del termometro per attivare un evento quando la temperatura scende sotto il punto di congelamento.
- Aggiornare l'app principale che utilizza la classe di runtime del termometro in modo che gestisca tale evento.
Note
Per informazioni sull'installazione e l'uso di C++/WinRT Visual Studio Extension (VSIX) e del pacchetto NuGet (che insieme forniscono il modello di progetto e il supporto per la compilazione), vedi Visual Studio supporto per C++/WinRT.
Importante
Per concetti e termini essenziali che supportano la comprensione di come usare e creare classi di runtime con C++/WinRT, vedi Usare API con C++/WinRT e Creare API con C++/WinRT.
Creare TermometroWRC e TermometroCoreApp
Se vuoi seguire gli aggiornamenti illustrati in questo argomento, in modo da poter compilare ed eseguire il codice, il primo passaggio consiste nel seguire la procedura dettagliata nell'argomento Windows Runtime componenti con C++/WinRT. In questo modo, avrai il componente Windows Runtime ThermometerWRC e l'applicazione Core ThermometerCoreApp che lo usa.
Aggiornare ThermometerWRC per generare un evento
Aggiornare Thermometer.idl per avere un aspetto simile all'elenco seguente. Ecco come dichiarare un evento il cui tipo di delegato è EventHandler con un argomento di tipo numero in virgola mobile a precisione singola.
// Thermometer.idl
namespace ThermometerWRC
{
runtimeclass Thermometer
{
Thermometer();
void AdjustTemperature(Single deltaFahrenheit);
event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
};
}
Salva il file. Il progetto non può essere compilato fino in fondo nello stato corrente, ma esegui comunque ora una compilazione per generare versioni aggiornate dei file stub \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h e Thermometer.cpp. All'interno di questi file è ora possibile visualizzare le implementazioni stub dell'evento TemperatureIsBelowFreezing . In C++/WinRT, un evento dichiarato in IDL viene implementato come un insieme di funzioni sovraccaricate (analogamente a come una proprietà viene implementata come una coppia di funzioni get e set sovraccaricate). Un overload accetta un delegato da registrare e restituisce un token ( winrt::event_token). L'altro accetta un token e revoca la registrazione del delegato associato.
Ora apri Thermometer.h e Thermometer.cpp e aggiorna l'implementazione della classe runtime Thermometer. In Thermometer.h, aggiungere le due versioni in overload della funzione TemperatureIsBelowFreezing, nonché un membro dati evento privato da utilizzare nell'implementazione di tali funzioni.
// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
struct Thermometer : ThermometerT<Thermometer>
{
...
winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler);
void TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept;
private:
winrt::event<Windows::Foundation::EventHandler<float>> m_temperatureIsBelowFreezingEvent;
...
};
}
...
Come si può notare sopra, un evento è rappresentato dal modello di struct winrt::event , con parametri in base a un particolare tipo delegato (che può essere parametrizzato da un tipo args).
In Thermometer.cpp implementare le due versioni sovraccaricate della funzione TemperatureIsBelowFreezing.
// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler)
{
return m_temperatureIsBelowFreezingEvent.add(handler);
}
void Thermometer::TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept
{
m_temperatureIsBelowFreezingEvent.remove(token);
}
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
m_temperatureFahrenheit += deltaFahrenheit;
if (m_temperatureFahrenheit < 32.f) m_temperatureIsBelowFreezingEvent(*this, m_temperatureFahrenheit);
}
}
Note
Per informazioni dettagliate sul revocatore di eventi automatici, vedere Revocare un delegato registrato. Ottieni senza costi aggiuntivi l'implementazione della revoca automatica degli eventi per il tuo evento. In altre parole, non è necessario implementare l'overload per il revocatore di eventi, perché è già fornito dalla proiezione C++/WinRT.
Gli altri sovraccarichi (quelli per la registrazione e la revoca manuale) non sono integrati nella proiezione. Questo consente di offrire la flessibilità necessaria per implementarle in modo ottimale per lo scenario in uso. Le chiamate a event::add e a event::remove, come mostrato in queste implementazioni, costituiscono un'impostazione predefinita efficiente e sicura rispetto alla concorrenza/thread-safe. Tuttavia, se si dispone di un numero molto elevato di eventi, è possibile che non si desideri un campo evento per ognuno, ma piuttosto optare per un certo tipo di implementazione di tipo sparse.
È anche possibile notare sopra che l'implementazione della funzione AdjustTemperature è stata aggiornata per generare l'evento TemperatureIsBelowFreezing se la temperatura scende al di sotto del congelamento.
Aggiornare ThermometerCoreApp per gestire l'evento
Nel progetto ThermometerCoreApp , in App.cppapportare le modifiche seguenti al codice per registrare un gestore eventi e quindi fare in modo che la temperatura sia inferiore al congelamento.
WINRT_ASSERT è una definizione di macro e si espande fino a _ASSERTE.
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
winrt::event_token m_eventToken;
...
void Initialize(CoreApplicationView const &)
{
m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto &, float temperatureFahrenheit)
{
WINRT_ASSERT(temperatureFahrenheit < 32.f); // Put a breakpoint here.
});
}
...
void Uninitialize()
{
m_thermometer.TemperatureIsBelowFreezing(m_eventToken);
}
...
void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
{
m_thermometer.AdjustTemperature(-1.f);
...
}
...
};
Tenere presente la modifica apportata al metodo OnPointerPressed . Ora, ogni volta che si fa clic sulla finestra, si sottrae 1 grado Fahrenheit dalla temperatura del termometro. E ora l'app gestisce l'evento che viene generato quando la temperatura scende al di sotto del congelamento. Per dimostrare che l'evento viene generato come previsto, inserire un punto di interruzione all'interno dell'espressione lambda che gestisce l'evento TemperatureIsBelowFreezing , eseguire l'app e fare clic all'interno della finestra.
Delegati con parametri in un'interfaccia ABI
Se l'evento deve essere accessibile in un'interfaccia ABI (Application Binary Interface), ad esempio tra un componente e l'applicazione che ne usa, l'evento deve usare un tipo delegato Windows Runtime. L'esempio precedente usa il tipo delegato Windows::Foundation::EventHandler<T> di Windows Runtime. TypedEventHandler<TSender, TResult> è un altro esempio di un tipo delegato Windows Runtime.
I parametri di tipo di questi due tipi delegato devono attraversare l'ABI, quindi anche i parametri di tipo devono essere tipi di Windows Runtime. Sono comprese le classi di runtime di Windows, le classi di runtime di terze parti e i tipi primitivi, come numeri e stringhe. Il compilatore segnala l'errore "T deve essere un tipo WinRT" se si dimentica questo vincolo.
Di seguito è riportato un esempio sotto forma di listato di codice. Iniziare con i progetti ThermometerWRC e ThermometerCoreApp creati in precedenza in questo argomento e modificare il codice in tali progetti in modo che abbia un aspetto simile al codice in questi elenchi.
Questo primo elenco è per il progetto ThermometerWRC . Dopo aver modificato ThermometerWRC.idl come illustrato di seguito, compila il progetto e quindi copia MyEventArgs.h e .cpp nel progetto (dalla cartella Generated Files), proprio come hai fatto in precedenza con Thermometer.h e .cpp. Ricordarsi di eliminare da entrambi i static_assert file.
// ThermometerWRC.idl
namespace ThermometerWRC
{
[default_interface]
runtimeclass MyEventArgs
{
Single TemperatureFahrenheit{ get; };
}
[default_interface]
runtimeclass Thermometer
{
...
event Windows.Foundation.EventHandler<ThermometerWRC.MyEventArgs> TemperatureIsBelowFreezing;
...
};
}
// MyEventArgs.h
#pragma once
#include "MyEventArgs.g.h"
namespace winrt::ThermometerWRC::implementation
{
struct MyEventArgs : MyEventArgsT<MyEventArgs>
{
MyEventArgs() = default;
MyEventArgs(float temperatureFahrenheit);
float TemperatureFahrenheit();
private:
float m_temperatureFahrenheit{ 0.f };
};
}
// MyEventArgs.cpp
#include "pch.h"
#include "MyEventArgs.h"
#include "MyEventArgs.g.cpp"
namespace winrt::ThermometerWRC::implementation
{
MyEventArgs::MyEventArgs(float temperatureFahrenheit) : m_temperatureFahrenheit(temperatureFahrenheit)
{
}
float MyEventArgs::TemperatureFahrenheit()
{
return m_temperatureFahrenheit;
}
}
// Thermometer.h
...
struct Thermometer : ThermometerT<Thermometer>
{
...
winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler);
...
private:
winrt::event<Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs>> m_temperatureIsBelowFreezingEvent;
...
}
...
// Thermometer.cpp
#include "MyEventArgs.h"
...
winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler) { ... }
...
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
m_temperatureFahrenheit += deltaFahrenheit;
if (m_temperatureFahrenheit < 32.f)
{
auto args = winrt::make_self<winrt::ThermometerWRC::implementation::MyEventArgs>(m_temperatureFahrenheit);
m_temperatureIsBelowFreezingEvent(*this, *args);
}
}
...
Questo elenco è per il progetto ThermometerCoreApp .
// App.cpp
...
void Initialize(CoreApplicationView const&)
{
m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto&, ThermometerWRC::MyEventArgs args)
{
float degrees = args.TemperatureFahrenheit();
WINRT_ASSERT(degrees < 32.f); // Put a breakpoint here.
});
}
...
Segnali semplici in un'interfaccia ABI
Se non è necessario passare parametri o argomenti all'evento, è possibile definire un semplice tipo delegato di Windows Runtime personalizzato. L'esempio seguente mostra una versione più semplice della classe di fase di esecuzione Thermometer. Dichiara un tipo delegato denominato SignalDelegate e quindi lo usa per generare un evento di tipo segnale anziché un evento con un parametro .
// ThermometerWRC.idl
namespace ThermometerWRC
{
delegate void SignalDelegate();
runtimeclass Thermometer
{
Thermometer();
event ThermometerWRC.SignalDelegate SignalTemperatureIsBelowFreezing;
void AdjustTemperature(Single value);
};
}
// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
struct Thermometer : ThermometerT<Thermometer>
{
...
winrt::event_token SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler);
void SignalTemperatureIsBelowFreezing(winrt::event_token const& token);
void AdjustTemperature(float deltaFahrenheit);
private:
winrt::event<ThermometerWRC::SignalDelegate> m_signal;
float m_temperatureFahrenheit{ 0.f };
};
}
// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
winrt::event_token Thermometer::SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler)
{
return m_signal.add(handler);
}
void Thermometer::SignalTemperatureIsBelowFreezing(winrt::event_token const& token)
{
m_signal.remove(token);
}
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
m_temperatureFahrenheit += deltaFahrenheit;
if (m_temperatureFahrenheit < 32.f)
{
m_signal();
}
}
}
// App.cpp
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
ThermometerWRC::Thermometer m_thermometer;
winrt::event_token m_eventToken;
...
void Initialize(CoreApplicationView const &)
{
m_eventToken = m_thermometer.SignalTemperatureIsBelowFreezing([] { /* ... */ });
}
...
void Uninitialize()
{
m_thermometer.SignalTemperatureIsBelowFreezing(m_eventToken);
}
...
void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
{
m_thermometer.AdjustTemperature(-1.f);
...
}
...
};
Delegati con parametri, segnali semplici e callback all'interno di un progetto
Se sono necessari eventi interni al progetto di Visual Studio (non tra file binari), in cui tali eventi non sono limitati ai tipi di Windows Runtime, è comunque possibile usare il modello di classe winrt::event<Delegate>. È sufficiente usare winrt::d elegate anziché un tipo delegato Windows Runtime effettivo, poiché winrt::d elegate supporta anche parametri non Windows Runtime.
L'esempio seguente mostra prima di tutto una firma del delegato che non accetta parametri (essenzialmente un segnale semplice) e quindi una che accetta una stringa.
winrt::event<winrt::delegate<>> signal;
signal.add([] { std::wcout << L"Hello, "; });
signal.add([] { std::wcout << L"World!" << std::endl; });
signal();
winrt::event<winrt::delegate<std::wstring>> log;
log.add([](std::wstring const& message) { std::wcout << message.c_str() << std::endl; });
log.add([](std::wstring const& message) { Persist(message); });
log(L"Hello, World!");
Si noti che è possibile aggiungere all'evento tutti i delegate sottoscritti che si desidera. Tuttavia, esiste un sovraccarico associato a un evento. Se tutto ciò di cui hai bisogno è un semplice callback con un solo delegato sottoscrittore, puoi usare winrt::delegate<... T> da solo.
winrt::delegate<> signalCallback;
signalCallback = [] { std::wcout << L"Hello, World!" << std::endl; };
signalCallback();
winrt::delegate<std::wstring> logCallback;
logCallback = [](std::wstring const& message) { std::wcout << message.c_str() << std::endl; }f;
logCallback(L"Hello, World!");
Se si esegue la conversione da una codebase C++/CX in cui gli eventi e i delegati vengono usati internamente all'interno di un progetto, winrt::d elegate consentirà di replicare tale modello in C++/WinRT.
Eventi rinviabili
Un modello comune nella Windows Runtime è l'evento rinviabile. Un gestore di eventi richiede un differimento chiamando il metodo GetDeferral dell'argomento dell'evento. In questo modo si indica all’origine dell’evento che le attività successive all’evento devono essere posticipate fino al completamento del differimento. In questo modo un gestore eventi può eseguire azioni asincrone in risposta a un evento.
Il modello di struttura winrt::deferrable_event_args è una classe di supporto per implementare (ossia produrre) il modello di differimento di Windows Runtime. Ecco un esempio.
// Widget.idl
namespace Sample
{
runtimeclass WidgetStartingEventArgs
{
Windows.Foundation.Deferral GetDeferral();
Boolean Cancel;
};
runtimeclass Widget
{
event Windows.Foundation.TypedEventHandler<
Widget, WidgetStartingEventArgs> Starting;
};
}
// Widget.h
namespace winrt::Sample::implementation
{
struct Widget : WidgetT<Widget>
{
Widget() = default;
event_token Starting(Windows::Foundation::TypedEventHandler<
Sample::Widget, Sample::WidgetStartingEventArgs> const& handler)
{
return m_starting.add(handler);
}
void Starting(event_token const& token) noexcept
{
m_starting.remove(token);
}
private:
event<Windows::Foundation::TypedEventHandler<
Sample::Widget, Sample::WidgetStartingEventArgs>> m_starting;
};
struct WidgetStartingEventArgs : WidgetStartingEventArgsT<WidgetStartingEventArgs>,
deferrable_event_args<WidgetStartingEventArgs>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
bool Cancel() const noexcept { return m_cancel; }
void Cancel(bool value) noexcept { m_cancel = value; }
bool m_cancel = false;
};
}
Ecco come il destinatario dell'evento usa il modello di evento rinviabile.
// EventRecipient.h
widget.Starting([](auto sender, auto args) -> fire_and_forget
{
auto deferral = args.GetDeferral();
if (!co_await CanWidgetStartAsync(sender))
{
// Do not allow the widget to start.
args.Cancel(true);
}
deferral.Complete();
});
In qualità di implementatore (producer) dell'origine dell'evento, fai derivare la classe degli argomenti dell'evento da winrt::deferrable_event_args. deferrable_event_args<T> implementa T::GetDeferral al posto tuo. Espone anche un nuovo metodo helper deferrable_event_args::wait_for_deferrals, che viene completato quando tutti i differimenti in sospeso sono stati completati (se non sono stati eseguiti rinvii, viene completato immediatamente).
// Widget.h
IAsyncOperation<bool> TryStartWidget(Widget const& widget)
{
auto args = make_self<WidgetStartingEventArgs>();
// Raise the event to let people know that the widget is starting
// and give them a chance to prevent it.
m_starting(widget, *args);
// Wait for deferrals to complete.
co_await args->wait_for_deferrals();
// Use the results.
bool started = false;
if (!args->Cancel())
{
widget.InsertBattery();
widget.FlipPowerSwitch();
started = true;
}
co_return started;
}
Linee guida di progettazione
È consigliabile passare eventi e non delegati come parametri di funzione. La funzione add di winrt::event è l'unica eccezione, perché è necessario passare un delegato in questo caso. Il motivo di questa linea guida è che i delegati possono assumere forme diverse nei diversi linguaggi di Windows Runtime, a seconda che supportino la registrazione di un solo client o di più client. Gli eventi, con il modello di sottoscrittore multiplo, costituiscono un'opzione molto più prevedibile e coerente.
La firma per un delegato del gestore eventi deve essere costituita da due parametri: sender (IInspectable) e args (un tipo di argomento evento, ad esempio RoutedEventArgs).
Si noti che queste linee guida non si applicano necessariamente se si progetta un'API interna. Anche se le API interne diventano spesso pubbliche nel tempo.