diff --git a/include/wil/cppwinrt_authoring.h b/include/wil/cppwinrt_authoring.h index 6b85beb2..0111c04d 100644 --- a/include/wil/cppwinrt_authoring.h +++ b/include/wil/cppwinrt_authoring.h @@ -330,5 +330,117 @@ struct single_threaded_notifying_property : single_threaded_rw_property */ #define INIT_NOTIFYING_PROPERTY(NAME, VALUE) NAME(&m_propertyChanged, *this, L"" #NAME, VALUE) +namespace details +{ +#ifdef WINRT_Microsoft_UI_Xaml_Data_H + using Xaml_DependencyProperty = winrt::Microsoft::UI::Xaml::DependencyProperty; + using Xaml_PropertyChangedCallback = winrt::Microsoft::UI::Xaml::PropertyChangedCallback; + using Xaml_PropertyMetadata = winrt::Microsoft::UI::Xaml::PropertyMetadata; +#elif defined(WINRT_Windows_UI_Xaml_Data_H) + using Xaml_DependencyProperty = winrt::Windows::UI::Xaml::DependencyProperty; + using Xaml_PropertyChangedCallback = winrt::Windows::UI::Xaml::PropertyChangedCallback; + using Xaml_PropertyMetadata = winrt::Windows::UI::Xaml::PropertyMetadata; +#endif + + using Xaml_DependencyObject_GetValue = std::function; + using Xaml_DependencyObject_SetValue = std::function; + + template + inline Xaml_DependencyProperty register_dependency_property( + const std::wstring_view& propertyNameString) + { + return Xaml_DependencyProperty::Register( + propertyNameString, + winrt::template xaml_typename(), + winrt::template xaml_typename(), + nullptr); + } + + template + inline Xaml_DependencyProperty register_dependency_property( + const std::wstring_view& propertyNameString, + const DefaultValueType& defaultValue, + const Xaml_PropertyChangedCallback& propertyChangedCallback = nullptr) + { + // TODO: assert T and PropertyType are compatible + return Xaml_DependencyProperty::Register( + propertyNameString, + winrt::template xaml_typename(), + winrt::template xaml_typename(), + Xaml_PropertyMetadata{winrt::box_value(defaultValue), propertyChangedCallback}); + } +} + +//template +//struct single_threaded_dependency_property +//{ +// using Type = T; +// +// single_threaded_dependency_property( +// wil::details::Xaml_DependencyProperty const& dp, +// wil::details::Xaml_DependencyObject_GetValue const& getValue, +// wil::details::Xaml_DependencyObject_SetValue const& setValue) : +// m_dp(dp), m_getValue(getValue), m_setValue(setValue) +// { +// } +// +// template +// auto& operator()(Q&& q) +// { +// return winrt::unbox_value(m_getValue(m_dp)); +// } +// +// template +// auto& operator=(Q&& q) +// { +// m_setValue(m_dp, winrt::box_value(value)); +// } +// +//private: +// wil::details::Xaml_DependencyProperty const& m_dp{ nullptr }; +// wil::details::Xaml_DependencyObject_GetValue m_getValue{nullptr}; +// wil::details::Xaml_DependencyObject_SetValue m_setValue{nullptr}; +//}; + +#define WIL_DEFINE_DP(baseClass, type, name) \ + static wil::details::Xaml_DependencyProperty name##Property() \ + { \ + static wil::details::Xaml_DependencyProperty s_##name##Property = \ + wil::details::register_dependency_property(L"" #name); \ + return s_##name##Property; \ + } \ + auto name() const \ + { \ + return winrt::unbox_value(GetValue(name##Property())); \ + } \ + void name(type value) const \ + { \ + SetValue(name##Property(), winrt::box_value(value)); \ + } \ + static void Ensure##name##Property() \ + { \ + name##Property(); \ + } + +#define WIL_DEFINE_DP_WITH_DEFAULT_VALUE_AND_CALLBACK(baseClass, type, name, defaultValue, propertyChangedCallback) \ + static wil::details::Xaml_DependencyProperty name##Property() \ + { \ + static wil::details::Xaml_DependencyProperty s_##name##Property = \ + wil::details::register_dependency_property(L"" #name, defaultValue, propertyChangedCallback); \ + return s_##name##Property; \ + } \ + auto name() const \ + { \ + return winrt::unbox_value(GetValue(name##Property())); \ + } \ + void name(type value) const \ + { \ + SetValue(name##Property(), winrt::box_value(value)); \ + } \ + static void Ensure##name##Property() \ + { \ + name##Property(); \ + } + #endif // !defined(__WIL_CPPWINRT_AUTHORING_INCLUDED_XAML_DATA) && (defined(WINRT_Microsoft_UI_Xaml_Data_H) || defined(WINRT_Windows_UI_Xaml_Data_H)) } // namespace wil diff --git a/tests/CppWinRTAuthoringTests.cpp b/tests/CppWinRTAuthoringTests.cpp index e08e3606..06c74aff 100644 --- a/tests/CppWinRTAuthoringTests.cpp +++ b/tests/CppWinRTAuthoringTests.cpp @@ -7,8 +7,10 @@ #if _MSVC_LANG >= 201703L #include #include +#include #include #include +#include #endif #include @@ -173,6 +175,71 @@ TEST_CASE("CppWinRTAuthoringTests::InStruct", "[property]") REQUIRE(test.Prop2() == 33); } +// Simple mock of GetValue & SetValue for testing dependency properties. +struct mock_dependency_object +{ + winrt::Windows::Foundation::IInspectable GetValue(winrt::Windows::UI::Xaml::DependencyProperty const& dp) const + { + return m_values.at(dp); + } + void SetValue(winrt::Windows::UI::Xaml::DependencyProperty dp, winrt::Windows::Foundation::IInspectable const& value) const + { + // We have to do a const_cast because ordinarily SetValue is const--- + // it's delegated to WinRT, and C++/WinRT marks most of its functions + // const. + const_cast(this)->m_values[dp] = value; + } + + std::map m_values{}; +}; + +struct my_dependency_properties : mock_dependency_object +{ + WIL_DEFINE_DP(my_dependency_properties, int32_t, MyProperty); + WIL_DEFINE_DP_WITH_DEFAULT_VALUE_AND_CALLBACK(my_dependency_properties, int32_t, MyPropertyWithDefault, 42, nullptr); + WIL_DEFINE_DP(my_dependency_properties, winrt::hstring, MyStringProperty); +}; + +namespace winrt +{ + // Fake the xaml_typename specialization + template<> + inline Windows::UI::Xaml::Interop::TypeName xaml_typename() + { + static const Windows::UI::Xaml::Interop::TypeName name{hstring{L"my_dependency_properties"}, Windows::UI::Xaml::Interop::TypeKind::Custom}; + return name; + } +} + +TEST_CASE("CppWinRTAuthoringTests::DependencyProperties", "[property]") +{ + // Throws if not ensured (?) + auto obj = my_dependency_properties{}; + // REQUIRE_FAILFAST_MSG(E_NOTIMPL, [&obj] { obj.MyProperty(); }); + // REQUIRE_FAILFAST_MSG(E_NOTIMPL, [&obj] { obj.MyProperty(42); }); + // REQUIRE_FAILFAST_MSG(E_NOTIMPL, [&obj] { obj.MyPropertyWithDefault(); }); + // REQUIRE_FAILFAST_MSG(E_NOTIMPL, [&obj] { obj.MyPropertyWithDefault(42); }); + // REQUIRE_FAILFAST_MSG(E_NOTIMPL, [&obj] { obj.MyStringProperty(); }); + // REQUIRE_FAILFAST_MSG(E_NOTIMPL, [&obj] { obj.MyStringProperty(L"foo"); }); + + // Register the dependency property + my_dependency_properties::EnsureMyPropertyProperty(); + my_dependency_properties::EnsureMyPropertyWithDefaultProperty(); + my_dependency_properties::EnsureMyStringPropertyProperty(); + + // Now it should work + obj.MyProperty(42); + REQUIRE(obj.MyProperty() == 42); + + // TODO: handle defaults + // REQUIRE(obj.MyPropertyWithDefault() == 42); + obj.MyPropertyWithDefault(43); + REQUIRE(obj.MyPropertyWithDefault() == 43); + + obj.MyStringProperty(L"foo"); + REQUIRE(obj.MyStringProperty() == L"foo"); +} + #ifdef WINRT_Windows_Foundation_H TEST_CASE("CppWinRTAuthoringTests::Events", "[property]") {