Сообщений 15 Оценка 180 [+2/-0] Оценить |
При создании пользовательского интерфейса иногда требуется определить момент, когда курсор мыши выходит за пределы окна. Для решения этой задачи существуют различные методы. Можно воспользоваться готовой функцией TrackMouseEvent, которая появилась в Win32 API, начиная с Windows 98/NT4, или же эквивалентной ей функцией _TrackMouseEvent из библиотеки comctl32.dll. А можно добиться требуемого поведения "вручную", использую стандартные средства.
Демонстрационный проект MouseOut
Сначала рассмотрим функцию _TrackMouseEvent. Она появилась вместе с программой Internet Explorer 3.0. На новых платформах эта функция просто вызывает TrackMouseEvent, а на более старых эмулирует её поведение. Вот почему использовать её предпочтительнее, чем аналог "без подчёрка".
Функция _TrackMouseEvent позволяет отслеживать события, связанные с мышью, которые не входят в стандартный набор. В случае выхода за границы клиентской области окна она посылает ему сообщение WM_MOUSELEAVE, а в случае "зависания" курсора на одном месте - сообщение WM_MOUSEHOVER. Существуют также сообщения WM_NCMOUSELEAVE и WM_NCMOUSEHOVER, относящиеся к окну целиком, а не только к клиентской области.
Нужно подчеркнуть два важных момента, связанных с функцией _TrackMouseEvent. Во-первых, она посылает не все перечисленные сообщения, а только те, которые вы ей "закажете". Во-вторых, она обладает "одноразовым действием". Это означает, что после отправки ровно одного сообщения она прекращает следить за окном. Если вы хотите получать уведомления и дальше, вам придётся вызывать эту функцию снова. Где это лучше делать - зависит от ситуации. В своём примере я вызываю её из обработчика WM_MOUSEMOVE. Возможно, вы захотите вызывать её в другом месте.
Демонстрационная программа MouseOut отслеживает положение курсора мыши с помощью _TrackMouseEvent. Когда курсор находится над клиентской областью окна, она принимает красный цвет, в противном случае окрашивается в синий. Функция окна, использованная в примере, выглядит так (обратите внимание на обработчики WM_MOUSEMOVE и WM_MOUSELEAVE).
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; static BOOL bMouseInside; switch (message) { case WM_CREATE: { RECT rt; POINT pt = { 0, 0 }; GetClientRect(hWnd, &rt); ClientToScreen(hWnd, &pt); OffsetRect(&rt, pt.x, pt.y); GetCursorPos(&pt); bMouseInside = PtInRect(&rt, pt); } break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_MOUSEMOVE: { TRACKMOUSEEVENT tme; tme.cbSize = sizeof(tme); tme.hwndTrack = hWnd; tme.dwFlags = TME_LEAVE; _TrackMouseEvent(&tme); if(!bMouseInside) { bMouseInside = TRUE; InvalidateRect(hWnd, NULL, FALSE); UpdateWindow(hWnd); } } break; case WM_MOUSELEAVE: bMouseInside = FALSE; InvalidateRect(hWnd, NULL, FALSE); UpdateWindow(hWnd); break; case WM_PAINT: { hdc = BeginPaint(hWnd, &ps); RECT rt; GetClientRect(hWnd, &rt); HBRUSH hBr = CreateSolidBrush(bMouseInside ? RGB(255,0,0) : RGB(0,0,255)); HBRUSH hOld = (HBRUSH)SelectObject(hdc, hBr); Rectangle(hdc, rt.left, rt.top, rt.right, rt.bottom); SelectObject(hdc, hOld); DeleteObject(hBr); EndPaint(hWnd, &ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } |
Демонстрационный проект MouseExEvent
В данном случае мы будем делать всё сами. Алгоритм работы этого способа следующий: как только курсор входит в область нашего окна, окно начинает получать сообщение WM_MOUSEMOVE, обрабатывая которое мы создаём таймер с небольшим интервалом и выставляем флаг, указывающий, что курсор находится в нашем окне (в данном примере роль флага одновременно выполняет идентификатор созданного таймера m_uTimer: если он не равен нулю, значит курсор находится в нашем окне). После этого мы посылаем нашему окну сообщение UWM_MOUSE_ENTER, которое определено как WM_USER+0.
BOOL CMouseEvent::Notify(CWnd *pWnd) { if( m_uTimer != 0 ) return TRUE; m_uTimer = ::SetTimer( pWnd->m_hWnd, IDS_TIMER_MOUSE_EVENT, 10, NULL ); if( m_uTimer ) { m_hWnd = pWnd->m_hWnd; ::SendMessage( pWnd->m_hWnd, UWM_MOUSE_ENTER, 0, 0 ); } return m_uTimer != 0 ? TRUE : FALSE ; } |
Обратите внимание, что если таймер уже запущен, то функция ничего не делает. HWND окна, для которого создан таймер, сохраняется в переменной. Это сделано для того, чтобы корректно уничтожить таймер в деструкторе объекта. Это может потребоваться, если мы закрываем окно нажатием на ALT+F4 или это делает приложение в тот момент, когда курсор находится в окне.
CMouseEvent::~CMouseEvent() { if( m_uTimer && m_hWnd ) { ::KillTimer( m_hWnd, m_uTimer ); m_uTimer = 0; m_hWnd = NULL; } } |
Далее с заданным интервалом наше окно получает сообщения WM_TIMER, которые мы обрабатываем следующим образом: получаем координаты курсора, а затем получаем по ним указатель на CWnd окна, которое находится под курсором, с помощью CWnd::WindowFromPoint(CPoint pt). Сравнивая полученное значение с указателем на наше окно, мы можем сказать, оставил курсор окно, или нет. Если нет, ничего не делаем, иначе уничтожаем таймер и посылаем нашему окну сообщение UWM_MOUSE_LEAVE, которое определено как WM_USER+1.
BOOL CMouseEvent::Verify(CWnd *pWnd ) { CPoint pt; ::GetCursorPos( &pt ); if( pWnd != CWnd::WindowFromPoint( pt ) ) { ::KillTimer( pWnd->m_hWnd, m_uTimer ); m_uTimer = 0; m_hWnd = NULL; ::SendMessage( pWnd->m_hWnd, UWM_MOUSE_LEAVE, 0, 0 ); } return TRUE; } |
Рассмотренный способ сложнее в реализации, чем при использовании _TrackMouseEvent. Зато он будет работать на всех, в том числе на очень старых платформах. Кроме того, он предоставляет программисту больше гибкости. Какой из способов следует предпочесть, зависит от конкретной ситуации.
Сообщений 15 Оценка 180 [+2/-0] Оценить |