Search code examples
c++xamlwindows-runtimewinui-3c++-winrt

WinUI3 C++ Data Binding


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

enter image description here

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?


Solution

  • 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