Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building the Smallest Win32 GUI Application in C++ with Visual Studio

Tech 2

Project setup in Visual Studio

  • File → New → Project
  • Select "Installed" → "C++" → "Windows Desktop" → "Windows Desktop Wizard" (or "Win32 Project" on older VS)
  • Name the project (for example, MinimalWin32App)
  • Choose "Windows application" and check "Empty project" (no ATL/MFC, no precompiled header if you prefer manual setup)
  • Add a new C++ source file (e.g., main.cpp)

Required headers

At minimum, include the Win32 API definitions.

#include <windows.h>

Modern Visual Studio defaults to Unicode. The code below uses wide-character (W) APIs.

Entry point and program flow

A GUI Win32 program starts from WinMain or wWinMain. The core steps are:

  • Define and register a window class (WNDCLASSEX)
  • Create the main window
  • Show and udpate the window
  • Run the message loop
  • Handle messages in a window procedure (WndProc)

Complete minimal example

The following self-contained program creates a window and paints "Hello, Windows!" centerde in the client area.

#include <windows.h>

// Window procedure declaration
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow) {
    // 1) Register a window class
    const wchar_t kClassName[]   = L"MinimalWin32WindowClass";
    const wchar_t kWindowTitle[] = L"Minimal Win32 Window";

    WNDCLASSEXW wc{};
    wc.cbSize        = sizeof(wc);
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIconW(nullptr, IDI_APPLICATION);
    wc.hCursor       = LoadCursorW(nullptr, IDC_ARROW);
    wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
    wc.lpszMenuName  = nullptr;
    wc.lpszClassName = kClassName;
    wc.hIconSm       = wc.hIcon;

    if (!RegisterClassExW(&wc)) {
        MessageBoxW(nullptr, L"RegisterClassExW failed", L"Error", MB_ICONERROR);
        return 0;
    }

    // 2) Create the window
    HWND hwnd = CreateWindowExW(
        0,
        kClassName,
        kWindowTitle,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        640, 240,
        nullptr,
        nullptr,
        hInstance,
        nullptr
    );

    if (!hwnd) {
        MessageBoxW(nullptr, L"CreateWindowExW failed", L"Error", MB_ICONERROR);
        return 0;
    }

    // 3) Show and update the window
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // 4) Message loop
    MSG msg{};
    while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    return static_cast<int>(msg.wParam);
}

// 5) Window procedure: handle paint and destroy
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            RECT rc;
            GetClientRect(hwnd, &rc);
            SetBkMode(hdc, TRANSPARENT);
            SetTextColor(hdc, RGB(32, 32, 32));
            const wchar_t* text = L"Hello, Windows!";
            DrawTextW(hdc, text, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

            EndPaint(hwnd, &ps);
            return 0;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProcW(hwnd, msg, wParam, lParam);
    }
}

Key building blocks

  • WNDCLASSEX: Describes the window type (procedure function, background brush, cursor, icons, style, and class name). Registration with RegisterClassExW makes the class available to CreateWindowExW.
  • CreateWindowExW: Instantiates the top-level window using the registered class.
  • ShowWindow/UpdateWindow: Makes the window visible and triggers an initial paint if needed.
  • Message loop: Retrieves messages from the queue and routes them too the window procedure.
  • Window procedure (WindowProc): Switches on message IDs. WM_PAINT uses BeginPaint/EndPaint to draw; WM_DESTROY posts the quit message.

Notes

  • With Unicode builds, always call wide-character APIs (the W-suffixed versions) or rely on the TCHAR mapping if you prefer the generic macros.
  • DrawTextW is used instead of TextOut to conveniently center text within the client rectangle.
  • If project settings disable precompiled headers or additional libraries, ensure the subsystem is "Windows" and default libraries for User32/GDI32 are linked by the toolchain.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.