Gw Temp

Menu

Tutorial - 'Visual C++ and GUIs using the Windows API - Part 1' by AzureFenrir

An item about Programming Languages posted on

Blurb

Do you ever stare at those beautiful Visual C++ programs with their cool graphics and windows and wonder "how the heck do they do that?" In this tutorial, AzureFenrir helps get you started on Graphical Visual C++ using the Windows API!

Body

Visual C++ and GUIs using the Windows API - Part 1

Hello! It's AzureFenrir again, with yet ANOTHER C++ tutorial. This time, I will demonstrate the basics of the FORBIDDEN C++ Scrolls of Doom, the Graphical User Interface! MWAHAHAHAHAHA!

*slap*

Ah, that's better...anyway, this series of tutorials will show you one way to write Visual C++ programs for windows - with graphics and all. This means no more ugly monochrome DOS boxes! Yay!

Visual C++ (actually, Visual C) syntax is really not much different from C++ syntax-wise. All the C++ statements - if, else, do while, switch - are still present and intact. You simply have to use the Windows API functions instead of the egular old DOS cout and cin statements.


NOTE: This tutorial assumes that you have mastered the basics of C++ and have experience working with advanced concepts such as pointers. Simply reading a few C++ tutorials probably won't do - you have to attempt to program in C++ and gain experience working with basic DOS-based C++ stuff.


All right! Let us start with the headers and stuff of a simple "Hello, World!" program:

// C++ Hello World Program

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, PSTR szCmdLine, int iCmdShow)
{
...


Now, what the heck is an LRESULT CALLBACK?! What is a HINSTANCE? Why do we need a WndProc function?! Isn't this driving you crazy?

Well, let me explain. "windows.h" is the header that defines almost all visual C++ functions. If you're writing API-based programs, then you need this header file...ALWAYS. In Visual C++, the WinMain function is used in lieu of main.

That was pretty self-explainatory, but the function prototype for WndProc might require some explanation. You see, most windows require a WINDOW PROCEDURE, which processes certain commands for a window (for example, drawing on them). Think of the WndProc function as some sort of universal handler that takes care of doing stuff in a window.

LRESULT simply means long. If you've ever bowsed through the C++ include files and looked at WinDef.h, you would find a line containing
typedef LONG LRESULT;
. Of course, we (amd most other programmers) use LRESULT because it sounds more nerdy, or mor Visual C++ish.

OVERLY TECHNICAL STUFF WARNING ---

CALLBACK and WINAPI's definition is actually very interesting. They usually substitute for __stdcall, a definition keyword that tells the function to do stack cleanup and call itself in a standard C convention, a convention that is readable by Windows (which was also programmed in C). However, there is also these definition in WinTypes.h:

#define WINAPI      FAR PASCAL
#define CALLBACK    FAR PASCAL


This has interested and puzzled me to no ends. FAR is an assembly/Pascal type that is more or less similar to long. However, the purpose of the PASCAL, or __pascal, keyword is definitely very puzzling. I still have absolutely no idea why the keyword in in there, what is does, and what PASCAL has in common with C++ or the windows API.

--- END OVERLY TECHNICAL STUFF

Anyway, just realize that WndProc functions should always be preceded by CALLBACK and functions that you want Windows to be able to recognize (such as WinMain) should always use by WINAPI.

HINSTANCE means "Handle to an Instance." A handle is basically a pointer to something that is stored in a LONG. An Instance is basically an open of a program - every time the user opens your program (or any other program, for that matter), it is assigned a new Instance, which uniquly identifies this opened version. hInstance gives you this Instance handle, and hPrevInst gives you the instance of another "version" of your program that is still running.

Example: Someone runs your program, and your program is assigned an instance of 33333333. The user runs your program again, and the hInstance for this program may be...say...44444444. In this case, hPrevinstance will be 33333333, the hInstance of the previous version of your program. If the user quit the first version before opening the second, however, hPrevInstance will hold 0 because the previous "version" is no longer running.

PSTR means "Pointer to a String," which is pretty self explanatory. Visual C++ uses its own uppercase naming convention for popular types (such as LONG for long). szCmdLine holds and command line parameters that the user may have entered. For example, if someone runs your program with the command "MyProgram.exe /s /t /g google", szCmdLine would hold "/s /t /g google".

iCmdShow is how the user wants your program (or window) shown (e.g. Minimized, Hidden, Maximized in the Background).

I'll talk about the WndProc parameters later. Now, you want to type the following:


HWND hwnd;
MSG msg;
WNDCLASSEX wndcls;



HWND is a handle to a window. It will identify the window so that your program can manipulate it. MSG is a message structure, which I'll explain later. Now, we want to focus on the WNDCLASSEX structure and our instance of it, wndcls.

Every window will have its own WINDOW CLASS, which will hold some basic information about a window (for example, its icon). Unless if you want to use an existing window class (not recommended), you should definitely fill this structure out and register a new class for your program.

So, let's fill out this structure:


wndcls.cbSize = sizeof(wndcls);
wndcls.style = CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = WndProc;
wndcls.cbClsExtra = 0;
wndcls.cbWndExtra = 0;
wndcls.hInstance = hInstance;
wndcls.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wndcls.hCursor = LoadCursor (NULL, IDC_ARROW);
wndcls.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wndcls.lpszWindowName = "MyProgramClass";
wndcls.lpszMenuName = NULL;
wndcls.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

RegisterClassEx (&wndcls);


cbSize contains the total size of the structure. You might think it's stupid to do that - I mean, Windows could at least know the size of it's own structure, right? Well, Windows is programmed in C, and sometimes has to recognize more than one different type of structure for a parameter (usually in future versions to add more features). Therefore, it's wiser for windows to make sure that it knows exactly how many bytes it needs to recognize so it doesn't intrude on oter stuff.

Ignore style for now. It specifies the flags (or features) that you want in windows defined by this class. If you want to know all the flags, check out the MSDN library or the Borland Help Files.

lpfnWndProc specifies the handle to the Window Procedure for that specific window. This window procedure will either handle all the stuff that the window needs to handle or pass it down to a default procedure.

The two "Extra" parameters basically asks you if you need extra bytes to store extra information about a window. We don't need to store any additional information in either the window or the class, so this field will remain zero.

hInstance holds the instance that the class belongs to. The class will usually only be usable by that instance.

hIcon, hCursor, and hIconSm are self-explainatory. Since we do not have a resource file and thus, no custom icons or cursors, we are forced to use default icons and cursors. The LoadIcon and LoadCursor functions, in this case, allows us to use the system default icons/cursors.

lpszWindowName is basically the class name that will be assigned to windows of this class. This can be anything you want it to be.

lpszMenuName specifies the resource name of the menu that this window uses. Since we do not have or use any menus for this program, this field remains a zero.

hbrBackground points to a HBRUSH (handle to a brush) that specifies the brush used to paint the background of the window. Since I haven't gotten to brushes yet, we'll just use the white brush that windows allows us to use. Note that GetStockObject returns a generic pointer (void *, I think), so a type conversion (HBRUSH) must be used to convert it to a brush handle so C++ won't give us data type incompatibility errors.

As an alternative, you can use a color value (such as COLOR_BTNFACE) converted to an (HBRUSH). Don't worry about this if you do not understand. I'll discuss colors and brushes when I get to basic GUI objects.

Finally, RegisterClassEx registers the class so our windows could use it. Note that RegisterClassEx requires a POINTER to a wndcls structure, so the & operator is necessary.

Let's go on to actually creating a window that we can do things with:


hwnd = CreateWindowEx(WS_EX_APPWINDOW, "MyProgramClass",
"Super Duper Hello World Application in Visual C!!!",
WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL,
NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);



Kudos! Anyway, CreateWindowEx is an API function that...well...creates an window! In this case, I'll explain the parameters.

WS_EX_APPWINDOW: This is the Extended Style of a window. WS_EX_APPWINDOW basically specifies that the window is an application window and should appear on the taskbar. Extended styles should be listed in your C++ help file (or online at msdn.microsoft.com). All extended style definitions begin with WS_EX_.

"MyProgramClass" is the class that the window belongs to. It should be the same as wndcls.lpszWindowName.

"Super Duper Hello World..." is the caption of the window, or what appears on the top "caption" bar and status bar.

WS_OVERLAPPEDWINDOW is the Style of the window. It's just like extended style, but specifies different style properties. WS_OVERLAPPEDWINDOW creates a regular window. Styles, like Extended Styles, can be found in your C++ help file or on msdn.microsoft.com.

The next four parameters (100, 100, 500, 500) specifies the x coordinate, the y coordinate, the width, and the height of the window, respectively, in pixels.

The first parameter with a NULL specifies the parent window, if this window is a child control/MDI child. In this case, the window does not have a parent, so the parameter contains a NULL.

The second NULL points to a menu, which we are not using.

hInstance is the instance that the window belongs to.

The last NULL specifies any extra information that you would like to add. Because we do not have any extra information for the window, this parameter is NULL.

NOTE: If you want to store and extra infomation on a window (such as assign each window a certain number), you must first fill wndcls.cbWndExtra with the number of bytes of extra information (for example, if you want to store a 32-bit number, then you should set cbWndExtra to 4:
wndcls.cbWndExtra = 4;
). Then, you can specify a pointer to the extra information that you want to store. Remember that you must use a POINTER TO A VALUE.


Show window just shows the window with the style that the user specified (iCmdShow). So if the user wanted the program to start maximized, then Showwindow will maximize the window. You could also use WS_VISIBLE and WS_MAXIMIZE/WS_MINIMIZE, but I won't get into that. Finally, UpdateWindow simply tells the program to repaint the window.

Now, let us go to the must important portion of a visual C++ program, the Message Loop:


while (GetMessage(&msg, NULL, 0, 0))
{ TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam;



As you guys probably know, once a function reaches a return statement or its own end brace, it will end. Thus, if WinMain encountered its end brace, it will simply end the program there, and our program will end up doing absolutely nothing. Therefore, we need a message loop, which interprets all messages coming to the program and sends it to the appropriate function.

But what are messages?

A message is Windows's way of telling your window what happened. For example, if the user moves his.her mouse inside your window, a message is generated (WM_MOUSEMOVE, if you're curious). If the user presses a key, a message is generated (actually, three: WM_KEYDOWN, WM_KEYUP, and WM_CHANGE). If a user sneezes on the moniter, no messages will be generates (unless if the vibration moves the mouse, in which case a message will be generated). The messages that are generated will be sent to your Window Procedure (WndProc), which will take care of the message by doing stuff (like drawing text, beeping, or detonating and blow up the world!!!). Yep, WndProc is a hard worker who doesn't get paid...*sniff*

This loop ensures that your program doesn't end by grabbing messages off of the message loop and processing them. GetMessage, in this case, takes the mext message in the loop and stores it in msg. The message is then handled by the appropriate window procedure (see, windows is not a true multi-tasking environment after all!), and the next message is then obtained.

If GetMessage encounters a message called WM_QUIT, it will return zero, which will cause the program to terminate.

Now, let us look at the actual code of the all-mighty WndProc:


LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

switch (iMsg)
{
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps);

GetClientRect(hwnd, &rect);
DrawText(hdc, "Hwllo, World! Hello, GW! Hello, Bart! Goodbye, GGZ!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc (hwnd, iMsg, wParam, lParam);
}
}


Whoa! Amazing, isn't it?

Explain I shall. The first parameter of the WndProc function, hwnd, tells you the hwnd of the window that sent the message. This allows you to easily manipulate the window.

The second parameter, iMsg, tells you what message is actually being sent. For example, is it WM_PAINT, which tells you to paint the window (BTW: UpdateWindow generates a WM_PAINT message), or is it WM_MOUSEMOVE?

The last two parameters, wParam and lParam, contains any additional information that may be sent with the message. For example, for a WM_MOUSEMOVE message, they might contain the x and y coordinates of the mouse. They might even contain pointers to structures if two parameters are not enough.

An HDC is the handle to a Device Context, which basically tells you where to draw something. Like a HWND, a device context allows you to manipulate a window, except it allows you to manipulate a window's graphics instead of its inner properties. Because most winodws contain graphics, HDCs are used A LOT.

A PAINTSTRUCT is basically a structure that specifies a few painting properties. We do not use this structure yet, so we won't worry about it.

Of course, as it's name implies, a RECT specifies a rectangle. It contains parameters for x, y, width, and height.

The switch structure is there so we can single out certain messages to handle. In this case, we are taking care of the WM_PAINT and WM_DESTROY messages and letting windows hanlde the rest.


In WM_PAINT (which is called every time the form needs to be repainted), we have the following:

        hdc = BeginPaint (hwnd, &ps);

GetClientRect(hwnd, &rect);
DrawText(hdc, "Hello, World! Hello, GW! Hello, Bart! Goodbye, GGZ!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

EndPaint(hwnd, &ps);


BeginPaint allows us to start painting on a form. It returns an HDC that we can use to draw stuff.

GetClientRect allows us the get the Client Area of a window. In Layman's terms, it gets the rectangular area of a window that we could actually draw on and store it in rect.

DrawText actually writes the text onto the window. The first parameter specifies the device context that the words should be drawn on. The second parameter is the actual text to draw. They're pretty much self-explainatory.

NOTE: Remember that in windows, a combination of a carrier break and a line feed creates new lines. Thus, to start a new line, you must use \r\n instead of simply \n.

The third parameter is the number of characters to draw. Since we put -1 there, windows will continue to read characters until it reaches a NULL character (\0), when it will stop and draw the string.

The fourth parameter is a rectangle that encloses the text. Since we need to center the text on the client area, we use the previously populated structure &rect for this parameter.

The fifth parameter specifies the flags for the text. Since we need to draw a single line of characters centered in the window, we use DT_SINGLELINE, DT_CENTER, and DT_VCENTER. You must use the bitwise OR operator (|) to join the flags. The boolean OR operator (||) will not work.

Finally, EndPaint stops the painting, and return 0 indicates that we've successfully handled the message.


The WM_DESTROY message indicates that the user closed our window. In which case, we must close the program by using PostQuitMessage(0), which sends a WM_QUIT message to the message loop. The message loop then ends, and our program is finished.

If we do not include the PostQuitMessage(0) or do not handle the WM_DESTROY message, then the window will still close. However, the program's instance will still exist, and our program will still be running and taking up resources (which is not good).

Finally, DefWindowProc passes any unhandled messages to the Default Windows Procedure, which handles any message sthat our program does not deal with. This is the procedure that allows our Window Procedure to be this short, even though there are about 900 existant messages that we could handle (and about two billion possible messages).

Let's wrap this tutorial up with the actual code:



#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, PSTR szCmdLine, int iCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASSEX wndcls;

wndcls.cbSize = sizeof(wndcls);
wndcls.style = CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = WndProc;
wndcls.cbClsExtra = 0;
wndcls.cbWndExtra = 0;
wndcls.hInstance = hInstance;
wndcls.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wndcls.hCursor = LoadCursor (NULL, IDC_ARROW);
wndcls.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wndcls.lpszWindowName = "MyProgramClass";
wndcls.lpszMenuName = NULL;
wndcls.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

RegisterClassEx (&wndcls);

hwnd = CreateWindowEx(WS_EX_APPWINDOW, "MyProgramClass", "Super Duper Hello World Application in Visual C!!!", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return msg.wParam;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

switch (iMsg)
{
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps);

GetClientRect(hwnd, &rect);
DrawText(hdc, "Hwllo, World! Hello, GW! Hello, Bart! Goodbye, GGZ!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc (hwnd, iMsg, wParam, lParam);
}
}





Phew...that about wraps up the basics of Old-Styled Visual C++ with the Windows API. On my next tutorial, I will talk about...uh...actually, what should I discuss? GUI Objects? Messages? Window Styles? Hooks and Pipes? What do you think, Rami?

Anyway, I must leave before the masked GW members find me. Tada!