Работа с мышкой.Содержание:
Частенько приложению требуется знать координаты курсора мышки. Обычно, это графические программы, которые отслеживают координаты курсора во время рисования какого-нибудь рисунка. Так же отслеживать положение мышки необходимо приложениям работающим с текстом для возможности выделения блоков текста. Для того, чтобы отслеживать курсор мышки, обычно необходимо обработать три сообщения WM_LBUTTONDOWN, WM_MOUSEMOVE, и WM_LBUTTONUP. Как правило, отслеживание курсора начинается с поступления сообщения WM_LBUTTONDOWN, в параметре lParam которого записаны координаты курсора. Далее начинается сам процесс отслеживания путём обработки потока сообщений WM_MOUSEMOVE которые постит само окно при перемещении мышки. Поступление сообщения WM_LBUTTONUP сигнализирует об окончании процесса отслеживания. Так же можно использовать функцию TrackMouseEvent, чтобы заставить систему посылать другие сообщения необходимые для отслеживания курсора. Сообщение WM_MOUSEHOVER посылается системой когда мышка попадает в клиентскую область, а сообщение WM_MOUSELEAVE - когда курсор покидает клиентскую область. Соответственно, сообщения WM_NCMOUSEHOVER и WM_NCMOUSELEAVE отвечают за неклиентскую область. Рисование линий при помощи мышки В данном разделе статьи представлена часть кода оконной процедуры, которая позволяет пользователю рисовать линии в клиентской области окна путём перетаскивания мышки. Когда оконная процедура получает сообщение WM_LBUTTONDOWN, то происходит захват мышки и сохранение координат курсора, используя их как начальную точку линии. При этом используется функция ClipCursor, чтобы ограничить перемещение курсора клиентской областью в процессе рисования. В течение первого сообщения WM_MOUSEMOVE оконная процедура рисует линию от начальной точки до текущей позиции курсора. В течение последующих сообщений WM_MOUSEMOVE, оконная процедура стирает предыдущую линию путём рисования повёрх неё линии инверсного цвета. Затем снова рисуется линия от начальной точки до текущих координат курсора. Поступление сообщения WM_LBUTTONUP сигнализирует об окончании рисования. Оконная процедура освобождает захват мышки и снимает ограничение движения мышки клиентской областью. Пример: LRESULT APIENTRY MainWndProc(HWND hwndMain, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
HDC hdc; // дескриптор контекста устройства
RECT rcClient; // прямоугольник клиентской области
POINT ptClientUL; // верхний левый угол клиент.области
POINT ptClientLR; // нижний правый угол клиент.области
static POINTS ptsBegin; // начальная точка
static POINTS ptsEnd; // новая конечная точка
static POINTS ptsPrevEnd; // предыдущая конечная точка
static BOOL fPrevLine = FALSE; // флаг предыдущей линии
switch (uMsg)
{
case WM_LBUTTONDOWN:
// Захватываем мышку.
SetCapture(hwndMain);
// Получаем экранные координаты клиентской области,
// и преобразуем их в клиентские координаты.
GetClientRect(hwndMain, &rcClient);
ptClientUL.x = rcClient.left;
ptClientUL.y = rcClient.top;
// Добавляем один пиксель справа и снизу, так как координаты,
// полученные из GetClientRect не включают левого и
// нижнего пикселей.
ptClientLR.x = rcClient.right + 1;
ptClientLR.y = rcClient.bottom + 1;
ClientToScreen(hwndMain, &ptClientUL);
ClientToScreen(hwndMain, &ptClientLR);
// Копируем клиентские координаты клиентской области
// в структуру rcClient. Ограничиваем курсор мышки клиентской
// областью, передав структуру rcClient в
// функцию ClipCursor.
SetRect(&rcClient, ptClientUL.x, ptClientUL.y,
ptClientLR.x, ptClientLR.y);
ClipCursor(&rcClient);
// Преобразуем координаты курсора для структуры POINTS,
// которая определяет начальную точку рисования линии
// в течение сообщения WM_MOUSEMOVE.
ptsBegin = MAKEPOINTS(lParam);
return 0;
case WM_MOUSEMOVE:
// Чтобы рисовалась линия, то при движении мышки
// пользователь должен удерживать нажатой левую кнопку мышки.
if (wParam & MK_LBUTTON)
{
// Получаем контекст устройства (DC) для клиентской области
hdc = GetDC(hwndMain);
// Следующая функция гарантирует, что пиксели
// предыдущей линии установлены в белый цвет, а
// вновь нарисованной линии - в чёрный.
SetROP2(hdc, R2_NOTXORPEN);
// Если линия была нарисована в предыдущем WM_MOUSEMOVE,
// то рисуем поверх неё. Тем самым, установив пиксели
// линии в белый цвет, мы сотрём её.
if (fPrevLine)
{
MoveToEx(hdc, ptsBegin.x, ptsBegin.y, (LPPOINT) NULL);
LineTo(hdc, ptsPrevEnd.x, ptsPrevEnd.y);
}
// Преобразуем текущие координаты курсора в структуру
// POINTS, а затем рисуем новую линию.
ptsEnd = MAKEPOINTS(lParam);
MoveToEx(hdc, ptsBegin.x, ptsBegin.y, (LPPOINT) NULL);
LineTo(hdc, ptsEnd.x, ptsEnd.y);
// Устанавливаем флаг предыдущей линии, сохраняем конечную
// точку новой линии, а затем освобождаем DC.
fPrevLine = TRUE;
ptsPrevEnd = ptsEnd;
ReleaseDC(hwndMain, hdc);
}
break;
case WM_LBUTTONUP:
// Пользователь закончил рисовать линию. Сбрасываем флаг
// предыдущей линии, освобождаем курсор мышки и
// освобождаем захват мышки.
fPrevLine = FALSE;
ClipCursor(NULL);
ReleaseCapture();
return 0;
case WM_DESTROY:
PostQuitMessage(0);
break;
// Обрабатываем другие сообщения.
Чтобы получать сообщения о двойном щелчке (double-click messages), класс окна должен содержать стиль CS_DBLCLKS. Этот стиль устанавливается при регистрации оконного класса, как показано ниже. Пример: BOOL InitApplication(HINSTANCE hInstance)
{
WNDCLASS wc;
wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_IBEAM);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWClass";
return RegisterClass(&wc);
}
Сообщение о двойном щелчке всегда предшевствует сообщению о нажатии кнопки. В данном разделе приведён пример, который был взят из обычного текстового редактора. Он включает код, позволяющий пользователю обычным щелчком устанавливать каретку в любом месте текста, а также выделять (подсвечивать) строку текста двойным щелчком. Пример: LRESULT APIENTRY MainWndProc(HWND hwndMain, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
HDC hdc; // дескриптор контекста устройства
TEXTMETRIC tm; // данные о размере шрифта
int i, j; // счётчики цикла
int cCR = 0; // счётчик возвратов каретки
char ch; // символ из буфера ввода
static int nBegLine; // начало выделенной линии
static int nCurrentLine = 0; // текущая выделенная строка
static int nLastLine = 0; // последняя строка текста
static int nCaretPosX = 0; // x-координата каретки
static int cch = 0; // количество введённых символов
static int nCharWidth = 0; // точная ширина символа
static char szHilite[128]; // строка текста, которая будет выделена
static DWORD dwCharX; // средняя ширина символов
static DWORD dwLineHeight; // высота строки
static POINTS ptsCursor; // координаты курсора мышки
static COLORREF crPrevText; // предыдущий цвет текста
static COLORREF crPrevBk; // предыдущий цвет фона
static PTCHAR pchInputBuf; // указатель на буфер ввода
static BOOL fTextSelected = FALSE; // флаг выделения текста
size_t * pcch;
HRESULT hResult;
switch (uMsg)
{
case WM_CREATE:
// Получаем параметры текущего шрифта.
hdc = GetDC(hwndMain);
GetTextMetrics(hdc, &tm);
ReleaseDC(hwndMain, hdc);
// Сохраняем среднюю ширину и высоту символа.
dwCharX = tm.tmAveCharWidth;
dwLineHeight = tm.tmHeight;
// Выделяем буфер для хранения ввода с клавиатуры.
pchInputBuf = (LPSTR) GlobalAlloc(GPTR,
BUFSIZE * sizeof(TCHAR));
return 0;
case WM_CHAR:
switch (wParam)
{
case 0x08: // backspace
case 0x0A: // перевод строки
case 0x1B: // escape
MessageBeep( (UINT) -1);
return 0;
case 0x09: // символ табуляции (tab)
// Преобразуем символы табуляции в четыре пробела.
for (i = 0; i < 4; i++)
SendMessage(hwndMain, WM_CHAR, 0x20, 0);
return 0;
case 0x0D: // возврат каретки
// Записываем символ возврата каретки и помещаем каретку
// в начало новой строки.
pchInputBuf[cch++] = 0x0D;
nCaretPosX = 0;
nCurrentLine += 1;
break;
default: // отображаемый символ
ch = (char) wParam;
HideCaret(hwndMain);
// Получаем ширину символа и отображаем его.
hdc = GetDC(hwndMain);
GetCharWidth32(hdc, (UINT) wParam, (UINT) wParam,
&nCharWidth);
TextOut(hdc, nCaretPosX,
nCurrentLine * dwLineHeight, &ch, 1);
ReleaseDC(hwndMain, hdc);
// Сохраняем символ в буфере.
pchInputBuf[cch++] = ch;
// Вычисляем новую горизонтальную координат каретки.
// Если координата достигла максимума, то вставляем
// перевод каретки и перемещаем каретку
// в начало следующей строки.
nCaretPosX += nCharWidth;
if ((DWORD) nCaretPosX > dwMaxCharX)
{
nCaretPosX = 0;
pchInputBuf[cch++] = 0x0D;
++nCurrentLine;
}
ShowCaret(hwndMain);
break;
}
SetCaretPos(nCaretPosX, nCurrentLine * dwLineHeight);
nLastLine = max(nLastLine, nCurrentLine);
break;
// Обрабатываем другие сообщения.
case WM_LBUTTONDOWN:
// Если строка текста уже выделена, то перерисовываем
// текст, чтобы убрать выделение.
if (fTextSelected)
{
hdc = GetDC(hwndMain);
SetTextColor(hdc, crPrevText);
SetBkColor(hdc, crPrevBk);
hResult = StringCchLength(szHilite, 128/sizeof(TCHAR), pcch);
if (FAILED(hResult))
{
// TODO: обработчик ошибки
}
TextOut(hdc, 0, nCurrentLine * dwLineHeight, szHilite, *pcch);
ReleaseDC(hwndMain, hdc);
ShowCaret(hwndMain);
fTextSelected = FALSE;
}
// Сохраняем текущие координаты курсора мышки.
ptsCursor = MAKEPOINTS(lParam);
// Определяем, на какой строке находится курсор, и сохраняем
// номер строки. Следим, чтобы номера строк не были больше
// номера последней строки текста. Результат используем
// для установки y-координаты каретки.
nCurrentLine = min((int)(ptsCursor.y / dwLineHeight),
nLastLine);
// Парсим текст буфера ввода, чтобы найти первый символ
// в выделенной строке текста. Каждая строка оканчивается
// символом возврата каретки, поэтому, чтобы найти
// выделенную строку, достаточно сосчитать возвраты каретки.
cCR = 0;
nBegLine = 0;
if (nCurrentLine != 0)
{
for (i = 0; (i < cch) &&
(cCR < nCurrentLine); i++)
{
if (pchInputBuf[i] == 0x0D)
++cCR;
}
nBegLine = i;
}
// Начиная с начала выделенной строки, измеряем ширину
// каждого символа, суммируя с шириной уже измеренного
// символа. Останавливаемся,
// когда сумма больше, чем x-координата курсора.
// Сумма используется для установки x-координаты каретки.
hdc = GetDC(hwndMain);
nCaretPosX = 0;
for (i = nBegLine;
(pchInputBuf[i] != 0x0D) && (i < cch); i++)
{
ch = pchInputBuf[i];
GetCharWidth32(hdc, (int) ch, (int) ch, &nCharWidth);
if ((nCaretPosX + nCharWidth) > ptsCursor.x) break;
else nCaretPosX += nCharWidth;
}
ReleaseDC(hwndMain, hdc);
// Устанавливаем каретку в то место, куда кликнул пользователь.
SetCaretPos(nCaretPosX, nCurrentLine * dwLineHeight);
break;
case WM_LBUTTONDBLCLK:
// Копируем выделенную строку в буфер.
for (i = nBegLine, j = 0; (pchInputBuf[i] != 0x0D) &&
(i < cch); i++)
{
szHilite[j++] = pchInputBuf[i];
}
szHilite[j] = '\0';
// Скрываем каретку, инвертируем цвет фона и символов,
// а затем перерисовываем выделенную строку.
HideCaret(hwndMain);
hdc = GetDC(hwndMain);
crPrevText = SetTextColor(hdc, RGB(255, 255, 255));
crPrevBk = SetBkColor(hdc, RGB(0, 0, 0));
hResult = StringCchLength(szHilite, 128/sizeof(TCHAR), pcch);
if (FAILED(hResult))
{
// TODO: обработчик ошибки
}
TextOut(hdc, 0, nCurrentLine * dwLineHeight, szHilite, *pcch);
SetTextColor(hdc, crPrevText);
SetBkColor(hdc, crPrevBk);
ReleaseDC(hwndMain, hdc);
fTextSelected = TRUE;
break;
// Обрабатываем другие сообщения.
default:
return DefWindowProc(hwndMain, uMsg, wParam, lParam);
}
return NULL;
}
Использование колеса мышки в документах с встраиваемыми объектами В этом разделе представлен пример, демонстрирующий работу с документом Microsoft® Word, с различными встраиваемыми объектами:
Сообщение MSH_MOUSEWHEEL всегда посылается главному окну в Microsoft Word, даже если активна встраиваемая таблица экселя. Следующая таблица объясняет, как сообщение MSH_MOUSEWHEEL обрабатывается в ответ на изменение фокуса.
Следующий пример демонстрирует, как приложение может обработать два сообщения от колёсика. Пример: /************************************************
* эта часть кода работает с MSH_MOUSEWHEEL
*************************************************/
#include "zmouse.h"
//
// Самое главное, это определеить, поддерживается ли в системе
// сообщение WM_MOUSEWHEEL.
//
#ifndef WM_MOUSEWHEEL
#define WM_MOUSEWHEEL WM_MOUSELAST+1
// ID сообщения для колёсика IntelliMouse
#endif
UINT uMSH_MOUSEWHEEL = 0; // Значение, возвращённое функцией
// RegisterWindowMessage()
/**************************************************/
INT WINAPI WinMain(
HINSTANCE hInst,
HINSTANCE hPrevInst,
LPSTR lpCmdLine,
INT nCmdShow)
{
MSG msg;
BOOL bRet;
if (!InitInstance(hInst, nCmdShow))
return FALSE;
//
// Новые IntelliMouse используют зарегистрированное сообщение
// для передачи информации о вращении колеса. Зарегистрируем!
uMSH_MOUSEWHEEL =
RegisterWindowMessage(MSH_MOUSEWHEEL);
if ( !uMSH_MOUSEWHEEL )
{
MessageBox(NULL,"
RegisterWindowMessag Failed!",
"Error",MB_OK);
return msg.wParam;
}
while (( bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// обработка ошибки и возможный выход
}
else
{
if (!TranslateAccelerator(ghwndApp,
ghaccelTable,
&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
return msg.wParam;
}
/************************************************
* Следующий код показывает как работать с WM_MOUSEWHEEL
*************************************************/
LONG APIENTRY MainWndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
static int nZoom = 0;
switch (msg)
{
case WM_MOUSEWHEEL:
((short) HIWORD(wParam)< 0) ? nZoom-- : nZoom++;
//
// Как-нибудь работаем с колёсиком...
//
break;
default:
//
// uMSH_MOUSEWHEEL это сообщение, зарегистрированное
// ддл-кой mswheel в версиях Windows, которые не
// поддерживают новые сообщения в системе.
if( msg == uMSH_MOUSEWHEEL )
{
((int)wParam < 0) ? nZoom-- : nZoom++;
//
// Как-нибудь работаем с колёсиком...
//
break;
}
return DefWindowProc(hwnd,
msg,
wParam,
lParam);
}
return 0L;
}
Получаем количество строк, проскроллированных колесом мышки Следующий пример, позволяет узнать количество проскроллированных строк. Для тех операционных систем, которые изначально поддерживают колёсико мышки, такие как Microsoft Windows NT® 4.0 и выше, рекомендуется использовать SystemParametersInfo. Пример: /* SPI_GETWHEELSCROLLLINES
определена в winuser.h начиная с Windows NT 4.0. Для того, чтобы
иметь возможность узнать кол-во проскроллированных строк была
обновлена функция SystemParametersInfo.
*/
#ifndef SPI_GETWHEELSCROLLLINES
#define SPI_GETWHEELSCROLLLINES 104
#endif
#include "zmouse.h"
/*********************************************************
* ФУНКЦИЯ: GetNumScrollLines
* Описание: Системно-независимый способ получения количества
* строк, проскроллированных колесом мышки
* Параметры: нет
* Возвращает : UINT: Кол-во строк, где WHEEL_PAGESCROLL
* указывает на то, что в данный момент идёт скроллирование.
*********************************************************/
UINT GetNumScrollLines(void)
{
HWND hdlMsWheel;
UINT ucNumLines=3; // 3 по умолчанию
OSVERSIONINFO osversion;
UINT uiMsh_MsgScrollLines;
memset(&osversion, 0, sizeof(osversion));
osversion.dwOSVersionInfoSize =sizeof(osversion);
GetVersionEx(&osversion);
// В Windows 9x & Windows NT 3.51, для получения количества строк
// используется MSWheel. В Windows NT 4.0 и выше, для этой цели
// используется SystemParametersInfo.
if ((osversion.dwPlatformId ==
VER_PLATFORM_WIN32_WINDOWS) ||
( (osversion.dwPlatformId ==
VER_PLATFORM_WIN32_NT) &&
(osversion.dwMajorVersion < 4) ) )
{
hdlMsWheel = FindWindow(MSH_WHEELMODULE_CLASS,
MSH_WHEELMODULE_TITLE);
if (hdlMsWheel)
{
uiMsh_MsgScrollLines = RegisterWindowMessage
(MSH_SCROLL_LINES);
if (uiMsh_MsgScrollLines)
ucNumLines = (int)SendMessage(hdlMsWheel,
uiMsh_MsgScrollLines,
0,
0);
}
}
else if ( (osversion.dwPlatformId ==
VER_PLATFORM_WIN32_NT) &&
(osversion.dwMajorVersion >= 4) )
{
SystemParametersInfo(SPI_GETWHEELSCROLLLINES,
0,
&ucNumLines, 0);
}
return(ucNumLines);
}
|