Creating Your First Window

  CU_WindowCreation.zip (12.6 KiB, 5,698 hits)

Before we can create anything with DirectX, we first need to know how to create a window. To do this, we follow a few basic steps:

  1. Define and register a window class that describes the window we want to make.
  2. Create the window.
  3. Create the loop that will read messages and update the window based on input or game logic.
  4. Create an event handler, or window procedure, that responds to all events sent by the window (moving, resizing, etc…)

The following is basic Windows programming. So learn it once, then copy and paste it forever.

#include “stdafx.h” 
#include “WinMain.h”
#define WINDOW_TITLE “Creating a Window” 
#define WINDOW_WIDTH 300 
#define WINDOW_HEIGHT 300

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR cmdLine, int cmdShow) {
  // Define the window 
  WNDCLASSEX wcex; 
  wcex.cbSize = sizeof(WNDCLASSEX); 
  wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 
  wcex.lpfnWndProc = WndProc; 
  wcex.cbClsExtra = 0; 
  wcex.cbWndExtra = 0; 
  wcex.hInstance = hInstance; 
  wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CUNIT)); 
  wcex.hCursor = LoadCursor(hInstance, IDC_ARROW); 
  wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 
  wcex.lpszMenuName = NULL; 
  wcex.lpszClassName = WINDOW_TITLE; 
  wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CUNIT));

  // Register the window 
  RegisterClassEx(&wcex);
  
  // Create the window 
  HWND hWnd = CreateWindow(WINDOW_TITLE, WINDOW_TITLE, WS_OVERLAPPEDWINDOW,
                           CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
                           WINDOW_HEIGHT, 0, 0, hInstance, 0 );
  
  // Adjust to actual desired size 
  RECT rect = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT }; 
  AdjustWindowRect(&rect, GetWindowLong(hWnd, GWL_STYLE), FALSE); 
  SetWindowPos(hWnd, 0, 0, 0, rect.right – rect.left,
               rect.bottom – rect.top, SWP_NOZORDER | SWP_NOMOVE );
  
  ShowWindow(hWnd, SW_SHOW); 
  UpdateWindow(hWnd);
  
  // Main loop 
  MSG msg; 
  while (1) {
    // Only render when there are no messages 
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
      if (msg.message == WM_QUIT) {
        break;
      } 
      TranslateMessage( &msg ); 
      DispatchMessage ( &msg );
    } else {
      // Render a frame
    }
  }
  return 0;
}

The WinMain function is the entry point of all Windows programs. If you’ve programmed a console application, this is the same as the Main function. First, we need to define the window class by filling out a WNDCLASSEX structure. This structure tells Windows all the properties of the window that we want to create. Check out MSDN for the details. Basically, this is where you can specify such things as a menu for the window as well as a custom icon. The lpfnWndProc member is set to a function pointer that we will write later on.

Registering the class is very simple. All we do is pass the address of our WNDCLASSEX structure we filled out as a parameter to the function, RegisterClassEx. Registering the window class tells Windows which WNDCLASSEX structure to use when we call CreateWindow.

With the window class defined and registered we can now create the window with CreateWindow. This is where we can specify the size and position of the window along with some basic window styles. The style flag I used, WS_OVERLAPPEDWINDOW, gives the window a border, title bar, close box, minimize box, and allows the window to be resized.

Usually when we specify a width and height, we intend those dimensions to form the rectangle that we will be drawing on, known as the client area. However, the window that is created thus far includes the pixels of the title bar and also the window menu if there was one. Therefore, we need to adjust the client area of the window to account for these additions. We can find what the client area needs to be by calling AdjustWindowRect. Using the adjusted RECT structure, we can update the dimensions of the window by calling SetWindowPos.

SetWindowPos

With the window all ready to go, we can display the window by calling ShowWindow and UpdateWindow.

The message loop is what continously updates the application until the user wants to quit. To be more efficient, we will only update and render a frame when there are no messages in the message queue. First we need to check if there are any messages that the window needs to take care of such as resizing, closing, etc. We check if there are any messages on the queue with PeekMessage. If there is a message, we test if the message is a quit message. If it’s not a quit message, we send the message off to our event handler by calling TranslateMessage and DispatchMessage. If there are no messages in the queue, we would update our game. Now we move on to the window procedure.

/**
  Application event handler. 

  @param[in] hWnd Unique handle to the window. 
  @param[in] message Incoming message. 
  @param[in] wParam Parameter of the message (unsigned int). 
  @param[in] lParam Parameter of the message (long). 
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  switch (message) { 
    case WM_DESTROY:
      PostQuitMessage(0); 
      return 0;
    case WM_PAINT:
      // Render a frame then validate the client area 
      ValidateRect(hWnd, 0); 
      return 0;
  } 
  return DefWindowProc(hWnd, message, wParam, lParam);
}

The window procedure, WndProc, is where we process all our messages. You can name this function whatever you want as long as you pass the same name to the WNDCLASSEX structure above. To process messages, just do a switch on the message to handle each case. There are a lot of possible messages, but usually you’ll just need a few. Here, I show how to process two messages: WM_DESTROY and WM_PAINT. The WM_DESTROY message is sent when the user closes a window. This usually means that the user wants to quit the program. Therefore, we need to tell the program to shutdown by calling PostQuitMessage. This puts a quit message onto the message queue so we can break from our loop in WinMain. The WM_PAINT message is sent whenever the window needs to repaint a portion of the client area, such as if another window is moved over our window. To handle this message, we would render our current frame and then call ValidateRect, which tells Windows that we’ve repainted the window. For all the messages that we didn’t handle, we send them off to let Windows deal with them with DefWindowProc.

That’s mostly all we need to know about Windows programming to start working with DirectX.