<-- [prev] . [next] -->
Quick introduction to Windows API

3. A scrolling text utility

Next we will make the disp() function more useful by allowing it to scroll the window. An expert in graphical interfaces might feel uncomfortable that the first thing we are going to do with a window is revert to old-style scrolling text. Surely the whole point is that we can now get away from that and do something more fancy? This is true, but in my experience it is very helpful to have a way to plonk text on the screen without any fuss. One uses it a lot during debugging ... and debugging is the only way one is going to develop the more fancy user interface that we eventually want.

Here is the code you want. I suggest you start a new source file, called "disp.cpp" and put it there. Remove the basic disp() function we wrote before. To give the rest of your code access to the new disp(), write a "disp.h" file with the function declarations, as follows:

// disp.h
// you need to have #include <windows.h> above this

// set up the window to be used by all the other display functions
void disp(HWND hwnd);

// The following display a message in the display window set by the last
// call to disp(hwnd).
void disp(const char* s);
void disp(const char* s, const int n);
void disp(const char* s, const int n1, const int n2);
void disp(const char* s, const double x1, const double x2=0.0);
void disp(const char* s1, const char* s2);


// a utitlity used by the disp functions (user does not need to call it)
void disp(const char* s, const int n1, const int n2, const int numpar);

You use these by calling disp(hwnd) early in your program, and then calling the others when you like. disp(const char*) does the main business of displaying text, the others are for convenience. I suggest you add a call to disp(hwnd) when your main program window is first created at the launch of the program. This means you add the following to WndProc, at the top of the switch statement (it is logical for this to be the first case considered):

   case WM_CREATE:
      disp(hwnd); // set the window for my disp utility functions
      return 0 ;
You also need to add
#include "disp.h"
at the top of your main file.

Have a go at using these functions and observing the scrolling text. Try expanding the window to full screen size. The scrolling text appears in the top right region of the window: examining the source code of disp(const char*) should show you how this is done, so you can adjust it if you want. Profficient C++ programmers will notice that I used low-level methods in these functions, avoiding the standard template library string class for example. You could make them more flexible if you want, but they are intended to be low-level utilities so I did not do that here. Also, one should mostly avoid global variables but here I think the convenience of hdisp_window_global justifies its use.

4. Introducing controls
(Ref: MSDN - MSDN Library - Win32 and COM Development - User Interface - Windows Controls)

In Windows API a control is a functioning object, usually provided in a library (i.e. the source code was written by someone else), that is itself a window and offers the user some functionaility. Windows comes with a number of standard controls that you will soon come to use.

You set up a control somewhere in your program, usually in response to the WM_CREATE message, and subsequently communicate with it using SendMessage or SendDlgItemMessage. Events occuring on the control, such as the user clicking there, cause the control the generate a message (WM_COMMAND for the simpler controls, WM_NOTIFY for others). All this is best understood with an example.

We begin with a simple control, called a static text box. This is simply a box on the screen which displays a line a text. It doesn't do anything: you can't type there for example. It is just for display. It might seem that such a simple thing is not (or does not need to be) a "window", but having it be a "window" in its own right can help with common tasks such as repositioning it or redrawing it after changes in the main window size.

Create the function at this link and put it where you like (I recommend in a new file called "mycreate_util.cpp" where you will gather all window creation code). Also create a header file (e.g. "mycreate_util.h") with this declaration:

HWND myCreateStatic(HWND hwnd, int id, char* s, int x, int y, int width, int height);
The myCreateStatic function takes care of most of the parameters you need to send to the windows function CreateWindowEx, asking you to supply just a few obvious ones.

Put this code in response to the WM_CREATE message in your WndProc:

   case WM_CREATE:
      myCreateStatic(hwnd, ID_STATIC, "the quick brown fox", 2, 20, 80, 30);
      return 0;
and don't forget to #include "mycreate_util.h" at the top of your main file. Finally, define ID_STATIC to be an integer of your choosing. This can be done by a global
const int ID_STATIC = 1000;
for example at the top of your main file or in another header file. This number is just a tag to label the control we created: it improves readability of the code, especially when you have many controls. Identification numbers can also be defined using #define, but I am following good C++ practice of preferring const int to #define.

Run the program. You see "the quick brown fox" displayed, and note that the static text control did its best to fit the text into the box, in this case running over two lines. If you increase the width of the box it will just use one line. Note also that the static box does more than merely display the text: when you cover or resize the window and then reveal it again, the static box redraws itself.

Despite the name, a static text box can change: you can move it and change the text. For example, try adding the following to the switch statement in WndProc:

   case WM_LBUTTONDOWN:
   {  HWND h = GetDlgItem(hwnd, ID_STATIC); 
      if (h) SetWindowText(h, "jumped over");
      return 0;
   }
   case WM_KEYDOWN:
      SetDlgItemText(hwnd, ID_STATIC, "the lazy dog");
      return 0;
Now when you click the window, the message changes to "jumped over", and when you press a key it changes to "the lazy dog". Note that this code snippet does not initially have a handle to the control's window. In the first case (WM_LBUTTONDOWN) it got the handle by a call to GetDlgItem specifying the main window, hwnd. This is because the static text control here is a child of the main window. We tell GetDlgItem which child window we want by supplying an ID number. The second case (WM_KEYDOWN) shows a shorthand offered by windows since this is a common task: the SetDlgItemText function does the whole job in one step. I guess that is what you are going to use from now on, but I wanted to point out the more basic way first.

Moving and resizing a window

To move a control such as a static text, use SetWindowPos. e.g.:

   {  
      int x = 100;
      int y = 200;
      int width = 100;
      int height = 20;
      HWND h = GetDlgItem(hwnd, ID_STATIC); 
      if (h) SetWindowPos(h, NULL, x, y, width, height, SWP_NOZORDER);
      return 0;
   }
You can also do this using MoveWindow, defined as
BOOL MoveWindow(HWND hwnd, int x, int y, int width, int height, BOOL repaint);
SetWindowPos is more general, so you don't need MoveWindow, but you might prefer its simpler format.

Now, to increase your confidence, I suggest you add the following loop at case WM_CREATE: in WndProc

      for(int i=1; i<10; ++i) 
         myCreateStatic(hwnd, ID_STATIC+i, "hello", 2, 20*(1+i), 80, 30);
This will create another 9 static text boxes, and hopefully give you the feeling that these are simple entities that you can work with.

The next most basic controls are the static frame, buttons, edit box and list box. Replace your "mycreate_util.cpp" file with the one at this link. You now have a new function. The declaration should be placed in your include file (mycreate_util.h):

HWND myCreateBox(HWND hwnd, int id, char* type, char* s, 
                 int x, int y, int width, int height);
This has the same parameters as myCreateStatic, and an extra one: the "type". You specify one of "button", "checkbox", "radio", "frame", "static", "edit", "list" (or the initial letter of one of these, either upper or lower case). The function then makes some standard style choices and creates the control. If you want to change those style choices, go ahead. The idea is that you are saved the bother of worrying about that in the first instance. As you go on, you will want individual controls of differing properties, and then you will write the call to CreateWindowEx yourself.

The "button", "check box" and "radio button" are the three most basic types of clickable object in a typical window. You will see them in action in a moment. The "frame" control simply draws a rectangle. This is used to improve the appearance of the application window, for example in order to put a box around related controls so that they appear to form a group. The "edit box" and "list box" provide the two most basic ways to allow the user to enter text and numbers, and select things from a list.

4.1. Buttons

Add this to your case WM_CREATE: to put some illustrative buttons in the main window of your program:


You can already run the program and see a few effects: try clicking the push button, checkbox and the radio button. The controls are taking care of their own visual appearence, but your program does not yet react to the effects. There are two things to think about. First, when you click a control it sends the message WM_COMMAND back to its parent window, so you can make your program react if you want to. Secondly, the checkbox and radio button controls have an internal parameter, their status, which you will want to be able to read and/or change.

You respond to the WM_COMMAND message by adding a "case WM_COMMAND:" to the switch statement in WndProc. See the source code here (control.cpp) for an example. The information on which control sent the WM_COMMAND message is sent via the lower part of wParam. The upper part of wParam says which type of message was emitted by the given control, if it can send one of several (not needed for a simple button).

You find out the state of a control by, for example:

UINT status = IsDlgButtonChecked(hwnd,ID_CHECKBOX);
where ID_CHECKBOX is an id number of a checkbox in window hwnd. You can set the state by, for example:
SendDlgItemMessage(hwnd, ID_DOT, BM_SETCHECK, BST_UNCHECKED, 0);
This sends a message to the control having index number ID_DOT in the window hwnd. The message is "I want to set the check-status" and "set it to unchecked". There is no prize for guessing what will happen if you send BST_CHECKED instead of BST_UNCHECKED.

4.2. Edit box: inputing text and numbers

The "edit" control is quite versatile, allowing the user to type in the box to edit the text. If you make it large this amounts to a simple word processor. Normally you use small boxes.

Once you add controls such as "edit box" that can respond to the mouse and keyboard, you introduce the issue of "focus", i.e. which window will receive messages generated by clicks or key presses? Edit boxes are designed to acquire the keyboard focus when you click on them. If you want your main window to get the focus back when you click in it, then add

      SetFocus(hwnd);       // set the keyboard focus to window hwnd
to your response to WM_LBUTTONDOWN in WndProc.

You need your program to be able to tell when the user has finished editing the text. You can do this by requiring the user to push a button (see later), or for now by simply picking up a mouse click in the main window. You then want to retrieve the text from the box. Unfortunately this is a little more complicated because the box does not offer you a pointer directly to its text string. Instead you have to copy the string out, and for safety you need to make sure you have an array long enough to hold the text. You can do this as follows:

int len = GetWindowTextLength(GetDlgItem(hwnd, ID_BOX));
if (len > 0)
{
   char* buf;

   buf = (char*)GlobalAlloc(GPTR, len + 1);       // add 1 for the final NULL
   GetDlgItemText(hwnd, ID_BOX, buf, len + 1);

   //... do stuff with text ...

   GlobalFree((HANDLE)buf);
}
It's a bit of a bore but that is the only way to prevent users from doing harm by tinkering with the control's text directly and possibly going out of array bounds. I assumed you gave the edit box the id ID_BOX. Note the use of GetDlgItem in the first line to get a handle to the edit box. This is because the GetWindowTextLength function takes a window handle as its parameter.

To get numerical input, you could convert a string to a number using sprintf. Windows offers a facility to do this for integers. Instead of GetDlgItemText you use GetDlgItemInt:

bool success;
int n = GetDlgItemInt(hwnd, ID_BOX, &success, FALSE);
The success parameter lets you know whether the text in the box was a valid integer. If it was not the routine returns zero. If you add ES_NUMBER to the style of an edit box at its creation, then the user is prevented from typing anything other than digits 0 to 9 there. This can be handy when you want positive integer input.

The other way to get keyboard input is to intercept every key message from the keyboard. These are the messages WM_KEYDOWN and WM_CHAR. The identity of the key pressed is sent via lParam and wParam. That is what you would do if you wanted to write your own keyboard entry facilities, for example in a console-like program or your own text processing program.

4.2. List box

The list box is a versatile control designed to allow users to select items from a list. You can associate data with each item, and respond to messages when items are selected. To add items to the list, you send it the LB_ADDSTRING message:

int index = SendDlgItemMessage(hwnd, ID_LISTBOX, LB_ADDSTRING, 0, (LPARAM)"Text for this item");
where I assumed your list box has the id number ID_LISTBOX. The list box responds by returning the index number of the new item. A list also stores a little data (i.e. in addition to the strings.) You can get access to the data using the messages LB_SETITEMDATA and LB_GETITEMDATA. Other useful messages to list boxes include LB_DELETESTRING, LB_INSERTSTRING, LB_GETCOUNT. I leave it to you to look them up if you want to find out more. If you download the source code at theForger's Win32 API Tutorial you can learn from the example program called ctl_one.

When the user selection is changed, a list box sends the message WM_COMMAND to its parent window, and indicates the specific message by setting the upper part of wParam equal to LBN_SELCHANGE. Here the letters "LBN" stand for "list box notification". Other useful messages a listbox can generate are LBN_DBLCLK, when the user double clicks an item, and LBN_ERRSPACE when the listbox runs out of memory. See msdn (under ... windows controls - control library - list box) for further details. Here is how the code could look:

case WM_COMMAND:
{  switch(LOWORD(wParam))
   {
      case ID_LISTBOX:  // this is is id of our list box
         switch(HIWORD(wParam))
         {
	        case LBN_DBLCLK:
	           // user double clicked the item. Do stuff here.
	        break;
            case LBN_SELCHANGE:
               // the "selection changed" message. Do stuff here.
            break;
            // ... other messages from the list box
         }
      break;
      case ID_EDIT1:   // a message from our edit box number 1
      // ... etc
  }
  return 0;
}
Actually, I don't like having one switch inside another, and I would recommend that, instead of putting this code in the WndProc function, you now introduce a call to a new function called something like atcommand. atcommand(hwnd,wParam,lParam) would handle all the possible command messages. Once you are comfortable with that, you can see where we are heading: functions such as atcommand can become methods of a window-handling class that you can invent.

4.3. Summary of basic functions for controls

To summarise, here is a list of functions we discussed in relation to controls:

HWND hwnd;      // the parent window
HWND h;         // the control
int idcontrol;  // the id number of the control in the window
bool success;
HFONT hfont;
...

h = CreateWindowEx(styleex, classname, "", style, x,y,width,height,
            hwnd, (HMENU) idcontrol, GetModuleHandle(NULL), NULL);
            
h = GetDlgItem(hwnd, idcontrol);     // get a handle to a control

// working directly with the control:
MoveWindow(h, x, y, width, height, true);                        // set the position and size
SetWindowPos(h, NULL, x, y, width, height, SWP_NOZORDER);        // ditto
SetWindowPos(h, HWND_TOP, x, y, 0, 0, SWP_NOSIZE);               // bring to front
SetWindowPos(h, HWND_TOP, x, y, width, height, SWP_SHOWWINDOW);  // resize and to front

SetWindowText(h, "jumped over");                           // set the text
SendMessage(h, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(FALSE, 0));  // set the font
int len = GetWindowTextLength(h);                          // get the length of the text 

// and you can always do things like:
len = GetWindowTextLength( GetDlgItem(hwnd, idcontrol) );

// working via the parent window + idcontrol:
SetDlgItemText(hwnd, ID_STATIC, "jumped over");           // set the text
GetDlgItemText(hwnd, idcontrol, buf, len + 1);            // get the text
int n = GetDlgItemInt(hwnd, idcontrol, &success, FALSE);  // get a number
int index = SendDlgItemMessage(hwnd, idcontrol, LB_ADDSTRING, 0, (LPARAM)"Text for this item");

// keyboard focus
SetFocus(hwnd);          // set the keyboard focus to window hwnd
HWND h = GetFocus(VOID)  // get the handle to the window currently possessing the keyboard focus

           
// memory management (or you might prefer to use a C++ string):
char* buf = (char*)GlobalAlloc(GPTR, len + 1); 
GlobalFree((HANDLE)buf);

We only introduced a few controls here. There are many more, including highly functional ones, but the methods to interact with them are mostly covered by the above functions.

<-- [prev] . [this] . [next] -->