Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система Microsoft Windows 3.1 для программиста

© Александр Фролов, Григорий Фролов
Том 12, М.: Диалог-МИФИ, 1993, 255 стр.

[Назад] [Содеожание] [Дальше]

2.1. Кнопки

Для создания кнопки, как мы уже говорили, ваше приложение должно создать дочернее окно на базе предопределенного класса "button". После этого родительское окно будет получать от кнопки сообщение с кодом WM_COMMAND. Этим сообщением кнопка информирует родительское окно о том, что с ней что-то сделали, например, нажали.

Создание кнопки

Для создания кнопки вам надо вызвать функцию CreateWindow. Мы уже рассказывали вам об этой функции в предыдущем томе. С помощью нее мы создавали окна во всех наших приложениях. Для удобства приведем прототип функции CreateWindow еще раз:

HWND CreateWindow(LPCSTR lpszClassName, 
   LPCSTR lpszWindowName, DWORD dwStyle,
   int x, int y, int nWidth, int nHeight,
   HWND hwndParent, HMENU hmenu, HINSTANCE hinst,
   void FAR* lpvParam);

Параметр функции lpszClassName - указатель на строку, содержащую имя класса, на базе которого создается окно. Для создания кнопки необходимо указать имя класса "button".

Параметр функции lpszWindowName - указатель на строку, содержащую заголовок окна (Title Bar). Эта строка будет написана на кнопке.

Параметр dwStyle - стиль создаваемого окна. Этот параметр задается как логическая комбинация отдельных битов. Для кнопки следует задать стиль как комбинацию констант WS_CHILD, WS_VISIBLE и константы, определяющей один из возможных стилей кнопки.

Парамеры x и y функции CreateWindow определяют горизонтальную (x) и вертикальную (y) координату кнопки относительно верхнего левого угла родительского окна.

Параметры nWidth и nHeight определяют, соответственно, ширину и высоту создаваемой кнопки.

Параметр hwndParent определяет идентификатор родительского окна, на поверхности которого создается кнопка.

Параметр hmenu - идентификатор меню или идентификатор порожденного (child) окна. Для каждого создаваемого вами дочернего окна вы должны определить собственный идентификатор. Родительское окно будет получать от дочерних окон сообщения. При помощи идентификатора дочернего окна функция родительского окна сможет определить дочернее окно, пославшее сообщение родительскому окну.

Параметр hinst - идентификатор приложения, которое создает окно. Необходимо использовать значение, передаваемое функции WinMain через параметр hInstance.

Последний параметр функции (lpvParam) представляет собой дальний указатель на область данных, определяемых приложением. Этот параметр передается в функцию окна вместе с сообщением WM_CREATE при создании окна. Для кнопки вы должны указать значение NULL.

Для создания кнопки с надписью "Help" в точке с координатами (10, 30) и размерами (40, 20) можно использовать, например, такой вызов функции CreateWindow:

hHelpButton = CreateWindow("button", "Help",
  WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
  10, 30,
  40, 20,
  hWnd,
  (HMENU)IDB_Help,
  hInstance, NULL);

Стиль кнопки влияет на ее внешний вид и поведение:

Стиль кнопки Внешний вид Описание
BS_3STATE Переключатель, который может находится в одном из трех состояний: включенном (квадратик перечеркнут), выключенном (квадратик не перечеркнут), неактивном (квадратик отображается серым цветом)
BS_AUTO3STATE Аналогично стилю BS_3STATE, но внешний вид кнопки изменяется автоматически при ее переключении
BS_AUTOCHECKBOX Переключатель, который может находиться в одном из двух состояний: включенном или выключенном. Внешний вид кнопки изменяется автоматически при ее переключении
BS_AUTORADIOBUTTON Переключатель, который может находиться в одном из двух состояний: включенном (внутри окружности имеется жирная черная точка) или выключенном (окружность не закрашена). Внешний вид кнопки изменяется автоматически при ее переключении
BS_CHECKBOX Переключатель, который может находиться в одном из двух состояний: включенном или выключенном.
BS_DEFPUSHBUTTON Стандартная кнопка с толстой рамкой вокруг
BS_GROUPBOX Прямоугольная область, внутри которой могут находиться другие кнопки. Обычно используется в диалоговых панелях. Этот орган управления не воспринимает сообщения от мыши или клавиатуры
BS_LEFTTEXT Этот стиль указывается вместе с другими и означает, что текст, расположенный около кнопки, должен находиться слева, а не справа от кнопки
BS_OWNERDRAW Внешний вид определяется родительским окном Внешний вид кнопки определяется родительским окном, которое само рисует кнопку во включенном, выключенном или неактивном состоянии
BS_PUSHBUTTON Стандартная кнопка без рамки
BS_RADIOBUTTON Переключатель, который может находиться в одном из двух состояний: включенном или выключенном.
BS_USERBUTTON Внешний вид определяется родительским окном Устаревший стиль, аналогичный по назначению стилю BS_OWNERDRAW. Не рекомендуется к использованию. Этот стиль не описан в документации SDK для Windows версии 3.1, но определен в файле windows.h

Некоторые стили кнопок, описанные в этой таблице, используются для создания переключателей. Переключатели мы рассмотрим позже.

Таким образом, указав функции CreateWindow класс окна "button", мы создаем кнопку. Но с помощью класса "button" можно реализовать несколько перечисленных видов кнопок. Для уточнения вида кнопки мы дополнительно в стиле окна определяем стиль кнопки, указывая константу с префиксом имени BS_. За исключением константы BS_LEFTTEXT, допустимо указывать только одну из перечисленных выше констант. Константа BS_LEFTTEXT используется совместно с другими стилями кнопок, как правило, при создании кнопок в виде переключателей с текстом, расположенным слева от квадратика или кружка переключателя.

Сообщение WM_COMMAND

Сообщение с кодом WM_COMMAND передается функции родительского окна от органа управления, созданного этим окном. При создании органа управления (например, кнопки на базе класса "button") вы вызываете функцию CreateWindow, которой указываете идентификатор родительского окна и идентификатор органа управления.

Если орган управления изменяет свое состояние (например, когда вы нажали на кнопку), функция родительского окна получает сообщение WM_COMMAND. Вместе с этим сообщением функция родительского окна получает в параметре wParam идентификатор органа управления. Младшее слово параметра lParam содержит идентификатор дочернего окна, т. е. идентификатор окна органа управления. Старшее слово содержит код извещения от органа управления (notification code), по которому можно судить о том, какое действие было выполнено над органом управления.

Когда вы нажимаете на кнопку, родительское окно получает сообщение WM_COMMAND с кодом извещения, равным BN_CLICKED. Получив такое сообщение, приложение определяет, что была нажата кнопка, идентификатор которой находится в параметре wParam.

При использовании кнопки устаревшего стиля BS_USERBUTTON функция родительского окна может получить сообщения с кодами извещения BN_DISABLE, BN_DOUBLECLICKED, BN_HLITE, BN_PAINT, BN_UNHILITE. В новых приложениях эти коды извещения не используются.

Орган управления BS_GROUPBOX не обрабатывает сообщения от мыши или клавиатуры и не посылает в родительское окно сообщение WM_CONTROL. Он используется в качестве объединяющей рамки с заголовком, внутри которой располагаются другие органы управления, такие как кнопки или переключатели.

Приложение BUTTON

Приложение BUTTON демонстрирует способ создания стандартных кнопок и обработку сообщений от них. В главном окне приложения создается две кнопки с названием "Button 1" и "Button 2". Если нажать на одну из них, на экране появится диалоговая панель с сообщением о номере нажатой кнопки (рис. 2.1).

Рис. 2.1. Главное окно приложения BUTTON

Главный файл приложения BUTTON приведен в листинге 2.2.


Листинг 2.1. Файл button\button.cpp


// ----------------------------------------
// Стандартные кнопки
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mem.h>

// Идентификаторы кнопок
#define IDB_Button1 1
#define IDB_Button2 2

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "ButtonAppClass";

// Заголовок окна
char const szWindowTitle[] = "Button Demo";

// =====================================
// Функция WinMain
// =====================================

#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Идентификаторы кнопок
  HWND hButton1, hButton2;

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // задаем размеры и расположение
    CW_USEDEFAULT,       // окна, принятые по умолчанию 
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);               // указатель на дополнительные
                         // параметры

  // Если создать окно не удалось, завершаем приложение
  if(!hwnd)
    return FALSE;

  // Рисуем главное окно
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Создаем первую кнопку
  hButton1 = CreateWindow("button", "Button 1",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 20,
    90, 30,
    hwnd,
    (HMENU) IDB_Button1,
    hInstance, NULL);

  // Создаем вторую кнопку
  hButton2 = CreateWindow("button", "Button 2",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 60,
    90, 30,
    hwnd,
    (HMENU) IDB_Button2,
    hInstance, NULL);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна

  memset(&wc, 0, sizeof(wc));

  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);

  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    // Сообщение приходит, когда вы нажимаете
    // на одну из двух созданных кнопок
    case WM_COMMAND:
    {
      // Если нажата первая кнопка, выводим
      // сообщение
      if(wParam == IDB_Button1)
      {
        MessageBox(hwnd, "Нажата кнопка Button 1",
          "Message WM_COMMAND",MB_OK);
      }
      // Если нажата вторая кнопка, выводим
      // другое сообщение
      else if(wParam == IDB_Button2)
      {
        MessageBox(hwnd, "Нажата кнопка Button 2",
          "Message WM_COMMAND",MB_OK);
      }
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Файл определения модуля приложения приведен в листинге 2.2.


Листинг 2.2. Файл button\button.def


; =============================
; Файл определения модуля
; =============================
NAME BUTTON
DESCRIPTION 'Приложение BUTTON, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

В начале главного файла приложения определены идентификаторы двух создаваемых кнопок:

#define IDB_Button1 1
#define IDB_Button2 2

Функция главного окна будет использовать эти идентификаторы для того чтобы различать кнопки. В этих определениях вы можете использовать произвольные численные значения.

После создания и вывода на экран главного окна приложения функция WinMain создает кнопки, вызывая функцию CreateWindow:

hButton1 = CreateWindow("button", "Button 1",
  WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
  20, 20,
  90, 30,
  hwnd,
  (HMENU) IDB_Button1,
  hInstance, NULL);

Для первой кнопки указывается предопределенный класс окна 'button", заголовок "Button 1", стиль кнопки BS_PUSHBUTTON, расположение, размеры, а также идентификатор кнопки IDB_Button1.

Вторая кнопка создается аналогично. Она имеет те же размеры, но расположена ниже, имеет заголовок "Button 2" и идентификатор IDB_Button2.

В функции окна добавился обработчик сообщения WM_COMMAND. Это сообщение поступает в функцию окна, когда вы нажимаете любую из двух созданных кнопок. Обработчик анализирует идентификатор кнопки, передаваемый вместе с сообщением в параметре wParam, и выводит соответствующее сообщение:

case WM_COMMAND:
{
  if(wParam == IDB_Button1)
  {
    MessageBox(hwnd, "Нажата кнопка Button 1",
      "Message WM_COMMAND",MB_OK);
  }
  else if(wParam == IDB_Button2)
  {
    MessageBox(hwnd, "Нажата кнопка Button 2",
      "Message WM_COMMAND",MB_OK);
  }
  return 0;
}

Управление кнопкой из приложения

У вас есть две возможности управления кнопкой из приложения Windows (или другим органом управления). Во-первых, вы можете, вызывая специальные функции, динамически перемещать орган управления, делать его активным или неактивным, скрывать его или отображать в окне. Во-вторых, кнопке или другому органу управления (как и большинству объектов Windows) можно посылать сообщения, в ответ на которые этот орган будет выполнять различные действия.

Вызов функций управления окном

Для перемещения органа управления внутри окна можно воспользоваться функцией MoveWindow, описанной нами ранее. Функция MoveWindow определяет новое расположение и размеры окна:

BOOL WINAPI MoveWindow(HWND hwnd,
   int nLeft, int nTop, int nWidth, int nHeight,
   BOOL fRepaint);

Параметр hwnd указывает идентификатор перемещаемого окна. Для перемещения органа управления вам необходимо указать его идентификатор, полученный от функции CreateWindow.

Параметр nLeft указывает новую координату левой границы окна, параметр nTop - новую координату нижней границы окна. Эти параметры определяют новое положение органа управления в системе координат, связанной с родительским окном. Напомним, что при перемещении обычного перекрывающегося (overlapped) или временного (pop-up) окна используется экранная система координат.

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

Последний параметр fRepaint представляет собой флаг, определяющий, надо ли перерисовывать окно после его перемещения. Если значение этого параметра равно TRUE, функция окна после перемещения окна получит сообщение WM_PAINT. Если указать это значение как FALSE, никакая часть окна не будет перерисована. При перемещении органа управления в качестве этого параметра следует указать TRUE.

Иногда возникает необходимость заблокировать орган управления. Например, если в данный момент времени или в данной конфигурации программных или аппаратных средств некоторый режим работы приложения недоступен, имеет смысл заблокировать орган управления, включающий такой режим. Это позволит уберечь пользователя от ошибочного включения режима. Использование недоступного режима может закончиться для него в лучшем случае получением предупреждающего сообщения.

Для блокирования и разблокирования органа управления следует пользоваться функцией EnableWindow:

BOOL WINAPI EnableWindow(HWND hWnd, BOOL fEnable);

Функция EnableWindow позволяет разрешать или запрещать поступление сообщений от клавиатуры или мыши в окно или орган управления, идентификатор которого задан параметром hWnd.

Параметр fEnable определяет, будет ли указанное окно заблокировано или наоборот, разблокировано. Для того чтобы заблокировать окно (или орган управления) необходимо для этого парамера указать значение FALSE. Если надо разблокировать окно, используйте значение TRUE.

В любой момент времени приложение может определить, является ли окно или орган управления заблокированным или нет. Для этого следует использовать функцию IsWindowEnabled:

BOOL WINAPI IsWindowEnabled(HWND hWnd);

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

Можно вообще убрать орган управления из окна, скрыв его при помощи функции ShowWindow:

BOOL ShowWindow(HWND hwnd, int nCmdShow);

Функция отображает окно, идентификатор которого задан параметром hwnd, в нормальном, максимально увеличенном или уменьшенном до пиктограммы виде, в зависимости от значения параметра nCmdShow. Если использовать эту функцию для органа управления, вы можете его скрыть, указав в параметре nCmdShow значение SW_HIDE.

Для восстановления органа управления надо вызвать эту функцию с параметром SW_SHOWNORMAL.

Можно изменить текст, написанный на кнопке. Для этого следует использовать функцию SetWindowText:

void WINAPI SetWindowText(HWND hWnd, LPCSTR lpszString);

Эта функция устанавливает новый текст для заголовка окна (или органа управления), идентификатор которого указан при помощи параметра hWnd. Параметр lpszString является дальним указателем на строку символов, закрытую двоичным нулем, которая будет использована в качестве нового заголовка.

И, наконец, вы можете уничтожить созданный вами орган управления. Для этого следует вызвать функцию DestroyWindow:

BOOL WINAPI DestroyWindow(HWND hWnd);

Функция DestroyWindow уничтожает окно, идентификатор которого задан в качестве параметра hWnd, и освобождает все связанные с ним ресурсы.

Возвращаемое значение равно TRUE в случае успеха или FALSE при ошибке.

Передача сообщений органу управления

В операционной системе Windows широко используется практика передачи сообщений от одного объекта к другому. Приложение BUTTON демонстрирует передачу сообщения WM_COMMAND от кнопки родительскому окну, создавшему эту кнопку. Однако родительское окно может само послать сообщение кнопке или любому другому органу управления, если оно знает его идентификатор.

Существует два способа передачи сообщений.

Первый способ - запись сообщения в очередь приложения. Он основан на использовании функции PostMessage:

BOOL WINAPI PostMessage(HWND hWnd, UINT uMsg,
   WPARAM wParam, LPARAM lParam);

Функция PostMessage помещает сообщение в очередь сообщений для окна, указанного параметром hWnd, и сразу возвращает управление. Возвращаемое значение равно TRUE в случае успешной записи сообщения в очередь или FALSE при ошибке. Записанное при помощи функции PostMessage сообщение будет выбрано и обработано в цикле обработки сообщений.

Параметр uMsg задает идентификатор передаваемого сообщения. Параметры wParam и lParam используются для передачи параметров сообщения.

Второй способ - непосредственная передача сообщения функции окна минуя очередь сообщений. Этот способ реализуется функцией SendMessage:

LRESULT WINAPI SendMessage(HWND hWnd, UINT uMsg,
   WPARAM wParam, LPARAM lParam);

Параметры функции SendMessage используются аналогично параметрам функции PostMessage. Но в отличие от последней функция SendMessage вызывает функцию окна и возвращает управление только после возврата из функции окна.

Возвращаемое функцией SendMessage значение зависит от обработчика сообщения в функции окна.

Сообщения для кнопки

Для управления кнопкой вы можете использовать сообщение BM_SETSTATE, которое позволяет установить кнопку в нажатое или отжатое состояние.

Для установки кнопки в нажатое состояние следует передать ей сообщение BM_SETSTATE с параметром wParam, равным TRUE, и lParam, равным 0:

SendMessage(hButton, BM_SETSTATE, TRUE, 0L);

Для возврата кнопки в исходное состояние передайте ей то же самое сообщение, но с параметром wParam, равным FALSE:

SendMessage(hButton, BM_SETSTATE, FALSE, 0L);

Приложение BUTNCTL

Приложение BUTNCTL создает в своем главном окне пять кнопок (рис. 2.2).

Рис. 2.2. Главное окно приложения BUTNCTL

Кнопка "Button 1" работает также, как и в предыдущем приложении. А именно, если на нее нажать, появится диалоговая панель с сообщением о том, что нажата первая кнопка.

Остальные кнопки управляют первой кнопкой. С помощью кнопки "PUSH" вы можете нажимать первую кнопку (и она останется в нажатом состоянии), с помощью кнопки "POP" вы сможете вернуть первую кнопку в исходное состояние. Кнопка "OFF" блокирует первую кнопку, не пропуская в ее функцию окна сообщения от клавиатуры и мыши, кнопка "ON" разблокирует первую кнопку.

Когда вы будете проверять работу приложения, обратите внимание, что с помощью кнопок "PUSH" и "POP" вы сможете изменять состояние первой кнопки, даже если она заблокирована. Это связано с тем, что заблокированное окно не получает сообщения от мыши и клавиатуры, но получает другие сообщения, например, от таймера, других окон или приложений.

Главный файл приложения BUTNCTL приведен в листинге 2.3.


Листинг 2.3. Файл butnctl\butnctl.cpp


// ----------------------------------------
// Управление стандартной кнопкой
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>

// Идентификаторы кнопок
#define IDB_Button1 1
#define IDB_Button2 2
#define IDB_Button3 3
#define IDB_Button4 4
#define IDB_Button5 5

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "ButtonCtlAppClass";

// Заголовок окна
char const szWindowTitle[] = "Button Control Demo";

// Идентификаторы кнопок
HWND hButton1, hButton2, hButton3, hButton4, hButton5;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // задаем размеры и расположение
    CW_USEDEFAULT,       // окна, принятые по умолчанию 
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);               // указатель на дополнительные
                         // параметры
  // Если создать окно не удалось, завершаем приложение
  if(!hwnd)
    return FALSE;

  // Рисуем главное окно
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Создаем пять кнопок
  hButton1 = CreateWindow("button", "Button 1",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 20, 90, 30,
    hwnd, (HMENU) IDB_Button1, hInstance, NULL);

  hButton2 = CreateWindow("button", "Button 2",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 60, 90, 30, hwnd,
    (HMENU) IDB_Button2, hInstance, NULL);

  hButton3 = CreateWindow("button", "Button 3",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 100, 90, 30, hwnd,
    (HMENU) IDB_Button3, hInstance, NULL);

  hButton4 = CreateWindow("button", "Button 4",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 140, 90, 30, hwnd,
    (HMENU) IDB_Button4, hInstance, NULL);

  hButton5 = CreateWindow("button", "Button 5",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 180, 90, 30, hwnd,
    (HMENU) IDB_Button5, hInstance, NULL);

  // Увеличиваем горизонтальный размер
  // первой кнопки
  MoveWindow(hButton1, 20, 20, 180, 30, TRUE);

  // Изменяем надписи на остальных кнопках
  SetWindowText(hButton2, "PUSH");
  SetWindowText(hButton3, "POP");
  SetWindowText(hButton4, "OFF");
  SetWindowText(hButton5, "ON");

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна

  memset(&wc, 0, sizeof(wc));

  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);

  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    // Сообщение приходит, когда вы нажимаете
    // на одну из двух созданных кнопок
    case WM_COMMAND:
    {
      // Если нажата первая кнопка, выводим
      // сообщение
      if(wParam == IDB_Button1)
      {
          MessageBox(hwnd, "Нажата кнопка Button 1",
          "Message WM_COMMAND",MB_OK);
      }
      // Если нажата вторая кнопка,
      // переводим первую кнопку в нажатое состояние
      else if(wParam == IDB_Button2)
      {
        SendMessage(hButton1, BM_SETSTATE, TRUE, 0L);
      }
      // Если нажата третья кнопка,
      // возвращаем первую кнопку в исходное состояние
      else if(wParam == IDB_Button3)
      {
          SendMessage(hButton1, BM_SETSTATE, FALSE, 0L);
      }
      // Если нажата четвертая кнопка,
      // переводим первую кнопку в неактивное состояние
      else if(wParam == IDB_Button4)
      {
          EnableWindow(hButton1, FALSE);
      }
      // Если нажата пятая кнопка,
      // переводим первую кнопку в активное состояние
      else if(wParam == IDB_Button5)
      {
          EnableWindow(hButton1, TRUE);
      }
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

В приложении определены идентификаторы пяти кнопок - от IDB_Button1 до IDB_Button5, а также пять переменных для хранения идентификаторов окон класса 'button".

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

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

Длина первой кнопки увеличивается до 180 пикселов, для чего вызывается функция MoveWindow:

MoveWindow(hButton1, 20, 20, 180, 30, TRUE);

Для всех остальных кнопок изменяются надписи:

SetWindowText(hButton2, "PUSH");
SetWindowText(hButton3, "POP");
SetWindowText(hButton4, "OFF");
SetWindowText(hButton5, "ON");

Функция окна обрабатывает сообщение WM_COMMAND, которое может поступать от всех пяти кнопок. Кнопки различаются по параметру wParam.

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

Если нажать на вторую кнопку (с надписью "DOWN"), функция окна передает сообщение первой кнопке, в результате чего она переходит в нажатое состояние:

SendMessage(hButton1, BM_SETSTATE, TRUE, 0L);

Если нажать на кнопку с надписью "POP", возвращается исходное состояние первой кнопки:

SendMessage(hButton1, BM_SETSTATE, FALSE, 0L);

Кнопка с надписью "OFF" предназначена для перевода первой кнопки в неактивное состояние. Для этого вызывается функция EnableWindow со значением второго параметра, равным FALSE:

EnableWindow(hButton1, FALSE);

И, наконец, последняя, пятая кнопка с надписью "ON" снова возвращает первую кнопку в активное состояние:

EnableWindow(hButton1, TRUE);

Файл определения модуля для приложения BUTNCTL приведен в листинге 2.4.


Листинг 2.4. Файл butnctl\butnctl.def


; =============================
; Файл определения модуля
; =============================
NAME BUTNCTL
DESCRIPTION 'Приложение BUTNCTL, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

Переключатели

Очень часто в приложениях Windows требуется организовать выбор различных режимов работы. На рис. 2.3. показана диалоговая панель "Options", которая используется в текстовом процессоре Microsoft Word for Windows для настройки параметров клавиатуры.

Рис. 2.3. Диалоговая панель для настройки параметров клавиатуры в текстовом процессоре Microsoft Word for Windows

В этой диалоговой панели есть множество различных управляющих органов. В правом нижнем углу расположен орган управления (группа) в виде прямоугольника с заголовком "Context". Это окно класса "button", имеющее стиль BS_GROUPBOX. Внутри него расположены два переключателя. Они созданы также на базе класса "button", но имеют стиль BS_RADIOBUTTON (или BS_AUTORADIOBUTTON).

Группа с названием "Short Key" содержит два переключателя, созданных на базе окна "button", но имеющих стиль BS_CHECKBOX (или BS_AUTOCHECKBOX).

Переключатели BS_RADIOBUTTON и BS_AUTORADIOBUTTON используются аналогично кнопкам переключения диапазонов в радиоприемнике (отсюда и название стиля таких переключателей). Обычно в одной группе располагают несколько таких "радиопереключателей", причем включенным может быть только один (ни один радиоприемник не позволит вам принимать передачи сразу в двух диапазонах). Такие переключатели называются переключателями с зависимой фиксацией, так как включение одного переключателя в группе вызывает выключение остальных. Разумеется, ваше приложение должно само обеспечить такой режим работы переключателей.

Переключатели BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE, BS_AUTO3STATE используются как отдельные независимые переключатели. Из них можно сделать переключатель с независимой фиксацией, в котором одновременно могут быть включены несколько переключателей.

Разумеется, все сказанное выше относительно использования переключателей имеет скорее характер рекомендаций, чем обязательное требование. Однако при разработке приложения вам необходимо позаботиться о том, чтобы интерфейс пользователя соответствовал стандарту, изложенному в руководстве по разработке пользовательского интерфейса (это руководство поставляется в составе SDK и называется The Windows Interface: An Application Design Guide). В этом случае органы управления вашего приложения будут делать то, что ожидает от них пользователь, освоивший работу с другими приложениями Windows.

Обычно переключатели и группы переключателей используются в более сложных органах управления - в диалоговых панелях, которыми мы займемся позже. Однако ваше приложение может создавать отдельные переключатели в любом своем окне, как и рассмотренные нами ранее кнопки со стилями BS_PUSHBUTTON или BS_DEFPUSHBUTTON.

Вы можете работать с переключателями типа BS_AUTORADIOBUTTON или BS_AUTOCHECKBOX точно таким же образом, что и с кнопками типа BS_PUSHBUTTON или BS_DEFPUSHBUTTON. Когда вы устанавливаете курсор мыши на такой переключатель и нажимаете левую клавишу мыши, состояние переключателя меняется на противоположное. При этом неперечеркнутый квадратик становится перечеркнутым и наоборот, перечеркнутый квадратик становится неперечеркнутым. Состояние переключателя BS_AUTORADIOBUTTON отмечается жирной точкой, которая для включенного переключателя изображается внутри кружочка.

При изменении состояния переключателя родительское окно получает сообщение WM_COMMAND с кодом извещения BN_CLICKED.

Переключатель, имеющий стиль BS_3STATE или BS_AUTO3STATE, внешне похож на переключатель со стилем BS_CHECKBOX, но дополнительно имеет третье состояние. В этом третьем состоянии он изображается серым цветом и может использоваться, например, для индикации недоступного для установки параметра.

Слово "AUTO" в названии стиля переключателя используется для обозначения режима автоматической перерисовки переключателя при изменении его состояния. О чем здесь идет речь?

Когда вы нажимаете кнопку, имеющую стиль BS_PUSHBUTTON или BS_DEFPUSHBUTTON, она автоматически уходит "вглубь", т. е. автоматически перерисовывается в соответствии со своим текущим состоянием. Переключатели BS_CHECKBOX, BS_RADIOBUTTON, а также BS_3STATE не перерисовываются при их переключении. Вы должны их перерисовывать сами, посылая им сообщение BM_SETCHECK:

SendMessage(hButton, BM_SETCHECK, 1, 0L);

Параметр wParam сообщения BM_SETCHECK определяет состояние переключателя, которое необходимо установить:

Значение Описание
0 Установка переключателя в выключенное состояние (прямоугольник не перечеркнут, в кружке нет точки)
1 Установка переключателя во включенное состояние (прямоугольник перечеркнут, в кружке имеется точка)
2 Установка переключателя в неактивное состояние. Это значение используется только для переключателей, имеющих стиль BS_3STATE или BS_AUTO3STATE. При этом переключатель будет изображен серым цветом

Параметр lParam сообщения BM_SETCHECK должен быть равен 0.

В любой момент времени приложение может узнать состояние переключателя, посылая ему сообщение BM_GETCHECK:

WORD nState;
nState = (WORD) SendMessage(hButton, BM_GETCHECK, 0, 0L);

Парамеры wParam и lParam сообщения BM_GETCHECK должны быть равны 0.

Возвращаемое значение, которое будет записано в переменную nState, может быть равно 0 (для выключенного переключателя), 1 (для включенного) или 2 (для переключателя, который находится в неактивном состоянии и отображается серым цветом).

Мы еще вернемся к переключателям, когда будем заниматься диалоговыми панелями.

Кнопки, которые рисует родительское окно

Если вас не удовлетворяет внешний вид стандартных кнопок (или других стандартных органов управления, созданных на базе класса "button"), вы можете при создании кнопки указать стиль BS_OWNERDRAW. Этот стиль несовместим с остальными стилями кнопок.

Если вы создали кнопку со стилем BS_OWNERDRAW, она будет работать аналогично кнопкам других стилей, но процедура рисования кнопки возлагается на родительское окно. При этом оно может нарисовать кнопку с использованием пиктограмм (с помощью функции DrawIcon), графических изображений bitmap или любым другим способом.

Обычно кнопки BS_OWNERDRAW используют при отображении наборов инструментальных средств, называемых Toolbar. Примером такого набора может служить набор кнопок с пиктограммами, предназначенных для запуска команд в текстовом процессоре Microsoft Word for Windows (рис. 2.4).

Рис. 2.4. Кнопки с пиктограммами

Кнопка BS_OWNERDRAW, как и кнопка BS_PUSHBUTTON, посылает в родительское окно сообщение WM_COMMAND с кодом извещения BN_CLICKED. Дополнительно такая кнопка посылает в родительское окно сообщение WM_DRAWITEM, которое говорит о том, что надо нарисовать орган управления в том или ином состоянии.

Обработчик сообщения WM_DRAWITEM должен вернуть значение TRUE.

Параметр wParam сообщения WM_DRAWITEM содержит идентификатор органа управления, пославшего сообщение WM_DRAWITEM (для органа управления типа меню этот параметр равен 0).

Параметр lParam содержит дальний указатель на структуру типа DRAWITEMSTRUCT, описанную в файле windows.h:

typedef struct tagDRAWITEMSTRUCT
{
  UINT   CtlType;
  UINT   CtlID;
  UINT   itemID;
  UINT   itemAction;
  UINT   itemState;
  HWND   hwndItem;
  HDC    hDC;
  RECT   rcItem;
  DWORD  itemData;
} DRAWITEMSTRUCT;

В файле windows.h определены также ближний и дальний указатели на эту структуру:

typedef DRAWITEMSTRUCT NEAR* PDRAWITEMSTRUCT;
typedef DRAWITEMSTRUCT FAR* LPDRAWITEMSTRUCT;

Анализируя поля структуры DRAWITEMSTRUCT, родительское окно может определить тип и идентификатор органа управления, требующего перерисовки, а также состояние, в котором необходимо изобразить данный орган управления (включен, выключен, получил фокус ввода, неактивен, выбран для использования).

Приведем назначение отдельных полей структуры DRAWITEMSTRUCT.

Имя поля Описание
CtlType Тип органа управления. Может принимать следующие значения:
ODT_BUTTONODT_BUTTON - кнопка;
ODT_COMBOBOXODT_COMBOBOX - орган COMBOBOX (рассмотрим позже);
ODT_LISTBOXODT_LISTBOX - орган LISTBOX (рассмотрим позже);
ODT_MENUODT_MENU - меню
CtlID Идентификатор органа управления. Не используется для меню
itemID Идентификатор строки для органов COMBOBOX, LISTBOX или меню
itemAction Действия, которые необходимо выполнить при изображении органа управления. Определен в виде отдельных битовых флагов:
ODA_DRAWENTIREODA_DRAWENTIRE - требуется перерисовать весь орган управления;
ODA_FOCUSODA_FOCUS - этот бит устанавливается в 1, если орган управления получил или потерял фокус ввода, новое состояние органа управления можно узнать, проанализировав содержимое поля itemState;
ODA_SELECTODA_SELECT - изменилось состояние органа управления (он стал включенным, выключенным или неактивным), для уточнения состояния необходимо использовать поле itemState
itemState Вид, в котором необходимо изобразить орган управления. Определен в виде отдельных битовых флагов:
ODS_CHECKEDODS_CHECKED - выбрана строка меню (этот бит используется только для меню);
ODS_DISABLEDODS_DISABLED - орган управления неактивен;
ODS_FOCUSODS_FOCUS - орган управления получил фокус ввода;
ODS_GRAYEDODS_GRAYED - строка меню должна быть изображена серым цветом (этот бит используется только для меню);
ODS_SELECTEDODS_SELECTED - орган управления выбран
hwndItem Для кнопок, органов управления COMBOBOX и LISTBOX это поле содержит идентификатор окна. Для меню это поле содержит идентификатор меню
hDC Контекст устройства, который необходимо использовать для рисования органа управления
rcItem Прямоугольные границы органа управления, внутри которого его необходимо нарисовать
itemData Используется только для органов управления COMBOBOX и LISTBOX

Приложение OWNBUT

Приложение OWNBUT создает кнопку со стилем BS_OWNERDRAW. Функция главного окна приложения рисует кнопку при помощи трех изображений bitmap, загружая их при необходимости из ресурсов. Первое изображение предназначено для рисования отжатой кнопки, второе - нажатой, и третье - неактивной.

Внешний вид главного окна приложения показан на рис. 2.5.

Рис. 2.5. Главное окно приложения OWNBUT

В верхнем левом углу главного окна расположена кнопка со стилем BS_OWNERDRAW. Остальные кнопки предназначены для управления первой кнопкой и работают так же, как и предыдущем приложении.

Главный файл приложения OWNBTN представлен в листинге 2.5.


Листинг 2.5. Файл ownbut\ownbut.cpp


// ----------------------------------------
// Управление нестандартной кнопкой
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>
#include "ownbut.hpp"

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawButton(LPDRAWITEMSTRUCT);
void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap);

// Имя класса окна
char const szClassName[]   = "OwnButtonAppClass";

// Заголовок окна
char const szWindowTitle[] = "Owner Draw Button Demo";

// Идентификаторы кнопок
HWND hButton1, hButton2, hButton3, hButton4, hButton5;

// Идентификатор копии приложения
HINSTANCE hInst;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
  HINSTANCE hPrevInstance,
  LPSTR     lpszCmdLine, 
  int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Сохраняем идентификатор текущей копии приложения
  // в глобальной переменной 
  hInst = hInstance;

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // задаем размеры и расположение
    CW_USEDEFAULT,       // окна, принятые по умолчанию 
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);               // указатель на дополнительные
                         // параметры

  // Если создать окно не удалось, завершаем приложение
  if(!hwnd)
    return FALSE;

  // Рисуем главное окно
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Создаем пять кнопок

  // Эту кнопку будет рисовать функция
  // родительского окна
  hButton1 = CreateWindow("button", "Button 1",
    WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
    20, 20, 180, 30,
    hwnd, (HMENU) IDB_Button1, hInstance, NULL);

  hButton2 = CreateWindow("button", "PUSH",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 60, 90, 30, hwnd,
    (HMENU) IDB_Button2, hInstance, NULL);

  hButton3 = CreateWindow("button", "POP",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 100, 90, 30, hwnd,
    (HMENU) IDB_Button3, hInstance, NULL);

  hButton4 = CreateWindow("button", "OFF",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 140, 90, 30, hwnd,
    (HMENU) IDB_Button4, hInstance, NULL);

  hButton5 = CreateWindow("button", "ON",
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
    20, 180, 90, 30, hwnd,
    (HMENU) IDB_Button5, hInstance, NULL);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна

  memset(&wc, 0, sizeof(wc));

  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);

  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    // Сообщение приходит, когда вы нажимаете
    // на одну из двух созданных кнопок
    case WM_COMMAND:
    {
      // Если нажата первая кнопка, выводим
      // сообщение
      if(wParam == IDB_Button1)
      {
        MessageBox(hwnd, "Нажата кнопка Button 1",
          "Message WM_COMMAND",MB_OK);
      }
      else if(wParam == IDB_Button2)
      {
        SendMessage(hButton1, BM_SETSTATE, TRUE, 0L);
      }
      else if(wParam == IDB_Button3)
      {
        SendMessage(hButton1, BM_SETSTATE, FALSE, 0L);
      }
      else if(wParam == IDB_Button4)
      {
        EnableWindow(hButton1, FALSE);
      }
      else if(wParam == IDB_Button5)
      {
        EnableWindow(hButton1, TRUE);
      }
      return 0;
    }

    // Это сообщение приходит при изменении состояния
    // дочернего окна органа управления, когда окно
    // нужно перерисовать
    case WM_DRAWITEM:
    {
      // Перерисовываем кнопку
      DrawButton( (LPDRAWITEMSTRUCT)lParam );
      break;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

// =====================================
// Функция DrawButton
// Перерисовывает кнопку
// =====================================

void DrawButton(LPDRAWITEMSTRUCT lpInfo)
{
   HBITMAP hbm;
   int ResourceID;

   // Обрабатываем сообщение WM_DRAWITEM
   // только если оно поступило от кнопки 
   if(lpInfo->CtlType != ODT_BUTTON)
     return;

   // Так как в приложении может быть несколько
   // кнопок, посылающих сообщение WM_DRAWITEM,
   // проверяем идентификатор кнопки
   switch (lpInfo->CtlID)
   {
      case IDB_Button1:
      {
        // Загружаем идентификатор изображения
        // кнопки в нормальном (отжатом) состоянии
        ResourceID = IDR_BUTTONUP;
        break;
      }
      default:
        return;
   }

   // Если кнопка выбрана, рисуем ее в нажатом
   // состоянии
   if (lpInfo->itemState & ODS_SELECTED)
   {
     ResourceID = IDR_BUTTONDOWN;
   }

   // если кнопка неактивна, загружаем идентификатор
   // изображения кнопки в неактивном состоянии
   else if (lpInfo->itemState & ODS_DISABLED)
   {
     ResourceID = IDR_BUTTONGR;
   }

   // Загружаем изображение кнопки из ресурсов приложения
   hbm = LoadBitmap(hInst, MAKEINTRESOURCE(ResourceID));

   // При ошибке ничего не рисуем
   if(!hbm) return;

   // Если кнопка выбрана и ее надо целиком
   // перерисовать, вызываем функцию DrawBitmap
   if((lpInfo->itemAction & ODA_DRAWENTIRE) ||
      (lpInfo->itemAction & ODA_SELECT))
   {
     // Рисуем кнопку
     DrawBitmap(lpInfo->hDC,
       (lpInfo->rcItem).left,
       (lpInfo->rcItem).top , hbm);
   }

   // Удаляем изображение кнопки, так как оно
   // нам больше не нужно
   DeleteObject(hbm);
}

В начале своей работы функция WinMain сохраняет идентификатор текущей копии приложения в глобальной переменной:

hInst = hInstance;

Затем она инициализирует главное окно приложения и создает пять кнопок, первая из которых имеет стиль BS_OWNERDRAW:

hButton1 = CreateWindow("button", "Button 1",
  WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
  20, 20, 180, 30,
  hwnd, (HMENU) IDB_Button1, hInstance, NULL);

Функция главного окна приложения OWNBTN кроме сообщения WM_COMMAND дополнительно обрабатывает сообщение WM_DRAWITEM, в ответ на которое она вызывает функцию DrawButton, определенную в нашем приложении:

case WM_DRAWITEM:
{
  DrawButton( (LPDRAWITEMSTRUCT)lParam );
  break;
}

Функция DrawButton перерисовывает кнопку. В качестве параметра этой функции передается указатель на структуру DRAWITEMSTRUCT, содержащую всю информацию, необходимую для перерисовки кнопки.

Вначале эта функция проверяет тип органа управления, приславшего сообщение WM_DRAWITEM. Тип должен быть равен ODT_BUTTON.

Затем проверяется идентификатор кнопки. В нашем приложении есть только одна кнопка со стилем BS_OWNERDRAW, но их могло бы быть и несколько. В переменную ResourceID загружается идентификатор ресурса (изображения bitmap), соответствующего изображению кнопки в нормальном (отжатом) состоянии:

   switch (lpInfo->CtlID)
   {
      case IDB_Button1:
      {
        ResourceID = IDR_BUTTONUP;
        break;
      }
      default: return;
   }

Далее в зависимости от текущего состояния кнопки на момент прихода сообщения WM_DRAWITEM содержимое переменной ResourceID заменяется на идентификатор соответствующего изображения кнопки.

Для выбранной кнопки (в поле itemState структуры DRAWITEMSTRUCT установлен бит ODS_SELECTED) в переменную ResourceID записывается идентификатор изображения нажатой кнопки IDR_BUTTONDOWN.

Для неактивной кнопки в эту же переменную записывается идентификатор изображения неактивной кнопки IDR_BUTTONGR.

После этого нужное изображение загружается из ресурсов приложения, для чего вызывается функция LoadBitmap:

hbm = LoadBitmap(hInst, MAKEINTRESOURCE(ResourceID));

Далее функция DrawButton проверяет действие, которое нужно выполнить при рисовании кнопки. Если состояние кнопки изменилось (установлен бит ODA_SELECT) и требуется перерисовать всю кнопку (установлен бит ODA_DRAWENTIRE), выполняется вызов функции DrawBitmap, определенной в нашем приложении.

Создавая функцию DrawBitmap, мы сделали так, что количество, назначение и формат ее параметров в точности соответствует функции DrawIcon, с помощью которой мы рисовали пиктограммы (вы можете легко изменить исходный текст приложения OWNBUT для того чтобы для рисования кнопки использовать не изображения bitmap, а пиктограммы; сделайте это самостоятельно). В нашем примере в качестве первого параметра функции передается контекст устройства, полученный в структуре DRAWITEMSTRUCT вместе с сообщением WM_DRAWITEM. Второй и третий параметры используются для определения координат верхнего левого угла кнопки в системе координат, связанной с родительским окном. Последний параметр - идентификатор изображения bitmap, который необходимо нарисовать:

DrawBitmap(lpInfo->hDC,
  (lpInfo->rcItem).left,
  (lpInfo->rcItem).top , hbm);

После того как кнопка нарисована, следует удалить bitmap, загруженный функцией LoadBitmap. Для этого наше приложение вызывает функцию DeleteObject:

DeleteObject(hbm);

Идентификаторы кнопок и изображений bitmap определены в файле ownbut.hpp (листинг 2.6).


Листинг 2.6. Файл ownbut\ownbut.hpp


// Идентификаторы кнопок

#define IDB_Button1 1
#define IDB_Button2 2
#define IDB_Button3 3
#define IDB_Button4 4
#define IDB_Button5 5

#define IDR_BUTTONUP     200
#define IDR_BUTTONDOWN   201
#define IDR_BUTTONGR     202

В файле описания ресурсов приложения (листинг 2.7) определены три изображения bitmap, используемые для рисования кнопки в различных состояниях.


Листинг 2.7. Файл ownbut\ownbut.rc


#include "ownbut.hpp"
IDR_BUTTONUP   BITMAP  mybtnup.bmp
IDR_BUTTONDOWN BITMAP  mybtndn.bmp
IDR_BUTTONGR   BITMAP  mybtngr.bmp

В листинге 2.8 изображен файл mybtnup.bmp. Этот файл содержит объемное изображение кнопки в нормальном (отжатом) состоянии.


Листинг 2.8. Файл ownbut\mybtnup.bmp



Файл mybtndn.bmp (листинг 2.9) хранит изображение кнопки в нажатом состоянии.


Листинг 2.9. Файл ownbut\mybtndn.bmp



Для изображения кнопки в неактивном состоянии используется файл mybtngr.bmp (листинг 2.10).


Листинг 2.10. Файл ownbut\mybtngr.bmp



Для рисования изображения bitmap мы подготовили функцию DrawBitmap (листинг 2.11). Детальное описание этой функции мы отложим до главы, посвященной изображениям bitmap.


Листинг 2.11. Файл ownbut\drawbmp.cpp


// ----------------------------------------
// Рисование изображения типа bitmap
// ----------------------------------------

#define STRICT
#include <windows.h>

void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap)
{
  HBITMAP hbm, hOldbm;
  HDC hMemDC;
  BITMAP bm;
  POINT  ptSize, ptOrg;

  // Создаем контекст памяти, совместимый
  // с контекстом отображения
  hMemDC = CreateCompatibleDC(hDC);

  // Выбираем изображение bitmap в контекст памяти
  hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);

  // Если не было ошибок, продолжаем работу
  if (hOldbm)
  {
    // Для контекста памяти устанавливаем тот же
    // режим отображения, что используется в
    // контексте отображения
    SetMapMode(hMemDC, GetMapMode(hDC));

    // Определяем размеры изображения
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);

    ptSize.x = bm.bmWidth;   // ширина
    ptSize.y = bm.bmHeight;  // высота

    // Преобразуем координаты устройства в логические
    // для устройства вывода
    DPtoLP(hDC, &ptSize, 1);

    ptOrg.x = 0;
    ptOrg.y = 0;

    // Преобразуем координаты устройства в логические
    // для контекста памяти
    DPtoLP(hMemDC, &ptOrg, 1);

    // Рисуем изображение bitmap
    BitBlt(hDC, x, y, ptSize.x, ptSize.y,
        hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);

    // Восстанавливаем контекст памяти
    SelectObject(hMemDC, hOldbm);
  }

  // Удаляем контекст памяти
  DeleteDC(hMemDC);
}

Файл определения модуля для приложения OWNBUT представлен в листинге 2.12.


Листинг 2.12. Файл ownbut\ownbut.def


; =============================
; Файл определения модуля
; =============================
NAME OWNBUT
DESCRIPTION 'Приложение OWNBUT, (C) 1994, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 5120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple

Кнопки и клавиатура

Обычно для работы с кнопками используется мышь. Но, как мы уже говорили, с приложениями Windows вы можете работать и без мыши. В частности, в диалоговых панелях вы можете, нажимая клавишу <Tab>, передавать фокус ввода от одной кнопки к другой. Если кнопка имеет фокус ввода, ее функция окна будет получать сообщения от клавиатуры. Кнопка реагирует только на клавишу пробела - если вы нажмете пробел, когда кнопка имеет фокус ввода, кнопка (или переключатель, который есть ни что иное, как разновидность кнопки) изменит свое состояние.

Для того чтобы ваше приложение могло использовать клавишу <Tab> для передачи фокуса ввода от одного органа управления другому, оно должно создать для клавиши <Tab> свой обработчик сообщения WM_CHAR. Этот обработчик должен установить фокус ввода на следующий (из имеющихся) орган управления, вызвав функцию SetFocus.

К счастью, в Windows имеется объект, специально предназначенный для объединения нескольких органов управления - диалоговая панель. Функции поддержки диалоговых панелей определены внутри Windows. Они выполняют всю работу, необходимую для организации передачи фокуса ввода между различными органами управления, расположенными на диалоговой панели.

[Назад] [Содеожание] [Дальше]