3

I've been trying to design a small interface with winapi (no MFC) for some weeks and there is a problem I've not been able to solve. The creation of my window is specified in pixels, but the size of my controls is in logical units.

I thought the solution would be to use the MulDiv function when I create the font for my window:

#include <windows.h>

LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
    switch(msg) {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

const char * g_szClassName = "SampleWindow";


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
    //Let's register our window class
    WNDCLASSEX wc;
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = wndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = CreateSolidBrush(RGB(240,240,240));
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = NULL;

    if(!RegisterClassEx(&wc)) {
        return 0;
    }

    DWORD dwStyle=( (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)&~WS_MAXIMIZEBOX);
    RECT rect;
    rect.left = rect.top = 0;
    rect.right = 300;
    rect.bottom = 100;
    ::AdjustWindowRect(&rect, dwStyle, false);

    HWND mWindow = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "Sample Window",
        dwStyle,
        CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top,// (x,y,width,height)
        NULL, NULL, hInstance, NULL);

    if(mWindow == NULL) {
        return 0;
    }

    HDC hdc = GetDC(mWindow);

    NONCLIENTMETRICS ncm;
    ncm.cbSize = sizeof(NONCLIENTMETRICS);
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
    ncm.lfMessageFont.lfWidth = 0;
    ncm.lfMessageFont.lfHeight = -MulDiv(16, GetDeviceCaps(hdc, LOGPIXELSY), 72);
    HFONT normalFont = CreateFontIndirect(&ncm.lfMessageFont);

    HWND someText = CreateWindow( "Static", "This text will scale badly", 
        WS_VISIBLE | WS_CHILD | SS_LEFT,  // Styles 
        10, 10, 500, 30, // (x, y, width, height)
        mWindow, (HMENU) NULL, hInstance, NULL);

    SendMessage(someText, WM_SETFONT, (WPARAM) normalFont, MAKELPARAM(TRUE, 0));

    ShowWindow(mWindow, SW_SHOW);
    UpdateWindow(mWindow);

    //Run the window
    MSG Msg;
    while(GetMessage(&Msg, NULL, 0, 0) > 0) {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    DeleteObject(normalFont);
    ReleaseDC(hdc);
}

Normally I compile the code with MSVC2010, but this minimal example was compiled with gcc 7.2.0 (MinGW 64 bit). This works in my computer, also playing with the scaling settings of windows 10. However, I've noticed in some computers the text looks bigger. I just can't get a window with a control to preserve the ratio between the window and the elements across all test machines. My application is not DPI aware, so every element should be scaled the same way, but it is every Edit and Static control which just seems to disobey the 16pt Font I'm asking for.

What is the correct way to get a ratio-preserving window with winapi?

Here is an example of what happens. The correct picture is as it looks in my machine (changing the scaling does scale the whole window, and it preserves the ratio between the font and the window) and the right is a Dell computer running the same program (Changing the scaling parameters modifies nothing, the whole window gets scaled but the text still looks bad). Normal case Badly scaled case

Thank you.

Malanche
  • 191
  • 8
  • 10
    *"My application is not DPI aware"* - this is your problem. Also system fonts themselves may vary in size on different machines. – user7860670 Jan 26 '18 at 13:05
  • But by not being DPI aware, then every element (including the window) should resize by some constant factor, no? And as I said, in my computer playing with the scaling options works without problems. I thought the font could change between computers but that is why I "override" the height and width parameters when creating a font. – Malanche Jan 26 '18 at 13:09
  • 2
    By not being DPI aware you make system run your application in compatibility mode supplying virtualized (read - "fake") data on window dimensions and stretch window content to fit actual DPI (potentially blurring it). See [High DPI Desktop Application Development on Windows](https://msdn.microsoft.com/en-us/library/windows/desktop/mt843498(v=vs.85).aspx). – user7860670 Jan 26 '18 at 13:14
  • There is just too little information here to do more than guesswork. E. g. _"in some computers the text looks bigger"_ - what do you know about these computers, which OS are they running and so on. Include some screenshots. Also a [mcve] is recommended so we can try to reproduce. – zett42 Jan 26 '18 at 13:22
  • @zett42 I will add the minimum example, but it is just any basic "hello world" for winapi with the addition of me defining which font to use. Also, the other computers are Same OS (windows 10 64 bit), just different brands (the ones that fail are always Dell for some odd reason) – Malanche Jan 26 '18 at 13:24
  • @zett42 Is that enough information? – Malanche Jan 26 '18 at 13:56
  • This is much better. – zett42 Jan 26 '18 at 14:22
  • `GetDC(mWindow)` is resource leak. Use `HDC hdc = GetDC(mWindow)`, and clean up with `ReleaseDC`. Your MCVE looks too simple, it has scaling problems unrelated to DPI settings. Maybe you can improve it. – Barmak Shemirani Jan 26 '18 at 16:02
  • @BarmakShemirani Thank you, I fixed the memory leak now. – Malanche Jan 26 '18 at 16:32

1 Answers1

3

Do not adjust text size yourself in a non-DPI aware application.

Either let the OS take care of all adjustments, or else manifest your application as DPI-aware and calculate size changes yourself.

Now, this doesn't mean that you should hard-code sizes in a non-aware application. The best thing to do is to use SystemParametersInfo to query for the user's preferred font size, just as you are already doing for the font face. Do not apply adjustments based on LOGPIXELSX and LOGPIXELSY on top of that.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I don't adjust the size based on DPI. I am creating a window of, let's say 600x400 pixels, and I want some text to have characters that are 20 pixels high. What I care, is that the height of the text is 5% the height of the window, regardless of DPI, regardless of the resolution of the screen, but I can't seem to achieve this (running the same program in two different computers give different scales for the text but not for the window). If I don't know the size of the text, then I can't properly position my controls inside the window without overlapping. – Malanche Jan 26 '18 at 16:06
  • And another little thing (forgive my ignorance). What is the inverse function to MulDiv? If I want to keep the font size from the system, then I need to arrange my controls in a relative position to each other knowing the size of each element. – Malanche Jan 26 '18 at 16:12
  • 1
    @Malanche: MulDiv with the numerator and denominator swapped? – Ben Voigt Jan 26 '18 at 16:20
  • My bad, I didn't know what MulDiv was doing, I thought it was a function specific to transform between logic units and pixel count. I will mark the answer as correct but does not clear entirely my doubt. Is there some system of coordinates which is common to fonts and all controls that I can specify without worrying about pixel density or resolution? I don't like to specify some things in logical units and others in pixels. Thank you. – Malanche Jan 26 '18 at 16:28
  • Always worth reading documentation when you don't know what a function does – David Heffernan Jan 26 '18 at 16:35
  • @Malanche: Not only are there points (for font size) and pixels (for window size and coordinates) but then dialog box resources use dialog units (which are relative to the system font size) – Ben Voigt Jan 26 '18 at 18:31