c++cwinapidialogcreatewindow

Creating a Dialog Box with text input and other user interactions with just Win32 API?


I'm making a (modal) dialog box with text input in C or C++ with just the Win32 API by itself (without MFC or such):

image

I have done it from scratch (see code below), and it works, but each time I add a new basic functionality and use it, I discover another missing feature which is nevertheless common in such dialog boxes with text inputs (present in so many software!):

Question: How, with pure Win32 API and C/C++, to create a dialog box with a text input, without reinventing the wheel + the endless list of little UI interactions we are used to?

Is there a built-in way in the Win32 API?

Practically, would someone have a minimal main.cpp example, compilable with cl main.cpp /link user32.lib or similar, achieving this without requiring a .rc file, and without requiring an IDE?

Note: if using a .rc makes the code simpler, then let's use a .rc file. Here without .rc file the example Creating a Template in Memory looks unnecessarily complex.


My current code:

LRESULT CALLBACK EditWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch (message) {
        case WM_CREATE:
            hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_LEFT, 10, 10, 580, 25, hWnd, (HMENU)ID_EDIT, NULL, NULL);
            SetFocus(hEdit);
            originalEditProc = (WNDPROC)GetWindowLongPtr(hEdit, GWLP_WNDPROC);
            SetWindowLongPtr(hEdit, GWLP_WNDPROC, (LONG_PTR)EditWndProc);
            CreateWindow("BUTTON", "OK", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 10, 50, 80, 30, hWnd, (HMENU)ID_OK, NULL, NULL);
            CreateWindow("BUTTON", "Cancel", WS_CHILD | WS_VISIBLE, 100, 50, 80, 30, hWnd, (HMENU)ID_CANCEL, NULL, NULL);
            break;
        case WM_COMMAND: {
            int wmId = LOWORD(wParam);
            if (wmId == ID_OK) {
                GetWindowText(hEdit, g_input, sizeof(g_input));
                DestroyWindow(hWnd);
            } else if (wmId == ID_CANCEL) {
                g_input[0] = '\0';
                DestroyWindow(hWnd);
            }
            break;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

LRESULT CALLBACK EditWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch (message) {
        case WM_KEYDOWN:
            if (wParam == VK_RETURN) {
                GetWindowText(hWnd, g_input, sizeof(g_input));
                HWND parentWnd = GetParent(hWnd);
                PostMessage(parentWnd, WM_COMMAND, ID_OK, 0);
                return 0; // Prevent default processing (no newline in edit control)
            }
            break;
        default:
            if (originalEditProc) {
                return CallWindowProc(originalEditProc, hWnd, message, wParam, lParam);
            }
            break;
    }
    return 0;
}

Solution

  • Thanks to @SimonMourier's example + modifications to add an Edit box, here is a minimal example, compilable with:

    call "C:\path\to\vcvarsall.bat" x64
    rc test.rc
    cl test.cpp /DUNICODE /link user32.lib test.res
    

    It has ARROW keys, CTRL+C / CTRL+X / CTRL+V, BACKSPACE, DEL, HOME, END keys, selection with SHIFT, right click context menu, TAB to cycle between elements, out of the box.

    Notes:

    resource.h

    #define IDS_APP_TITLE           103
    #define IDR_MAINFRAME           128
    #define IDD_TEST_DIALOG         102
    #define IDD_ABOUTBOX            103
    #define IDM_ABOUT               104
    #define IDM_EXIT                105
    #define IDI_TEST                107
    #define IDI_SMALL               108
    #define IDC_TEST                109
    #define IDC_MYEDIT              110
    #define IDC_MYICON              2
    #define IDC_STATIC              -1
    #define MAX_LOADSTRING          100
    

    test.rc

    #include <windows.h>
    #include "resource.h"
    LANGUAGE 9, 1
    IDI_TEST ICON "test.ico"
    IDC_TEST MENU
    BEGIN
        POPUP "&File"
        BEGIN
            MENUITEM "E&xit",                IDM_EXIT
        END
        POPUP "&Help"
        BEGIN
            MENUITEM "&About ...",           IDM_ABOUT
        END
    END
    IDC_TEST ACCELERATORS
    BEGIN
        "?", IDM_ABOUT, ASCII, ALT
        "/", IDM_ABOUT, ASCII, ALT
    END
    IDD_ABOUTBOX DIALOGEX 0, 0, 235, 62
    STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
    CAPTION "About Test"
    FONT 8, "MS Shell Dlg"
    BEGIN
        CONTROL "Edit Control", IDC_MYEDIT, "Edit", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL | WS_TABSTOP, 14, 14, 200, 12
        DEFPUSHBUTTON   "OK",IDOK,113,41,50,14,WS_GROUP
        PUSHBUTTON   "Cancel",IDCANCEL,173,41,50,14,WS_GROUP
    END
    STRINGTABLE
    BEGIN
       IDC_TEST   "TEST"
       IDS_APP_TITLE "TEST"
    END
    

    test.cpp

    #include <windows.h>
    #include "resource.h"
    HINSTANCE hInst;
    WCHAR szTitle[MAX_LOADSTRING];
    WCHAR szWindowClass[MAX_LOADSTRING];
    ATOM MyRegisterClass(HINSTANCE hInstance);
    BOOL InitInstance(HINSTANCE, int);
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) {
        LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
        LoadStringW(hInstance, IDC_TEST, szWindowClass, MAX_LOADSTRING);
        MyRegisterClass(hInstance);
        if (!InitInstance(hInstance, nCmdShow))
            return FALSE;
        HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST));
        MSG msg;
        while (GetMessage(&msg, nullptr, 0, 0)) {
            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        return (int)msg.wParam;
    }
    ATOM MyRegisterClass(HINSTANCE hInstance) {
        WNDCLASSEXW wcex{};
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProc;
        wcex.hInstance = hInstance;
        wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST));
        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TEST);
        wcex.lpszClassName = szWindowClass;
        return RegisterClassExW(&wcex);
    }
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) {
        hInst = hInstance;
        HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
        if (!hWnd)
            return FALSE;
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);
        return TRUE;
    }
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
        int wmId = LOWORD(wParam);
        switch (message) {
            case WM_COMMAND:
                switch (wmId) {
                    case IDM_ABOUT:
                        DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                        break;
                    case IDM_EXIT:
                        DestroyWindow(hWnd);
                        break;
                    default:
                        return DefWindowProc(hWnd, message, wParam, lParam);
                }
                break;
            case WM_PAINT: {
                    PAINTSTRUCT ps;
                    HDC hdc = BeginPaint(hWnd, &ps);
                    EndPaint(hWnd, &ps);
                }
                break;
            case WM_DESTROY:
                PostQuitMessage(0);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }
    INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message) {
            case WM_INITDIALOG:
                return (INT_PTR)TRUE;
            case WM_COMMAND:
                if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
                    EndDialog(hDlg, LOWORD(wParam));
                    return (INT_PTR)TRUE;
                }
                break;
        }
        return (INT_PTR)FALSE;
    }