В настоящей главе мы рассмотрим элементы управления между которыми много общего - списки и комбинированные списки. И те, и другие, предназначены для выбора одной или нескольких альтернатив из некоторого набора.
Примечание: В данной главе слово "элемент" используется в двух различных смыслах: как элемент списка (item), и как элемент управления Windows (control), в том числе и сам список. Надеюсь, по контексту прозрачно, какой элемент имеется в виду.
"LISTBOX")Начнем с того, что создадим самый простой список и заполним его строками.
. . . . . const ID_LISTBOX_SINGLE = $101; . . . . . ctl := CreateWindowEx (WS_EX_CLIENTEDGE, 'LISTBOX', '', WS_CHILD or WS_VISIBLE or WS_VSCROLL or LBS_HASSTRINGS or LBS_NOTIFY, 10,10, 135,100, Wnd, ID_LISTBOX_SINGLE, HInstance, nil ); . . . . . SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Первая строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Вторая строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Третья строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Четвертая строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Пятая строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Шестая строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Седьмая строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Восьмая строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Девятая строка'))); SendMessage (ctl, LB_ADDSTRING, 0, longint(pchar('Десятая строка'))); . . . . .
Вообще-то, стиль LBS_HASSTRINGS можно было и не указывать - он подразумевается по умолчанию, если не указаны стили LBS_OWNERDRAWxxx. Следует обратить внимание на стиль WS_VSCROLL, который создает вертикальную полосу прокрутки. У полос прокрутки в списках есть две особенности:
LBS_DISABLENOSCROLL - тогда она показывается всегда, а когда не нужна - серой - недоступной.Стиль LBS_NOTIFY требуется для того, чтобы мы могли получать сообщения когда
пользователь будет взаимодействовать с нашими списками.
Добавление строки производится при помощи специального сообщения LB_ADDSTRING.
При этом в параметре wParam передается 0, а в lParam - указатель на добавляемую
строку, которая должна оканчиваться нулевым символом (ASCIIZ, в Паскале - тип
PChar).
Если не брать во внимание варианты списка с ручной отрисовкой, то остается еще
один - список со множественным выбором. Отличается стилем LBS_EXTENDEDSEL или
LBS_MULTIPLESEL. В первом случае (второй список в прилагаемом примере) возможен
множественный выбор при помощи клавиш Shift и Ctrl; а во втором - клик мышью
устанавливает выделение на текущий элемент, если он не выделен, и снимает с уже
выделенного, не меняя при этом состояния других элементов списка.
"COMBOBOX")Комбинированные списки предствляют собой сочетание списка для выбора и поля редактирования.
Создание комбинированных списков практически аналогично созданию списков обычных за исключением того, что:
"COMBOBOX" (как можно было догадаться);LB_ADDSTRING, а CB_ADDSTRING.Существует три основных разновидности комбинированных списков:
CBS_SIMPLE - список элементов виден
всегда. В нашем примере такого нет.CBS_DROPDOWN - первый пример.CBS_DROPDOWNLIST - второй пример.Для того, чтобы правильно понимать сообщения от списков, следует разобраться
поподробнее с сообщением WM_COMMAND. В предыдущих примерах нам было достаточно
знать, что младшее слово wParam содержит идентификатор элемента, однако сейчас
возникла необходимость различать сообщения о разных действиях со списками.
Параметры сообщения WM_COMMAND:
wNotifyCode - HiWord (wParam) - код события;wID - LoWord (wParam) - идентификатор элемента;hwndCtl - lParam - дескриптор окна элемента.Для примера напишем обработку, которая при изменении выделения в первом списке загонит соответствующую строку в заголовок главного окна.
. . . . . procedure ProcessCommand(Wnd : HWnd; Code : word; ID : word; Ctl : HWnd); var idx : dword; len : dword; str : pchar; begin case ID of ID_LISTBOX_SINGLE : case Code of LBN_SELCHANGE : begin idx := SendMessage (Ctl, LB_GETCURSEL, 0, 0); len := SendMessage (Ctl, LB_GETTEXTLEN, idx, 0); getMem (str, len + 1); SendMessage (Ctl, LB_GETTEXT, idx, longint(str)); SendMessage (Wnd, WM_SETTEXT, 0, longint(str)); freeMem (str) end end end end; . . . . . // внутри оконной функции главного окна WM_COMMAND : ProcessCommand (Wnd, HiWord (wParam), LoWord(wParam), HWnd(lParam)); . . . . .
Итак, по порядку. Сначала мы определяем, что сообщение пришло именно от нужного
нам списка, то есть - ID_LISTBOX_SINGLE. Затем анализируем код события. Кроме
LBN_SELCHANGE здесь могут быть следующие значения:
LBN_DBLCLK - двойной щелчок по списку;LBN_KILLFOCUS - потеря списком фокуса ввода;LBN_SELCANCEL - снято выделение с одного из элементов списка;LBN_SETFOCUS - список получил фокус ввода.Обратим внимание на отсутствие события на простой клик. Дело в том, что простой
клик обязательно должен привести к одному из двух событий - SELCANCEL или
SELCHANGE.
Ну, а определив, что произошло именно нужное нам событие мы начинаем действовать. Как уже привыкли - при помощи сообщений.
LB_GETCURSEL параметров не имеет, возвращаемое значение - индекс
текущего выделения. Заметим, что правильно работает это сообщение только для
списков с одиночным выбором, для списка с множественным выбором будет
возвращен индекс элемента на котором находится курсор (обычно это такая
пунктирная рамка), но для этой цели лучше использовать сообщение
LB_GETCARETINDEX. Для списков множественного выбора можно проверить состояние
каждого элемента сообщением LB_GETSEL.LB_GETTEXTSEL использует один параметр - индекс элемента,
передаваемый в wParam. Возвращает длину строки соответствующего элемента.LB_GETTEXT. wParam - индекс элемента, lParam - адрес буфера, куда строка
будет помещена.WM_SETTEXT устанавливаем новый заголовок.Конечно действия, которые можно совершать со списками, не ограничиваются получением названия текущей строки. Однако, их количество достаточно велико и у меня нет никакого желания здесь их разбирать. Рекомендую раздел SDK, который называется "List Box Messages".
Как и в предыдущем случае, различных действий с комбинированными списками много. Посему остановимся на небольшом специфическом примере, а именно - осуществим часто используемую фишку - автоподстановки подходящей строки при вводе символов в поле ввода.
. . . . . ID_COMBO_DROPDOWN : case Code of CBN_EDITCHANGE : begin len := SendMessage (Ctl, WM_GETTEXTLENGTH, 0, 0); getMem (str, len + 1); SendMessage (Ctl, WM_GETTEXT, len + 1, longint(str)); if not (SendMessage (Ctl, CB_FINDSTRING, 0, longint(str)) = CB_ERR) then begin SendMessage (Ctl, CB_SELECTSTRING, 0, longint(str)); SendMessage (Ctl, CB_SETEDITSEL, 0, MAKELPARAM (len, -1)) end; freeMem (str) end end . . . . .
Итак, во-первых - мы обрабатываем событие CBN_EDITCHANGE, которое возникает при
изменении текста поля ввода пользователем.
Во-вторых, для работы с текстом в поле ввода мы используем стандартные
сообщения WM_GETTEXT и WM_GETTEXTLENGTH. А вот дальше - пошла специфика:
CB_FINDSTRING позволяет нам определить, существует ли в списке
строка, начинающаяся с введенных нами символов. Если не существует, то
подставлять нечего.CB_SELECTSTRING работает аналогично CB_FINDSTRING, но при этом еще
и выбирает соответствующую строку.CB_SETEDITSEL позволяет выделить в поле ввода произвольную
подстроку. В этом сообщении HiWord (lParam) должно быть равно конечной
позиции выделения (-1 для того, чтобы выделить все до конца строки, как в
нашем случае), а LoWord (lParam) - начальной позиции. Функция MAKEPARAM()
соответствует сишному макросу и попросту делает из двух значений типа Word
одно типа LongInt.В нашем примере есть одна существенная проблема - не обрабатываются нажатия
Delete и BackSpace. Это происходит потому, что они удаляют в первую очередь
выделенную часть, после чего строка заново находится, подставляется и
выделяется. Для того, чтобы их корректно обрабатывать требуется значительное
усложнение примера - например, хранение промежуточной строки и снятие выделения
вместе с хвостом в случае, если начало строки не изменилось... Хотя наиболее
корректный способ - написать класс-наследник от "COMBOBOX", или реализовать
соответствующую функциональность с нуля - чтобы можно было адекватно реагировать
на любые клавиши.
Аналогично предыдущему, для изучения всех возможностей комбинированных списков рекомендую раздел SDK "Combo Box Messages" и смежные.
Мы вкратце рассмотрели элементы управления, которые позволяют реализовать выбор одной или нескольких альтернатив - списки и комбинированные списки. Пример, описанный в настоящей главе можно найти в архиве. А выглядит он так:

Рис. 4-1. Списки
| FPC | 3.2.2 | release |
| Lazarus | 3.2 | release |
| MSE | 5.10.0 | release |
| fpGUI | 1.4.1 | release |