In reference to my question about TreeView, I was suggested to use data binding to populate the items of the TreView.
I create a new Black App, Packaged, WinUI3 in Desktop C++ VS 2022 project.
This runs and displays the button
I change the MainWindow IDL:
namespace App1
{
[default_interface]
runtimeclass MainWindow : Microsoft.UI.Xaml.Window, Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
MainWindow();
String MyProperty;
}
}
I change the .h
namespace winrt::App1::implementation
{
struct MainWindow : MainWindowT<MainWindow>
{
winrt::hstring j = L"TEXT 1";
MainWindow()
{
// Xaml objects should not call InitializeComponent during construction.
// See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent
}
winrt::hstring MyProperty();
void MyProperty(winrt::hstring);
void myButton_Click(IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
winrt::event_token PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& value);
void PropertyChanged(winrt::event_token const& token);
winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
};
}
namespace winrt::App1::factory_implementation
{
struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
{
};
}
I change the .cpp:
#include "pch.h"
#include "MainWindow.xaml.h"
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif
using namespace winrt;
using namespace Microsoft::UI::Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace winrt::App1::implementation
{
winrt::hstring MainWindow::MyProperty()
{
return j;
}
void MainWindow::MyProperty(winrt::hstring nv)
{
j = nv;
m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Content" });
}
winrt::event_token MainWindow::PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
return m_propertyChanged.add(handler);
}
void MainWindow::PropertyChanged(winrt::event_token const& token)
{
m_propertyChanged.remove(token);
}
void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)
{
MyProperty(L"YO");
}
}
And finally, the XAML:
<Button x:Name="myButton" Click="myButton_Click" Content="{x:Bind MyProperty, Mode=OneWay}"></Button>
This only works once. It once calls my property and sets the "TEXT 1" to the button, but when myButton_Click
is called the call to
m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Content" });
does nothing. The button doesn't update its content.
What am I doing wrong?
EDIT: When I do m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"" });
, that is, no "Content" string, it works.
Why?
It doesn't work both ways because you specified OneWay
mode (actually I don't really know why as it should work w/o it...):
<Button x:Name="myButton" Click="myButton_Click" Content="{x:Bind MyProperty, Mode=OneWay}"></Button>
But you can declare it like this:
<Button x:Name="myButton" Click="myButton_Click" Content="{x:Bind MyProperty, Mode=TwoWay}"></Button>
But when you do this with C++/WinRT, you may get this type of error (note in .NET and C#, it would work just fine):
MainWindow.xaml.g.hpp(155,71): error C2665: 'winrt::to_hstring': no overloaded function could convert all the argument types
This is because Content
is of Object
type in IDL (aka IInspectable
) while your property is of String
type in IDL (aka hstring
) and there's no implicit conversion brought by C++/WinRT
, see also https://github.com/microsoft/cppwinrt/issues/942
So you can fix it creating this conversion, for example (mimicking C++/WinRT's base.h
to_hstring
overloads) for example in MainWindow.xaml.h:
WINRT_EXPORT namespace winrt
{
inline hstring to_hstring(IInspectable value)
{
auto pv = value.try_as<IPropertyValue>();
if (pv && pv.Type() == PropertyType::String)
return pv.GetString();
return L"???";
}
}
Also make sure your property is protected against infinite loop:
void MainWindow::MyProperty(hstring value)
{
if (_myProperty == value)
return;
_myProperty = value;
RaisePropertyChanged(L"MyProperty");
}
I have created a sample app here: https://github.com/smourier/WinUI3Cpp