I have already asked a similar question here, but now the problems seems to be a bit different, so I figured I'd create a new question for it.
I am using SetWindowPos()
to move/resize windows from another process. This works fine for as long as all screens are using the same display scaling, but under the following scenario it doesn't work as expected:
- Primary Screen is at (0,0) with 3440x1440 and 150% scaling.
- Secondary Screen is at (3440, 0) with 900x1440 and 100% scaling.
- My application is
PROCESS_PER_MONITOR_DPI_AWARE_V2
and the target application isPROCESS_DPI_UNAWARE
(gets scaled by Windows).
Now if I move a window so that the upper left is on the primary screen and the center is still on the secondary screen, for example to (3400, 0).
SetWindowPos(hwnd, HWND_BOTTOM, 3300, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
Then this is what happens:
- The window is scaled according to the second screen's 100% display scaling.
- The window is not moved to (3300, 0). Instead, the coordinates it receives in the
WM_WINDOWPOSCHANGING
message are (2200, 0). The coordinates seem to get scaled down to logical coordinates.
I am therefore unable to move a window to that location. I tried to use PhysicalToLogicalPointForPerMonitorDPI()
on the coordinates I pass to SetWindowPos()
, but without success (it doesn't even change the coordinates).
Now it seems like I just can't move the window to any position such that the upper left is on the primary screen, but the window's center is still on the secondary screen, because Windows will scale the coordinates down and if I manually scale them up, then I would already position the window on the second screen and Windows does not apply the scaling anymore. Even if I was able to get around that, manually calculating the scaling is possible with a two-screen setup, but quickly becomes too complex with more screens. So, how do I get this to work?
EDIT1: I tried to use SetThreadDpiAwarenessContext()
as suggested, but it still doesn't work. Now, when I move the window to (3000,0) it is moved to (4500,0) instead. It seems like I somehow need to scale the coordinates I pass to SetWindowPos()
, but I have no idea how.
m_previousContext = SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext(hwnd));
if(m_previousContext == NULL)
Log::out<Log::Level::Error>("Failed to set thread dpi awareness context.");
SetWindowPos(hwnd, HWND_BOTTOM, 3000, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
Also, isn't this really inefficient if I regularly resize and move windows around?
EDIT2: I've attach a link to a minimal working binary. You can download it from Google Drive here. It requires Windows 10, version 1607 to run. When I run it on the setup mentioned above with SetWindowPos.exe 002108B6 3000 0
then the window is moved to (4500,0) instead.
Below is the code:
int main(int argc, char** argv)
{
if(argc < 4)
{
std::cerr << "Usage: SetWindowPos.exe <HWND> <x> <y>" << std::endl;
return 1;
}
HWND hwnd;
int x, y;
try
{
hwnd = (HWND)hexStrToInt(argv[1]); // I've omitted the implementation of hexStrToInt
x = atoi(argv[2]);
y = atoi(argv[3]);
}
catch(...)
{
std::cerr << "Invalid arguments." << std::endl;
return 1;
}
if(IsWindow(hwnd) == FALSE)
{
std::cerr << "Invalid window handle " << argv[1] << "." << std::endl;
return 1;
}
auto context = SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext(hwnd));
SetWindowPos(hwnd, HWND_BOTTOM, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
SetThreadDpiAwarenessContext(context);
return 0;
}
PhysicalToLogicalPointForPerMonitorDPI
. The function doesn't do anything, even though it returnsTRUE
. So I searched for it in the Chromium source code to see how they are using it. But it turns out that they don't use that function at all. Instead, they useGetDpiForMonitor
to calculate the scaling factor of the corresponding monitor. – GetFree