Эта глава посвящена мыши. Несмотря на то что операционная система Windows может работать без мыши, на практике редко можно встретить пользователя Windows, который не знает, что такое мышь и не работает с ней. Даже в портативные компьютеры типа Notebook встраивается графическое устройство ввода, которое служит аналогом мыши. Это устройство называется трэкбол.
За исключением простейших случаев, приложение Windows должно позволять пользователю выполнять большинство операций без клавиатуры. Конечно, существуют такие операции, которые вряд ли можно выполнить мышью, например ввод текста. Хотя в принципе вы можете отобразить в окне клавиатуру и разрешить пользователю нажимать нарисованные на ней клавиши при помощи мыши и таким образом вводить текст. В этом чисто теоретическом случае пользователь, вероятно, сможет забыть о существовании клавиатуры и отложить ее куда-нибудь в сторону, чтобы она не мешалась под руками и не загромождала рабочий стол.
Напомним, что определить присутствие мыши можно с помощью функции GetSystemMetrics, передав ей в качестве параметра значение SM_MOUSEPRESENT. Если мышь есть, эта функция возвращает ненулевое значение. В Windows версии 3.1 нет никакого способа определить количество клавиш мыши, подключенной к компьютеру.
Если ваше приложение не может нормально работать без мыши (например, ваше приложение - это графический редактор), вы можете выдать сообщение о том, что компьютер должен быть оборудован мышью. В этом случае на самом видном месте в документации и на коробке, в которой продается дистрибутив вашей программы с документацией, следует указать, что для работы приложения необходима мышь.
Мышь может порождать много сообщений, всего их 22! Однако большинство из них вы можете благополучно проигнорировать, передав эти сообщения "всеядной" функции DefWindowProc. Сообщения, поступающие от мыши, содержат информацию о текущем расположении курсора, о его расположении в момент, когда вы нажимаете на клавиши мыши, и другую аналогичную информацию.
Куда попадают сообщения от мыши?
Существует два режима, определяющих два способа распределения сообщений от мыши.
В первом режиме, который установлен по умолчанию, сообщения от мыши направляются функции окна, расположенного под курсором мыши. Если в главном окне приложения создано дочернее окно и курсор мыши располагается над дочерним окном, сообщения мыши попадут в функцию дочернего окна, но не в функцию главного окна приложения. Это же касается и временных (pop-up) окон.
Во втором режиме окно может захватить мышь для монопольного использования. В этом случае функция этого окна будет всегда получать все сообщения мыши, независимо от расположения курсора мыши. Для того чтобы захватить мышь, приложение должно вызвать функцию SetCapture:
HWND WINAPI SetCapture(HWND hwnd);
Параметр hwnd функции указывает идентификатор окна, которое будет получать все сообщения от мыши вне зависимости от расположения курсора.
Функция SetCapture возвращает идентификатор окна, которое захватывало мышь до вызова функции или NULL, если такого окна не было.
Функция ReleaseCapture возвращает нормальный режим обработки сообщений мыши:
void WINAPI ReleaseCapture(void);
Эта функция не имеет параметров и не возвращает никакого значения.
Функция GetCapture позволяет определить идентификатор окна, захватившего мышь:
HWND WINAPI GetCapture(void);
Если ни одно окно не захватывало мышь, эта функция возвратит значение NULL.
В любом случае на получение сообщений от мыши никак не влияет факт приобретения или потери окном фокуса ввода.
Приведем полный список сообщений, поступающих от мыши.
Сообщение |
Описание |
WM_LBUTTONDBLCLK |
Двойной щелчок левой клавишей мыши во
внутренней (client) области окна |
WM_LBUTTONDOWN |
Нажата левая клавиша мыши во внутренней
области окна |
WM_LBUTTONUP |
Отпущена левая клавиша мыши во
внутренней области окна |
WM_MBUTTONDBLCLK |
Двойной щелчок средней клавишей мыши во
внутренней области окна |
WM_MBUTTONDOWN |
Нажата средняя клавиша мыши во
внутренней области окна |
WM_MBUTTOMUP |
Отпущена средняя клавиша мыши во
внутренней области окна |
WM_MOUSEMOVE |
Перемещение курсора мыши во внутренней
области окна |
WM_RBUTTONDBLCLK |
Двойной щелчок правой клавишей мыши во
внутренней области окна |
WM_RBUTTONDOWN |
Нажата правая клавиша мыши во
внутренней области окна |
WM_RBUTTONUP |
Отпущена правая клавиша мыши во
внутренней области окна |
WM_NCHITTEST |
Перемещение мыши в любом месте экрана |
WM_MOUSEACTIVE |
Нажата клавиша мыши над неактивным
окном |
WM_NCLBUTTONDBLCLK |
Двойной щелчок левой клавишей мыши во
внешней (non-client) области окна |
WM_NCLBUTTONDOWN |
Нажата левая клавиша мыши во внешней
области окна |
WM_NCLBUTTONUP |
Отпущена левая клавиша мыши во внешней
области окна |
WM_NCMBUTTONDBLCLK |
Двойной щелчок средней клавишей мыши во
внешней области окна |
WM_NCMBUTTONDOWN |
Нажата средняя клавиша мыши во внешней
области окна |
WM_NCMBUTTOMUP |
Отпущена средняя клавиша мыши во
внешней области окна |
WM_NCMOUSEMOVE |
Перемещение курсора мыши во внешней
области окна |
WM_NCRBUTTONDBLCLK |
Двойной щелчок правой клавишей мыши во
внешней области окна |
WM_NCRBUTTONDOWN |
Нажата правая клавиша мыши во внешней
области окна |
WM_NCRBUTTONUP |
Отпущена правая клавиша мыши во внешней
области окна |
Из приведенных выше 22 сообщений 21 сообщение образуется из сообщения WM_NCHITTEST. Это сообщение генерируется драйвером мыши при любых перемещениях мыши. Разумеется, драйвер не отслеживает перемещение мыши для каждого пиксела экрана. Период возникновения сообщений WM_NCHITTEST зависит от скорости перемещения мыши, параметров драйвера, аппаратуры мыши и т. п.
Сообщение WM_NCHITTEST не использует параметр wParam. В младшем слове параметра lParam передается горизонтальная позиция курсора мыши, а в старшем - вертикальная. Координаты вычисляются относительно верхнего левого угла экрана.
Приложения редко обрабатывают сообщение WM_NCHITTEST, обычно оно передается функции DefWindowProc. Получив это сообщение, функция DefWindowProc определяет положение курсора мыши относительно расположенных на экране объектов и возвращает одно из приведенных ниже значений, описанных в файле windows.h).
Значение |
Расположение курсора мыши |
HTBORDER |
На рамке окна, которое создано без
толстой рамки, предназначенной для изменения
размера окна |
HTBOTTOM |
На нижней горизонтальной линии рамки
окна |
HTBOTTOMLEFT |
В левом нижнем углу рамки |
HTBOTTOMRIGHT |
В правом нижнем углу рамки |
HTCAPTION |
На заголовке окна (title-bar) |
HTCLIENT |
Во внутренней области окна (client area) |
HTERROR |
Над поверхностью экрана или на линии,
разделяющей различные окна. Дополнительно
функция DefWindowProc выдает звуковой сигнал |
HTGROWBOX |
В области изменения размера окна (size box) |
HTHSCROLL |
На горизонтальной полосе просмотра |
HTLEFT |
На левой вертикальной линии рамки окна |
HTMAXBUTTON |
На кнопке максимизиции |
HTMENU |
В области меню |
HTMINBUTTON |
На кнопке минимизации |
HTNOWHERE |
Над поверхностью экрана или на линии,
разделяющей различные окна |
HTREDUCE |
В области минимизации |
HTRIGHT |
На правой вертикальной линии рамки окна |
HTSIZE |
В области изменения размера окна (size box).
То же самое, что и HTGROWBOX |
HTSYSMENU |
В области системного меню |
HTTOP |
На верхней горизонтальной линии рамки
окна |
HTTOPLEFT |
В верхнем левом углу рамки окна |
HTTOPRIGHT |
В правом верхнем углу рамки окна |
HTTRANSPARENT |
В окне, которое перекрыто другим окном |
HTVSCROLL |
На вертикальной полосе просмотра |
HTZOOM |
В области максимизиции |
После обработки сообщения WM_HITTEST Windows анализирует расположение курсора и генерирует одно из сообщений, описанных выше.
Если курсор находится во внутренней области окна (client area), функция DefWindowProc возвращает значение HTCLIENT. В этом случае функция окна, над которой находится курсор мыши (или функция окна, захватившая мышь), будет получать сообщения о событиях во внутренней области окна. Это все описанные выше сообщения, кроме сообщений с префиксом WM_NC и сообщения WM_MOUSEACTIVATE (сочетание букв NC в символическом имени сообщения означает Non Client).
Внешняя область окна (Non Client) соответствует пространству между внешним контуром окна и его внутренней областью. В этом пространстве располагаются такие элементы, как рамка окна, кнопки максимизиции и минимизации, системное меню и меню окна и т. п.
Если при обработке сообщения WM_HITTEST выясняется, что курсор мыши расположен во внешней области окна, функция окна получает сообщения мыши с префиксом WM_NC.
Эти сообщения также редко используются приложениями и обычно передаются функции DefWindowProc. Обработка сообщений с префиксом WM_NC заключается в перемещении окна, изменении его размеров, активизации меню и т. д., в зависимости от самого сообщения.
Ваше приложение может перехватить сообщения с префиксом WM_NC, но, если эти сообщения не будут переданы функции DefWindowProc, Windows не сможет выполнять соответствующие им действия.
Эти сообщения генерируются в том случае, если при обработке сообщения WM_HITTEST функция DefWindowProc вернула значение HTCLIENT.
Для всех сообщений из этой группы параметр lParam содержит координаты курсора мыши, а параметр wParam - значение, с помощью которого можно определить, какие клавиши на мыши и клавиатуре были нажаты в тот момент, когда произошло событие, связанное с сообщением.
Младшее слово параметра lParam содержит горизонтальные координаты курсора мыши, старшее - вертикальные.
Параметр wParam может состоять из отдельных битовых флагов, перечисленных ниже.
Значение |
Описание |
MK_CONTROL |
На клавиатуре была нажата клавиша
<Control> |
MK_LBUTTON |
Была нажата левая клавиша мыши |
MK_MBUTTON |
Была нажата средняя клавиша мыши |
MK_RBUTTON |
Была нажата правая клавиша мыши |
MK_SHIFT |
На клавиатуре была нажата клавиша
<Shift> |
Анализируя параметр wParam, приложение может определить, были ли в момент события нажаты какие-либо клавиши мыши или клавиши <Control> и <Shift>, расположенные на клавиатуре.
Следует учесть, что вы можете нажать клавишу мыши, когда курсор находится над одним окном, затем переместить курсор в другое окно и там отпустить клавишу мыши. В этом случае одно из сообщений о том, что была нажата клавиша мыши (WM_LBUTTONDOWN, WM_RBUTTONDOWN или WM_MBUTTONDOWN), попадет в функцию первого окна, а сообщение о том, что клавиша мыши была отпущена (WM_LBUTTONUP, WM_RBUTTONUP или WM_MBUTTONUP), - во второе.
Когда мы работали с клавиатурными сообщениями, вслед за сообщением о том, что клавиша была нажата, всегда следовало сообщение о том, что клавиша была отпущена. При обработке сообщений мыши ваша функция окна может получить сообщение о том, что клавиша мыши была отпущена без предварительного сообщения о том, что она была нажата. Аналогично, вы можете никогда не дождаться сообщения об отпускании клавиши мыши после прихода сообщения о том, что клавиша мыши нажата: это сообщение может уйти в другое окно.
Следует сделать особое замечание относительно сообщений о двойном щелчке мыши. Это сообщения WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK и WM_RBUTTONDBLCLK.
Двойным щелчком (double click) называется пара одиночных щелчков, между которыми прошло достаточно мало времени. Изменить значение интервала, в течение которого должны поступить два одиночных щелчка, чтобы система распознала их как один двойной щелчок, проще всего при помощи стандартного приложения Windows с именем Control Panel.
Еще одно условие распознавания двойного щелчка менее очевидно и заключается в том, что за интервал между двумя одиночными щелчками курсор мыши не должен переместиться на слишком большое расстояние. С помощью функции GetSystemMetrics в Windows версии 3.1 вы можете определить размеры прямоугольника, внутри которого должны быть сделаны два щелчка мышью, для того чтобы они могли распознаваться как один двойной щелчок. Для этого ей надо передать в качестве параметра значения SM_CXDOUBLECLK (ширина прямоугольника) и SM_CYDOUBLECLK (высота прямоугольника).
Кроме всего этого, для того чтобы окно могло получать сообщения о двойном щелчке мышью, при регистрации класса окна необходимо определить стиль класса окна CS_DBLCLKS.
Если выполнить двойной щелчок левой клавишей мыши в окне, для класса которого не определен стиль CS_DBLCLKS, функция окна последовательно получит следующие сообщения:
WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDOWN WM_LBUTTONUP
Если же сделать то же самое в окне, способном принимать сообщения о двойном щелчке, функция окна в ответ на двойной щелчок получит следующую последовательность сообщений:
WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK WM_LBUTTONUP
Как нетрудно заметить, перед сообщением WM_LBUTTONDBLCLK функция окна получит сообщение WM_LBUTTONDOWN. Дело в том, что после первого щелчка Windows еще не знает, будет ли следом обычный или двойной щелчок, - все зависит от интервала времени и перемещения курсора.
Обычно правильно спроектированное приложение по двойному щелчку выполняет ту же самую операцию, которую оно бы выполнило по второму щелчку из серии.
Например, по одиночному щелчку мыши приложение может выбрать команду из меню. Для выбранной команды одиночный щелчок может запустить ее на выполнение. Тогда двойной щелчок должен сразу запускать команду на выполнение. В этом случае одиночный щелчок выберет команду, а двойной - запустит ее. Если вы соберетесь сделать двойной щелчок, а получится два одиночных, ничего особенного не произойдет - приложение будет вести себя так, как вы и ожидаете, а именно, запустится выбранная вами команда.
Сообщение WM_MOUSEMOVE извещает приложение о перемещении курсора мыши. С помощью этого сообщения приложение может, например, рисовать в окне линии вслед за перемещением курсора.
Последнее сообщение из группы сообщений для внутренней области окна имеет имя WM_MOUSEACTIVATE. Оно посылается функции неактивного окна, когда вы помещаете в это окно курсор мыши и делаете щелчок левой или правой клавишей. Если передать это сообщение функции DefWindowProc, в ответ на него Windows сделает указанное окно активным.
Сообщение WM_MOUSEACTIVATE передает параметры wParam и lParam.
Параметр wParam содержит идентификатор окна, которое будет активным. Если активным становится окно, имеющее дочерние окна, передается идентификатор самого старшего, родительского окна.
Младшее слово параметра lParam содержит результат обработки сообщения WM_NCHITTEST функцией DefWindowProc. Мы описали возможные значения, когда рассказывали о сообщении WM_NCHITTEST.
Старшее слово параметра lParam содержит код сообщения мыши, соответствующий способу, которым данное окно было выбрано. Это может быть код сообщений типа WM_LBUTTONDOWN, WM_RBUTTONDOWN и т. п.
Для сообщения WM_MOUSEACTIVATE определен код возврата:
Код возврата |
Описание |
MA_ACTIVATE |
Сделать окно активным |
MA_ACTIVATEANDEAT |
Не делать окно активным |
MA_NOACTIVATE |
Сделать окно активным и удалить события,
связанные с мышью |
MA_NOACTIVATEANDEAT |
Не делать окно активным и удалить
события, связанные с мышью |
Приложение MOUSEXY демонстрирует обработку сообщений мыши, имеющих отношение к внутренней области окна.
Задачей приложения является отображение координат курсора мыши в тот момент, когда вы в окне приложения нажимаете левую клавишу курсора. Если нажать правую клавишу мыши, содержимое внутренней области окна должно быть стерто. Обработка двойного щелчка левой клавишей мыши должна сводиться к выводу сообщения (рис. 6.1).
Рис. 6.1. Главное окно приложения MOUSEXY
Функция WinMain приложения MOUSEXY находится в файле mousexy.cpp (листинг 6.1).
Листинг 6.1. Файл mousexy\mousexy.cpp
// ---------------------------------------- // Обработка сообщений от мыши // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "MOUSEXYAppClass"; char const szWindowTitle[] = "MOUSEXY Application"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(!InitApp(hInstance)) return FALSE; hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL); if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); // Задаем стиль класса окна, позволяющий // получать сообщения о двойных // щелчках мыши wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc); return (aWndClass != 0); }
При регистрации класса окна определяется стиль класса окна, разрешающий получение функцией окна сообщений о двойных щелчках мышью. Дополнительно указывается, что содержимое окна должно перерисовываться при изменении вертикального или горизонтального размера окна:
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
Функция окна (листинг 6.2) обрабатывает сообщения мыши WM_LBUTTONDOWN, WM_RBUTTONDOWN и WM_LBUTTONDBLCLK.
Листинг 6.2. Файл mousexy\wndproc.cpp
// ===================================== // Функция WndProc // ===================================== #define STRICT #include <windows.h> LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; switch (msg) { // Нажали левую клавишу мыши case WM_LBUTTONDOWN: { WORD xPos, yPos, nSize; BYTE szBuf[80]; // Сохраняем координаты курсора мыши xPos = LOWORD(lParam); yPos = HIWORD(lParam); hdc = GetDC(hwnd); // Подготавливаем текстовую строку, содержащую // координаты курсора мыши nSize = wsprintf(szBuf, "(%d, %d)", xPos, yPos); // Выводим координаты курсора мыши // в точке, соответствующей положению // курсора мыши TextOut(hdc, xPos, yPos, szBuf, nSize); ReleaseDC(hwnd, hdc); return 0; } // Нажали правую клавишу мыши case WM_RBUTTONDOWN: { // Перерисовываем все окно, стирая фон InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); return 0; } // Двойной щелчок левой клавишей мыши case WM_LBUTTONDBLCLK: { // Выдаем сообщение MessageBox(hwnd, "Двойной щелчок!", "MouseXY", MB_OK | MB_ICONINFORMATION); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }
В ответ на сообщение WM_LBUTTONDOWN функция окна сохраняет координаты курсора в переменных xPos и yPos, затем она подготавливает текстовую строку, содержащую координаты курсора и выводит ее в позицию курсора мыши:
nSize = wsprintf(szBuf, "(%d, %d)", xPos, yPos); TextOut(hdc, xPos, yPos, szBuf, nSize);
По сообщению WM_RBUTTONDOWN функция окна перерисовывает окно:
InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd);
На сообщение WM_LBUTTONDBLCLK функция окна реагирует выдачей диалоговой панели с сообщением.
Файл определения модуля приложения MOUSEXY приведен в листинге 6.3.
Листинг 6.3. Файл mousexy\mousexy.def
; ============================= ; Файл определения модуля ; ============================= NAME MOUSEXY DESCRIPTION 'Приложение MOUSEXY, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение MOUSENC демонстрирует обработку сообщений WM_NCHITTEST и WM_MOUSEMOVE.
Основной файл приложения приведен в листинге 6.4.
Листинг 6.4. Файл mousenc\mousenc.cpp
// ---------------------------------------- // Обработка сообщений от мыши // для внешней (non-client) области окна // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "MOUSENCAppClass"; char const szWindowTitle[] = "MOUSENC Application"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(!InitApp(hInstance)) return FALSE; hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL); if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); // Задаем стиль класса окна, позволяющий // получать сообщения о двойных // щелчках мыши wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc); return (aWndClass != 0); }
При регистрации класса окна указывается, что окно должно быть способно принимать сообщения о двойном щелчке мышью.
Функция главного окна определена в файле wndproc.cpp (листинг 6.5).
Листинг 6.5. Файл mousenc\wndproc.cpp
// ===================================== // Функция WndProc // ===================================== #define STRICT #include <windows.h> LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; WORD xPosScr, yPosScr, nSizeScr; WORD xPos, yPos, nSize; BYTE szBuf[80]; static TEXTMETRIC tm; static int cxChar, cyChar; switch (msg) { case WM_CREATE: { // Получаем контекст отображения, // необходимый для определения метрик шрифта hdc = GetDC(hwnd); // Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); // Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm); // Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth; // Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading; // Освобождаем контекст ReleaseDC(hwnd, hdc); return 0; } case WM_NCHITTEST: { // Если убрать знак комментария со следующей // строки, окно можно будет передвигать не только // при помощи заголовка окна, но и просто // установив курсор мыши в любую область окна // return HTCAPTION; // Сохраняем координаты курсора мыши xPosScr = LOWORD(lParam); yPosScr = HIWORD(lParam); hdc = GetDC(hwnd); // Подготавливаем текстовую строку, содержащую // координаты курсора мыши nSize = wsprintf(szBuf, "(%-3d, %-3d)", xPosScr, yPosScr); // Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); // Выводим экранные координаты курсора мыши TextOut(hdc, cxChar, 0, szBuf, nSize); ReleaseDC(hwnd, hdc); break; } case WM_MOUSEMOVE: { // Сохраняем координаты курсора мыши xPos = LOWORD(lParam); yPos = HIWORD(lParam); hdc = GetDC(hwnd); // Подготавливаем текстовую строку, содержащую // координаты курсора мыши nSize = wsprintf(szBuf, "(%-3d, %-3d)", xPos, yPos); // Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); // Выводим оконные координаты курсора мыши TextOut(hdc, cxChar, cyChar, szBuf, nSize); ReleaseDC(hwnd, hdc); break; } // Двойной щелчок левой клавишей мыши // завершает работу приложения case WM_LBUTTONDBLCLK: case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }
При создании окна функция окна получает сообщение WM_CREATE. Обработчик этого сообщения выбирает системный шрифт с фиксированной шириной букв и определяет метрики выбранного шрифта. Метрики шрифта сохраняются для дальнейшего использования.
В самом начале обработчика сообщения WM_NCHITTEST имеется строка, закрытая символом комментария:
// return HTCAPTION;
Если убрать комментарий, обработчик примет следующий вид:
case WM_NCHITTEST: { return HTCAPTION; }
В этом случае в каком бы месте окна вы ни щелкнули мышью, Windows будет думать, что вы щелкнули в области заголовка окна. В этом случае Windows вызовет процедуру перемещения окна, которая обычно вызывается, когда вы перемещаете окно за его заголовок.
Фактически здесь используется объектная ориентированность Windows, позволяющая вам подменить метод, с помощью которого Windows определяет расположение курсора мыши относительно элементов окна.
Как это можно использовать на практике?
Вы, например, можете создать окно, не имеющее заголовка, но которое тем не менее можно перемещать при помощи мыши. Вспомните внешний вид, который можно придать стандартному приложению Windows с именем Clock (рис. 6.2).
Рис. 6.2. Приложение Clock
Несмотря на то что главное окно приложения Clock в данном случае не имеет заголовка, его все же можно перемещать по экрану. Аналогичного эффекта можете добиться и вы, если соответствующим образом обработаете сообщение WM_NCHITTEST.
Если же оставить обработчик в таком виде, как он представлен в нашем примере, после прихода сообщения WM_NCHITTEST в левом верхнем углу окна будут отображены текущие экранные координаты курсора (если курсор находится внутри окна).
Обработчик сообщения WM_MOUSEMOVE тоже отображает текущие координаты курсора мыши (строкой ниже), но только в оконных, а не экранных координатах (рис. 6.3).
Рис. 6.3. Главное окно приложения MOUSENC
Обратите внимание, что работа обработчика сообщения WM_NCHITTEST завершается передачей управления функции DefWindowProc. Если этого не сделать, Windows не сможет правильно определить расположение курсора и, следовательно, не сможет выполнить такие операции, как перемещение окна и изменение размера окна.
Обработчик сообщения WM_LBUTTONDBLCLK завершает работу приложения, вызывая функцию PostQuitMessage.
Файл определения модуля для приложения MOUSENC приведен в листинге 6.6.
Листинг 6.6. Файл mousenc\mousenc.def
; ============================= ; Файл определения модуля ; ============================= NAME MOUSENC DESCRIPTION 'Приложение MOUSENC, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
В отличие от текстового курсора курсор мыши можно перемещать не только с помощью специальных функций, но и вручную (было бы странно, если курсор мыши нельзя было бы перемещать вручную).
Вы также можете изменять форму курсора мыши. Можно определить форму курсора (или, иными словами, определить курсор) при регистрации класса окна или изменить ее в любое время в процессе работы приложения.
При регистрации класса окна мы задавали форму курсора следующим способом:
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
Второй параметр функции LoadCursor в нашем случае выбирает одну из предопределенных форм курсора, а именно стандартный курсор в виде стрелки. Возможные значения для выбора предопределенных форм курсора представлены ниже.
Символическое имя |
Описание |
IDC_ARROW |
Стандартный курсор в виде стрелки |
IDC_CROSS |
Курсор в виде перекрещивающихся линий |
IDC_IBEAM |
Текстовый курсор в виде буквы "I" |
IDC_ICON |
Пустая пиктограмма |
IDC_SIZE |
Курсор в виде четырех стрелок,
указывающих в разных направлениях |
IDC_SIZENESW |
Двойная стрелка, указывающая в
северо-восточном и юго-западном направлении |
IDC_SIZENS |
Двойная стрелка, указывающая в севером и
южном направлении |
IDC_SIZENWSE |
Двойная стрелка, указывающая в
северо-западном и юго-восточном направлении |
IDC_SIZEWE |
Двойная стрелка, указывающая в
восточном и западном направлении |
IDC_UPARROW |
Вертикальная стрелка |
IDC_WAIT |
Курсор в виде песочных часов |
Вы можете попробовать изменить курсор в любом из уже рассмотренных нами ранее приложений, создающих окна.
Забегая вперед, скажем, что вы можете создать курсор произвольной формы с помощью такого средства, как Borland Resource Workshop или Microsoft SDK. В этом случае вы должны нарисовать курсор в виде небольшой картинки, состоящей из отдельных точек. Эта картинка создается специальным графическим редактором и сохраняется в файле с расширением .cur. Затем файл подключается к ресурсам приложения, которые записываются в исполняемый exe-файл. Каждый ресурс в файле имеет свой идентификатор. Вы можете изменить форму курсора, если укажете идентификатор ресурса, соответствующего новому изображению курсора.
Для того чтобы можно было изменить форму курсора, прежде всего надо загрузить новый курсор при помощи функции LoadCursor, которая входит в программный интерфейс Windows:
HCURSOR WINAPI LoadCursor(HINSTANCE hinst, LPCSTR lpszCursor);
Для загрузки нового курсора из ресурсов приложения в качестве первого параметра (hinst) необходимо указать идентификатор приложения, полученный через параметры функции WinMain. Второй параметр (lpszCursor) при этом должен указывать на идентификатор ресурса. Подробнее об использовании ресурсов мы поговорим позже в отдельной главе.
Если же в качестве первого параметра указать значение NULL, для загрузки курсора можно использовать перечисленные выше символические имена с префиксом IDC_. Именно так мы и поступаем при регистрации класса окна:
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
Функция LoadCursor возвращает идентификатор загруженного курсора или NULL при ошибке.
Для динамического изменения формы курсора (например, во время обработки сообщения) следует использовать функцию SetCursor:
HCURSOR WINAPI SetCursor(HCURSOR hcur);
Параметр hcur функции SetCursor должен указывать идентификатор нового курсора, подготовленный при помощи функции LoadCursor. Если указать параметр как NULL, изображение курсора исчезнет с экрана.
Для того чтобы выключить изображение курсора мыши или вновь включить его, используют функцию ShowCursor:
int WINAPI ShowCursor(BOOL fShow);
Функция управляет содержимым счетчика, который используется для определения момента включения или выключения изображения курсора мыши. Первоначально содержимое счетчика равно нулю. Этот счетчик увеличивается, когда необходимо включить курсор, и уменьшается при выключении курсора. Если счетчик больше или равен нулю, курсор мыши находится во включенном (видимом) состоянии.
Для включения курсора в качестве параметра fShow функции следует передать значение TRUE, для выключения - FALSE.
Возвращаемое функцией ShowCursor значение равно новому содержимому счетчика.
Наблюдая за работой стандартных приложений Windows, вы можете заметить, что часто на время выполнения длительных операций курсор принимает форму песочных часов. Как правило, все такие операции выполняются во время обработки какого-либо одного сообщения. Перед началом выполнения операции вы можете вызвать функцию LoadCursor с параметром IDC_WAIT, а затем вернуть прежнюю форму, вызвав эту же функцию еще раз. Дополнительно на время выполнения операции обработчик сообщения должен захватить мышь, вызвав функцию SetCapture. В этом случае вы не сможете с помощью мыши переключиться на другое приложение и прервать таким образом ход длительной операции. После выполнения операции следует освободить мышь, вызвав функцию ReleaseCapture.
Ваше приложение может установить курсор мыши в новое положение или определить текущие координаты курсора.
Для установки курсора мыши в новое положение следует вызвать функцию SetCursorPos:
void WINAPI SetCursorPos(int x, int y);
Первый параметр функции определяет горизонтальную координату курсора, второй вертикальную. Начало системы координат расположено в верхнем левом углу экрана.
Для определения текущих экранных координат курсора мыши необходимо использовать функцию GetCursorPos:
void WINAPI GetCursorPos(POINT FAR* lppt);
Эта функция записывает в поля x и y структуры типа POINT соответственно горизонтальную и вертикальную координату курсора мыши.
Существует редко используемая возможность ограничения перемещения курсора прямоугольной областью. Для ограничения свободы перемещения курсора следует использовать функцию ClipCursor:
void WINAPI ClipCursor(const RECT FAR* lprc);
В качестве параметра lprc функции передается указатель на структуру типа RECT, в которой указываются координаты области ограничения. Как только необходимость в ограничении пропадет, следует освободить движение мыши, вызвав функцию ClipCursor с параметром NULL.
Программный интерфейс Windows версии 3.1 содержит функцию GetClipCursor, с помощью которой можно определить расположение и размер области, ограничивающей движение курсора:
void WINAPI GetClipCursor(RECT FAR* lprc);
В качестве параметра lprc функции передается указатель на структуру типа RECT, в которую будут записаны координаты области ограничения.
Несмотря на то что практически каждый компьютер, на котором используется операционная система Windows, оснащен мышью, ваше приложение должно, по возможности, работать и без мыши. Для этого ему необходимо вначале включить курсор мыши (который по умолчанию находится в невидимом состоянии, если компьютер не оборудован мышью), а затем передвигать его самостоятельно. Для передвижения курсора мыши можно использовать, например, клавиши перемещения текстового курсора.
Мы подготовили приложение MOUSEKEY, которое демонстрирует способ использования клавиатурных сообщений для перемещения курсора мыши.
Главный файл приложения приведен в листинге 6.7.
Листинг 6.7. Файл mousekey\mousekey.cpp
// ---------------------------------------- // Управление курсором мыши при // помощи клавиатуры // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "MOUSEKEYAppClass"; char const szWindowTitle[] = "MOUSEKEY Application"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(!InitApp(hInstance)) return FALSE; hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL); if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc); return (aWndClass != 0); }
Функция WinMain создает одно главное окно и не имеет никаких особенностей.
Функция главного окна приложения приведена в листинге 6.8.
Листинг 6.8. Файл mousekey\wndproc.cpp
#define STRICT #include <windows.h> // Прототипы функций int max(int value1, int value2); int min(int value1, int value2); // -------------------------------------------- // Функция max // -------------------------------------------- int max(int value1, int value2) { return((value1 > value2) ? value1 : value2); } // -------------------------------------------- // Функция min // -------------------------------------------- int min(int value1, int value2) { return((value1 < value2) ? value1 : value2); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static POINT pt; RECT rc; switch (msg) { // Нажали клавишу case WM_KEYDOWN: { // Получаем текущие экранные // координаты курсора мыши GetCursorPos(&pt); // Преобразуем экранные координаты // в оконные координаты ScreenToClient(hwnd, &pt); // Для клавиш позиционирования текстового // курсора изменяем соответствующим образом // координаты курсора мыши switch(wParam) { case VK_DOWN: // вниз { pt.y += 20; break; } case VK_UP: // вверх { pt.y -= 20; break; } case VK_LEFT: // влево { pt.x -= 20; break; } case VK_RIGHT: // вправо { pt.x += 20; break; } // Для всех остальных клавиш // ничего не делаем default: { return 0; } } // Получаем координаты внутренней // области окна GetClientRect(hwnd, &rc); // Вычисляем новые координаты курсора мыши // таким образом, чтобы курсор не выходил // за пределы окна pt.x = max(min(pt.x, rc.right), rc.left); pt.y = max(min(pt.y, rc.bottom), rc.top); // Преобразуем оконные координаты в экранные ClientToScreen(hwnd, &pt); // Устанавливаем курсор мыши // в новую позицию SetCursorPos(pt.x, pt.y); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }
Обработчик сообщения WM_KEYDOWN вызывает функцию GetCursorPos, которая записывает текущие экранные координаты курсора мыши в структуру pt.
Напомним, что начало экранных координат находится в левом верхнем углу экрана видеомонитора. Начало оконных координат находится в левом верхнем углу внутренней области окна. Поэтому нам необходимо преобразовать экранные координаты в оконные, для чего вызывается функция ScreenToClient:
void WINAPI ScreenToClient(HWND hwnd, POINT FAR* lppt);
В качестве первого параметра функции (hwnd) указывается идентификатор окна, для которого выполняется преобразование. Второй параметр (lppt) является указателем на структуру типа POINT. В эту структуру записываются преобразованные координаты. В нашем случае это оконные координаты курсора мыши:
ScreenToClient(hwnd, &pt);
Далее функция окна анализирует параметр wParam, который содержит код нажатой виртуальной клавиши. В зависимости от того, какая из клавиш перемещения курсора была нажата, происходит изменение отдельных компонент структуры pt, содержащей оконные координаты курсора мыши.
Для того чтобы установить курсор мыши в новое положение, функция окна должна преобразовать новые оконные координаты курсора мыши в экранные координаты, так как функция установки курсора мыши использует экранные координаты. Кроме того, необходимо убедиться, что при перемещении курсор мыши не выйдет за пределы главного окна приложения.
Для получения координат главного окна приложения вызывается функция GetClientRect:
GetClientRect(hwnd, &rc);
Она записывает сведения о расположении окна в структуру rc.
Далее новые координаты курсора сравниваются с границами окна приложения и при необходимости корректируются.
Для выполнения преобразования оконных координат курсора в экранные используется функция ClientToScreen:
void WINAPI ClientToScreen(HWND hwnd, POINT FAR* lppt);
Назначение параметров этой функции аналогично назначению параметров функции ScreenToClient.
После выполнения преобразования координат функция окна устанавливает курсор мыши в новое положение, для чего вызывает функцию SetCursorPos:
SetCursorPos(pt.x, pt.y);
Файл определения модуля для приложения MOUSEKEY приведен в листинге 6.9.
Листинг 6.9. Файл mousekey\mousekey.def
; ============================= ; Файл определения модуля ; ============================= NAME MOUSEKEY DESCRIPTION 'Приложение MOUSEKEY, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple