-
Notifications
You must be signed in to change notification settings - Fork 235
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 thePropertyChanged
event. - 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.h
orwinrt/Windows.UI.Xaml.Data.INotifyPropertyChanged.h
to have access to these helpers.
- If you are writing a WinUI 3 project (eg, if your controls are in the
Microsoft
namespace), usewinrt/Microsoft.UI.Xaml.Data.INotifyPropertyChanged.h
- If you are writing a UWP project (eg, if your controls are in the
Windows
namespace), usewinrt/Windows.UI.Xaml.Data.INotifyPropertyChanged.h
You 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 changed
See 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) {});