I'm saving/restoring my window position using GetWindowPlacement/SetWindowPlacement on Windows 10. My application is DPI aware. The issue occurs when SetWindowPlacement is both sizing and moving the window from monitor #1 with one DPI to monitor #2 with a different DPI. The coordinates have been saved as the correct size for monitor #2 in the WINDOWPLACEMENT structure.
The window is first resized during SetWindowPlacement while it's still on monitor #1. Then window is moved to monitor #2, which causes a WM_DPICHANGED message to fire, saying the window size should be changed. The suggested size is incorrect since it's changing the size of the window which was already the correct size for monitor #2. What is the correct way to solve this? Should I be setting a flag before SetWindowPlacement to ignore WM_DPICHANGED messages until that call is done? Are there cases where that will cause me to miss a message I shouldn't be ignoring? Thanks
Edit: Attached repro for @SongZhu-MSFT. In this test case I'm using a Surface Studio 2 as my primary monitor, running at 4500x3000, using 175% scaling. On the right of that monitor, aligned to the bottom is a 1920x1080 monitor set to 100% scaling. This code attempts to open the monitor on the right monitor with a set size, however a DPICHANGE message comes through during the SetWindowPlacement() call which causes the size to be adjusted incorrectly, unless I manually avoid it. Sample code is edited from: https://learn.microsoft.com/en-us/windows/win32/learnwin32/windows-hello-world-sample
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
WINDOWPLACEMENT wp = {};
wp.length = sizeof(wp);
wp.showCmd = 1;
wp.ptMaxPosition.x = -1;
wp.ptMaxPosition.y = -1;
wp.ptMinPosition.x = -1;
wp.ptMinPosition.y = -1;
wp.rcNormalPosition.left = 4510;
wp.rcNormalPosition.top = 2320;
wp.rcNormalPosition.right = wp.rcNormalPosition.left + 1850;
wp.rcNormalPosition.bottom = 2909;
::SetWindowPlacement((HWND)hwnd, &wp);
ShowWindow(hwnd, nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_DPICHANGED:
{
int dpi = HIWORD(wParam);
{
RECT* const prcNewWindow = (RECT*)lParam;
SetWindowPos(hwnd,
NULL,
prcNewWindow->left,
prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top,
SWP_NOZORDER | SWP_NOACTIVATE);
}
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// All painting occurs here, between BeginPaint and EndPaint.
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
And the .manifest I'm using.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,permonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
</windowsSettings>
</application>
</assembly>
There are two ways to solve this:
SetWindowPlacement
, set a flag like s_IsInsideWindowMove
to true
, and if it's set when WM_DPICHANGED
fires, do not follow the suggestion to resize the window. Once SetWindowPlacement
returns, set the flag back to false
;SetWindowPlacement
as if you were placing it on the monitor with the same DPI as the monitor the window is currently on. For instance, if you're moving the window from DPI 144
to DPI 192
monitor and you want the end result size to be 800x600
, ask SetWindowPlacement
for a size of 600x450.We use the first option as it's just easier to reason about and implement.