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.
MyUserControlneeds to implementINotifyPropertyChanged.INotifyPropertyChangedcould be unrelated. You may have a look at this document Build XAML controls with C++/WinRT.TemplateBindingyourBorderbackground toMyUserControlbackground. If it works, the clue isTemplateBinding.