Windows Programming Using Visual C++

Based on LaMothe's examples from Tricks of the Windows Game Programming Gurus

First Steps

Hungarian notation

In Microsoft applications variable names are prefixed by 1 or 2 character code indicating their type

First Windows Program

// DEMO2_2.CPP - a simple message box from LaMothe #define WIN32_LEAN_AND_MEAN // these 3 lines are always used #include <windows.h> // the main windows headers #include <windowsx.h> // a lot of cool macros // main entry point for all windows programs - don't need or use C++ main() int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow) { // call message box api with NULL for parent window handle MessageBox(NULL, "THERE CAN BE ONLY ONE!!!", "MY FIRST WINDOWS PROGRAM", MB_OK | MB_ICONEXCLAMATION); // exit program return(0); } // end WinMain

Everything in Windows programming is a window - even scrollbars and buttons

You begin by declaring a window using WNDCLASS and setting its style parameters (see wndclass declarations the example below).

You need to set the address of the event handler (in the example below WindowProc() is the event handler - which is a user defined function to handle all Windows messages

A Movable Window

// DEMO3_10.CPP - WM_MOVE demo from LaMothe // INCLUDES /////////////////////////////////////////////// #define WIN32_LEAN_AND_MEAN // just say no to MFC #include <windows.h> // include all the windows headers #include <windowsx.h> // include useful macros #include <mmsystem.h> // very important and include WINMM.LIB too! #include <stdio.h> #include <stdlib.h> #include <math.h> // DEFINES //////////////////////////////////////////////// // defines for windows #define WINDOW_CLASS_NAME "WINCLASS1" // GLOBALS //////////////////////////////////////////////// HWND main_window_handle = NULL; // globally track main window HINSTANCE hinstance_app = NULL; // globally track hinstance // FUNCTIONS ////////////////////////////////////////////// LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // this is the main message handler of the system PAINTSTRUCT ps; // used in WM_PAINT HDC hdc; // handle to a device context char buffer[80]; // used to print strings // what is the message switch(msg) { case WM_CREATE: { // do initialization stuff here // return success return(0); } break; case WM_MOVE: { // extract the position int xpos = LOWORD(lparam); int ypos = HIWORD(lparam); // get a graphics context hdc = GetDC(hwnd); // set the foreground color to green SetTextColor(hdc, RGB(0,255,0)); // set the background color to black SetBkColor(hdc, RGB(0,0,0)); // set the transparency mode to OPAQUE SetBkMode(hdc, OPAQUE); // draw the size of the window sprintf(buffer,"WM_MOVE Called - New Positition = (%d,%d)", xpos, ypos); TextOut(hdc, 0,0, buffer, strlen(buffer)); // release the dc back ReleaseDC(hwnd, hdc); } break; case WM_PAINT: { // simply validate the window hdc = BeginPaint(hwnd,&ps); // end painting EndPaint(hwnd,&ps); // return success return(0); } break; case WM_DESTROY: { // kill the application, this sends a WM_QUIT message PostQuitMessage(0); // return success return(0); } break; default:break; } // end switch // process any messages that we didn't take care of return (DefWindowProc(hwnd, msg, wparam, lparam)); } // end WinProc // WINMAIN //////////////////////////////////////////////// int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow) { WNDCLASSEX winclass; // this will hold the class we create HWND hwnd; // generic window handle MSG msg; // generic message // first fill in the window class stucture winclass.cbSize = sizeof(WNDCLASSEX); winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; winclass.lpfnWndProc = WindowProc; //set event handler winclass.cbClsExtra = 0; //reserve data space winclass.cbWndExtra = 0; // winclass.hInstance = hinstance; //set instance of application winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //load program icon winclass.hCursor = LoadCursor(NULL, IDC_ARROW); //load cursor type winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); //set background brush winclass.lpszMenuName = NULL; //arrach resource menu winclass.lpszClassName = WINDOW_CLASS_NAME; //set Windows class name winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // save hinstance in global hinstance_app = hinstance; // register the window class if (!RegisterClassEx(&winclass)) return(0); // create the window if (!(hwnd = CreateWindowEx(NULL, // extended style WINDOW_CLASS_NAME, // class "WM_MOVE Demo", // title WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0,0, // initial x,y 400,300, // initial width, height NULL, // handle to parent NULL, // handle to menu hinstance,// instance of this application NULL))) // extra creation parms return(0); // save main window handle main_window_handle = hwnd; // enter main event loop, but this time we use PeekMessage() // instead of GetMessage() to retrieve messages while(TRUE) { // test if there is a message in queue, if so get it if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // test if this is a quit if (msg.message == WM_QUIT) break; // translate any accelerator keys TranslateMessage(&msg); // send the message to the window proc DispatchMessage(&msg); } // end if // main processing goes here // My_Main(); // or whatever your loop is called } // end while // return to Windows like this return(msg.wParam); } // end WinMain

You handle additional Windows messages by adding more sections to the switch statement in WindowProc.

What kinds of things?

How do you send your own messages to Windows?

You call one of these two functions in your application program

  1. PostMessage() - sends message to queue for normal processing
  2. SendMessage() - bypasses queue and request immediate handling

How do you process keyboard input?

Checking for key press?

Checking for key release

Bit Code Information

Key Press Example

case WM_KEYDOWN: { int virtual_code = (int) wparam; int key_bits = (int) lparam; switch (virtual_code) { case VK_RIGHT: { } break; case VK_LEFT: { } break; default: break; return (0); } } break;

Here is the main event loop from a LaMothe program that processes cursor key presses without relying on message processing in WindowProc().

#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0) #define KEYUP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1) // save main window handle main_window_handle = hwnd; // enter main event loop, but this time we use PeekMessage() // instead of GetMessage() to retrieve messages while(TRUE) { // test if there is a message in queue, if so get it if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // test if this is a quit if (msg.message == WM_QUIT) break; // translate any accelerator keys TranslateMessage(&msg); // send the message to the window proc DispatchMessage(&msg); } // end if // main processing goes here // get a graphics context hdc = GetDC(hwnd); // set the foreground color to green SetTextColor(hdc, RGB(0,255,0)); // set the background color to black SetBkColor(hdc, RGB(0,0,0)); // set the transparency mode to OPAQUE SetBkMode(hdc, OPAQUE); // print out the state of each arrow key sprintf(buffer,"Up Arrow: = %d ",KEYDOWN(VK_UP)); TextOut(hdc, 0,0, buffer, strlen(buffer)); sprintf(buffer,"Down Arrow: = %d ",KEYDOWN(VK_DOWN)); TextOut(hdc, 0,16, buffer, strlen(buffer)); sprintf(buffer,"Right Arrow: = %d ",KEYDOWN(VK_RIGHT)); TextOut(hdc, 0,32, buffer, strlen(buffer)); sprintf(buffer,"Left Arrow: = %d ",KEYDOWN(VK_LEFT)); TextOut(hdc, 0,48, buffer, strlen(buffer)); // release the dc back ReleaseDC(hwnd, hdc); } // end while // return to Windows like this return(msg.wParam);

Handling Mouse Events

Mouse Button Constants

// DEMO3_13.CPP - WM_MOUSEMOVE demo from LaMothe // INCLUDES /////////////////////////////////////////////// #define WIN32_LEAN_AND_MEAN // just say no to MFC #include <windows.h> // include all the windows headers #include <windowsx.h> // include useful macros #include <mmsystem.h> // very important and include WINMM.LIB too! #include <stdio.h> #include <stdlib.h> #include <math.h> // DEFINES //////////////////////////////////////////////// // defines for windows #define WINDOW_CLASS_NAME "WINCLASS1" // GLOBALS //////////////////////////////////////////////// HWND main_window_handle = NULL; // globally track main window HINSTANCE hinstance_app = NULL; // globally track hinstance // FUNCTIONS ////////////////////////////////////////////// LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // this is the main message handler of the system PAINTSTRUCT ps; // used in WM_PAINT HDC hdc; // handle to a device context char buffer[80]; // used to print strings // what is the message switch(msg) { case WM_CREATE: { // do initialization stuff here // return success return(0); } break; case WM_MOUSEMOVE: { // get the position of the mouse int mouse_x = (int)LOWORD(lparam); int mouse_y = (int)HIWORD(lparam); // get the button state int buttons = (int)wparam; // get a graphics context hdc = GetDC(hwnd); // set the foreground color to green SetTextColor(hdc, RGB(0,255,0)); // set the background color to black SetBkColor(hdc, RGB(0,0,0)); // set the transparency mode to OPAQUE SetBkMode(hdc, OPAQUE); // print the ascii code and key state sprintf(buffer,"Mouse (X,Y) = (%d,%d) ",mouse_x,mouse_y); TextOut(hdc, 0,0, buffer, strlen(buffer)); sprintf(buffer,"Right Button = %d ",((buttons & MK_RBUTTON) ? 1 : 0)); TextOut(hdc, 0,16, buffer, strlen(buffer)); sprintf(buffer,"Left Button = %d ",((buttons & MK_LBUTTON) ? 1 : 0)); TextOut(hdc, 0,32, buffer, strlen(buffer)); // release the dc back ReleaseDC(hwnd, hdc); } break; case WM_PAINT: { // simply validate the window hdc = BeginPaint(hwnd,&ps); // end painting EndPaint(hwnd,&ps); // return success return(0); } break; case WM_DESTROY: { // kill the application, this sends a WM_QUIT message PostQuitMessage(0); // return success return(0); } break; default:break; } // end switch // process any messages that we didn't take care of return (DefWindowProc(hwnd, msg, wparam, lparam)); } // end WinProc // WINMAIN //////////////////////////////////////////////// int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow) { WNDCLASSEX winclass; // this will hold the class we create HWND hwnd; // generic window handle MSG msg; // generic message // first fill in the window class stucture winclass.cbSize = sizeof(WNDCLASSEX); winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; winclass.lpfnWndProc = WindowProc; winclass.cbClsExtra = 0; winclass.cbWndExtra = 0; winclass.hInstance = hinstance; winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); winclass.hCursor = LoadCursor(NULL, IDC_ARROW); winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); winclass.lpszMenuName = NULL; winclass.lpszClassName = WINDOW_CLASS_NAME; winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // save hinstance in global hinstance_app = hinstance; // register the window class if (!RegisterClassEx(&winclass)) return(0); // create the window if (!(hwnd = CreateWindowEx(NULL, // extended style WINDOW_CLASS_NAME, // class "Mouse Tracking Demo", // title WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0,0, // initial x,y 400,300, // initial width, height NULL, // handle to parent NULL, // handle to menu hinstance, // instance of this application NULL))) // extra creation parms return(0); // save main window handle main_window_handle = hwnd; // enter main event loop, but this time we use PeekMessage() // instead of GetMessage() to retrieve messages while(TRUE) { // test if there is a message in queue, if so get it if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // test if this is a quit if (msg.message == WM_QUIT) break; // translate any accelerator keys TranslateMessage(&msg); // send the message to the window proc DispatchMessage(&msg); } // end if // main processing goes here // MY_Main(); // or whatever your loop is called } // end while // return to Windows like this return(msg.wParam); } // end WinMain

Using Windows GDI to Draw Text and Graphics

Whenever you or windows disturbs the client part of a window a WM_PAINT message is sent to be processed by your application. The area to be repainted is called the invalid rectangle. A pointer to the invalid rectangle is shipped along with the WM_PAINT message as part of the validating process.

There are two means of processing a WM_PAINT message

  1. BeginPaint()...EndPaint()
  2. GetDC()...ReleaseDC()

How invalidate a Windows client using BeginPaint()

case WM_PAINT: { InvalidateRect(hwnd,NULL,TRUE); //using NULL as pointer to invalid rectangle //marks entire window as invlaid hdc = BeginPaint(hwnd,&ps); // fixes go here Endpaint(hwnd,&ps); return(0); } break;

validating a window without doing any drawing

case WM_PAINT: { ValidateRect(hwnd,NULL); return(0); }; break;

You can avoid worrying about repaint by using DC instead

case WM_PAINT: { hdc = GetDC(hwnd); // do graphics here ReleaseDC(hwnd); ValidateRect(hwd,NULL); return(0); } break;

Display surface fundamentals (GDI)

Text Printing Using GDI

Here is a LaMothe example that draws text using Peekmessage and TextOut()

// save main window handle main_window_handle = hwnd; // get the dc and hold it HDC hdc = GetDC(hwnd); // enter main event loop, this time we use PeekMessage() while(TRUE) { // test if there is a message in queue, if so get it if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // test if this is a quit if (msg.message == WM_QUIT) break; // translate any accelerator keys TranslateMessage(&msg); // send the message to the window proc DispatchMessage(&msg); } // end if // set the foreground color to random SetTextColor(hdc, RGB(rand()%256,rand()%256,rand()%256)); // set the background color to black SetBkColor(hdc, RGB(0,0,0)); // finally set the transparency mode to transparent SetBkMode(hdc, TRANSPARENT); // draw some text at a random location TextOut(hdc,rand()%400,rand()%400, "GDI Text Demo!", strlen("GDI Text Demo!")); Sleep(10); } // end while // release the dc ReleaseDC(hwnd,hdc); // return to Windows like this return(msg.wParam);

Drawing Shapes (points, lines, ellipses, polygons)

GDI pens are objects used to draw lines. Pens have two properties.

  1. color (RGB)
  2. style (e.g. solid, dashed, etc.)

Brushes are used to fill-in objects

//to create a pen HPEN red_pen = CreatePen(PS_Solid,0,RGB(255,0,0)); //to save an old pen for reuse before creating a new pen HPEN old_pen = SelectObject(hdc,red_pen); //to delete a pen Delete(object(red-pen)); //create a solid blue brush HBRUSH blue_brush = CreateSolidBrush(RGB(0,0,255)); /create a green patterned brush HBRUSH green_cross = CreateHatchBrush(HS_DIAGCROSS,RGB(),255,0); //selecting and deleting brushes is similar to pens //plotting white pixel at (x,y) from upper left (0,0) SetPixel(hdc,x,y,RGB(255,255,255): //drawing lines SelectObject(hdc,red_pen); MoveToEx(hdc,10,20,NULL); LineTo(hdc,10,40,NULL); LineTo(hdc,50,150,NULL); //can be continued to draw shapes //drawing a wire frame rectangle static RECT rect = {100,100,200,200} FrameRect(hdc,&rect,blue_brush); //drawing a filled rectangle FillRect(hdc,&rect,blue_brush); //drawing a circle centered at (100,100) with diameter 20 Ellipse(hdc,90,90,110,110); //drawing an ellipse centered at (100,100) with //major axis = 100 and minor axis = 60 Ellipse(hdc,100-50,100-30,100+50,100+30);

LaMothe example that draws random rectangles

// save main window handle main_window_handle = hwnd; while(TRUE) { // test if there is a message in queue, if so get it if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // test if this is a quit if (msg.message == WM_QUIT) break; // translate any accelerator keys TranslateMessage(&msg); // send the message to the window proc DispatchMessage(&msg); } // end if // draw a random rectangle // get the graphics device context hdc = GetDC(hwnd); RECT rect; // used to hold rect info // create a random rectangle rect.left = rand()%WINDOW_WIDTH; rect.top = rand()%WINDOW_HEIGHT; rect.right = rand()%WINDOW_WIDTH; rect.bottom = rand()%WINDOW_HEIGHT; // create a random brush HBRUSH hbrush = CreateSolidBrush(RGB(rand()%256,rand()%256,rand()%256)); // draw either a filled rect or a wireframe rect if ((rand()%2)==1) FillRect(hdc,&rect,hbrush); else FrameRect(hdc,&rect,hbrush); // now delete the brush DeleteObject(hbrush); // release the device context ReleaseDC(hwnd,hdc); // check for exit by escape if (KEYDOWN(VK_ESCAPE)) SendMessage(hwnd, WM_CLOSE, 0,0); } // end while // return to Windows like this return(msg.wParam);

Simple Animtation (Draw, Erase, Redraw)

LaMothe example that simulates a moving ball.

// save main window handle main_window_handle = hwnd; // get the graphics device context once this time and keep it // this is possible due to the CS_OWNDC flag hdc = GetDC(hwnd); // seed random number generator srand(GetTickCount()); // create the pens and brushes HPEN white_pen = CreatePen(PS_SOLID, 1, RGB(255,255,255)); HPEN black_pen = CreatePen(PS_SOLID, 1, RGB(0,0,0)); HBRUSH green_brush = CreateSolidBrush(RGB(0,255,0)); HBRUSH black_brush = CreateSolidBrush(RGB(0,0,0)); // starting position of ball int ball_x = WINDOW_WIDTH/2; int ball_y = WINDOW_HEIGHT/2; // initial velocity of ball int xv = -4+rand()%8; int yv = -4+rand()%8; // enter main event loop while(TRUE) { // test if there is a message in queue, if so get it if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // test if this is a quit if (msg.message == WM_QUIT) break; // translate any accelerator keys TranslateMessage(&msg); // send the message to the window proc DispatchMessage(&msg); } // end if // ERASE the last position of the ball // first select the black pen and brush into context SelectObject(hdc, black_pen); SelectObject(hdc, black_brush); // draw the ball Ellipse(hdc, ball_x, ball_y, ball_x + 32, ball_y + 32); // MOVE the ball ball_x+=xv; ball_y+=yv; // test for collisions, first x-axis if (ball_x < 0 || ball_x > WINDOW_WIDTH - 32) { // invert x-velocity of ball xv=-xv; // push ball back ball_x+=xv; } // end if else // test for y-axis collisions if (ball_y < 0 || ball_y > WINDOW_HEIGHT - 32) { // invert y-velocity of ball yv=-yv; // push ball back ball_y+=yv; } // end if // now select the green and white colors for brush and pen SelectObject(hdc, white_pen); SelectObject(hdc, green_brush); // DRAW the ball Ellipse(hdc, ball_x, ball_y, ball_x + 32, ball_y + 32); // main processing goes here if (KEYDOWN(VK_ESCAPE)) SendMessage(hwnd, WM_CLOSE, 0,0); // slow system down a little Sleep(10); } // end while // delete all the objects DeleteObject(white_pen); DeleteObject(black_pen); DeleteObject(green_brush); DeleteObject(black_brush); // release the device context ReleaseDC(hwnd,hdc); // return to Windows like this return(msg.wParam);