Home  Contents

First steps in Windows API

In this part of the Windows API tutorial, we will create some simple examples.

Simple program

Here is a very simple program. It will pop up a small dialog box.

#include <windows.h>

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    PWSTR pCmdLine, int CmdShow)
{
  MessageBoxW(NULL, L"First Program", L"First", MB_OK);

  return 0;
}

A small dialog box is shown on the screen. It has a caption, message, and an OK button.

#include <windows.h>

We include the basic function declarations, constants, data types, and structures.

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    PWSTR pCmdLine, int CmdShow)

The wWinMain() function is an entry point to our application.

MessageBoxW(NULL, L"First Program", L"First", MB_OK);

The MessageBoxW() function displays a simple message box. The first parameter is the owner window. In our case, the dialog box has no owner. The next two parameters provide the message text and the caption. The last parameter defines the message dialog type. We have a dialog box with one OK button.

Simple message box
Simple message box

Centering a window

In the next code example, we will center the window on the screen.

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CenterWindow(HWND);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR pCmdLine, int nCmdShow)
{
  MSG  msg;    
  WNDCLASSW wc = {0};
  wc.lpszClassName = L"Center";
  wc.hInstance     = hInstance;
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
  wc.lpfnWndProc   = WndProc;
  wc.hCursor       = LoadCursor(0, IDC_ARROW);
  
  RegisterClassW(&wc);
  CreateWindowW(wc.lpszClassName, L"Center",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                100, 100, 250, 150, 0, 0, hInstance, 0);  

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

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam)
{
  switch(msg)  
  {
      case WM_CREATE:
      {
          CenterWindow(hwnd);
          return 0;
      }

      case WM_DESTROY:
      {
          PostQuitMessage(0);
          return 0;
      }
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void CenterWindow(HWND hwnd)
{
    RECT rc;
    
    GetWindowRect(hwnd, &rc) ;
    
    SetWindowPos(hwnd, 0, 
        (GetSystemMetrics(SM_CXSCREEN) - rc.right)/2,
        (GetSystemMetrics(SM_CYSCREEN) - rc.bottom)/2,
         0, 0, SWP_NOZORDER|SWP_NOSIZE);
}

In order to center a window on the screen, we need to have the dimensions of the window and of the screen.

case WM_CREATE:
{
    CenterWindow(hwnd);
    return 0;
}

We call the user defined CenterWindow() function during the WM_CREATE message.

GetWindowRect(hwnd, &rc) ;

With the GetWindowRect() function, we retrieve the dimensions of the bounding rectangle of the specified window.

SetWindowPos(hwnd, 0, 
    (GetSystemMetrics(SM_CXSCREEN) - rc.right)/2,
    (GetSystemMetrics(SM_CYSCREEN) - rc.bottom)/2,
     0, 0, SWP_NOZORDER|SWP_NOSIZE);

The SetWindowPos() method positions a window on the screen. The GetSystemMetrics() function is used to get the width and height of the screen.

More Windows

A window is created from a specific window class. A window class defines a set of behaviours that several windows might have in common. Some classes are already predefined in the system. A custom window class must be registered. After that, we can create windows of this new window class. A window is created using by calling the CreateWindowW() function. Its first parameter is the window class name.

Each window has a window procedure. It is a function that is called by the OS, when users interact with the window. In the following example, we create three windows. One parent window and two child windows.

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK PanelProc(HWND, UINT, WPARAM, LPARAM);

void RegisterRedPanelClass(void);
void RegisterBluePanelClass(void);


int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
			              PWSTR lpCmdLine, int nCmdShow)
{
  MSG  msg;    
  WNDCLASSW wc = {0};
  wc.lpszClassName = L"ColorWindows";
  wc.hInstance     = hInstance;
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
  wc.lpfnWndProc   = WndProc;
  wc.hCursor       = LoadCursor(0, IDC_ARROW);
  
  RegisterClassW(&wc);
  CreateWindowW(wc.lpszClassName, L"ColorWindows",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                100, 100, 250, 180, 0, 0, hInstance, 0);  

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

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam)
{    
  switch(msg)  
  {
    case WM_CREATE:

      RegisterRedPanelClass();

      CreateWindowW(L"RedPanelClass", NULL, 
        WS_CHILD | WS_VISIBLE,
        20, 20, 80, 80,
        hwnd, (HMENU) 1, NULL, NULL);

      RegisterBluePanelClass();

      CreateWindowW(L"BluePanelClass", NULL, 
        WS_CHILD | WS_VISIBLE,
        120, 20, 80, 80,
        hwnd, (HMENU) 2, NULL, NULL);

    break;

    case WM_DESTROY:
    
      PostQuitMessage(0);
      return 0; 
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK PanelProc( HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam )
{
  switch(msg)  
  {
    case WM_LBUTTONUP:
    
      Beep(50, 40);
      break;    
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void RegisterRedPanelClass(void) 
{
  HBRUSH hbrush = CreateSolidBrush(RGB(255, 0, 0));

  WNDCLASSW rwc = {0};
  rwc.lpszClassName = L"RedPanelClass";
  rwc.hbrBackground = hbrush;
  rwc.lpfnWndProc   = PanelProc;
  rwc.hCursor       = LoadCursor(0, IDC_ARROW);
  RegisterClassW(&rwc); 
}

void RegisterBluePanelClass(void) 
{
  HBRUSH hbrush = CreateSolidBrush(RGB(0, 0, 255));

  WNDCLASSW rwc = {0};
  rwc.lpszClassName = L"BluePanelClass";
  rwc.hbrBackground = hbrush;
  rwc.lpfnWndProc   = PanelProc;
  rwc.hCursor       = LoadCursor(0, IDC_ARROW);

  RegisterClassW(&rwc);
}

We have an application window with two child windows. The two child windows have blue and red backgrounds.

HBRUSH hbrush = CreateSolidBrush(RGB(255, 0, 0));
...
rwc.hbrBackground = hbrush;

To create a coloured window background, we create a custom solid brush by calling the CreateSolidBrush() function. To specify a colour, we use the RGB macro. As we know, any colour can be created by combining red, green a blue colours. Then we set the hbrBackground parameter of the window class structure to this newly created brush.

RegisterRedPanelClass();

CreateWindowW(L"RedPanelClass", NULL, 
  WS_CHILD | WS_VISIBLE,
  20, 20, 80, 80,
  hwnd, (HMENU) 1, NULL, NULL);

First we register a new window class. After this step, we create a window of this class.

Both of our child windows share the PanelProc window procedure. This procedure is called by the Windows OS when we interact with it.

case WM_LBUTTONUP:

  Beep(50, 40);
  break;    

We interact with our child windows, when we click on them. By left clicking on the child window, the Windows OS calls the child window procedure and sends a WM_LBUTTONUP message. In our example, we call the Beep() function. If we left click on the background of the two child windows, we hear a beep sound.

void RegisterRedPanelClass(void) 
{
  HBRUSH hbrush = CreateSolidBrush(RGB(255, 0, 0));

  WNDCLASSW rwc = {0};
  rwc.lpszClassName = L"RedPanelClass";
  rwc.hbrBackground = hbrush;
  rwc.lpfnWndProc   = PanelProc;
  rwc.hCursor       = LoadCursor(0, IDC_ARROW);
  RegisterClassW(&rwc); 
}   

This function registers a new window class. Windows of this window class type have red backgrounds. The edit, button, or static controls are created from predefined window classes, which are already available to all processes. So in these cases we do not need to register a window class for them.

More windows
Figure: More windows

The escape key

Applications are often terminated by pressing the escape key. A message box is also shown to confirm the termination.

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    PWSTR pCmdLine, int CmdShow)
{
  MSG  msg;    
  WNDCLASSW wc = {0};
  wc.lpszClassName = L"Escape";
  wc.hInstance     = hInstance;
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
  wc.lpfnWndProc   = WndProc;
  wc.hCursor       = LoadCursor(0, IDC_ARROW);
    
  RegisterClassW(&wc);
  CreateWindowW(wc.lpszClassName, L"Escape",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                100, 100, 250, 180, 0, 0, hInstance, 0);  

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

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam )
{    
  switch(msg)  
  {
    case WM_KEYDOWN:

      if (wParam == VK_ESCAPE) {
          int ret = MessageBoxW(NULL, L"Are you sure to quit?", 
                                  L"Message", MB_OKCANCEL);
          if ( ret == IDOK) {
    	        SendMessage(hwnd, WM_CLOSE, 0, 0);
          }
      }
      break;

    case WM_DESTROY:

        PostQuitMessage(0);
      break; 
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

It is a common practice to ask a user if he really wants to close an application. If we have a clock or a calculator than it does not matter that much. But if we have a text editor or a drawing application, it does matter. We might accidentally press the escape key and loose all our modifications.

case WM_KEYDOWN:

  if (wParam == VK_ESCAPE) {
      int ret = MessageBoxW(NULL, L"Are you sure to quit?", 
                              L"Message", MB_OKCANCEL);
      if ( ret == IDOK) {
	        SendMessage(hwnd, WM_CLOSE, 0, 0);
      }
  }
  break;

If we press a key, the window procedure receives a WM_KEYDOWN message. The wParam parameter has a key code. We can close the window by sending a WM_CLOSE message.

Moving a window

When we move a window on the screen, the window procedure receives the WM_MOVE message. In our example we display the current window position on the screen.

#include <windows.h>
#include <wchar.h>


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateLabels(HWND);

HWND hwndSta1;
HWND hwndSta2;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    PWSTR pCmdLine, int CmdShow)
{
  HWND hwnd;
  MSG  msg;

  WNDCLASSW wc = {0};
  wc.lpszClassName = L"Moving";
  wc.hInstance     = hInstance ;
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
  wc.lpfnWndProc   = WndProc;
  wc.hCursor       = LoadCursor(0, IDC_ARROW);
  
  RegisterClassW(&wc);
  hwnd = CreateWindowW(wc.lpszClassName, L"Moving",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                150, 150, 250, 180, 0, 0, hInstance, 0);
		
  CreateLabels(hwnd);

  while( GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam)
{

  wchar_t buf[10];
  RECT rect;

  switch(msg)  
  {
    case WM_MOVE:
      GetWindowRect(hwnd, &rect);

      _itow(rect.left, buf, 10);
      SetWindowTextW(hwndSta1, buf);

      _itow(rect.top, buf, 10);
      SetWindowTextW(hwndSta2, buf);

      break;

    case WM_DESTROY:
      PostQuitMessage(0);
      break; 
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void CreateLabels(HWND hwnd){

  CreateWindowW(L"static", L"x: ",
      WS_CHILD | WS_VISIBLE,
      10, 10, 25, 25, 
      hwnd, (HMENU) 1, NULL, NULL);

  hwndSta1 = CreateWindowW(L"static", L"150",
      WS_CHILD | WS_VISIBLE,
      40, 10, 55, 25, 
      hwnd, (HMENU) 2, NULL, NULL);

  CreateWindowW(L"static", L"y: ",
      WS_CHILD | WS_VISIBLE,
      10, 30, 25, 25, 
      hwnd, (HMENU) 3, NULL, NULL);

  hwndSta2 = CreateWindowW(L"static", L"150",
      WS_CHILD | WS_VISIBLE,
      40, 30, 55, 25, 
      hwnd, (HMENU) 4, NULL, NULL);
}

Normally, we would create the static text controls during the WM_CREATE message. This was not possible. Because the window is being moved during the creation and we receive error messages. We wanted to access a window that did not exist yet. That is why we put the creation of the static text windows in a separate function, which is called immediately after the main window is created.

void CreateLabels(HWND hwnd){

  CreateWindowW(L"static", L"x: ",
      WS_CHILD | WS_VISIBLE,
      10, 10, 25, 25, 
      hwnd, (HMENU) 1, NULL, NULL);

  hwndSta1 = CreateWindowW(L"static", L"150",
      WS_CHILD | WS_VISIBLE,
      40, 10, 55, 25, 
      hwnd, (HMENU) 2, NULL, NULL);

  CreateWindowW(L"static", L"y: ",
      WS_CHILD | WS_VISIBLE,
      10, 30, 25, 25, 
      hwnd, (HMENU) 3, NULL, NULL);

  hwndSta2 = CreateWindowW(L"static", L"150",
      WS_CHILD | WS_VISIBLE,
      40, 30, 55, 25, 
      hwnd, (HMENU) 4, NULL, NULL);
}

There are four static text controls. Two of them change during the life of the application. So we need only two handles.

case WM_MOVE:
  GetWindowRect(hwnd, &rect);

  _itow(rect.left, buf, 10);
  SetWindowTextW(hwndSta1, buf);

  _itow(rect.top, buf, 10);
  SetWindowTextW(hwndSta2, buf);

  break;

To get the window coordinates, we call the GetWindowRect() function. The coordinate is a number. We must convert the number to characters. To accomplish this, we use the _itow() function call.

Moving a window
Figure: Moving a window

Flashing a window

Sometimes when an important event happens, the title bar, or the taskbar button start to flash. The flashing is the change of the title bar from inactive status to active status and vice versa. This is a common feature in Miranda IM, when we receive a new message.

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow)
{
  MSG  msg;    
  WNDCLASSW wc = {0};
  wc.lpszClassName = L"Flash";
  wc.hInstance     = hInstance;
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
  wc.lpfnWndProc   = WndProc;
  wc.hCursor       = LoadCursor(0,IDC_ARROW);

  RegisterClassW(&wc);
  CreateWindowW(wc.lpszClassName, L"Flash",
               WS_OVERLAPPEDWINDOW | WS_VISIBLE,
               100, 100, 250, 180, 0, 0, hInstance, 0);

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

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam )
{
  FLASHWINFO fwi;

  switch(msg)  
  {
      case WM_CREATE:

          CreateWindowW(L"Button", L"Flash",
                  WS_CHILD | WS_VISIBLE,
                  10, 10, 80, 25, 
                  hwnd, (HMENU) 1, NULL, NULL);
          break;

      case WM_COMMAND:

          fwi.cbSize = sizeof(fwi);
          fwi.dwFlags = FLASHW_ALL;
          fwi.dwTimeout = 0;
          fwi.hwnd = hwnd;
          fwi.uCount = 4;

          FlashWindowEx(&fwi);
          break;

      case WM_DESTROY:

          PostQuitMessage(0);
          break; 
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

In order to flash a window, we must do two steps. Create and fill a FLASHWINFO structure and call the FlashWindowEx() function.

fwi.dwFlags = FLASHW_ALL;

We have set the FLASHW_ALL flag. This will flash both the title bar and the taskbar button. To flash only the titlebar, we use FLASHW_CAPTION. To flash the taskbar button, we can use the FLASHW_TRAY flag.

fwi.dwTimeout = 0;

The dwTimeout member is the rate at which the window is to be flashed, in milliseconds. If dwTimeout is zero, the function uses he default cursor blink rate.

fwi.hwnd = hwnd;
fwi.uCount = 4;

Here we set which window to flash and how many times we want to flash it. In our case, we will flash the main window four times.

FlashWindowEx(&fwi);

This function call actually starts the flashing.

In this part of the Windows API tutorial, we have created some simple examples.