Win32 API programming with C - Using common controls

Introduction

Using Win32 API common controls in a C programming environment involves leveraging the rich set of user interface components provided by the Windows operating system. Common controls are pre-defined, reusable control classes such as buttons, edit controls, list views, tree views, and more, which can be used to build the graphical user interface (GUI) of an application. This article provides an overview of how to incorporate these controls into your C applications using the Win32 API.

A control is a child window that an application uses in conjunction with another window to enable user interaction. Controls are most often used within dialog boxes, but they can also be used in other windows.

Include Common Controls

Before we can use the common controls in our application, we need to ensure that our project includes the necessary header file and library. The primary header file for common controls is commctrl.h, which defines the functions, macros, and structures used to create and manage these controls.

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

Additionally, the project must link against the comctl32.lib library. If you're using a command-line compiler like GCC, you can link against this library using the -lcomctl32 option.

Initialize Common Controls

Before creating any common control, you must initialize the common controls library by calling the InitCommonControlsEx function. This function enables specific control classes or sets of controls in your application. It takes a pointer to an INITCOMMONCONTROLSEX structure as its parameter, which specifies which control classes you intend to use.

INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_STANDARD_CLASSES;  // Enables a set of common controls.
InitCommonControlsEx(&icex);

Create common controls

Once the common controls library is initialized, we can create controls by using the CreateWindowEx function, specifying the type of control you want to create via the control's class name. Each control type has a predefined class name, such as "BUTTON", "EDIT", "LISTVIEW", etc.

For example, to create a button control:

// Add Button control
HWND hwndButton = CreateWindowEx(
    0,                                                     // Optional window styles.
    "BUTTON",                                              // Predefined class; Button.
    "Click Me",                                            // Button text.
    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles.
    350,                                                   // x position.
    50,                                                    // y position.
    100,                                                   // Button width.
    50,                                                    // Button height.
    hwnd,                                                  // Parent window.
    (HMENU)ID_MYBUTTON,                                    // Button ID.
    (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE),     // Instance handle
    NULL);                                                 // Pointer not needed.

Handle Messages

To make common controls interactive, we need to handle messages sent by these controls to the parent window. This is typically done in the window procedure of the parent window. For instance, when a button is clicked, it sends a WM_COMMAND message to its parent window.

case WM_COMMAND:
    if (LOWORD(wParam) == ID_MYBUTTON) {
        MessageBox(hwnd, "Button clicked!", "Notification", MB_OK);
    }
    break;

Let's add and Edit and a Button control for our application. When we click the Button we want to display the text that was added inside the Edit control.

Here's the full code below:

#include <windows.h>
#include <commctrl.h>
#include "resource.h"

#pragma comment (lib, "comctl32")

// global variables
const char g_szClassName[] = "myWindowClass";

#define IDC_MAIN_EDIT    103
#define ID_MYBUTTON        104

// function declarations
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LPSTR ReadTextFromEdit(HWND hEdit);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG msg;

    //Step 1: Register the Window Class
    wc.cbSize         = sizeof(WNDCLASSEX);                                                                           // The size, in bytes, of this structure
    wc.style         = 0;                                                                                            // The class style(s)
    wc.lpfnWndProc     = WndProc;                                                                                      // A pointer to the window procedure.
    wc.cbClsExtra     = 0;                                                                                            // The number of extra bytes to allocate following the window-class structure.
    wc.cbWndExtra     = 0;                                                                                            // The number of extra bytes to allocate following the window instance.
    wc.hInstance     = hInstance;                                                                                    // A handle to the instance that contains the window procedure for the class.
    wc.hIcon         = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON));                                 // A handle to the class icon. This member must be a handle to an icon resource. If this member is NULL, the system provides a default icon.
    wc.hCursor         = LoadCursor(NULL, IDC_ARROW);                                                                  // A handle to the class cursor. This member must be a handle to a cursor resource. If this member is NULL, an application must explicitly set the cursor shape whenever the mouse moves into the application's window.
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);                                                                     // A handle to the class background brush.
    wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MYMENU);                                                                  // Pointer to a null-terminated character string that specifies the resource name of the class menu.
    wc.lpszClassName = g_szClassName;                                                                                // A string that identifies the window class.
    wc.hIconSm         = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 32, 32, 0);  // A handle to a small icon that is associated with the window class.

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        0,                      // Optional window styles.
        g_szClassName,          // Window class
        "My application",       // Window text
        WS_OVERLAPPEDWINDOW,    // Window style
        CW_USEDEFAULT,          // Position X
        CW_USEDEFAULT,          // Position Y
        800,                    // Width
        600,                    // Height
        NULL,                   // Parent window
        NULL,                   // Menu
        hInstance,              // Instance handle
        NULL                    // Additional application data
    );

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    INITCOMMONCONTROLSEX icex;
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC = ICC_STANDARD_CLASSES;  // Enables a set of common controls.
    InitCommonControlsEx(&icex);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CREATE:
        {
            // Add Edit control
            HFONT hfDefault;
            HWND hEdit;

            hEdit = CreateWindowEx(
                WS_EX_CLIENTEDGE,                                      // Optional window styles.
                "EDIT",                                                // Predefined class; Edit.
                "Default text",                                        
                WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
                0, 0, 300, 200, 
                hwnd,
                (HMENU)IDC_MAIN_EDIT, 
                (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE),
                NULL);
            if (hEdit == NULL)
                MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK | MB_ICONERROR);

            hfDefault = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
            SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault, MAKELPARAM(FALSE, 0));

            // Add Button control
            HWND hButton = CreateWindowEx(
                0,                                                     // Optional window styles.
                "BUTTON",                                              // Predefined class; Button.
                "Click Me",                                            // Button text.
                WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles.
                350,                                                   // x position.
                50,                                                    // y position.
                100,                                                   // Button width.
                50,                                                    // Button height.
                hwnd,                                                  // Parent window.
                (HMENU)ID_MYBUTTON,                                    // Button ID.
                (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE),
                NULL);                                                 // Pointer not needed.

            if (hButton == NULL)
                MessageBox(hwnd, "Could not create button.", "Error", MB_OK | MB_ICONERROR);

        }
        break;
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_MYBUTTON:
                    HWND hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
                    MessageBox(hwnd, ReadTextFromEdit(hEdit), "Notification", MB_OK);
                    break;
                case ID_FILE_ABOUT:
                    MessageBox(hwnd, "About menu item clicked", "Notice", MB_OK | MB_ICONINFORMATION);
                    break;
                case ID_FILE_EXIT:
                    DestroyWindow(hwnd);
                    break;
                }
            break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
        }
    return 0;
}

LPSTR ReadTextFromEdit(HWND hEdit)
{
    DWORD dwTextLength;

    dwTextLength = GetWindowTextLength(hEdit);
    if (dwTextLength > 0)
    {
        LPSTR editText;
        DWORD dwBufferSize = dwTextLength + 1;

        editText = (LPSTR)GlobalAlloc(GPTR, dwBufferSize);
        if (editText != NULL)
        {
            if (GetWindowText(hEdit, editText, dwBufferSize))
            {
                return editText;
            }
        }
    }

    return NULL;
}

Ensure your compiler and linker are correctly set up to include the Win32 API libraries, especially comctl32.lib, as mentioned earlier. If you're using an Integrated Development Environment (IDE) like Visual Studio, these settings might be configured through project properties.