Шаг 49.
Библиотека OWL.
Изменение курсора мыши

    На этом шаге мы рассмотрим вопросы, связанные с изменением внешнего вида курсора мыши.

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

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

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\dialog.h>
#include <owl\radiobut.h>
#include <owl\gdiobjec.h>
#include "pr49_1.rc"

// Буфер обмена.
struct
{
  BOOL buttons [12];
} transBuf;

//Класс приложения.
class TApp: public TApplication
{
  public:
	 TApp():TApplication() {}
	 void InitMainWindow();
};

// Класс главного окна.
class TWndw : public  TFrameWindow
{
  public:
	 TWndw (TWindow *parent, const char far *title);
  protected:
	 void CmCursor();
  private:
	 void ChangeCursor();

	 DECLARE_RESPONSE_TABLE (TWndw);
};

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
	EV_COMMAND (CM_CURSOR, CmCursor),
END_RESPONSE_TABLE;

// Класс диалогового окна Select Cursor.
class TDlg : public TDialog
{
  public:
	 TDlg(TWindow *parent, TResId resId);
};

// TWndw::TWndw()
// Это конструктор главного окна.
TWndw::TWndw(TWindow *parent, const char far *title):
			 TFrameWindow (parent, title)
{
  // Определить расположение и размеры окна.
  Attr.X = 20;
  Attr.Y = 20;
  Attr.W = 400;
  Attr.H = 300;
  // Добавить меню к окну.
  AssignMenu(MENU_1);
  // Инициализировать буфер обмена.
  memset (&transBuf,0,sizeof(transBuf));
  transBuf.buttons[0] = TRUE;
}

// TWndw::CmCursor()
// Эта функция реагирует на сообщение CM_CURSOR,
// которое посылается, когда пользователь
// выбирает команду Select Cursor в меню Cursor.
void TWndw::CmCursor()
{
  // Создать диалоговое окно Select Cursor.
  TDialog *dialog = new TDlg (this, CURSORDLG);
  // Отобразить окно.
  int result = dialog->Execute();
  // Если пользователь выходит по кнопке ОК...
  if (result == IDOK)
	 //то переключиться на выбранный курсор.
	 ChangeCursor();
}

// TWndw::ChangeCutsor()
// Эта функция находит выбранный пользователем тип
// курсора по значениям радио-кнопок выбора из
// диалогового окна SelectCursor, а затем заменяет
// курсор на выбранный.
void TWndw::ChangeCursor()
{
  int selection;
  const char *cursor;
  HINSTANCE  instance;
  HCURSOR  hCursor;
  // Найти нужную радио-кнопку.
  for (int x = 0; x < 12; ++x)
	 if (transBuf.buttons[x]) selection = x;
  // Найти тип курсора, выбранного пользователем.
  switch (selection)
  {
	 case  0 :  cursor = IDC_ARROW;break;
	 case  1 :  cursor = IDC_CROSS;break;
	 case  2 :  cursor = IDC_IBEAM;break;
	 case  3 :  cursor = IDC_ICON;break;
	 case  4 :  cursor = IDC_SIZE;break;
	 case  5 :  cursor = IDC_SIZENESW;break;
	 case  6 :  cursor = IDC_SIZENS;break;
	 case  7 :  cursor = IDC_SIZENWSE; break;
	 case  8 :  cursor = IDC_SIZEWE; break;
	 case  9 :  cursor = IDC_UPARROW; break;
	 case 10 :  cursor = IDC_WAIT; break;
	 case 11 :  cursor = "IDC_TARGET"; break;
  }
  // Если пользователь выбрал стандартный курсор ...
  if (selection == 11)
	 // Получить ссылку на этот экземпляр приложения.
	 instance = *GetApplication() ;
  else
	 // Иначе присвоить instance значение NULL, чтобы
	 // загрузить  системный курсор Windows.
	 instance = NULL;
  // Получить ключ выбранного курсора.
  hCursor = LoadCursor (instance, cursor);
  // Изменить задаваемый по умолчанию курсор
  // оконного класса на выбранный.
  SetClassWord (GCW_HCURSOR, (WORD) hCursor);
}
//**************************************
// Реализация класса TDlg.
//**************************************
// TDlg::TDlg()
// Это конструктор диалогового окна SelectCursor.
TDlg::TDlg(TWindow *parent, TResId resId): TDialog (parent, resId)
{
  // Создать управляющие элементы OWL для каждого
  // управляющего элемента диалогового окна, которое
  // будет принимать участие в передаче данных.
  new TRadioButton(this, ID_ARROWBUT, 0);
  new TRadioButton(this, ID_CROSSBUT, 0);
  new TRadioButton(this, ID_IBEAMBUT, 0);
  new TRadioButton(this, ID_ICONBUT, 0);
  new TRadioButton(this, ID_SIZEBUT, 0);
  new TRadioButton(this, ID_NESWBUT, 0);
  new TRadioButton(this, ID_NSBUT, 0);
  new TRadioButton(this, ID_NWSEBUT, 0);
  new TRadioButton(this, ID_WEBUT, 0);
  new TRadioButton(this, ID_UPBUT, 0);
  new TRadioButton(this, ID_WAITBUT, 0) ;
  new TRadioButton(this, ID_TARGETBUT, 0);
  // Установить адрес буфера обмена.
  TransferBuffer = &transBuf;
}

void TApp::InitMainWindow()
{
  TFrameWindow *wndw = new TWndw (0,"Изменение курсора");
  SetMainWindow(wndw);
}

int OwlMain(int,char *[])
{
  return TApp().Run();
}

    Файл ресурсов:

#ifndef WORKSHOP_INVOKED
#include "windows.h"
#endif

#define MENU_1          100
#define CURSORDLG       200
#define CM_CURSOR       201
#define CM_EXIT         24310
#define ID_ARROWBUT     101
#define ID_CROSSBUT     102
#define ID_IBEAMBUT     103
#define ID_ICONBUT      104
#define ID_SIZEBUT      105
#define ID_NESWBUT      106
#define ID_NSBUT        107
#define ID_NWSEBUT      108
#define ID_WEBUT        109
#define ID_UPBUT        110
#define ID_WAITBUT      111
#define ID_TARGETBUT    112

#ifdef RC_INVOKED


CURSORDLG DIALOG 6, 15, 200, 149
STYLE DS_MODALFRAME | WS_POPUP | WS_CHILD | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CLASS  "BorDlg"
CAPTION  "Выбор курсора"
FONT 8, "MS Sans Serif"
{
  CONTROL " ", -1, "BorShade", BSS_HDIP | WS_CHILD |
	 WS_VISIBLE, 6, 106, 189, 3
  CONTROL " ", IDOK, "BorBtn", BS_DEFPUSHBUTTON |
	 WS_CHILD | WS_VISIBLE | WS_TABSTOP, 50, 115, 37, 25
  CONTROL " ", IDCANCEL, "BorBtn", BS_PUSHBUTTON |
	 WS_CHILD | WS_VISIBLE | WS_TABSTOP, 106, 115, 37, 25
  CONTROL " ", -1, "BorShade", BSS_GROUP | WS_CHILD | WS_VISIBLE, 6, 9, 187, 87
  CONTROL "Arrow", ID_ARROWBUT, "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 14, 16, 48, 12
  CONTROL "Crosshair", ID_CROSSBUT, "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 14, 35, 48, 12
  CONTROL "I-Beam", ID_IBEAMBUT, "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 14, 56, 48, 12
  CONTROL "Icon", ID_ICONBUT, "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 14, 75, 48, 13
  CONTROL "Size", ID_SIZEBUT, "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 76, 16, 52, 12
  CONTROL "NE - SW",  ID_NESWBUT,  "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 75, 35, 52, 12
  CONTROL "North  - South",  ID_NSBUT,  "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 74, 56, 53, 12
  CONTROL "NW - SE",  ID_NWSEBUT,  "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 74, 75, 53, 13
  CONTROL "West - East",  ID_WEBUT,  "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 137, 16, 47, 12
  CONTROL "Up Arrow",  ID_UPBUT,  "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 137, 35, 47, 12
  CONTROL   "Wait",   ID_WAITBUT,    "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 137, 56, 47, 12
  CONTROL  "Target",  ID_TARGETBUT,  "BorRadio",
	 BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 137, 77, 47, 12
}



MENU_1 MENU
{
  POPUP "&File"
  {
	  MENUITEM "E&xit", CM_EXIT
  }
  POPUP "&Cursor"
  {
	  MENUITEM "&Change cursor...", CM_CURSOR
  }
}

IDC_TARGET CURSOR  "target.cur"

#endif
Текст этого приложения можно взять здесь.


    Замечание. Перед компиляцией программы нужно установить в окне TargetExpert значение параметра Platform в Windows 3.x (16):


Рис.1. Окно TargetExpert


    Запустите эту программу, а затем выберите команду Change Cursor из меню Cursor. Вы увидите диалоговое окно Выбор курсора, показанное на рисунке 2.


Рис.2. Выбор курсора

    Первые 11 курсоров, перечисленные в списке окна диалога, являются системными курсорами Windows. Последний курсор (Target-мишень) - это пользовательский курсор, спроектированный с помощью Resource Workshop (рисунок 3).


Рис.3. Проектирование курсора

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

    Пример начинается с включения необходимых программе заголовочных файлов. Далее программа определяет буфер обмена для окна диалога:

struct
{
  BOOL buttons [12];
} transBuf;

    Эта структура содержит 12-элементный массив булевых величин, каждая из которых представляет состояние одной из кнопок окна диалога. Класс диалогового окна содержит только конструктор:

class TDlg : public TDialog
{
  public:
	 TDlg(TWindow *parent, TResId resId);
};

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

TDlg::TDlg(TWindow *parent, TResId resId): TDialog (parent, resId)
{
  // Создать управляющие элементы OWL для каждого
  // управляющего элемента диалогового окна, которое
  // будет принимать участие в передаче данных.
  new TRadioButton(this, ID_ARROWBUT, 0);
  new TRadioButton(this, ID_CROSSBUT, 0);
  new TRadioButton(this, ID_IBEAMBUT, 0);
  new TRadioButton(this, ID_ICONBUT, 0);
  new TRadioButton(this, ID_SIZEBUT, 0);
  new TRadioButton(this, ID_NESWBUT, 0);
  new TRadioButton(this, ID_NSBUT, 0);
  new TRadioButton(this, ID_NWSEBUT, 0);
  new TRadioButton(this, ID_WEBUT, 0);
  new TRadioButton(this, ID_UPBUT, 0);
  new TRadioButton(this, ID_WAITBUT, 0) ;
  new TRadioButton(this, ID_TARGETBUT, 0);
  // Установить адрес буфера обмена.
  TransferBuffer = &transBuf;
}

    Конструктор TRadioButton получает в качестве аргументов указатель на родительское окно, идентификатор кнопки выбора и указатель на объект TGroupBox кнопок. (Существует еще четвертый аргумент - TLibId, но OWL обеспечивает его значение по умолчанию, и вам очень редко требуется изменять его.) Если нет группового окна, третий аргумент равен нулю. Конструктор TRadioButton является перегружаемым, имеющим две формы. Вторая форма позволяет создавать кнопку выбора, не связанную ни с каким окном диалога.

    В конструкторе главного окна программа вначале заполняет буфер обмена нулевыми значениями, за исключением первого элемента массива, который имеет значение TRUE, поэтому выбрана радиокнопка Arrow при первом появлении диалогового окна:

  memset (&transBuf,0,sizeof(transBuf));
  transBuf.buttons[0] = TRUE;

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

    Когда пользователь выбирает команду Change Cursor из меню Cursor, OWL вызывает CmCursor():

void TWndw::CmCursor()
{
  // Создать диалоговое окно Select Cursor.
  TDialog *dialog = new TDlg (this, CURSORDLG);
  // Отобразить окно.
  int result = dialog->Execute();
  // Если пользователь выходит по кнопке ОК...
  if (result == IDOK)
	 //то переключиться на выбранный курсор.
	 ChangeCursor();
}

    Эта функция конструирует и отображает диалоговое окно Выбор курсора. Если пользователь выходит из диалогового окна по кнопке ОК, CmCursor() вызывает частную функцию-член ChangeCursor() для изменения курсора на выбранный.

    ChangeCursor() сначала использует цикл for для итераций по массиву булевых переменных буфера обмена, отыскивая выбранную пользователем кнопку:

  for (int x = 0; x < 12; ++x)
	 if (transBuf.buttons[x]) selection = x;

    Существует одна и только одна выбранная кнопка и, следовательно, только одно значение в массиве будет равно TRUE. Когда ChangeCursor() находит значение выбранной кнопки, она использует его в операторе switch для нахождения затребованного пользователем курсора:

  // Найти тип курсора, выбранного пользователем.
  switch (selection)
  {
	 case  0 :  cursor = IDC_ARROW; break;
	 case  1 :  cursor = IDC_CROSS; break;
	 case  2 :  cursor = IDC_IBEAM; break;
	 case  3 :  cursor = IDC_ICON; break;
	 case  4 :  cursor = IDC_SIZE; break;
	 case  5 :  cursor = IDC_SIZENESW; break;
	 case  6 :  cursor = IDC_SIZENS; break;
	 case  7 :  cursor = IDC_SIZENWSE; break;
	 case  8 :  cursor = IDC_SIZEWE; break;
	 case  9 :  cursor = IDC_UPARROW; break;
	 case 10 :  cursor = IDC_WAIT; break;
	 case 11 :  cursor = "IDC_TARGET"; break;
  }

    Все константы в операторе switch (например, IDC_ARROW, IDC_WAIT, IDC_BEAM и т.д.), за исключением последней, предварительно определены Windows - по одной для каждой из 11 курсоров. Каждая из констант представляет собой длинный указатель на строку, содержащую название изображения курсора. Последний случай в списке, IDC_TARGET, - это имя пользовательского курсора, который показан на рисунке 3.

    После того, как ChangeCursor() присвоила имя курсора символьному указателю Cursor, можно двигаться дальше и установить новый курсор. Однако функция LoadCursor() в Windows API требует в качестве первого аргумента дескриптор экземпляра. Если пользователь выбрал собственный курсор, следует использовать дескриптор экземпляра для текущего приложения, в противном случае нуль, определяющий системный курсор, следует использовать как первый аргумент LoadCursor():

  // Если пользователь выбрал стандартный курсор ...
  if (selection == 11)
	 // Получить ссылку на этот экземпляр приложения.
	 instance = *GetApplication() ;
  else
	 // Иначе присвоить instance значение NULL, чтобы
	 // загрузить  системный курсор Windows.
	 instance = NULL;

    Вышеописанный оператор if берет на себя заботу об этой незначительной проблеме с дескриптором экземпляра приложения.

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

    Когда ChangeCursor() определил соответствующий дескриптор экземпляра приложения, он вызывает LoadCursor() с этим дескриптором и указателем на имя курсора, чтобы получить дескриптор курсора, который хранится в hCursor:

  // Получить ключ выбранного курсора.
  hCursor = LoadCursor (instance, cursor);

    Наконец, обращение к SetClassWord() изменяет курсор оконного класса:

  // Изменить задаваемый по умолчанию курсор
  // оконного класса на выбранный.
  SetClassWord (GCW_HCURSOR, (WORD) hCursor);

    Главное окно программы наследует SetClassWord() из TWindow. Эта функция является OWL-версией для функции в Windows API того же названия. Вы можете использовать ее для изменения значения любого слова в структуре WNDCLASS для Windows. Первый аргумент представляет собой индекс подлежащего замене слова; Windows имеет заранее предопределенные константы, которые можно использовать для этого аргумента, как показано в таблице 1.

Таблица 1. Константы индекса слов для класса WNDCLASS
Константа Описание
GCW_HCURSOR Изменяет дескриптор курсора.
GCW_HICON Изменяет дескриптор пиктограммы.
GCW_HBRBACKGROUND Изменяет дескриптор кисти фона.
GCW_STYLE Изменяет бит стиля.
GCW_CBCLSEXTRA Изменяет данные дополнительного оконного класса.
GCW_CBWNDEXTRA Изменяет данные дополнительного оконного класса.

    Почему надо использовать SetClassWord() для изменения пиктограммы, а не более простую функцию SetCursor()? Проблема состоит в том, что Windows выдвигает очень жесткие требования к сохранению установок курсора, находящихся в структуре WNDCLASS. Т.е. в большинстве случаев, как только вы установите курсор одного вида, Windows возвращает его опять к курсору оконного класса. Следовательно, чтобы создать новую установку на продолжительное время, вы должны обмануть Windows, изменив признак управления курсора в структуре WNDCLASS. Однако, если вы хотите показать курсор в виде песочных часов (IDC_WAIT) во время выполнения таких продолжительных задач, как чтение файла, вы можете использовать SetCursor(); Windows не может восстановить курсор до тех пор, пока вы не позволите это сделать.

    На следующем шаге мы рассмотрим управление линейками прокрутки.




Предыдущий шаг Содержание Следующий шаг