在 Win32 中创建 ComboBox 无法正常工作

Creating ComboBox in Win32 not working properly

我是第一次创建 Win32 ComboBox。我这里有个问题。

当为 ComboBox 调用 CreateWindow 时,它使用 WM_CREATE 消息再次调用 WndProc 回调函数,所以发生的是 ComboBox 生成一个子 ComboBox,像递归一样一次又一次。

代码如下:

#include <stdio.h> 
#include <conio.h> 
#include <Windows.h> 
#include <random> 
#include <time.h> 
#include <string>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HINSTANCE g_hInst; 
LPCTSTR lpszClass = L"ComboBox"; 
const WCHAR *items[] = { L"Apple", L"Orange", L"Melon", L"Grape", L"Strawberry" };
HWND hwnd;

enum COMMAND_ID {
    COMMAND_ID_CONTROL_COMBO_0
};

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{ 
    srand(time(NULL));
    g_hInst = hInstance;

    WNDCLASS wndClass;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndClass.hInstance = hInstance;
    wndClass.lpfnWndProc = WndProc;
    wndClass.lpszClassName = lpszClass;
    wndClass.lpszMenuName = NULL;
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&wndClass);

    hwnd = CreateWindow(
        lpszClass,
        lpszClass,  
        WS_CAPTION | WS_SYSMENU | WS_THICKFRAME,
        CW_USEDEFAULT, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT,   
        NULL,       
        (HMENU)NULL,        
        hInstance,      
        NULL);

    ShowWindow(hwnd, nCmdShow);

    MSG msg;
    while (true)
    {   
        GetMessage(&msg, NULL, 0, 0);
        if (msg.message == WM_QUIT)
            break; 
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
} 

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam)
{   
    static HWND hCombo;
    static WCHAR str[128];

    switch (msg)
    {
        case WM_CREATE:
        {
            hCombo = CreateWindow(
                L"combobox",
                NULL,
                WS_CHILD | WS_VISIBLE | WS_BORDER | CBS_DROPDOWN,
                10, 10, 200, 200,
                hWnd,
                (HMENU)COMMAND_ID_CONTROL_COMBO_0,
                g_hInst,
                NULL);

            for (int i = 0; i < 5; ++i)
            {
                SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)items[i]);
            }

            SendMessage(hCombo, CB_SETCURSEL, 0, NULL);
        }
        break;

        case WM_COMMAND:
        {
            switch (LOWORD(wparam))
            {
                case COMMAND_ID_CONTROL_COMBO_0:
                    switch (HIWORD(wparam))
                    {
                        case CBN_SELCHANGE:
                        {
                            int iCurSel = SendMessage(hCombo, CB_GETCURSEL, NULL, NULL);
                            SendMessage(hCombo, CB_GETLBTEXT, iCurSel, (LPARAM)str);
                            SetWindowText(hWnd, str);
                        }
                        break;

                        case CBN_EDITCHANGE:
                            GetWindowText(hCombo, str, 128);
                            SetWindowText(hWnd, str);
                            break;
                    }
                    break;

                default:
                    break;
            }
        }
        return 0;
    }

    return DefWindowProc(hWnd, msg, wparam, lparam);
}

结果如下:

我试图设置一些布尔标志来只执行一次 WM_CREATE,它起作用了,我的意思是只创建一个没有任何子项的 ComboBox。

但是,它看起来只是一个带有边框标记的白色 window,没有箭头按钮或 ComboBox 应该具有的任何下拉页面。

当我创建按钮、复选框、列表框等不同的控件时,这种递归情况从未发生过

并且创建的 ComboBox 看起来也没有正确的形状。

希望我只是遗漏了一些简单的东西。

您实际上根本没有创建 Win32 ComboBox。您正在注册自己的名为 "ComboBox" 的 class,然后创建 class 的 window,这会创建 class 的 window,它创建了 class 的 window,递归地依此类推。

您需要更改此行:

LPCTSTR lpszClass = L"ComboBox";

到不同的唯一名称,例如 "MyWindowClass"


附带说明一下,您的消息循环结构错误。它应该看起来像这样:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

并且在您的 WndProc() 中,return 0; 语句 下方 WM_COMMAND 处理程序位于错误的位置。它需要移动 insideWM_COMMAND 处理程序而不是:

case WM_COMMAND:
{
    switch (...)
    {
        ...
    }
    return 0; // <-- moved here
}
//return 0; // <-- from here

When calling CreateWindow for the ComboBox, it calls the WndProc callback function again with the WM_CREATE message, so what happens is the ComboBox makes a child ComboBox, again and again like recursion.

WM_CREATE消息在window创建后被发送到新window的window过程。您的第一条 WM_CREATE 消息是由这一行 hwnd = CreateWindow() 生成的。然后在第一条 WM_CREATE 消息中创建另一个 window,因此它将生成第二条 WM_CREATE 消息。因为你用同一个注册的class("ComboBox" / "combobox",不区分大小写)创建所有这些windows,它们都使用同一个window 程序。所以你一次又一次地收到 WM_CREATE 消息,直到 CreateWindow 无法创建 window 和 return NULL.

But, it just looked like only a white window with a border mark, there's no arrow button or anything to dropdown page that the ComboBox is supposed to have.

根本原因是您注册了一个与现有系统 class 同名的 class:"ComboBox" / "combobox"。这个新注册的 class 覆盖了现有的。它只是一个常见的 window 而不是 @RemyLebeau 指出的预定义组合框控件。

An application can register an application local class having the same name as a system class. This replaces the system class in the context of the application but does not prevent other applications from using the system class.

参考“How the System Locates a Window Class”。

要使 Combobox 以预期的形状显示,您需要做的是将 lpszClass 更改为非预定义的,例如 "SimpleComboBoxExample".

建议使用Combobox Class Name: WC_COMBOBOX 的预定义宏,而不是L"combobox".

更多参考:“How to Create a Simple Combo Box”。