Documente Academic
Documente Profesional
Documente Cultură
{From: http://www.geocities.com/Heartland/Meadows/9818/win32tut/index.html}
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
6. Press F9 to compile and run the project. You should see the following,
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,
/*
LPCTSTR lpText,
/*
LPCTSTR lpCaption,/*
UINT uType);
/*
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
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:
1. Download this file OR
2. Download the Windows platform SDK from msdn
.
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
2
3
4
5
#include <windows.h>
HWND hwndMain;
// Callback function
4 - 22
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
5 - 22
Whew. That was long! Press F9 to compile and run. You have a basic window on your
screen!
Breaking it up
Messages and the MSG Structure
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.
DispatchMessage()
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.
The WNDCLASSEX structure is defined as,
typedef struct _WNDCLASSEX {
UINT
cbSize;
UINT
style;
WNDPROC lpfnWndProc;
int
cbClsExtra;
int
cbWndExtra;
HANDLE hInstance;
HICON
hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON
hIconSm;
} WNDCLASSEX;
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
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].
Callback functions have to be defined as,
LRESULT CALLBACK
function-name(
HWND hwnd,
message
UINT msg,
WPARAM wParam,
LPARAM lParam
);
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
68
69
70
...
case WM_DESTROY:
// User closed the window
PostQuitMessage(0);
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()
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
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM
lParam)
{
return DefWindowProc(hwnd,msg,wParam,lParam);
}
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
72
73
default:
// Call the default window handler
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.
Add this code at line 70
68
69
70
71
72
73
...
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
wsprintf(str,
11 - 22
74
75
76
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
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
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;
// Fill in the WNDCLASS Struct
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_DBLCLKS;
wcx.lpfnWndProc = MainWndProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.hInstance = hInstance;
wcx.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wcx.hCursor = LoadCursor (NULL, IDC_ARROW);
wcx.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
wcx.lpszMenuName = NULL;
wcx.lpszClassName = "Draw";
wcx.hIconSm = NULL;
// 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;
}
// Callback function for the Main Window class
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM
lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd,msg,wParam,lParam);
}
return 0;
}
15 - 22
#define
#define
#define
#define
SHAPE_LINE
SHAPE_RECT
SHAPE_CIRCLE
SHAPE_TEXT
1
2
3
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
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,
1. Set drawing flag to false
2. Erase the ghost shape
3. Draw a permanent one
LRESULT CALLBACK
MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
CHAR ch; // This will contain keyboard input
HDC hdc; // Handle to the device context
HBRUSH hbrush; // Handle to BRUSH object
HBRUSH hOldBrush; // Stores the old BRUSH object
HPEN hPen;
static POINT oldPoint; // Stores the old point
static POINT beginPoint; // Stores the begin point
POINT thisPoint; // Stores the current point
static bool drawing = false;
int ropOld; // Stores the old Raster Operator
RECT rc;
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
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
0
0
1
1
Y
0
1
0
1
XOR(X,Y)
0
1
1
0
NOT(XOR(X,Y))
1
0
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,
20 - 22
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
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