Основным паттерном при разработке UI приложений для Windows Runtime является MVVM. В документации говорится, что объектом привязки может быть объект CLR, объект пользовательского интерфейса, объект среды выполнения Windows Runtime(если у него есть атрибут BindableAttribute или если он реализует ICustomPropertyProvider).
Наиболее простым сценарием при разработке приложений является добавление атрибута BindableAttribute к классу ViewModel и реализация интерфейса INotifyPropertyChanged. Если интересно, как это сделать с помощью MIDL, C++ и WRL, то добро пожаловать под кат.
Описание интерфейсов
Создадим idl файл и опишем интерфейсы.
import "inspectable.idl";
import "windows.ui.xaml.customattributes.idl";
import "windows.ui.xaml.data.idl";
#define VERSION 0x00000001
namespace DataBinding
{
interface INumber;
interface INumberFactory;
runtimeclass Number;
}
namespace DataBinding
{
[exclusiveto(Number)]
[uuid(5b197688-2f57-4d01-92cd-a888f10dcd90)]
[version(VERSION)]
interface INumber : IInspectable
{
[propget]
HRESULT Value([out, retval] INT32* value);
[propput]
HRESULT Value([in] INT32 value);
}
[exclusiveto(Number)]
[uuid(baec017b-23ec-46d3-8d67-78bf0af1a9f1)]
[version(VERSION)]
interface INumberFactory : IInspectable
{
};
[activatable(1.0)]
[bindable]
[marshaling_behavior(agile)]
[threading(both)]
[version(VERSION)]
runtimeclass Number
{
[default] interface INumber;
interface Windows.UI.Xaml.Data.INotifyPropertyChanged;
}
}
В пространстве имен DataBinding мы определяем несколько объектов. Первый — это эксклюзивный интерфейс для класса. Данный интерфейс имеет единственное целочисленное свойство Value, доступное для чтения и записи. Второй — это интерфейс фабрики для создания объекта класса. Третий- это класс объекта, который реализует два интерфейса. Атрибут activatable класса Number указывают, что объект данного класса может быть создан без передачи параметров фабрике. Атрибут bindable — это как раз тот самый BindableAttribute, который необходим для работы механизма привязки данных. Описание данного атрибута содержится в файле «windows.ui.xaml.customattributes.idl», поэтому данный файл импортируется во второй строчке кода. Скомпилировав файл, получим заголовочный файл, который потом необходимо будет подключить в нашем файле с кодом.
Реализация интерфейсов
Большую часть работы по описанию интерфейсов за нас выполняет MIDL компилятор при создании заголовочного файла, а использование WRL упрощает реализацию заданных интерфейсов за счёт определения макросов и шаблонных классов. Допустимый код может выглядеть так:
#include <wrl.h>
#include <wrl/wrappers/corewrappers.h>
#include <wrl/event.h>
#include "DataBinding_h.h"
using ABI::DataBinding::INumber;
using ABI::DataBinding::INumberFactory;
using ABI::Windows::UI::Xaml::Data::IPropertyChangedEventArgsFactory;
using ABI::Windows::UI::Xaml::Data::INotifyPropertyChanged;
using ABI::Windows::UI::Xaml::Data::IPropertyChangedEventHandler;
using ABI::Windows::UI::Xaml::Data::IPropertyChangedEventArgs;
using ABI::Windows::UI::Xaml::Data::PropertyChangedEventArgs;
using Microsoft::WRL::RuntimeClassFlags;
using Microsoft::WRL::RuntimeClassType;
using Microsoft::WRL::EventSource;
using Microsoft::WRL::Make;
using Microsoft::WRL::RuntimeClass;
using Microsoft::WRL::ActivationFactory;
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::Wrappers::HStringReference;
class Number sealed : public RuntimeClass < RuntimeClassFlags<RuntimeClassType::WinRt>, INumber, INotifyPropertyChanged >
{
InspectableClass(RuntimeClass_DataBinding_Number, BaseTrust);
private:
INT32 _value;
EventSource<IPropertyChangedEventHandler> _notifyEventSource;
ComPtr<IPropertyChangedEventArgs> _valueChangedEventArgs;
public:
Number()
: _value(0)
{
ComPtr<IPropertyChangedEventArgsFactory> propertyChangedEventArgsFactory;
RoGetActivationFactory(
HStringReference(RuntimeClass_Windows_UI_Xaml_Data_PropertyChangedEventArgs).Get(),
ABI::Windows::UI::Xaml::Data::IID_IPropertyChangedEventArgsFactory,
reinterpret_cast<void**>(propertyChangedEventArgsFactory.GetAddressOf()));
ComPtr<IInspectable> inner;
propertyChangedEventArgsFactory->CreateInstance(
HStringReference(L"Value").Get(),
nullptr,
reinterpret_cast<IInspectable**>(_valueChangedEventArgs.GetAddressOf()),
_valueChangedEventArgs.GetAddressOf());
}
virtual HRESULT STDMETHODCALLTYPE get_Value(INT32* value) override
{
*value = _value;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE put_Value(INT32 value) override
{
_value = value;
_notifyEventSource.InvokeAll(reinterpret_cast<IInspectable*>(this), _valueChangedEventArgs.Get());
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE add_PropertyChanged(IPropertyChangedEventHandler* handler, EventRegistrationToken* token) override
{
return _notifyEventSource.Add(handler, token);
}
virtual HRESULT STDMETHODCALLTYPE remove_PropertyChanged(EventRegistrationToken token) override
{
return _notifyEventSource.Remove(token);
}
};
class NumberFactory : public ActivationFactory < INumberFactory >
{
InspectableClassStatic(RuntimeClass_DataBinding_Number, BaseTrust);
public:
virtual HRESULT STDMETHODCALLTYPE ActivateInstance(IInspectable** instance) override
{
*instance = reinterpret_cast<IInspectable*>(Make<Number>().Detach());
return nullptr != *instance ? S_OK : E_OUTOFMEMORY;
}
};
ActivatableClassWithFactory(Number, NumberFactory);
Данный код определяет два класса. Первый — тот самый runtime class Number. Реализует интерфейсы INumber и INotifyPropertyChanged, переопределяя pure virtual методы интерфейсов. Второй — класс фабрики, для создания объектов класса Number. Для успешной компиляции кода необходимо существования заголовочного файла «windows.ui.xaml.customattributes.h», так как директива его включения автоматически вставляется MIDL компилятором в подключаемый заголовочный файл(но можно и вручную удалить эту директиву из кода h-файла). Я его создал в директории $(WindowsSDK_MetadataPath).
Проверка
Скомпилировав код компонента, можно его использовать в любом проекте для Windows Runtime. Я тестировал в C# проекте. Создал простое представление, отображающие значение свойства Value объекта с помощью DataBinding, и изменил значение свойства объекта по таймеру(3 секунды, чтобы элемент успел отобразиться с начальным значением свойства).
Автор: altk