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