Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Dialog Based Win32 C Program, Step by Step

0.00/5 (No votes)
9 Aug 2011 2  
Writing a dialog based program using only pure Win32 C code
In this article, I’ll discuss how to use a dialog box as the main window for your program, step by step, from scratch. I’ll use Visual Studio 2008, but the steps should be similar for other Visual Studio versions, or even other IDEs.

1. Introduction

When writing pure Win32 programs, usually you see tutorials showing how to use “raw” windows, by filling a WNDCLASSEX structure, calling RegisterClassEx and then CreateWindowEx. This is explained in detail in Charles Petzold's classic Programming Windows book – a must-have for any Win32 programmer, let me say.

But sometimes you don’t need to create a new window entirely from scratch, a simple dialog box would fit your needs.

In this article, I’ll discuss how to use a dialog box as the main window for your program, step by step, from scratch. A dialog box resource can be quickly created – with labels, editboxes, and buttons – using any resource editor. Here I’ll use Visual Studio 2008, but the steps should be similar for other Visual Studio versions, or even other IDEs.

I’ll use pure Win32 C code to keep things as simple as possible: no MFC, no ATL, no WTL, or whatever. I’ll also use the TCHAR functions (declared in tchar.h, more information here) to make the code portable with ANSI and Unicode, and only functions that are both x86 and x64 compatible.

1.1. Program Structure

Our program will be composed of three files:

  • C source file – the source code we’ll effectively write, and the central theme of this article;
  • RC resource script – describes the dialog box resources, easily created by Visual Studio or any resource editor, or even by hand, and compiled with a resource compiler; and
  • H resource header – simply the macro constants used in the RC file to identify the resources, usually created automatically together with the RC script.

2. The Dialog Box

Before writing the C source code, we’ll create an empty project and add a dialog box resource to it. When doing so, a resource script is created, containing the dialog box code. Let’s start a new project:

Image 1

Choose “Visual C++” and “Win32” from the tree in the left, then “Win32 project”, and give a name to it. Pay attention to the directory you are saving it. Then click OK:

Image 2

Now choose “Windows application” and “Empty project”. When creating an empty project, Visual Studio will create no files for us, and this is important because here we want to create a pure Win32 program, with no additional libraries. Then, click “Finish”:

Image 3

Now, let’s add the dialog box. In the Solution Explorer window – if you can’t see it, enable it in the “View” menu – right-click the project name and choose “Add”, “Resource”:

Image 4

Here you can see a couple of resource items whose script can be generated automatically by Visual Studio. We’ll use just the dialog box, so choose “Dialog” and click “New”:

Image 5

Once done, you should see your dialog in the resource editor, where you can add controls – like editboxes, buttons, and labels – by just using the mouse, positioning and arranging them really quick – much quicker than you would do with a “raw window” application, where you must deal with the code directly. My dialog looks like this:

Image 6

At this point, we have a resource script and a resource header, they can be seen in the Solution Explorer. Now it’s time to write the source code to bring this dialog box alive.

3. The Source Code

Let’s add an empty source file to our project. In the Solution Explorer, right-click the “Source Files” folder, then “Add”, “New Item”. Then give any name to the file, like “main.c”.

Image 7

In Visual Studio, by default, the source files will be compiled according to the file extension: C files compiled as plain C; and CPP, CXX (and some others) compiled as C++. Here we’ll write C code, but it can also be compiled as C++, so the file extension can be any of those cited. Particularly, I used the C extension, to make it clear it’s a plain C program.

Our C source will have only two functions:

  • WinMain – the program entry point, which will have the main program loop; and
  • DialogProc – the dialog box procedure, which will process the dialog messages.

Let’s start writing the code with the normal Win32 entry point function (the TCHAR version of it):

#include <Windows.h>
#include <tchar.h>

/* usually, the resource editor creates this file to us: */
#include "resource.h"

int _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPCTSTR lpCmdLine, int nCmdShow)
{
  return 0;
}

4. Dialog Creation and Message Loop

The dialog will be created inside the WinMain function with the CreateDialogParam function (instead of CreateWindowEx), and there is no window class registration. Then, we make it visible with a call to ShowWindow:

HWND hDlg;
hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
ShowWindow(hDlg, nCmdShow);

IDD_DIALOG1 is the resource identifier to our dialog box, declared in resource.h. DialogProc is our dialog box procedure, which will handle all dialog messages – I’ll show it later on.

Then it follows the main program message loop. It’s the heart of any Win32 program – see it as the bridge between the operational system and your program. It also exists in common “raw window” programs, although slightly different from this. Here, the message loop is specifically to deal with a dialog box as the main window:

BOOL ret;
MSG msg;
while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
  if(ret == -1) /* error found */
    return -1;

  if(!IsDialogMessage(hDlg, &msg)) {
    TranslateMessage(&msg); /* translate virtual-key messages */
    DispatchMessage(&msg); /* send it to dialog procedure */
  }
}

The IsDialogMessage function immediately forwards the message to our dialog box procedure if it belongs to it. Otherwise, the message enters regular handling. More information about the message loop can be found here.

There is a possibility to bypass this program loop (not writing it), as explained by Iczelion in the 10th lesson of his wonderful Win32 Assembly article series. However, by doing so, we have less control: we cannot put any verification in the loop, like accelerator handling, for example. So, let’s keep the loop in our code.

4.1. Enabling Visual Styles

In order to get the common controls 6 visual styles, introduced with Windows XP, you must not only call InitCommonControls (declared in CommCtrl.h), but also embed a manifest XML file into your code. Fortunately, there is a handy trick you can use in the Visual C++ compiler, which I learned from Raymond Chen's blog. Just add this to your code:

#pragma comment(linker, \
  "\"/manifestdependency:type='Win32' "\
  "name='Microsoft.Windows.Common-Controls' "\
  "version='6.0.0.0' "\
  "processorArchitecture='*' "\
  "publicKeyToken='6595b64144ccf1df' "\
  "language='*'\"")

This will generate and embed the XML manifest file automatically, and you’ll never worry about it again.

To call InitCommonControls, you must statically link your program to ComCtl32.lib, and this can be accomplished with a #pragma comment directive as well:

#pragma comment(lib, "ComCtl32.lib")

4.2. Our WinMain

So far, this is our complete WinMain function (without the dialog box procedure yet):

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"

#pragma comment(linker, \
  "\"/manifestdependency:type='Win32' "\
  "name='Microsoft.Windows.Common-Controls' "\
  "version='6.0.0.0' "\
  "processorArchitecture='*' "\
  "publicKeyToken='6595b64144ccf1df' "\
  "language='*'\"")

#pragma comment(lib, "ComCtl32.lib")

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
  HWND hDlg;
  MSG msg;
  BOOL ret;

  InitCommonControls();
  hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
  ShowWindow(hDlg, nCmdShow);

  while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
    if(ret == -1)
      return -1;

    if(!IsDialogMessage(hDlg, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return 0;
}

5. Dialog Box Procedure

The dialog procedure is responsible for handling all program messages, responding to all events. It starts like this:

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  return FALSE;
}

Windows calls this function for every program message. If we return FALSE, it means Windows can carry out the default processing for the message, because we’re not interested in it; if we return TRUE, we tell Windows we’ve actually processed the message. This is slightly different from the “raw” window message handling through WndProc, where you return a call to the DefWindowProc function. Here, we must not call DefWindowProc, just return FALSE.

So, we’ll write only the handling of messages that are interesting to us. Among the most commonly used messages, we have WM_INITDIALOG, WM_COMMAND, and WM_SIZE. But to build a minimal functional program, we need only two:

switch(uMsg)
{
case WM_CLOSE: /* there are more things to go here, */
  return TRUE; /* just continue reading on... */

case WM_DESTROY:
  return TRUE;
}

Notice we don’t need to handle the WM_PAINT message with dialog boxes.

5.1. The Minimal Message Handling

WM_CLOSE is called just prior to window closing. If you want to ask the user if he really wants to close the program, here is the place to put this check. To close the window, we call DestroyWindow – if we don’t call it, the window won’t be closed.

So here’s the message handling, also prompting the user. If you don’t need to prompt the user, just omit the MessageBox check and call DestroyWindow directly. And don’t forget to return TRUE here, whether you close the window or not:

case WM_CLOSE:
  if(MessageBox(hDlg,
    TEXT("Close the window?"), TEXT("Close"),
    MB_ICONQUESTION | MB_YESNO) == IDYES)
  {
    DestroyWindow(hDlg);
  }
  return TRUE;

Finally, we must handle the WM_DESTROY message, telling Windows we want to quit the main program thread. We do this by calling the PostQuitMessage function:

case WM_DESTROY:
  PostQuitMessage(0);
  return TRUE;

The WM_DESTROY message is also the best place to free resources that were allocated by the program and are still waiting to be deallocated – it’s final cleanup time. But don’t forget to do the cleanup before calling PostQuitMessage.

5.2. Closing on ESC

An interesting feature of the dialog boxes is that they can be easily programmed to be closed when the user hits the ESC key, and it can also be done when the dialog box is the main window as well. To do so, we must handle the WM_COMMAND message and wait for the IDCANCEL identifier, which comes in the low word of the WPARAM argument, and that’s what we do:

case WM_COMMAND:
  switch(LOWORD(wParam))
  {
  case IDCANCEL:
    SendMessage(hDlg, WM_CLOSE, 0, 0);
    return TRUE;
  }
  break;

The IDCANCEL identifier is declared in WinUser.h, which is included in Windows.h, so it’s always available.

Notice that when handling IDCANCEL, we send a WM_CLOSE message to our dialog window, which causes the dialog procedure to be called again with the WM_CLOSE message that we previously coded, so the user will be prompted if he wants to close the window (because that’s the way we coded it).

6. The Final Program

So here is our final program. It’s the C source code for a minimally functional Win32 dialog based program, with the message loop and visual styles properly enabled, just ready to go. You can keep this to use as the skeleton for any Win32 dialog based program.

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"

#pragma comment(linker, \
  "\"/manifestdependency:type='Win32' "\
  "name='Microsoft.Windows.Common-Controls' "\
  "version='6.0.0.0' "\
  "processorArchitecture='*' "\
  "publicKeyToken='6595b64144ccf1df' "\
  "language='*'\"")

#pragma comment(lib, "ComCtl32.lib")

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg)
  {
  case WM_COMMAND:
    switch(LOWORD(wParam))
    {
    case IDCANCEL:
      SendMessage(hDlg, WM_CLOSE, 0, 0);
      return TRUE;
    }
    break;

  case WM_CLOSE:
    if(MessageBox(hDlg, TEXT("Close the program?"), TEXT("Close"),
      MB_ICONQUESTION | MB_YESNO) == IDYES)
    {
      DestroyWindow(hDlg);
    }
    return TRUE;

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

  return FALSE;
}

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
  HWND hDlg;
  MSG msg;
  BOOL ret;

  InitCommonControls();
  hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
  ShowWindow(hDlg, nCmdShow);

  while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
    if(ret == -1)
      return -1;

    if(!IsDialogMessage(hDlg, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return 0;
}

7. Further Organization

Usually, when your program grows in complexity, you’ll end up with a huge DialogProc stuffed with code, and therefore very painful to maintain. A useful approach to this is the use of function calls to each message — the famous subroutines. It’s specially handy because it isolates the logic of the code, you can see clearly each part of the program, giving you the notion of responding to events. For example, our program could have two dedicated functions:

void onCancel(HWND hDlg)
{
  SendMessage(hDlg, WM_CLOSE, 0, 0);
}

void onClose(HWND hDlg)
{
  if(MessageBox(hDlg, TEXT("Close the program?"), TEXT("Close"),
    MB_ICONQUESTION | MB_YESNO) == IDYES)
  {
    DestroyWindow(hDlg);
  }
}

And it would allow us to rewrite our DialogProc like this:

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) /* more compact, each "case" through a single line, easier on the eyes */
  {
  case WM_COMMAND:
    switch(LOWORD(wParam))
    {
    case IDCANCEL: onCancel(hDlg); return TRUE; /* call subroutine */
    }
    break;

  case WM_CLOSE:   onClose(hDlg); return TRUE; /* call subroutine */
  case WM_DESTROY: PostQuitMessage(0); return TRUE;
  }

  return FALSE;
}

History

  • 20th July, 2011: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here