为什么在更改WinAPI控件的大小后不调用WM_NCCALSIZE?

时间:2018-06-13 18:36:52

标签: c++ winapi controls

我有一个丰富的编辑控件,它的大小在创建后会被更改。

我想自定义控件的外观,因此每次更改控件的大小时,我都需要重新计算其客户区的大小。想法是每次组件收到WM_NCCALCSIZE消息时更改客户区的大小,因为WM_NCCALCSIZE的MSDN文档声明:

  

必须计算窗口客户区的大小和位置时发送。通过处理此消息,当窗口的大小或位置发生变化时,应用程序可以控制窗口客户区的内容。

问题是在控件大小改变后没有调用WM_NCCALCSIZE(在创建控件时只调用一次)。

此行为并非特定于丰富的编辑控件。同样如此,例如用于按钮控件。在屏幕上显示组件之前或之后是否更改大小也无关紧要。

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

#pragma comment(lib, "comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' \
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK RichEditProc(HWND, UINT, WPARAM, LPARAM);

WNDPROC richEditOrigProc;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    LoadLibrary(TEXT("msftedit.dll"));

    WNDCLASSEX mainwcex;
    mainwcex.cbSize = sizeof(WNDCLASSEX);
    mainwcex.style = CS_HREDRAW | CS_VREDRAW;
    mainwcex.lpfnWndProc = WindowProc;
    mainwcex.cbClsExtra = 0;
    mainwcex.cbWndExtra = 0;
    mainwcex.hInstance = hInstance;
    mainwcex.hIcon = NULL;
    mainwcex.hCursor = (HICON)LoadCursor(NULL, IDC_ARROW);
    mainwcex.hbrBackground = GetSysColorBrush(COLOR_MENU);
    mainwcex.lpszMenuName = NULL;
    mainwcex.lpszClassName = "mainwindow";
    mainwcex.hIconSm = NULL;

    RegisterClassEx(&mainwcex);

    HWND mainWindow = CreateWindowEx(
        NULL,
        "mainwindow",
        NULL,
        WS_OVERLAPPEDWINDOW,
        100,
        100,
        600,
        400,
        NULL,
        NULL,
        hInstance,
        NULL);

    HWND richEditControl = CreateWindowEx(
        NULL,
        "RICHEDIT50W",
        "Rich Edit",
        WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
        50,
        50,
        100,
        25,
        mainWindow,
        NULL,
        hInstance,
        NULL);

    richEditOrigProc = (WNDPROC) SetWindowLongPtr(richEditControl, GWLP_WNDPROC, (LONG_PTR) RichEditProc);

    // Changes the width of rich edit control from 100 px to 400 px.
    SetWindowPos(richEditControl, NULL, 0, 0, 400, 25,
        SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);

    ShowWindow(mainWindow, nCmdShow);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!IsDialogMessage(mainWindow, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return static_cast<int>(msg.wParam);
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK RichEditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
        case WM_NCCALCSIZE:
        {
            RECT newClientRect;
            GetWindowRect(hWnd, &newClientRect);
            MapWindowPoints(HWND_DESKTOP, GetParent(hWnd), reinterpret_cast<POINT*>(&newClientRect), 2);
            InflateRect(&newClientRect, -3, -3);
            int width = newClientRect.right - newClientRect.left;
            int height = newClientRect.bottom - newClientRect.top;
            if (wParam) {
                NCCALCSIZE_PARAMS* ncParams = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
                ncParams->rgrc[0].left = newClientRect.left;
                ncParams->rgrc[0].top = newClientRect.top;
                ncParams->rgrc[0].right = newClientRect.right;
                ncParams->rgrc[0].bottom = newClientRect.bottom;
                return WVR_HREDRAW;
            }
            return 0;
        }
    }
    return CallWindowProc(richEditOrigProc, hWnd, uMsg, wParam, lParam);
}

在此示例中,很明显,虽然富编辑控件的总大小已更改,但它的客户区域不会重新计算并与其初始大小相对应。

调整自定义控件客户区域的正确方法是什么,以反映其总大小的变化。

3 个答案:

答案 0 :(得分:0)

如果您想在调整控件的大小时执行操作,则应该抓住WM_SIZE,而不是WM_NCCALCSIZE

WM_NCCALCSIZE的目的是允许(例如)标题窗口在标题栏的高度发生变化时重新计算其客户区的大小。 (用户可以通过控制面板进行调整,或者至少是他们习惯的。)我希望在创建控件时发送WM_NCCALCSIZE,这有点神秘感。

WM_SIZE已完整记录在MSDN

另外:我没有看到你在代码中的任何地方实际调整控件的大小......

答案 1 :(得分:0)

如果你坚持使用WM_NCCALCSIZE,你应该能够通过指定SWP_FRAMECHANGED标志强制它与SetWindowPos。

答案 2 :(得分:0)

WM_NCCALSIZE消息的处理方式错误。不应将新客户端矩形的坐标设置为rgrc[0]的{​​{1}}矩形,而应仅设置相对于控件的总界限的递增/递减。这些递增/递减值指定非客户区域的大小,即客户区域的边距。

更改控件的大小后,客户区的大小会相对于新控件的范围自动调整。

例如,此代码将客户区设置为NCCALCSIZE_PARAMS的上/下边距以及3 px的左/右边距。

50 px