Windows Programming Using Visual C++
Based on LaMothe's examples from
Tricks of the Windows Game Programming Gurus
First Steps
- Open Visual C++
- Create a win32 work space
- Use the wizard to cvreate a win32 console application
-
You will need to use the "Settings" dialog box under "Projects" tab to
load .lib files that are needed (e.g. winmm.lib)
- All run time DLL’s are easiest to use if they are in the windows/system directory
Hungarian notation
In Microsoft applications variable names are prefixed by 1 or 2 character code indicating
their type
- c = char
- by = byte
- n = number
- i = integer
- lp = 32 bit pointer
- fn = function
- s = string
- dw = double word
- l = long word
- w = word
- msg = message
- Classes start with a upper case C
First Windows Program
// DEMO2_2.CPP - a simple message box from LaMothe
#define WIN32_LEAN_AND_MEAN // these 3 lines are always used
#include // the main windows headers
#include // 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 // include all the windows headers
#include // include useful macros
#include // very important and include WINMM.LIB too!
#include
#include
#include
// 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?
- Close window
- Force a repaint
- Kill the application
- Track mouse cursorr
- Get key presses
How do you send your own messages to Windows?
You call one of these two functions in your application program
- PostMessage() - sends message to queue for normal processing
- SendMessage() - bypasses queue and request immediate handling
How do you process keyboard input?
- use the WM_CHAR message
- wparam = ASCII character code
- lparam = bit code information
-
extended key codes are handled using predefined virtual key names
(e.g. VK_ESCAPE = 1B = escape or VK_UP = 26 = up arrow)
Checking for key press?
- WM_KEYDOWN message can be used to trap any key press
- wparam = vitrual key code code
- lparam = bit code information
Checking for key release
- WM_KEYUP message can be used to trap key release
- wparam = vitrual key code code
- lparam = bit code information
Bit Code Information
- 0-15 = repeat count
- 16-23 = OEM specific scan code
- 24 = boolean extended key code?
- 25-28 = reserved
- 29 = boolean key down?
- 30 = boolean current state same as last?
- 31 = boolean transistion state (0=key being released, 1=key being pressed)
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
- WM_MOUSEMOVE - sent when mouse moves inside window
- Wparam deals with the button flags
- Lparam - low word is x position
- Lparam - high word is the y position
Mouse Button Constants
- MK_LBUTTON - left
- MK_MBUTTON - middle
- MK_RBUTTON - right
- MK_CONTROL - Ctrl pressed?
- MK_SHIFT - shift pressed?
// DEMO3_13.CPP - WM_MOUSEMOVE demo from LaMothe
// INCLUDES ///////////////////////////////////////////////
#define WIN32_LEAN_AND_MEAN // just say no to MFC
#include // include all the windows headers
#include // include useful macros
#include // very important and include WINMM.LIB too!
#include
#include
#include
// 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
- BeginPaint()...EndPaint()
- adv - does all needed validating
-
dis - invalid rectangle is same as clipping rectangle, so no drawing outside
the invalid rectangle
- fools windows into believing you fixed the rectangle whether you did or not
- GetDC()...ReleaseDC()
- adv - can draw outside the invalid rectangle
- dis - need to validate the rectangle whether you have drawn anything or not
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)
- Resolution and pixels -- 640 x 480, 800 x 600, 1024 x 768
- Color depth - number of color bits per pixel 8, 16, 24, or 32
- Color space - actual number of colors available (e.g. 8 bits = 256 colors)
- RGB mode - true rgb values are encoded
-
Palette mode - indirect color scheme 256 colors at a time with values 0 to 255
are stored with each pixel (8 bit color) color value is looked up in CLUT
(color look-up table)
Text Printing Using GDI
- Two types of fonts available - fixed and proportional
- Two techniques
- TextOut() - not formatting or processing possible
- DrawText() - justification and clippling is allowed
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.
- color (RGB)
- 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);