-
Notifications
You must be signed in to change notification settings - Fork 274
CppWinRT authoring helpers
WIL includes C++/WinRT authoring helpers that aid with implementing WinRT classes using C++/WinRT.
For example, if you have a class in an IDL file like this:
runtimeclass SomeViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged {
String MyStr;
Int32 MyInt;
event Windows.Foundation.EventHandler<String> MyEvent;
void DoSomething();
}You can use these helpers to implement your SomeViewModel:
struct SomeViewModel : SomeViewModelT<SomeViewModel>,
wil::notify_property_changed_base<SomeViewModel>
{
WIL_NOTIFYING_PROPERTY(winrt::hstring, MyStr, L"Hello from WIL!");
WIL_NOTIFYING_PROPERTY(int32_t, MyInt, 42);
wil::untyped_event<winrt::hstring> MyEvent;
void DoSomething()
{
// Automatically fires a property-changed event
MyStr(L"This fires a property-changed event!");
// You can also fire events manually
m_MyInt = 123; // no event
RaisePropertyChanged(L"MyInt");
// Fire custom events
MyEvent.invoke(*this, L"Fire events!");
}
};Versus implementing all of that manually:
(expand to see full implementation)
struct SomeViewModel : SomeViewModelT<SomeViewModel>
{
// Windows.UI.Xaml.Data.INotifyPropertyChanged
winrt::event_token PropertyChanged(winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler const& value)
{
return m_propertyChanged.add(value);
}
void PropertyChanged(const winrt::event_token& token)
{
m_propertyChanged.remove(token);
}
// MyStr
winrt::hstring m_MyStr = L"Hello from WIL!";
winrt::hstring MyStr() {
return m_MyStr;
}
void MyStr(winrt::hstring v) {
m_MyStr = v;
m_propertyChanged(*this, { L"MyStr" });
}
// MyInt
int32_t m_MyInt = 42;
int32_t MyInt() {
return m_MyInt;
}
void MyInt(int32_t v) {
m_MyInt = v;
m_propertyChanged(*this, { L"MyInt" });
}
// MyEvent
winrt::event_token MyEvent(winrt::Windows::Foundation::EventHandler<winrt::hstring> const& value)
{
return m_myEvent.add(value);
}
void MyEvent(const winrt::event_token& token)
{
m_myEvent.remove(token);
}
void DoSomething()
{
// Basically unchanged.
}
private:
winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
winrt::event<winrt::hstring> m_myEvent;
};Import wil/cppwinrt_authoring.h:
#include <wil/cppwinrt_authoring.h>| WinRT read? | WinRT write? | Can fire property changed? | Example IDL | Notes | |
|---|---|---|---|---|---|
wil::single_threaded_property<T> |
✅ | ❌ (internal writes only*) | ❌ | String MyStr{ get; }; |
|
wil::single_threaded_rw_property<T> |
✅ | ✅ | ❌ | String MyStr; |
|
WIL_NOTIFYING_PROPERTY(T, Name, defaultValue) |
✅ | ✅ | ✅ |
String MyStr; in a class that inherits from INotifyPropertyChanged
|
|
wil::single_threaded_notifying_property<T> |
✅ | ✅ | ✅ |
String MyStr; in a class that inherits from INotifyPropertyChanged
|
Same as above + also, must be initialized. See Notifying properties below |
* wil::single_threaded_property does not expose set, so it can only be used for {get;}; properties. It does this by not implementing void Name(T const& value), which is how C++/WinRT exposes settable properties to WinRT. However, it still exposes a public void operator=(T const& value) C++ function, so any code with access to the direct implementation class (including the implementation class itself) can still set the value.
// MyComponent.idl
namespace MyNamespace
{
runtimeclass MyComponent
{
String ReadWriteProperty; // Equivalent: `ReadWriteProperty{get;set;};`
Boolean ReadableProperty{ get; };
SomeOtherComponent AnObjectProperty;
void DoStuff();
}
runtimeclass SomeOtherComponent {
// ...
}
}// MyComponent.h
namespace winrt::MyNamespace::implementation
{
struct MyComponent : MyComponentT<MyComponent>
{
// ...
wil::single_threaded_rw_property<winrt::hstring> ReadWriteProperty = L"Default value";
// Can default-initialize, too:
wil::single_threaded_property<bool> ReadableProperty{};
// Make sure to use the projection type, not the implementation type.
//
// If we used `<SomeOtherComponent>`, we'd get the implementation type,
// `winrt::MyNamespace::implementation::SomeOtherComponent`, which
// wouldn't compile.
wil::single_threaded_rw_property<winrt::MyNamespace::SomeOtherComponent> AnObjectProperty{ nullptr };
void DoStuff()
{
// Set the properties
ReadWriteProperty = L"A new value";
ReadableProperty = true;
AnObjectProperty = winrt::make<SomeOtherComponent>();
// Only the rw properties expose the C++/WinRT "setter" syntax
ReadWriteProperty(L"A newer value");
// ReadableProperty(false); // won't compile
AnObjectProperty(winrt::make<SomeOtherComponent>());
// Read the properties
if (ReadableProperty == false) {}
if (ReadWriteProperty != L"Foo") {}
AnObjectProperty.SomeMethod();
// C++/WinRT "getter" syntax
if (ReadableProperty() == false) {}
if (ReadWriteProperty() != L"Foo") {}
AnObjectProperty().SomeMethod();
}
};
}// Other code
// Other code can use these properties as exposed via the IDL
const auto instance = winrt::make<MyComponent>();
// Read
const auto val = instance.ReadWriteProperty();
const bool anotherVal = instance.ReadableProperty();
const auto objVal = instance.AnObjectProperty();
// Write - can only write to rw exposed via the IDL.
//
// NOTE: if your IDL specifies just `{get;}` (read-only), then external code
// will not be able to set the property, even if you implemented the property
// with `wil::single_threaded_rw_property<T>`. Externally writeable properties
// must be settable in the IDL.
instance.ReadWriteProperty(L"Hello!");
// instance.ReadableProperty(false); // fails to compile
instance.AnObjectProperty(nullptr);
// No other methods are exposed externally for these properties.// Bar.idl
runtimeclass Bar : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
// 2 properties to demonstrate the 2 syntaxes for declaring notifying properties:
String MacroThingy;
String PropertyThingy;
void DoStuff();
}// Bar.h
// Inherit from wil::notify_property_changed_base<T> to automatically implement
// INotifyPropertyChanged and add the RaisePropertyChanged(winrt::hstring const&) helper.
struct Bar : BarT<Bar>, wil::notify_property_changed_base<Bar>
{
// One-liner macro, no initialization required:
WIL_NOTIFYING_PROPERTY(hstring, MacroThingy, L"Hello!");
// Must be initialized in the constructor:
wil::single_threaded_notifying_property<hstring> PropertyThingy;
Bar() : INIT_NOTIFYING_PROPERTY(PropertyThingy, L"This works too")
{}
void DoStuff()
{
// Fires change events automatically
MacroThingy(L"A new value");
PropertyThingy(L"Change is the only constant");
// wil::single_threaded_notifying_property has operator=
// that also fires change events
PropertyThingy = L"So powerful, so majestic";
// MacroThingy = L"This won't compile";
// You can set the value without firing events
m_MacroThingy = L"Ssh, be vewy quiet";
//PropertyThingy.m_value = L"Nobody's gonna know"; // Does not work
// And you can always fire events manually
RaisePropertyChanged(L"MacroThingy");
RaisePropertyChanged(PropertyThingy.Name());
}
}In C++/WinRT, classes and ViewModels often implement observable properties via the INotifyPropertyChanged interface. Simply put, the INotifyPropertyChanged interface specifies an event called PropertyChanged, which you should fire whenever any property in your class changes. Other code (including XAML x:Bind) can listen to this event and update UI accordingly.
Thus, implementing observable properties requires 2 distinct steps:
- Your class must inherit from
Microsoft.UI.Xaml.Data.INotifyPropertyChanged(for WinUI 3 apps) orWindows.UI.Xaml.Data.INotifyPropertyChanged(for UWP apps) and implement thePropertyChangedevent. - Whenever you change a property, you must fire
PropertyChanged.
WIL includes helpers for both steps.
Note: You must import
winrt/Microsoft.UI.Xaml.Data.INotifyPropertyChanged.horwinrt/Windows.UI.Xaml.Data.INotifyPropertyChanged.hto have access to these helpers.
- If you are writing a WinUI 3 project (eg, if your controls are in the
Microsoftnamespace), usewinrt/Microsoft.UI.Xaml.Data.INotifyPropertyChanged.h- If you are writing a UWP project (eg, if your controls are in the
Windowsnamespace), usewinrt/Windows.UI.Xaml.Data.INotifyPropertyChanged.hYou will get weird compilation errors otherwise.
wil::notify_property_changed_base<T> is a base class that automatically implements INotifyPropertyChanged, exposing the internal method RaisePropertyChanged(winrt::hstring const& property).
For example, if you have a runtimeclass in IDL:
runtimeclass Foo : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
// ...
}You can inherit from wil::notify_property_changed_base<T> (where T is the class itself, see CRTP) to automatically implement that interface:
struct Foo : FooT<Foo>, wil::notify_property_changed_base<Foo>
{
// ... I can call RaisePropertyChanged(L"Bar") in my class ...
}Once you inherit from wil::notify_proprety_changed_base<T>, you have multiple options for implementing notifying properties:
// 0. Call `RaisePropertyChanged(winrt::hstring const&)` manually
m_somePropertyValue = L"changed";
RaisePropertyChanged(L"SomePropertyValue");
// 1. Declare your properties with WIL_NOTIFYING_PROPERTY
// in h:
WIL_NOTIFYING_PROPERTY(winrt::hstring, MyCoolMacroProperty, L"Default value");
// in cpp:
MyCoolMacroProperty = L"New value"; // automatically fires property changed
// 2. Declare your properties with wil::single_threaded_notifying_property<T>
// in h:
wil::single_threaded_notifying_property<winrt::hstring> MyCoolObjectProperty;
MyConstructor() : INIT_NOTIFYING_PROPERTY(MyCoolObjectProperty, L"This works too")
{} // Must be initialized in the constructor
// in cpp
MyCoolObjectProperty = L"Another value"; // automatically fires property changedSee examples above. See also XAML controls; bind to a C++/WinRT property for a plain C++ implementation.
WIL also offers handlers for implementing events.
| Handler type | Example IDL | |
|---|---|---|
wil::untyped_event<T> |
Windows::Foundation::EventHandler<T> |
event Windows.Foundation.EventHandler<String> UriFetched; |
wil::typed_event<TSender, TArgs> |
Windows::Foundation::TypedEventHandler<TSender, TArgs> |
event Windows.Foundation.TypedEventHandler<ModalPage, String> OkClicked; |
See also Author events in C++/WinRT.
Note that other event types are not supported at this time (e.g. Windows.UI.Xaml.Data.CurrentChangingEventHandler). See https://github.com/microsoft/wil/issues/354.
// EventComponent.idl
runtimeclass ComplexEventArgs {
// ...
}
runtimeclass EventComponent
{
event Windows.Foundation.EventHandler<String> SimpleEvent;
event Windows.Foundation.TypedEventHandler<EventComponent, ComplexEventArgs> ComplexEvent;
void DoStuff();
}// EventComponent.h
struct EventComponent : EventComponentT<EventComponent>
{
wil::untyped_event<winrt::hstring> SimpleEvent;
wil::typed_event<winrt::MyNamespace::EventComponent, winrt::MyNamespace::ComplexEventArgs> ComplexEvent;
void DoStuff()
{
// Fire the events!
SimpleEvent.invoke(*this, L"Args");
const auto args = winrt::make<ComplexEventArgs>();
ComplexEvent.invoke(*this, args);
}
}// Other code
// Other code can register for events, just like any other C++/WinRT code
// See https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/handle-events
const auto instance = winrt::make<EventComponent>(); // Or external code can simply call `EventComponent{}`
// Register & unregister
const auto token = instance.SimpleEvent(
[](winrt::Windows::Foundation::IInspectable const& sender, winrt::hstring const& args)
{});
instance.SimpleEvent(token);
const auto token2 = instance.ComplexEvent(
[](winrt::MyNamespace::EventComponent const& sender, winrt::MyNamespace::ComplexEventArgs const& args)
{});
instance.ComplexEvent(token2);
// Auto-revoker
const auto autoRevoker = instance.SimpleEvent(winrt::auto_revoke,
[](winrt::Windows::Foundation::IInspectable const& sender, winrt::hstring const& args) {});
const auto autoRevoker = instance.ComplexEvent(winrt::auto_revoke, [](auto sender, auto args) {});