List Box Control

A list box generally provides a series of options, which are shown in a scroll window. The user can select one or more items with the keyboard or mouse operation. The selected items are usually highlighted. The most typical use of list box is the open file dialog box, as shown in Figure 1.

alt

Figure 1 Typical use of list box: “Open File” dialog box

We can create a list box control by calling CreateWindow function with CTRL_LISTBOX as the control class name.

Types and Styles of List Box

List box controls of MiniGUI can be divided into three types: single-selection list box, multiple-selection list box and bitmap list box. A list box is single-selection style by default; namely, the user can only select one item. To create a multiple-selection list box, you should use LBS_MULTIPLESEL style. When using this style, the user can select an item by clicking this item, and cancel the selection by clicking it again. When the list box has the input focus, you can also use the space key to select or cancel selection of an item. The effect of running a multiple-selection list box is as shown in Figure 2.

alt

Figure 2 Multiple-selection list box

Besides the two list box types described above, MiniGUI also provides an advanced list box type. In this type of list box, the list item can be not only text string, but also attached by a bitmap or an icon. With such a list box, we can also display a check box besides the list item to represent checked or uncheck status. To create such an advanced list box, LBS_USEICON or LBS_CHECKBOX style should be specified. Fig 21.3 shows the running effect of the advanced list box. If you want the selection state auto-switched when the user clicks the check box, you can use LBS_AUTOCHECK style. The advanced list box can also have LBS_MULTIPLESEL style.

alt

Figure 3 Advanced list box control

Except the styles described above to distinguish the type of list box, you can also specify other general styles when creating a list box.

By default, the message handler of a list box only shows the list items, with no border around them. You can add border by using the window style WS_BORDE. In addition, you can also add a vertical scroll bar with window style WS_VSCROLL to scroll the list items by the mouse and add a horizontal scroll bar with window style WS_HSCROLL.

The default list box styles does not generate notification message when the user select a list item, so the program must send a message to the list box to get the selection state of item. Therefore, a list box control generally includes LBS_NOTIFY style, which can feed back some state information in time to the application during the user’s operation.

In addition, if you want the list box control sort the list items, you can use another commonly used style LBS_SORT.

Generally, the most commonly used style combination for creating list box is as follows:

(LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER)

Messages of List Box

Adding Item into List Box

After a list box is created, the next step is to add text strings to it. You can realize this by sending messages to the window message handler of the list box by calling SendMessage. The items in a list box can be referred to by an index value; the top-most item has index value of zero. In the following example, hwndList is the handle of the list box control, and index is the index value. When SendMessage is used to pass the text string, lParam is the pointer to the NULL-terminated string.

When the stored content of the list box exceeds the available memory space, SendMessage returns LB_ERRSPACE. If error is caused by other reasons, SendMessage returns LB_ERR. If the operation is successful, SendMessage returns LB_OKAY. We can determine above two errors by testing the non-zero value of SendMessage.

If you adopt LBS_SORT style, or just want to append the new text string as the last item of a list box, the simplest approach to append string into the list box is using LB_ADDSTRING message:

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

We can also use LB_INSERTSTRING to specify an index, and insert a text string to the specified position of the list box.

SendMessage (hwndList, LB_INSERTSTRING, index, (LPARAM)string) ;

For example, if index value is 4, string would be a text string with index 4: the fifth string counting from the beginning (due to zero-based), and all the text strings after this position will move backward. When the index is -1, the string will append to the last position. We can also use LB_INSERTSTRING for a list box with LBS_SORT style, but at this time the list box will ignore the index, and insert the new item according to the sorted result.

It should be noted that, after specifying LBS_CHECKBOX or LBS_USEICON style, when you add an item to a list box, you must use LISTBOXITEMINFO structure, and cannot use the string address directly, for example:

HICON hIcon1;           /* Declare an icon handle */
LISTBOXITEMINFO lbii;  /* Declare a structure variable of the list box item*/

hIcon1 = LoadIconFromFile (HDC_SCREEN, "res/audio.ico", 1);  /* Load an icon */

/* Set the structure information, and add an item */
lbii.hIcon = hIcon1;
lbii.cmFlag = CMFLAG_CHECKED;
lbii.string = "abcdefg";
SendMessage (hChildWnd3, LB_ADDSTRING, 0, (LPARAM)&lbii);

Here, the value of cmFlag can be CMFLAG_CHECKED, CMFLAG_BLANK, or CMFLAG_PARTCHECKED, indicating checked, unchecked, and partly checked, respectively.

We can also display bitmaps in an advanced list box, rather than icons by default. If you want a list box item display a bitmap instead of an icon, you can include IMGFLAG_BITMAP in the flag, and specify the pointer to the bitmap object:

/* Set structure information, and add an item */
lbii.hIcon = (DWORD) GetSystemBitmap (SYSBMP_MAXIMIZE);
lbii.cmFlag = CMFLAG_CHECKED | IMGFLAG_BITMAP;
lbii.string = "abcdefg";
SendMessage (hChildWnd3, LB_ADDSTRING, 0, (LPARAM)&lbii);

Deleting Item from List Box

Send LB_DELETESTRING message and specify the index value, and then you can delete an item with the index from the list box:

SendMessage (hwndList, LB_DELETESTRING, index, 0) ;

We can even use LB_RESETCONTENT message to clear all contents in the list box:

SendMessage (hwndList, LB_RESETCONTENT, 0, 0) ;

Selecting and Getting Item

Send LB_GETCOUNT to get the number of items in the list box:

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

When you need to get the text string of a certain item, you can send LB_GETTEXTLEN message to get the length of the string of the specified item in a list box:

length = SendMessage (hwndList, LB_GETTEXTLEN, index, 0) ;

And then, copy the item to a text buffer by sending the message MSG_GETTEXT:

length = SendMessage (hwndList, LB_GETTEXT, index, (LPARAM)buffer) ;

In these two conditions, the length value returned by above messages is the length of the text. For the length of a NULL-terminate string, the buffer must be big enough. You can use the string length returned by LB_GETTEXTLEN message to allocate some local memory for storing the string.

If we need to set the string of list item, you can send LB_SETTEXT message:

SendMessage (hwndList, LB_SETTEXT, index, buffer) ;

For an advanced list box, we must use LB_GETITEMDATA and LB_SETITEMDATA to get other information of a list item, such as the bitmap object or the handle to icon, state of check box, and these messages also can be used to get or set the text string of item:

HICON hIcon1;           /* Declare an icon handle */
LISTBOXITEMINFO lbii;   /* Declare a struct variable of the list box item info */

hIcon1 = LoadIconFromFile (HDC_SCREEN, "res/audio.ico", 1);  /* Load an icon */

/* Set the structure information, and set an item */
lbii.hIcon = hIcon1;
lbii.cmFlag = CMFLAG_CHECKED;
lbii.string = "new item";
SendMessage (hChildWnd3, LB_SETITEMDATA, index, (LPARAM)&lbii);

The following messages are used to retrieve the selection state of list items; these messages have different calling method for single-selection list box and multiple-selection list box. Let us to look at the single-selection list box first.

Generally, the user selects an item by mouse and keyboard. But we also can control the current selected item by program, at this time, we need send LB_SETCURSEL message:

SendMessage (hwndList, LB_SETCURSEL, index, 0) ;

In contrast, we can use LB_GETCURSEL to get the current selected item:

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

If no item is selected, then the message returns LB_ERR.

For multiple-selection list box, LB_SETCURSEL and LB_GETCURSEL can only be used to set or get the current highlighted item, but cannot get all selected items. But we can use LB_SETSEL to set the selection state of a certain item without affecting other items:

SendMessage (hwndList, LB_SETSEL, wParam, (LPARAM)index) ;

If wParam is not 0, this function selects and highlights an item; if wParam is 0, this function cancels the selection. In contrast, we can use LB_GETSEL to get the selection state of a certain item:

select = SendMessage (hwndList, LB_GETSEL, index, 0) ;

Here, if the item specified by index is selected, select is a non-zero value, else select is 0.

In addition, you can also use LB_GETSELCOUNT message to get the number of all selected items in a multiple-selection list box. Then you can send LB_GETSELITEMS message to get the index values of all the selected items. The following is an example:

int i, sel_count;
    int* sel_items;

    sel_count = SendMessage (hwndList, LB_GETSELCOUNT, 0, 0L) ;
    if (sel_count == 0)
       return;

     sel_items = alloca (sizeof(int)*sel_count);
     SendMessage (hwndList, LB_GETSELITEMS, sel_count, sel_items);
     for (i = 0; i < sel_count; i++) {
          /* sel_items [i] is the index of one selected item */
}

Searching Item Including a Text String

index = SendMessage (hwndList, LB_FINDSTRING, (LPARAM)string) ;

Here, string is the pointer to a string, which should be found; the message returns the index of the fuzzy matched string, and LB_ERR means failure. Using LB_FINDSTRINGEXACT message will search the matched item exactly.

Setting/Getting the Status of Check Mark

status = SendMessage (hwndList, LB_GETCHECKMARK, index, 0) ;

The message LB_GETCHECKMARK returns the check mark status of the check box specified by index. If corresponding item is not found, LB_ERR returned. CMFLAG_CHECKED indicates the check box of the item is checked. CMFLAG_PARTCHECKED indicates the check box of the item is partly checked. CMFLAG_BLANK indicates the check box of the item is not checked.

ret = SendMessage (hwndList, LB_SETCHECKMARK, index, (LPARAM)status) ;

The message LB_SETCHECKMARK sets the check mark status of the check box specified by index to be the value of status. If the item specified by the index is not found, it returns LB_ERR for failure, else returns LB_OKAY for success.

Setting the Bold Status of Item

ret = SendMessage (hwndList, LB_SETITEMBOLD, index, (LPARAM)status) ;

The message LB_ SETITEMBOLD sets the bold status of the item specified by index to be the value of status (TRUE or FALSE). If the item specified by the index is not found, it returns LB_ERR for failure.

Setting/Getting the Disable Status of Item

status = SendMessage (hwndList, LB_GETITEMDISABLE, index, 0) ;

The message LB_GETI TEMDISABLE returns the disable status of the item specified by index. If corresponding item is not found, LB_ERR returned. 1 indicates the item is disabled. 0 indicates the item is not disabled.

ret = SendMessage (hwndList, LB_SETITEMDISABLE, index, (LPARAM)status) ;

The message LB_SETITEMDISABLE sets the disable status of the item specified by index to be the value of status. If the item specified by the index is not found, it returns LB_ERR for failure.

Adding Multiple Items into List Box

The message LB_MULTIADDITEM is used to adding multiple items into List Box. When the stored content of the list box exceeds the available memory space, SendMessage returns LB_ERRSPACE. If error is caused by other reasons, SendMessage returns LB_ERR. If the operation is successful, SendMessage returns LB_OKAY. We can determine above two errors by testing the non-zero value of SendMessage.

If you adopt LBS_SORT style, or just want to append the new text string array as the last items of a list box, sample is as follows:

int num = 2;
const char text[num][] = {“item1”, “item2”};

SendMessage (hwndList, LB_MULTIADDITEM, num, (LPARAM)text);

Parameter hwndList is the handle of the list box control; num is the number of item added, and text is the string text array address.

It should be noted that, after specifying LBS_CHECKBOX or LBS_USEICON style, when you add multiple items to a list box, you must use LISTBOXITEMINFO structure, and cannot use the string array address directly, for example:

int num = 2;
HICON hIcon1;           /* Declare an icon handle */
LISTBOXITEMINFO lbii[num];   /* Declare a struct variable of the list box item info */

hIcon1 = LoadIconFromFile (HDC_SCREEN, "res/audio.ico", 1);  /* Load an icon */

/* Set the structure information, and add items */
lbii[0].hIcon = hIcon1;
lbii[0].cmFlag = CMFLAG_CHECKED;
lbii[0].string = "item1";

lbii[1].hIcon = hIcon1;
lbii[1].cmFlag = CMFLAG_CHECKED;
lbii[1].string = "item2";

SendMessage (hwndList, LB_MULTIADDITEM, num, (LPARAM)lbii);

Other Messages

A list box with style of LBS_SORT uses the standard C function strncmp to sort items. But we can overload the default sort method by using LB_SETSTRCMPFUNC in order to sort items according to the expected method. For example:

static int my_strcmp (const char* s1, const char* s2, size_t n)
{
    int i1 = atoi (s1);
    int i2 = atoi (s2);
    return (i1 – i2);
}

    SendMessage (hwndList, LB_SETSTRCMPFUNC, 0, (LPARAM)my_strcmp);

Thus, the list box will use the user-defined function to sort items. Sort function described above can be used to sort the items in form as 1, 2, 3, 4, 10, 20 etc. according to integer values, while the default sort rule would sort the items above as 1, 10, 2, 20, 3, and 4. Generally speaking, application should use the message to set a new string comparison function before adding any item.

We also can associate an additional 32-bit data with each list item, and get the value at appropriate time. For doing this, we can use LB_SETITEMADDDATA and LB_GETITEMADDDATA messages. The values operated by the two messages have no meaning for the list box control. The control only takes charge to store the value and return the value when needed.

In addition, we can also use LB_SETITEMHEIGHT message to set the height of items, and use LB_GETITEMHEIGHT to get the height. Generally, height of items depends on the size of control font, and varies when the control font changes (call SetWindowFont to change). The users can also set themselves height of items. The actual height will be the maximum of the height set and control font size.

Notification Codes of List Box

Notification codes generated by a list box with LBS_NOTIFY style and their meanings are shown in Table 1.

Table 1 Notification codes of list box

Notification code identifier

Meaning

LBN_ERRSPACE

Indicates that memory allocation failure.

LBN_SELCHANGE

Indicates the current selected item changes

LBN_CLICKED

Indicates click on an item

LBN_DBLCLK

Indicates double click on an item

LBN_SELCANCEL

Indicates cancel of the selection

LBN_SETFOCUS

Indicates gain of input focus

LBN_KILLFOCUS

Indicates loss of input focus

LBN_CLICKCHECKMARK

Indicates click on the check mark

LBN_ENTER

Indicates the user has pressed the ENTER key

A list box control will not send notification messages described above unless the window style of the list box have LBS_NOTIFY style. Certainly, if you have called SetNotificationCallback function to set the notification callback function, the control will not send MSG_COMMAND notification message to its parent window, but call the specified notification callback function directly.

LBN_ERRSPACE indicates that the memory allocation fails. LBN_SELCHANGE indicates the currently selected item has been changed, which occurs in the following cases: the user changes the highlighted item with the keyboard or mouse, or the user switches the selection status with the Space key or mouse. LBN_CLICKED indicates the mouse, which occurs when the user uses the mouse to click an item, clicks the list box. LBN_DBLCLK indicates an item is double clicked by the user if LBS_CHECKBOX style is set, LBN_CLICKCHECKMARK indicates the user clicked the check mark, and if LBS_AUTOCHECK style is set at the same time, the check box will be auto-switched between the checked or unchecked status.

Based on the requirements of applications, either LBN_SELCHANGE or LBN_DBLCLK might be used, or both might be used. Program will receive many messages of LBN_SELCHANGE, but the message of LBN_DBLCLK will be received only when users double click.

Sample Program

The program in List 1 provides an example for the use of list box controls. The program imitates the “Open File” dialog box to realize the file deleting function. Initially, the program lists all the files in the current directory, and the user can also change to other directories through the directory list box. The user can select multiple files to be deleted in the file list box by select the check marks. When the user pushes the “Delete” button, the program will prompt the user. Of course, to protect the user’s files, the program does not delete the files really. The effect of the dialog box created by this program is shown in Figure 4. Please refer to listbox.c file of the demo program package of this guide for complete source code.

List 1 The use of list box controls

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <errno.h>

#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <minigui/control.h>

#define IDL_DIR    100
#define IDL_FILE   110
#define IDC_PATH   120

/* Define dialog box template */
static DLGTEMPLATE DlgDelFiles =
{
    WS_BORDER | WS_CAPTION,
    WS_EX_NONE,
    100, 100, 304, 225,
    "删除文件",
    0, 0,
    7, NULL,
    0
};

static CTRLDATA CtrlDelFiles[] =
{
    {
        CTRL_STATIC,
        WS_VISIBLE | SS_SIMPLE,
        10, 10, 130, 15,
        IDC_STATIC,
        "目录列表框",
        0
    },
    /* This list box displays directories */
    {
        CTRL_LISTBOX,
        WS_VISIBLE | WS_VSCROLL | WS_BORDER | LBS_SORT | LBS_NOTIFY,
        10, 30, 130, 100,
        IDL_DIR,
        "",
        0
    },
    {
        CTRL_STATIC,
        WS_VISIBLE | SS_SIMPLE,
        150, 10, 130, 15,
        IDC_STATIC,
       "文件列表框",
        0
    },
    /* This list box displays files with a check box in front for each file */
    {
        CTRL_LISTBOX,
        WS_VISIBLE | WS_VSCROLL | WS_BORDER | LBS_SORT | LBS_AUTOCHECKBOX,
        150, 30, 130, 100,
        IDL_FILE,
        "",
        0
    },
    /* This static control is used to display the current path information */
    {
        CTRL_STATIC,
        WS_VISIBLE | SS_SIMPLE,
        10, 150, 290, 15,
        IDC_PATH,
       "路径:",
        0
    },
    {
        "button",
        WS_VISIBLE | BS_DEFPUSHBUTTON | WS_TABSTOP | WS_GROUP,
        10, 170, 130, 25,
        IDOK,
        "删除",
        0
    },
    {
        "button",
        WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
        150, 170, 130, 25,
        IDCANCEL,
        "取消",
        0
    },
};

/* This function gets all the directory items in the current directory,
 * and adds them to the directory list box and file list box, respectively
 */
static void fill_boxes (HWND hDlg, const char* path)
{
    struct dirent* dir_ent;
    DIR*   dir;
    struct stat ftype;
    char   fullpath [PATH_MAX + 1];

    SendDlgItemMessage (hDlg, IDL_DIR, LB_RESETCONTENT, 0, (LPARAM)0);
    SendDlgItemMessage (hDlg, IDL_FILE, LB_RESETCONTENT, 0, (LPARAM)0);
    SetWindowText (GetDlgItem (hDlg, IDC_PATH), path);

    if ((dir = opendir (path)) == NULL)
         return;

    while ( (dir_ent = readdir ( dir )) = NULL ) {

        /* Assemble full path name. */
        strncpy (fullpath, path, PATH_MAX);
        strcat (fullpath, "/");
        strcat (fullpath, dir_ent->d_name);

        if (stat (fullpath, &ftype) < 0 ) {
           continue;
        }

        if (S_ISDIR (ftype.st_mode))
            SendDlgItemMessage (hDlg, IDL_DIR, LB_ADDSTRING, 0, (LPARAM)dir_ent->d_name);
        else if (S_ISREG (ftype.st_mode)) {
            /* When using the list box of a checkbox,
             * the following structure need to be used */
            LISTBOXITEMINFO lbii;

            lbii.string = dir_ent->d_name;
            lbii.cmFlag = CMFLAG_BLANK;
            lbii.hIcon = 0;
            SendDlgItemMessage (hDlg, IDL_FILE, LB_ADDSTRING, 0, (LPARAM)&lbii);
        }
    }

    closedir (dir);
}

static void dir_notif_proc (HWND hwnd, int id, int nc, DWORD add_data)
{
    /* When the user double clicked the directory name or
     * pressed the ENTER key, he will enter the corresponding directory */
    if (nc == LBN_DBLCLK || nc == LBN_ENTER) {
        int cur_sel = SendMessage (hwnd, LB_GETCURSEL, 0, 0L);
        if (cur_sel >= 0) {
            char cwd [MAX_PATH + 1];
            char dir [MAX_NAME + 1];
            GetWindowText (GetDlgItem (GetParent (hwnd), IDC_PATH), cwd, MAX_PATH);
            SendMessage (hwnd, LB_GETTEXT, cur_sel, (LPARAM)dir);

            if (strcmp (dir, ".") == 0)
                return;
            strcat (cwd, "/");
            strcat (cwd, dir);
            /* Fill the two list boxes again */
            fill_boxes (GetParent (hwnd), cwd);
        }
    }
}

static void file_notif_proc (HWND hwnd, int id, int nc, DWORD add_data)
{
    /* Do nothing */
}

static void prompt (HWND hDlg)
{
    int i;
    char files [1024] = "你选择要删除的文件是:\n";

    /* Get all the checked files */
    for (i = 0; i < SendDlgItemMessage (hDlg, IDL_FILE, LB_GETCOUNT, 0, 0L); i++) {
    char file [MAX_NAME + 1];
        int status = SendDlgItemMessage (hDlg, IDL_FILE, LB_GETCHECKMARK, i, 0);
        if (status == CMFLAG_CHECKED) {
            SendDlgItemMessage (hDlg, IDL_FILE, LB_GETTEXT, i, (LPARAM)file);
            strcat (files, file);
            strcat (files, "\n");
        }
    }

    /* Prompt the user */
    MessageBox (hDlg, files, "确认删除", MB_OK | MB_ICONINFORMATION);

    /* Here these files are actually deleted */
}

static int DelFilesBoxProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
    case MSG_INITDIALOG:
    {
        char cwd [MAX_PATH + 1];
        SetNotificationCallback (GetDlgItem (hDlg, IDL_DIR), dir_notif_proc);
        SetNotificationCallback (GetDlgItem (hDlg, IDL_FILE), file_notif_proc);
        fill_boxes (hDlg, getcwd (cwd, MAX_PATH));
        return 1;
    }

    case MSG_COMMAND:
        switch (wParam) {
        case IDOK:
            prompt (hDlg);
        case IDCANCEL:
            EndDialog (hDlg, wParam);
            break;
        }
        break;

    }

    return DefaultDialogProc (hDlg, message, wParam, lParam);
}

int MiniGUIMain (int argc, const char* argv[])
{
#ifdef _MGRM_PROCESSES
    JoinLayer(NAME_DEF_LAYER , "listbox" , 0 , 0);

#endif

    DlgDelFiles.controls = CtrlDelFiles;

    DialogBoxIndirectParam (&DlgDelFiles, HWND_DESKTOP, DelFilesBoxProc, 0L);

    return 0;
}

#ifndef _MGRM_PROCESSES
#include <minigui/dti.c>
#endif
alt

Figure 4 “Delete File” dialog box


<< Button Control | Table of Contents | Edit Box Control >>

Last updated