4
votes

I am creating a program that moves/resizes windows from another process with SetWindowPos(). My own program is PROCESS_PER_MONITOR_DPI_AWARE. The other programs could be anything from PROCESS_DPI_UNAWARE, PROCESS_SYSTEM_DPI_AWARE or PROCESS_PER_MONITOR_DPI_AWARE.

Because my own program is PROCESS_PER_MONITOR_DPI_AWARE, the coordinates I pass to SetWindowPos() are in physical coordinates. What I now want to do is resize the client area to a specific size in logical coordinates.

What I have tried to do is

  1. Get the DPI of the monitor where the window is placed as screenDPI.
  2. Get the DPI of the target window as windowDPI.
  3. Get scaleFactor as screenDPI / windowDPI.
  4. Scale the desired client area size by scaleFactor
  5. Calculated the extra size for the window frame by subtracting the current client rect size from the window rect size.

This works for the most part, but when I am using two screens with different display scaling, then

  • the calculation of the window frame size is off if I move the window from one screen to the next.
  • this fails for an application that uses PROCESS_SYSTEM_DPI_AWARE, when the window is located on the secondary screen (which uses 96dpi compared to the primary screen with 120dpi). This has nothing to do with the window frame size and I am not yet sure why exactly it fails, but the target x and y coordinates are scaled up so that the window is moved offscreen.
  • what happens if, because of the resize, the center of the window changes the screen? Then the screenDPI will no longer be correct, right? How would I handle that case?

I know that there is also the function AdjustWindowRectExForDpi, but somehow I can't get it to work properly. What is the dpi value I am supposed to pass to it? The dpi of the target screen, the dpi of the target window or the dpi of my own program? Additionally, this function is only available from Windows 10 onwards, so how would I handle it on an older Windows client?

I would appreciate some help with this. Thanks!

1

1 Answers

2
votes

What is the dpi value I am supposed to pass to it? The dpi of the target screen, the dpi of the target window or the dpi of my own program?

The DPI of the window you need to move from one screen to the next.

code sample:

#include <Windows.h>

LRESULT CALLBACK startup_window_procedure(HWND window, UINT message, WPARAM w_param, LPARAM l_param)
{
    switch (message)
    {
    case WM_DESTROY:
    {
        PostQuitMessage(0);
        return 0;
    }

    case WM_DPICHANGED:
    {
        // Resize the window
        RECT* new_rect = reinterpret_cast<RECT*>(l_param);

        if (!SetWindowPos(window, nullptr, new_rect->left, new_rect->top, new_rect->right - new_rect->left, new_rect->bottom - new_rect->top, SWP_NOZORDER | SWP_NOACTIVATE))
        {
            return 1;
        }

        return 0;
    }
    }

    return DefWindowProcW(window, message, w_param, l_param);
}

int CALLBACK wWinMain(HINSTANCE instance, HINSTANCE prev_instance, PWSTR cmd_line, int cmd_show)
{
    constexpr auto window_class_name = L"example_dialog";
    constexpr auto window_style = WS_OVERLAPPEDWINDOW;

    // Enable per-monitor DPI-awareness version 2
    if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
    {
        return 1;
    }

    // Create the window
    WNDCLASSEXW window_class;
    window_class.cbSize = sizeof(window_class);
    window_class.style = CS_HREDRAW | CS_VREDRAW;
    window_class.lpfnWndProc = startup_window_procedure;
    window_class.cbClsExtra = 0;
    window_class.cbWndExtra = 0;
    window_class.hInstance = instance;
    window_class.hIcon = nullptr;
    window_class.hCursor = nullptr;
    window_class.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
    window_class.lpszMenuName = nullptr;
    window_class.lpszClassName = window_class_name;
    window_class.hIconSm = nullptr;

    if (!RegisterClassExW(&window_class))
    {
        return 1;
    }

    HWND window = CreateWindowExW(0, window_class_name, L"Example window", window_style, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, nullptr, nullptr, instance, nullptr);

    if (!window)
    {
        return 1;
    }

    UINT dpi = GetDpiForWindow(window);
    float scaling_factor = static_cast<float>(dpi) / 96;
    // Actually set the appropriate window size
    RECT scale;
    scale.left = 0;
    scale.top = 0;
    scale.right = static_cast<LONG>(300 * scaling_factor);
    scale.bottom = static_cast<LONG>(150 * scaling_factor);

    if (!AdjustWindowRectExForDpi(&scale, window_style, false, 0, dpi))
    {
        return 1;
    }

    if (!SetWindowPos(window, nullptr, 0, 0, scale.right - scale.left, scale.bottom - scale.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE))
    {
        return 1;
    }

    ShowWindow(window, SW_SHOWNORMAL);

    // Message loop
    MSG message;
    int result;

    while ((result = GetMessageW(&message, nullptr, 0, 0)) != 0)
    {
        if (result == -1)
        {
            return 1;
        }
        else
        {
            TranslateMessage(&message);
            DispatchMessageW(&message);
        }
    }

    return static_cast<int>(message.wParam);
}

The windows can move from one screen to the next and recalculate window size successfully.