AngelCode.com > Developer resources

A simple window application
Andreas Jönsson, May 2000

Introduction

Since for almost all windows application you need to create a window and handle its messages, I have decided to write a small tutorial on just how you go about to do that. The application that I have created for this tutorial doesn't do anything fancy, it just sits there on the screen allowing the user to resize and move it.

Includes and libraries

All functions that we will use when creating our window is declared in windows.h so we need to include that before we can use them. There are however so much more declared in that header file that aren't needed. We can trim away some of that by defining WIN32_LEAN_AND_MEAN. This will give us a smaller header file and speed up the compilation.

// Don't include rarely used code to speed up compilation
#define WIN32_LEAN_AND_MEAN 

#include <windows.h>

Before we can build our window application though we need the implementation of the window functions as well. These implementations can be found in user32.lib and gdi32.lib so we must tell the linker to link with those.

The application's entry point

When a Win32 application starts the Windows operative system calls the WinMain() function. When this function returns the application is terminated. So we need to create our window in this function as well as handle its messages until we are ready to quit. To do this we will create two other functions Create() and Run(). Create() will create the window, while Run() will receive and handle the messages.

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, 
                   LPSTR lpCmdLine, int nShowCmd)
{
  // Create the window
  HRESULT hr;
  if( FAILED(hr = Create(hInst)) )
    return hr;

  // Run the application
  return Run();
}

Creating the window

Before we can create the window for our application we need to register a window class. This is not a C++ class, but a dataset that tells the OS how to treat the windows that use the window class. The window class is registered by initializing the WNDCLASS structure and then calling RegisterClass().

HRESULT Create(HINSTANCE hInst)
{
  // Register a window class
  WNDCLASS WndClass;
  
  WndClass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  WndClass.lpfnWndProc   = (WNDPROC)WndProc; 
  WndClass.cbClsExtra    = 0;                
  WndClass.cbWndExtra    = 0;                
  WndClass.hInstance     = hInst;
  WndClass.hIcon         = LoadIcon(NULL, IDI_WINLOGO); 
  WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW); 
  WndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
  WndClass.lpszMenuName  = NULL;             
  WndClass.lpszClassName = "Application Window"; 

  if( !RegisterClass( &WndClass ) )
    return E_FAIL;

The window procedure that is defined with lpfnWndProc is a function that we create to handle the messages sent to our window. I will tell you more about this function later as it isn't really part of the creation process. I use LoadIcon(), LoadCursor(), and GetStockObject() to fetch standard objects so I don't need to create them myself. If you would like to create them yourself you should do it with the resource manager in VC++ and then load them as resources.

Now that we have registered the window class we can create our window. In this application we will use CreateWindow() to do that, you can also use CreateWindowEx(). The difference between the two is that CreateWindowEx() have more options, one of which is that it allows you to position the window above all other windows so that it can cover the entire screen.

  // Create the render window
  HWND hWnd = CreateWindow(
    "Application Window",   // lpClassName
    "Test Window",          // lpWindowName
    WS_OVERLAPPEDWINDOW,    // dwStyle
    CW_USEDEFAULT,          // x
    CW_USEDEFAULT,          // y
    640,                    // nWidth
    480,                    // nHeight
    NULL,                   // hWndParent
    NULL,                   // hMenu
    hInst,                  // hInstance
    NULL );                 // lpParam
                   
  if( hWnd == NULL )
    return E_FAIL;

  // Show the window
  ShowWindow( hWnd, SW_SHOW );
  UpdateWindow( hWnd );

  return S_OK;
}

After the window has been created we show it to the user by calling ShowWindow() and UpdateWindow().

Running the application

While our application is running we must also retrieve and and handle every message that is sent to us from the OS. If we don't do this the user will be unable to give us any input, move the window, or even close it. To retrieve the messages we can use either PeekMessage() or GetMessage(). The major difference between the two is that GetMessage() doesn't return until it finds a message to retrieve, this allows us to free up precious CPU usage for other programs to use. PeekMessage() returns immediately wether there are any messages or not, this allows us to utilize the time between messages, for example to render a 3D scene. In this tutorial I will use GetMessage() since there is nothing to do between messages.

After the message has been retrieved it must be dispatched to our message handler, WndProc(). A simple call to DispatchMessage() does that. If we want we can translate the message into another before dispatching it. For example TranslateMessage() translates keyboard messages into character messages, which can be very useful if we want text input. TranslateAccelerator() translates the keyboard messages into menu commands, which might be useful if we have a menu. When we call a translator function the message isn't altered, the function merely inserts a new message with the translated meaning into the messagequeue so it can be retrieved later.

int Run()
{
  // Check messages until we receive a WM_QUIT message
  MSG  Msg;
  while( GetMessage( &Msg, NULL, 0U, 0U ) )
  {
    // Dispatch the message to the window procedure
    DispatchMessage( &Msg );
  }

  return Msg.wParam;
}

I forgot to mention that we must continue to check for messages until we receive a WM_QUIT message. Because GetMessage() returns zero when it retrieves a WM_QUIT message we can use it directly in the while statement. With PeekMessage() that returns zero when there are no messages to handle we cannot do that, instead we have to check the type of message that was retrieved which is done simply by comparing WM_QUIT with Msg.

Handling the messages

After the message has been dispatched, the OS will send it to our message handler, WndProc(). Here we will take care of the messages that we wish to do something special with. For all the others we call the default handler, DefWindowProc().

In this tutorial I only process the WM_DESTROY message, which is sent to the application just before the window is destroyed. When this message is received I call PostQuitMessage() which sends a WM_QUIT message to my window so that the application can close down. It is not forbidden to call that function from somewhere else in the program, but I think it is good practice to only call it when the WM_DESTROY message is received.

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg,
                         WPARAM wParam, LPARAM lParam)
{
  switch( uMsg )
  {
  case WM_DESTROY:
    // Tell the message loop to quit
    PostQuitMessage(0);
    return 0;
  }

  // Let the default window procedure handle 
  // any message that we don't care about
  return DefWindowProc( hWnd, uMsg, wParam, lParam );
}

There are of course a lot of other messages that can be received by the application, but I'll let you discover them as you need them.

Conclusion

I hope that I have been able to give you some insight as to how a window application works. If I haven't then I suggest you either ask me about what you didn't understand, or find another tutorial that can teach you better than this one. And please don't forget to look in the Win32 API docs.

Further reading

Platform SDK, Microsoft