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