Search code examples
winapicomboboxcontrolsparent-childwindow-messages

Creating ComboBox in Win32 not working properly


I am creating a Win32 ComboBox for the first time. And I have a problem 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.

Here is the code:

#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);
}

And here is the result:

image

I tried to put some boolean flag to execute WM_CREATE only once, and it works, I mean only creating one ComboBox without any child in it.

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.

This recursive case never happened when I was creating different controls like Buttons, CheckBoxes, ListBoxes, etc.

And the ComboBox created doesn't look like it has the proper shape, too.

Hope I am just missing something simple.


Solution

  • 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 message is sent to the window procedure of the new window after the window is created. Your first WM_CREATE message is generated by this line hwnd = CreateWindow(). Then you create another window in first WM_CREATE message, so it will generate second WM_CREATE message. Because you use the same registered class ("ComboBox" / "combobox", it is not case sensitive) to create all these windows, all of them use the same one window procedure. So you receive WM_CREATE message again and again until CreateWindow fail to create a window and 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.

    The root cause is you register a class with the same name as the existing system class: "ComboBox" / "combobox". This new registered class override the existing one. It is just a common window instead of a predefined Combobox control as @RemyLebeau pointed out.

    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.

    Refer to "How the System Locates a Window Class".

    To make the Combobox display in expected shape, what you need to do is changing the lpszClass to a non-predefined one, for example, like "SimpleComboBoxExample".

    It is suggested to use predefined macro of Combobox Class Name: WC_COMBOBOX instead of L"combobox".

    More reference: "How to Create a Simple Combo Box".