Sunteți pe pagina 1din 22

C Windows Programming Tutorial

{From: http://www.geocities.com/Heartland/Meadows/9818/win32tut/index.html}

Lesson 1 - Your First windows program


Click here to download the source files to this tutorial.
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.
1. If you haven't started Dev-C++ already, do so now.
2. Select File->New Project. In the dialog box that appears, choose the project type
as an Empty Project and name the project Lesson1. Also choose the C language.

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,

Congratulations! You have just written your first windows application.

Whoa! What just happened there?


Lets break down the code.
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,
int WINAPI WinMain(
HINSTANCE hInst,
/*
HINSTANCE hPrevInstance,/*
LPSTR lpCmdLine,
/*
int nCmdShow);
/*

Handle to the current instance */


Handle to the previous instance */
pointer to command line arguments */
show state of the window */

The parameters of WinMain are self-explanatory, except perhaps nCmdShow.


nCmdShow tells you in what manner you are expected to display this window
(Maximized, Minimized, etc).

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);
/*

Handle of owner window */


Address of text in message box */
Address of title of message box */
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:
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;

//Main window handle

// 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

LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);


// Windows entry point
int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow)
{
MSG msg; // MSG structure to store messages
WNDCLASSEX wcx; // WINDOW class information
// Initialize the struct to zero
ZeroMemory(&wcx,sizeof(WNDCLASSEX));
wcx.cbSize = sizeof(WNDCLASSEX); // Window size. Must always be sizeof(WNDCLASSEX)
wcx.style = CS_HREDRAW|CS_VREDRAW |CS_DBLCLKS ; // Class styles
wcx.lpfnWndProc = (WNDPROC)MainWndProc; // Pointer to the callback procedure
wcx.cbClsExtra = 0; // Extra byte to allocate following the wndclassex structure
wcx.cbWndExtra = 0; // Extra byte to allocate following an instance of the structure
wcx.hInstance = hInstance; // Instance of the application
wcx.hIcon = NULL; // Class Icon
wcx.hCursor = LoadCursor(NULL, IDC_ARROW); // Class Cursor
wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW); // Background brush
wcx.lpszMenuName = NULL; // Menu resource
wcx.lpszClassName = "Lesson2"; // Name of this class
wcx.hIconSm = NULL; // Small icon for this class
// Register this window class with MS-Windows
if (!RegisterClassEx(&wcx))
return 0;
// Create the window
hwndMain = CreateWindowEx(0, //Extended window style
"Lesson2", // Window class name
"Lesson 2 - A simple win32 application", // Window title
WS_OVERLAPPEDWINDOW, // Window style
CW_USEDEFAULT,CW_USEDEFAULT, // (x,y) pos of the window
CW_USEDEFAULT,CW_USEDEFAULT, // Width and height of the window
HWND_DESKTOP, // HWND of the parent window (can be null also)
NULL, // Handle to menu
hInstance, // Handle to application instance
NULL); // Pointer to window creation data
// Check if window creation was successful
if (!hwndMain)
return 0;
// Make the window visible
ShowWindow(hwndMain,SW_SHOW);
// Process messages coming to this window
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// return value to the system
return msg.wParam;
}
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
// User closed the window
PostQuitMessage(0);
break;
default:
// Call the default window handler
return DefWindowProc(hwnd,msg,wParam,lParam);
}
return 0;
}

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.

Event driven programming model


To understand event driven programming, we draw analogies from the real world.
1. A person places an order at a restaurant.
2. The waiter conveys this order to the chef.
3. The chef gets busy and soon the order is served.
1. The user clicks the maximize button.
2. Windows tells your application that the maximize button has been pressed.
3. Your application then redraws its window so that it covers the screen.
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.
TranslateMessage()

translates virtual key messages to character messages.

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.
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

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.

Creating the window


A window is created by calling the CreateWindowEx(..) defined as,
HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName, //
LPCTSTR lpWindowName, //
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
//
HWND hWndParent,
HMENU hMenu,
//
HINSTANCE hInstance, //
LPVOID lpParam
);

// extended window style


pointer to registered class name
pointer to window name
// window style
// horizontal position of window
// vertical position of window
// window width
window height
// handle to parent or owner window
handle to menu, or child-window identifier
handle to application instance
// 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].
Callback functions have to be defined as,
LRESULT CALLBACK
function-name(
HWND hwnd,
message
UINT msg,
WPARAM wParam,
LPARAM lParam
);

// Handle of window which received this


// The message
// Extra information
// 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
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()

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
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

"Co-ordinates are\nX=%i and Y=%i",pt.x,pt.y);


MessageBox(hwnd, str, "Left Button Clicked", MB_OK);
break;
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
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;
}

Selecting the Drawing Shape


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

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

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.
This means handling the WM_LBUTTONDOWN, WM_MOUSEMOVE and
WM_LBUTTONUP messages.

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

// 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
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,

Whats with the white interior?


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 brush to hollow
logbrush.lbStyle = BS_HOLLOW;
hbrush = CreateBrushIndirect(&logbrush);
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);
...

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
);

Add this code to draw text:

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

S-ar putea să vă placă și