[ Nomad Programming, Graphics, and Algorithms Tutorial ] [ Compiler : MS Visual C++ 6.0 ] [ Part 7 - Window Tracking, DCs, and GDIs ] [ http://nomad.openglforums.com ] <<------------------------------------------------------>> [ 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! [ INTRODUCTION ] Now that I have introduced to you guys how to set up a window under MS Windows (see Part 6!), I will now introduce to you how to know "more" about each message that is passed to a window (remember the lParam and wParam parameters on our WndProc? What? No? Well, read Part 6 again. Huh? What? Yes? You've read it? But I didn't discussed them there? Well, that's because I will be discussing them here...:) ). I'll also be discussing about Device Contexts (DCs) and the Graphical Device Interfaces (GDIs)...this topic is quite easy once you get a hang of it, but is a bit daunting to those uninitiated. But that's OK right? Nothing that a little effort couldn't do...:). SideNote: For the GDIs, I will introduce only how to draw texts, and plot pixels to a window. If you would like to learn more (like drawing lines, filled polygons and stuffs), then go and have a look at the largest library the world has ever seen...the Internet! Or you could have a look at the REFERENCE Section below...:). [ TUTORIAL ] Again, I'll be using the words "MS Windows" should I want to refer to the OS, and "window(s)" when I'm going to refer to a window (duh...:P). OK, let's begin with the tracking of a window. To track a specific status of a window, we must first know WHAT message to deal with. Listed below are the messages I'll discuss with you in this text file: //----8<----[ LIST BEGIN ]--------// WM_CREATE // window is created WM_DESTROY // window is destroyed WM_SIZE // window is resized WM_MOVE // window is moved around the screen WM_PAINT // window's paint message //----8<----[ LIST END ]--------// You should be familiar with the WM_CREATE, WM_DESTROY, and WM_SIZE...they were in the code in my last tutorial (Part 6)...but then again, I didn't use them last time...;). The "WM" in each of the message listed above means "Window Message"...;). Each of the message is sent by MS Windows to the window's WndProc whenever the corresponding act was committed by the user. For example, if the user clicked the "x" button on the upper right corner of our window, MS Windows would call the window's WndProc function and say that the message WM_DESTROY has been committed by the user. (Argh! I sound like I'm writing a novel about murder or somethin'...). Anyway... WM_CREATE and WM_DESTROY are pretty self-explanatory. You'd want to place your initializations (e.g. assigning values to variables, memory allocations, etc.) in the WM_CREATE message and some finalizations (e.g. free the allocated memories) in the WM_DESTROY message. We also don't bother with the lParam and wParam parameters passed to our WndProc if we deal with WM_CREATE and WM_DESTROY. For WM_SIZE, well, we start dealing with lParam here. You see, when you resize a window, you get a new size for the window (horizontally and vertically). So how would someone like you get the new x-size and y-size of a window from lParam??? Well, if you'd remember the Hungarian Notation, lParam would mean that it's a long integer (it has the prefix "l"). Now, a long integer may be divided to two words, with one word storing the new x-size and the other, the new y-size. Here's an illustration to clarify things up: +------------------------------------------------------+ | (WORD) y-size | (WORD) x-size | +------------------------------------------------------+ \_________________________ ___________________________/ \/ lParam (i.e. one long integer) So we get the new x-size using LOWORD(lParam), and the new y-size using HIWORD(lParam)...neat huh?...:). While the lParam gives you the new width and height size of the window, the wParam gives you some more information. These information are listed below: //----8<----[ LIST BEGIN ]--------// SIZE_MAXIMIZED // window is maximized SIZE_MINIMIZED // window is minimized SIZE_RESTORED // window is restored //----8<----[ LIST END ]--------// To summarize so far, when the WM_SIZE message is passed to our window's WndProc, we get the new width and height through lParam, and we get further information from wParam (as listed above this paragraph...:) ). The next message I'll be discussing is the WM_MOVE. The WM_MOVE message is passed to our window's WndProc if the user has moved our window around the screen. So all the information that we need is the new x and y coordinates of our window. To get these new coordinates, we use lParam! We use LOWORD(lParam) and HIWORD(lParam) to get our new x and y coordinates, respectively. Comparing WM_MOVE with WM_SIZE: //----8<----[ LIST BEGIN ]--------// WM_SIZE: new_x_size = LOWORD(lParam) new_y_size = HIWORD(lParam) WM_MOVE: new_x_coord = LOWORD(lParam) new_y_coord = HIWORD(lParam) //----8<----[ LIST END ]--------// Isn't that just nice?...:). Oh yeah, the new x/y coordinates you get are the upper left coordinates of your window...:). OK! We're done with all but one message! I'll discuss about WM_PAINT after we're through with DCs...:). Here we start knowing about the Device Context (DC). There are different ways that you can get the DC, and I'll be teaching you guys one way today, they are the GetDC() and ReleaseDC() functions. (FYI: GetDC() and ReleaseDC() are useful because they can be "invoked" on any message, which means you can do GDI stuffs on any message! Another way to use the DC is BeginPaint()/EndPaint(). They seem to be the better way to handle DCs when you have a WM_PAINT message. But for now, GetDC()/ReleaseDC() will have to do...:) ). Think of the DC as a pathway for you to draw something to your window. Remember the time when we grabbed a handle to our window inside WinMain() (via CreateWindow())? Well, think of that handle as a pointer that points to your window, while the DC points to the area of your window that you can draw on (called the "Client Area"...:) ). We get the DC through the following: hDC = GetDC(hWindow); where hDC is of type HDC and hWindow is a handle of a window you want to get the DC from...:). And also, always remember to RELEASE your DCs!!! You do it through the following: ReleaseDC(hWindow,hDC); OK, now we see how to use the DC in our WM_PAINT message. The function to plot pixels looks like this: SetPixel(HDC, x, y, COLORREF); The HDC parameter is our DC, followed by the x and y coordinate of our pixel, and then the color that we want our pixel to be. You'd want to specify the color (in the COLORREF parameter) through this code: RGB(red, green, blue) Where red, green, and blue has a range of 0 to 255. Note that RGB() is not a function, it's actually defined as: #define RGB(r,g,b) ((COLORREF)(((BYTE)(r) | \ ((WORD)((BYTE)(g)) << 8)) | \ (((DWORD)(BYTE)(b)) << 16))) (See those bitwise shifts? I told you going through Parts 1 to 5 wasn't a total waste of your time...:) ) You don't need to #define RGB...it's already defined when you #include ...:). OK, so as an example, if we wanted to plot a pixel at coordinates (5,10) with a strong RED...it'd look like: SetPixel(hDC, 5, 10, RGB(255,0,0)); To draw texts, we use the following function: TextOut (HDC, x, y, psText, iLength) ; The HDC is our DC, followed by the x and y coordinate of our text. psText (the "ps" in "psText" means "pointer to a string"...just thought you should know...:) ) is our string itself, and iLength is the length of our string... we get our iLength through the function strlen()...:). Here's a sample using TextOut(): TextOut (hDC, 1, 5, "Hello World", strlen("Hello World")) ; Look at the NOMAD.SOURCE Section below to see how all of what I discussed above is used. You should be able to read through the code and know what's happening...:). [ ENDING ] And that's that! Not hard eh? If there is anything that's not clear to you, your mind, or to that part of you that lets you comprehend the things here, you may email me at (according to priority): willietang@hehe.com or willietang@yahoo.com Wow! I'm glad that's over...I really tried my best to explain everything as clear as possible to beginners... I am not a "seasoned" Win32 programmer. In fact, I've just learned everything that I'm writing here (starting Part 6, that is)...I write these tutorials so I know how much I know already and how much I am perplexed on whichever topic that occupies my mind...I find myself learning better this way...:). You'll also notice that a bulk of the code in the NOMAD.SOURCE Section below is copied from Part 6! Part 7 will be about detecting keypresses...:). Briefly, we'll deal with new messages. These messages are (obviously) for handling keyboard inputs. I'll discuss how to read keyboard inputs and also tell you guys how to disable the Alt-F4 keystroke when your window has the focus (useful in games!!!)...all in Part 8. [ REFERENCE ] I want to recommend the following sites to you: URL : http://www.GameDev.net URL : http://www.WinProg.org The first link has LOTS of articles...mostly for game development. Really nice...worth a look!!! The other link focuses on MS Windows programming using the Win32 API. [ NOMAD.SOURCE ] /* Author : Willie Tang * Compiler : MS Visual C++ 6.0 * Created : December 24, 2001 * Modified : January 03, 2002 * * Notes : The code below works as a Win32 Application. * To run the code, create a new project (File->New). * In the Projects Tab, select "Win32 Application", * give the project a name them click OK. Then select * the "An Empty Project" radio button, click Finish. * * To insert the code to the project. Click on the * Project menu->Add to Project->New. On the Files * Tab, select "C++ Source File", give any name to * the file, then click OK. After that, copy and paste * the code below to the editor. ************************************************************/ #include #include #include //--[ I know, I know, global variables are bad...but hey, //--[ my goal is to let you know how to use these stuffs. //--[ Besides, it's actually OK to use global variables //--[ for small programs...;). HWND hWindow; // handle to window HDC hDC; // window's device context char stringState[30]; // used to draw text to window char stringSize[30]; // used to draw text to window char stringCoord[30]; // used to draw text to window int xsize, ysize; // stores window's x/y size int xcoord, ycoord; // stores window's x/y coordinate LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_CREATE: // window is created sprintf(stringSize, "User has not resized window"); sprintf(stringState,"Window has not changed state"); sprintf(stringCoord,"User has not moved window"); break; case WM_DESTROY: // window is destroyed PostQuitMessage(0); // add "quit" to msg queue break; case WM_SIZE: // window is resized xsize = LOWORD(lParam); // get window's x-size ysize = HIWORD(lParam); // get window's y-size sprintf(stringSize,"X-size: %d | Y-size %d", xsize, ysize); if(wParam == SIZE_MAXIMIZED) sprintf(stringState,"Maximized State"); else if(wParam == SIZE_RESTORED) sprintf(stringState,"Restored State"); //--[ Another possible state for wParam is SIZE_MINIMIZED. //--[ But since you won't be able to read stringState on //--[ screen when the window is minimized, I didn't add //--[ the sprintf() for SIZE_MINIMIZED here...:) break; case WM_MOVE: // window is moved xcoord = LOWORD(lParam); // get window's x-coordinate (upper left) ycoord = HIWORD(lParam); // get window's y-coordinate (upper left) sprintf(stringCoord,"X-coord: %d | Y-coord %d", xcoord, ycoord); break; case WM_PAINT: // draw-to-window message hDC = GetDC(hWindow); // get device context TextOut(hDC, 5,15, stringState, strlen(stringState)); TextOut(hDC, 5,35, stringCoord, strlen(stringCoord)); TextOut(hDC, 5,55, stringSize, strlen(stringSize)); SetPixel(hDC, // device context rand() % xsize, // random x-coordinate rand() % ysize, // random y-coordinate RGB(rand() % 255, // random red intensity rand() % 255, // random green intensity rand() % 255)); // random blue intensity ReleaseDC(hWindow, hDC); // release device context break; default: return(DefWindowProc(hWnd, message, wParam, lParam)); } return(0L); // return a zero } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASS wndclass; MSG msg; // Initialize values of wndclass' fields 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 = (HBRUSH)CreateSolidBrush(RGB(0,0,0)); wndclass.lpszMenuName = NULL; // window's menu name wndclass.lpszClassName = "MyWindowName"; // window's name // Register wndclass if(RegisterClass(&wndclass) == 0) return FALSE; // Create our window and get its handle hWindow = CreateWindow ("MyWindowName", // 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()'s parameter NULL); // disregard... // Check if window was created if(hWindow == NULL) return FALSE; // Show our window 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 // Keep on grabbing messages while(GetMessage(&msg, hWindow, 0,0)) // keep on getting msgs { TranslateMessage(&msg); // convert to "known" msg DispatchMessage(&msg); // send to msg queue } return 0; } <<------------------------------------------------------>> [ Written By: Willie Tang ] [ Written On: 12.23.2001 ] [ Updated On: 01.03.2002 ]