На данном занятии мы разберем так называемые стандартные элементы управления Windows. Кроме уже использованных ранее кнопок, это: статические элементы, смысл которых в отображении текста или картики, разного рода перелючатели, списки выбора, поля ввода, комбинированные списки и полосы прокрутки. Чтобы не захламлять тестовый пример, списки и полосы прокрутки мы отложим на следующие занятия.
Как уже говорилось, элементы управления являются дочерними окнами.
Соответственно и создаются - как и все окна - функцией
CreateWindowEx()
.
Будем рассматривать по классам.
"BUTTON"
С данным классом мы уже сталкивались на предыдущем занятии. Повторять снова да ладом работу с обычными кнопками не будем. Рассмотрим другие варианты элементов того же класса.
Флажками мы будем называть элементы управления, представляющие собой строку текста и пимпочку с галочкой - checkbox по-английски. Я бы выделил два варианта этого элемента: с двумя состояниями (есть галочка на пимпочке vs нет галочки) и с тремя состояниями (еще одно - серое - как правило, трактуется в качестве неопределенного).
. . . . . const ID_CHECKBOX_1 = $101; ID_CHECKBOX_2 = $102; . . . . . ctl := CreateWindowEx ( 0, 'BUTTON', 'Флажок', WS_CHILD or WS_VISIBLE or BS_AUTOCHECKBOX, 10, 10, 150, 20, Wnd, ID_CHECKBOX_1, HInstance, nil ); . . . . . ctl := CreateWindowEx ( 0, 'BUTTON', 'Флажок с неопределенным состоянием', WS_CHILD or WS_VISIBLE or BS_AUTO3STATE, 10, 30, 300, 20, Wnd, ID_CHECKBOX_2, HInstance, nil ); . . . . .
Как видно, разница в стилях BS_AUTOCHECKBOX
и
BS_AUTO3STATE
. Есть и аналоги этих стилей без AUTO
- в этом случае галочку придется ставить явно, а оно нам надо?
При автоматическом переключении явная установка флажка также возможна -
для этого следует послать сообщение BM_SETCHECK
, где в
параметре wParam
следует передать желаемое состояние.
Соответственно, получить состояние флажка мы можем при помощи сообщения
BM_GETCHECK
. Замечу, что флажок, как и вообще стандартные
элементы управления, при клике на него посылает родительскому окну сообщение
WM_COMMAND
. Воспользуемся этим, чтобы вывести пользователю
сообщение о текущем состоянии флажка.
. . . . . const cbStates : array [0..2] of pchar = ('BST_UNCHECKED', 'BST_CHECKED', 'BST_INDETERMINATE'); . . . . . // далее внутри case по COMMAND ID_CHECKBOX_1 : MessageBox (Wnd, cbStates[SendMessage(Ctl, BM_GETCHECK, 0, 0)], 'Флажок', MB_ICONASTERISK); ID_CHECKBOX_2 : MessageBox (Wnd, cbStates[SendMessage(Ctl, BM_GETCHECK, 0, 0)], 'Флажок с неопределенным состоянием', MB_ICONASTERISK) . . . . .
Строки, заложенные нами в константу cbStates
- это имена
констант, которые соответствуют возможным состояниям флажка.
В завершение разговора о флажках создадим еще один - другой формы. Если
к стилям флажка побитово добавить BS_PUSHLIKE
, то он будет
выглядеть как кнопка, способная "залипать" в нажатом состоянии. Код не привожу
- он очевиден (см. пример).
Теперь рассмотрим элементы управления, весьма похожие на флажки за исключением того, что состояние одного элемента зависит от состояния других. Если быть точным, то только один элемент в группе может быть выбран.
. . . . . const ID_RADIOBUTTON_1 = $104; ID_RADIOBUTTON_2 = $105; ID_RADIOBUTTON_3 = $106; ID_RADIOBUTTON_4 = $107; ID_RADIOBUTTON_5 = $108; . . . . . ctl := CreateWindowEx ( 0, 'BUTTON', 'Группа 1 - вариант 1', WS_CHILD or WS_VISIBLE or BS_AUTORADIOBUTTON or WS_GROUP, 10, 80, 300, 20, Wnd, ID_RADIOBUTTON_1, HInstance, nil ); ctl := CreateWindowEx ( 0, 'BUTTON', 'Группа 1 - вариант 2', WS_CHILD or WS_VISIBLE or BS_AUTORADIOBUTTON, 10, 100, 300, 20, Wnd, ID_RADIOBUTTON_2, HInstance, nil ); ctl := CreateWindowEx ( 0, 'BUTTON', 'Группа 1 - вариант 3', WS_CHILD or WS_VISIBLE or BS_AUTORADIOBUTTON, 10, 120, 300, 20, Wnd, ID_RADIOBUTTON_3, HInstance, nil ); ctl := CreateWindowEx ( 0, 'BUTTON', 'Группа 2 - вариант 1', WS_CHILD or WS_VISIBLE or BS_AUTORADIOBUTTON or BS_PUSHLIKE or WS_GROUP, 10, 150, 300, 20, Wnd, ID_RADIOBUTTON_4, HInstance, nil ); ctl := CreateWindowEx ( 0, 'BUTTON', 'Группа 2 - вариант 2', WS_CHILD or WS_VISIBLE or BS_AUTORADIOBUTTON or BS_PUSHLIKE, 10, 170, 300, 20, Wnd, ID_RADIOBUTTON_5, HInstance, nil ); . . . . .
Дополнительный стиль WS_GROUP
означает, что данный элемент
- первый в новой группе.
Обрабатывать WM_COMMAND
мы от них пока не будем. Тем не менее
- они его посылают.
Как вариант, можно обойтись и без WS_GROUP
, но тогда будут друг
от друга зависеть все элементы, имеющие одного родителя. В таком виде
удобно дать им специального родителя - тоже элемент управления -
рамка группы. Она также относится к классу "BUTTON"
и определяется
стилем BS_GROUPBOX
. Вообще, рамка группы может использоваться и для
визуальной группировки других (произвольных) элементов. Издевательства над
нею оставим в качестве домашнего задания.
В заключение - замечу, что к элементам выбора также применим стиль
BS_PUSHLIKE
, чем мы и воспользовались для второй группы.
Итак, рассмотрим стандартные элементы управления, называемые полями ввода.
Они относятся к оконному классу "EDIT"
. Такого
разнообразия, как в классе "BUTTON"
, уже не
наблюдается. Все окна класса можно считать однотипными, и служат они одной
цели - вводу текста. Пожалуй, единственное оправданное их
подразделение - на однострочные и многострочные.
Создадим простейшее однострочное поле:
. . . . . const ID_EDIT_1 = $109; . . . . . ctl := CreateWindowEx ( WS_EX_CLIENTEDGE, 'EDIT', 'Некий текст', WS_CHILD or WS_VISIBLE or ES_AUTOHSCROLL, 10, 200, 300, 20, Wnd, ID_EDIT_1, HInstance, nil ); . . . . .
Думается, все должно быть понятно. Разве что - стиль
ES_AUTOHSCROLL
. Данный стиль означает следующее - когда,
набирая текст, мы доходим до края поля, уже набранный текст автоматически
прокручивается, чтобы новый мог уместиться.
Теперь займемся многострочным:
. . . . . const ID_EDIT_2 = $10A; . . . . . ctl := CreateWindowEx ( WS_EX_CLIENTEDGE, 'EDIT', 'Некий'+#13#10+'многострочный текст', WS_CHILD or WS_VISIBLE or ES_MULTILINE or ES_AUTOHSCROLL or ES_AUTOVSCROLL or ES_WANTRETURN or WS_HSCROLL or WS_VSCROLL, 10, 220, 300, 80, Wnd, ID_EDIT_2, HInstance, nil ); . . . . .
Вот теперь новые стили у нас появились. Разберем их:
ES_MULTILINE
- определяет многострочное поле ввода.ES_AUTOVSCROLL
- автоматическая вертикальная прокрутка.
Аналогичен ES_AUTOHSCROLL
, вступает в действие тогда, когда,
находясь на последней строчке, мы нажимаем клавишу Enter.ES_WANTRETURN
- позволяет обрабатывать нажатие клавиши
Enter как перевод строки. В нашем случае его можно было и не указывать, однако,
если бы мы создавали модальное диалоговое окно, то клавиша Enter сработала б как
нажатие кнопки по умолчанию.WS_HSCROLL
и Вообще, для полей ввода существует множество управляющих сообщений. Нас будут
интересовать главным образом два: WM_GETTEXT
и
WM_SETTEXT
. Как видим, это не специфические сообщения, а общие для
всех окон. Для любого элемента управления они позволяют получать
и изменять его текст. Для полей ввода это наиболее атуальное действие. Однако,
само использование этих сообщений мы оставим на потом, пока
лишь разберем их параметры.
Параметры сообщения WM_GETTEXT
:
cchTextMax
- wParam
- максимальное
число символов, которое поместится в буфер, нами предварительно выделенный.lpszText
- lParam
- указатель на
данный буфер.Оконная функция обработав данное сообщение возвращает количество символов, реально скопированных в буфер.
Для того, чтобы узнать, какой размер буфера нам понадобится, следует
использовать сообщение WM_GETTEXTLENGTH
, которое
параметров не имеет, а возвращает соответственно, размер текста
элемента управления.
Сообщение WM_SETTEXT
:
lpszText
- lParam
- указатель на
нуль-терминированную строку с текстом, который следует установить.Возвращаемое значение соотвествует успешности выполненного действия.
Вообще, как правило, в сообщениях Windows соблюдается традиция, когда
различные указатели передаются через lParam
, а размеры
структур, длины строк и так далее - через wParam
.
Так. Отвлечемся пока от полей ввода и займемся статическими элементами.
Статическими мы будем называть стандартные элементы управления, принадлежащие
оконному классу "STATIC"
. Их основная особенность -
то, что они никак не реагируют на действия пользователя. То есть - сами по
себе не реагируют, программно же можно и заставить.
Для начала создадим элемент со статическим текстом:
. . . . . const ID_STATIC_1 = $10B; . . . . . ctl := CreateWindowEx ( WS_EX_CLIENTEDGE, 'STATIC', 'Некий'+#13#10+'статический текст', WS_CHILD or WS_VISIBLE or SS_LEFT, 10, 310, 260, 40, Wnd, ID_STATIC_1, HInstance, nil ); . . . . .
А теперь - поучимся им управлять. У нас остались незадействованными элементы выбора. Вот сейчас мы вторую группу и припашем. При выборе первого варианта в статический элемент мы запихнем текст из односторочного поля ввода, а при выборе второго - соответственно из многострочного.
Для этого мы добавим обработку WM_COMMAND
с командами
ID_RADIOBUTTON_4
и ID_RADIOBUTTON_5
. Приведу часть
кода для первого вариата (для второго - полностью аналогично):
. . . . . var edt, stc : HWnd; len : dword; str : pchar; . . . . . // Это - внутри оператора case ID_RADIOBUTTON_4 : begin edt := GetDlgItem (Wnd, ID_EDIT_1); len := SendMessage (edt, WM_GETTEXTLENGTH, 0, 0); getMem (str, len + 1); SendMessage (edt, WM_GETTEXT, len + 1, longint(str)); stc := GetDlgItem (Wnd, ID_STATIC_1); SendMessage (stc, WM_SETTEXT, 0, longint(str)); freeMem (str) end; . . . . .
Начинаем разбираться.
Функция GetDlgItem()
возвращает дескриптор дочернего окна по его
идентификатору. Таким образом: вызов GetDlgItem (Wnd, ID_EDIT_1)
дает нам дескриптор однострочного поля ввода.
Теперь нам надо получить текст, введенный в поле. Для этого мы сначала
выясняем его длину, поскольку буфер под текст мы выделяем сами.
Длина текста в символах добывается при помощи сообщения
WM_GETTEXTLENGTH
. При выделении памяти не забываем
накинуть лишний байт на завершающий ноль. Как и в большинстве функций WinAPI
здесь используются строки ASCIIZ.
Затем получаем текст поля ввода. Нам остается только найти дескриптор
статического окна и послать ему сообщение WM_SETTEXT
.
Указатель на строку передается через lParam
.
После установки текста в статическом элементе, память под буфером следует освободить.
Теперь мы создадим другой вид статического элемента - содержащий не текст, а иконку.
. . . . . const ID_STATIC_2 = $10C; . . . . . ctl := CreateWindowEx ( 0, 'STATIC', nil, WS_CHILD or WS_VISIBLE or SS_ICON, 274, 310, 32, 32, Wnd, ID_STATIC_2, HInstance, nil ); SendMessage (ctl, STM_SETIMAGE, IMAGE_ICON, LoadIcon (0, IDI_ASTERISK)) . . . . .
Итак, что мы здесь видим?
nil
. Данный текст система должна рассмаривать как имя ресурса
иконки, а мы такового ресурса не создавали.SS_ICON
, который,
соответственно, и обозначает статический элемент-иконку.STM_SETIMAGE
, первый параметр которого -
тип изображения, второй - его дескриптор (handle).Замечу, что изменять изображение мы можем и при дальнейшей работе программы. Например, для индикации некоего состояния.
Итак, мы рассмотрели часть стандартных элементов управления. Я бы особенно
рекомендовал обратить внимание на сообщения, которые мы использовали.
В первую очередь - WM_COMMAND
и WM_GET/SETTEXT
.
С ними нам часто придется сталкиваться.
Ну и как обычно: пример и скриншоты.
Рис. 3-1. Окно программы
Рис. 3-2. Реакция