C++/WinRT WinUI3中如何获取自定义标题栏元素实时坐标
C++/WinRT WinUI3 获取元素坐标的问题
问题描述
- 调用
m_mainWindow.MyBut().ActualOffset()或m_mainWindow.MyBut().TransformToVisual()获取的坐标值始终为0,仅当窗口尺寸改变时,坐标属性值才会更新为正确值。 - 已查阅Button相关官方文档,希望了解如何正确获取元素坐标,不确定是否存在C++/WinRT与WinUI的适配问题。
相关代码
#include "pch.h" #include "TitlebarPage.xaml.h" #if __has_include("TitlebarPage.g.cpp") #include "TitlebarPage.g.cpp" #endif using namespace winrt; using namespace Windows::UI; using namespace Microsoft::UI::Xaml; using namespace Microsoft::UI::Xaml::Controls; using namespace Microsoft::UI::Xaml::Navigation; using namespace Microsoft::UI::Input; namespace winrt::SampleApp::implementation { winrt::hstring defaultTitle = L"WinUI C++ Desktop sample App"; TitlebarPage::TitlebarPage() { InitializeComponent(); TitlebarCustomBtn().PointerPressed({this, &TitlebarPage::TitlebarCustomBtn_Click}); TitlebarCustomBtn().PointerPressed({ this, &TitlebarPage::TitlebarCustomBtn_Click }); } void TitlebarPage::OnNavigatedTo(NavigationEventArgs const& e) { m_mainWindow = e.Parameter().as<MainWindow>(); AppWindow appWindow = m_mainWindow.MyAppWindow(); m_appWindow = appWindow; m_changedToken = m_appWindow.Changed({ this, &TitlebarPage::AppWindowChangedHandler }); } void TitlebarPage::AppWindowChangedHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Windowing::AppWindowChangedEventArgs const& e) { if (e.DidSizeChange() && sender.as<AppWindow>().TitleBar().ExtendsContentIntoTitleBar()) { SetCustomTitleBarDragRegion(); } } void TitlebarPage::TitlebarBrandingBtn_Click( winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& /*e*/) { m_appWindow.TitleBar().ResetToDefault(); m_mainWindow.MyTitleBar(). Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); m_brandTitleBar = !m_brandTitleBar; if (AppWindowTitleBar::IsCustomizationSupported() && m_brandTitleBar) { m_appWindow.Title(L"Default titlebar with custom color customization"); m_appWindow.TitleBar().ForegroundColor(Colors::Transparent()); m_appWindow.TitleBar().BackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().InactiveBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().InactiveForegroundColor(Colors::Transparent()); //Buttons m_appWindow.TitleBar().ButtonBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonForegroundColor(Colors::Transparent()); m_appWindow.TitleBar(). ButtonInactiveBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar(). ButtonInactiveForegroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonHoverBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonHoverForegroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonPressedBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonPressedForegroundColor(Colors::Transparent()); } else { m_appWindow.TitleBar().ResetToDefault(); m_appWindow.Title(defaultTitle); } } void TitlebarPage::TitlebarCustomBtn_Click(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& /*e*/) { if (AppWindowTitleBar::IsCustomizationSupported() && !m_customTitleBar) { m_customTitleBar = true; m_appWindow.TitleBar().ExtendsContentIntoTitleBar(true); // Show the custom titlebar m_mainWindow.MyTitleBar(). Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); // Set Button colors to match the custom titlebar m_appWindow.TitleBar().ButtonBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonForegroundColor(Colors::Gray()); m_appWindow.TitleBar(). ButtonInactiveBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonInactiveForegroundColor(Colors::Gray()); m_appWindow.TitleBar().ButtonHoverBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonHoverForegroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonPressedBackgroundColor(Colors::Transparent()); m_appWindow.TitleBar().ButtonPressedForegroundColor(Colors::Gray()); m_appWindow.TitleBar(). IconShowOptions(IconShowOptions::HideIconAndSystemMenu); //Set the drag region for the custom titlebar SetCustomTitleBarDragRegion(); } else { // Bring back the default titlebar m_customTitleBar = false; m_mainWindow.MyTitleBar(). Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed); m_appWindow.TitleBar().ResetToDefault(); } } void TitlebarPage::ResetTitlebarBtn_Click(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& /*e*/) { m_mainWindow.MyTitleBar().Visibility( winrt::Microsoft::UI::Xaml::Visibility::Collapsed); m_appWindow.TitleBar().ResetToDefault(); m_appWindow.Title(defaultTitle); } void TitlebarPage::SetCustomTitleBarDragRegion() { double scaleAdjustment = m_mainWindow.MyTitleBar().XamlRoot().RasterizationScale(); double scaleAdjustment2 =m_mainWindow.MyBut().XamlRoot().RasterizationScale(); GeneralTransform transform = m_mainWindow.MyBut().TransformToVisual(nullptr); static Rect bunds; bunds = transform.TransformBounds(Rect(0, 0, m_mainWindow.MyBut().ActualWidth(), m_mainWindow.MyBut().ActualHeight())); std::vector<Windows::Graphics::RectInt32> dragRects; Windows::Graphics::RectInt32 MyButRect; MyButRect.X = static_cast<int>(bunds.X * scaleAdjustment); MyButRect.Y = static_cast<int>(bunds.Y * scaleAdjustment); MyButRect.Height = static_cast<int>(bunds.Height * scaleAdjustment); MyButRect.Width = static_cast<int>(bunds.Width * scaleAdjustment); dragRects.push_back(MyButRect); InputNonClientPointerSource nonClientInputSrc = InputNonClientPointerSource::GetForWindowId(m_appWindow.Id()); nonClientInputSrc.SetRegionRects(NonClientRegionKind::Passthrough, dragRects); } }
解决方案
问题根源
调用坐标获取方法时,目标元素尚未完成布局渲染,此时获取到的是初始默认值0;窗口尺寸改变会触发UI布局更新,元素完成渲染后才能拿到正确坐标。
具体修复步骤
延迟到元素加载完成后获取坐标
在元素的Loaded事件中执行坐标获取逻辑,确保UI已完成初始化:// 在TitlebarPage构造函数中添加 TitlebarPage::TitlebarPage() { InitializeComponent(); TitlebarCustomBtn().PointerPressed({this, &TitlebarPage::TitlebarCustomBtn_Click}); // 注册MyBut的Loaded事件 m_mainWindow.MyBut().Loaded([this](auto const&, auto const&) { this->SetCustomTitleBarDragRegion(); }); }监听布局变化动态更新坐标
若元素位置可能动态改变,监听LayoutUpdated事件,每次布局更新后重新计算坐标:// 在OnNavigatedTo中添加 void TitlebarPage::OnNavigatedTo(NavigationEventArgs const& e) { m_mainWindow = e.Parameter().as<MainWindow>(); // ... 现有代码 ... // 注册布局更新事件 m_mainWindow.MyBut().LayoutUpdated([this](auto const&, auto const&) { this->SetCustomTitleBarDragRegion(); }); }修正
SetCustomTitleBarDragRegion方法
移除静态变量避免缓存旧值,统一使用元素自身的缩放比例:void TitlebarPage::SetCustomTitleBarDragRegion() { auto myBut = m_mainWindow.MyBut(); if (!myBut) return; auto xamlRoot = myBut.XamlRoot(); if (!xamlRoot) return; double scaleAdjustment = xamlRoot.RasterizationScale(); GeneralTransform transform = myBut.TransformToVisual(nullptr); Rect bunds = transform.TransformBounds(Rect(0, 0, myBut.ActualWidth(), myBut.ActualHeight())); std::vector<Windows::Graphics::RectInt32> dragRects; Windows::Graphics::RectInt32 MyButRect; MyButRect.X = static_cast<int>(bunds.X * scaleAdjustment); MyButRect.Y = static_cast<int>(bunds.Y * scaleAdjustment); MyButRect.Height = static_cast<int>(bunds.Height * scaleAdjustment); MyButRect.Width = static_cast<int>(bunds.Width * scaleAdjustment); dragRects.push_back(MyButRect); InputNonClientPointerSource nonClientInputSrc = InputNonClientPointerSource::GetForWindowId(m_appWindow.Id()); if (nonClientInputSrc) { nonClientInputSrc.SetRegionRects(NonClientRegionKind::Passthrough, dragRects); } }切换标题栏后强制触发布局更新
在显示自定义标题栏后,手动触发UI布局更新,再调用坐标计算方法:void TitlebarPage::TitlebarCustomBtn_Click(...) { if (AppWindowTitleBar::IsCustomizationSupported() && !m_customTitleBar) { // ... 现有代码 ... m_mainWindow.MyTitleBar().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible); // 强制更新布局 m_mainWindow.MyTitleBar().UpdateLayout(); m_mainWindow.MyBut().UpdateLayout(); // 调用坐标计算方法 SetCustomTitleBarDragRegion(); } // ... 现有代码 ... }
内容的提问来源于stack exchange,提问作者Jzhang
相关产品推荐
相关产品推荐

