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 ;
#include "disp.h"
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.
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);
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;
const int ID_STATIC = 1000;
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;
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; }
BOOL MoveWindow(HWND hwnd, int x, int y, int width, int height, BOOL repaint);
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);
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);
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.
Add this to your case WM_CREATE: to put some illustrative buttons in the main window of your program:
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);
SendDlgItemMessage(hwnd, ID_DOT, BM_SETCHECK, BST_UNCHECKED, 0);
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
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); }
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 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.
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");
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; }
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.