![]()
::[ PART 06 - WIN32 API BASICS
]::[ Source
]::[ Printable Version
]::
::[
DISCLAIMER
]::
I will hold no responsibility to whatever happens to you, your computer, your sanity, your pet, or whatever that may happen to your existence for your reading these texts and for the outcome of the various source code(s) given in each tutorial. So in short, read at your own risk!
If you feel like you've been cheated, going through parts 1 to 5, just to abandon everything...well, no, you don't abandon everything. And hey, not many people really know those old stuffs nowadays. They make you more...'learned'...;). SideNote: What I will discuss to you here is but a small part of Windows programming. I will deal with the Win32 API directly, rather than using the popular MFC. The knowledge here will be enough for us to be comfortable in a windowing environment...which OpenGL requires...;).
Well, every program under MS Windows is created by a group of windows. A window could be those boxes you see with a minimize-maximize-close button on the upper right corner, or it could be a dialog box, radio box, or a check box too (among others). I will discuss how to create one of those window with a min-max-close button on the upper right corner. Well, every window has a "class" (NOT the "class" that you know from object-oriented programming). This "class" is actually a structure that you fill information and register with. This structure has the following fields:
//----8<----[ CODE BEGIN ]--------//
typedef struct {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS;
//----8<----[ CODE END ]--------//
This structure can be found in the WINUSER.H file. Note that you'll actually see two structures named WNDCLASSA and WNDCLASSW in the file rather than a WNDCLASS structure. The WNDCLASS structure you see above is just really "unifying" two structures...if you're using Unicode, you're actually using WNDCLASSW, else, you're using the ASCII (or WNDCLASSA)...but don't worry about that...just use WNDCLASS to simplify your life! Now in the structure you might see some new data types. And you will also see the odd prefix they add to each of the structure's fields (i.e. the "h" in "hCursor", or the "lpsz" in the "lpszMenuName"). This is because they use the Hungarian Notation. Briefly, Hungarian Notations are really a convention you use to know what data type each variable / field is. The following is a list of prefixes and their data type:
//----8<----[ LIST BEGIN ]--------//
PREFIX DATA TYPE
c char or WCHAR or TCHAR
by BYTE (unsigned char)
n short
i int
x, y int used as x or y coordinate
cx, cy int used as x or y length;
'c' stands for 'count'
b or f BOOL (int); 'f' stands for 'flag'
w WORD (unsigned short)
l LONG (long)
dw DWORD (unsigned long)
fn function
s string
sz string terminated by 0 character
h handle
p pointer
//----8<----[ LIST END ]--------//
The list was taken from the book "Programming Windows, Fifth Edition" by Charles Petzold. So going back to our structure, the field named lpfnWndProc might be read as a "long pointer to a function that will be the window's WndProc". I'll discuss about WndProc (read as "win prock") later. Describing each field:
style // describe the window through flags
lpfnWndProc // pointer to windows' WndProc
cbClsExtra // disregard this for now
cbWndExtra // disregard this for now
hInstance // handle to the instance of the class
hIcon // handle to the icon of the application
hCursor // handle to the (mouse) cursor
hbrBackground // handle to the background "brush"
lpszMenuName // disregard this for now
lpszClassName // name used to refer to this class
If you're wonderin' what a handle or flag is right now, well, it'll be clear to you later. Now that we have a structure, we fill it with values. You'd want to fill it inside the WinMain() function. WinMain() is MS Windows' equivalent to the main() function most of you probably are so used to...:). Here's how you might do it:
//----8<----[ CODE BEGIN ]--------//
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd)
{ WNDCLASS wndclass; // remember this?
wndclass.style = CS_HREDRAW | CS_VREDRAW; // window's style
wndclass.lpfnWndProc = (WNDPROC)WndProc; // pointer to WndProc
wndclass.cbClsExtra = 0; // disregard...
wndclass.cbWndExtra = 0; // disregard...
wndclass.hInstance = hInstance; // copy from WinMain()
wndclass.hIcon = LoadIcon(NULL,
IDI_APPLICATION); // window icon
// IDI_ASTERISK
// IDI_EXCLAMATION
// IDI_HAND
// IDI_QUESTION
// IDI_WINLOGO
wndclass.hCursor = LoadCursor(NULL,
IDC_ARROW); // window's mouse cursor
// IDC_CROSS // a...'cross'...:)
// IDC_NO // slashed circle
// IDC_WAIT // hourglass
// IDC_HELP // arrow with question mark
// IDC_UPARROW // arrow pointing up
wndclass.hbrBackground = NULL; // handle to bground brush
wndclass.lpszMenuName = NULL; // window's menu name
wndclass.lpszClassName = "MyWindow"; // window's name
}
//----8<----[ CODE END ]--------//
A bit long huh? If you're starting to be discouraged by now because this is waaaaayyyy too much information to store in one's head, well, guess what? Nobody really remembers each field to each structure. You'd want to just copy and paste the code then replace the value to each field...:). You might notice from the line:
wndclass.style = CS_HREDRAW | CS_VREDRAW;
Two thingi-ma-dingies being OR'ed with each other. They are the flags! You combine two or more flags by OR'ing them (I mean the bitwise-OR...NOT the logical one...:) ). CS_HREDRAW means that the window will be redrawn when it is resized horizontally. While CS_VREDRAW...uhmm, you SHOULD get the flow...:). Now in the lines:
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
// IDI_ASTERISK
// IDI_EXCLAMATION
// IDI_HAND
// IDI_QUESTION
// IDI_WINLOGO
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
// IDC_CROSS
// IDC_NO
// IDC_WAIT
// IDC_HELP
// IDC_UPARROW
You "load" to wndclass.hIcon with what it needs...a "handle" to an icon! Think of handles as a pointer that points to a specific thing only...in this case, an icon...:). The first parameter to LoadIcon()/LoadCursor() was set to NULL to use MS Window's standard icons. (Just to let you know, the first parameter accepts a value of type HINSTANCE. This "HINSTANCE" is read as a "Handle to an Instance". What you place in the first parameter should be the handle to the program/module where a desired icon/cursor is stored...:) ) The code above also defines the handle of our mouse cursor to be an arrow (IDC_ARROW). If you want to change it, just choose one from the commented IDC_xxx flags below LoadCursor(). The same goes for the LoadIcon() too...:). After giving values to our WNDCLASS structure, we register our class to make MS Windows know what our window looks like. We do it like this:
if(RegisterClass(&wndclass) == 0)
return FALSE;
Obviously, if there was an error in registering our class, we quit...:). But if we are successful in registering our class, the next step we do is to actually CREATE the window!!! Let's recap first the steps so far:
The up and coming step is:
//----8<----[ CODE BEGIN ]--------//
HWND CreateWindow(
LPCTSTR lpClassName, // copy from wndclass.lpszClassName
LPCTSTR lpWindowName, // caption to appear on title bar
DWORD dwStyle, // describe the window through flags
int x, // x coordinate (upper-left) of window
int y, // y coordinate (upper-left) of window
int nWidth, // width of the window
int nHeight, // height of the window
HWND hWndParent, // disregard for now...
HMENU hMenu, // disregard for now...
HANDLE hInstance, // from WinMain()...again...
LPVOID lpParam // disregard for now...
);
//----8<----[ CODE END ]--------//
Note that it returns a type HWND. This is the handle to our window! So we must create another variable of type HWND. So far our code would look something like the one below:
//----8<----[ CODE BEGIN ]--------//
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd)
{ WNDCLASS wndclass;
HWND hWindow;
// Initialize values of wndclass' fields
// ...
// Code omitted for brevity
// ...
// Register wndclass
if(RegisterClass(&wndclass) == 0)
return FALSE;
// Create our window and get its handle
hWindow = CreateWindow
("MyWindow", // from wndclass.lpszClassName
"My Title Bar", // this will show on title bar
WS_OVERLAPPEDWINDOW, // border, title, min-max-close button
// WS_OVERLAPPED // border, title
// WS_POPUP // no title, no button
// WS_VISIBLE // window is visible from start
// WS_VSCROLL // window has vertical scroll bar
// WS_HSCROLL // window has horizontal scroll bar
// WS_MAXIMIZE // window is created in maximized state
// WS_MINIMIZE // window is created in minimized state
0, // starting x-coord (upper-left)
0, // starting y-coord (upper-left)
CW_USEDEFAULT, // window's x-size
CW_USEDEFAULT, // window's y-size
NULL, // parent window's handle
NULL, // handle to a menu
hInstance, // copy from WinMain()
NULL); // disregard...
}
//----8<----[ CODE END ]--------//
Again, the third parameter of CreateWindow() accepts flags, which means you can OR it with the "commented-out" flags below WS_OVERLAPPEDWINDOW to define your window...:). Oh yeah, if you're an adventurous guy and thought of OR'ing WS_OVERLAPPEDWINDOW with WS_OVERLAPPED...well, you'll get the one with the min-max-close button...guess why...:). CW_USEDEFAULT lets you use the default value MS Windows wants to give it. It can also be used in the x/y coordinate parameter. Now that we've created a window, let's show it shall we? But first let's check if it really WAS created...it's done through:
if(hWindow == NULL) return FALSE;
Showing the window is as easy as:
//----8<----[ CODE BEGIN ]--------//
ShowWindow(hWindow, SW_SHOW); // show created window
// SW_HIDE // hide created window
// SW_RESTORE // show window in restored state
// SW_MAXIMIZE // show window in maximized state
// SW_MINIMIZE // show window in minimized state
//----8<----[ CODE END ]--------//
No, you can't BITWISE-OR the second parameter here...guess why...;). Oh yeah, it seems I haven't discussed anything about that WndProc...well, here goes... When I initialized my wndclass variable above inside WinMain(), I had a line like this:
wndclass.lpfnWndProc = (WNDPROC)WndProc;
WndProc is actually a function that MS Windows calls within our program to know what to do whenever something happens (i.e. when user resizes the window, presses a key, closes the window, etc.). It's the "event handler" for our window. You can give it any name you want, but for clarity's sake, I'll stick to "WndProc" here. Here's how WndProc looks like:
//----8<----[ CODE BEGIN ]--------//
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{ switch(message)
{
case WM_CREATE: // window is created
//--[ DO STUFFS HERE...
break;
case WM_DESTROY: // window is destroyed
//--[ DO STUFFS HERE...
PostQuitMessage(0); // add "quit" to msg queue
break;
case WM_SIZE: // window is resized
//--[ DO STUFFS HERE...
break;
default:
return(DefWindowProc(hWnd, message, wParam, lParam));
}
return(0L);
}
//----8<----[ CODE END ]--------//
So you see, WndProc's got nothing but a sequence of switch statements! What happens is MS Windows calls our program whenever something happens while our window is active. As an example, when we resize our window, MS Windows would call WndProc and pass as its parameter the message with value WM_SIZE and the handle of the window that was active when we resized our window. The "PostQuitMessage(0);" line stops our window from getting message. Think of it as a line that lets the GetMessage() function return FALSE (more on GetMessage() below). wParam and lParam are used to get more information about what happened...but we won't deal with them here...they're for Part 7...;). LRESULT is equivalent to a long data type. Think of CALLBACK as a keyword that lets MS Windows know that it's a dependable function that it can call on when needed...:). There ARE other types of messages, but I chose to gradually show them as we progress (we don't need all of them anyway...). DefWindowProc() takes care of all other messages that might be passed to our WndProc. Without it, messages would be lost, and that's a bad, bad, and I mean BAD thing...so keep it there...:). So how do we keep on getting messages? We make a loop in WinMain(). Here's the code:
//----8<----[ CODE BEGIN ]--------//
while(GetMessage(&msg, hWindow, 0,0)) // keep on getting msgs
{ TranslateMessage(&msg); // convert to "known" msg
DispatchMessage(&msg); // send to msg queue
}
//----8<----[ CODE END ]--------//
TranslateMessage() "converts" the message into something MS Windows recognizes...then the message is dispatched to our window's message queue where it will be used in our WndProc. GetMessage() has the following parameters:
//----8<----[ CODE BEGIN ]--------//
BOOL GetMessage(
LPMSG lpMsg, // the message information
HWND hWnd, // handle to our window
UINT FilterMsgMin, // disregard...
UINT FilterMsgMax // disregard...
);
//----8<----[ CODE END ]--------//
The reason I said to disregard FilterMsgMin and FilterMsgMax is because we want to receive all of the message that MS Windows sends us...;). Anyway... The msg passed to our GetMessage() function is of type MSG. MSG has the following structure:
//----8<----[ CODE BEGIN ]--------//
typedef struct tagMSG {
HWND hWnd; // passed to WndProc's parameter
UINT message; // passed to WndProc's parameter
WPARAM wParam; // passed to WndProc's parameter
LPARAM lParam; // passed to WndProc's parameter
DWORD time; // time stamp (in milliseconds)
POINT pt; // mouse coordinate
} MSG;
//----8<----[ CODE END ]--------//
The "POINT pt;" line stores the x and y coordinates of the mouse when the message occurred. So you see, we use GetMessage() to get values to be used for our window's WndProc. It stores the message value (i.e. they are the WM_CREATE, WM_DESTROY, WM_MOVE, etc.), the wParam, and so on. And now we're done!!!
While Windows Programming may seem tedious, everything that was shown here are but overhead codes you'll have to deal with. And not only that, these codes are just copy-and-paste codes, so what's important is for you to know what each part is doing...NOT remember each part...:). Oh yeah, the code below (under the NOMAD.SOURCE Section) creates a window alright. But the window's content is dependent on the background on which it first appeared. Run it and you will know what I mean. It's stupid too, try resizing and moving the window...;). Part 7 will be about DCs, GDIs, and how to use the wParam and lParam inside the WndProc. You will see there what I mean by copy-and-paste codes. We'll try to discuss how to draw a pixel and show texts on our window. Briefly, DC stands for "Device Context". It's like an ID tag that gives you the right to draw on a specific window. GDI means "Graphics Device Interface" and is used to draw basic stuffs on our window...so watch out for Part 7!
|
|