В настоящей главе мы рассмотрим элементы управления между которыми много общего - списки и комбинированные списки. И те, и другие, предназначены для выбора одной или нескольких альтернатив из некоторого набора.
Примечание: В данной главе слово "элемент" используется в двух различных смыслах: как элемент списка (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. Списки