C Windows Programming Tutorial PDF
C Windows Programming Tutorial PDF
C Windows Programming Tutorial PDF
{From: http://www.geocities.com/Heartland/Meadows/9818/win32tut/index.html}
Welcome to a journey in Windows programming. In this lesson, you will create your first
windows application which will display a "Hello World" message box.
3. You land up with an empty project. Now we will set the project type to Win32
GUI. This tells the compiler that the project is to be compiled as a windows GUI
application. Press Alt+P to get the project options. Select Win32 GUI under Type.
4. Right click Lesson1 on the Project Panel and click New Unit. A new file is
created. Save it as lesson1.c
5. Now enter the code as given below:
1 - 22
/* Windows Programming Tutorial Series
* Lesson 1 - Your first windows program
* Pravin Paratey (October 08, 2002)
**/
#include <windows.h>
int WINAPI
WinMain(HINSTANCE hInst,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MessageBox (NULL, "Hello World! This is my first WIN32 program",
"Lesson 1", MB_OK);
return 0;
}
6. Press F9 to compile and run the project. You should see the following,
1. #include <windows.h>
All Windows programs must include the header file windows.h. This file has the
definitions of Windows system calls or the WinAPI. The WinAPI has everything
necessary for programming under windows.
2. WinMain (..)
This is the entry point of a windows application. This is like the main() of a
console based application. WinMain is declared as,
2 - 22
We haven't used any of these parameters in this lesson, but we'll see their uses in
the coming lessons.
3. MessageBox(..)
This is a windows function which displays a messagebox. The MessageBox
function is declared as,
int MessageBox(
HWND hWnd, /* Handle of owner window */
LPCTSTR lpText, /* Address of text in message box */
LPCTSTR lpCaption,/* Address of title of message box */
UINT uType); /* Style of message box */
4. return 0
This is the return value to the system.
This brings us to the end of the first lesson. Click here to go to the next lesson.
3 - 22
Lesson 2 - Understanding messages and
events
Click here to download the source files to this tutorial.
In this tutorial you will be introduced to the event-driven programming model. You will
learn how Windows uses messages to communicate with applications, how event based
programming works, what callback functions are, and while doing this create a basic
windows application
Before we begin
If you do not have the platform SDK help files (WinAPI docs) - no, they do not come
with Dev-C++ - then I suggest you do one of the following:
And so it begins
Create a blank windows project in Dev-C++ (In case you've forgotten how this is done,
take a look at the previous tutorial). Name it lesson2. Open a new file(Press Ctrl+N) and
save it(Ctrl+S) as main.c. Enter the following code,
1 #include <windows.h>
2
3 HWND hwndMain; //Main window handle
4
5 // Callback function
4 - 22
6 LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
7 // Windows entry point
8 int WINAPI
9 WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow)
10 {
11 MSG msg; // MSG structure to store messages
12 WNDCLASSEX wcx; // WINDOW class information
13
14 // Initialize the struct to zero
15 ZeroMemory(&wcx,sizeof(WNDCLASSEX));
16 wcx.cbSize = sizeof(WNDCLASSEX); // Window size. Must always be sizeof(WNDCLASSEX)
17 wcx.style = CS_HREDRAW|CS_VREDRAW |CS_DBLCLKS ; // Class styles
18 wcx.lpfnWndProc = (WNDPROC)MainWndProc; // Pointer to the callback procedure
19 wcx.cbClsExtra = 0; // Extra byte to allocate following the wndclassex structure
20 wcx.cbWndExtra = 0; // Extra byte to allocate following an instance of the structure
21 wcx.hInstance = hInstance; // Instance of the application
22 wcx.hIcon = NULL; // Class Icon
23 wcx.hCursor = LoadCursor(NULL, IDC_ARROW); // Class Cursor
24 wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW); // Background brush
25 wcx.lpszMenuName = NULL; // Menu resource
26 wcx.lpszClassName = "Lesson2"; // Name of this class
27 wcx.hIconSm = NULL; // Small icon for this class
28
29 // Register this window class with MS-Windows
30 if (!RegisterClassEx(&wcx))
31 return 0;
32
33 // Create the window
34 hwndMain = CreateWindowEx(0, //Extended window style
35 "Lesson2", // Window class name
36 "Lesson 2 - A simple win32 application", // Window title
37 WS_OVERLAPPEDWINDOW, // Window style
38 CW_USEDEFAULT,CW_USEDEFAULT, // (x,y) pos of the window
39 CW_USEDEFAULT,CW_USEDEFAULT, // Width and height of the window
40 HWND_DESKTOP, // HWND of the parent window (can be null also)
41 NULL, // Handle to menu
42 hInstance, // Handle to application instance
43 NULL); // Pointer to window creation data
44
45 // Check if window creation was successful
46 if (!hwndMain)
47 return 0;
48
49 // Make the window visible
50 ShowWindow(hwndMain,SW_SHOW);
51
52 // Process messages coming to this window
53 while (GetMessage(&msg,NULL,0,0))
54 {
55 TranslateMessage(&msg);
56 DispatchMessage(&msg);
57 }
58
59 // return value to the system
60 return msg.wParam;
61 }
62
63 LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
64 {
65 switch (msg)
66 {
67 case WM_DESTROY:
68 // User closed the window
69 PostQuitMessage(0);
70 break;
71 default:
72 // Call the default window handler
73 return DefWindowProc(hwnd,msg,wParam,lParam);
74 }
75 return 0;
76 }
5 - 22
Whew. That was long! Press F9 to compile and run. You have a basic window on your
screen!
Breaking it up
The MSG structure is what stores the messages received by your application. Before
going any further, lets take a look at the event-driven programming model.
Every time windows has to communicate with your application, it sends messages to your
application. Once all initializations have been done and the window shown on screen, all
your application has to do is poll for windows messages.
The lines up to 51 create and show the window and the lines 52-57 poll for messages.
The GetMessage() function gets the next message to be processed from the message
queue. GetMessage() returns a non-zero value for every message other than WM_QUIT.
This means that the while loop continues until it is time to quit.
DispatchMessage() dispatches the message to a window procedure. This means that for
messages coming to our window, the MainWndProc() is called by Windows(tm) through
DispatchMessage().
How does Windows(tm) know which function to call? Well, we tell Windows(tm) during
WNDCLASSEX initialization [line 18].
WNDCLASSEX structure
Every window that you create has an associated WNDCLASSEX structure. The
WNDCLASSEX structure provides all the information necessary for Windows(tm) to do
6 - 22
perform window related functions like drawing its icon, cursor, menu, calling the
callback function which will receive messages and so on.
cbSize
This must always be set to sizeof(WNDCLASSEX).
style
This specifies the class styles. Take a look at your SDK documentation for the
values this member can take.
lpfnWndProc
Pointer to the WndProc which will handle this windows' messages.
cbClsExtra
Number of extra bytes to allocate at the end of the WNDCLASSEX structure.
cbWndExtra
Number of extra bytes to allocate at the end of the window instance.
hInstance
Identifies the instance that the window procedure of this class is within.
hIcon
Handle to the icon associated with windows of this class.
hCursor
Handle to the cursor for windows of this class.
hbrBackground
Identifies the class background brush.
lpszMenuName
Identifies the menu for windows of this class.
lpszClassName
Pointer to a NULL terminated string or an atom specifying the class of this
structure.
hIconSm
Handle to the small icon associated with this class.
7 - 22
Registering your window class
After you've created your window class, you need to tell Windows(tm) about it. This is
done by registering the class with windows. The function call is RegisterClassEx(..).
Once this is done, you can create instances of this window by calling
CreateWindowEx(..) with the proper arguments.
HWND CreateWindowEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);
Lines 34-43 create the window. If the creation was successful a non-zero handle is
returned by CreateWindowEx after which ShowWindow() shows the window on the
screen.
TIP: It is a good idea to keep referring to these functions in your sdk docs while reading
this tutorial.
8 - 22
Callback functions
A callback function is the one that receives the messages sent to your application. This is
where you do something about the message. We provide a pointer to this function while
defining the window class [line 18].
LRESULT CALLBACK
function-name(
HWND hwnd, // Handle of window which received this
message
UINT msg, // The message
WPARAM wParam, // Extra information
LPARAM lParam // Extra information
);
HWND hwnd
The handle of the window is specified so that you know which window to act
upon. This is necessary because you may have created more than one instance of
the window.
UINT msg
This contains the message sent.
WPARAM wParam and WPARAM lParam
9 - 22
wParam and lParam are used to pass extra info about the message. For example a
WM_LBUTTONDOWN (left mouse button down) message will have the x and y
co-ordinates as the upper and lower word of lParam and wParam will tell if any
modifier keys (ctrl, alt, shift) have been pressed.
MainWndProc
63 LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM
lParam)
64 {
65 switch (msg)
66 {
...
The switch statement lets us select which message was sent. There are over 200 messages
that windows can send your application. To read about them, just search for WM_ in
your sdk docs.
WM_DESTROY
...
67 case WM_DESTROY:
68 // User closed the window
69 PostQuitMessage(0);
70 break;
10 - 22
...
The WM_DESTROY message is sent to your application when the user teminates the
application either by clicking the X at the upper right corner, pressing Alt+F4, or quits
the application by other means.
PostQuitMessage() causes GetMessage(..) [line 53] to return false and thus breaking
out of the while loop and exiting the application. The argument to PostQuitMessage is
the return value to the system.
DefWindowProc(..)
What about the other 200 or so messages? Surely you, the programmer, aren't going to
write code for all the 200 messages. Fortunately, Windows(tm) provides the
DefWindowProc(..) function which handles all the messages. For the purposes of
displaying a simple window, your MainWndProc could very well have consisted of
What this means is that every time you want to do something about a message, add the
case switch for the message and write the code which does something about it. All
messages that you don't want to handle should be passed to the DefWindowProc(). This
is what we have done in our code.
...
71 default:
72 // Call the default window handler
73 return DefWindowProc(hwnd,msg,wParam,lParam);
...
Adding Functionality
Lets pop up a MessageBox which will display the co-ordinates of the point where the left
mouse button was pressed. To do this you will have to handle the
WM_LBUTTONDOWN message.
...
68 PostQuitMessage(0);
69 break;
70 case WM_LBUTTONDOWN:
71 pt.x = LOWORD(lParam);
72 pt.y = HIWORD(lParam);
73 wsprintf(str,
11 - 22
"Co-ordinates are\nX=%i and Y=%i",pt.x,pt.y);
74 MessageBox(hwnd, str, "Left Button Clicked", MB_OK);
75 break;
76 default:
...
Press F9. This is what you should see when you click anywhere inside the window.
Exercise
Try this exercise. Pop up a message every time a key is pressed on the keyboard.
Hint: Handle the WM_CHAR message. Remember to refer to your sdk docs.
If you can manage that, give yourself a pat on the back. You now understand the basics
of event-driven programming - the mechanism which Windows(tm) uses to communicate
with your application. You have crossed one of the more difficult hurdles in learning
windows programming.
Don't worry if you could not do the exercise or if things are still a bit hazy. These
concepts will be used in every single lesson after this and it will soon become second
nature to you.
12 - 22
Understanding GDI Elements - A Part of
the series on Win32 Programming
Click here to download the source code for this tutorial.
GDI (which stands for graphics device interface) is what enables you to draw on the
screen without worrying about the underlying hardware. GDI functions also allow you to
draw on other output devices like the printer, memory devices and metafiles. Windows
provides functions that let you draw - among other stuff - lines, circles, text and images in
various colors and styles.
You can think of GDI as a drawing kit complete with paintbrushes, watercolors, canvas,
rulers and other instruments.
Device Contexts
The first thing you must understand is device contexts. A device context is a structure
that defines a set of graphic objects and their associated attributes, as well as the graphic
modes that affect output. The graphic objects include a pen for line drawing, a brush for
painting and filling, a bitmap for copying or scrolling parts of the screen, a palette for
defining the set of available colors, a region for clipping and other operations, and a path
for painting and drawing operations.
Following our analogy, a device context can be considered as a canvas with certain
special properties. You can draw and paint on this canvas and it will always appear to
others the way it was intended, even if the canvas were transformed into a sketch paper or
a post-it note.
Before your application draws anything on screen, it must get its device context. This is
done through the GetDC() call which is defined as HDC GetDC(HWND hwnd). After you
are done with drawing, you must release it. This is done by the ReleaseDC() call defined
as int ReleaseDC(HWND hwnd, HDC hdc).
Graphics Objects
Pens and Brushes are used to draw lines and paint interiors of closed objects.
Fonts
TODO
13 - 22
Palettes
TODO
Bitmaps
TODO
Working Example
Now that all the theory is out of our way, let us put our new knowledge to practice. In
this chapter we will build an application that lets us draw lines, rectangles, circles and
bitmaps.
We will begin with our skeleton code that draws a simple window.
// draw.c
// Pravin Paratey
#include
// Variables
HWND hwndMain; // Main window HWND
// Functions
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM
lParam);
int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
WNDCLASSEX wcx;
// Register class
if(!RegisterClassEx(&wcx))
14 - 22
return 0;
hwndMain = CreateWindowEx(0,
"Draw",
"Draw Example Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
400,
300,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwndMain,nCmdShow);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd,msg,wParam,lParam);
}
return 0;
}
A real application would use a toolbar to let the user select the shape to draw. Since
creating a toolbar is too much trouble, well address this by using character input from
the keyboard to switch between the drawing shapes.
Lets keep a global variable drawShape to indicate the current tool. Add the following
lines at the beginning of the file. The lines to be added are shown in bold.
#include
// Defines
#define SHAPE_NULL 0
15 - 22
#define SHAPE_LINE 1
#define SHAPE_RECT 2
#define SHAPE_CIRCLE 3
#define SHAPE_TEXT 4
// Variables
HWND hwndMain; // Main window HWND
int drawShape; // The shape to draw
// Functions
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM
lParam);
Now add the following code to the MainWndProc message handler. This will select the
current tool.
LRESULT CALLBACK
MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
CHAR ch;
switch(msg)
{
case WM_CHAR:
if(!drawing)
{
ch = (TCHAR) wParam;
switch(ch)
{
case 'n': // NULL
drawShape = SHAPE_NULL;
break;
case 'l': // LINE
drawShape = SHAPE_LINE;
break;
case 'r': // RECTANGLE
drawShape = SHAPE_RECT;
break;
case 'c': // CIRCLE
drawShape = SHAPE_CIRCLE;
break;
case 'f': // FREEHAND
drawShape = SHAPE_FREEHAND;
break;
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
Drawing Shapes
16 - 22
To draw a shape we will have to
1. Get the point where the user clicked the left mouse button.
2. Draw a ghost shape while the user moves the mouse with the left button down.
3. Draw the final shape when the user releases the left mouse button.
WM_LBUTTONDOWN
1. Set drawing flag to indicate drawing has begun.
2. Store the start point in beginPoint variable.
3. Text is to be drawn at the current mouse location. If the selected shape is text,
draw it.
WM_MOUSEMOVE
1. Check if the left mouse button is down and that we are actually drawing (This is
necessary because clicking the mouse outside the window and moving the mouse
in will also generate this message).
2. Erase old shape.
3. Draw new shape.
Erasing and drawing this ghost or rubber shape is achieved by setting the draw mode of
the pen to NOTXOR. NOTXOR has the property that drawing twice undoes the effect of
the first draw.
WM_LBUTTONUP
If the drawing flag was set,
17 - 22
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
// Says, We've begun drawing
drawing = true;
// Store the begin point
beginPoint.x = LOWORD(lParam); // Returns x co-ordinate
beginPoint.y = HIWORD(lParam); // Returns y co-ordinate
// Store the old point
oldPoint = beginPoint;
break;
case WM_LBUTTONUP:
if (drawing)
{
drawing = false;
// Get Device context
hdc = GetDC(hwnd);
// Set ROP
ropOld = SetROP2(hdc, R2_NOTXORPEN);
// Get this point
thisPoint.x = LOWORD(lParam);
thisPoint.y = HIWORD(lParam);
switch(drawShape)
{
// This statement isn't needed
case SHAPE_NULL: // Do Nothing
break;
case SHAPE_LINE:
// Erase old line
MoveToEx(hdc, beginPoint.x, beginPoint.y, NULL);
LineTo(hdc, oldPoint.x, oldPoint.y);
// Reset ROP
SetROP2(hdc, ropOld);
// Draw permanent one
MoveToEx(hdc, beginPoint.x, beginPoint.y, NULL);
LineTo(hdc, thisPoint.x, thisPoint.y);
break;
case SHAPE_RECT:
// Erase old rect
Rectangle(hdc, beginPoint.x, beginPoint.y,
oldPoint.x, oldPoint.y);
// Reset ROP
SetROP2(hdc, ropOld);
// Draw permanent one
Rectangle(hdc, beginPoint.x, beginPoint.y,
thisPoint.x, thisPoint.y);
break;
case SHAPE_CIRCLE:
// Erase old circle
Ellipse(hdc, beginPoint.x, beginPoint.y, oldPoint.x,
oldPoint.y);
// Reset ROP
SetROP2(hdc, ropOld);
// Draw permanent one
Ellipse(hdc, beginPoint.x, beginPoint.y, thisPoint.x,
thisPoint.y);
break;
}
ReleaseDC(hwnd, hdc);
}
break;
case WM_MOUSEMOVE:
// Have we begun drawing?
// Is the left button down?
if (drawing && ((MK_LBUTTON & wParam) == MK_LBUTTON))
{
// Get Device context
hdc = GetDC(hwnd);
18 - 22
// Set rop to NOTXOR so that we can draw and erase
ropOld = SetROP2(hdc, R2_NOTXORPEN);
// Store this point
thisPoint.x = LOWORD(lParam);
thisPoint.y = HIWORD(lParam);
switch(drawShape)
{
// This statement isn't needed
case SHAPE_NULL: // Do Nothing
break;
case SHAPE_LINE:
// Erase old line
MoveToEx(hdc, beginPoint.x, beginPoint.y, NULL);
LineTo(hdc, oldPoint.x, oldPoint.y);
// Draw new one
MoveToEx(hdc, beginPoint.x, beginPoint.y, NULL);
LineTo(hdc, thisPoint.x, thisPoint.y);
break;
case SHAPE_RECT:
// Erase old rect
Rectangle(hdc, beginPoint.x, beginPoint.y,
oldPoint.x, oldPoint.y);
// Draw new one
Rectangle(hdc, beginPoint.x, beginPoint.y,
thisPoint.x, thisPoint.y);
break;
case SHAPE_CIRCLE:
// Erase old circle
Ellipse(hdc, beginPoint.x, beginPoint.y, oldPoint.x,
oldPoint.y);
// Draw new one
Ellipse(hdc, beginPoint.x, beginPoint.y, thisPoint.x,
thisPoint.y);
break;
}
oldPoint = thisPoint;
SetROP2(hdc, ropOld);
ReleaseDC(hwnd, hdc);
}
break;
default:
return DefWindowProc(hwnd,msg,wParam,lParam);
Drawing Lines
The LineTo function draws a line from the current position to the point specified in its
arguments. Before drawing a line, we must move our pen to the starting co-ordinates.
This is done through the MoveToEx function.
Raster Operations
Raster Operations specify how the current PEN and BRUSH colors will be combined
with the colors on the screen.
R2_NOTXORPEN
R2_NOTXOR is really NOT (XOR (current_color, screen_color)). For two one bit
numbers, the truth table is,
19 - 22
X Y XOR(X,Y) NOT(XOR(X,Y))
0 0 0 1
0 1 1 0
1 0 1 0
1 1 0 1
This is a good time to compile and run your app. Try switching tools and drawing a few
lines, rectangles and ellipses. Your screen should look something like this,
The inside of the rectangle and circle is filled in white because it is the default brush. If
you dont want the insides to be filled, add the following code.
...
ReleaseCapture();
// Get Device context
hdc = GetDC(hwnd);
// Set ROP
ropOld = SetROP2(hdc, R2_NOTXORPEN);
// Get this point
thisPoint.x = LOWORD(lParam);
...
...
20 - 22
To change the fill color, add the code:
...
ReleaseCapture();
// Get Device context
hdc = GetDC(hwnd)
hbrush = CreateSolidBrush(RGB(255,0,0)); //RED brush
holdbrush = (HBRUSH)SelectObject(hdc, hbrush);
// Set ROP
ropOld = SetROP2(hdc, R2_NOTXORPEN);
// Get this point
thisPoint.x = LOWORD(lParam);
...
SelectObject(hdc, holdbrush); // Restore the brush
DeleteObject(hbrush); // Free memory
ReleaseDC(hwnd, hdc);
...
Drawing Text
Windows provides a variety of functions to draw text. We will use the DrawTextEx()
function defined as
int DrawTextEx(
HDC hdc, // handle to device context
LPTSTR lpchText, // pointer to string to draw
int cchText, // length of string to draw
LPRECT lprc, // pointer to rectangle coordinates
UINT dwDTFormat, // formatting options
LPDRAWTEXTPARAMS lpDTParams // pointer to structure for options
);
WM_CHAR
...
case 'c': // CIRCLE
drawShape = SHAPE_CIRCLE;
break;
case 't': // TEXT
drawShape = SHAPE_TEXT;
break;
...
WM_MOUSEMOVE
...
case SHAPE_CIRCLE:
// Erase old circle
21 - 22
Ellipse(hdc, beginPoint.x, beginPoint.y, oldPoint.x,
oldPoint.y);
// Draw new one
Ellipse(hdc, beginPoint.x, beginPoint.y, thisPoint.x,
thisPoint.y);
break;
case SHAPE_TEXT:
// Erase old text rect
Rectangle(hdc, beginPoint.x, beginPoint.y, oldPoint.x,
oldPoint.y);
// Draw new one
Rectangle(hdc, beginPoint.x, beginPoint.y, thisPoint.x,
thisPoint.y);
break;
...
WM_LBUTTONUP
case SHAPE_TEXT:
Rectangle(hdc, beginPoint.x, beginPoint.y, oldPoint.x,
oldPoint.y);
// Reset ROP
SetROP2(hdc, ropOld);
rc.left = beginPoint.x;
rc.top = beginPoint.y;
rc.right = thisPoint.x;
rc.bottom = thisPoint.y;
DrawTextEx(hdc, strOut, lstrlen(strOut), &rc,
DT_END_ELLIPSIS|DT_NOCLIP|DT_WORDBREAK, NULL);
break;
Persistent Drawing
If you minimize the window and restore it, youll notice that everything youve drawn is
erased. This is because Windows sends the WM_PAINT message to your application telling
it to redraw its client area. To keep persistent drawings, youve got two options to keep
a list of all objects and draw them on every call to WM_PAINT or to keep a snapshot of the
image and redraw it on WM_PAINT.
22 - 22