The Listbox Class

The final predefined child window control I'll discuss in this chapter is the list box. A list box is a collection of text strings displayed as a scrollable columnar list within a rectangle. A program can add or remove strings in the list by sending messages to the list box window procedure. The list box control sends WM_COMMAND messages to its parent window when an item in the list is selected. The parent window can then determine which item has been selected.

A list box can be either single selection or multiple selection. The latter allows the user to select more than one item from the list box. When a list box has the input focus, it displays a dashed line surrounding an item in the list box. This cursor does not indicate the selected item in the list box. The selected item is indicated by highlighting, which displays the item in reverse video.

In a single-selection list box, the user can select the item that the cursor is positioned on by pressing the Spacebar. The arrow keys move both the cursor and the current selection and can scroll the contents of the list box. The Page Up and Page Down keys also scroll the list box by moving the cursor but not the selection. Pressing a letter key moves the cursor and the selection to the first (or next) item that begins with that letter. An item can also be selected by clicking or double-clicking the mouse on the item.

In a multiple-selection list box, the Spacebar toggles the selection state of the item where the cursor is positioned. (If the item is already selected, it is deselected.) The arrow keys deselect all previously selected items and move the cursor and selection, just as in single-selection list boxes. However, the Ctrl key and the arrow keys can move the cursor without moving the selection. The Shift key and arrow keys can extend a selection.

Clicking or double-clicking an item in a multiple-selection list box deselects all previously selected items and selects the clicked item. However, clicking an item while pressing the Shift key toggles the selection state of the item without changing the selection state of any other item.

List Box Styles

You create a list box child window control with CreateWindow using "listbox" as the window class and WS_CHILD as the window style. However, this default list box style does not send WM_COMMAND messages to its parent, meaning that a program would have to interrogate the list box (via messages to the list box controls) regarding the selection of items within the list box. Therefore, list box controls almost always include the list box style identifier LBS_NOTIFY, which allows the parent window to receive WM_COMMAND messages from the list box. If you want the list box control to sort the items in the list box, you can also use LBS_SORT, another common style.

By default, list boxes are single selection. Multiple-selection list boxes are relatively rare. If you want to create one, you use the style LBS_MULTIPLESEL. Normally, a list box updates itself when a new item is added to the scroll box list. You can prevent this by including the style LBS_NOREDRAW. You will probably not want to use this style, however. Instead, you can temporarily prevent the repainting of a list box control by using the WM_SETREDRAW message that I'll describe a little later.

By default, the list box window procedure displays only the list of items without any border around it. You can add a border with the window style identifier WS_BORDER. And to add a vertical scroll bar for scrolling through the list with the mouse, you use the window style identifier WS_VSCROLL.

The Windows header files define a list box style called LBS_STANDARD that includes the most commonly used styles. It is defined as

 (LBS_NOTIFY ¦ LBS_SORT ¦ WS_VSCROLL ¦ WS_BORDER) 

You can also use the WS_SIZEBOX and WS_CAPTION identifiers, but these will allow the user to resize the list box and to move it around its parent's client area.

The width of a list box should accommodate the width of the longest string plus the width of the scroll bar. You can get the width of the vertical scroll bar using

 GetSystemMetrics (SM_CXVSCROLL) ; 

You can calculate the height of the list box by multiplying the height of a character by the number of items you want to appear in view.

Putting Strings in the List Box

After you've created the list box, the next step is to put text strings in it. You do this by sending messages to the list box window procedure using the SendMessage call. The text strings are generally referenced by an index number that starts at 0 for the topmost item. In the examples that follow, hwndList is the handle to the child window list box control, and iIndex is the index value. In cases where you pass a text string in the SendMessage call, the lParam parameter is a pointer to a null-terminated string.

In most of these examples, the SendMessage call can return LB_ERRSPACE (defined as -2) if the window procedure runs out of available memory space to store the contents of the list box. SendMessage returns LB_ERR (-1) if an error occurs for other reasons and LB_OKAY (0) if the operation is successful. You can test SendMessage for a nonzero value to detect either of the two errors.

If you use the LBS_SORT style (or if you are placing strings in the list box in the order that you want them to appear), the easiest way to fill up a list box is with the LB_ADDSTRING message:

 SendMessage (hwndList, LB_ADDSTRING, 0, (LPARAM) szString) ; 

If you do not use LBS_SORT, you can insert strings into your list box by specifying an index value with LB_INSERTSTRING:

 SendMessage (hwndList, LB_INSERTSTRING, iIndex, (LPARAM) szString) ; 

For instance, if iIndex is equal to 4, szString becomes the new string with an index value of 4—the fifth string from the top because counting starts at 0. Any strings below this point are pushed down. An iIndex value of -1 adds the string to the bottom. You can use LB_INSERTSTRING with list boxes that have the LBS_SORT style, but the list box contents will not be re-sorted. (You can also insert strings into a list box using the LB_DIR message, a topic I discuss in detail toward the end of this chapter.)

You can delete a string from the list box by specifying the index value with the LB_DELETESTRING message:

 SendMessage (hwndList, LB_DELETESTRING, iIndex, 0) ; 

You can clear out the list box by using LB_RESETCONTENT:

 SendMessage (hwndList, LB_RESETCONTENT, 0, 0) ; 

The list box window procedure updates the display when an item is added to or deleted from the list box. If you have a number of strings to add or delete, you may want to temporarily inhibit this action by turning off the control's redraw flag:

 SendMessage (hwndList, WM_SETREDRAW, FALSE, 0) ; 

After you've finished, you can turn the redraw flag back on:

 SendMessage (hwndList, WM_SETREDRAW, TRUE, 0) ; 

A list box created with the LBS_NOREDRAW style begins with the redraw flag turned off.

Selecting and Extracting Entries

The SendMessage calls that carry out the tasks shown below usually return a value. If an error occurs, this value is set to LB_ERR (defined as -1).

After you've put some items into a list box, you can find out how many items are in the list box:

 iCount = SendMessage (hwndList, LB_GETCOUNT, 0, 0) ; 

Some of the other calls are different for single-selection and multiple-selection list boxes. Let's first look at single-selection list boxes.

Normally, you'll let a user select from a list box. But if you want to highlight a default selection, you can use

 SendMessage (hwndList, LB_SETCURSEL, iIndex, 0) ; 

Setting iParam to -1 in this call deselects all items.

You can also select an item based on its initial characters:

 iIndex = SendMessage (hwndList, LB_SELECTSTRING, iIndex,                       (LPARAM) szSearchString) ; 

The iIndex given as the iParam parameter to the SendMessage call is the index following which the search begins for an item with initial characters that match szSearchString. An iIndex value of -1 starts the search from the top. SendMessage returns the index of the selected item, or LB_ERR if no initial characters match szSearchString.

When you get a WM_COMMAND message from the list box (or at any other time), you can determine the index of the current selection using LB_GETCURSEL:

 iIndex = SendMessage (hwndList, LB_GETCURSEL, 0, 0) ; 

The iIndex value returned from the call is LB_ERR if no item is selected.

You can determine the length of any string in the list box:

 iLength = SendMessage (hwndList, LB_GETTEXTLEN, iIndex, 0) ; 

and copy the item into the text buffer:

 iLength = SendMessage (hwndList, LB_GETTEXT, iIndex,                        (LPARAM) szBuffer) ; 

In both cases, the iLength value returned from the call is the length of the string. The szBuffer array must be large enough for the length of the string and a terminating NULL. You may want to use LB_GETTEXTLEN to first allocate some memory to hold the string.

For a multiple-selection list box, you cannot use LB_SETCURSEL, LB_GETCURSEL, or LB_SELECTSTRING. Instead, you use LB_SETSEL to set the selection state of a particular item without affecting other items that might also be selected:

 SendMessage (hwndList, LB_SETSEL, wParam, iIndex) ; 

The wParam parameter is nonzero to select and highlight the item and 0 to deselect it. If the lParam parameter is -1, all items are either selected or deselected. You can also determine the selection state of a particular item using

 iSelect = SendMessage (hwndList, LB_GETSEL, iIndex, 0) ; 

where iSelect is set to nonzero if the item indexed by iIndex is selected and 0 if it is not.

Receiving Messages from List Boxes

When a user clicks on a list box with the mouse, the list box receives the input focus. A parent window can give the input focus to a list box control by using

 SetFocus (hwndList) ; 

When a list box has the input focus, the cursor movement keys, letter keys, and Spacebar can also be used to select items from the list box.

A list box control sends WM_COMMAND messages to its parent. The meanings of the wParam and lParam variables are the same as for the button and edit controls:

LOWORD (wParam) Child window ID
HIWORD (wParam) Notification code
lParam Child window handle

The notification codes and their values are as follows:

LBN_ERRSPACE -2
LBN_SELCHANGE 1
LBN_DBLCLK 2
LBN_SELCANCEL 3
LBN_SETFOCUS 4
LBN_KILLFOCUS 5

The list box control sends the parent window LBN_SELCHANGE and LBN_DBLCLK codes only if the list box window style includes LBS_NOTIFY.

The LBN_ERRSPACE code indicates that the list box control has run out of space. The LBN_SELCHANGE code indicates that the current selection has changed; these messages occur as the user moves the highlight through the list box, toggles the selection state with the Spacebar, or clicks an item with the mouse. The LBN_DBLCLK code indicates that a list box item has been double-clicked with the mouse. (The notification code values for LBN_SELCHANGE and LBN_DBLCLK refer to the number of mouse clicks.)

Depending on your application, you may want to use either LBN_SELCHANGE or LBN_DBLCLK messages or both. Your program will get many LBN_SELCHANGE messages, but LBN_DBLCLK messages occur only when the user double-clicks with the mouse. If your program uses double-clicks, you'll need to provide a keyboard interface that duplicates LBN_DBLCLK.

A Simple List Box Application

Now that you know how to create a list box, fill it with text items, receive messages from the list box, and extract strings, it's time to program an application. The ENVIRON program, shown in Figure 9-8, uses a list box in its client area to display the name of your current operating system environment variables (such as PATH and WINDIR). As you select an environment variable, the environment string is displayed across the top of the client area.

Figure 9-8. The ENVIRON program.

ENVIRON.C

 /*----------------------------------------    ENVIRON.C -- Environment List Box                 (c) Charles Petzold, 1998   ----------------------------------------*/ #include <windows.h> #define ID_LIST     1 #define ID_TEXT     2 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,                     PSTR szCmdLine, int iCmdShow) {      static TCHAR szAppName[] = TEXT ("Environ") ;      HWND         hwnd ;      MSG          msg ;      WNDCLASS     wndclass ;            wndclass.style         = CS_HREDRAW | CS_VREDRAW ;      wndclass.lpfnWndProc   = WndProc ;      wndclass.cbClsExtra    = 0 ;      wndclass.cbWndExtra    = 0 ;      wndclass.hInstance     = hInstance ;      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;      wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1) ;      wndclass.lpszMenuName  = NULL ;      wndclass.lpszClassName = szAppName ;            if (!RegisterClass (&wndclass))      {           MessageBox (NULL, TEXT ("This program requires Windows NT!"),                       szAppName, MB_ICONERROR) ;           return 0 ;      }            hwnd = CreateWindow (szAppName, TEXT ("Environment List Box"),                           WS_OVERLAPPEDWINDOW,                           CW_USEDEFAULT, CW_USEDEFAULT,                           CW_USEDEFAULT, CW_USEDEFAULT,                           NULL, NULL, hInstance, NULL) ;            ShowWindow (hwnd, iCmdShow) ;      UpdateWindow (hwnd) ;            while (GetMessage (&msg, NULL, 0, 0))      {           TranslateMessage (&msg) ;           DispatchMessage (&msg) ;      }      return msg.wParam ; } void FillListBox (HWND hwndList)  {      int     iLength ;      TCHAR * pVarBlock, * pVarBeg, * pVarEnd, * pVarName ;      pVarBlock = GetEnvironmentStrings () ;  // Get pointer to environment block      while (*pVarBlock)      {           if (*pVarBlock != `=`)   // Skip variable names beginning with `=`           {                pVarBeg = pVarBlock ;              // Beginning of variable name                while (*pVarBlock++ != `=`) ;      // Scan until `=`                pVarEnd = pVarBlock - 1 ;          // Points to `=` sign                iLength = pVarEnd - pVarBeg ;      // Length of variable name                     // Allocate memory for the variable name and terminating                     // zero. Copy the variable name and append a zero.                pVarName = calloc (iLength + 1, sizeof (TCHAR)) ;                CopyMemory (pVarName, pVarBeg, iLength * sizeof (TCHAR)) ;                pVarName[iLength] = `\0' ;                     // Put the variable name in the list box and free memory.                SendMessage (hwndList, LB_ADDSTRING, 0, (LPARAM) pVarName) ;                free (pVarName) ;           }           while (*pVarBlock++ != `\0') ;     // Scan until terminating zero      }      FreeEnvironmentStrings (pVarBlock) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {      static HWND  hwndList, hwndText ;      int          iIndex, iLength, cxChar, cyChar ;      TCHAR      * pVarName, * pVarValue ;      switch (message)      {      case WM_CREATE :           cxChar = LOWORD (GetDialogBaseUnits ()) ;           cyChar = HIWORD (GetDialogBaseUnits ()) ;                // Create listbox and static text windows.           hwndList = CreateWindow (TEXT ("listbox"), NULL,                               WS_CHILD | WS_VISIBLE | LBS_STANDARD,                               cxChar, cyChar * 3,                               cxChar * 16 + GetSystemMetrics (SM_CXVSCROLL),                               cyChar * 5,                               hwnd, (HMENU) ID_LIST,                               (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE),                               NULL) ;                      hwndText = CreateWindow (TEXT ("static"), NULL,                               WS_CHILD | WS_VISIBLE | SS_LEFT,                               cxChar, cyChar,                                GetSystemMetrics (SM_CXSCREEN), cyChar,                               hwnd, (HMENU) ID_TEXT,                               (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE),                               NULL) ;           FillListBox (hwndList) ;           return 0 ;                 case WM_SETFOCUS :           SetFocus (hwndList) ;           return 0 ;      case WM_COMMAND :           if (LOWORD (wParam) == ID_LIST && HIWORD (wParam) == LBN_SELCHANGE)           {                     // Get current selection.                iIndex  = SendMessage (hwndList, LB_GETCURSEL, 0, 0) ;                iLength = SendMessage (hwndList, LB_GETTEXTLEN, iIndex, 0) + 1 ;                pVarName = calloc (iLength, sizeof (TCHAR)) ;                SendMessage (hwndList, LB_GETTEXT, iIndex, (LPARAM) pVarName) ;                     // Get environment string.                iLength = GetEnvironmentVariable (pVarName, NULL, 0) ;                pVarValue = calloc (iLength, sizeof (TCHAR)) ;                GetEnvironmentVariable (pVarName, pVarValue, iLength) ;                     // Show it in window.                                SetWindowText (hwndText, pVarValue) ;                free (pVarName) ;                free (pVarValue) ;           }           return 0 ;      case WM_DESTROY :           PostQuitMessage (0) ;           return 0 ;      }      return DefWindowProc (hwnd, message, wParam, lParam) ; } 

ENVIRON creates two child windows: a list box with the style LBS_STANDARD and a static window with the style SS_LEFT (left-justified text). ENVIRON uses the GetEnvironmentStrings function to obtain a pointer to a memory block containing all the environment variable names and values. ENVIRON parses through this block in its FillListBox function, using the message LB_ADDSTRING to direct the list box window procedure to place each string in the list box.

When you run ENVIRON, you can select an environment variable using the mouse or the keyboard. Each time you change the selection, the list box sends a WM_COMMAND message to the parent window, which is WndProc. When WndProc receives a WM_COMMAND message, it checks to see whether the low word of wParam is ID_LIST (the child ID of the list box) and whether the high word of wParam (the notification code) is equal to LBN_SELCHANGE. If so, it obtains the index of the selection using the LB_GETCURSEL message and the text itself—the environment variable name—using LB_GETTEXT. The ENVIRON program uses the C function GetEnvironmentVariable to obtain the environment string corresponding to that variable and SetWindowText to pass this string to the static child window control, which displays the text.

Listing Files

I've been saving the best for last: LB_DIR, the most powerful list box message. This function call fills the list box with a file directory list, optionally including subdirectories and valid disk drives:

 SendMessage (hwndList, LB_DIR, iAttr, (LPARAM) szFileSpec) ; 

Using file attribute codes

The iAttr parameter is a file attribute code. The least significant byte is a file attribute code that can be a combination of the values in the following table.

iAttr Value Attribute
DDL_READWRITE 0x0000 Normal file
DDL_READONLY 0x0001 Read-only file
DDL_HIDDEN 0x0002 Hidden file
DDL_SYSTEM 0x0004 System file
DDL_DIRECTORY 0x0010 Subdirectory
DDL_ARCHIVE 0x0020 File with archive bit set

The next highest byte provides some additional control over the items desired:

iAttr Value Option
DDL_DRIVES 0x4000 Include drive letters
DDL_EXCLUSIVE 0x8000 Exclusive search only

The DDL prefix stands for "dialog directory list."

When the iAttr value of the LB_DIR message is DDL_READWRITE, the list box lists normal files, read-only files, and files with the archive bit set. When the value is DDL_DIRECTORY, the list includes child subdirectories in addition to these files with the directory names in square brackets. A value of DDL_DRIVES | DDL_DIRECTORY expands the list to include all valid drives where the drive letters are shown between dashes.

Setting the topmost bit of iAttr lists the files with the indicated flag while excluding normal files. For a Windows file backup program, for instance, you might want to list only files that have been modified since the last backup. Such files have their archive bits set, so you would use DDL_EXCLUSIVE | DDL_ARCHIVE.

Ordering file lists

The lParam parameter is a pointer to a file specification string such as "*.*". This file specification does not affect the subdirectories that the list box includes.

You'll want to use the LBS_SORT message for list boxes with file lists. The list box will first list files satisfying the file specification and then (optionally) list subdirectory names. The first subdirectory listing will take this form:

[..]

This "double-dot" subdirectory entry lets the user back up one level toward the root directory. (The entry will not appear if you're listing files in the root directory.) Finally, the specific subdirectory names are listed in this form:

[SUBDIR]

These are followed (also optionally) by a list of valid disk drives in the form

[-A-]

A head for Windows

A well-known UNIX utility named head displays the beginning lines of a file. Let's use a list box to write a similar program for Windows. HEAD, shown in Figure 9-9, lists all files and child subdirectories in the list box. It allows you to choose a file to display by double-clicking on the filename with the mouse or by pressing the Enter key when the filename is selected. You can also change the subdirectory using either of these methods. The program displays up to 8 KB of the beginning of the file in the right side of the client area of HEAD's window.

Figure 9-9. The HEAD program.

HEAD.C

 /*----------------------------------------    HEAD.C -- Displays beginning (head) of file              (c) Charles Petzold, 1998   ----------------------------------------*/ #include <windows.h> #define ID_LIST     1 #define ID_TEXT     2 #define MAXREAD     8192 #define DIRATTR     (DDL_READWRITE | DDL_READONLY | DDL_HIDDEN | DDL_SYSTEM | \                      DDL_DIRECTORY | DDL_ARCHIVE  | DDL_DRIVES) #define DTFLAGS     (DT_WORDBREAK | DT_EXPANDTABS | DT_NOCLIP | DT_NOPREFIX) LRESULT CALLBACK WndProc  (HWND, UINT, WPARAM, LPARAM) ; LRESULT CALLBACK ListProc (HWND, UINT, WPARAM, LPARAM) ; WNDPROC OldList ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,                     PSTR szCmdLine, int iCmdShow) {      static TCHAR szAppName[] = TEXT ("head") ;      HWND         hwnd ;      MSG          msg ;      WNDCLASS     wndclass ;            wndclass.style         = CS_HREDRAW | CS_VREDRAW ;      wndclass.lpfnWndProc   = WndProc ;      wndclass.cbClsExtra    = 0 ;      wndclass.cbWndExtra    = 0 ;      wndclass.hInstance     = hInstance ;      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;      wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;      wndclass.lpszMenuName  = NULL ;      wndclass.lpszClassName = szAppName ;            if (!RegisterClass (&wndclass))      {           MessageBox (NULL, TEXT ("This program requires Windows NT!"),                       szAppName, MB_ICONERROR) ;           return 0 ;      }            hwnd = CreateWindow (szAppName, TEXT ("head"),                           WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,                           CW_USEDEFAULT, CW_USEDEFAULT,                           CW_USEDEFAULT, CW_USEDEFAULT,                           NULL, NULL, hInstance, NULL) ;            ShowWindow (hwnd, iCmdShow) ;      UpdateWindow (hwnd) ;            while (GetMessage (&msg, NULL, 0, 0))      {           TranslateMessage (&msg) ;           DispatchMessage (&msg) ;      }      return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {      static BOOL     bValidFile ;      static BYTE     buffer[MAXREAD] ;      static HWND     hwndList, hwndText ;      static RECT     rect ;      static TCHAR    szFile[MAX_PATH + 1] ;      HANDLE          hFile ;      HDC             hdc ;      int             i, cxChar, cyChar ;      PAINTSTRUCT     ps ;      TCHAR           szBuffer[MAX_PATH + 1] ;      switch (message)      {      case WM_CREATE :           cxChar = LOWORD (GetDialogBaseUnits ()) ;           cyChar = HIWORD (GetDialogBaseUnits ()) ;                      rect.left = 20 * cxChar ;           rect.top  =  3 * cyChar ;                      hwndList = CreateWindow (TEXT ("listbox"), NULL,                               WS_CHILDWINDOW | WS_VISIBLE | LBS_STANDARD,                               cxChar, cyChar * 3,                               cxChar * 13 + GetSystemMetrics (SM_CXVSCROLL),                               cyChar * 10,                               hwnd, (HMENU) ID_LIST,                               (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE),                               NULL) ;           GetCurrentDirectory (MAX_PATH + 1, szBuffer) ;                      hwndText = CreateWindow (TEXT ("static"), szBuffer,                               WS_CHILDWINDOW | WS_VISIBLE | SS_LEFT,                               cxChar, cyChar, cxChar * MAX_PATH, cyChar,                               hwnd, (HMENU) ID_TEXT,                               (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE),                               NULL) ;                      OldList = (WNDPROC) SetWindowLong (hwndList, GWL_WNDPROC,                                                (LPARAM) ListProc) ;                      SendMessage (hwndList, LB_DIR, DIRATTR, (LPARAM) TEXT ("*.*")) ;           return 0 ;                 case WM_SIZE :           rect.right  = LOWORD (lParam) ;           rect.bottom = HIWORD (lParam) ;           return 0 ;                 case WM_SETFOCUS :           SetFocus (hwndList) ;           return 0 ;                 case WM_COMMAND :           if (LOWORD (wParam) == ID_LIST && HIWORD (wParam) == LBN_DBLCLK)           {                if (LB_ERR == (i = SendMessage (hwndList, LB_GETCURSEL, 0, 0)))                     break ;                                SendMessage (hwndList, LB_GETTEXT, i, (LPARAM) szBuffer) ;                                if (INVALID_HANDLE_VALUE != (hFile = CreateFile (szBuffer,                           GENERIC_READ, FILE_SHARE_READ, NULL,                           OPEN_EXISTING, 0, NULL)))                {                     CloseHandle (hFile) ;                     bValidFile = TRUE ;                     lstrcpy (szFile, szBuffer) ;                     GetCurrentDirectory (MAX_PATH + 1, szBuffer) ;                     if (szBuffer [lstrlen (szBuffer) - 1] != `\\')                          lstrcat (szBuffer, TEXT ("\\")) ;                     SetWindowText (hwndText, lstrcat (szBuffer, szFile)) ;                }                else                {                     bValidFile = FALSE ;                     szBuffer [lstrlen (szBuffer) - 1] = `\0' ;                          // If setting the directory doesn't work, maybe it's                          // a drive change, so try that.                     if (!SetCurrentDirectory (szBuffer + 1))                     {                          szBuffer [3] = `:' ;                          szBuffer [4] = `\0' ;                          SetCurrentDirectory (szBuffer + 2) ;                     }                          // Get the new directory name and fill the list box.                     GetCurrentDirectory (MAX_PATH + 1, szBuffer) ;                     SetWindowText (hwndText, szBuffer) ;                     SendMessage (hwndList, LB_RESETCONTENT, 0, 0) ;                     SendMessage (hwndList, LB_DIR, DIRATTR,                                             (LPARAM) TEXT ("*.*")) ;                }                InvalidateRect (hwnd, NULL, TRUE) ;           }           return 0 ;      case WM_PAINT :           if (!bValidFile)                break ;           if (INVALID_HANDLE_VALUE == (hFile = CreateFile (szFile,                    GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)))           {                bValidFile = FALSE ;                break ;           }           ReadFile (hFile, buffer, MAXREAD, &i, NULL) ;           CloseHandle (hFile) ;                // i now equals the number of bytes in buffer.                // Commence getting a device context for displaying text.           hdc = BeginPaint (hwnd, &ps) ;           SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;           SetTextColor (hdc, GetSysColor (COLOR_BTNTEXT)) ;           SetBkColor   (hdc, GetSysColor (COLOR_BTNFACE)) ;                // Assume the file is ASCII           DrawTextA (hdc, buffer, i, &rect, DTFLAGS) ;           EndPaint (hwnd, &ps) ;           return 0 ;                 case WM_DESTROY :           PostQuitMessage (0) ;           return 0 ;      }      return DefWindowProc (hwnd, message, wParam, lParam) ; }       LRESULT CALLBACK ListProc (HWND hwnd, UINT message,                             WPARAM wParam, LPARAM lParam) {      if (message == WM_KEYDOWN && wParam == VK_RETURN)           SendMessage (GetParent (hwnd), WM_COMMAND,                         MAKELONG (1, LBN_DBLCLK), (LPARAM) hwnd) ;                 return CallWindowProc (OldList, hwnd, message, wParam, lParam) ; } 

In ENVIRON, when we selected an environment variable—either with a mouse click or with the keyboard—the program displayed an environment string. If we used this select-display approach in HEAD, however, the program would be too slow because it would continually need to open and close files as you moved the selection through the list box. Instead, HEAD requires that the file or subdirectory be double-clicked. This presents a bit of a problem because list box controls have no automatic keyboard interface that corresponds to a mouse double-click. As we know, we should provide keyboard interfaces when possible.

The solution? Window subclassing, of course. The list box subclass function in HEAD is named ListProc. It simply looks for a WM_KEYDOWN message with wParam equal to VK_RETURN and sends a WM_COMMAND message with an LBN_DBLCLK notification code back to the parent. The WM_COMMAND processing in WndProc uses the Windows function CreateFile to check for the selection from the list. If CreateFile returns an error, the selection is not a file, so it's probably a subdirectory. HEAD then uses SetCurrentDirectory to change the subdirectory. If SetCurrentDirectory doesn't work, the program assumes the user has selected a drive letter. Changing drives also requires a call to SetCurrentDirectory, except the preliminary dash needs to be avoided and a colon needs to be added. It sends an LB_RESETCONTENT message to the list box to clear out the contents and an LB_DIR message to fill the list box with files from the new subdirectory.

The WM_PAINT message processing in WndProc opens the file using the Windows CreateFile function. This returns a handle to the file that can be passed to the Windows functions ReadFile and CloseHandle.

And now, for the first time in this chapter, we encounter an issue involving Unicode. In a perfect world, perhaps, text files would be recognized by the operating system so that ReadFile could convert an ASCII file into Unicode text, or a Unicode file into ASCII text. But this is not the case. ReadFile just reads the bytes of the file without any conversion. This means that DrawTextA (in an executable compiled without the UNICODE identifier defined) would interpret the text as ASCII and DrawTextW (in the Unicode version) would assume the text is Unicode.

So what the program should really be doing is trying to figure out whether the file has ASCII text or Unicode text and then calling DrawTextA or DrawTextW appropriately. Instead, HEAD takes a much simpler approach and uses DrawTextA regardless.



Programming Windows
Concurrent Programming on Windows
ISBN: 032143482X
EAN: 2147483647
Year: 1998
Pages: 112
Authors: Joe Duffy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net