8. Органы управления

В приложениях Presentation Manager используются различные органы управления, например, кнопки, полосы просмотра, однострочные и многострочные редакторы текстов, различные списки и т. д. Создание таких органов управления с использованием только функций рисования представляет достаточно сложную задачу, так как их поведение нетривиально.

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

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

Как же создаются органы управления в приложениях Presentation Manager?

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

Здесь используется механизм наследования. Приложение создает объект (орган управления), который наследует свойства базового объекта, реализуемые функцией окна, зарегистрированной системой Presentation Manager для того или иного предопределенного класса окна.

При необходимости программист может переопределить те или иные методы (обработчики отдельных сообщений) для объекта, используя технику, которая называется Window Subclassing . Однако для обычных органов управления в большинстве случаев этого делать не нужно.

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

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

8.1. Кнопки

Работая в среде Workplace Shell с приложениями Presentation Manager, вы имели дело с кнопками и переключателями. Последние бывают квадратной либо круглой формы. Переключатели создаются на базе того же самого предопределенного класса окна, что и кнопки прямоугольной формы с надписанным на них текстом или нарисованным графическим изображением.

Для создания кнопки или переключателя (а так же других органов управления) используется функция WinCreateWindow . Кнопка создается на базе предопределенного класса окна WC_BUTTON , например, следующим образом:

hWndButton = WinCreateWindow (hWnd, WC_BUTTON ,
  "Кнопка", WS_VISIBLE  | BS_PUSHBUTTON,
  0, 0, 0, 0,
  hWnd, HWND_TOP , BTN1_ID, NULL, NULL);

Заметим, что здесь используется именно функция WinCreateWindow , а не функция WinCreateStdWindow , при помощи которой мы создаем главное окно приложения. Эта функция может быть использована для создания дочернего окна на базе зарегистрированного вами класса.

Функция WinCreateWindow

Приведем прототип функции WinCreateWindow , предназначенной для создания окон (и, в частности, окон органов управления):

HWND WinCreateWindow (
  HWND hwndParent,       // родительское окно
  PSZ pszClass,          // имя класса
  PSZ pszName,           // заголовок окна
  ULONG flStyle,         // стиль класса окна
  LONG x,                // координата по оси X
  LONG y,                // координата по оси Y
  LONG cx,               // ширина
  LONG cy,               // высота
  HWND hwndOwner,        // окно-владелец
  HWND hwndInsertBehind, // окно-брат, за которым 
                         // отображается окно
  ULONG id,              // идентификатор окна
  PVOID pCtlData,        // управляющие данные
  PVOID pPresParams);    // параметры отображения

В качестве параметра hwndParent вы должны указать идентификатор дочернего окна.

Параметр pszClass определяет имя класса, на базе которого создается окно. Для кнопки здесь необходимо указать имя WC_BUTTON .

Параметр pszName задает текст для окна. Если создается кнопка, то это тот самый текст, который будет написан на кнопке.

С помощью параметра flStyle можно указать стиль класса окна, определяющий поведение и внешний вид окон, создаваемых на базе этого класса. Этот параметр мы опишем немного позже.

Расположение окна и его размеры определяется параметрами x (координата по оси X), y (координата по оси Y), cx (ширина), cy (высота).

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

Через параметр hwndInsertBehind передается идентификатор братского (или сестринского, что одно и то же) окна, за которым будет нарисовано создаваемое окно. Вы можете также указать константы HWND_TOP или HWND_BOTTOM . В первом случае создаваемое окно окажется наверху, во втором - за всеми остальными братскими окнами.

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

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

Аналогично, данные, передаваемые через параметр pPresParams, также попадают в функцию дочернего окна через параметр mp2 сообщения WM_CREATE . Для того чтобы понять, как использовать параметр pPresParams, опишем подробнее параметры сообщения WM_CREATE.

Сообщение WM_CREATE

Как вы уже знаете, сообщение WM_CREATE передается функции окна при его создании. Параметр mp1 этого сообщения содержит значение параметра pCtlData, переданного в функцию WinCreateWindow .

В параметр mp2 при этом записывается указатель на следующую структуру:

typedef struct _CREATESTRUCT 
{
 PVOID   pPresParams;  // параметры отображения
 PVOID   pCtlData;     // управляющие данные
 ULONG   id;           // идентификатор окна
 HWND    hwndInsertBehind;  // окно-брат, за которым
                            // отображается окно
 HWND    hwndOwner;    // окно-владелец
 LONG    cy;           // высота окна
 LONG    cx;           // ширина окна
 LONG    y;            // координата по оси Y
 LONG    x;            // координата по оси X
 ULONG   flStyle;      // стиль окна
 PSZ     pszText;      // заголовок окна
 PSZ     pszClassName; // имя класса
 HWND    hwndParent;   // родительское окно
} CREATESTRUCT;
typedef CREATESTRUCT *PCREATESTRUCT;

Нетрудно заметить, что поля этой структуры повторяют аналогичные параметры функции WinCreateWindow . Анализируя поля структуры CREATESTRUCT, обработчик сообщения WM_CREATE может проверить параметры создаваемого окна.

Если эти параметры верны, обработчик может вернуть значение FALSE, что разрешает создание окна. Если же один или несколько параметров имеют неправильное знчение, функция окна может вернуть значение TRUE. Окно при этом создано не будет, а функция WinCreateWindow вернет значение NULLHANDLE.

Стили кнопок

Параметр flStyle определяет стиль класса окна, влияющий на поведение и внешний вид окон, создаваемых на базе этого класса. Для кнопок и переключателей помимо стилей с префиксом WS_, описанных в начале нашей книги, можно указывать следующие стили (некоторые стили можно комбинировать при помощи логической операции ИЛИ, о чем будет сказано дополнительно):

Стиль Описание
BS_PUSHBUTTON Кнопка в виде прямоугольника с текстом. Когда пользователь нажимает эту кнопку, родительское окно получает извещающее сообщение
BS_DEFAULT Кнопка с толстой рамкой. В диалоговых панелях вместо этой кнопки пользователь может нажать клавишу <Enter>. Этот стиль можно использовать вместе со стилями BS_PUSHBUTTON и BS_USERBUTTON
BS_CHECKBOX Переключатель квадратной формы, который может находиться в одном из двух состояний: включенном (внутри квадрата отображается галочка) или выключенном (без галочки). Справа от квадрата находится подпись (текст заголовка)
BS_AUTOCHECKBOX Аналогично предыдущему, но внешний вид при переключении изменяется автоматически
BS_RADIOBUTTON Аналогично BS_CHECKBOX, но переключатель имеет круглую форму. Во включенном состоянии внутри круга отображается жирная точка
BS_AUTORADIOBUTTON Аналогично предыдущему, но внешний вид при переключении изменяется автоматически. Обычно используется в диалоговых панелях в составе группы аналогичных переключателей, при этом во время включения одного из переключателей остальные автоматически выключаются
BS_AUTOSIZE Размер кнопки, создаваемой с этим стилем, изменяется автоматически таким образом, чтобы надпись всегда помещалась на ней целиком. При создании окна с этим стилем функции WinCreateWindow в качестве параметров cx или cy можно указывать значение -1
BS_3STATE Прямоугольная кнопка с автоматическим переключением, которая может находиться в трех состояниях: внутри квадрата отображается маленький черный квадратик (включен), черный квадратик не отображается (выключен), переключатель отображается серым цветом (переключатель не активен)
BS_AUTO3STATE Аналогично предыдущему, но изображение изменяется автоматически при переключении
BS_BITMAP Вместо текста на поверхности кнопки рисуется растровое изображение. Этот стиль можно использовать только вместе со стилем BS_PUSHBUTTON
BS_HELP Когда пользователь нажимает кнопку с этим стилем, родительское окно вместо сообщения WM_COMMAND получает сообщение с кодом WM_HELP . Этот стиль можно использовать вместе со стилем BS_PUSHBUTTON
BS_ICON Вместо текста на поверхности кнопки рисуется пиктограмма. Этот стиль можно использовать только вместе со стилем BS_PUSHBUTTON
BS_MINIICON Аналогично предыдущему, но на поверхности кнопки можно размещать пиктограммы уменьшенного размера
BS_NOBORDER Кнопка не имеет рамки. Этот стиль можно использовать только вместе со стилем BS_PUSHBUTTON
BS_NOCURSORSELECT Круглая кнопка, которая не выбирается после получения фокуса ввода в результате использования клавиш перемещения курсора или табуляции (стиль используется в диалоговых панелях) . Этот стиль можно использовать только вместе со стилем BS_AUTORADIOBUTTON
BS_NOPOINTERFOCUS Круглая кнопка, которая не выбирается щелчком мыши. В результате курсор останется в том поле, в котором требуется ввести информацию (стиль используется в диалоговых панелях). Этот стиль можно комбинировать со всеми другими стилями
BS_SYSCOMMAND Вместо сообщения WM_COMMAND кнопка с этим стилем посылает родительскому окну извещающее сообщение WM_SYSCOMMAND . Этот стиль можно использовать только вместе со стилем BS_PUSHBUTTON
BS_TEXT На поверхности кнопки может отображаться текст вместе с пиктограммой (обычной или уменьшенной), или с растровым изображением. Этот стиль можно использовать вместе со стилем BS_PUSHBUTTON. Кроме того, необходимо указать стили BS_BITMAP, BS_ICON или BS_MINIICON
BS_USERBUTTON Внешний вид кнопки определяется приложением

Установка размеров и расположения кнопки

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

case WM_SIZE :
{
  WinSetWindowPos (hWndButton, HWND_TOP ,
    10, 10, 200, 50, 
    SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

  WinInvalidateRect (hWnd, NULL, TRUE);
  return 0;
}

Пользователь может изменять размеры окна, при этом, если не заботиться о стирании содержимого окна, он увидит в нем несколько одинаковых кнопок. Функция WinInvalidateRect в приведенном выше фрагменте кода вызывает перерисовку окна hWnd приложения, создавшего в этом окне кнопку hWndButton.

Извещение от кнопки

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

Код этого сообщения зависит от стиля кнопки. Если не указаны стили BS_SYSCOMMAND или BS_HELP, извещение посылается в виде сообщения WM_COMMAND . Если же указаны перечисленные выше стили, вместо сообщения WM_COMMAND посылаются, соответственно, сообщения WM_SYSCOMMAND или WM_HELP .

Что же касается переключателей, то они посылают извещение в форме сообщения WM_CONTROL , которое мы рассмотрим в разделе "Переключатели".

Через параметр mp1 сообщения WM_COMMAND передается идентификатор органа управления. Однако вспомним, что сообщение WM_COMMAND посылает также меню приложения. Для того чтобы различить источник извещающего сообщения WM_COMMAND, необходимо проанализировать параметр mp2.

Младшее слово параметра mp2 содержит искомый идентификатор источника сообщения и может принимать одно из перечисленных ниже значений:

Значение Описание
CMDSRC_PUSHBUTTON Извещающее сообщение WM_COMMAND посылается кнопкой, идентификатор которой передается через параметр mp1
CMDSRC_MENU Источником сообщения является меню, идентификатор которого передается через параметр mp1
CMDSRC_ACCELERATOR Сообщение пришло от акселератора с командным значением mp1
CMDSRC_FONTDLG Источником сообщения является диалоговая панель для выбора шрифта. Через параметр mp1 передается идентификатор этой диалоговой панели
CMDSRC_FILEDLG Источником сообщения является диалоговая панель для выбора файла. Через параметр mp1 передается идентификатор этой диалоговой панели
CMDSRC_OTHER Прочий источник сообщения

Старшее слово параметра mp2 сообщения WM_COMMAND может содержать значение TRUE или FALSE. В первом случае извещение появилось в результате работы с органом управления при помощи мыши, во втором - при помощи клавиатуры.

Для того чтобы было удобнее разбирать параметры сообщения WM_COMMAND , в файле pmwin.h определена структура CMDMSG, указатель на нее PCMDMSG и макрокоманда COMMANDMSG:

typedef struct _COMMANDMSG
{
  USHORT cmd;    // идентификатор органа управления
  USHORT unused; // не используется
  USHORT source; // источник извещающего сообщения
  USHORT fMouse; // признак использования мыши
} CMDMSG;
typedef CMDMSG *PCMDMSG;

#define COMMANDMSG(pmsg) \
  ((PCMDMSG)((PBYTE)pmsg + sizeof(MPARAM)))

Обработчик сообщения WM_COMMAND обычно имеет такой вид:

case WM_COMMAND :
{
  switch (COMMANDMSG(&msg) -> cmd)
  {
    case BTN1_ID:
    {
       // Обработка извещения от кнопки BTN1_ID
         . . .
       break;
    }
    case BTN2_ID:
    {
       // Обработка извещения от кнопки BTN2_ID
         . . .
       break;
    }
    default:
       break;
  }
  return 0;
}

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

if(COMMANDMSG (&msg) -> source == CMDSRC_PUSHBUTTON)
{
  // Сообщение от кнопки
  . . .
}
else if(COMMANDMSG(&msg) -> source == CMDSRC_MENU)
{
  // Сообщение от меню
  . . .
}

Для извлечения параметров кнопки вы можете также использовать макрокоманды SHORT1FROMMP и SHORT2FROMMP :

cmd    = SHORT1FROMMP (mp1);
source = SHORT1FROMMP (mp2);
fMouse = SHORT1FROMMP (mp2);

Сообщения WM_SYSCOMMAND и WM_HELP

Родительское окно может получать извещения от органов управления в форме сообщений WM_SYSCOMMAND или WM_HELP . Первое из этих сообщений приходит, например, когда пользователь нажимает комбинацию клавиш, соответствующую акселератору меню. Сообщение WM_HELP используется приложениями для работы со справочной системой, которую мы опишем в одной из следующих книг "Библиотеки системного программиста", посвященной программированию для IBM OS/2.

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

Управление кнопками

Приложение при необходимости может управлять состоянием кнопок, блокируя или разблокируя их, а также изменяя состояние кнопок.

Для блокировки кнопки необходимо использовать функцию WinEnableWindow, которая имеет следующий прототип:

BOOL WinEnableWindow(
  HWND hwnd,         // идентификатор окна
  BOOL fNewEnabled); // новое состояние

Через параметр hwnd вы должны передать этой функции идентификатор окна кнопки. Обращаем внимание, что это идентификатор окна кнопки, который возвращается функцией WinCreateWindow при создании кнопки, а не идентификатор кнопки, передаваемый этой же функции в качестве одного из параметров.

Параметр fNewEnabled определяет новое состояние кнопки. Если он равен TRUE, кнопка разблокируется, а если FALSE - блокируется.

Заблокированную кнопку нельзя нажать ни мышью, ни при помощи клавиатуры (если такая кнопка имеет фокус ввода). Надпись на заблокированной кнопке отображается серым цветом.

Когда состояние кнопки меняется, родительское окно получает сообщение WM_ENABLE. Параметр mp1 этого сообщения указывает новое состояние кнопки и может принимать те же значения, что и описанный только что параметр fNewEnabled функции WinEnableWindow. Параметр mp2 не используется.

Сообщение WM_ENABLE - это извещение родительского окна об изменении состояния кнопки. В ответ на это извещение родительское окно может выполнить какие-либо действия, связанные с тем, что данная кнопка оказалась заблокированной или наоборот, разблокированной.

8.2. Приложение BUTTON

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

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

Исходные тексты приложения приведены в листинге 8.1.

Листинг 8.1. Файл button\button.c

// =================================================
// Определения
// =================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS
#include <os2.h>
#include "button.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

// =================================================
// Глобальные переменные
// =================================================

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

// Идентификаторы кнопок
HWND hWndButton1;
HWND hWndButton2;

CHAR szAppTitle[] = "Button Demo";

// =================================================
// Главная функция приложения main 
// =================================================

int main ()
{
  HMQ   hmq;
  QMSG   qmsg;
  BOOL  fRc;

  // Флаги для создания окна Frame Window 
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR       | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON;

  // Имя класса главного окна
  CHAR  szWndClass[] = "BTNDEMO";

  hab = WinInitialize (0);
  if(hab == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue (hab, 0);
  if(hmq == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinTerminate (hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass (hab, szWndClass,
    (PFNWP)WndProc, 0, 0);
  if(fRc == FALSE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow  (HWND_DESKTOP,
    WS_VISIBLE ,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);
  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Запускаем цикл обработки сообщений
  while(WinGetMsg (hab, &qmsg, 0, 0, 0))
    WinDispatchMsg (hab, &qmsg);

  WinDestroyWindow(hWndFrame);
  WinDestroyMsgQueue (hmq);
  WinTerminate (hab);
  return(0);
}

// =================================================
// Функция главного окна приложения
// =================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  HPS hps;
  RECTL rec;

  switch (msg)
  {
    // Создаем две кнопки
    case WM_CREATE :
    {
      hWndButton1 = WinCreateWindow (hWnd, WC_BUTTON ,
        "Кнопка 1", WS_VISIBLE  | BS_PUSHBUTTON,
        0, 0, 0, 0,
        hWnd, HWND_TOP , BTN1_ID, NULL, NULL);

      hWndButton2 = WinCreateWindow (hWnd, WC_BUTTON ,
        "Кнопка 2", WS_VISIBLE  | BS_PUSHBUTTON,
        0, 0, 0, 0,
        hWnd, HWND_TOP , BTN2_ID, NULL, NULL);

      return FALSE;
    }

    // Изменяем размер и расположение кнопок
    case WM_SIZE :
    {
      WinSetWindowPos (hWndButton1, HWND_TOP ,
        10, 10, 200, 50, 
        SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

      WinSetWindowPos (hWndButton2, HWND_TOP ,
        10, 70, 200, 50, 
        SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

      // Перерисовываем окно приложения
      WinInvalidateRect (hWnd, NULL, TRUE);
      return 0;
    }

    case WM_PAINT :
    {
      // Получаем пространство отображения
      hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

      // Закрашиваем область, требующую обновления
      WinFillRect (hps, &rec, CLR_WHITE);

      // Возвращаем пространство отображения
      WinEndPaint (hps);
      return 0;
    }

    case WM_ERASEBACKGROUND :
      return(MRFROMLONG(1L));

    // Это сообщение поступает в функцию окна,
    // когда пользователь нажимает одну из двух
    // кнопок
    case WM_COMMAND :
    {
      switch (COMMANDMSG(&msg) -> cmd)
      {
        case BTN1_ID:
        {
          WinMessageBox (HWND_DESKTOP, hWnd,
           "Нажата кнопка 1",
           "Сообщение", 0,
           MB_INFORMATION | MB_APPLMODAL |
             MB_MOVEABLE | MB_OK);
          break;
        }

        case BTN2_ID:
        {
          WinMessageBox (HWND_DESKTOP, hWnd,
           "Нажата кнопка 2",
           "Сообщение", 0,
           MB_INFORMATION | MB_APPLMODAL |
             MB_MOVEABLE | MB_OK);
          break;
        }
        default:
          break;
      }
      return 0;
    }
    default:
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
  }
}

Глобальные переменные

В области глобальных переменных определены переменные hWndButton1 и hWndButton2, в которых хранятся идентификаторы двух созданных приложением кнопок.

Функция main

Функция main не имеет особенностей. Она создает главное окно приложений, очередь сообщений и запускает цикл обработки сообщений.

Функция окна WndProc

Рассмотрим обработку сообщений, поступающих в функцию окна приложения BUTTON.

Сообщение WM_CREATE

Обработчик сообщения WM_CREATE создает две кнопки, пользуясь для этого функцией WinCreateWindow :

hWndButton1 = WinCreateWindow (hWnd, WC_BUTTON ,
  "Кнопка 1", WS_VISIBLE  | BS_PUSHBUTTON,
  0, 0, 0, 0,
  hWnd, HWND_TOP , BTN1_ID, NULL, NULL);
hWndButton2 = WinCreateWindow (hWnd, WC_BUTTON ,
  "Кнопка 2", WS_VISIBLE  | BS_PUSHBUTTON,
  0, 0, 0, 0,
  hWnd, HWND_TOP , BTN2_ID, NULL, NULL);

Первая из этих кнопок имеет идентифкатор BTN1_ID, вторая - BTN2_ID. Идентификаторы окон для этих кнопок записываются, соответственно, в глобальные переменные hWndButton1 и hWndButton2.

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

Обработчик сообщения WM_CREATE возвращает значение FALSE, разрешая тем самым создание главного окна приложения.

Сообщение WM_SIZE

Обработчик сообщения WM_SIZE изменяет расположение и размеры двух кнопок, используя для этого функцию WinSetWindowPos :

WinSetWindowPos (hWndButton1, HWND_TOP ,
  10, 10, 200, 50, SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );
WinSetWindowPos (hWndButton2, HWND_TOP ,
  10, 70, 200, 50, SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

После этого содержимое главного окна перерисовывается.

Сообщение WM_PAINT

Единственная задача обработчика сообщения WM_PAINT заключается в закраске внутренней области окна Client Window при изменении размеров главного окна приложения. Для закраски используется функция WinFillRect .

Сообщение WM_COMMAND

Так как наше приложение не имеет меню, обработчик сообщения WM_COMMAND анализирует только идентификатор кнопки, пославшей извещающее сообщение. Этот идентификатор извлекается при помощи макрокоманды COMMANDMSG.

В зависимости от того, какая кнопка была нажата, обработчик сообщения WM_COMMAND выводит на экран то или иное сообщение.

Обратите внимание на параметры функции WinMessageBox , с помощью которой выводится это сообщение:

WinMessageBox (HWND_DESKTOP, hWnd,"Нажата кнопка 1",
  "Сообщение", 0,
  MB_INFORMATION | MB_APPLMODAL | MB_MOVEABLE | MB_OK);

При вызове функции WinMessageBox мы указали флаг MB_APPLMODAL , в результате чего диалоговая панель с сообщением стала модальной в рамках приложения BUTTON. Последнее означает, что работа приложения будет продолжена только после того, как пользователь нажмет расположенную на диалоговой панели кнопку OK.

Указав флаг MB_SYSTEMMODAL , можно создать так называемую системную модальную диалоговую панель . Пока пользователь не завершит работу с этой диалоговой панелью, работа системы Presentation Manager будет приостановлена. Системные модальные диалоговые панели используются, например, для отображения критичных сообщений, когда для продолжения работы системы требуется вмешательство пользователя.

Файл button.h

Файл button.h (листинг 8.2) содержит описание идентификатора главного окна приложения ID_APP_FRAMEWND, а также идентификаторов двух кнопок BTN1_ID и BTN2_ID.

Листинг 8.2. Файл button\button.h

#define ID_APP_FRAMEWND 1
#define BTN1_ID         100
#define BTN2_ID         101

Файл button.rc

В файле button.rc (листинг 8.3) описана пиктограмма приложения BUTTON.

Листинг 8.3. Файл button\button.rc

#include <os2.h>
#include "button.h"
ICON ID_APP_FRAMEWND BUTTON.ICO

Файл button.def

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

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

NAME        BUTTON   WINDOWAPI
DESCRIPTION 'Button Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc

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

Переключатели , как и кнопки, создаются при помощи функции WinCreateWindow на базе предопределенного класса окна WC_BUTTON . Однако в отличие от кнопок, для переключателей необходимо указывать стили BS_CHECKBOX , BS_AUTOCHECKBOX , BS_RADIOBUTTON или BS_AUTORADIOBUTTON :

hWndCheckBox = WinCreateWindow (hWnd, WC_BUTTON ,
  "Text", WS_VISIBLE  | BS_AUTOCHECKBOX,
  0, 0, 0, 0, hWnd, HWND_TOP , CHCKBOX_ID, NULL, NULL);

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

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

Рис. 8.2. Диалоговая панель с переключателями

Сообщение WM_CONTROL

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

Через младшее слово параметра mp1 этого сообщения передается идентификатор переключателя:

id = SHORT1FROMMP (mp1);

Если в окне создано несколько переключателей, следует проанализировать значение id для определения переключателя, который послал сообщение WM_CONTROL .

Старшее слово параметра mp1 содержит код извещения, который для переключателя может иметь значение BN_CLICKED или BN_DBLCLICKED . В первом случае пользователь сделал одиночный щелчок мышью по переключателю, во втором - двойной. Вот пример извлечения кода извещения:

nNotifCode = SHORT2FROMMP (mp1);

Если переключатель был создан с использованием стиля BS_USERBUTTON , в старшем слове параметра mp1 может передаваться код извещения BN_PAINT . В этом случае переключатель должен нарисовать себя в одном из трех состояний: заблокированном, выбранном или обычном. Подробнее об этом мы расскажем в одной из следующих наших книг, посвященных программированию для IBM OS/2.

И, наконец, для обычных переключателей через параметр mp2 сообщения WM_CONTROL передается идентификатор окна переключателя. Для переключателей со стилем BS_USERBUTTON через этот параметр передается указатель на структуру USERBUTTON , определенную следующим образом:

typedef struct _USERBUTTON 
{
  HWND  hwnd; // идентификатор окна
  HPS   hps;  // идентификатор пространства отображения
  ULONG fsState;    // новое состояние переключателя
  ULONG fsStateOld; // старое состояние переключателя
} USERBUTTON;
typedef USERBUTTON *PUSERBUTTON;

Определение состояния переключателя

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

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

Для определения текущего состояния переключателя в окно переключателя необходимо послать сообщение BM_QUERYCHECK (оба параметра этого сообщения не используются и должны быть равны нулю):

USHORT usState;
usState = (USHORT)WinSendMsg (hWndCheckBox,
  BM_QUERYCHECK, MPFROMSHORT(0), NULL);

После того как в приведенном выше примере функция WinSendMsg возвратит управление, в переменную usState будет записано состояние кнопки:

Значение Состояние переключателя
0 Выключен и изображается без галочки
1 Включен и отмечен галочкой
2 Находится в неопределенном состоянии

Изменение состояния переключателя

Состояние автоматического переключателя, определенного со стилями BS_AUTOCHECKBOX или BS_AUTORADIOBUTTON, изменяется, когда пользователь делает щелчок мышью в его окне. Если же переключатель имеет стиль BS_CHECKBOX или BS_RADIOBUTTON, приложение должно само изменять его состояние.

Для изменения состояния переключателя (обычного или автоматического) ему необходимо послать сообщение BM_SETCHECK . В параметре mp1 этого сообщения следует указать новое состояние (0, 1 или 2). Параметр mp2 не используется и должен содержать нулевое значение.

В следующем фрагменте кода приложение проверяет текущее состояние перключателя, записанное в переменной fButton3Checked. Если переключатель выключен, он включается, а если включен - выключается. После этого содержимое переменной fButton3Checked изменяется на противоположеное:

if(fButton3Checked)
  // Выключение переключателя
  WinSendMsg (hWndButton3, BM_SETCHECK,
    MPFROMSHORT(0), NULL);
else
  // Включение переключателя
  WinSendMsg (hWndButton3, BM_SETCHECK,
    MPFROMSHORT(1), NULL);
fButton3Checked = ~fButton3Checked;

8.4. Приложение CHECK

Для демонстрации техники работы с переключателями мы подготовили исходные тексты приложения CHECK. В своем главном окне (рис. 8.3) это приложение создает кнопку и два переключателя. Первый из этих переключателей обычный, а второй - автоматический.

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

При помощи мыши вы можете изменять состояние переключателей с номерами 2 и 3 (первый номер присвоен кнопке с надписью "Переключатели"). Второй переключатель - автоматический и имеет стиль BS_AUTOCHECKBOX, третий - обычный со стилем BS_CHECKBOX.

Если после установки состояния переключателей нажать кнопку, расположенную в нижней части окна приложения, на экране появится диалоговая панель, отображающая текущее состояние переключателей. Эта панель показана на рис. 8.2.

Исходные тексты приложения CHECK приведены в листинге 8.5.

Листинг 8.5. Файл check\check.c

// =================================================
// Определения
// =================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS
#include <os2.h>
#include <stdio.h>
#include "check.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

// =================================================
// Глобальные переменные
// =================================================

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

// Идентификаторы кнопок
HWND hWndButton1;
HWND hWndButton2;
HWND hWndButton3;

// Состояние переключателей
BOOL fButton2Checked = FALSE;
BOOL fButton3Checked = FALSE;

CHAR szAppTitle[] = "CheckBox Demo";

// =================================================
// Главная функция приложения main 
// =================================================

int main ()
{
  HMQ   hmq;
  QMSG   qmsg;
  BOOL  fRc;

  // Флаги для создания окна Frame Window 
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR       | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON;

  // Имя класса главного окна
  CHAR  szWndClass[] = "BTNDEMO";

  hab = WinInitialize (0);
  if(hab == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue (hab, 0);
  if(hmq == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinTerminate (hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass (hab, szWndClass,
    (PFNWP)WndProc, 0, 0);
  if(fRc == FALSE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow  (HWND_DESKTOP,
    WS_VISIBLE ,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);
  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Запускаем цикл обработки сообщений
  while(WinGetMsg (hab, &qmsg, 0, 0, 0))
    WinDispatchMsg (hab, &qmsg);

  WinDestroyWindow(hWndFrame);
  WinDestroyMsgQueue (hmq);
  WinTerminate (hab);
  return(0);
}

// =================================================
// Функция главного окна приложения
// =================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  HPS hps;
  RECTL rec;
  CHAR szBuf[256];

  switch (msg)
  {
    // Создаем кнопку и два переключателя
    case WM_CREATE :
    {
      hWndButton1 = WinCreateWindow (hWnd, WC_BUTTON ,
        "Переключатели", WS_VISIBLE  | BS_PUSHBUTTON,
        0, 0, 0, 0,
        hWnd, HWND_TOP , BTN1_ID, NULL, NULL);

      // Автоматический переключатель
      hWndButton2 = WinCreateWindow (hWnd, WC_BUTTON ,
        "(2) AutoCheckBox", 
        WS_VISIBLE  | BS_AUTOCHECKBOX,
        0, 0, 0, 0,
        hWnd, HWND_TOP , BTN2_ID, NULL, NULL);

      // Обычный переключатель
      hWndButton3 = WinCreateWindow (hWnd, WC_BUTTON ,
        "(3) CheckBox", WS_VISIBLE  | BS_CHECKBOX,
        0, 0, 0, 0,
        hWnd, HWND_TOP , BTN3_ID, NULL, NULL);
      return FALSE;
    }

    // Изменяем размер и расположение органов
    // управления
    case WM_SIZE :
    {
      WinSetWindowPos (hWndButton1, HWND_TOP ,
        10, 10, 200, 50, 
        SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

      WinSetWindowPos (hWndButton2, HWND_TOP ,
        10, 70, 200, 50, 
        SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

      WinSetWindowPos (hWndButton3, HWND_TOP ,
        10, 110, 200, 50, 
        SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

      // Перерисовываем окно приложения
      WinInvalidateRect (hWnd, NULL, TRUE);
      return 0;
    }

    case WM_PAINT :
    {
      // Получаем пространство отображения
      hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

      // Закрашиваем область, требующую обновление
      WinFillRect (hps, &rec, CLR_WHITE);

      // Возвращаем пространство отображения
      WinEndPaint (hps);
      return 0;
    }

    case WM_ERASEBACKGROUND :
      return(MRFROMLONG(1L));

    // Это сообщение приходит при изменении
    // состояния переключателей
    case WM_CONTROL :
    {
      switch (SHORT1FROMMP (mp1))
      {
        // Если сообщение пришло от автоматического
        // переключателя, получаем и сохраняем его
        // текущее состояние
        case BTN2_ID:
        {
          fButton2Checked = (BOOL) 
            WinSendMsg (hWndButton2,
            BM_QUERYCHECK, MPFROMSHORT(0), NULL);
          break;
        }

        // Если сообщение пришло от обычного 
        // переключателя, изменяем его состояние 
        // и сохраняем новое состояние 
        // в переменной fButton3Checked
        case BTN3_ID:
        {
          if(fButton3Checked)
            WinSendMsg (hWndButton3, BM_SETCHECK,
              MPFROMSHORT(0), NULL);

          else
            WinSendMsg (hWndButton3, BM_SETCHECK,
              MPFROMSHORT(1), NULL);

          fButton3Checked = ~fButton3Checked;
          break;
        }

        default:
          break;
      }
      return 0;
    }

    // Когда пользователь нажимает кнопку,
    // определяем и сохраняем состояние
    // обоих переключателей
    case WM_COMMAND :
    {
      switch (COMMANDMSG(&msg) -> cmd)
      {
        case BTN1_ID:
        {
          sprintf (szBuf, "Состояние переключаталей:\n"
            "(3) третий - %d\n(2) второй - %d",
            fButton3Checked ? 1 : 0,
            fButton2Checked ? 1 : 0);

          WinMessageBox (HWND_DESKTOP, hWnd, szBuf,
           "Состояние переключателей", 0,
           MB_INFORMATION | MB_APPLMODAL |
             MB_MOVEABLE | MB_OK);
          break;
        }

        default:
          break;
      }
      return 0;
    }

    default:
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
  }
}

Глобальные переменные

В области глобальных переменных определены переменные hWndButton1, hWndButton2 и hWndButton3, предназначенные, соответственно, для хранения идентификаторов кнопки и двух переключателей.

Состояние переключателей записывается в переменные fButton2Checked и fButton3Checked.

Функция main

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

Функция WndProc

Рассмотрим обработку сообщений, выполняемую функцией WndProc.

Сообщение WM_CREATE

Обработчик сообщения WM_CREATE создает кнопку и два переключателя. Идентификатор окна кнопки записывается в переменную hWndButton1. Идентификатор кнопки равен BTN1_ID.

Первый из создаваемых переключателей имеет стиль BS_AUTOCHECKBOX и работает автоматически. Идентификатор окна автоматического переключателя хранится в переменной hWndButton2, а идентификатор переключателя равен BTN2_ID.

Второй переключатель создается с использованием стиля BS_CHECKBOX. Идентификатор окна для него хранится в переменной hWndButton3, а идентификатор самого переключателя равен BTN3_ID.

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

Сообщение WM_SIZE

Обработчик сообщения WM_SIZE располагает кнопку в нижней части окна. Выше нее размещаются переключатели. Изменение расположения и размеров органов управления выполняется функцией WinSetWindowPos . Перед возвращением управления обработчик сообщения WM_SIZE заказывает перерисовку окна, вызывая функцию WinInvalidateRect . Окно будет перерисовано при обработке сообщения WM_PAINT .

Сообщение WM_CONTROL

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

Внешний вид и состояние автоматического переключателя изменяется сразу после щелчка, поэтому все, что делает обработчик сообщения WM_CONTROL для автоматического переключателя - это определяет его текущее состояние. Для этого окну переключателя при помощи функции WinSendMsg посылается сообщение BM_QUERYCHECK. Полученное от этой функции состояние записывается в переменную fButton2Checked.

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

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

Обработчик сообщения WM_CONTROL в этом случае проверяет содержимое переменной fButton3Checked и выполняет включение, либо выключение переключателя. Для управления переключателем с помощью функции WinSendMsg ему посылается сообщение BM_SETCHECK. Если переключатель необходимо включить, параметр mp1 этого сообщения равен 1, если выключить - 0.

После изменения состояния переключателя обработчик сообщения WM_CONTROL соответствующим образом изменяет содержимое переменной fButton3Checked.

Сообщение WM_COMMAND

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

В ответ на это сообщение функция окна формирует текстовую строку szBuf, включая в нее состояние обоих переключателей:

sprintf (szBuf, "Состояние переключаталей:\n"
  "(3) третий - %d\n(2) второй - %d",
  fButton3Checked ? 1 : 0, fButton2Checked ? 1 : 0);

Эта строка будет использована для отображения в диалоговой панели, создаваемой на экране функцией WinMessageBox . Обратите внимание, что для принудительного перехода на новую строку в этой диалоговой панели мы включили в строку символы \n.

Файл check.h

Файл check.h (листинг 8.6) содержит определения константы ID_APP_FRAMEWND и идентификаторов органов управления BTN1_ID, BTN2_ID и BTN3_ID.

Листинг 8.6. Файл check\check.h

#define ID_APP_FRAMEWND 1
#define BTN1_ID         100
#define BTN2_ID         101
#define BTN3_ID         102

Файл check.rc

Файл описания ресурсов приложения check.rc (листинг 8.7) содержит только описание пиктограммы.

Листинг 8.7. Файл check\check.rc

#include <os2.h>
#include "check.h"
ICON ID_APP_FRAMEWND CHECK.ICO

Файл check.def

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

Листинг 8.8. Файл check\check.def

NAME        CHECK   WINDOWAPI
DESCRIPTION 'Check Box Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc

8.5. Полоса просмотра

Полосы просмотра (Scrollbar ) используются в приложениях Presentation Manager для просмотра текста или изображения, которое не помещается в окне. Полосы просмотра бывают горизонтальные или вертикальные. Обычно они располагаются, соответственно, в нижней и правой части окна, хотя вы можете создать окно полосы просмотра в любом месте любого окна вашего приложения.

Полоса просмотра представляет собой орган управления, созданный на базе предопределенного класса WC_SCROLLBAR .

Для создания полосы просмотра вы можете воспользоваться функцией WinCreateWindow, как это мы делали для создания кнопок и переключателей в предыдущих приложениях. Однако если вам необходимо создать полосы просмотра в нижней или левой части окна, то это можно сделать проще: достаточно при создании окна указать функции WinCreateStdWindow флаги FCF_VERTSCROLL (вертикальная полоса проосмотра) и FCF_HORZSCROLL (горизонтальная полоса просмотра).

Горизонтальная и вертикальная полоса просмотра посылает в функцию родительского окна сообщения WM_HSCROLL и WM_VSCROLL , соответственно. Через параметр mp1 этих сообщений передается идентификатор полосы просмотра, через младшее слово параметра mp1 - текущая позиция движка полосы просмотра, а через старшее слово этого же параметра - код операции, выполненной пользователем над полосой просмотра (сдвиг в ту или иную сторону, перемещение ползунка мышью и т. д.).

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

Рис. 8.4. Вертикальная полоса просмотра

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

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

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

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

Горизонтальная полоса просмотра состоит из тех же объектов, что и вертикальная. Она обеспечивает свертку документа в горизонтальном направлении.

Создание полосы просмотра

Как мы уже говорили, существует два способа создания полос просмотра в окне приложения.

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

Использование класса WC_SCROLLBAR

Для создания полосы просмотра с помощью функции WinCreateWindow вы должны в первом параметре функции указать класс окна WC_SCROLLBAR:

#define IDC_SCROLLBAR 100
HWND hWndScroll;
hWndScroll = WinCreateWindow (hWnd, WC_BUTTON ,
  NULL,  // текст указывать не нужно
  WS_VISIBLE  | SBS_VERT,
  0, 0, 0, 0,
  hWnd, HWND_TOP , IDC_SCROLLBAR, NULL, NULL);

Заголовок окна не используется, поэтому третий параметр функции WinCreateWindow должен быть указан как NULL.

Четвертый параметр, определяющий стиль окна, наряду с константой WS_VISIBLE должен содержать определение стиля полосы просмотра. Существует четыре стиля для полосы просмотра. Соответствующие символические константы имеют префикс имени SBS_ (например, SBS_HORZ ).

Одиннадцатый параметр функции WinCreateWindow должен задавать идентификатор полосы просмотра.

Стили полосы просмотра

При создании полосы просмотра функцией WinCreateWindow вы можете указать следующие стили:

Стиль Описание
SBS_HORZ Создается горизонтальная полоса просмотра
SBS_VERT Создается вертикальная полоса просмотра
SBS_AUTOTRACK При перемещении движка полосы просмотра, созданной с использованием стиля SBS_AUTOTRACK, перемещается полное изображение движка. Если же этот стиль не указан, перемещается только изображение прямоугольного контура движка, а сам движок будет нарисован в новой позиции только после завершения пользователем операции перемещения
SBS_THUMBSIZE Этот стиль используется для вычисления размера движка

Определение полос просмотра при создании окна

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

Для того чтобы у окна появились вертикальная и горизонтальная полосы просмотра, при создании окна функцией WinCreateStdWindow необходимо указать флаги FCF_VERTSCROLL и FCF_HORZSCROLL (соответственно, для создания вертикальной и горизонтальной полосы просмотра):

HWND hWndFrame;
ULONG flFrameFlags =
  FCF_SYSMENU    | FCF_TITLEBAR      | FCF_MINMAX   |
  FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
  FCF_ICON       | FCF_VERTSCROLL   | FCF_HORZSCROLL ;
hWndFrame = WinCreateStdWindow (HWND_DESKTOP,
  WS_VISIBLE,
  &flFrameFlags, szWndClass, szAppTitle,
  0, 0, ID_APP_FRAMEWND, &hWndClient);

Сообщения от полосы просмотра

Горизонтальные полосы просмотра, определенные для окна (одним из описанных выше способов) посылают в окно сообщение WM_HSCROLL , а все вертикальные - WM_VSCROLL .

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

Опишем параметры сообщений WM_HSCROLL и WM_VSCROLL.

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

usidentifier = (USHORT) mp1;      // идентификатор окна
sslider      = SHORT1FROMMP(mp2); // позиция движка
uscmd        = SHORT2FROMMP(mp2); // код операции

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

Ниже мы перечислим коды команд, которые могут поступать от полос просмотра.

Код комады Описание
SB_LINELEFT Пользователь сделал щелчок мышью по левой кнопке горизонтальной полосы просмотра. Значение позиции полосы просмотра уменьшается на единицу
SB_LINERIGHT Аналогично предыдущему, но по правой кнопке. Значение позиции полосы просмотра увеличивается на единицу
SB_PAGELEFT Пользователь сделал щелчок по полосе просмотра слева от движка
SB_PAGERIGHT Аналогично предыдущему, но щелчок сделан справа от движка
SB_LINEUP Пользователь сделал щелчок мышью по верхней кнопке горизонтальной полосы просмотра. Значение позиции полосы просмотра уменьшается на единицу
SB_LINEDOWN Аналогично предыдущему, но щелчок сделан по нижней кнопке полосы просмотра. Значение позиции полосы просмотра увеличивается на единицу
SB_SLIDERPOSITION Движок установлен в конечную позицию
SB_SLIDERTRACK Сообщение с этим кодом непрерывно поступает в процессе перемещения движка полосы просмотра при помощи мыши
SB_ENDSCROLL Сообщение с кодом SB_ENDSCROLL посылается в том случае, если пользователь завершил перемещение движка полосы просмотра

Обычно в приложениях Presentation Manager организуется управление полосами просмотра при помощи клавиатуры. Для управления движком полосы просмотра при этом используются клавиши перемещения курсора и клавиши <PgUp>, <PgDn>, <Home> и <End>. Как правило, с помощью клавиш <Home> и <End> вы можете перейти, соответственно, в начало и в конец документа.

Так как действия, выполняемые при работе с движком, одинаковы для полосы просмотра и дублирующих ее клавиш, имеет смысл предусмотреть единый обработчик сообщений от полосы просмотра. Для добавления клавиатурного интерфейса обработчик клавиатурного сообщения WM_CHAR может посылать в функцию окна сообщения полосы просмотра. Например, если обработчик сообщения WM_CHAR обнаружил, что вы нажали клавишу <PgUp>, он может послать в функцию окна сообщение WM_VSCROLL с кодом, равным SB_PAGEUP. Результат будет в точности такой же, как будто для свертки документа на одну страницу вверх вы воспользовались полосой просмотра, а не клавиатурой.

Такой подход позволяет локализовать всю логику свертки в обработчике сообщений полосы просмотра. При этом сильно упрощается процедура подключения клавиатурного интерфейса - обработчик клавиатурного сообщения WM_CHAR должен послать в функцию окна сообщение полосы просмотра, соответствующее коду нажатой клавиши. Но ему не надо дублировать действия обработчиков сообщений WM_VSCROLL и WM_HSCROLL.

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

Клавиша Код извещения
Перемещение курсора вверх SB_LINEUP или SB_LINELEFT (эти значения равны)
- // - влево SB_LINEUP или SB_LINELEFT
- // - вниз SB_LINEDOWN или SB_LINERIGHT
- // - вправо SB_LINEDOWN или SB_LINERIGHT
<Page Up> SB_PAGEUP или SB_PAGELEFT
<Page Down> SB_PAGEDOWN или SB_PAGERIGHT

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

Клавиша Код извещения
Перемещение курсора вверх SB_LINEUP
- // - влево SB_LINELEFT
- // - вниз SB_LINEDOWN
- // - вправо SB_LINERIGHT
<Page Up> SB_PAGEUP
<Page Down> SB_PAGEDOWN
<Home> SB_SLIDERTRACK (установлена минимальная позиция)
<End> SB_SLIDERTRACK (установлена максимальная позиция)
<Ctrl + Home> SB_SLIDERTRACK (установлена минимальная позиция)
<Ctrl + End> SB_SLIDERTRACK (установлена максимальная позиция)
<Ctrl + Up> SB_SLIDERTRACK (установлена минимальная позиция). Используется для вертикальной полосы просмотра
<Ctrl + Down> SB_SLIDERTRACK (установлена максимальная позиция). Используется для вертикальной полосы просмотра
<F7> SB_PAGEUP
<F8> SB_PAGEDOWN
<Ctrl + Page Up> SB_PAGELEFT
<Ctrl + Page Down> SB_PAGERIGHT

Инициализация полосы просмотра

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

После того как вы создали полосу просмотра одним из описанных выше способов, ее необходимо проинициализировать, указав диапазон изменений значений позиции. И то, и другое можно сделать, передав окну полосы просмотра сообщение SBM_SETSCROLLBAR , например, так:

WinSendMsg(hwndYScroll,
  SBM_SETSCROLLBAR, (MPARAM)0, MPFROM2SHORT(0, YSIZE));
WinSendMsg(hwndXScroll,
  SBM_SETSCROLLBAR, (MPARAM)0, MPFROM2SHORT(0, XSIZE));

Здесь мы устанавливаем текущую позицию и диапазон изменения значений для вертикальной и горизонтальной полосы просмотра с идентификаторами окон hwndYScroll и hwndXScroll, соответственно.

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

Если полоса просмотра создавалась функцией WinCreateWindow, то для передачи сообщений полосе вы должны использовать идентификатор окна, полученный от этой функции. Если же полоса просмотра создавалась при помощи флагов FCF_VERTSCROLL или FCF_HORZSCROLL , для получения идентификатора этой полосы вам необходимо воспользоваться функциями WinWindowFromID и WinQueryWindow, как это показано ниже:

hwndYScroll = WinWindowFromID(
  WinQueryWindow(hWnd, QW_PARENT), FID_VERTSCROLL),
hwndXScroll = WinWindowFromID(
  WinQueryWindow(hWnd, QW_PARENT), FID_HORZSCROLL),

Полосы просмотра, создаваемые как дочерние для окна Frame Window, имеют идентификаторы FID_VERTSCROLL (вертикальная полоса просмотра) и FID_HORZSCROLL (горизонтальная полоса просмотра). Для получения идентификатора окна этих полос вы должны передать указанные значения в качестве второго параметра функции WinWindowFromID . Что же касается первого параметра этой функции, то через него следует передать идентификатор родительского окна.

Если идентификаторы окон полос просмотра определяются в функции главного окна приложения, идентификатор родительского окна можно получить при помощи функции WinQueryWindow . Для этого ей через первый параметр необходимо передать идентификатор окна hWnd, передаваемый в функцию главного окна приложения, а через второй параметр - константу QW_PARENT .

Итак, опишем параметры сообщения SBM_SETSCROLLBAR , предназначенного для установки позиции и диапазона изменений значений позиции.

Параметр mp1 задает новое значение позиции движка. Это значение должно лежать в пределах от минимального, определяемого младшим словом параметра mp2, и максимального, которое задается старшим словом этого же параметра.

В приведенном выше примере мы устанавливали для вертикальной полосы просмотра диапазон изменения значений от 0 до YSIZE, причем начальное значение было равно нулю. Это соответствует расположению движка в верхней части полосы просмотра.

Управление полосой просмотра

Если вам нужно установить только новую позицию движка, оставив прежним диапазон изменения значений, воспользуйтесь сообщением SBM_SETPOS :

WinSendMsg(hwndYScroll, 
  SBM_SETPOS, (MPARAM)10, (MPARAM)NULL);

Новое значение позиции вы должны передать с параметром mp1 этого сообщения. Параметр mp2 не используется и должен быть равен нулю.

Определение параметров полосы просмотра

Для того чтобы узнать текущее положение движка, окну полосы просмотра необходимо послать сообщение SBM_QUERYPOS , например:

USHORT usSliderPos;
usSliderPos = (USHORT)WinSendMsg(hwndYScroll,
  SBM_QUERYPOS, (MPARAM) NULL, (MPARAM) NULL);

Функция WinSendMsg возвратит искомое значение текущей позиции.

Для определения текущего диапазона изменения позиции окну полосы просмотра необходимо послать сообщение SBM_QUERYRANGE , например, так:

USHORT  usMinimum, usMaximum;
MRESULT mResult;
mResult = WinSendMsg(hwndYScroll, 
  SBM_QUERYRANGE, (MPARAM) NULL, (MPARAM) NULL);
usMinimum = SHORT1FROMMR(mResult); // минимальная позиция
usMaximum = SHORT2FROMMR(mResult); // максимальная позиция

Минимальная и максимальная позиции передаются, соответственно, через младшее и старшее слово, возвращаемое функцией WinSendMsg.

8.6. Приложение SCROLL

В приложении SCROLL мы создаем в главном окне приложения две полосы просмотра - вертикальную и горизонтальную, указывая соответствующие флаги при вызове функции WinCreateStdWindow. Эти полосы используются для просмотра метрик шрифта с названием Courier (рис. 8.5).

Рис. 8.5. Просмотра метрик шрифта Courier в окне приложения SCROLL

В одной из следующих наших книг, посвященной программированию для операционной системы IBM OS/2 Warp, мы изучим эти метрики. А пока вы можете убедиться, что кроме высоты и ширины символов для описания шрифта используется дополнительно несколько десятков других параметров.

Исходные тексты приложения SCROLL приведены в листинге 8.9.

Листинг 8.9. Файл scroll\scroll.c

// =================================================
// Определения
// =================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS
#include <os2.h>
#include <string.h>
#include <stdio.h>
#include "scroll.h"

#define YSIZE 50
#define XSIZE 50

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

// =================================================
// Глобальные переменные
// =================================================

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

CHAR szAppTitle[] = "Scroll Demo";

// Размеры окна Client Window
SHORT cxClient;
SHORT cyClient;

// Размеры символов выбранного шрифта
SHORT cxChar, cyChar, cyDesc;

// Структура для записи метрик шрифта
FONTMETRICS fm;

// Идентификаторы полос просмотра
HWND hwndXScroll;
HWND hwndYScroll;

// Текущие координаты движков
INT nXScrollPos;
INT nYScrollPos;

// Текущие координаты для вывода текста
LONG cxCurrentPosition;
LONG cyCurrentPosition;

// =================================================
// Главная функция приложения main
// =================================================

int main()
{
  HMQ   hmq;
  QMSG  qmsg;
  BOOL  fRc;
  HPS hps;

  // Флаги для создания окна Frame Window
  // Добавляем флаги FCF_VERTSCROLL  и FCF_HORZSCROLL ,
  // в результате чего в главном окне будут созданы
  // вертикальная и горизонтальная полосы просмотра
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR      | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON       | FCF_VERTSCROLL   | FCF_HORZSCROLL ;

  // Имя класса главного окна
  CHAR  szWndClass[] = "SCRDEMO";

  hab = WinInitialize(0);
  if(hab == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue(hab, 0);
  if(hmq == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinTerminate(hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass(hab, szWndClass,
    (PFNWP)WndProc, 0, 0);
  if(fRc == FALSE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow (HWND_DESKTOP,
    WS_VISIBLE,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);
  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);

    return(-1);
  }

  // Получаем пространство отображения
  hps = WinGetPS(hWndFrame);

  // Выбираем в пространство отображения шрифт
  // с фиксированной шириной символов
  SetCourierFont(hps);

  // Определяем метрики шрифта
  GpiQueryFontMetrics(hps, (LONG)sizeof(fm), &fm);

  cxChar = fm.lAveCharWidth;
  cyChar = fm.lMaxBaselineExt;
  cyDesc = fm.lMaxDescender;

  // Устанавливаем шрифт, выбранный в пространство
  // отображения по умолчанию
  ResetFont(hps);

  // Возвращаем пространство отображения
  WinReleasePS(hps);

  // Запускаем цикл обработки сообщений
  while(WinGetMsg(hab, &qmsg, 0, 0, 0))
    WinDispatchMsg(hab, &qmsg);

  WinDestroyWindow(hWndFrame);
  WinDestroyMsgQueue(hmq);
  WinTerminate(hab);
  return(0);
}

// =================================================
// Функция главного окна приложения
// =================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  HPS hps;
  RECTL rec;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Начальные координаты движков
      nYScrollPos = 0;
      nXScrollPos = 0;

      // Начальные координаты для вывода текста
      cyCurrentPosition = cyClient;
      cxCurrentPosition =
        nXScrollPos * cxChar + cxChar;

      // Определяем идентификатор окна для
      // вертикальной полосы просмотра
      hwndYScroll = WinWindowFromID(
        WinQueryWindow(hWnd, QW_PARENT),
        FID_VERTSCROLL),

      // Устанавливаем диапазон изменений координат
      // движка и начальное положение для
      // вертикальной полосы просмотра
      WinSendMsg(hwndYScroll,
        SBM_SETSCROLLBAR, (MPARAM)0,
        MPFROM2SHORT(0, YSIZE));

      // Выполняем аналогичные действия для
      // горизонтальной полосы просмотра
      hwndXScroll = WinWindowFromID(
        WinQueryWindow(hWnd, QW_PARENT), 
        FID_HORZSCROLL),

      WinSendMsg(hwndXScroll,
        SBM_SETSCROLLBAR, (MPARAM)0,
        MPFROM2SHORT(0, XSIZE));

      return FALSE;
    }

    case WM_SIZE:
    {
      // получаем и сохраняем размеры главного окна
      cxClient = SHORT1FROMMP(mp2);
      cyClient = SHORT2FROMMP(mp2);

      // Перерисовываем окно приложения
      WinInvalidateRect(hWnd, NULL, TRUE);
      return 0;
    }

    case WM_PAINT:
    {
      // Получаем пространство отображения
      hps = WinBeginPaint(hWnd, NULLHANDLE, &rec);

      // Закрашиваем область, требующую обновление
      WinFillRect(hps, &rec, CLR_WHITE);

      // Выбираем в пространство отображения шрифт
      // с фиксированной шириной символов
      SetCourierFont(hps);

      // Устанавливаем начальные координаты для
      // вывода текста
      cxCurrentPosition = -nXScrollPos * cxChar 
        + cxChar;
      cyCurrentPosition = cyClient;

      // Выводим метрики шрифта
      PrintString(hps, fm.szFamilyname,
        "szFamilyname");
      PrintString(hps, fm.szFacename,       
        "szFacename");

      PrintLong(hps, fm.idRegistry,         
        "idRegistry");
      PrintLong(hps, fm.usCodePage,         
        "usCodePage");
      PrintLong(hps, fm.lEmHeight,          
        "lEmHeight");
      PrintLong(hps, fm.lXHeight,           
        "lXHeight");
      PrintLong(hps, fm.lMaxAscender,       
        "lMaxAscender");
      PrintLong(hps, fm.lMaxDescender,      
        "lMaxDescender");
      PrintLong(hps, fm.lLowerCaseAscent,   
        "lLowerCaseAscent");
      PrintLong(hps, fm.lLowerCaseDescent,  
        "lLowerCaseDescent");
      PrintLong(hps, fm.lInternalLeading,   
        "lInternalLeading");
      PrintLong(hps, fm.lExternalLeading,   
        "lExternalLeading");
      PrintLong(hps, fm.lAveCharWidth,      
        "lAveCharWidth");
      PrintLong(hps, fm.lMaxCharInc,        
        "lMaxCharInc");
      PrintLong(hps, fm.lEmInc, "lEmInc");
      PrintLong(hps, fm.lMaxBaselineExt,    
         "lMaxBaselineExt");
      PrintLong(hps, fm.sCharSlope,         
         "sCharSlope");
      PrintLong(hps, fm.sInlineDir,         
         "sInlineDir");
      PrintLong(hps, fm.sCharRot,           
         "sCharRot");
      PrintLong(hps, fm.usWeightClass,      
         "usWeightClass");
      PrintLong(hps, fm.usWidthClass,       
         "usWidthClass");
      PrintLong(hps, fm.sXDeviceRes,        
         "sXDeviceRes");
      PrintLong(hps, fm.sYDeviceRes,        
         "sYDeviceRes");
      PrintLong(hps, fm.sFirstChar,         
         "sFirstChar");
      PrintLong(hps, fm.sLastChar,          
         "sLastChar");
      PrintLong(hps, fm.sDefaultChar,       
         "sDefaultChar");
      PrintLong(hps, fm.sBreakChar,         
         "sBreakChar");
      PrintLong(hps, fm.sNominalPointSize,  
         "sNominalPointSize");
      PrintLong(hps, fm.sMinimumPointSize,  
         "sMinimumPointSize");
      PrintLong(hps, fm.sMaximumPointSize,  
         "sMaximumPointSize");
      PrintLong(hps, fm.fsType, "fsType");
      PrintLong(hps, fm.fsDefn, "fsDefn");
      PrintLong(hps, fm.fsSelection,        
         "fsSelection");
      PrintLong(hps, fm.fsCapabilities,     
         "fsCapabilities");
      PrintLong(hps, fm.lSubscriptXSize,    
         "lSubscriptXSize");
      PrintLong(hps, fm.lSubscriptYSize,    
         "lSubscriptYSize");
      PrintLong(hps, fm.lSubscriptXOffset,  
         "lSubscriptXOffset");
      PrintLong(hps, fm.lSubscriptYOffset,  
         "lSubscriptYOffset");
      PrintLong(hps, fm.lSuperscriptXSize,  
         "lSuperscriptXSize");
      PrintLong(hps, fm.lSuperscriptYSize,  
         "lSuperscriptYSize");
      PrintLong(hps, fm.lSuperscriptXOffset,
         "lSuperscriptXOffset");
      PrintLong(hps, fm.lSuperscriptYOffset,
         "lSuperscriptYOffset");
      PrintLong(hps, fm.lUnderscoreSize,    
         "lUnderscoreSize");
      PrintLong(hps, fm.lUnderscorePosition,
         "lUnderscorePosition");
      PrintLong(hps, fm.lStrikeoutSize,     
         "lStrikeoutSize");
      PrintLong(hps, fm.lStrikeoutPosition, 
         "lStrikeoutPosition");
      PrintLong(hps, fm.sKerningPairs,      
         "sKerningPairs");
      PrintLong(hps, fm.sFamilyClass,       
         "sFamilyClass");
      PrintLong(hps, fm.lMatch, "lMatch");
      PrintLong(hps, fm.FamilyNameAtom,     
         "FamilyNameAtom");
      PrintLong(hps, fm.FaceNameAtom,       
         "FaceNameAtom");

      // Устанавливаем шрифт, выбранный в пространство
      // отображения по умолчанию
      ResetFont(hps);

      // Возвращаем пространство отображения
      WinEndPaint(hps);
      return 0;
    }

    case WM_ERASEBACKGROUND:
      return(MRFROMLONG(1L));

    // Это сообщение приходит от вертикальной
    // полосы просмотра
    case WM_VSCROLL:
    {
      // В зависимости от кода извещения
      // изменяем содержимое переменной, в которой
      // хранится координата движка
      switch (SHORT2FROMMP(mp2))
      {
        case SB_LINEDOWN:
        {
          nYScrollPos += 1;
          break;
        }
        case SB_LINEUP:
        {
          nYScrollPos -= 1;
          break;
        }
        case SB_PAGEDOWN:
        {
          nYScrollPos += cyClient / cyChar;
          break;
        }
        case SB_PAGEUP:
        {
          nYScrollPos -= cyClient / cyChar;
          break;
        }
        case SB_SLIDERTRACK:
        {
          nYScrollPos = SHORT1FROMMP(mp2);
          break;
        }

        default:
          break;
      }

      // Ограничиваем диапазон изменения
      // координаты движка
      if(nYScrollPos > YSIZE) nYScrollPos = YSIZE;
      if(nYScrollPos < 0)  nYScrollPos = 0;

      // Устанавливаем новую позицию движка
      WinSendMsg(hwndYScroll, SBM_SETPOS,
        (MPARAM)nYScrollPos, NULL);

      // Перерисовываем окно приложения
      WinInvalidateRect(hWnd, NULL, TRUE);

      return 0;
    }

    // Это сообщение приходит от горизонтальной
    // полосы просмотра
    case WM_HSCROLL:
    {
      // В зависимости от кода извещения
      // изменяем содержимое переменной, в которой
      // хранится координата движка
      switch (SHORT2FROMMP(mp2))
      {
        case SB_LINELEFT:
        {
          nXScrollPos -= 1;
          break;
        }
        case SB_LINERIGHT:
        {
          nXScrollPos += 1;
          break;
        }
        case SB_PAGERIGHT:
        {
          nXScrollPos += 10;
          break;
        }
        case SB_PAGELEFT:
        {
          nXScrollPos -= 10;
          break;
        }
        case SB_SLIDERTRACK:
        {
          nXScrollPos = SHORT1FROMMP(mp2);
          break;
        }

        default:
          break;
      }

      // Ограничиваем диапазон изменения
      // координаты движка
      if(nXScrollPos < 0)  nXScrollPos = 0;

      // Устанавливаем новую позицию движка
      WinSendMsg(hwndXScroll, SBM_SETPOS,
        (MPARAM)nXScrollPos, NULL);

      // Перерисовываем окно приложения
      WinInvalidateRect(hWnd, NULL, TRUE);

      return 0;
    }

    // Это сообщение появляется, когда пользователь
    // нажимает или отжимает клавишу
    case WM_CHAR:
    {
      // Пропускаем только виртуальные клавиши
      if(!(CHARMSG(&msg) ->fs & KC_VIRTUALKEY))
        return 0;

      // Фильтруем сообщения, поступающие при
      // отжатии клавиш
      if(!(CHARMSG(&msg) ->fs & KC_KEYUP))
        return 0;

      // В зависимости от виртуального кода клавиши
      // посылаем окну нашего прилоджения
      // сообщения WM_HSCROLL или WM_HSCROLL для
      // работы с полосами просмотра при помощи
      // клавиатуры
      switch(CHARMSG(&msg) -> vkey)
      {
        case VK_LEFT:
        {
           WinSendMsg(hWnd, WM_HSCROLL,
             NULL, MPFROM2SHORT(0, SB_LINELEFT));
          break;
        }
        case VK_RIGHT:
        {
           WinSendMsg(hWnd, WM_HSCROLL,
             NULL, MPFROM2SHORT(0, SB_LINERIGHT));
          break;
        }
        case VK_UP:
        {
           WinSendMsg(hWnd, WM_VSCROLL,
             NULL, MPFROM2SHORT(0, SB_LINEUP));
          break;
        }
        case VK_DOWN:
        {
           WinSendMsg(hWnd, WM_VSCROLL,
             NULL, MPFROM2SHORT(0, SB_LINEDOWN));
          break;
        }
        case VK_PAGEUP:
        {
           WinSendMsg(hWnd, WM_VSCROLL,
             NULL, MPFROM2SHORT(0, SB_PAGEUP));
          break;
        }
        case VK_PAGEDOWN:
        {
           WinSendMsg(hWnd, WM_VSCROLL,
             NULL, MPFROM2SHORT(0, SB_PAGEDOWN));
          break;
        }
        default:
          break;
      }

      return 0;
    }

    default:
      return(WinDefWindowProc(hWnd, msg, mp1, mp2));
  }
}

// =================================================
// Выбор шрифта с фиксированной шириной символов
// =================================================

void SetCourierFont(HPS hps)
{
  FATTRS fat;

  // Заполняем структуру описанием нужного
  // нам шрифта
  fat.usRecordLength = sizeof(FATTRS);
  strcpy(fat.szFacename ,"Courier");
  fat.fsSelection = 0;
  fat.lMatch = 0L;
  fat.idRegistry = 0;
  fat.usCodePage = 850;
  fat.lMaxBaselineExt = 12L;
  fat.lAveCharWidth = 12L;
  fat.fsType = 0;
  fat.fsFontUse = FATTR_FONTUSE_NOMIX;

  // Создаем логический шрифт, имеющий идентификатор 1L
  GpiCreateLogFont(hps, NULL, 1L, &fat);

  // Выбираем созданный шрифт 
  // в пространство отображения
  GpiSetCharSet(hps, 1L);
}

// =================================================
// Установка шрифта, выбранного в пространство
// отображения по умолчанию
// =================================================
void ResetFont(HPS hps)
{
  GpiSetCharSet(hps, LCID_DEFAULT);
  GpiDeleteSetId(hps, 1L);
}

// =================================================
// Вывод в окне значения строчной переменной
// =================================================

void PrintString(HPS hps, PSZ pszValue, PSZ pszName)
{
  int i;
  CHAR szBuf[80];
  POINTL ptl;

  // Выводим название поля структуры
  sprintf(szBuf, "%s", pszName);
  i = strlen(szBuf);

  ptl.x = cxCurrentPosition;
  ptl.y = cyCurrentPosition -
    cyChar * (1 - nYScrollPos);
  GpiCharStringAt(hps, &ptl, i, szBuf);

  // Выводим значение, записанное в этом поле
  sprintf(szBuf, "= %s", pszValue);
  i = strlen(szBuf);

  ptl.x = cxCurrentPosition + 20 * cxChar;
  ptl.y = cyCurrentPosition -
    cyChar * (1 - nYScrollPos);
  GpiCharStringAt(hps, &ptl, i, szBuf);

  // Изменяем текущую позицию для вывода
  cyCurrentPosition -= cyChar;
}

// =================================================
// Вывод в окне значения переменной типа LONG
// =================================================

void PrintLong(HPS hps, LONG lValue, PSZ pszName)
{
  int i;
  CHAR szBuf[80];
  POINTL ptl;

  sprintf(szBuf, "%s", pszName);
  i = strlen(szBuf);

  ptl.x = cxCurrentPosition;
  ptl.y = cyCurrentPosition - 
     cyChar * (1 - nYScrollPos);
  GpiCharStringAt(hps, &ptl, i, szBuf);

  sprintf(szBuf, "= %ld", lValue);
  i = strlen(szBuf);

  ptl.x = cxCurrentPosition + 20 * cxChar;
  ptl.y = cyCurrentPosition - 
    cyChar * (1 - nYScrollPos);
  GpiCharStringAt(hps, &ptl, i, szBuf);

  cyCurrentPosition -= cyChar;
}

Определения и глобальные переменные

В программе определены две константы с именами XSIZE и YSIZE, которые задают верхнюю границу изменений значений, соответственно, горизонтальной и вертикальной полосы просмотра.

Обработчик сообщения WM_SIZE записывает в переменные cxClient и cyClient размеры окна Client Window.

В переменных cxChar, cyChar и cyDesc хранятся основные размеры символов для шрифта Courier. Мы выбрали этот шрифт для отображения информации в главном окне приложения.

Структура fm содержит метрики шрифта, которые вы будете просматривать в окне Client Window.

Для того чтобы посылать сообщения окнам полос просмотра, необходимо знать их идентификаторы. Эти идентификаторы хранятся в переменных hwndXScroll и hwndYScroll (соответственно, для горизонтальной и вертикальной полосы просмотра).

В переменных nXScrollPos и nYScrollPos хранятся текущие координаты движков полос просмотра. Эти координаты нужны для определения текущих координат, начиная с которых в окне Client Window будет выводиться текст. Начальные координаты для вывода текста записаны в переменных cxCurrentPosition, cyCurrentPosition и, разумеется, зависят от положения движков полос просмотра.

Функция main

Особенностью функции main является то, что при создании главного окна приложения используются флаги FCF_VERTSCROLL и FCF_HORZSCROLL :

ULONG flFrameFlags =
  FCF_SYSMENU    | FCF_TITLEBAR      | FCF_MINMAX   |
  FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
  FCF_ICON       | FCF_VERTSCROLL     | FCF_HORZSCROLL ;

Для определения отображаемых в своем главном окне метрик шрифта функция main получает пространство отображения, выбирает в него шрифт Courier и с помощью функции GpiQueryFontMetrics заполняет структуру fm. Затем функция main восстанавливает шрифт, выбранный в пространство отображения по умолчанию, и возвращает пространство отображения. После этого запускается цикл обработки сообщений.

Функция окна WndProc

Рассмотрим обработку сообщений функцией окна WndProc.

Сообщение WM_CREATE

В процессе создания окна приложения начальные координаты движков, хранящиеся в переменных nXScrollPos и nYScrollPos, устанавливаются в нулевое значение. Это соответствует начальному положению движков, в которое они устанавливаются при создании этих полос просмотра.

Начальные координаты для вывода текста в окне Client Window устанавливаются следующим образом:

cyCurrentPosition = cyClient;
cxCurrentPosition = nXScrollPos * cxChar + cxChar;

Так как начальная координата по оси Y устанавливается равной высоте окна, вывод текста начнется в верхней части экрана. Начальная позиция по оси X устанавливается равной средней ширине символа выбранного шрифта, поэтому первый символ первой строки текста будет нарисован в верхнем левом углу экрана с небольшим отступом слева.

Далее обработчик сообщения WM_CREATE определяет идентификаторы окон полос просмотра, сохраняя их в глобальных переменных hwndYScroll и hwndXScroll. Для определения этих идентификаторов используется функция WinWindowFromID.

Так как через первый параметр этой функции необходимо передать идентификатор окна, которое является родительским для полос просмотра, мы получаем идентификатор родительского окна при помощи функции WinQueryWindow. В качестве первого параметра этой функции передается идентификатор окна Client Window, а в качестве второго - константу QW_PARENT. Так как окно Client Window и окна полос просмотра имеют одно и то же родительское окно, функция WinQueryWindow вернет нужный идентификатор окна, который можно передать функции WinWindowFromID.

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

WinSendMsg(hwndYScroll, SBM_SETSCROLLBAR, (MPARAM)0,
  MPFROM2SHORT(0, YSIZE));
WinSendMsg(hwndXScroll, SBM_SETSCROLLBAR, (MPARAM)0,
  MPFROM2SHORT(0, XSIZE));

Сообщение WM_SIZE

Обработчик сообщения WM_SIZE сохраняет размеры окна Client Window в глобальных переменных cxClient и cyClient, а затем объявляет, что окно требует перерисовки. Последнее выполняется с помощью функции WinInvalidateRect.

Сообщение WM_PAINT

При обработке сообщения WM_PAINT функция окна нашего приложения рисует в окне Client Window строки значений параметров метрик шрифта.

Прежде всего обработчик сообщения WM_PAINT получает пространство отображения и закрашивает внутреннюю область окна. Затем с помощью функции SetCourierFont, определенной в нашем приложении, в пространство отображения выбирается шрифт с фиксированной шириной символов Courier.

Затем начальные координаты для вывода текста устанавливаются так, как это показано ниже:

cxCurrentPosition = -nXScrollPos * cxChar + cxChar;
cyCurrentPosition = cyClient;

Вывод текста всегда начинается в верхней части окна, так как в переменную cyCurrentPosition записывается значение высоты окна cyClient. Что же касается оси X, то текущая позиция по этой оси определяется с учетом текущей позиции движка горизонтальной полосы просмотра. Чем эта позиция больше (т. е. чем правее расположен движок), тем меньше значение позиции вывода текста.

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

Далее обработчик сообщения WM_PAINT несколько раз вызывает функции PrintString и PrintLong, отображающие, соответственно, текстовые и числовые параметры метрик шрифта. Эти функции после рисования одной строки текста изменяют значение текущей позиции вывода по оси Y.

Перед тем как вернуть управление, обработчик сообщения WM_PAINT восстанавливает шрифт и возвращает пространство отображения.

Сообщение WM_VSCROLL

Сообщение WM_VSCROLL поступает в функцию окна, когда пользователь работает с вертикальной полосой просмотра при помощи мыши или нажимает клавиши, которые используются нашим приложением для работы с этой полосой просмотра.

Анализируя значение старшего слова параметра mp2, в котором хранится код команды, обработчик сообщения WM_VSCROLL изменяет соответствующим образом значение глобальной переменной nYScrollPos. Напомним, что в этой переменной хранится текуще положение движка вертикальной полосы просмотра.

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

WinSendMsg(hwndYScroll, SBM_SETPOS,
  (MPARAM)nYScrollPos, NULL);

Для того чтобы указанные изменения позиции движка отразились на содержимом окна Client Window, его необходимо перерисовать. Поэтому перед возвращением управления обработчик сообщения WM_VSCROLL вызывает функцию WinInvalidateRect, перерисовывая окно приложения.

Сообщение WM_HSCROLL

Сообщение WM_HSCROLL поступает в функцию окна, когда пользователь работает с горизонтальной полосой просмотра или клавишами, предназначенными для свертки текста по горизонтали. Обработчик этого сообщения выполняет действия, аналогичные действиям обработчика сообщения WM_VSCROLL, однако он изменяет содержимое глобальной переменной nXScrollPos (позиция движка горизонтальной полосы просмотра).

Сообщение WM_CHAR

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

Прежде всего этот обработчик отфильтровывает сообщения от клавиш, не имеющих кода виртуальной клавиши, и сообщения, которые приходят при отжатии клавиш:

if(!(CHARMSG(&msg) ->fs & KC_VIRTUALKEY))
  return 0;
if(!(CHARMSG(&msg) ->fs & KC_KEYUP))
  return 0;

Затем анализируется код виртуальной клавиши и в зависимости от него окну Client Window посылаются сообщения WM_HSCROLL или WM_VSCROLL с соответствующим кодом команды, например:

switch(CHARMSG(&msg) -> vkey)
{
  case VK_LEFT:
  {
    WinSendMsg(hWnd, WM_HSCROLL,
      NULL, MPFROM2SHORT(0, SB_LINELEFT));
    break;
  }
  case VK_RIGHT:
  {
    WinSendMsg(hWnd, WM_HSCROLL,
      NULL, MPFROM2SHORT(0, SB_LINERIGHT));
    break;
   }
   . . .
}

Функция PrintString

Функция PrintString, определенная в нашем приложении, используется для ввода в окно приложения текстовых параметров метрик шрифта.

Вначале эта функция подготавливает в буфере szBuf название поля шрифта, а переменной i - размер соответствующей строки символов. Затем в структуру ptl типа POINTL записываются начальные координаты вывода строки. Рисование строки выполняется функцией GpiCharStringAt :

ptl.x = cxCurrentPosition;
ptl.y = cyCurrentPosition - cyChar * (1 - nYScrollPos);
GpiCharStringAt(hps, &ptl, i, szBuf);

Заметьте, что текущая координата по оси Y зависит от положения движка вертикальной полосы просмотра.

Далее аналогичным образом выполняется печать значения поля.

Перед возвращением управления функция уменьшает текущую позицию вывода по оси Y на высоту символа, чтобы следующая строка была нарисована ниже:

cyCurrentPosition -= cyChar;

Функция PrintLong

Функция PrintLong аналогична только что описанной функции PrintString, и отличается от последней только тем, что используется для отображения значения типа LONG.

Файл scroll.h

Файл scroll.h содержит определения константы ID_APP_FRAMEWND, а также прототипы функций, определенных в приложении SCROLL (листинг 8.10).

Листинг 8.10. Файл scroll\scroll.h

#define ID_APP_FRAMEWND 1
void SetCourierFont(HPS hps);
void ResetFont(HPS hps);
void PrintString(HPS hps, PSZ pszValue, PSZ pszName);
void PrintLong(HPS hps, LONG lValue, PSZ pszName);

Файл scroll.rc

Файл описания ресурсов приложения scroll.rc представлен в листинге 8.11.

Листинг 8.11. Файл scroll\scroll.rc

#include <os2.h>
#include "scroll.h"
ICON ID_APP_FRAMEWND SCROLL.ICO

Файл scroll.def

Файл определения модуля scroll.def приведен в листинге 8.12.

Листинг 8.12. Файл scroll\scroll.def

NAME        SCROLL   WINDOWAPI
DESCRIPTION 'Scroll Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc

8.7. Регуляторы

Для регулировки каких-либо величин (цвета, громкости, яркости, размеров и т. п.) в приложениях Presentation Manager вы, конечно, можете использовать только что рассмотренную полосу просмотра. Однако есть более подходящее решение - создание регуляторов на базе предопределенного класса окна WC_CIRCULARSLIDER или WC_SLIDER .

Первый из этих классов позволяет легко создать регулятор круглой формы, напоминающий хорошо знакомые вам регуляторы в радиоаппаратуре. На базе класса WC_SLIDER можно создать линейный регулятор. В нашей книге мы рассмотрим только регулятор круглой формы (рис. 8.6), отложив описание линейного регулятора до одной из следующих книг, посвященных операционной системе IBM OS/2 Warp.

Рис. 8.6. Регулятор круглой формы

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

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

Создание круглого регулятора

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

hWndCirc1 = WinCreateWindow(hWnd,
  WC_CIRCULARSLIDER ,
  "Красный", WS_VISIBLE | CSS_NOTEXT, 0, 0, 0, 0,
  hWnd, HWND_TOP, CIRCSLD1_ID, NULL, NULL);

Здесь мы привели фрагмент обработчика сообщения WM_CREATE функции окна приложения Client Window, имеющего идентификатор hWnd. Размеры и расположение регулятора будут определены позже при обработке сообщения WM_SIZE. Как вы можете заметить, регулятор создается точно также, как и любой другой описанные нами ранее орган управления.

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

Стиль Описание
CSS_360 С помощью этого стиля вы можете создать ручку управления, которая будет поворачиваться на 360 градусов. При этом будут автоматически установлены стили CSS_NONUMBER и CSS_NOBUTTON
CSS_CIRCULARVALUE Вместо риски на поверхности ручки будет создана круглая метка в виде небольшого углубления круглой формы
CSS_MIDPOINT Начальная и конечная риска на шкале регулятора будет иметь увеличенную длину
CSS_NOBUTTON Если указан этот стиль, в окне органа управления не будет кнопок + и -
CSS_NONUMBER Отмена отображения цифрового значения текущей позиции
CSS_NOTEXT Отмена отображения подписи под регулятором
CSS_POINTSELECT Если указан стиль CSS_POINTSELECT, изменяется способ управления регулятором при помощи мыши. Если установить курсор мыши в новую позицию и сделать щелчок левой клавишей мыши, регулятор скачкообразно изменит свою текущую позицию таким образом, что она будет совпадать с позицией курсора
CSS_PROPORTIONALTICKS Длина рисок на шкале регулятора будет вычисляться в процентах от радиуа регулятора. Этот способ удобен при работе видеоадаптера в режимах с низким разрешением

Установка параметров круглого регулятора

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

Диапазон изменения значений

Для установки диапазона изменения значений регулятору с помощью функции WinSendMsg посылается сообщение CSM_SETRANGE :

WinSendMsg(hWndCirc1, CSM_SETRANGE,
  MPFROMLONG(0L), MPFROMLONG(255L));

Через параметр mp1 этого сообщения передается начальное значение, а через параметр mp2 - конечное значение позиции. В приведенном выше примере при вращении ручки регулятора текущая позиция будет изменяться в диапазоне от 0 до 255.

Начальная позиция

Начальная позиция регулятора устанавливается при помощи сообщения CSM_SETVALUE . Ниже показан фрагмент кода, в котором начальная позиция устанавливается равной 255:

WinSendMsg(hWndCirc1, CSM_SETVALUE,
  MPFROMLONG(255L), NULL);

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

Шаг изменения позиции и шаг размещения меток

Оба эти параметра устанавливаются одновременно при помощи сообщения CSM_SETINCREMENT :

WinSendMsg(hWndCirc1, CSM_SETINCREMENT,
  MPFROMLONG(10L), MPFROMLONG(2L));

Через параметр mp1 передается значение шага изменения позиции, а через параметр mp2 - значение шага размещения меток. Чем больше шаг изменения позиции, тем грубее регулировка.

Замена кнопок

При необходимости вы можете изменить внешний вид кнопок + и -, подготовив для них новые растровые изображения. Идентификаторы этих изображений необходимо передать вместе с сообщением CSM_SETBITMAPDATA . Подробное описание этого сообщения мы приведем в одной из следующих книг после того, как познакомим вас с графическим интерфейсом системы Presentation Manager и опишем способы работы с растровыми изображениями.

Определение параметров круглого регулятора

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

Текущее значение позиции

С помощью сообщения CSM_QUERYVALUE приложение может определить текущее значение позиции. Для этого через параметр mp1 вместе с этим сообщением необходимо передать указатель на переменную типа USHORT, в которую будет записано искомое значение.

Диапазон изменения значений

Посылая окну регулятора сообщение CSM_QUERYRANGE , приложение может определить диапазон изменения значений. Через параметр mp1 при этом необходимо передать указатель на переменную типа USHORT, в которую будет записано начальное значение позиции, а через параметр mp2 - указатель на переменную такого же типа для записи конечной позиции регулятора.

Радиус регулятора

Вы можете определить радиус регулятора, посылая его окну сообщение CSM_QUERYRADIUS . Через параметр mp1 при этом необходимо передать указатель на переменную типа USHORT, в которую будет записан радиус.

Шаг изменения позиции и шаг размещения меток

Для определения шага изменения позиции и шага размещения меток окну регулятора необходимо послать сообщение CSM_QUERYINCREMENT . Через параметры mp1 и mp2 необходимо передать указатели на переменные типа USHORT, в первую из которых будет записано значение шага изменения позиции, а во вторую - значение шага размещения меток.

Извещения от круглого регулятора

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

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

Через старшее слово параметра mp1 передается код извещения, который соответствует операции, выполняемой пользователем. Код извещения может принимать одно из следующих значений:

Код извещения Описание
CSN_SETFOCUS Регулятор получил или потерял фокус ввода. В первом случае через параметр mp2 передается значение TRUE, во втором - FALSE
CSN_CHANGED Пользователь изменил текущую позицию регулятора. Новое значение позиции передается через параметр mp2
CSN_TRACKING Ручка регулятора перемещается при помощи мыши. Промежуточные значения позиции передаются через параметр mp2

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

8.8. Приложение SLIDER

В приложении SLIDER мы создаем три круглых регулятора (рис. 8.7), с помощью которых можно изменять цвет окна приложения. Каждый из этих регуляторов изменяет значение цветовой компоненты (красной, зеленой или синей) в диапазоне от 0 до 255 (максимально возможное значение). Устанавливая значения компонент, вы можете выбрать любой цвет окна.

Рис. 8.7. Главное окно приложения SLIDER с регуляторами для изменения компонент цвета окна

Исходные тексты приложения приведены в листинге 8.13.

Листинг 8.13. Файл slider\slider.c

// =================================================
// Определения
// =================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS
#include <os2.h>
#include "slider.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

// =================================================
// Глобальные переменные
// =================================================

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

// Идентификаторы регуляторов
HWND hWndCirc1;
HWND hWndCirc2;
HWND hWndCirc3;

// Компоненты цвета
SHORT sColorR = 255, sColorG = 255, sColorB = 255;

CHAR szAppTitle[] = "Slider Demo";

// =================================================
// Главная функция приложения main
// =================================================

int main()
{
  HMQ   hmq;
  QMSG  qmsg;
  BOOL  fRc;

  // Флаги для создания окна Frame Window
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR      | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON;

  // Имя класса главного окна
  CHAR  szWndClass[] = "SLIDERDEMO";

  hab = WinInitialize(0);
  if(hab == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue(hab, 0);
  if(hmq == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinTerminate(hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass(hab, szWndClass,
    (PFNWP)WndProc, 0, 0);
  if(fRc == FALSE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow (HWND_DESKTOP,
    WS_VISIBLE,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);
  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);

    return(-1);
  }

  // Запускаем цикл обработки сообщений
  while(WinGetMsg(hab, &qmsg, 0, 0, 0))
    WinDispatchMsg(hab, &qmsg);

  WinDestroyWindow(hWndFrame);
  WinDestroyMsgQueue(hmq);
  WinTerminate(hab);
  return(0);
}

// =================================================
// Функция главного окна приложения
// =================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  HPS hps;
  RECTL rec;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Создаем регулятор красного цвета
      hWndCirc1 = WinCreateWindow(hWnd,
        WC_CIRCULARSLIDER,
        "Красный", WS_VISIBLE, 0, 0, 0, 0,
        hWnd, HWND_TOP, CIRCSLD1_ID, NULL, NULL);

      // Устанавливаем диапазон изменения значений
      WinSendMsg(hWndCirc1, CSM_SETRANGE,
        MPFROMLONG(0L), MPFROMLONG(255L));

      // Устанавливаем шаг для изменения позиции
      // и шаг размещения меток
      WinSendMsg(hWndCirc1, CSM_SETINCREMENT,
        MPFROMLONG(10L), MPFROMLONG(2L));

      // Устанавливаем начальное значение
      WinSendMsg(hWndCirc1, CSM_SETVALUE,
        MPFROMLONG(255L), NULL);

      // Создаем регулятор зеленого цвета
      // и настраиваем его параметры
      hWndCirc2 = WinCreateWindow(hWnd,
        WC_CIRCULARSLIDER,
        "Зеленый", WS_VISIBLE, 0, 0, 0, 0,
        hWnd, HWND_TOP, CIRCSLD2_ID, NULL, NULL);

      WinSendMsg(hWndCirc2, CSM_SETRANGE,
        MPFROMLONG(0L), MPFROMLONG(255L));

      WinSendMsg(hWndCirc2, CSM_SETINCREMENT,
        MPFROMLONG(10L), MPFROMLONG(2L));

      WinSendMsg(hWndCirc2, CSM_SETVALUE,
        MPFROMLONG(255L), NULL);

      // Создаем регулятор синего цвета
      // и настраиваем его параметры
      hWndCirc3 = WinCreateWindow(hWnd, 
        WC_CIRCULARSLIDER,
        "Синий", WS_VISIBLE, 0, 0, 0, 0,
        hWnd, HWND_TOP, CIRCSLD3_ID, NULL, NULL);

      WinSendMsg(hWndCirc3, CSM_SETRANGE,
        MPFROMLONG(0L), MPFROMLONG(255L));

      WinSendMsg(hWndCirc3, CSM_SETINCREMENT,
        MPFROMLONG(10L), MPFROMLONG(2L));

      WinSendMsg(hWndCirc3, CSM_SETVALUE,
        MPFROMLONG(255L), NULL);

      return FALSE;
    }

    // Изменяем размер и расположение регуляторов
    case WM_SIZE:
    {
      WinSetWindowPos(hWndCirc1, HWND_TOP,
        0, 0, 150, 150, 
        SWP_SIZE | SWP_MOVE | SWP_ZORDER);

      WinSetWindowPos(hWndCirc2, HWND_TOP,
        150, 0, 150, 150, 
        SWP_SIZE | SWP_MOVE | SWP_ZORDER);

      WinSetWindowPos(hWndCirc3, HWND_TOP,
        300, 0, 150, 150, 
        SWP_SIZE | SWP_MOVE | SWP_ZORDER);

      // Перерисовываем окно приложения
      WinInvalidateRect(hWnd, NULL, TRUE);
      return 0;
    }

    case WM_PAINT:
    {
      // Получаем пространство отображения
      hps = WinBeginPaint(hWnd, NULLHANDLE, &rec);

      // Переключаем таблицу цветов в режим RGB
      GpiCreateLogColorTable(hps,
        LCOL_RESET, LCOLF_RGB, 0L, 0L, NULL);

      // Закрашиваем область, требующую обновление
      WinFillRect(hps, &rec,
        (ULONG)sColorR << 16 |
        (ULONG)sColorG << 8  | (ULONG)sColorB);

      // Возвращаем пространство отображения
      WinEndPaint(hps);
      return 0;
    }

    case WM_ERASEBACKGROUND:
      return(MRFROMLONG(1L));

    case WM_CONTROL:
    {
      // Сообщение пришло от красного регулятора
      if(SHORT1FROMMP(mp1) == CIRCSLD1_ID)
      {
        // Выделяем код извещения
        if(SHORT2FROMMP(mp1) == CSN_CHANGED)
        {
           // Устанавливаем новое значение цвета
           sColorR = (SHORT)mp2;

           // Перерисовываем окно
           WinInvalidateRect(hWnd, NULL, TRUE);
        }
      }

      // Сообщение от зеленого регулятора
      else if(SHORT1FROMMP(mp1) == CIRCSLD2_ID)
      {
        if(SHORT2FROMMP(mp1) == CSN_CHANGED)
        {
           sColorG = (SHORT)mp2;
           WinInvalidateRect(hWnd, NULL, TRUE);
        }
      }

      // Сообщение от синего регулятора
      else if(SHORT1FROMMP(mp1) == CIRCSLD3_ID)
      {
        if(SHORT2FROMMP(mp1) == CSN_CHANGED)
        {
           sColorB = (SHORT)mp2;
           WinInvalidateRect(hWnd, NULL, TRUE);
        }
      }
      return 0;
    }
    default:
      return(WinDefWindowProc(hWnd, msg, mp1, mp2));
  }
}

Глобальные переменные

В глобальных переменных hWndCirc1, hWndCirc2 и hWndCirc3 хранятся идентификаторы регуляторов цвета, создаваемых при обработке сообщения WM_CREATE. Текущие значения отдельных цветовых компонент записываются в переменные sColorR (красный цвет), sColorG (зеленый цвет) и sColorB (синий цвет).

Функция main

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

Функция WndProc

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

Сообщение WM_CREATE

Обработчик сообщения WM_CREATE создает три одинаковых регулятора, предназначенных для установки красной, зеленой и синей компоненты цвета фона окна Client Window. Все эти регуляторы создаются функцией WinCreateWindow на базе предопределенного класса окна WC_CIRCULARSLIDER.

Для каждого регулятора устанавливается диапазон изменения значений от 0 до 255. Последнее значение соответствует наиболее насыщенному цвету. Установка диапазона изменения значений выполняется при помощи сообщения CSM_SETRANGE, которое посылается по очереди окнам всех трех регуляторов.

Аналогично при помощи сообщения CSM_SETINCREMENT устанавливается шаг изменения позиции, равный 10, и шаг размещения меток, равный 2.

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

Сообщение WM_SIZE

При обработке сообщения WM_SIZE устанавливаются расположение и размеры регуляторов, для чего используется функция WinSetWindowPos. Все регуляторы будут расположены рядом в нижней части окна Client Window.

Перед тем как возвратить управление, обработчик сообщения WM_SIZE вызывает функцию WinInvalidateRect для перерисовки окна приложения.

Сообщение WM_PAINT

При обработке сообщения WM_PAINT выполняется переключение таблицы цветов, установленной для пространства отображения, в режим RGB , когда цвет определяется тремя цветовыми компонентами. Для этого вызывается функция GpiCreateLogColorTable , которая будет описана в одном из следующих томов "Библиотеки системного программиста" в разделе, посвященном графическому интерфейсу Presentation Manager.

GpiCreateLogColorTable(hps,
  LCOL_RESET, LCOLF_RGB, 0L, 0L, NULL);

Далее внутренняя область окна закрашивается функцией WinFillRect:

WinFillRect(hps, &rec,
  (ULONG)sColorR << 16 |
  (ULONG)sColorG << 8  | (ULONG)sColorB);

При этом цвет образуется из компонент, хранящихся в глобальных переменных sColorR, sColorG и sColorB. Когда пользователь вращает ручки регуляторов, в функцию окна поступает сообщение WM_CONTROL. Обработчик этого сообщения изменяет соответствующим образом содержимое указанных глобальных переменных и перерисовывает окно приложения.

Сообщение WM_CONTROL

Обработчик сообщения WM_CONTROL прежде всего анализирует младшее слово параметра mp1, через который передается идентификатор органа управления (в нашем случае, идентификатор одного из трех регуляторов). Далее из старшего слова параметра mp1 выделяется код извещения, и если он равен SLN_CHANGE, сохраняется новое значение позиции регулятора, передаваемое через параметр mp2:

if(SHORT1FROMMP(mp1) == CIRCSLD1_ID)
{
  if(SHORT2FROMMP(mp1) == CSN_CHANGED)
  {
    sColorR = (SHORT)mp2;
    WinInvalidateRect(hWnd, NULL, TRUE);
  }
}

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

Файл slider.h

Файл slider.h (листинг 8.14) содержит определения константы ID_APP_FRAMEWND, а также идентификаторов круглых регуляторов цвета CIRCSLD1_ID, CIRCSLD2_ID и CIRCSLD3_ID.

Листинг 8.14. Файл slider\slider.h

#define ID_APP_FRAMEWND 1
#define CIRCSLD1_ID     100
#define CIRCSLD2_ID     101
#define CIRCSLD3_ID     102

Файл slider.rc

Файл описания ресурсов приложения slider.rc представлен в листинге 8.15.

Листинг 8.15. Файл slider\slider.rc

#include <os2.h>
#include "slider.h"
ICON ID_APP_FRAMEWND SLIDER.ICO

Файл slider.def

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

Листинг 8.16. Файл slider\slider.def

NAME        SLIDER   WINDOWAPI
DESCRIPTION 'Slider Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc