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;
}
Compile and link
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.
Subscribe to my newsletter
Read articles from Ciprian Fusa directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ciprian Fusa
Ciprian Fusa
I am a .NET Developer and Consultant with over 15 years of experience, I specialize in crafting scalable, high-performance applications that drive business growth and innovation.