Taming Windoze



by Telemachos






Introduction


Hi guys, guess what - time for another tutorial from Peroxide!
It has been a loooong time since my last tutorial and much has happened both in MY little world but also in the big mean world of graphical programming.
The most significant change must be the definite death of DOS! I know, some of you will start screaming in anger at me now, others might faint in disbelief - has Telemachos, the extreme DOS fanatic, finally lost his mind? Has he turned to embrace the enemy, has he gotten himself a job at M$? (no)
Well, the fact is that I realized I didn't really had a choice anymore. All new graphic cards only support Windows, making my old VESA code for DOS useless. Also with the release of Direct-X we, the old graphic "hackers" from the glory days of DOS, have gotten the direct access to hardware we need to make FAST graphics in Windows! And I actually MEAN fast! Often quite a bit faster than we could make it in DOS because Direct-X takes care of using the hardware acceleration found on just about any graphics card these days - but thats another topic which I'll cover in my next tutorial.

So, the series has turned into a Windows/Direct-X tutorial series - but as you might know this also means the end of the Pascal support :( From now on the language used will be C++ only, sorry all you Pascal fans - I know that it has been the fact that my tutorials were mostly written in pascal that has made the series as popular as it is now, and I thank you all for all your inputs, feedback and praising words! It is you that made the series worth writing back then!
If there is interest I might put together a small "crash course" in C/C++ programming to help those of you that might want to switch into C++ - it's actually quite easy to change to a new language as you don't have to learn all the basic programming stuff again. In fact it's mostly a question of syntax used to achieve the same things! (And then there is the object oriented programming style etc, etc.. )

Those of you who prefer to read my tutorials offline can download this package which also contains the small demo program which demonstrates how to build a "skeleton" to use as starting point when developing a windows program.

Thanks to André LaMothe who is the original source for much of the stuff I'll cover.



What is THIS tutorial about ?

The primary goal of my turning this series into a Windows series is to show you how to use Direct-X (DirectDraw to be precise) for making fast game-like graphics. BUT, those of you who have been reading my previous tutorials will know that I like to start at the bottom slowly working my way up towards the final goal - and these Windows tutorials wont be any different! So, today I'm not going to use Direct-X. Instead I'm going to show you how to program some basic stuff using the normal Win32 functions.
This approach serves TWO main purposes :

  • You will learn to understand how Windows work and you will learn how to code the "frame" code which must surround every Windows program and you will learn to use the GDI to do simple graphics and text. Even when using Direct-X you might often want to use the GDI for simple things like text - so learn it well! Finally you will learn a few neat tricks about handling input from keyboard and mouse.
  • You will learn to appreciate the work Direct-X does for you :)
  • Here is the stuff the tutorial covers :
  • Creating a Windows class.
  • Creating a Window.
  • Learn about the Windows messages.
  • Learn how to code an event handler.
  • Learn about the GDI and GDC.
  • Learn to draw graphics using the GDI.
  • Learn about input through keyboard and mouse.
  • About notation : Something which can become slightly confusing when discussing Windows programming is to make clear when I talk about Windows 95/98 and when I talk about a window in Windows :)
    When I refer to the OS Windows 95/98 I will write : Windows (with a capital W). When I refer to a window in Windows I will write it in all lower-caps like : window, your windows etc. Get it ? :)



    The Windows Class - what the #&%# is that ?!?

    You might think a window is just a window - but NO, there are MANY things you need to tell Windows about any window you want to create. You describe a specific class of windows in a class called the Windows Class. Technically the name class is a bit misleading as we are not talking about a class like we use the word when talking about object oriented code - for this "class" the name structure might be more fitting. Anyway, here is how it looks like :

    typedef struct _WNDCLASS
     {
      UINT      style;         // some flags which describes the window, see below
      WNDPROC   lpfnWndProc;   // A pointer to the event handler for this window
      int       cbClsExtra;    // We will not use this.
      int       cbWndExtra;    // We will not use this.
      HANDLE    hInstance;     // handle to the instance of the window
      HICON     hIcon;         // handle to the icon of the application
      HCURSOR   hCursor;       // Take a guess
      HBRUSH    hbrBackground; // handle to the background "brush"
      LPCTSTR   lpszMenuName;  // This will become clear later.
      LPCTSTR   lpszClassName; // We give the class a name through which we will refer to it later.
      } WNDCLASS; 
     
    WNDCLASS windowsClass;
    
    The style field is very interesting and at the same time it introduces you to one of the most confusing things in windows coding - all the damn flags!!! There are SOOOO many flags you must remember when coding windows applications - and its NOT getting any better once I introduce direct-X in the next tutorial. Anyway, here are the flags you should normally use. Usually I'll only show a small part of the flags available - namely those I find important. If you are curious look up the rest in the help for your compiler.
      windowsClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    
    Remember you put more flags together by "or'ing" them together. ( | is the bitwise or operation ) Pay attention to the CS_OWNDC flag - the meaning of that should become clear later on in this text. For now, just write it.

    The next field we need to fill out is the lpfnWndProc. That is a function pointer to the event handler. Those of you who have read PXDTUT6 about interrupts and keyboard handlers have seen something like this before - the event handler basically does the same as an keyboard handler. It reacts - not on interrupts this time - but on MESSAGES sent to it by Windows - but we will go more into this in the section called the event handler.
    Anyway, we set it like this :
      windowsClass.lpfnWndProc = MyEventHandler;
    
    The two next fields are for something we wont get into now, just set them to 0.

    The next field is the hInstance field. Each Windows application gets a kind of unique ID number passed to its main function. This parameter is of type HINSTANCE and this is the value we assign to this field :
      windowsClass.hInstance = hinstace; // from the WinMain() 
    
    The two next fields are somewhat similar - they are handles to the icon representing the application and a handle to the default cursor of the application. They are set like this :
      windowsClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
      windowsClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    
    See, more flags!! I told you - and we are just beginning. Here are a few more flags you can use for the icon and cursor :
      IDI_APPLICATION : default icon
      IDI_ASTERISK    : Guess..
      IDI_EXCLAMATION : Guess..
      IDI_HAND        : Guess..
      IDI_QUESTION    : Guess..
      
      IDC_ARROW       : standard arrow
      IDC_CROSS       : Guess
      IDC_NO          : Slashed circle
      IDC_WAIT        : Hourglass
    
    Next you need load a background brush for your application. A brush is kinda like a background and can actually be a bitmap. But there is also a bunch of build in brushes which will do fine for our purposes (when we want to use bitmap as backgrounds we will use Direct-X). Load it like this :
      windowsClass.hbrBackground = GetStockObject(WHITE_BRUSH); // set a white background
    
    Oh no!! More flags... Yes, here are a few more which could come in handy :
      BLACK_BRUSH,  DKGRAY_BRUSH, GRAY_BRUSH, LTGRAY_BRUSH
    
    Cool! Only two more fields to fill in before we have a windows class defined!
    The first one is the lpszMenuName. This field demonstrates a second slightly confusing thing about windows programming : Many things can be done in many different ways! This field is ONE way to attach a menu to an application (and it's the way WE are gonna do it) - for now just set it to NULL as we will have to discuss a few more things before we can make a menu.
      windowsClass.lpszMenuName = NULL;
    
    The last field is the name we want to know the class we just created by. This is the name we will use when we actually wish to create a window of this class - set it to any name you like :
      windowsClass.lpszClassName = "MyVeryFirstWindowClass";
    
    Now, before we can actually USE this Windows Class we just defined we got to register it with Windows to let Windows know of it - after this step we can create windows by referring to the ASCII name we gave the class :
    Register it like this :
     if( !RegisterClass(&windowsClass) == 0)
       {
        // an error occurred - deal with it here!
         }
    




    Creating a window - more pain, more flags :

    Alright! Now we have gone through all that pain just to define how our window should look - so now we just open it right? Yeah, wish that were true - but in reality we are only half way there!
    First of all I want to talk a bit of some things you must always add to the main file of your program. Fortunately for you some nice guy at Microsoft put together two header files which includes all the Windows stuff you are going to need. So you start your program like this :
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    #include <windowsx.h>
    
    The WIN32_LEAN_AND_MEAN define forces the compiler to make some choices within the windows header files that we want it to make, so just write it.

    Next I wanna tell you a bit about the WinMain() function. Just like a normal C/C++ has entry point at a function called main which MUST be there for the program to work a Windows program has entry point at a function WinMain(). The prototype of this function is like this :
       int WINAPI WinMain(HINSTANCE hinstance, 
                          HINSTANCE hprevinstance, 
                          LPSTR lpcmdline, 
                          int ncmdshow);
    
    Its important you define the WinMain exactly like this in your own programs!!
    A comment about the lpcmdline; In normal C/C++ you get parameters passed to the program as an array of strings like this *argv[].
    In Windows the parameters are ONE STRING ONLY!!

    For example : A call to a program like this : MyProg.exe param1 param2 would result in parameters like this in normal C/C++
      argv[0] == "MyProg.exe"
      argv[1] == "param1"
      argv[2] == "param2"
    
    but in Windows you would get :
      lpcmdline == "param1 param2"
    
    Notice that in windows we don't get the name of the application file in the parameter string!!
    Now is the time to create that window. The function to do this takes 11 parameters!!! Its prototype is like this :
       HWND CreateWindow( LPCTSTR lpClassName,   // the ASCII name of the class
                          LPCTSTR lpWindowName,  // The title of the window.
                          DWORD   dwStyle,       // ACK - more flags. See below
                          int     x,             // x position of window
                          int     y,             // y position of window
                          int     nWidth,        // width of window
                          int     nHeight,       // height of window
                          HWND    hWndParent,    // handle to parent window, usually NULL
                          HMENU   hMenu,         // handle to a menu. Set to NULL
                          HANDLE  hInstance,     // the one from WinMain() again...
                          LPVOID  lpParam);      // Set to NULL
                          
    
    I wont insult your intelligence by going into all those parameters in detail. The only one I'll talk about is the dwStyle parameter. As when defining the class we can set multible flags by or'ing them together. Hero goes the list :
       WS_OVERLAPPED       : This window type has a border and a title bar with a "close" button
       WS_OVERLAPPEDWINDOW : Same as above + it has maximize and minimize buttons
       WS_POPUP            : Creates a window without any title bar and no buttons.
       WS_VISIBLE          : The window is visible from the beginning.
       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.
    
    Notice that the function returns a variable of type HWND - a handle to the window. This variable is needed in many, many windows calls to determine which window we want to act upon. So save it in a global variable.
    You should also check that the return value is not NULL. If it is so an error has occurred and the window was NOT created!!
    To create a window of the type we defined in our class we would do :
                  
      HWND main_window = NULL;
    
      main_window = CreateWindow("MyVeryFirstWindowClass",         // ASCII name of class
                                 "Hello World",                    // title of window
                                 WS_OVERLAPPEDWINDOW | WS_VISIBLE  // style of window
                                 100,100,                          // window position
                                 300,200,                          // window size
                                 NULL,                             // handle to parent window 
                                                                   // (there is none!!)
                                 NULL,                             // handle to menu
                                 hinstance,                        // from WinMain()
                                 NULL);                            // because I say so :)
                               
      if (main_window == NULL)
        {
          // handle the error here!
          }
    
    Cool - we actually made a window pop up on the screen!!! Not that is does much... There is ONE more function I'll mention now - the ShowWindow function. If you do not set the window visible when you create it you can do it in the code like this :
       
       ShowWindow(main_window, SW_SHOW);
    
    Other flags for this function includes :
       SW_SHOW, SW_HIDE, SW_RESTORE, SW_MAXIMIZE, SW_MINIMIZE, SW_SHOWMAXIMIZED and
       SW_SHOWMINIMIZED
    




    The Windows Messages :

    As we all know Windows is a multi tasking OS which means many totally many independent programs can be started and run at the same time. (Well - not really at the same time, but thats how we see it)
    We, the programmers of the applications are responsible for making the program do whatever it does - but there are also several things we do NOT handle ourselves, like moving the windows around the screen, drawing the frame around them and many other things. In windows such things (among many others) are called an EVENT. Whenever an event happens Windows yells to the affected window what happened - it sends a MESSAGE to the window. For example, if we resize a window, Windows tells the application in the window : "Hey, someone just resized the window you live in - deal with it dude!"

    Sometimes events happens faster than an application can handle them. To avoid loosing any events Windows has something called a message queue where messages "get in line" to be handled when the event handler is free. Sometimes a message is very urgent - it MUST be handled NOW! In this case we can actually skip the queue and put our message directly in the front of the queue - but we will look at that later. For now, think of a Windows program to follow this structure : It has a main loop where it gets a message from the queue (if any are waiting), looks at it and deals with it. Then it returns to get another message etc, etc.

    These are SOME if the messages Windows can send to an application :
      WM_CREATE  : when a window is first created
      WM_CLOSE   : when a window is closed (for example by clicking the cross in the title bar)
      WM_DESTROY : when a window is destroyed and removed completely from memory. See above.
      WM_MOVE    : Guess
      WM_QUIT    : When the Windows application is terminating
      WM_SIZE    : When a window has changed its size.
      WM_PAINT   : IMPORTANT!! When a window needs repainting. We will look closely at this later.
      WM_USER    : This is when you send your own messages. 
    
    Many more messages exist - some of which has to do with input/output via mouse or keyboard. We will look briefly at those later. The WM_PAINT message will be discussed in great detail in the section about using the GDI / GDC to draw graphics.



    The event handler :

    Remember that function pointer I referred to when we defined the Windows Class we wanted to use in our programs ? The function is what takes care of dealing with those messages I described in the section above this one.
    A (very) basic event handler looks like this :
    LRESULT CALLBACK MyEventHandler (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
     {
      HDC deviceContext;          // used for graphics, more on this in a later section
      PAINTSTRUCT paintstruct;    // used for graphics too...
    
      switch(msg)
          {
           
           case WM_CREATE:
               {
                //window is now created, you could initialize your application here.
                
                return(0); 
                 } break;
                 
           case WM_PAINT:
               {
                // window needs repainting. For now, don't understand - just accept ;) 
                deviceContext = BeginPaint(hwnd,&paintstruct);
                EndPaint(hwnd, &paintstruct);
                
                return(0); 
                } break;
                
           case WM_DESTROY:
               {
                PostQuitMessage(0);  // send a WM_QUIT which we handle in our main loop!!
                
                return(0); 
                } break;
                
           default : break;  // do nothing if we don't recognize the message..
           
           } // switch
           
       return(DefWindowsProc(hwnd, msg, wparam, lparam)); // let windows look at messages
                                                          // we didn't handle.
       } //end MyEventHandler.
    
    The event handler above really doesn't do anything - but once we get into Direct-X we will actually use an event handler somewhat like this one!!
    The main loop of our program looks like this :
      while(TRUE)  // run forever - or until we manually break out of the loop!
       {
        if (PeekMessage(&msg, NULL,0,0,PM_REMOVE)) // check if a message awaits and remove 
                                                   // it from the queue if there is one..
               {
                 if (msg.message == WM_QUIT) break; // exit loop if a quit message
                 
                 TranslateMessage(&msg); //Converts the message to a format used in the
                                         //event handler
                 
                 DispatchMessage(&msg); // THIS function sends the message to the event
                                        // handler
                 }
               
         // Here you are free to do anything not related to messages - like the inner loop
         // of a game or something like that.
         
       } // end while(TRUE)
    
    This is where the demo program comes into the picture. It does nothing but create a window on the screen and close it when the close-button is pressed - but it is created solely from the code we have discussed so far in this tutorial.
    It can be used as a code "skeleton" or "frame" which MUST surround EVERY Windows program you make! Yes, it really DOES take that much code to even get started with a program in Windows!!

    In the next section I'll cover the WM_PAINT message in detail and show you how to do graphics in your Windows application using the build-in GDI - Graphic Device Interface!

    I will NOT cover this theory in a second demo program, but after completing this tutorial you should be able to understand whats going on there and make that yourself.



    The GDC / GDI :

    Ok, so what exactly ARE these GDC / GDI things I have been talking about all the time. Well, GDI is short for 'Graphics Device Interface' and its an API that Windows provides for all graphics manipulation of the screen (not counting DirectDraw of course).
    The GDI operates through a GDC which is short for 'Graphics Device Context'. The GDC is a pointer to a structure with all sorts of information about the system Windows is currently running on - such as screen resolution, bit-depth etc.

    Most functions in the GDI needs to have a GDC passed on to them as a parameter to operate correctly.

    Remember that Windows message I mentioned before - WM_PAINT? As I said, it's a message that Windows sends to an application whenever its windows needs to be repainted. The reason for the message could be that the window was resized, restored from minimized state or perhaps another window (possibly belonging to a totally independent application) was moved in front of our window and then moved to reveal our window again. In all those cases Windows realize that the contents of the window needs repainting - but it has no clue as for what to put in them, so it sends a message to the application.

    In the simple event handler from demo program 1 we just did the following to handle the message :
      
       deviceContext = BeginPaint(hwnd,&paintstruct);
       EndPaint(hwnd, &paintstruct);   
    
    Between those two lines we could have used the deviceContext we retrieve for doing all sorts of graphics with the GDI - like drawing points, lines, text, bitmaps, polygons etc. You might wonder why we didn't just left the WM_PAINT handler empty - why retrieve the deviceContext just to release it again in the next line? Well, messages can get lost so we need a way to tell Windows that we caught the message and dealt with it. EndPaint does exactly that which is why we can't leave it out of our message handler.
    Another way of getting a Device Context (DC from now on) is with the following function call :
      deviceContext = GetDC(main_window);
    
    After you are done with the DC you release it to Windows with :
      ReleaseDC(main_window, deviceContext);
    
    It is VERY important that you ALWAYS remember to release a DC once you are done with it. There are only a limited number of DC's available in Windows so if you don't release them you'll end up not being able to get one when you need one.



    Drawing graphics with the GDI :

    So, now its time to draw some graphics in our application window. It sounds easy enough huh? We just squeeze a couple of GDI calls in between or BeginPaint/EndPaint calls in the WM_PAINT message handler! True enough, we don't KNOW any GDI calls yet, but that HAS to be easy right?
    Well, for the most part you are right! There is a catch though. Windows talks about 'Invalid rectangles' and about 'Validating the rectangle'. The invalid rectangle is the part of the window which has been disturbed. This is often not the whole window - for example another application could overlap only the lower left part of our application. Often when programming graphic intensive applications (which I assume most of my readers are interested in) we don't bother updating only PART of the screen/window. So we want to update the whole window each time we receive a WM_PAINT message. The problem is that we can't just do that. Windows automatically clips all graphics drawn outside the invalid rectangle. Fortunately it's possible to invalidate an area ourselves. The following function call invalidates the entire window :
      InvalidateRect(main_window, NULL, TRUE);
    
    The NULL parameter is actually a pointer to a structure of type RECT which defines the area to invalidate. The fields in RECT are : rect.top, rect.bottom, rect.left and rect.right. When we pass a NULL pointer the function invalidates the whole window. The boolean value we pass on as the last parameter tells whether or not Windows should clear the background of the window next time BeginPaint is called.

    So, if we want to repaint the entire window each time we receive a WM_PAINT message our handler must look like this :
      InvalidateRect(main_window, NULL, TRUE);
      deviceContext = BeginPaint(hwnd,&paintstruct);
      
      // add GDI calls here
      
      EndPaint(hwnd, &paintstruct);   
    
    Well, lets get something on the screen - shall we? Lets start out with one of the things GDI is actually useful for even when we switch to DirectDraw; Printing out text! In DirectDraw you are back in the old DOS days - if you want text you gotta make a bitmap font, load that to memory and handle drawing of the text string yourself, pixel by pixel. GDI is cool because it's device independent which means that it'll work in all resolutions and in all bit depths. That is also the thing that makes it very slow, but for drawing text that is normally OK.

    We draw text strings with the TextOut function. Its prototype looks like this :
      TextOut(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);
    
    The bad thing is that it only handles static strings so we'll have to format it with sprintf() if we want to use variables in it like this :
      int length = sprintf(target_buffer, "X position is %i", xpos); .  
    
    cbString is the length of the string which you can either get as return value from sprintf (as shown above) or with the function strlen() if you are drawing a static string with no variables.



    Lets see some shapes :

    The most simple shape you can imagine is the pixel. If you can draw a pixel, you can draw anything.
    The function to plot a pixel looks like this :
      COLORREF SetPixel(HDC hdc, int x, int y, COLORREF crColor);
    
    Notice that the SetPixel function RETURNS a color. That is the color which was actually plotted to the screen. This might sound stupid (and quite alarming - if you tell Windows to plot a pixel you would expect it to actually plot it with the color you told it to use right?) but remember GDI is device independent. So if you try and plot a pixel with some RGB value but Windows currently is running 16 color VGA then Windows will have to select the best color available and use that.
    COLORREF is a structure containing information about the RGB values for a color. It is most easily set by the macro RGB(r,b,g). So to set a pixel we do :
     COLORREF actual_color = SetPixel(deviceContext, 25,25, RGB(255,0,0));
    
    or just :
     SetPixel(deviceContext, 25,25, RGB(255,0,0));
    
    if we are not interested in the exact color actually drawn.

    Even though pixels can draw anything it's nice to have functions to draw common shapes like lines, circles etc. And the GDI gives us that.

    To draw such complex shapes we need a 'pen' to draw with. A pen has a color, a width and a style and is created with the CreatePen function call :
     HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor);
    
    The fnPenStyle is (yet another) flag which determines the style of the pen. It can be :
      PS_SOLID	: for a normal solid line
      PS_DASH	: guess
      PS_DOT	: guess
      PS_DASHDOT	: changes between a dash and a dot
      PS_DASHDOTDOT	: no, I'm not kiddin' you :)
      PS_NULL	: invisible pen
    
    COLORREF is a structure containing information about the RGB values for a color. It is most easily set by the macro RGB(r,b,g). So to create a green pen we do :
      HPEN green_pen = CreatePen(PS_SOLID, 0, RGB(0,255, 0));
    
    To use it you have to select it. The SelectObject function does the trick, and it also returns the current selected pen if you should want to restore it after you are done with the new pen :
     
      HPEN old_pen = SelectObject(deviceContext, green_pen);
    
    To clean up you delete a pen with DeleteObect(green_pen);

    The pen is only used for the outline of the shape though - we also need to create a 'brush' to fill the shape with. We will only look at brushes of constant color because if we want to use bitmaps and stuff like that we'll use DirectDraw.

    A brush is created like this :
      HBRUSH green_brush = CreateSolidBrush(RGB(0,255,0));
    
    The brush must also be selected in a similar way as with the pen :
      HBRUSH old_brush = SelectObject(deviceContext, green_brush);
    
    Also remember to delete a brush once you are done with it. But DON'T try to delete a brush that is currently selected - Windows will CRASH!!!

    To draw lines you have to do 2 things. First you have to move an invisible cursor to the starting point of the line :
      MoveToEx(deviceContext, xStart, yStart, NULL);
    
    Then draw the line :
      LineTo(deviceContext, xEnd, yEnd);
    
    The line is drawn with the currently selected pen.br> You draw a rectangle in like this :
      Rectangle(deviceContext, xTopLeft, yTopLeft, xBottomRight, yBottomRight);
    
    A rectangle is drawn using the currently selected pen for the outline, and the currently selected brush for the fill color.

    There are of course many more GDI function calls which can draw graphics, but I'll not describe more of them. Look them up in your favorite win32 programming reference if you need them, but you'll end up doing most of your graphics in DirectDraw anyway.



    Lets get some input to our program!

    I'll keep this section short as this is actually really simple. Lets start with the keyboard. First I'll introduce you to a line of serious dark mojo that you just add to the top of your program. (Seriously it's not really that mystic but I don't think it's worth explaining in greater detail)

    #define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)

    Basicly this will let you use the KEY_DOWN as a boolean function in your program which returns information about keystates. the parameter vk_code is short for "virtual key code" and can look like this : VK_F1, VK_F2, VK_LEFT, VK_RIGHT, VK_ESCAPE etc. etc. Look them up at msdn.microsoft.com.

    Now anywhere in your code you can write :
    if (KEY_DOWN(VK_LEFT)) {
      .
      .
      // code to move player left
      .
      .
    }
    
    now, is that simple or not ??

    The mouse is also easy to handle because by now you know everything about the event handler, right ? ;)
    Actually you can get by with only writing one single extra case for your event handler because everytime the mouse moves the message WM_MOUSEMOVE is sent and that message contains information on both mouse position and button state.

    I think I'll just show you the code because by now it should be fairly obvious stuff for you to understand :
    CASE WM_MOUSEMOVE {
    	int mouse_x = (int)LOWORD(lParam);
    	int mouse_y = (int)HIWORD(lParam);
    	
    	int buttons = (int)wParam;
    	
    	
    	// do something here. Often you will only want to do something if a button
    	// was clicked. Lets check for that :
    	
    	if (button & MK_LBUTTON) { // left button
    	   .
    	   .
    	   // shoot or something
    	   .
    	   .
    	}
    	
    	if (button & MK_RBUTTON) { // right button
    	   .
    	   .
    	   // move maybe..
    	   .
    	   .
    	}
    	
    	return (0); // homework : why this ??
    } break;
    
    You can also catch messages for mousepresses if you want. For example the message WM_LBUTTONDBLCLICK (checks left button doubleclick which might be useful), WM_LBUTTONDOWN (left button is down) and many more.

    In each of these messages you can also get the mouse position exactly as in the more general WM_MOUSEMOVE message - it's even the same code.



    Last remarks (some things never change ;)

    Well, that's about all for now.
    Hope you found this doc useful - and BTW : If you DO make anything public using these techniques please mention me in your greets or where ever you se fit. I DO love to see my name in a greeting :=)

    What should you use this tutorial for ? Well, to be honest - NOT for game programming. Still, it DOES give you an advantage knowing a bit more about whats going on inside the depth of Windows. Even when you start programming in DirectX you will encounter things like the message handler and you might even want to draw text using the GDI at some point. The KEY_DOWN macro is useful in any windows program and while DirectInput might be better this macro is so easy to implement and use that I'm certain you'll find good use for it until you find the time to explore DirectInput.

    My next tutorial will come out shortly and will take a HUGE step upwards in complexity. It'll be on the subject of collision detection in an arbitrary 3d world of triangles so don't think of it as a follow-up for this tutorial ;)

    Keep on codin'
    Telemachos
     September, 2000