繁体   English   中英

win32:如何计算控件大小以获得跨 Windows 版本/主题的一致外观?

[英]win32: How to calculate control sizes for a consistent look across windows versions / themes?

在一个简单的 GUI 库上工作,我从后端开始,现在在计算控件的首选大小时遇到​​了一些问题。 我正在将我的结果与Windows.Forms结果进行比较。

现在,我正在使用设计规范和指南 - 视觉设计布局(如按钮和文本框高 14 个“对话框逻辑单位”)中的值来计算实现中的像素大小,同时保持 Windows 窗体的所有默认值。 我创建了这些简单的演示实现:

Windows 窗体 (demo.cs):

using System.Drawing;
using System.Windows.Forms;

namespace W32CtlTest
{
    public class Demo : Form
    {
        private FlowLayoutPanel panel;
        private Button button;
        private TextBox textBox;

        public Demo() : base()
        {
            Text = "winforms";
            panel = new FlowLayoutPanel();

            button = new Button();
            button.Text = "test";
            button.Click += (sender, args) =>
            {
                Close();
            };
            panel.Controls.Add(button);

            textBox = new TextBox();
            panel.Controls.Add(textBox);

            Controls.Add(panel);
        }

        protected override Size DefaultSize
        {
            get
            {
                return new Size(240,100);
            }
        }

        public static void Main(string[] argv)
        {
            if (argv.Length < 1 || argv[0] != "-s")
            {
                Application.EnableVisualStyles();
            }
            Application.Run(new Demo());
        }
    }
}

使用C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe /out:demo.exe /lib:C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319 /reference:System.Windows.Forms.dll,System.Drawing.dll demo.cs

Win32 API (demo.c):

#include <string.h>

#include <windows.h>
#include <commctrl.h>

static HINSTANCE instance;
static HWND mainWindow;
static HWND button;
static HWND textBox;

#define WC_mainWindow L"W32CtlTestDemo"
#define CID_button 0x101

static NONCLIENTMETRICSW ncm;
static HFONT messageFont;
static TEXTMETRICW messageFontMetrics;
static int buttonWidth;
static int buttonHeight;
static int textBoxWidth;
static int textBoxHeight;

/* hack to enable visual styles without relying on manifest
 * found at http://stackoverflow.com/a/10444161
 * modified for unicode-only code */
static int enableVisualStyles(void)
{
    wchar_t dir[MAX_PATH];
    ULONG_PTR ulpActivationCookie = 0;
    ACTCTXW actCtx =
    {
        sizeof(actCtx),
        ACTCTX_FLAG_RESOURCE_NAME_VALID
            | ACTCTX_FLAG_SET_PROCESS_DEFAULT
            | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,
        L"shell32.dll", 0, 0, dir, (LPWSTR)124,
        0, 0
    };
    UINT cch = GetSystemDirectoryW(dir, sizeof(dir) / sizeof(*dir));
    if (cch >= sizeof(dir) / sizeof(*dir)) { return 0; }
    dir[cch] = L'\0';
    ActivateActCtx(CreateActCtxW(&actCtx), &ulpActivationCookie);
    return (int) ulpActivationCookie;
}

static void init(void)
{
    INITCOMMONCONTROLSEX icx;
    icx.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icx.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&icx);
    ncm.cbSize = sizeof(ncm);
    SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
    messageFont = CreateFontIndirectW(&ncm.lfStatusFont);
    HDC dc = GetDC(0);
    SelectObject(dc, (HGDIOBJ) messageFont);
    GetTextMetricsW(dc, &messageFontMetrics);
    SIZE sampleSize;
    GetTextExtentExPointW(dc,
            L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
            52, 0, 0, 0, &sampleSize);
    ReleaseDC(0, dc);
    buttonWidth = MulDiv(sampleSize.cx, 50, 4 * 52);
    buttonHeight = MulDiv(messageFontMetrics.tmHeight, 14, 8);
    textBoxWidth = 100;
    textBoxHeight = MulDiv(messageFontMetrics.tmHeight, 14, 8);
    instance = GetModuleHandleW(0);
}

static LRESULT CALLBACK wproc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg)
    {
    case WM_CREATE:
        button = CreateWindowExW(0, L"Button", L"test",
                WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
                2, 2, buttonWidth, buttonHeight,
                w, (HMENU)CID_button, instance, 0);
        SendMessageW(button, WM_SETFONT, (WPARAM)messageFont, 0);

        textBox = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"",
                WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
                6 + buttonWidth, 2, textBoxWidth, textBoxHeight,
                w, 0, instance, 0);
        SendMessageW(textBox, WM_SETFONT, (WPARAM)messageFont, 0);

        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_COMMAND:
        switch (LOWORD(wp))
        {
        case CID_button:
            DestroyWindow(w);
            break;
        }
        break;

    }

    return DefWindowProcW(w, msg, wp, lp);
}

int main(int argc, char **argv)
{
    if (argc < 2 || strcmp(argv[1], "-s"))
    {
        enableVisualStyles();
    }

    init();

    WNDCLASSEXW wc;
    memset(&wc, 0, sizeof(wc));
    wc.cbSize = sizeof(wc);
    wc.hInstance = instance;
    wc.lpszClassName = WC_mainWindow;
    wc.lpfnWndProc = wproc;
    wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
    wc.hCursor = LoadCursorA(0, IDC_ARROW);
    RegisterClassExW(&wc);

    mainWindow = CreateWindowExW(0, WC_mainWindow, L"winapi",
            WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 100,
            0, 0, instance, 0);
    ShowWindow(mainWindow, SW_SHOWNORMAL);

    MSG msg;
    while (GetMessageW(&msg, 0, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    return (int)msg.wParam;
}

gcc -odemo.exe -O2 demo.c -lgdi32 -lcomctl32


测试代码也可以在github上找到


在 Windows 10 上看起来像这样,在上排启用视觉样式,在下排禁用视觉样式:

在 Windows 10 上控制外观

我很快发现的一件事是Windows.Forms不使用消息字体(如我所料)而是使用DEFAULT_GUI_FONT虽然这不是正确的做法,但我相应地更改了我的 win32 代码,以便我可以比较结果更好的:

使用 DEFAULT_GUI_FONT 控制 Windows 10 上的外观

为了完整起见,以下是没有视觉样式的 Windows 7 上的样子:

使用 DEFAULT_GUI_FONT 控制 Windows 7 上的外观

现在我的问题是:

  1. 使用消息字体是否正确? 那么,Windows.Forms 肯定把这个“错了”?

  2. 显然,Windows.Forms 对按钮使用 14 DLU 高度,但对 TextBox 使用一些较小的高度。 这与设计规范相矛盾。 那么 Windows.Forms 在这里也是错误的吗? 或者 TextBoxes 实际上应该更小,所以文本看起来不像是“悬挂在天花板上”? 我认为这确实比 Windows.Forms 看起来更好。

  3. 比较启用/禁用的视觉样式,我发现如果没有视觉样式,我的按钮和文本框的高度相同,但在 Windows 10 上启用视觉样式时,文本框实际上更高。 是否有“主题特定指标”之类的东西,如果有,我该如何使用它来纠正我的计算?

这只是我在此处添加以供参考的部分答案:

事实上,根据Raymond Chen 的这篇博客文章,使用DEFAULT_GUI_FONT错误的。 因此,无需相信会做“正确的事情”。

设计规范指出编辑控件的高度应与按钮 (14 DLU) 相同。 要将这些转换为像素大小,需要对话框基本单位 (DBU),虽然GetDialogBaseUnits()仅返回系统字体的它们,但MSDN文章描述了如何为其他字体计算它们。

1 个垂直 DBU 对应 8 个 DLU,因此一个 Edit 控件将比它包含的文本高 6 个 DLU。 这看起来不太好,因为“编辑”控件不会将文本垂直居中,而是将其在顶部对齐。 通过为 Edit 控件计算较小的大小来避免这种情况。 缺点是编辑控件不会很好地与按钮对齐。

我通过在覆盖的窗口过程中缩小编辑控件的客户区,找到了一种解决该问题的“hacky”解决方案。 以下代码比较了结果(并包含使用系统字体的控件以确保完整性):

#include <stdlib.h>
#include <string.h>

#include <windows.h>
#include <commctrl.h>

typedef struct PaddedControl
{
    WNDPROC baseWndProc;
    int vshrink;
} PaddedControl;

static HINSTANCE instance;
static HWND mainWindow;
static HWND buttonSF;
static HWND textBoxSF;
static HWND buttonMF;
static HWND textBoxMF;
static HWND buttonMFC;
static HWND textBoxMFC;
static PaddedControl textBoxMFCPadded;

#define WC_mainWindow L"W32CtlTestDemo"

static NONCLIENTMETRICSW ncm;
static HFONT messageFont;
static TEXTMETRICW messageFontMetrics;
static int controlHeightSF;
static int controlHeightMF;
static int buttonWidthSF;
static int buttonWidthMF;

/* hack to enable visual styles without relying on manifest
 * found at http://stackoverflow.com/a/10444161
 * modified for unicode-only code */
static int enableVisualStyles(void)
{
    wchar_t dir[MAX_PATH];
    ULONG_PTR ulpActivationCookie = 0;
    ACTCTXW actCtx =
    {
        sizeof(actCtx),
        ACTCTX_FLAG_RESOURCE_NAME_VALID
            | ACTCTX_FLAG_SET_PROCESS_DEFAULT
            | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,
        L"shell32.dll", 0, 0, dir, (LPWSTR)124,
        0, 0
    };
    UINT cch = GetSystemDirectoryW(dir, sizeof(dir) / sizeof(*dir));
    if (cch >= sizeof(dir) / sizeof(*dir)) { return 0; }
    dir[cch] = L'\0';
    ActivateActCtx(CreateActCtxW(&actCtx), &ulpActivationCookie);
    return (int) ulpActivationCookie;
}

static void init(void)
{
    INITCOMMONCONTROLSEX icx;
    icx.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icx.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&icx);
    ncm.cbSize = sizeof(ncm);
    SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
    messageFont = CreateFontIndirectW(&ncm.lfStatusFont);

    LONG sysDbu = GetDialogBaseUnits();
    HDC dc = GetDC(0);
    SelectObject(dc, (HGDIOBJ) messageFont);
    GetTextMetricsW(dc, &messageFontMetrics);
    SIZE sampleSize;
    GetTextExtentExPointW(dc,
            L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
            52, 0, 0, 0, &sampleSize);
    ReleaseDC(0, dc);
    controlHeightSF = MulDiv(HIWORD(sysDbu), 14, 8);
    controlHeightMF = MulDiv(messageFontMetrics.tmHeight, 14, 8);
    buttonWidthSF = MulDiv(LOWORD(sysDbu), 50, 4);
    buttonWidthMF = MulDiv(sampleSize.cx, 50, 4 * 52);
    instance = GetModuleHandleW(0);
}

static LRESULT CALLBACK paddedControlProc(
        HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
    PaddedControl *self = (PaddedControl *)GetPropW(w, L"paddedControl");
    WNDCLASSEXW wc;

    switch (msg)
    {
    case WM_ERASEBKGND:
        wc.cbSize = sizeof(wc);
        GetClassInfoExW(0, L"Edit", &wc);
        RECT cr;
        GetClientRect(w, &cr);
        cr.top -= self->vshrink;
        cr.bottom += self->vshrink;
        HDC dc = GetDC(w);
        FillRect(dc, &cr, wc.hbrBackground);
        ReleaseDC(w, dc);
        return 1;

    case WM_NCCALCSIZE:
        if (!wp) break;
        LRESULT result = CallWindowProcW(self->baseWndProc, w, msg, wp, lp);
        NCCALCSIZE_PARAMS *p = (NCCALCSIZE_PARAMS *)lp;
        int height = p->rgrc[0].bottom - p->rgrc[0].top;
        self->vshrink = 0;
        if (height > messageFontMetrics.tmHeight + 3)
        {
            self->vshrink = (height - messageFontMetrics.tmHeight - 3) / 2;
            p->rgrc[0].top += self->vshrink;
            p->rgrc[0].bottom -= self->vshrink;
        }
        return result;
    }

    return CallWindowProcW(self->baseWndProc, w, msg, wp, lp);
}

static LRESULT CALLBACK wproc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg)
    {
    case WM_CREATE:
        buttonSF = CreateWindowExW(0, L"Button", L"sysfont",
                WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
                4, 4, buttonWidthSF, controlHeightSF,
                w, 0, instance, 0);

        buttonMF = CreateWindowExW(0, L"Button", L"msgfont",
                WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
                4, 8 + controlHeightSF, buttonWidthMF, controlHeightMF,
                w, 0, instance, 0);
        SendMessageW(buttonMF, WM_SETFONT, (WPARAM)messageFont, 0);

        buttonMFC = CreateWindowExW(0, L"Button", L"msgfont adj",
                WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
                4, 12 + controlHeightSF + controlHeightMF,
                buttonWidthMF, controlHeightMF,
                w, 0, instance, 0);
        SendMessageW(buttonMFC, WM_SETFONT, (WPARAM)messageFont, 0);

        textBoxSF = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
                WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
                8 + buttonWidthSF, 4, 100, controlHeightSF,
                w, 0, instance, 0);

        textBoxMF = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
                WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
                8 + buttonWidthMF, 8 + controlHeightSF,
                100, controlHeightMF,
                w, 0, instance, 0);
        SendMessageW(textBoxMF, WM_SETFONT, (WPARAM)messageFont, 0);

        textBoxMFC = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
                WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
                8 + buttonWidthMF, 12 + controlHeightSF + controlHeightMF,
                100, controlHeightMF,
                w, 0, instance, 0);
        memset(&textBoxMFCPadded, 0, sizeof(PaddedControl));
        textBoxMFCPadded.baseWndProc = (WNDPROC)SetWindowLongPtr(
                textBoxMFC, GWLP_WNDPROC, (LONG_PTR)paddedControlProc);
        SetPropW(textBoxMFC, L"paddedControl", &textBoxMFCPadded);
        SetWindowPos(textBoxMFC, 0, 0, 0, 0, 0,
                SWP_NOOWNERZORDER|SWP_NOSIZE|SWP_NOMOVE|SWP_FRAMECHANGED);
        SendMessageW(textBoxMFC, WM_SETFONT, (WPARAM)messageFont, 0);

        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    }

    return DefWindowProcW(w, msg, wp, lp);
}

int main(int argc, char **argv)
{
    if (argc < 2 || strcmp(argv[1], "-s"))
    {
        enableVisualStyles();
    }

    init();

    WNDCLASSEXW wc;
    memset(&wc, 0, sizeof(wc));
    wc.cbSize = sizeof(wc);
    wc.hInstance = instance;
    wc.lpszClassName = WC_mainWindow;
    wc.lpfnWndProc = wproc;
    wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
    wc.hCursor = LoadCursorA(0, IDC_ARROW);
    RegisterClassExW(&wc);

    mainWindow = CreateWindowExW(0, WC_mainWindow, L"fontdemo",
            WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 180,
            0, 0, instance, 0);
    ShowWindow(mainWindow, SW_SHOWNORMAL);

    MSG msg;
    while (GetMessageW(&msg, 0, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    return (int)msg.wParam;
}

使用此 hack 的最后一行控件是迄今为止我能实现的最佳控件:

win10和win7下的截图

如您所见,仍然存在的一个问题是 Button 和 Edit 控件的高度与 Windows 10 的视觉样式主题不同。所以我仍然很高兴看到这个问题的更好答案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM