Skip to content

Simple Windows Dialogs From C++ – Part 2

October 8, 2011

Introduction

In the first part of this two-part tutorial, I looked at how to utilise the dialogs that Windows provides from your console applications. However, those dialogs are limited in in functionality to what the Microsoft developers thought was needed, and in any case cover only a tiny fraction of the requirements of real application developers.

In this tutorial, I cover how to create custom dialogs in Windows and how to interface them with your C++ code. As with the previous tutorial, all examples use the GCC toolset and the command line.

The Password Dialog

Suppose you want to write a command line C++ program which prompts the user for a user name and a password, and then does something vaguely security-oriented like logging on to a database. Your first thought is probably to write some code like this:

string user,pwd;
cout << "user name: ";
cin >> user;
cout << "password: ";
cin >> pwd;

but this displays the password in plain-text so that anyone looking over the user’s shoulder can see it. How to fix this?

In fact, there is no way of fixing it using the features of Standard C++. You cannot turn off console echoing, or make things appear as asterisks. You have to use Windows-specific stuff to get this done. The rest of this tutorial covers how to write a simple dialog that looks like this:

Screenshot_Enter user and password_2011-10-04_13-49-04

and how to interface such a dialog with normal C++ code.

The C++ Program

As with the line-numbering program in the first tutorial, you need to have the basic C++ console program in place before you can add the dialog. Here’s a simple version:

#include <iostream> #include <string> using namespace std; struct UserPassGetter { string user, pwd; bool got; UserPassGetter() : got( false ) {} void Get(); }; void UserPassGetter :: Get() { cout << "user: "; getline( cin, user ); cout << "pasword: "; getline( cin, pwd ); got = user != ""; } int main() { UserPassGetter upg; upg.Get(); if ( upg.got ) { cout << "user: " << upg.user

<< " password: " << upg.pwd << endl; } }

The use of the UserPassGetter struct looks like OO gone mad, and pretty poor OO at that!  But bear with me – there is a reason for creating this struct, which will become clear (I hope) later. I suggest you copy & paste this program, compile and run it to make sure you know what it does. Then, your task is to re-implement the Get function to use a Windows dialog.

Messages and Callbacks 101

Before you can dive into writing a custom dialog like the one for user/password entry, you need to know a couple of things about how the GUI side of Windows works.

Almost everything in the Windows GUI is done using messages. Your application gets sent messages by the Windows OS when things like mouse clicks happen, and your application will need to send messages itself to do stuff like setting the text of input fields or disabling buttons.

Messages in Windows are simply C structs. The MSG struct is defined in the windows.h header file, and looks like this, with my annotation:

typedef struct tagMSG {
  HWND   hwnd;       // who is to receive me?
  UINT   message;    // which message is this?
  WPARAM wParam;     // message-specific parameter
  LPARAM lParam;     // another message-specific parameter
  DWORD  time;       // time sent
  POINT  pt;         // mouse position when sent
} MSG;

The hwnd member is a window handle. Window handles are as ubiquitous in Windows GUI programming as messages and are unique identifiers used by Windows to locate specific Windows. Every GUI element you see on your screen has its own unique handle.

The message member is the identifier for the message. These are defined in the windows.h header file, and are things like WM_MOVE, which gets sent when a window has been moved, WM_CLOSE which is sent when a window is about to close, and WM_COMMAND which gets sent when the user clicks on a button or types in some text into a text edit.

The wParam and lParam members are general-purpose integer parameters. Their use and meaning varies with the message being sent. For example, if WM_MOVE is sent, the new X,Y coordinates of the Window are packed into the low- and high-order words of the lParam parameter, whereas the WM_CLOSE message does not use either parameter.

Lastly, the time and pt members specify the time and the mouse position when the message was sent – these are rarely used directly by Windows programmers.

If an application wants to process the messages that Windows will be sending it, it needs to run a message loop (actually, most applications run more than one). This loop takes on many, many forms, but basically it looks like this:

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

The functions called here are all part of the Windows API. GetMessage retrieves a message from the operating system, which is then translated (I won’t go into this), before being dispatched to the relevant window in your application. If no message loop is run, no messages get processed, and your GUI will freeze up.

Notice that you did not need to specify the window that the message gets sent to. This is because, before a message loop is started, your application must register its windows with the operating system and provide each window with a callback function called a window procedure. A callback is a function that you never call yourself, but that Windows knows about and will call for you, in this case whenever a message is dispatched via the DispatchMessage API.  This sounds complicated, but it saves you from having a giant switch containing all the possible windows that a message could be dispatched to – you just register the windows with the operating system and let it do the work.

The callback function that you will have to provide to provide to process messages from your dialog will look like this:

BOOL CALLBACK DialogProc( HWND hwnd, UINT message,
                                WPARAM wp, LPARAM lp );

The name of the function is not significant, and BOOL and CALLBACK are macros defined in windows.h. The parameters to the function are the members of the MSG struct that you need to process. Why the MSG itself is not passed into the function is one of the many mysteries of Windows programming.

Ok, with that out of the way, let’s get on with creating the user/password dialog.

Designing The Dialog

It is possible to create dialogs dynamically at run-time by calling the CreateWindow API for all the various buttons, edits, static texts etc. that make up a dialog. However, you don’t want to go there, particularly in raw C. It is much easier to specify the dialog as a Windows resource. Resources are things like dialogs, icons, string tables etc. that are compiled by a resource compiler into a binary form, and then linked in to your executable by your toolchain’s  linker.

The resource for the dialog you are going to create looks like this:

#include <windows.h>
#include "pwddlg.h"
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDD_PWDDLG DIALOG 0, 0, 145, 67
STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "Enter User and Password"
FONT 10, "Arial"
{
    DEFPUSHBUTTON   "OK", IDOK, 21, 47, 50, 14
    PUSHBUTTON      "Cancel", IDCANCEL, 83, 46, 50, 14
    LTEXT           "User Name", IDC_STATIC, 7, 7, 40, 8, SS_LEFT
    EDITTEXT        IDC_USEREDIT, 45, 4, 92, 14, ES_AUTOHSCROLL
    LTEXT           "Password", IDC_STATIC, 7, 24, 32, 8, SS_LEFT
    EDITTEXT        IDC_PWDEDIT, 45, 22, 92, 14, ES_AUTOHSCROLL | ES_PASSWORD
}

I don’t want want to go into detail about the format of dialog resources here, but (squinting a bit) you can see that this specifies a dialog, gives it the identifier IDD_PWDDLG, and creates a couple of buttons with the identifier’s IDOK and IDCANCEL and a couple of text edits with the identifiers IDC_USEREDIT and IDC_PWDEDIT.

It is much easier to use a resource editor to create a dialog resource than typing it in via a text editor. Windows does not ship with a resource editor, but there are a number of freeware ones around. The one I use is ResEdit, which is available from here. I do not intend to go into how to use a resource editor (they are basically simple drag-and-drop GUI builders) except to say that they will all generate two text files – an .RC file, which contains the resource(s) in text format, and a C header file which contains the resource identifiers. If you don’t want to mess about with the resource editor, simply copy the above code into a file called pwddlg.rc.The resource editor will also create a header file, which looks like this:

#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif
#define IDD_PWDDLG                              111
#define IDC_USEREDIT                            1001
#define IDC_PWDEDIT                             1003

Once again, if you don’t want to be bothered with the resource editor, just copy this code and save it as pwddlg.h.

The rest of this code assumes that you have used  exactly the same names in your dialog that are used in the two files above. If the names do not match exactly, either your code will not compile (which is good – at least you will know what is wrong), or the dialog will simply silently fail to work  – I recommend that, the first time through, you simply copy the above code.

Having got your resources defined in the .RC and the header file, you need to compile them into a binary .RES file file that the linker can stuff into your final executable. The GCC toolchain provides the windres resource compiler to do this:

> windres  -J rc -O coff -i pwddlg.rc -o pwddlg.res

 

Writing The Dialog Code

The first thing you need to do is to provide the callback function (remember them?) that Windows will call when messages are dispatched to your dialog. This  is pretty much boilerplate code, at least at this stage, and should look like this:

BOOL CALLBACK DialogProc( HWND hwnd, UINT message,
                           WPARAM , LPARAM  ) {
switch ( message ) {      
  case WM_DESTROY:         
     PostQuitMessage(0);         
     return TRUE;
  case WM_CLOSE:
     DestroyWindow (hwnd);
     return TRUE;     
  }
  return FALSE;
}

What this function does is two-fold – if Windows sends the dialog a WM_CLOSE message, it means that the X button at the top-right of the caption bar  has been pressed. You need to respond to this in order that the dialog can be closed, and the correct response is to destroy the dialog via the DestroyWindow API. Calling this will (among other things) result in a WM_DESTROY message being sent, and you also need to process that – I’ll explain what the PostQuitMessage API call does a bit later.

All of the message handling cases in the switch return TRUE, which indicates to Windows that we handled the message and so it doesn’t need to bother. For all the other possible messages (of which there are hundreds) we want Windows to apply its default handling, so the function returns FALSE – a simple rule; if you handle the message, return FALSE, if you don’t return TRUE. Note that FALSE and TRUE are not directly equivalent to the false and true keywords in C++.

You now need to create and display the dialog. This is done inside the Get function, using the CreateDialog API:

void UserPassGetter :: Get() {
  HWND dlg = CreateDialog( GetModuleHandle(0),
                           MAKEINTRESOURCE( IDD_PWDDLG  ),
                           0, DialogProc );
}

The parameters to this function are the handle (unique identifier) for the application itself, which can be got via the call to the GetModuleHandle API, the identifier of the dialog resource that you created earlier, suitably mangled by the MAKEINTRESOURCE macro, the handle of the owning window (we use zero to make the desktop be the owner), and lastly a function pointer to the callback function to use for this dialog, which is the function you created above.

The CreateDialog function returns the window handle of the created dialog, which you will need shortly. It’s instructive to compile the application as it currently stands, and to do this you need to also specify the .RES file on the compiler command line. assuming you have called this program ex6.cpp, you want:

> g++ ex6.cpp pwddlg.res  -o ex6

Note that you will need to #include both windows.h and the pwddlg.h header file for this to work.

If you run the resulting executable, you should see that the dialog appears very briefly on screen, then disappears again, and the application terminates. Why is this? Well, the call to CreateDialog creates the dialog, returns the dialogs handle, but then the Get function terminates, and so does the application, taking all resources with it. You need some way of keeping the dialog up on the screen so that it can process messages, and that something is the message loop!

You need to modify the Get function so that it looks like this:

void UserPassGetter :: Get() {
  HWND dlg = CreateDialog(  GetModuleHandl (0), 
                            MAKEINTRESOURCE( IDD_PWDDLG  ), 
                            0, DialogProc );
  MSG msg;
  while ( GetMessage( & msg, 0, 0, 0 ) ) {
    if ( ! IsDialogMessage( dlg, & msg ) ) {
      TranslateMessage( & msg );
      DispatchMessage( & msg );
    }
  }	
}

This looks very like the message loop you saw earlier, except that there is an extra call to the IsDialogMessage API. This is the API that actually dispatches messages to dialog boxes – DispatchMessage dispatches them to "ordinary" windows . It has two parameters – the handle of the dialog box we are interested in, and the message.

If you compile and run this code, you should find that the dialog box now stays up on screen, that you can close it using the X button, that you can type text into the edit controls, but that the OK and Cancel buttons don’t work. This is because you are  not yet processing the messages that these controls produce.

If no dialog appears, it is almost certainly because at this point:

MAKEINTRESOURCE( IDD_PWDDLG  ), 

your identifier is wrong. If you get the dialog resource id wrong, no error message will be produced, but the dialog simply won’t work. The other alternative is that you failed to link in the .RES file – once again, no error messages will be produced.

Processing Command Messages

In order to get the OK and Cancel buttons to work, you need to process the WM_COMMAND messages that Windows is sending to your callback function but which you are currently ignoring. These messages are sent whenever the user interacts with one of the controls on the dialog, such as the buttons or the text inputs.

The WM_COMMAND message provides the resource id of the control that caused the message to be sent (i.e. the resource id of a button if a button was clicked) in the low-order word of the first parameter passed in to the DialogProc callback function, and the actual event (i.e. a button click) in the high-order word. So in outline, the processing of command messages looks like this (LOWORD and HIWORD more  macros defined in windows.h):

BOOL CALLBACK DialogProc( HWND hwnd, UINT message, WPARAM wp, LPARAM ) { switch (message) { case WM_COMMAND: { int ctrl = LOWORD( wp );

int event = HIWORD( wp ); if ( ctrl == IDOK && event == BTN_CLICKED ) { // handle OK button } else if ( ctrl == IDCANCEL && event == BTN_CLICKED ) { // handle cancel button } else { // do Windows default handling } } // other message handlers } return FALSE; }

The easiest button to handle is the Cancel button, identified by IDCANCEL. This should do exactly what the X button does – just close the dialog. You already know what the X button does – it’s the processing associated with the WM_CLOSE handler. It should also set the got flag in the UserPassGetter struct to false.

The processing for the message from the OK button, identified by IDOK,is a bit more complicated. You need to extract the text from the two controls, use the extracted text to set the UserPassGetter  members user and pwd, set got to true, and then do what what IDCANCEL does.

This is all far too much code to put into an if statement, which is itself inside a case statement, inside a switch! You need to write some helper functions. Let’s start by writing a function to extract the text from a text input field given the field’s resource id and the handle of the dialog that contains it:

string GetInputText( HWND dlg, int resid  ) {
  HWND hc = GetDlgItem( dlg, resid );
  int n = GetWindowTextLength( hc ) + 1;
  string s( n, 0 );
  GetWindowText( hc, &s[0], n );
  return s;
}

Here, the GetDlgItem API is used to get the handle of the specified resource. Using that handle, another API, GetWindowTextLength is used to obtain the length of the text in the text input. As you are dealing with C code, you want 1 more than that to account for the null terminator. Then, you create a C++ string of the required length, and fill it with zero bytes. Using this string as a buffer, you call the GetWindowText API to pull the text out of the text input and into the C++ string. Lastly, the now populated C++ string is returned from the function. This is a good example of how C++ can simplify using the C API. If you were using C only, you would need to worry about malloc’ing strings and about managing the code to free them later.

Now you need to use this function from inside DialogProc  to set the string member variables of UserPassGetter. Unfortunately, there is a bit of a hitch here – you do not have access to the UserPassGetter struct! The DialogProc callback, who’s signature is that of a generic function used by Windows, and which cannot be changed, does not know anything about UserPassGetter ! How to fix this?

One way is simply to use a bunch of global variables for the password strings and the got status, which can be set from inside the DialogProc function. This will work, and might even be the best idea for a very small program such as this one. However, I’m going to show a slightly more complex solution, which is the basis for architectures that can cope with more than a single dialog.

The CreateDialogParam Function

The way to fix this problem is to inject the address of the UserPassGetter struct that you are working on  struct into the DialogProc function. You can do that using the CreateDialogParam function, which takes one extra parameter over the function you are currently using:

HWND dlg = CreateDialogParam( GetModuleHandle(0), 
                         MAKEINTRESOURCE( IDD_PWDDLG  ), 
                         0, DialogProc,
                         (LPARAM) this );

The extra LPARAM parameter is simply an integer with which you can do with as you will – in this case you are going to use it to pass the address of the UserPassGetter struct into the dialog procedure.

The dialog procedure receives this address as the lParam parameter of a WM_INITDIALOG message, which is sent once when the dialog is first being created. You need to hang on to that address, and the easiest way to do that is to use a function static variable:

BOOL CALLBACK DialogProc( HWND hwnd, UINT message,
                           WPARAM wp, LPARAM lp  ) {
  static UserPassGetter * getter = 0;
  switch ( message ) {
    case WM_INITDIALOG:
       getter = (UserPassGetter *) lp;
       return TRUE;
 
  ....

You are now in a position to write the cases for dealing with the OK and CANCEL button:

case WM_COMMAND: { int ctl = LOWORD( wp ); int event = HIWORD( wp ); if ( ctl == IDCANCEL && event == BN_CLICKED ) { getter->got = false; DestroyWindow (hwnd); return TRUE; } else if ( ctl == IDOK && event == BN_CLICKED ) { getter->user = GetInputText( hwnd, IDC_USEREDIT ); getter->pwd = GetInputText( hwnd, IDC_PWDEDIT ); getter->got = true; DestroyWindow (hwnd); return TRUE;

}

return FALSE; }

If you add this code to your switch, you should find that the OK and cancel buttons now work, and that your program now prints out the user and password when you press the OK button.

Loose Ends

One thing that has not yet been explained is the PostQuitMessage API called in response to the WM_DESTROY message. In fact, this is quite trivial – when you want the application to close, you need the message loop to stop running. All PostQuitMessage does is to put a WM_QUIT message in the Windows message queue. When the GetMessage API encounters this message, it returns FALSE and so the message loop terminates.

The Final Code

#include <windows.h> #include <iostream> #include <string> #include "pwddlg.h" using namespace std; struct UserPassGetter { string user, pwd; bool got; UserPassGetter() : got( false ) {} void Get(); }; string GetInputText( HWND dlg, int resid ) { HWND hc = GetDlgItem( dlg, resid ); int n = GetWindowTextLength( hc ) + 1; string s( n, 0 ); GetWindowText( hc, &s[0], n ); return s; } BOOL CALLBACK DialogProc( HWND hwnd, UINT message, WPARAM wp, LPARAM lp ) { static UserPassGetter * getter = 0; switch ( message ) { case WM_INITDIALOG: getter = (UserPassGetter *) lp; return TRUE; case WM_COMMAND: { int ctl = LOWORD( wp ); int event = HIWORD( wp ); if ( ctl == IDCANCEL && event == BN_CLICKED ) { getter->got = false; DestroyWindow (hwnd); return TRUE; }

else if ( ctl == IDOK && event == BN_CLICKED ) { getter->user = GetInputText( hwnd, IDC_USEREDIT ); getter->pwd = GetInputText( hwnd, IDC_PWDEDIT ); getter->got = true; DestroyWindow (hwnd); return TRUE; } return FALSE; } case WM_DESTROY: PostQuitMessage(0); return TRUE; case WM_CLOSE: DestroyWindow (hwnd); return TRUE; } return FALSE; } void UserPassGetter :: Get() { HWND dlg = CreateDialogParam( GetModuleHandle(0), MAKEINTRESOURCE( IDD_PWDDLG ), 0, DialogProc, (LPARAM) this ); MSG msg; while ( GetMessage( & msg, 0, 0, 0 ) ) { if ( ! IsDialogMessage( dlg, & msg ) ) { TranslateMessage( & msg ); DispatchMessage( & msg ); } } } int main() { UserPassGetter upg; upg.Get(); if ( upg.got ) { cout << "user: " << upg.user << " password: " << upg.pwd << endl; } }

This may look quite involved for what it does. However, quite a lot of it is either boilerplate, or code which could (and should) be put in a library, such as the GetInputText function. The actual code that is specific to the user/password input dialog is less than a dozen lines.

Using The Microsoft C++ Compiler

As described in the first tutorial, to use the Microsoft command line compiler, you will have to start up a command prompt using the VS20xx Prompt shortcut that should get installed along with Visual C++. This will set up all the required environment variables for you. For the  custom dialog build, you first need to use the rc resource compiler to build the binary resource:

> rc pwddlg.rc

and then compile:

> cl ex5.cpp user32.lib pwddlg.res

Conclusion

This tutorial has demonstrated how to create a simple dialog for inputting a user name and password. To go on from here, you should first read the MSDN Help on all of the API functions used here.  You should then try to create your own dialog from scratch, using a resource editor – a simple dialog to display a multi-line message via a read-only text input would be a good place to start. You could also take a look at the source for my Swine project, which creates a number of simple dialogs, and gives some ideas for how to make them more generic.

Advertisements

From → c++, tutorial, windows

One Comment
  1. Bill permalink

    >”a simple rule; if you handle the message, return FALSE, if you don’t return TRUE.”
    I think your summary got this backward.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: