6. Работа с мышью

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

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

6.1. Определение количества кнопок

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

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

LONG WinQuerySysValue (
  HWND hwndDeskTop, // идентификатор окна рабочего стола 
  LONG iSysValue);  // код системного параметра

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

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

LONG lButtons; // количество кнопок
lButtons = WinQuerySysValue (HWND_DESKTOP, SV_CMOUSEBUTTONS );

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

6.2. Нумерация кнопок

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

Для однокнопочной мыши все просто - единственная кнопка имеет первый номер. Левая кнопка двухкнопочной мыши имеет первый номер, а правая - второй. Кнопки трехкнопочной мыши нумеруются следующим образом: левая имеет первый номер, правая - третий, средняя - второй.

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

6.3. Сообщения, поступающие от мыши

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

Сообщения от кнопок мыши

Когда пользователь нажимает клавишу мыши, функция окна, расположенного под курсором мыши, получает одно из следующих сообщений: WM_BUTTON1DOWN , WM_BUTTON2DOWN , WM_BUTTON3DOWN (соответственно, для первой, второй и третьей кнопки). При отпускании клавиши мыши функция окна получает сообщения WM_BUTTON1UP , WM_BUTTON2UP и WM_BUTTON3UP (в зависимости от номера кнопки). Пример обработки сообщения WM_BUTTON1DOWN есть в приложении MYWINDOW, исходные тексты которого мы привели в первой главе этой книги.

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

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

В ответ на двойной щелчок в функцию окна поступают сообщения WM_BUTTON1DBLCLK , WM_BUTTON2DBLCLK и WM_BUTTON3DBLCLK . Заметим, что в ответ на первый щелчок при этом в функцию окна потсупят сообщения о нажатии кнопки и об отпускани кнопки. В ответ на второй щелчок придет сообщение о двойном щелчке и затем сообщение об отпускании кнопки.

Через параметр mp1 вместе с перечисленными выше сообщениями передаются координаты курсора мыши. Вы можете извлечь их с помощью макрокоманд SHORT1FROMMP или SHORT2FROMMP (соответственно, координату X и Y). Через параметр mp2 передаются флаги и результаты теста Hit Test (о котором мы расскажем позже).

Для извлечения параметров собщений мыши удобно использовать макрокоманду MOUSEMSG , например:

case WM_BUTTON1DOWN :
{
  cxPoint = MOUSEMSG(&msg) -> x;
  cyPoint = MOUSEMSG(&msg) -> y;
  break;
}

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

typedef struct _MOUSEMSG
{
  SHORT  x; // коодрината X
  SHORT  y; // координата Y
  USHORT codeHitTest; // результаты тестирования
  USHORT fsInp;       // флаги
} MSEMSG;
typedef MSEMSG *PMSEMSG;

#define MOUSEMSG(pmsg) \
   ((PMSEMSG)((PBYTE)pmsg + sizeof(MPARAM)))

В поле fsInp могут расплагаться флаги, аналогичные флагам, передаваемым вместе с сообщением WM_CHAR . Используя эти флаги, можно определить, была ли нажата на клавиатуре какая-либо клавиша в момент, когда пользователь нажал клавишу мыши. Список возможных значений для поля fsInp был приведен при описании сообщения WM_CHAR. Теперь к этому списку добавится только одно значение:

Флаг Маска Описание
KC_NONE 0x0000 На клавиатуре не была нажата ни одна клавиша

Поле codeHitTest мы опишем позже.

Сообщение WM_MOUSEMOVE

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

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

Сообщение WM_HITTEST

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

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

Обработчик сообщения WM_HITTEST получает через парметр mp1 координаты курсора мыши. Он может вернуть одно из перечисленных ниже значений (так называемый индикатор теста Hit Test):

Значение Описание
HT_NORMAL Сообщения мыши обрабатываются обычным образом
HT_TRANSPARENT Часть окна, расположенная под курсором мыши, считается прозрачной. Тест должен выполняться для окна, расположенного ниже под данным окном, как будто прозрачное окно не существует. В качестве прозрачного вы можете объявить, например, дочернее окно
HT_DISCARD Сообщения мыши не должны передаваться функции окна
HT_ERROR Аналогично предыдущему, однако раздается звуковой сигнал и окно отодвигается на задний план

По умолчанию обработчик активного окна возвращает значение HT_NORMAL, а обработчик заблокированного окна - значение HT_ERROR.

Другие сообщения

Одна из часто выполняемых с помощью мыши операций - перемещение объектов (drag and drop). Для выполнения этой операции вы можете использовать сообщения WM_BUTTON1MOTIONSTART и WM_BUTTON1MOTIONEND и аналогичные для кнопок с другими номерами. Первое из этих сообщений передается функции окна когда пользователь начинает операцию перемещения, нажав кнопку с соответствующим номером.

Через параметр mp1 этих сообщений передаются координаты курсора мыши, а через младшее слово параметра mp2 - результаты теста Hit Test.

Кроме этого, для обработки операций перемещения можно использовать сообщения WM_BEGINDRAG и WM_ENDDRAG . Через параметр mp1 сообщений WM_BEGINDRAG и WM_ENDDRAG передаются координаты курсора мыши, а через младшее слово параметра mp2 - флаг, определяющий устройство ввода, которое было использовано для выполнения операции перемещения. Если это была мышь, флаг равен TRUE, если клавиатура - FALSE.

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

6.4. Функции для работы с мышью

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

Захват курсора мыши

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

Для чего приложению может потребоваться захват мыши?

Например, для того чтобы выделить какую-либо область рабочего стола Workplace Shell или нарисовать что-либо вне области, занятой окном приложения. Типичный пример - приложение, сохраняющее выделенный фрагмент изображения рабочего стола в графическом файле.

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

WinSetCapture (HWND_DESKTOP, hWnd);

В качестве первого параметра функции передается идентификатор окна Desktop Window , в качестве второго - идентификатор окна, захватывающего мышь.

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

WinSetCapture (HWND_DESKTOP, NULLHANDLE);

Определение координат курсора мыши

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

Функция WinQueryPointerPos

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

BOOL WinQueryPointerPos (
  HWND    hwndDeskTop, // идентификатор окна Desktop Window 
  PPOINTL pptlPoint);  // адрес структуры типа POINTL

Положение курсора записывается в структуру типа POINTL, адрес которой передается функции через параметр pptlPoint. В случае успешного завершения функция возвращает значение TRUE, при ошибке - FALSE.

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

Функция WinQueryMsgPos

Так же как и функция WinQueryPointerPos , функция WinQueryMsgPos возвращает экранные координаты курсора мыши. Однако в отличие от функции WinQueryPointerPos, координаты курсора определяются не в момент извлечения сообщения из очереди, а в момент записи последнего сообщения в очередь приложения.

Прототип функции WinQueryMsgPos приведен ниже:

BOOL WinQueryMsgPos (
  HAB     hab,   // идентификатор блока Anchor-block
  PPOINTL pptl); // адрес структуры типа POINTL

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

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

Прототип функции WinGetKeyState представлен ниже:

LONG WinGetKeyState (
  HWND hwndDeskTop, // идентификатор окна Desktop Window 
  LONG vkey);       // код виртуальной клавиши

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

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

Константа Описание
0x0001 С момента запуска операционной системы клавиша была нажато нечетное количество раз
0x8000 Клавиша нажата

Установка позиции курсора мыши

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

BOOL WinSetPointer Pos (
  HWND hwndDeskTop, // идентификатор окна Desktop Window 
  LONG lx,          // новая экранная координата X
  LONG ly);         // новая экранная координата Y

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

Как убрать курсор мыши

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

BOOL WinShowPointer(
  HWND hwndDeskTop,  // идентификатор окна Desktop Window
  BOOL fShow);       // флаг

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

Функция WinShowPointer возвращает новое значение индикатора отображения курсора мыши.

6.5. Приложение MOUSEMOV

С помощью сообщения WM_MOUSEMOVE мы попробуем решить задачу перемещения окна приложения, не имеющего заголовка. Приложение MOUSEMOV создает окно без заголовка, системного меню и кнопок минимизации/максимизации, отображая в его центре текстовую строку (рис. 6.1).

Рис. 6.1. Окно приложения MOUSEMOV не имеет заголовка, но его можно перемещать при помощи мыши

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

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

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

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

Листинг 6.1. Файл mousemov\mousemov.c

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

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

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

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

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

CHAR szAppTitle[] = "Mouse Mover";

// Координаты курсора мыши в момент нажатия
// левой клавиши мыши
SHORT cxPoint;
SHORT cyPoint;

// Координаты курсора мыши в момент отпускания
// левой клавиши мыши
SHORT cxNewPoint;
SHORT cyNewPoint;

// Признак начала процесса перемещения окна
BOOL  fDrag = FALSE;

// Текст, который будет отображаться в окне
CHAR pszText[] =
  "Перемещайте окно при помощи левой клавиши мыши";

// Идентификаторы указателей мыши
HPOINTER hptr, hptr1;

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

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

  // Флаги для создания окна Frame Window 
  // Мы не создаем окна заголовка, системного меню,
  // а также окна кнопок минимизации и максимизации
  ULONG flFrameFlags = FCF_SIZEBORDER |
    FCF_SHELLPOSITION | FCF_TASKLIST | FCF_ICON;

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

  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;
  POINTL ptl;
  RECTL rec;
  SWP  swp;

  switch (msg)
  {
    // При создании окна загружаем указатели мыши
    case WM_CREATE :
    {
      hptr  = WinLoadPointer(HWND_DESKTOP,
        NULLHANDLE, ID_APP_POINTER);
      hptr1 = WinLoadPointer(HWND_DESKTOP,
        NULLHANDLE, ID_APP_POINTER1);
      return FALSE;
    }

    // При уничтожении окна удаляем указатели мыши
    case WM_DESTROY :
    {
      WinDestroyPointer (hptr);
      WinDestroyPointer (hptr1);
      return 0;
    }

    // Когда пользователь нажимает левую клавишу
    // мыши, запоминаем координаты курсора и
    // выдвигаем окно приложения на передний план
    case WM_BUTTON1DOWN :
    {
      cxPoint = MOUSEMSG(&msg) -> x;
      cyPoint = MOUSEMSG(&msg) -> y;

      // Изменяем расположение окна по оси Z
      WinSetWindowPos (hWndFrame, HWND_TOP ,
        0, 0, 0, 0, SWP _ZORDER );

      // Устанавливаем признак перемещения
      // главного окна приложения
      fDrag = TRUE;
      return 0;
    }

    // При отпускании левой клавиши мыши сбрасываем
    // признак перемещения окна
    case WM_BUTTON1UP :
    {
      // Сбрасываем признак перемещения
      // главного окна приложения
      fDrag = FALSE;
      return 0;
    }

    // Это сообщение приходит при перемещении курсора
    // мыши
    case WM_MOUSEMOVE :
    {
      // Если включен признак перемещения, определяем
      // новые координаты курсора и передвигаем окно
      if(fDrag)
      {
        // Выбираем указатель в виде закрытой руки
        WinSetPointer (HWND_DESKTOP, hptr1);

        cxNewPoint = MOUSEMSG(&msg) -> x;
        cyNewPoint = MOUSEMSG(&msg) -> y;

        // Определяем текущие координаты окна
        WinQueryWindow Pos(hWndFrame, &swp);

        // Передвигаем окно
        WinSetWindowPos (hWndFrame, HWND_TOP ,
          swp.x + (cxNewPoint - cxPoint),
          swp.y + (cyNewPoint - cyPoint),
          0, 0, SWP _MOVE );
      }

      else
        // Выбираем указатель в виде открытой руки
        WinSetPointer (HWND_DESKTOP, hptr);

      return (MRESULT)TRUE;
    }

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

      // Определяем размеры главного окна
      WinQueryWindow Rect(hWnd, &rec);

      // Выводим текст в центре окна
      WinDrawText (hps, -1, pszText, &rec, 0L, 0L,
        DT_WORDBREAK | DT_CENTER | DT_VCENTER |
        DT_TEXTATTRS | DT_ERASERECT);

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

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

    // При изменении размеров окна сохраняем новые
    // размеры и перерисовываем окно
    case WM_SIZE :
    {
      // При изменении размеров окна выполняем его
      // перерисовку
      WinInvalidateRect (hWnd, NULL, TRUE);
      return 0;
    }

    // Если пользователь сделал двойной щелчок левой
    // клавише мыши, завершаем работу приложения
    case WM_BUTTON1DBLCLK :
    {
      WinPostMsg (hWnd, WM_QUIT , 0L, 0L);
      return 0;
    }

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

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

В переменные cxPoint и cyPoint записываются координаты курсора мыши в момент, когда пользователь начинает перемещение окна приложения, нажав левую кнопку мыши. Эти координаты будут затем сравниваться с координатами курсора мыши после завершения процесса перемещения, которые хранятся в переменных cxNewPoint и cyNewPoint.

Переменная fDrag используется в качестве признака перемещения окна и проверяется при обработке сообщения WM_MOUSEMOVE . Вначале в нее записывается значение FALSE. Когда пользователь начинает перемещать окно, это значение изменяется на TRUE. После завершения процесса перемещения в переменную снова записывается значение FALSE.

В области глобальных переменных мы также определили переменные hptr и hptr1 типа HPOINTER , в которых хранятся идентификаторы курсоров мыши, загруженных из ресурсов прложения. В первой из этих переменных хранится идентификатор курсора в виде открытой ладони, во второй - в виде закрытой ладони.

Функция main

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

ULONG flFrameFlags = FCF_SIZEBORDER |
    FCF_SHELLPOSITION | FCF_TASKLIST | FCF_ICON;

В результате окно Frame Window не создает окна заголовка, системное меню и кнопки минимизации/максимизации. Без дополнительной обработки сообщений мыши пользователь не сможет перемещать такое окно. Размер окна можно изменять, так как указан флаг FCF_SIZEBORDER.

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

Функция окна обрабатывает сообщения WM_CREATE , WM_DESTROY , WM_BUTTON1DOWN , WM_BUTTON1UP , WM_MOUSEMOVE , WM_PAINT , WM_ERASEBACKGROUND , WM_SIZE и WM_BUTTON1DBLCLK .

Сообщение WM_CREATE

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

hptr  = WinLoadPointer(HWND_DESKTOP, NULLHANDLE, 
  ID_APP_POINTER);
hptr1 = WinLoadPointer(HWND_DESKTOP, NULLHANDLE, 
  ID_APP_POINTER1);

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

Сообщение WM_DESTROY

В ответ на сообщение WM_DESTROY , которое передается в функцию окна при завершении работы приложения, выполняется уничтожение двух загруженных курсоров мыши при помощи функции WinDestroyPointer :

WinDestroyPointer (hptr);
WinDestroyPointer (hptr1);

В качестве единственного параметра этой функции передается идентификатор уничтожаемого курсора мыши.

Сообщение WM_SIZE

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

Сообщение WM_PAINT

Обработчик сообщения WM_PAINT получает пространство отображения и затем определяет размеры окна, вызывая для этого функцию WinQueryWindow Rect. Далее с помощью функции WinDrawText в центре окна выводится текстовая строка, после чего пространство отображения освобождается.

Сообщение WM_BUTTON1DOWN

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

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

Далее окно выдвигается на передний план функцией WinSetWindowPos и устанавливается признак перемещения окна (в переменную fDrag записывается значение TRUE).

Сообщение WM_BUTTON1UP

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

Сообщение WM_MOUSEMOVE

Окно приложения получает сообщение WM_MOUSEMOVE всегда, когда над ним перемещается курсор мыши. Если пользователь нажал левую клавишу мыши и начал перемещение окна, обработчиком сообщения WM_BUTTON1UP устанавливается признак fDrag. В результате обработчик сообщения WM_MOUSEMOVE начинает процедуру перемещения окна.

Прежде всего он изменяет форму курсора мыши, вызывая функцию WinSetPointer :

WinSetPointer (HWND_DESKTOP, hptr1);

В качестве первого параметра этой функции передается идентификатор окна Desktop Window , а в качестве второго - идентификатор курсора мыши, который будет отображаться внутри окна.

На следующем шаге обработчик сообщения WM_MOUSEMOVE записывает новые координаты курсора в переменные cxNewPoint и cyNewPoint, а также определяет текущие координаты окна Frame Window , вызывая для этого функцию WinQueryWindow Pos.

Затем выполняется перемещение окна при помощи функции WinSetWindowPos :

WinSetWindowPos (hWndFrame, HWND_TOP ,
  swp.x + (cxNewPoint - cxPoint),
  swp.y + (cyNewPoint - cyPoint), 0, 0, SWP _MOVE );

Новые координаты окна выбираются исходя из текущих (записанных в структуре swp) и относительной величины смещения мыши по вертикали и горизонтали. Заметим, что функция WinQueryWindow Pos записывает в структуру swp координаты окна в системе координат, связанной с окном Desktop Window . Начало этой системы координат находится в левом нижнем углу экрана.

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

WinSetPointer (HWND_DESKTOP, hptr);

Сообщение WM_BUTTON1DBLCLK

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

Файл mousemov.h

В файле mousemov.h определены символичесике константы для ресурсов приложения (листинг 6.2).

Листинг 6.2. Файл mousemov\mousemov.h

#define ID_APP_FRAMEWND 1
#define ID_APP_POINTER  2
#define ID_APP_POINTER1 3

Файл mousemov.rc

В файле описания ресурсов приложения mousemov.rc (листинг 6.3) помимо пиктограммы определены два указателя мыши, для чего использован оператор POINTER .

Листинг 6.3. Файл mousemov\mousemov.rc

#include <os2.h>
#include "mousemov.h"
ICON    ID_APP_FRAMEWND  MOUSEMOV.ICO
POINTER ID_APP_POINTER   MOUSEMOV.PTR
POINTER ID_APP_POINTER1  MOUSE1.PTR

Файл определения модуля mousemov.def

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

Листинг 6.4. Файл mousemov\mousemov.def

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

6.6. Изменение внешнего вида курсора мыши

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

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

HPOINTER hptr;
hptr  = WinLoadPointer(HWND_DESKTOP, NULLHANDLE, 
  ID_APP_POINTER);

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

POINTER ID_APP_POINTER   MOUSEMOV.PTR

В файле MOUSEMOV.PTR находится изображение курсора мыши, подготовленное приложением Icon Editor . С помощью этого приложения вы можете создавать не только пиктограммы, но и курсоры мыши.

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

hptr = WinQuerySysPointer (HWND_DESKTOP,
  SPTR_ICONINFORMATION, FALSE);

Идентификатор встроенного курсора передается этой функции через второй параметр. Третий параметр определяет, надо ли копировать курсор для приложения (значение FALSE) либо достаточно просто вернуть идентификатор встроенного курсора (значение TRUE). В первом случае перед завершением приложение должно удалить копию встроенного курсора, передав соответствующий идентификатор фукнции WinDestroyPointer через ее единственный параметр.

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

WinSetPointer (HWND_DESKTOP, hptr);

Ниже мы приведем список всроенных идентификаторов курсора мыши вместе с кратким описанием и соответствующим изображением:

Идентификатор Описание Изображение курсора мыши
SPTR_ARROW Стандартный курсор
SPTR_TEXT Текстовый курсор, используется при редакторовании текста
SPTR_WAIT Курсор в виде часов, используется для режима, в котором пользователь должен ждать завершение какой-либо длительной операции
SPTR_MOVE Курсор для перемещения объекта
SPTR_SIZENWSE Курсор для одновременного изменения размеров окна по вертикали и горизонтали
SPTR_SIZENESW Аналогично предыдущему, но с другим направлением стрелки
SPTR_SIZEWE Курсор для изменения ширины окна
SPTR_SIZENS Курсор для изменения высоты окна

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

Идентификатор Описание Изображение пиктограммы
SPTR_APPICON Пиктограмма приложения
SPTR_ICONINFORMATION Информационное сообщение
SPTR_ICONQUESTION Запрос информации от пользователя
SPTR_ICONERROR Сообщение об ошибке
SPTR_ICONWARNING Предупреждающее сообщение
SPTR_ILLEGAL Запрещенные действия
SPTR_FILE Файл
SPTR_MULTFILE Группа файлов
SPTR_FOLDER Папка
SPTR_PROGRAM Программа

6.7. Управление курсором мыши с помощью клавиатуры

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

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

Определить текущие экранные координаты курсора мыши можно при помощи функции WinQueryPointerPos , например, так:

POINTL ptl;
WinQueryPointerPos (HWND_DESKTOP, &ptl);

Эта функция записывает в структуру ptl координаты курсора мыши в системе координат, связанной с окном рабочего стола Desktop Window . Если нужно сделать так, чтобы при управлении с помощью клавиатуры курсор мыши не выходил за рамки окна приложения, экранные координаты курсора мыши следует преобразовать в оконные. Нужные преобразования можно сделать при помощи функции WinMapWindowPoints , передав ей в качестве первого параметра идентификатор окна рабочего стола, а в качестве второго - идентификатор окна приложения:

WinMapWindowPoints (HWND_DESKTOP, hWnd, &ptl, 1);

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

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

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

WinMapWindowPoints (hWnd, HWND_DESKTOP, &ptl, 1);

Для решения второй задачи (установки курсора мыши в новую позицию) следует вызвать функцию WinSetPointer Pos , передав ей через первый параметр идентификатор окна рабочего стола, а через второй и третий - новые экранные координаты курсора мыши по оси X и Y, соответственно:

WinSetPointer Pos (HWND_DESKTOP, 
  (SHORT) ptl.x, (SHORT) ptl.y);

6.8. Приложение POINTER

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

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

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

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

Листинг 6.5. Файл pointer\pointer.c

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

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

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

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

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

CHAR szAppTitle[] = "Mouse Poiter";

// Размеры пиктограммы курсора мыши
LONG cxPointer, cyPointer;

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

// =================================================
// Главная функция приложения 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[] = "MOUSEPOINTER";

  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);
  }

  // Устанавливаем новую пиктограмму
  // для главного окна
  WinSendMsg (hWndFrame, WM_SETICON ,
    (MPARAM)WinQuerySysPointer (HWND_DESKTOP,
          SPTR_ICONINFORMATION, FALSE), NULL);

  // Запускаем цикл обработки сообщений
  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;
  POINTL ptl;
  LONG x, y;
  HPOINTER hptr;
  LONG xMouse, yMouse;

  switch (msg)
  {
    case WM_CREATE :
    {
      // Определяем и сохраняем размеры пиктограмм
      cxPointer = WinQuerySysValue (HWND_DESKTOP,
        SV_CXPOINTER);

      cyPointer = WinQuerySysValue (HWND_DESKTOP,
        SV_CYPOINTER);

      // Расстояние между пиктограммами
      cxPointer += 5;
      return FALSE;
    }

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

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

      // Рисуем стандартные изображения курсора мыши
      x = 0;
      y = cyClient - cyPointer;
      DrawMousePtr(hps, x, y, SPTR_ARROW);

      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_TEXT);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_WAIT);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_MOVE);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_SIZENWSE);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_SIZENESW);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_SIZEWE);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_SIZENS);

      // Рисуем стандартные пиктограммы
      x = 0;
      y = cyClient - 3*cyPointer;
      DrawMousePtr(hps, x, y, SPTR_APPICON);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_ICONINFORMATION);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_ICONQUESTION);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_ICONERROR);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_ICONWARNING);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_ILLEGAL);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_FILE);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_MULTFILE);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_FOLDER);
      x += cxPointer;
      DrawMousePtr(hps, x, y, SPTR_PROGRAM);

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

    case WM_MOUSEMOVE :
    {
       // Получаем идентификатор стандартной
       // пиктограммы SPTR_ICONINFORMATION
       hptr = WinQuerySysPointer (HWND_DESKTOP,
          SPTR_ICONINFORMATION, FALSE);

       // Устанавливаем курсор мыши так, чтобы он
       // имел форму пиктограммы SPTR_ICONINFORMATION
       WinSetPointer (HWND_DESKTOP, hptr);
       return (MRESULT)TRUE;
    }

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

    // При изменении размеров окна приложения
    // перерисовываем его содержимое
    case WM_SIZE :
    {
      // Сохраняем размеры окна Client Window 
      cxClient = SHORT1FROMMP (mp2);
      cyClient = SHORT2FROMMP (mp2);

      // Обновляем окно Client Window 
      WinInvalidateRect (hWnd, NULL, TRUE);
      return 0;
    }

    // Выполняем эмуляцию мыши с помощью клавиатуры
    case WM_CHAR :
    {
      // Фильтруем сообщения, соответствующие отжатию
      // клавиш, а также сообщения от обычных клавиш
      if((CHARMSG(&msg) ->fs & KC_KEYUP) |
         (!(CHARMSG(&msg) ->fs & KC_VIRTUALKEY)))
         return 0;

      // Определяем текущую позицию курсора мыши
      // в экранных координатах
      WinQueryPointerPos (HWND_DESKTOP, &ptl);

      // Преобразуем экранные координаты в оконные
      WinMapWindowPoints (HWND_DESKTOP, hWnd, &ptl, 1);

      // Сохраняем текущие оконные координаты курсора
      xMouse = ptl.x;
      yMouse = ptl.y;

      // Изменяем координаты в соответствии с
      // виртуальным кодом нажатой клавиши
      switch(CHARMSG(&msg) -> vkey)
      {
        case VK_LEFT:
        {
          xMouse -= cxPointer;
          break;
        }
        case VK_RIGHT:
        {
          xMouse += cxPointer;
          break;
        }
        case VK_DOWN:
        {
          yMouse -= cyPointer;
          break;
        }
        case VK_UP:
        {
          yMouse += cyPointer;
          break;
        }
        case VK_HOME:
        {
          xMouse = cxPointer;
          yMouse = cyClient - cyPointer;
          break;
        }
        case VK_END:
        {
          xMouse = cxClient - cxPointer;
          yMouse = cyPointer;
          break;
        }
        default:
          break;
      }

      // Не допускаем выхода курсора мыши за пределы
      // окна приложения
      ptl.x = max(min(xMouse, cxClient - 1), 0);
      ptl.y = max(min(yMouse, cyClient - 1), 0);

      // Преобразуем оконные координаты в экранные
      WinMapWindowPoints (hWnd, HWND_DESKTOP, &ptl, 1);

      // Устанавливаем новую позицию курсора мыши
      WinSetPointer Pos (HWND_DESKTOP,
        (SHORT) ptl.x, (SHORT) ptl.y);

      return 0;
    }

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

// =================================================
// Функция рисования курсора мыши
// =================================================

void DrawMousePtr(HPS hps, LONG x, LONG y, LONG lPtrId)
{
  HPOINTER hptr;

  // Получаем идентификатор стандартного курсора мыши
  hptr = WinQuerySysPointer (HWND_DESKTOP,
    lPtrId, FALSE);

  // Рисуем изображение курсора мыши
  WinDrawPointer(hps, x, y, hptr, DP_NORMAL);
}

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

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

Размеры окна приложения записываются в переменные cxClient и cyClient обработчиком сообщения WM_SIZE при создании окна и при изменении пользователем его размеров.

Функция main

Функция main имеет одну особенность - после создания главного окна приложения и перед запуском цикла обработки сообщений главному окну посылается сообщение WM_SETICON :

WinSendMsg (hWndFrame, WM_SETICON ,
 (MPARAM)WinQuerySysPointer (HWND_DESKTOP,
  SPTR_ICONINFORMATION, FALSE), NULL);

Через первый параметр вместе с этим сообщением передается идентификатор встроенной пиктограммы SPTR_ICONINFORMATION , полученный от функции WinQuerySysPointer . Второй параметр сообщения WM_SETICON равен нулю.

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

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

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

Сообщение WM_CREATE

Обработчик сообщения WM_CREATE определяет размеры курсора мыши. Эти размеры являются системными значениями, для получения которых необходимо использовать функцию WinQuerySysValue , передав ей через второй параметр константы SV_CXPOINTER (ширина курсора мыши) и SV_CYPOINTER (высота курсора мыши):

cxPointer = WinQuerySysValue (HWND_DESKTOP,
  SV_CXPOINTER);
cyPointer = WinQuerySysValue (HWND_DESKTOP,
  SV_CYPOINTER);

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

Сообщение WM_PAINT

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

x = 0;
y = cyClient - cyPointer;
DrawMousePtr(hps, x, y, SPTR_ARROW);

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

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

Сообщение WM_MOUSEMOVE

Обработчик сообщения WM_MOUSEMOVE с помощью функции WinQuerySysPointer получает идентификатор встроенной пиктограммы SPTR_ICONINFORMATION и использует его для изменения курсора мыши. Последняя операция выполняется с помощью фукнции WinSetPointer . Таким образом, для курсора мыши вы можете использовать не только встроенные изображения курсоров, но и встроенные изображения пиктограмм.

Обратите внимание, что обработка сообщения WM_MOUSEMOVE завершается возвращением значения TRUE. Это необходимо для того, чтобы отменить вызов стандартного обработчика данного сообщения, который устанавливает стандартный курсор мыши. Если после обработки сообщения WM_MOUSEMOVE передать управление функции WinDefWindowProc , форму курсора изменить не удастся, так как данная функция будет всегда восстанавливать стандартный курсор.

Сообщение WM_SIZE

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

Сообщение WM_CHAR

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

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

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

После фильтрации обработчик сообщения WM_CHAR определяет текущие экранные координаты курсора мыши и преобразует их в оконные координаты, вызывая для этого функции WinQueryPointerPos и WinMapWindowPoints . Текущие оконные координаты курсора мыши сохраняютя в глобальных переменных xMouse и yMouse.

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

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

Когда пользователь нажимает клавиши <Home> или <End>, курсор мыши устанавливается, соответственно, в верхний левый или правый нижний угол окна. Все остальные коды виртуальных клавиш фильтруются.

После изменения переменных xMouse и yMouse обработчик сообщения изменяет содержимое полей x и y структуры ptl таким образом, чтобы новые координаты курсора мыши не выходили за границы окна Client Window , размеры которого были определены при обработке сообщения WM_SIZE :

ptl.x = max(min(xMouse, cxClient - 1), 0);
ptl.y = max(min(yMouse, cyClient - 1), 0);

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

Функция DrawMousePtr

Функция DrawMousePtr, определенная в нашем приложении, получает с помощью функции WinQuerySysPointer идентификатор встроенного курсора мыши или встроенной пиктограммы, а затем рисует соответствующее изображение при помощи функции WinDrawPointer. Подробнее функции рисования мы рассмотрим позже в одной из следующих книг "Библиотеки системного программиста" в главе, посвященной графическому интерфейсу Presentation Manager.

Файл pointer.h

Файл pointer.h (листинг 6.6) содержит определение константы ID_APP_FRAMEWND, а также прототип функции DrawMousePtr.

Листинг 6.6. Файл pointer\pointer.h

#define ID_APP_FRAMEWND 1
void DrawMousePtr(HPS hps, LONG x, LONG y, 
  LONG lPtrId);

Файл pointer.rc

В файле описания ресуросв приложения pointer.rc (листинг 6.7) определена пиктограмма с идентификатором ID_APP_FRAMEWND.

Листинг 6.7. Файл pointer\pointer.rc

#include <os2.h>
#include "pointer.h"
ICON ID_APP_FRAMEWND POINTER.ICO

Файл pointer.def

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

Листинг 6.8. Файл pointer\pointer.def

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