0

The demo project contains a simple GUI with a button and a derived user control MyUserControl. MyUserControl only contains a border whose background is bound to the background of the user control:

void MyUserControl::CreateGui()
{
    winrt::Microsoft::UI::Xaml::Controls::Border b;
    Content(b);
    b.BorderThickness(winrt::Microsoft::UI::Xaml::Thickness{ 1, 1, 1, 1 });
    b.BorderBrush(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush(winrt::Windows::UI::Colors::Black()));

    winrt::Microsoft::UI::Xaml::Data::Binding binding;
    binding.Source(*this);
    binding.Path(winrt::Microsoft::UI::Xaml::PropertyPath(L"Background"));
    b.SetBinding(winrt::Microsoft::UI::Xaml::Controls::Border::BackgroundProperty(), binding);
}

MyUserControl also registers its own DependencyProperty AnotherBrush of type Brush:

void MyUserControl::InitClass()
{
    mAnotherBrushProperty =
        Microsoft::UI::Xaml::DependencyProperty::Register(
            L"AnotherBrush",
            winrt::xaml_typename<winrt::Microsoft::UI::Xaml::Media::Brush>(),
            winrt::xaml_typename<winrt::UnpackagedDesktopApp::MyUserControl>(),
            Microsoft::UI::Xaml::PropertyMetadata(nullptr)
        );
}

InitClass is called in App::OnLaunched before MainWindow is created. When the MainWindow is initialized, the brushes of the MyUserControl instance are set:

mUC = winrt::UnpackagedDesktopApp::MyUserControl();
sp.Children().Append(mUC);
mUC.Width(200);
mUC.Height(100);
mUC.Background(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush(winrt::Windows::UI::Colors::GreenYellow()));
mUC.AnotherBrush(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush(winrt::Windows::UI::Colors::Red()));
mUC.Foreground(winrt::Microsoft::UI::Xaml::Media::SolidColorBrush(winrt::Windows::UI::Colors::CornflowerBlue()));

When the button is pressed, the background of the MyUserControl instance is bound to its own dependency property AnotherBrush:

void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    auto checkAnotherBrush = mUC.AnotherBrush();

    winrt::Microsoft::UI::Xaml::Data::Binding binding;
    binding.Source(mUC);
    binding.Path(winrt::Microsoft::UI::Xaml::PropertyPath(L"AnotherBrush")); //doesn't work!!
    //binding.Path(winrt::Microsoft::UI::Xaml::PropertyPath(L"Foreground")); //works
    mUC.SetBinding(winrt::Microsoft::UI::Xaml::Controls::Control::BackgroundProperty(), binding);

    auto checkUCBackground = mUC.Background();

    mBtn.Content(box_value(L"Clicked"));
}

The background of MyUserControl should now have the same color as AnotherBrush (i.e. red). When debugging, you can see from the variable checkAnotherBrush that AnotherBrush is actually set. However, the variable checkUCBackground shows that the background is unexpectedly NULL after the binding has been applied. However, if the background is bound to the built-in Foreground property instead, it works!

The demo app can be downloaded here: DemoApp.zip

Since this is a basic functionality, I suspect that it is not a bug in the framework, but that I am doing something wrong. But what?

Here are the test conditions:

  • WinUI 3 - Windows App SDK 1.5.3: 1.5.240428000
  • Windows 10 (21H2): Build 19044
  • Microsoft Visual Studio Community 2022 (64-bit) - Current Version 17.10.0

Update:

Following YangXiaoPo's advice, I have also implemented the INotifyPropertyChanged and extended MyUserControl as follows:

//MyUserControl.idl
runtimeclass MyUserControl : Microsoft.UI.Xaml.Controls.UserControl, Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
    ...

    void NotifyPropertyChanged(String propertyName);
}

//MyUserControl.h
struct MyUserControl : MyUserControlT<MyUserControl>
 {
     ...

     static void OnAnotherBrushChanged(Microsoft::UI::Xaml::DependencyObject d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs e);

     winrt::event_token PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
     void PropertyChanged(winrt::event_token const& token) noexcept;
     winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> mPropertyChanged;
     void NotifyPropertyChanged(hstring const& propertyName);
 };

//MyUserControl.cpp
void MyUserControl::InitClass()
{
    mAnotherBrushProperty =
        Microsoft::UI::Xaml::DependencyProperty::Register(
            L"AnotherBrush",
            winrt::xaml_typename<winrt::Microsoft::UI::Xaml::Media::Brush>(),
            winrt::xaml_typename<winrt::UnpackagedDesktopApp::MyUserControl>(),
            Microsoft::UI::Xaml::PropertyMetadata{ nullptr, Microsoft::UI::Xaml::PropertyChangedCallback{ &MyUserControl::OnAnotherBrushChanged } }
        );
}

...

void MyUserControl::OnAnotherBrushChanged(Microsoft::UI::Xaml::DependencyObject d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs e)
{
    d.as<winrt::UnpackagedDesktopApp::MyUserControl>().NotifyPropertyChanged(L"AnotherBrush");
}

 winrt::event_token MyUserControl::PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
 {
     return mPropertyChanged.add(handler);
 }

 void MyUserControl::PropertyChanged(winrt::event_token const& token) noexcept
 {
     mPropertyChanged.remove(token);
 }

 void MyUserControl::NotifyPropertyChanged(hstring const& propertyName)
 {
     mPropertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ propertyName });
 }

The NotifyPropertyChanged method is called correctly, but this does not change anything. The value of Background is still set to NULL after the binding has been applied.

9
  • runtimeclass MyUserControl needs to implement INotifyPropertyChanged. Commented Jun 7, 2024 at 5:09
  • @YangXiaoPo: Thank you for your help. I actually thought that this was not necessary for a DependencyProperty. However. I have adapted the code accordingly (see update). Unfortunately, this has not changed anything. Commented Jun 10, 2024 at 6:36
  • INotifyPropertyChanged could be unrelated. You may have a look at this document Build XAML controls with C++/WinRT. Commented Jun 11, 2024 at 5:56
  • @YangXiaoPo: Checked it. The only significant differences in my app are the generation of the GUI in the code (CreateGui instead of Generic.xaml) and the binding over two levels (i.e. the background of the control is not set directly as in the documentation, but bound to AnotherBrush). The latter is the problem, because if I use the following code instead, it works: void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&) { mUC.Background(mUC.AnotherBrush()); mBtn.Content(box_value(L “Clicked”)); } What is wrong with this binding? Commented Jun 11, 2024 at 13:56
  • I suppose it will work if you TemplateBinding your Border background to MyUserControl background. If it works, the clue is TemplateBinding. Commented Jun 12, 2024 at 1:03

1 Answer 1

0

see [https://github.com/microsoft/microsoft-ui-xaml/issues/9678][1]

Answered by Evelyn Wu (Miscrosoft):

Because your custom control is neither used in XAML markup nor is its IDL declaration decorated with the [bindable] attribute, XamlCompiler does not know about it and therefore does not generate any type information for it. As a result, from the framework's perspective at run-time there is no type MyUserControl with a property AnotherBrush.

You can resolve this in one of two ways (and be sure to add #include "MyUserControl.h" to App.xaml.h to avoid a build break):

  1. Decorate the declaration of MyUserControl in MyUserControl.idl with a [bindable] attribute

  2. Add an instance of MyUserControl to one of the project's .xaml files.

Sign up to request clarification or add additional context in comments.

1 Comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.