Создание ComboBox в Win32 не работает должным образом

Я впервые создаю Win32 ComboBox. А у меня тут проблема.

При вызове CreateWindow для ComboBox он снова вызывает функцию обратного вызова WndProc с сообщением WM_CREATE, так что получается, что 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 без каких-либо дочерних элементов в нем.

Но это выглядело только как белое окно с рамкой, без кнопки со стрелкой или чего-либо еще на раскрывающейся странице, которую должен иметь ComboBox.

Этот рекурсивный случай никогда не случался, когда я создавал различные элементы управления, такие как кнопки, флажки, списки и т. д.

И созданный ComboBox тоже не выглядит так, как будто он имеет правильную форму.

Надеюсь, я просто пропустил что-то простое.


person KIM CHANGJUN    schedule 13.12.2020    source источник
comment
Поскольку ваше главное окно и дочернее окно имеют одно и то же имя класса, то и wndproc.   -  person user2120666    schedule 13.12.2020
comment
Код непонятен. Если вы хотите, чтобы люди помогали вам, вам нужно сделать так, чтобы им легко было видно вашу проблему. Однако это очень похоже на участника IOCCC и даже не соответствует категории в более известном формы программы. Пожалуйста, отредактируйте свой вопрос, чтобы исправить это.   -  person IInspectable    schedule 13.12.2020
comment
Ответ работает для вас?   -  person Rita Han    schedule 28.12.2020


Ответы (2)


При вызове CreateWindow для ComboBox он снова вызывает функцию обратного вызова WndProc с сообщением WM_CREATE, так что получается, что ComboBox снова и снова создает дочерний ComboBox, подобно рекурсии.

В оконную процедуру новое окно после создания окна. Ваше первое WM_CREATE сообщение генерируется этой строкой hwnd = CreateWindow(). Затем вы создаете другое окно в первом сообщении WM_CREATE, чтобы оно сгенерировало второе сообщение WM_CREATE. Поскольку вы используете один и тот же зарегистрированный класс ("ComboBox"/"combobox", регистр не учитывается) для создания всех этих окон, все они используют одну и ту же оконную процедуру. Таким образом, вы получаете сообщение WM_CREATE снова и снова, пока CreateWindow не сможет создать окно и не вернет NULL.

Но это выглядело только как белое окно с рамкой, без кнопки со стрелкой или чего-либо еще на раскрывающейся странице, которую должен иметь ComboBox.

Основная причина заключается в том, что вы регистрируете класс с тем же именем, что и существующий системный класс: "ComboBox"/"combobox". Этот новый зарегистрированный класс переопределяет существующий. Это просто обычное окно вместо предопределенного элемента управления Combobox, как указал @RemyLebeau.

Приложение может зарегистрировать локальный класс приложения, имеющий то же имя, что и системный класс. Это заменяет системный класс в контексте приложения, но не мешает другим приложениям использовать системный класс.

См. Как система находит класс окна.

Чтобы поле со списком отображалось в ожидаемой форме, вам нужно изменить lpszClass на непредопределенный, например, такой как "SimpleComboBoxExample".

Рекомендуется использовать предопределенный макрос Имя класса Combobox: WC_COMBOBOX вместо L"combobox".

Дополнительная ссылка: Как создать простой Поле со списком.

person Rita Han    schedule 23.12.2020

На самом деле вы вообще не создаете Win32 ComboBox. Вы регистрируете свой собственный класс с именем "ComboBox", а затем создаете окно этого класса, которое создает окно этого класса, которое создает окно этого класса и так далее рекурсивно.

Вам нужно изменить эту строку:

LPCTSTR lpszClass = L"ComboBox";

На другое уникальное имя, например "MyWindowClass".


С другой стороны, ваш цикл сообщений структурирован неправильно. Вместо этого это должно выглядеть так:

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

И в вашем WndProc() оператор return 0; под обработчиком WM_COMMAND находится не в том месте. Вместо этого его нужно переместить внутрь обработчика WM_COMMAND:

case WM_COMMAND:
{
    switch (...)
    {
        ...
    }
    return 0; // <-- moved here
}
//return 0; // <-- from here
person Remy Lebeau    schedule 13.12.2020