[win32] Opening a window

[win32] Opening a window

  • Every window in Windows has to under go this procedure

  • Define a WinMain entry point

  • Define a window class: WNDCLASS

         typedef struct tagWNDCLASSA {
             UINT      style;         // Bitflags for window properties
             WNDPROC   lpfnWndProc;   // Our event handling function
             int       cbClsExtra;    // Store extra bytes along with window cclass
             int       cbWndExtra;    // Extra memory associated with the window
             HINSTANCE hInstance;     // Instance the class should belong to
             HICON     hIcon;         // Icon for the window
             HCURSOR   hCursor;       // Cursor to use in the window
             HBRUSH    hbrBackground; // Brush to clear the window with
             LPCSTR    lpszMenuName;  // Used for menu bars
             LPCSTR    lpszClassName; // A name we give to the class
         } WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;
    

    Relevant syles are: (All of them seem to not be necessary anymore?)

    • CS_CLASSDC: share DeviceContext along all windows belonging to this windows class
    • CS_OWNDC: every window has its own DeviceContext ; So it does not have to be aquired and released
    • CS_HREDRAW: if the window should be redrawn if movement or size adjustment change the width of the client rect
    • CS_VREDRAW: if the window should be redrawn if movement or size adjustment change the height of the client rect
  • Define a window procedure: WndProc. Windows will call it to handle some events. If it did handle the event it should return 0. If the function does not want to handle the message and invoke the default behaviour, it can call DefWindowProc with the same arguments and return its return value.

         LRESULT WndProc(
             HWND   window,  // Handle to the window
             UINT   message, // The message that should be handled
             WPARAM w_param, // Additional message information
             LPARAM l_param  // Additional message information
         );
    

    Important messages are:

    • WM_SIZE: User changes the size of the window
    • WM_DESTROY: Windows deletes the window
    • WM_CLOSE: User closes the window
    • WM_ACTIVATEAPP: If the windows becomes active (e.g. user clicked it)
    • WM_PAINT: If the window should redraw
  • Register the window class by calling RegisterClass. If the function fails it returns 0.

         ATOM RegisterClassA(
            [in] const WNDCLASSA *lpWndClass // pointer to the window class
         );
    
  • Create a window by calling CreateWindow or CreateWindowEx. Returns a window handle on success or NULL otherwise.

         HWND CreateWindowExA(
           [in]           DWORD     dwExStyle,    // bitfield: e.g. scrollbars, drag&drop, ....
           [in, optional] LPCSTR    lpClassName,  // the name of our class
           [in, optional] LPCSTR    lpWindowName, // the name of the window
           [in]           DWORD     dwStyle,      // bitfield: e.g. border, caption, ...
           [in]           int       X,            // coodinates or CW_USEDEFAULT
           [in]           int       Y,            // coodinates or CW_USEDEFAULT
           [in]           int       nWidth,       // size or CW_USEDEFAULT
           [in]           int       nHeight,      // size or CW_USEDEFAULT
           [in, optional] HWND      hWndParent,   // parent window
           [in, optional] HMENU     hMenu,        // menu bar
           [in, optional] HINSTANCE hInstance,    // the instance
           [in, optional] LPVOID    lpParam       // Optional additional parameter for the
                                                  //   window, this would be a param for the
                                                  //   WM_CREATE message
         );
    
  • Write a message loop. Everytime you create a Windows application, Windows automatically creates a message queue for the application that it (or anybody else) sends messages to you with.

    There are two ways to pull messages off the queue

    • GetMessage: Returns the next message or if there is none, block until one arrives

              // returns 0 if the message was WM_QUIT
              BOOL GetMessage(
                 [out]          LPMSG lpMsg,         // out-param; the message
                 [in, optional] HWND  hWnd,          // if 0: get message of any window,
                                                     // else: only get messages from the given window
                 [in]           UINT  wMsgFilterMin, // Filters to only limit to some message range
                 [in]           UINT  wMsgFilterMax  // Filters to only limit to some message range
                                                     //   If both filters are 0, all messages are returned
              );
      
    • PeekMessage: Returns the next message but if there is none, don’t block. It has the same signature as GetMessage, only with an additional parameter, if the messages should be removed from the queue.

    Then the messages have to be translated via TranslateMessage. TranslateMessage translates virtual-key messages into character messages.

        BOOL TranslateMessage(
          [in] const MSG *lpMsg
        );
    

    Then the messages have the be dispatched to the handler via DispatchMessage.

        LRESULT DispatchMessage(
          [in] const MSG *lpMsg
        );
    

Notes #

  • Even though we pull out the messages ourselves, we should not call the handler function directly. Everything should go through DispatchMessage. Windows also reserves the right to call our handler when we yield to the system (for example while waiting in GetMessage). It does not have to happen via DispatchMessage.
    • Takeaway: Not every message goes through the message queue, some get short circuited by Windows directly to the WindowProc

My Hello World with buttons (no error checking) #

#include <Windows.h>

HWND button_hello;
HWND button_quit;

LRESULT CALLBACK window_callback(HWND window, UINT message,
                                 WPARAM w_param, LPARAM l_param)
{
    LRESULT result = 0;
    switch (message) {
        case WM_CLOSE: {
            PostQuitMessage(0);
        } break;

        case WM_COMMAND: {
            HWND pressed_button = (HWND)l_param;

            if (pressed_button == button_quit) {
                PostQuitMessage(0);
            } else if (pressed_button == button_hello) {
                DestroyWindow(button_hello);
                button_quit =
                    CreateWindowExA(0, "BUTTON", "quit",
                                    WS_TABSTOP|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON,
                                    120, 10, 100, 100,
                                    window, 0, GetModuleHandle(0), 0);

            }
        } break;

        case WM_PAINT: {
            PAINTSTRUCT paint;
            HDC device_context = BeginPaint(window, &paint);
            int x = paint.rcPaint.left;
            int y = paint.rcPaint.top;
            int width = paint.rcPaint.right - paint.rcPaint.left;
            int height = paint.rcPaint.bottom - paint.rcPaint.top;
            PatBlt(device_context, x, y, width, height, WHITENESS);
            EndPaint(window, &paint);
        } break;

        default: {
            result = DefWindowProc(window, message, w_param, l_param);
        }
    }
    return result;
}


int WINAPI WinMain(HINSTANCE instance, HINSTANCE prev_instance,
                   PSTR cmd_line, int show_command)
{
    WNDCLASSA window_class {
        .lpfnWndProc   = window_callback,
        .hInstance     = instance,
        .lpszClassName = "MyWindowClass"
    };

    RegisterClassA(&window_class);

    HWND main_window =
        CreateWindowExA(0, "MyWindowClass", "MyWindowName",
                        WS_OVERLAPPEDWINDOW|WS_VISIBLE,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        300, 300,
                        0, 0, instance, 0);

    button_hello =
        CreateWindowExA(0, "BUTTON", "hello",
                        WS_TABSTOP|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON,
                        10, 10, 100, 100,
                        main_window, 0, instance, 0);


    while (true) {
        MSG message;
        BOOL message_result = GetMessageA(&message, 0, 0, 0);
        if (message_result == 0 || message_result == -1)
            break;

        TranslateMessage(&message);
        DispatchMessageA(&message);

    }

    return 0;
}

Handmade Heros Hello World #

#include <windows.h>

LRESULT CALLBACK
MainWindowCallback(HWND Window,
                   UINT Message,
                   WPARAM WParam,
                   LPARAM LParam)
{
    LRESULT Result = 0;

    switch(Message)
    {
        case WM_SIZE:
        {
            OutputDebugStringA("WM_SIZE\n");
        } break;

        case WM_CLOSE:
        {
            OutputDebugStringA("WM_CLOSE\n");
        } break;

        case WM_ACTIVATEAPP:
        {
            OutputDebugStringA("WM_ACTIVATEAPP\n");
        } break;

        case WM_DESTROY:
        {
            OutputDebugStringA("WM_DESTROY\n");
        } break;

        case WM_PAINT:
        {
            PAINTSTRUCT Paint;
            HDC DeviceContext = BeginPaint(Window, &Paint);
            int X = Paint.rcPaint.left;
            int Y = Paint.rcPaint.top;
            int Width = Paint.rcPaint.right - Paint.rcPaint.left;
            int Height = Paint.rcPaint.bottom - Paint.rcPaint.top;
            static DWORD Operation = WHITENESS;
            PatBlt(DeviceContext, X, Y, Width, Height, Operation);
            if(Operation == WHITENESS)
            {
                Operation = BLACKNESS;
            }
            else
            {
                Operation = WHITENESS;
            }
            EndPaint(Window, &Paint);
        } break;

        default:
        {
//            OutputDebugStringA("default\n");
            Result = DefWindowProc(Window, Message, WParam, LParam);
        } break;
    }

    return(Result);
}

int CALLBACK
WinMain(HINSTANCE Instance,
        HINSTANCE PrevInstance,
        LPSTR CommandLine,
        int ShowCode)
{
    WNDCLASS WindowClass = {};

    // TODO(casey): Check if HREDRAW/VREDRAW/OWNDC still matter
    WindowClass.lpfnWndProc = MainWindowCallback;
    WindowClass.hInstance = Instance;
//    WindowClass.hIcon;
    WindowClass.lpszClassName = "HandmadeHeroWindowClass";

    if(RegisterClassA(&WindowClass))
    {
        HWND WindowHandle =
            CreateWindowExA(
                0,
                WindowClass.lpszClassName,
                "Handmade Hero",
                WS_OVERLAPPEDWINDOW|WS_VISIBLE,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                0,
                0,
                Instance,
                0);
        if(WindowHandle)
        {
            for(;;)
            {
                MSG Message;
                BOOL MessageResult = GetMessageA(&Message, 0, 0, 0);
                if(MessageResult > 0)
                {
                    TranslateMessage(&Message);
                    DispatchMessageA(&Message);
                }
                else
                {
                    break;
                }
            }
        }
        else
        {
            // TODO(casey): Logging
        }
    }
    else
    {
        // TODO(casey): Logging
    }

    return(0);
}

Wikipedias Hello World #

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
   static TCHAR const szAppName[] = TEXT("Klassenname");
   HWND       hWnd;
   MSG        msg;
   WNDCLASSEX wndclassex;

   wndclassex.cbSize        = sizeof (WNDCLASSEX);
   wndclassex.style         = CS_HREDRAW | CS_VREDRAW;
   wndclassex.lpfnWndProc   = &WndProc;
   wndclassex.cbClsExtra    = 0;
   wndclassex.cbWndExtra    = 0;
   wndclassex.hInstance     = hInstance;
   wndclassex.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
   wndclassex.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wndclassex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
   wndclassex.lpszMenuName  = NULL;
   wndclassex.lpszClassName = szAppName;
   wndclassex.hIconSm       = wndclassex.hIcon;

   if (!RegisterClassEx(&wndclassex))
   {
      MessageBox(NULL, TEXT("RegisterClassEx fehlgeschlagen!"),
                 szAppName, MB_OK | MB_ICONERROR);
      return -1;
   }

   hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, // erweiterter Fensterstil
                  szAppName, // Name der Fensterklasse
                  TEXT("Fenstertitel"), // Fenstertitel
                  WS_OVERLAPPEDWINDOW, // Fensterstil
                  CW_USEDEFAULT, // X-Position des Fensters
                  CW_USEDEFAULT, // Y-Position des Fensters
                  CW_USEDEFAULT, // Fensterbreite
                  CW_USEDEFAULT, // Fensterhöhe
                  NULL, // Übergeordnetes Fenster
                  NULL, // Menü
                  hInstance, // Programm-Kopiezähler (Programm-ID)
                  NULL); // zusätzliche Parameter

   ShowWindow(hWnd, iCmdShow);
   UpdateWindow(hWnd);

   while(GetMessage(&msg, NULL, 0, 0))
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

   UnregisterClass(szAppName, hInstance);

   return (int)msg.wParam;
}

// Die Hauptnachrichtenschleife
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;

   switch (message)
   {
   case WM_PAINT:
       hdc = BeginPaint(hWnd, &ps);
       TextOut(hdc, 0, 0, TEXT("Hello World!"), 12);
       EndPaint(hWnd, &ps);
       return 0;

   case WM_CLOSE:
       DestroyWindow(hWnd);
       break;

   case WM_DESTROY:
       PostQuitMessage(0);
       return 0;
   }

   return DefWindowProc(hWnd, message, wParam, lParam);
}
Code Snippet 1: "Hello World" example taken from Wikipedia
Calendar October 22, 2023