Windows Primer

Introduction

Window Class

Creating a Window

Destroying Your Window

Processing Window Messages

The Message Pump

Lesson Downloads

Further Reading

MSDN Links For Functions/Concepts Introduced Here

Introduction

Not surprisingly, the central element in all Windows applications is the window. In the typical Windows application, every visible object is a window. Whether it's a button or an edit box, they are all windows.

The typical Direct3D application works a little bit differently. Most of them take over the display and primarily use the window to receive messages from the operating system. While these applications may interact with their window and the "standard" Win32 API much less, they still have play nicely with Windows.

There are 3 main components you need to learn to set up and handle your window within a Direct3D application:

  • window classes
  • windows
  • message handlers

Window Class

A window class is not a class in the C++ sense, though there are similarities. Essentially it is a template that defines certain attributes. Any window created from this template inherits those attributes. Here is a simplified version of the declaration of the window class structure:

struct WNDCLASS {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
};

Since the goal of this tutorial series is to teach Direct3D I won't go into exhaustive detail on this structure. I'll briefly cover the more important members and then provide a code snippet from the lesson example code to illustrate initializing the structure, creating the window class and finally destroying it.

style - This controls various window behaviours. The only style I typically use is CS_OWNDC, which is handy if you plan on using GDI with Direct3D.

lpfnWndProc - This is a pointer to the message handler procedure for windows of this class. We'll cover it in more detail later.

cbClsExtra and cbWndExtra - These allow you to allocate extra memory as part of the class and window. Rarely used.

hInstance - This is a handle to your application. You can obtain it by calling GetModuleHandle() and passing in NULL as the only parameter. Also, the instance handle is passed to WinMain so you can get it from there too.

hIcon, hCursor, hbrBackground - These set defaults for rendering the window's icon, cursor, and background. These aren't generally very important in Direct3D applications since we do so much custom rendering.

lpszMenuName - Used in the creation of menus.

lpszClassName - This gives the class a public name to identify it. A window can be created from this template by giving this name.

After the window class structure has been filled in, you register it with Windows via the RegisterClass function. From the example code, here is a code snippet showing the initialization of the window class structure, registering the class, and finally un-registering it

WNDCLASS window_class;
HINSTANCE instance=GetModuleHandle(NULL);

   window_class.style          = CS_OWNDC;
   window_class.cbClsExtra     = 0;
   window_class.cbWndExtra     = 0;
   window_class.hInstance      = instance;
   window_class.hIcon          = LoadIcon(NULL,IDI_APPLICATION);
   window_class.hCursor        = LoadCursor(NULL,IDC_ARROW);
   window_class.hbrBackground  = (HBRUSH)GetStockObject(BLACK_BRUSH);
   window_class.lpszMenuName   = NULL;
   window_class.lpszClassName  = "DH Class";
   window_class.lpfnWndProc    = p_wndproc;     //An external function to handle window messages



   //Register the class with windows

   if(!RegisterClass(&window_class)){
      //Handle the error here

   }

   // The rest of your application goes here.


   //Finally, when we're done with the class we call UnregisterClass:

   UnregisterClass("DH Class",instance);

NOTE:All windows created from a given class should be closed before you call UnregisterClass for that class.

Creating a Window

Now that we've created our window class, we can create our window. Here is the prototype for CreateWindow:

HWND CreateWindow(
   LPCTSTR lpClassName,
   LPCTSTR lpWindowName,
   DWORD dwStyle,
   int x, y,
   int nWidth, nHeight,
   HWND hWndParent,
   HMENU hMenu,
   HINSTANCE hInstance,
   LPVOID lpParam
);

Again, I'll go over the parameters briefly and then show a code example using the call.

lpClassName - This is the name of the class that you created in the previous step.

lpWindowName - The name associated with your window. If you have a title bar, this will be the text shown there.

dwStyle - Window styles can determine whether a window has a title bar, how it's borders are drawn and many other things. In a full screen application, WS_POPUP|WS_VISIBLE is commonly used since it creates a visible window without borders of any kind. For windowed applications can make use of a wider variety of styles, but WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE is a common style.

x and y - Position of the top-left corner of the window. Position (0,0) is the top-left of the screen.

nWidth and nHeight - Width and Height of the window in pixels.

hWndParent - Identifies the window that you would like to be the parent of the newly created window. Not generally used for Direct3D applications.

hMenu - Handle to a menu.

hInstance - This is a handle to your application. You can obtain it by calling GetModuleHandle() and passing in NULL as the only parameter. Also, the instance handle is passed to WinMain so you can get it from there too.

lpParam - Strange Windows voodoo. Leave it NULL unless you know what you're doing.

Easy, right? It looks like a lot just to create a window, but generally you'll re-use the same code to create the window class and window with only minor tweaks. A lot of this information is used by business-style applications to maintain hierarchies of windows and other bizarre things that people who wear suits care about. For virtually any Direct3D project the code provided in this lesson can be dropped in with only minor changes.

There's one other thing I should mention. If your application is going to take over the display, it doesn't generally matter how big the window is. You'll write to the whole display regardless. It's common practice to create the window to fill the entire desktop area. This gives the user a hint your application has started while you load your flashy into. It can also block mouse clicks from going to the desktop and causing havoc.

In a windowed-mode application you don't have to worry about any of that, just make it the size you need. It isn't quite as easy as it first appears. When you request the window size, that size includes the window borders and title bar. So while you may request a 640 pixel wide window you may only get 630 pixels that you can draw on, the rest are used by the borders. To deal with this we just adjust the window size. By calling GetClientRect we find out what client size we were given and then use that to calculate how much larger we need to make the actual window.So let's look at the creation code.


   if(is_app_fullscreen){
      //Query the system to see how big the desktop is

      window_width=GetSystemMetrics(SM_CXSCREEN);
      window_height=GetSystemMetrics(SM_CYSCREEN);
      style=WS_POPUP|WS_VISIBLE;
   }else{
      //In windowed mode, we just make the window whatever size we need.

      window_width=desired_width;
      window_height=desired_height;
      style=WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE;
   }
   p_window=CreateWindow("DH Class",   //name of our registered class

                         window_name,  //Window name/title

                         style,        //Style flags

                         0,            //X position

                         0,            //Y position

                         window_width, //width of window

                         window_height,//height of window

                         NULL,         //Parent window

                         NULL,         //Menu

                         instance,     //application instance handle

                         NULL);        //pointer to window-creation data


   if(!p_window){
      //handle error

   }

   if(is_app_fullscreen){
      ShowCursor(FALSE);
   }

   if(!p_fullscreen){
      GetClientRect(*p_window,&client_rect);
      MoveWindow(*p_window,
                 0, //Left edge

                 0, //Top Edge

                 window_width+(window_width-client_rect.right),    //New Width

                 window_height+(window_height-client_rect.bottom), //New Height

                 TRUE);
   }

Destroying Your Window

This part gets its own section because there are multiple ways to do it and I want to discuss both of them.

In standard Windows programming you don't directly destroy a window. You send it a message telling it to destroy itself. This is commonly done within the message handler function which is a callback function. So a callback function which is only indirectly called by your application sends a message telling your window to destroy itself. Most programmers who haven't used Windows before think this is a pretty odd thing to do. If you allocate the resource, you should deallocate it as well. If you consider the history behind the Win32 API you'll see that it does make sense. The Windows programming model is event-based. The application sits there sleeping until it gets sent message, then it goes back to sleep until it gets the next one.

While the event-driven model makes a lot of sense for business applications it isn't appropriate for most games. Since games loop constantly updating and rendering as fast as they can, they generally use the Windows model as little as possible. It's often convenient to pretend that when a full-screen application is running, it owns the machine. It's not true, but with a little bit of effort it's easy to maintain the illusion.

My preferred method is to destroy the window explicitly. It creates a nice linear flow for your application that visibly matches your code. Create the window, use the window, then destroy the window. Nice and easy. To be honest the 2 methods are essentially the same. One is less direct than the other but at the end of the day they do the same thing. It's more an issue of programming style than anything else. I'm not going to show the standard method. If you want to see how it works, check out the Further Reading section at the end of the lesson.

Because of the way the Windows works, your Window may not be destroyed immediately. There may be pending messages in the queue. So to be extra careful, we make sure the queue is empty. Here's the code:

MSG msg;

   DestroyWindow(my_window);
   //Clean up any pending messages

   while(PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)){
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

Processing Window Messages

At this point you know that when you create a window class you have to pass a pointer to a message handling function. We've dropped hints about the function and what is does. Now we're going to lay it all out for you. Like all of the other things we've covered in this lesson, we're only doing a brief overview of the subject. There are a staggering number of messages that can be sent to a window, and covering them would take up at least a chapter on its own. So we'll cover the bits that are most likely you concern you as you work with Direct3D.

Whenever an event occurs (user hits a key, a timer ends, etc) a message is sent to the window. This message is handed off to your message handler function which checks if it's a message that it knows how to handle. If it can't handle the message it passes it off to a default window procedure called DefWindowProc. To let Windows know that the message was handled you return 0.

Here's a simple message handler:

LRESULT CALLBACK default_window_proc(HWND p_hwnd,UINT p_msg,WPARAM p_wparam,LPARAM p_lparam){
   
   switch(p_msg){
      case WM_KEYDOWN:  // A key has been pressed, end the app

      case WM_CLOSE:    //User hit the Close Window button, end the app

      case WM_LBUTTONDOWN: //user hit the left mouse button

         g_app_done=true;
         return 0;
   }
   
   return (DefWindowProc(p_hwnd,p_msg,p_wparam,p_lparam));
   
}

p_hwnd is a window handle. If your application had multiple windows that all used the same window class, you could use this to determine which window got sent the message.

p_msg is the message type. The message types shown above are WM_KEYDOWN, WM_CLOSE, and WM_LBUTTONDOWN. Whenever your window is active and the user hits a key, a WM_KEYDOWN message is sent.

p_wparam and p_lparam are message-specific data. In the case of WM_KEYDOWN, the WPARAM holds the keycode that tells you what key was pressed while the LPARAM holds flags to indicate repeat counts and a variety of other things. Going back a few years, WPARAM was defined as a WORD (16-bit) and LPARAM was defined as a LONG (32-bit). Now they're both defined as 32-bits which is a little confusing. This is an example of lack of planning when designing software. Microsoft is much better at doing this sort of thing now, but it's the sort of thing that's a huge pain to go back and fix.

In our example here, we set a global flag to signal our application to exit when the user hits a key, clicks the left mouse button or clicks the window close button. We return 0 to indicate that we handled the message. Any message we don't handle is passed off to DefWindowProc which in most cases will do the right thing so you don't have to worry about it.

The Message Pump

Periodically you have to check the message queue for messages and dispatch them to be processed. This process is commonly referred to as the Message Pump. Even if there are no messages you are interested in processing, you still have to pump the message queue. If you don't, after a little while Windows will decide that your application has stopped responding. This is a bad thing.

There isn't much to processing the messages. PeekMessage checks for a message and gets it if there is one waiting. TranslateMessage converts the message to a locale-correct character message if appropriate. Finally, DispatchMessage sends the message of for processing. This is the simplified version, feel free to read up on them more if you want more detail (see the Further Reading section below).

Here is a common method of processing the message queue. This method processes all waiting messages in the queue before moving on to the remainder of your main loop. You should make sure that your code to process messages (in your message handler) is fast. If it isn't it will hold up the rest of your main loop and can cause horrible stuttering in your frame rate.

Here's the code for this method:

void dhMessagePump(void){
MSG msg;

   while(PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)){
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

}

If you need fast input handling, investigating DirectInput might be a better option. With the potential messages greatly reduced, the method you choose to handle your messages isn't very significant.

Lesson Downloads

Further Reading

Programming Windows, Fifth Edition by Charles Petzold
From Amazon:

MSDN Links For Functions/Concepts Introduced Here

Back