The MVVM Design Pattern is used quite commonly in XAML-based UI applications. This library, known as mvvm-winrt provides a very lightweight set of classes and macros to simplify the implementation of MVVM classes.
The mvvm-winrt library is implemented as an all-header template library that uses static (compile-time) polymorphism for greatest efficiency, especially with optimized compilation.
#include <mvvm/notify_property_changed.h>template <typename Derived>
struct __declspec(empty_bases) notify_property_changedDerived
: The most-derived class; the one that you're implementing.
This file implements the notify_property_changed<Derived> class template, which implements a base
implementation of INotifyPropertyChanged. Although that interface only declares 1 member (the
PropertyChanged event), this class also provides protected methods to greatly simplify property
setters and getters for most common scenarios:
- Setter implementations that return a
boolindicating whether or not the property changed. : See theset_propertyoverloads that return aboolvalue:
template <typename Value> // On each of these
bool set_property(Value& valueField, Value const& newValue);
bool set_property(Value& valueField, Value const& newValue, Value& oldValue);
bool set_property(Value& valueField, Value const& newValue, std::wstring_view const& propertyName);
bool set_property(Value& valueField, Value const& newValue, Value& oldValue, std::wstring_view const& propertyName);
bool set_property(Value& valueField, Value const& newValue, std::initializer_list<const std::wstring_view> propertyNames);
bool set_property(Value& valueField, Value const& newValue, Value& oldValue, std::initializer_list<const std::wstring_view> propertyNames);- Setter implementations that return the previous value of the backing field.
See the
set_property*overloads that take a reference parameter tooldValue:
template <typename Value> // On each of these
bool set_property(Value& valueField, Value const& newValue, Value& oldValue);
bool set_property(Value& valueField, Value const& newValue, Value& oldValue, std::wstring_view const& propertyName);
bool set_property(Value& valueField, Value const& newValue, Value& oldValue, std::initializer_list<const std::wstring_view> propertyNames);
void set_property_no_compare(Value& valueField, Value const& newValue, Value& oldValue, std::wstring_view const& propertyName);
void set_property_no_compare(Value& valueField, Value const& newValue, Value& oldValue, std::initializer_list<const std::wstring_view> propertyNames);
void set_property_no_compare_no_notify(Value& valueField, Value const& newValue, Value& oldValue);- Setter implementations that always raise the
PropertyChangedevent, regardless of the value changing: : See theset_property_no_compare*methods:
template <typename Value> // On each of these
void set_property_no_compare(Value& valueField, Value const& newValue, std::wstring_view const& propertyName);
void set_property_no_compare(Value& valueField, Value const& newValue, Value& oldValue, std::wstring_view const& propertyName);
void set_property_no_compare(Value& valueField, Value const& newValue, std::initializer_list<const std::wstring_view> propertyNames);
void set_property_no_compare(Value& valueField, Value const& newValue, Value& oldValue, std::initializer_list<const std::wstring_view> propertyNames);
void set_property_no_compare_no_notify(Value& valueField, Value const& newValue);
void set_property_no_compare_no_notify(Value& valueField, Value const& newValue, Value& oldValue);- Setter implementations that never raise the
PropertyChangedevent:
template <typename Value> // On each of these
bool set_property(Value& valueField, Value const& newValue);
bool set_property(Value& valueField, Value const& newValue, Value& oldValue);
void set_property_no_compare_no_notify(Value& valueField, Value const& newValue);
void set_property_no_compare_no_notify(Value& valueField, Value const& newValue, Value& oldValue);- Setter implementations that raise the
PropertyChangedevent for a single property name:
template <typename Value> // On each of these
bool set_property(Value& valueField, Value const& newValue, std::wstring_view const& propertyName);
bool set_property(Value& valueField, Value const& newValue, Value& Value, std::wstring_view const& propertyName);
void set_property_no_compare(Value& valueField, Value const& newValue, std::wstring_view const& propertyName);
void set_property_no_compare(Value& valueField, Value const& newValue, Value& oldValue, std::wstring_view const& propertyName);- Setter implementations that raise the
PropertyChangedevent for a list of property names:
template <typename Value> // On each of these
bool set_property(Value& valueField, Value const& newValue, std::initializer_list<const std::wstring_view> propertyNames);
bool set_property(Value& valueField, Value const& newValue, Value& Value, std::initializer_list<const std::wstring_view> propertyNames);
void set_property_no_compare(Value& valueField, Value const& newValue, std::initializer_list<const std::wstring_view> propertyNames);
void set_property_no_compare(Value& valueField, Value const& newValue, Value& oldValue, std::initializer_list<const std::wstring_view> propertyNames);- Extensible to allow derived classes to provide their own flavor of thread synchronization.
See the following
view_model_baseclass for a discussion.
#include <mvvm/view_model_base.h>This class adds a dependency upon a Dispatcher property in the Derived class. Note that this
class does not actually declare or implement this property. The derived class is responsible for this. See
the classes below for implementations that do that.
The get_property_core and set_property_core methods perform the logic of getting and setting
the backing variables. This base method should be called withing whatever thread synchronization context is
appropriate for the scenario of the derived class. One such derivation is the view_model_base class
template. It overrides the get_property_override and set_property_override methods to call
the get_property_core and set_property_core methods from within a CoreDispatcher
context.
#include <mvvm/view_model.h>This class adds the Dispatcher property used by its base class, view_model_base<Derived>.
#include <mvvm/view.h>This class should be a mix-in to a UIElement-derived class, usually UserControl. It also adds
a ViewModel property backed by a member variable.
#include <mvvm/view_sync_data_context.h>This class should be a mix-in to a UIElement-derived class, usually UserControl. It also adds
a ViewModel property that synchronizes itself with the DataContext property of
a UIElement-derived class, to which this class is a mix-in. Synchronizing the DataContext with
the ViewModel property is sometimes a useful feature from XAML.
#include <mvvm/delegate_command.h>template <typename Parameter>
struct delegate_command;Parameter
: The type of data used by the command. If the command does not require data, this can be void.
This class implements the command pattern, implementing ICommand by deferring to lambda expressions
provided to the constructor. This pattern is used less often now since {x:Bind} can bind
directly to a method. It may still be useful in cases where the CanExecute functionality is desired.
It's also provided here for completeness of an MVVM base library.
To create an instance, use the winrt::make_self function and store the returned object in a
winrt::com_ptr. Like this:
// in the .h file:
winrt::com_ptr<::mvvm::delegate_command<void>> m_incrementCommand{ nullptr };
// in the .cpp file:
m_incrementCommand = winrt::make_self<::mvvm::delegate_command<void>>(
[this]() { MyProperty(MyProperty() + 1); },
[this]() { return IsIncrementAvailable(); } );
// then return it (usually as a property getter) as an ```ICommand``` interface:
ICommand MyEntityViewModel::IncrementCommand()
{
return *m_incrementCommand;
}// Constructors
delegate_command() noexcept {}
delegate_command(std::nullptr_t) noexcept {}
template <typename ExecuteHandler>
delegate_command(ExecuteHandler&& executeHandler);
template <typename ExecuteHandler, typename CanExecuteHandler>
delegate_command(ExecuteHandler executeHandler, CanExecuteHandler&& canExecuteHandler);
// ICommand implementation
event_token CanExecuteChanged(EventHandler<Windows::Foundation::IInspectable> const& handler);
void CanExecuteChanged(event_token token);
bool CanExecute(IInspectable const& parameter);
void Execute(IInspectable const& parameter);
// Raises the CanExecuteChanged event
void raise_CanExecuteChanged();#include <mvvm/name_of.h>NAME_OF(typeName, propertyName)
NAME_OF_NARROW(typeName, propertyName)typeName
| The class or struct that implements the property. This is not a string value.
propertyName
| The name of the property. This is not a string value - only the plain text name of the property.
NAME_OF (and NAME_OF_NARROW for 8-bit characters) can be used to get the name of a property as a
wstring_view or string_view constexpr. This is commonly needed when checking which
property changed upon receiving a PropertyChanged event, and also when raising that event from a view
model. It will enforce at compile time that propertyName is a member of typeName, then return
propertyName as a constexpr wstring_view (or string_view). There is no additional
runtime overhead to using this as opposed to hard coding string literals directly or in global variables when
optimized, because the Microsoft compiler de-duplicates string literals when O1 or O2 are enabled. The macros
work by using a ternary operator which the compiler can understand at compilation will always be false,
leaving only the string_view to propertyName. Because these return a wstring_view or
string_view, the result can be directly compared to a wchar_t* or char*, respectively
(whether const or not).
DEFINE_EVENT(type, name)
DEFINE_EVENT_WITH_RAISE(eventType, name, argsType)type
| The event handler type, such as RoutedEventHandler.
name
| The event name.
argsType
| The event parameter type, such as RoutedEventArgs.
These macros each expand to code similar to the following, where ##type## and ##name##
are substitued with the specified macro parameters.
private:
winrt::event<type> m_event##name;
public:
winrt::event_token name##(type const& handler)
{
return m_event##name.add(handler);
}
void name##(winrt::event_token token)
{
m_event##name.remove(token);
}When DEFINE_EVENT_WITH_RAISE is used, it does the above but also expands to a method with the
following naming pattern, signature, and implementation:
void raise_##name##(argsType const& args = {nullptr})
{
if (m_event##name)
{
m_event##name(*this, args);
}
}This raise_##name## method will pass the args parameter to the event subscribers as the
2nd parameter, with the 1st parameter being *this. Therefore the macro only works for event
handler delegates with two paramers, sender and args.
#include <mvvm/property_macros.h>DEFINE_PROPERTY(type, name, defaultValue)
DEFINE_PROPERTY_PRIVATE_SET(type, name, defaultValue)
DEFINE_PROPERTY_PROTECTED_SET(type, name, defaultValue)
DEFINE_PROPERTY_READONLY(type, name, defaultValue)type
: The type of the property.
name
: The name of the property. It is recommended to use the NAME_OF macro described below.
defaultValue
: Use {} for the type's default value.
This set of macros declares and implements a property's get and set accessors, as well
as the backing data field. They are implemented with the assumption that the class is derived from,
directly or indirectly, notify_property_changed class.
#include <mvvm/property_macros.h>
DEFINE_PROPERTY_CALLBACK(type, name, defaulValue)
DEFINE_PROPERTY_CALLBACK_PRIVATE_SET(type, name, defaulValue)
DEFINE_PROPERTY_CALLBACK_PROTECTED_SET(type, name, defaulValue)These macros declare a protected instance method with the following naming pattern and signature, where
##name## is the name of the property. The class should implement
this method in its CPP file, or in the H file below the class:
void On##name##Changed(type const& oldValue, type const& newValue);#include <mvvm/property_macros.h>
DEFINE_PROPERTY_NO_NOTIFY(type, name, defaulValue)
DEFINE_PROPERTY_NO_NOTIFY_PRIVATE_SET(type, name, defaulValue)
DEFINE_PROPERTY_NO_NOTIFY_PROTECTED_SET(type, name, defaulValue)#include <mvvm/property_macros.h>
DEFINE_PROPERTY_NO_COMPARE(type, name, defaulValue)
DEFINE_PROPERTY_NO_COMPARE_PRIVATE_SET(type, name, defaulValue)
DEFINE_PROPERTY_NO_COMPARE_PROTECTED_SET(type, name, defaulValue)