Книги

3. Стандартные элементы управления (первая часть)


На данном занятии мы разберем так называемые стандартные элементы управления Windows. Кроме уже использованных ранее кнопок, это: статические элементы, смысл которых в отображении текста или картики, разного рода перелючатели, списки выбора, поля ввода, комбинированные списки и полосы прокрутки. Чтобы не захламлять тестовый пример, списки и полосы прокрутки мы отложим на следующие занятия.

Как уже говорилось, элементы управления являются дочерними окнами. Соответственно и создаются - как и все окна - функцией CreateWindowEx().

Будем рассматривать по классам.


3.1 Класс "BUTTON"

С данным классом мы уже сталкивались на предыдущем занятии. Повторять снова да ладом работу с обычными кнопками не будем. Рассмотрим другие варианты элементов того же класса.


3.1.1 Флажки

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


3.1.2 Варианты выбора и группы

Теперь рассмотрим элементы управления, весьма похожие на флажки за исключением того, что состояние одного элемента зависит от состояния других. Если быть точным, то только один элемент в группе может быть выбран.

. . . . .
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, чем мы и воспользовались для второй группы.


3.2 Поля ввода

Итак, рассмотрим стандартные элементы управления, называемые полями ввода. Они относятся к оконному классу "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 и WS_VSCROLL - создают горизонтальную и вертикальную полосы прокрутки соответственно.

Вообще, для полей ввода существует множество управляющих сообщений. Нас будут интересовать главным образом два: WM_GETTEXT и WM_SETTEXT. Как видим, это не специфические сообщения, а общие для всех окон. Для любого элемента управления они позволяют получать и изменять его текст. Для полей ввода это наиболее атуальное действие. Однако, само использование этих сообщений мы оставим на потом, пока лишь разберем их параметры.

Параметры сообщения WM_GETTEXT:

  • cchTextMax - wParam - максимальное число символов, которое поместится в буфер, нами предварительно выделенный.
  • lpszText - lParam - указатель на данный буфер.

Оконная функция обработав данное сообщение возвращает количество символов, реально скопированных в буфер.

Для того, чтобы узнать, какой размер буфера нам понадобится, следует использовать сообщение WM_GETTEXTLENGTH, которое параметров не имеет, а возвращает соответственно, размер текста элемента управления.

Сообщение WM_SETTEXT:

  • lpszText - lParam - указатель на нуль-терминированную строку с текстом, который следует установить.

Возвращаемое значение соотвествует успешности выполненного действия.

Вообще, как правило, в сообщениях Windows соблюдается традиция, когда различные указатели передаются через lParam, а размеры структур, длины строк и так далее - через wParam.

Так. Отвлечемся пока от полей ввода и займемся статическими элементами.


3.3 Статические элементы управления

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

Итак, что мы здесь видим?

  • Во-первых, отсутствуют расширенные стили. До этого мы их использовали для того, чтобы получить 3d-окантовку для элемента управления. Сейчас же решили обойтись без этого. В результате получим иконку так, как будто мы ее просто нарисовали в нашем окне. Кстати, вообще-то выгодней именно рисовать, но как это делается, мы рассмотрим несколько позже.
  • Во-вторых, в качестве имени (текста) окна мы используем nil. Данный текст система должна рассмаривать как имя ресурса иконки, а мы такового ресурса не создавали.
  • В-третьих - стиль SS_ICON, который, соответственно, и обозначает статический элемент-иконку.
  • И в-четвертых, для того, чтобы установить нужную иконку, мы используем сообщение STM_SETIMAGE, первый параметр которого - тип изображения, второй - его дескриптор (handle).

Замечу, что изменять изображение мы можем и при дальнейшей работе программы. Например, для индикации некоего состояния.


3.4 Резюме

Итак, мы рассмотрели часть стандартных элементов управления. Я бы особенно рекомендовал обратить внимание на сообщения, которые мы использовали. В первую очередь - WM_COMMAND и WM_GET/SETTEXT. С ними нам часто придется сталкиваться.

Ну и как обычно: пример и скриншоты.


Рис. 3-1. Окно программы


Рис. 3-2. Реакция

Актуальные версии
FPC3.2.2release
Lazarus3.2release
MSE5.10.0release
fpGUI1.4.1release
links