@@ -61,10 +61,6 @@ struct TreeNode {
6161 double width = 0 , height = 0 ;
6262 double offsetX = 0 , offsetY = 0 ;
6363 bool hasBounds = false ;
64- // Layout info for offset computation
65- int orientation = -1 ; // -1=unknown, 0=vertical, 1=horizontal (StackPanel)
66- double spacing = 0 ;
67- double marginLeft = 0 , marginTop = 0 , marginRight = 0 , marginBottom = 0 ;
6864};
6965
7066class LvtTap ;
@@ -84,7 +80,6 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
8480
8581public:
8682 IVisualTreeService* m_vts = nullptr ;
87- IVisualTreeService2* m_vts2 = nullptr ;
8883 static constexpr UINT WM_COLLECT_BOUNDS = WM_USER + 100 ;
8984
9085public:
@@ -170,14 +165,7 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
170165 return S_OK;
171166 }
172167
173- // Try to get IVisualTreeService2 for GetPropertyIndex/GetProperty (Vector3 workaround)
174- hr = m_vts->QueryInterface (__uuidof (IVisualTreeService2), (void **)&m_vts2);
175- if (FAILED (hr)) {
176- LogMsg (" QI for IVisualTreeService2 failed (non-fatal): 0x%08X" , hr);
177- m_vts2 = nullptr ;
178- }
179-
180- m_diag = diag; // Keep reference for GetIInspectableFromHandle
168+ m_diag = diag;
181169
182170 // Create a message-only window on the UI thread for dispatching
183171 // GetPropertyValuesChain calls (which have thread affinity).
@@ -232,9 +220,8 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
232220 SendMessageW (self->m_msgWnd , WM_COLLECT_BOUNDS, 0 ,
233221 reinterpret_cast <LPARAM>(self));
234222 }
235- // Compute layout-based offsets for Panel children (workaround for
236- // WinUI3 ActualOffset serialization returning "0" for Vector3 types)
237- self->ComputeLayoutOffsets ();
223+ // Apply DPI scaling (XAML logical pixels → physical pixels)
224+ self->ApplyDpiScaling ();
238225 self->SerializeAndSend ();
239226 self->m_vts ->UnadviseVisualTreeChange (cb);
240227 }
@@ -302,7 +289,7 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
302289 }
303290
304291 // Collect bounds for a single node — isolated for SEH compatibility
305- static void CollectBoundsForNode (IVisualTreeService* vts, IVisualTreeService2* /* vts2 */ ,
292+ static void CollectBoundsForNode (IVisualTreeService* vts,
306293 TreeNode& node, InstanceHandle handle,
307294 bool logDetail) {
308295 if (logDetail) LogMsg (" CollectBoundsForNode ENTER handle=%llu" , (unsigned long long )handle);
@@ -339,19 +326,6 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
339326 foundOffset = true ;
340327 }
341328 }
342- // Collect layout properties for offset computation
343- // Only take the first value (most specific in the property chain)
344- if (name == L" Orientation" && !value.empty () && node.orientation < 0 ) {
345- node.orientation = _wtoi (value.c_str ());
346- } else if (name == L" Spacing" && !value.empty () && node.spacing == 0 ) {
347- node.spacing = _wtof (value.c_str ());
348- } else if (name == L" Margin" && !value.empty ()) {
349- double l = 0 , t = 0 , r = 0 , b = 0 ;
350- if (swscanf_s (value.c_str (), L" %lf,%lf,%lf,%lf" , &l, &t, &r, &b) >= 2 ) {
351- node.marginLeft = l; node.marginTop = t;
352- node.marginRight = r; node.marginBottom = b;
353- }
354- }
355329 if (props[i].Type ) SysFreeString (props[i].Type );
356330 if (props[i].DeclaringType ) SysFreeString (props[i].DeclaringType );
357331 if (props[i].ValueType ) SysFreeString (props[i].ValueType );
@@ -368,11 +342,11 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
368342 }
369343
370344 // SEH wrapper for single-node bounds collection (cannot use __try with C++ objects)
371- static int CollectBoundsForNodeSEH (IVisualTreeService* vts, IVisualTreeService2* vts2,
345+ static int CollectBoundsForNodeSEH (IVisualTreeService* vts,
372346 TreeNode& node, InstanceHandle handle,
373347 bool logDetail) {
374348 __try {
375- CollectBoundsForNode (vts, vts2, node, handle, logDetail);
349+ CollectBoundsForNode (vts, node, handle, logDetail);
376350 return 0 ;
377351 } __except (EXCEPTION_EXECUTE_HANDLER) {
378352 return GetExceptionCode ();
@@ -386,7 +360,7 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
386360 int idx = 0 ;
387361 for (auto & [handle, node] : m_nodes) {
388362 bool logDetail = false ; // Set to true for debugging
389- int code = CollectBoundsForNodeSEH (vts, m_vts2, node, handle, logDetail);
363+ int code = CollectBoundsForNodeSEH (vts, node, handle, logDetail);
390364 if (code != 0 ) {
391365 LogMsg (" GetPropertyValuesChain crashed for handle %llu: 0x%08X" ,
392366 (unsigned long long )handle, code);
@@ -397,58 +371,34 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
397371 LogMsg (" CollectBounds: collected bounds for %d/%zu nodes" , collected, m_nodes.size ());
398372 }
399373
400- // Compute layout-based offsets for children of StackPanel/Panel containers.
401- // WinUI3's XAML diagnostics can't serialize Vector3 ActualOffset values, so we
402- // approximate child positions by accumulating widths (horizontal) or heights (vertical)
403- // along the stacking axis of the parent container.
404- void ComputeLayoutOffsets () {
405- int computed = 0 ;
406- for (auto & [handle, node] : m_nodes) {
407- if (node.childHandles .empty ()) continue ;
408- // Determine orientation: StackPanel has explicit Orientation,
409- // other Panels (CommandBar, MenuBar, etc.) may default to horizontal
410- int orient = node.orientation ;
411- if (orient < 0 ) {
412- // Heuristic: types containing "Bar", "Toolbar", "CommandBar" → horizontal
413- // "StackPanel" with no explicit orientation → vertical (default)
414- auto & t = node.type ;
415- if (t.find (L" StackPanel" ) != std::wstring::npos) {
416- orient = 0 ; // vertical by default
417- } else if (t.find (L" Bar" ) != std::wstring::npos ||
418- t.find (L" Toolbar" ) != std::wstring::npos) {
419- orient = 1 ; // horizontal
420- }
374+ // Apply DPI scaling to XAML element dimensions.
375+ // XAML ActualWidth/ActualHeight are in logical pixels; bridge positions from
376+ // GetWindowRect are in physical pixels. Scale dimensions to match.
377+ void ApplyDpiScaling () {
378+ HWND hwnd = nullptr ;
379+ EnumWindows ([](HWND h, LPARAM lp) -> BOOL {
380+ DWORD pid = 0 ;
381+ GetWindowThreadProcessId (h, &pid);
382+ if (pid == GetCurrentProcessId () && IsWindowVisible (h)) {
383+ *reinterpret_cast <HWND*>(lp) = h;
384+ return FALSE ;
421385 }
422- if (orient < 0 ) continue ; // unknown container, skip
423-
424- double cursor = 0 ;
425- for (size_t ci = 0 ; ci < node.childHandles .size (); ci++) {
426- auto cit = m_nodes.find (node.childHandles [ci]);
427- if (cit == m_nodes.end ()) continue ;
428- auto & child = cit->second ;
429- if (!child.hasBounds ) continue ;
430-
431- // Only set offset if not already computed
432- if (child.offsetX == 0 && child.offsetY == 0 ) {
433- if (orient == 1 ) { // horizontal
434- child.offsetX = cursor + child.marginLeft ;
435- child.offsetY = child.marginTop ;
436- } else { // vertical
437- child.offsetX = child.marginLeft ;
438- child.offsetY = cursor + child.marginTop ;
439- }
440- computed++;
441- }
386+ return TRUE ;
387+ }, reinterpret_cast <LPARAM>(&hwnd));
442388
443- // Advance cursor
444- if (orient == 1 ) {
445- cursor += child.marginLeft + child.width + child.marginRight + node.spacing ;
446- } else {
447- cursor += child.marginTop + child.height + child.marginBottom + node.spacing ;
448- }
449- }
389+ if (!hwnd) return ;
390+ UINT dpi = GetDpiForWindow (hwnd);
391+ if (dpi == 0 || dpi == 96 ) return ;
392+
393+ double scale = static_cast <double >(dpi) / 96.0 ;
394+ LogMsg (" ApplyDpiScaling: DPI=%u scale=%.2f" , dpi, scale);
395+ for (auto & [handle, node] : m_nodes) {
396+ if (!node.hasBounds ) continue ;
397+ node.width *= scale;
398+ node.height *= scale;
399+ node.offsetX *= scale;
400+ node.offsetY *= scale;
450401 }
451- LogMsg (" ComputeLayoutOffsets: computed %d offsets" , computed);
452402 }
453403
454404 // Called on the UI thread via SendMessage from the worker thread
@@ -540,7 +490,6 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 {
540490
541491 ~LvtTap () {
542492 if (m_msgWnd) DestroyWindow (m_msgWnd);
543- if (m_vts2) m_vts2->Release ();
544493 if (m_vts) m_vts->Release ();
545494 if (m_diag) m_diag->Release ();
546495 if (m_site) m_site->Release ();
0 commit comments