Skip to content

Fix FancyZones editor overlay on mixed-DPI multi-monitor setups - #44440

Merged
leileizhang (lei9444) merged 2 commits into
microsoft:mainfrom
Memphizzz:fix/fancyzones-mixed-dpi-overlay
Jan 9, 2026
Merged

Fix FancyZones editor overlay on mixed-DPI multi-monitor setups#44440
leileizhang (lei9444) merged 2 commits into
microsoft:mainfrom
Memphizzz:fix/fancyzones-mixed-dpi-overlay

Conversation

@Memphizzz

Copy link
Copy Markdown
Contributor

Summary of the Pull Request

Use DPI-unaware context when positioning overlay windows to match the coordinate space from the C++ backend.

The FancyZones editor overlay windows were incorrectly positioned on secondary monitors when using different DPI scaling (e.g., 125%/150%/125%). Zones appeared shifted or clipped because they extended past monitor edges.

PR Checklist

Detailed Description of the Pull Request / Additional comments

Root Cause

The C++ backend uses a DPI-unaware thread to get virtual screen coordinates, but the WPF editor (PerMonitorV2 DPI-aware) interpreted these coordinates with DPI scaling applied, causing misalignment on non-primary monitors.

Fix

  • EditorParameters.cpp: Use consistent virtual coordinates for all monitor properties (removed DPIAware::Convert that was only applied to dimensions)
  • Monitor.cs: Reposition overlay windows using DPI-unaware context after HWND creation, matching the coordinate space from C++ backend
  • NativeMethods.cs: Added SetWindowPositionDpiUnaware() using SetThreadDpiAwarenessContext to temporarily switch DPI awareness

Validation Steps Performed

Manually tested on 3-monitor setup with 125%/150%/125% DPI scaling - overlays now correctly cover each monitor's work area.


🤖 This fix was developed with Claude Code after 6 hours of debugging DPI coordinate systems together.

Use DPI-unaware context when positioning overlay windows to match the coordinate space from the C++ backend.

Co-Authored-By: Claude <noreply@anthropic.com>
@Memphizzz

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@lei9444

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes FancyZones editor overlay window positioning issues on multi-monitor setups with different DPI scaling. The core problem was that the C++ backend uses DPI-unaware coordinates while the WPF editor interprets them with DPI scaling, causing misalignment on non-primary monitors.

Key Changes:

  • Removed inconsistent DPI conversion that was only applied to monitor dimensions
  • Added DPI-unaware window positioning function in C# to match coordinate space from C++ backend
  • Updated window positioning logic to use DPI-unaware context after HWND creation

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
EditorParameters.cpp Removed DPIAware::Convert for monitor dimensions, ensuring all monitor properties use consistent DPI-unaware coordinates from the dpiUnawareThread
NativeMethods.cs Added SetWindowPositionDpiUnaware() utility that temporarily switches to DPI-unaware context for window positioning via P/Invoke
Monitor.cs Updated constructor and Scale() method to use DPI-unaware positioning, added SourceInitialized event handler to reposition windows after HWND creation

return;
}
monitorInfoUnaware.cbSize = sizeof(monitorInfoUnaware);
GetMonitorInfo(monitor, &monitorInfoUnaware);

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value of GetMonitorInfo should be checked. If it fails, the monitorInfoUnaware structure will contain uninitialized data, leading to incorrect monitor dimensions being passed to the editor. Consider checking the return value and continuing to the next monitor if it fails, similar to how GetScreenDPIForMonitor is checked on line 187.

Copilot uses AI. Check for mistakes.
IntPtr oldContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
try
{
SetWindowPos(helper, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value of SetWindowPos should be checked. If it fails, the window positioning will silently fail and the overlay may still be mispositioned. Consider checking the return value and logging a warning or error if the operation fails, similar to how error handling is done elsewhere in the codebase (e.g., checking SetLastError when the function returns false).

Copilot uses AI. Check for mistakes.
@lei9444

leileizhang (lei9444) commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

I committed a fix that scales monitor dimensions by the DPI factor so the editor shows the actual physical resolution.
Before fix
image

After fix
image

@lei9444

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@lei9444
leileizhang (lei9444) merged commit 6c2a99d into microsoft:main Jan 9, 2026
11 checks passed
@yeelam-gordon Gordon Lam (yeelam-gordon) added the Product-FancyZones Refers to the FancyZones PowerToy label Jan 12, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


// After HWND is created, reposition using DPI-unaware context
// This matches the C++ backend which uses a DPI-unaware thread
Window.SourceInitialized += OnWindowSourceInitialized;

Copilot AI Jan 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SourceInitialized event handler is never unregistered. While this may not cause issues in practice because the Window lifecycle is managed by the application, consider unregistering the handler after it executes once (since repositioning only needs to happen once after HWND creation) or when the Window is closed to follow proper event handler cleanup patterns.

Suggested change
Window.SourceInitialized += OnWindowSourceInitialized;
EventHandler handler = null;
handler = (sender, args) =>
{
Window.SourceInitialized -= handler;
OnWindowSourceInitialized(sender, args);
};
Window.SourceInitialized += handler;

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +76
// Convert virtual coordinates to physical resolution by applying DPI scale
double scale = DPI / 96.0;
int physicalWidth = (int)Math.Round(ScreenBoundsWidth * scale);
int physicalHeight = (int)Math.Round(ScreenBoundsHeight * scale);
return physicalWidth + " × " + physicalHeight;

Copilot AI Jan 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When DPI is 0 (which occurs in span zones mode as set in EditorParameters.cpp line 151), the dimension calculation will result in 0x0 display. The scale will be 0/96.0 = 0, causing physicalWidth and physicalHeight to both be 0. Consider handling the DPI=0 case separately, similar to how it's marked as "unused" in the C++ code, or fall back to displaying the virtual coordinates without scaling.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-FancyZones Refers to the FancyZones PowerToy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FancyZones not using full screen FancyZones - Not Capturing full screen

5 participants