[win32] Opening a window
-
Every window in Windows has to under go this procedure
-
Define a WinMain entry point
-
Define a window class:
WNDCLASStypedef 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 classCS_OWNDC: every window has its own DeviceContext ; So it does not have to be aquired and releasedCS_HREDRAW: if the window should be redrawn if movement or size adjustment change the width of the client rectCS_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 return0. If the function does not want to handle the message and invoke the default behaviour, it can callDefWindowProcwith 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 windowWM_DESTROY: Windows deletes the windowWM_CLOSE: User closes the windowWM_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 returns0.ATOM RegisterClassA( [in] const WNDCLASSA *lpWndClass // pointer to the window class ); -
Create a window by calling
CreateWindoworCreateWindowEx. Returns a window handle on success orNULLotherwise.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);
}