Отдельно надо рассмотреть работу с мышью. Обычно, когда курсор мыши находится над каким-либо окном, это окно получает сообщения от мыши, причем внешний вид курсора мыши определен при регистрации класса окна - поле .hCursor структуры WNDCLASS. Если мы хотим использовать мышь в нашем приложении, то мы должны обрабатывать некоторые сообщения, посылаемые мышью.
WM_NCHITTEST 0 y & x
При перемещения мыши через область, занятую окном, окно получает сообщения WM_NCHITTEST, которые используются для того, что бы определить месторасположение мыши - на рамке, в углах рамки, на заголовке, во внутренней области и пр. Это должно быть определено при обработке данного сообщения и возвращаемый результат характеризует положение курсора мыши. Например HTCLIENT указывает, что курсор находится над внутренней областью окна, HTTOPLEFT - над верхним левым уголком рамки окна, размеры которого могут быть изменены и пр. Положение курсора указано в координатах экрана.
WM_NCMOUSEMOVE wHitTest y & x
Если WM_NCHITTEST определяет, что курсор находится над внешней областью окна, то окно получает сообщения WM_NCMOUSEMOVE; Y и X координаты заданы относительно экрана.
WM_NCLBUTTONDOWN wHitTest y & x
WM_NCLBUTTONUP wHitTest y & x
WM_NCRBUTTONDOWN wHitTest y & x
WM_NCRBUTTONUP wHitTest y & x
WM_NCMBUTTONDOWN wHitTest y & x
WM_NCMBUTTONUP wHitTest y & x
WM_NCLBUTTONDBLCLK wHitTest y & x
WM_NCRBUTTONDBLCLK wHitTest y & x
WM_NCMBUTTONDBLCLK wHitTest y & x
Эти сообщения посылаются при нажатии/отпускании соответствующих кнопок мыши над внешней областью окна; Положение курсора указано в координатах экрана, wParam указывает зону, над которой произошло данное событие.
WM_MOUSEMOVE nKeys y & x
Когда мышь перемещается во внутренней области окна (WM_NCHITTEST возвратило HTCLIENT) то оконная процедура обрабатывает поступающие сообщения WM_MOUSEMOVE, указывающие положение мыши в координатах окна и состояние некоторых клавиш (ctrl, shift и три кнопки мыши).
WM_LBUTTONDOWN nKeys y & x
WM_LBUTTONUP nKeys y & x
WM_RBUTTONDOWN nKeys y & x
WM_RBUTTONUP nKeys y & x
WM_MBUTTONDOWN nKeys y & x
WM_MBUTTONUP nKeys y & x
Эти сообщения посылаются окну, если Вы нажали одну из кнопок мыши, когда курсор находится над окном. Параметр nKeys указывает состояние некоторых клавиш.
WM_LBUTTONDBLCLK nKeys y&x
WM_RBUTTONDBLCLK nKeys y&x
WM_MBUTTONDBLCLK nKeys y&x
Это сообщения о двух быстрых нажатиях на одну кнопку мыши. Обычно окно их не получает. Если Вы хотите использовать эти сообщения, то Вы должны при регистрации класса окна указать стиль CS_DBLCLKS.
Кроме того надо иметь в виду, что перед сообщением о двойном нажатии на кнопку Вы получите сообщение о первом (одиночном) нажатии. Поэтому надо так распределять действия между одиночными и двойными нажатиями, что бы они не противоречили одно другому. В самом удачном случае двойное нажатие выполняет дополнительную обработку по сравнению с одиночным.
Вы можете легко изменять внешний вид курсора, изменяя его хендл в структуре класса окна:
UINT SetClassWord( hWnd, GCW_HCURSOR, hNewCursor );
однако при этом изменяется курсор для всех окон, принадлежащих к этому классу. Если Вы используете этот метод, то Вам надо проверять изменение активности окна - при деактивации Вы должны восстановить прежний курсор, при активации - установить требуемый. Для того, что бы получить хендл курсора мы можем воспользоваться следующими функциями:
HCURSOR LoadCursor( hInstance, lpszCursor );
HCURSOR CreateCursor(
hInstance, xHotSpot, yHotSpot, nWidth, nHeight, lpvAND, lpvXOR
);
BOOL DestroyCursor( hCursor );
Функция LoadCursor загружает стандартный курсор или определенный Вами, CreateCursor создает новый курсор с помощью двух масок - lpvAND и lpvXOR, которые позволяют описать четырехцветный курсор - черный, белый, цвет фона, инвертированный фон. В применении этой функции есть только один “подводный камень” - ширина и высота создаваемого курсора должны совпадать с требованиями драйвера. Для этого их надо предварительно узнать с помощью функции
int GetSystemMetrics( SM_CXCURSOR );
int GetSystemMetrics( SM_CYCURSOR );
Функция DestroyCursor предназначена для уничтожения курсор, который Вы загрузили из собственных ресурсов или создали с помощью CreateCursor.
Вы можете легко показать или спрятать курсор с помощью функции
int ShowCursor( bShow );
Вы можете установить или узнать позицию курсора с помощью функции
void SetCursorPos( nX, nY );
void GetCursorPos( lpPoint );
Также можно ограничить зону перемещения курсора с помощью функции
void ClipCursor( lpRect );
Еще одна функция,
HCURSOR SetCursor( hCursor );
используется для задания хендла курсора, отображаемого в данный момент. Однако использовать ее надо осторожно, так как курсор является разделяемым ресурсом. Если Вы используете эту функцию, то Вы должны восстанавливать предыдущий курсор перед тем, как он покинет пределы внутренней области окна, или окно потеряет активность. Перед использованием этой функции надо убедиться, что хендл курсора, определенный для класса окон равен 0, что бы система не восстанавливала его прежний вид при каждом перемещении. Эту функцию удобно применять только в случае “захвата” курсора.
В некоторых случаях бывает удобно применять так называемый “захват” курсора. “Захваченый” курсор может перемещаться по всему окну, но сообщения от мыши поступают только в то окно, которое захватило курсор. При окно не получает сообщений WM_NCHITTEST, WM_NCMOUSEMOVE и пр., а получает сообщения как бы от внутренней области окна. Конечно, делать такое нужно на небольшое время, например для реализации механизма “Перенести и положить” (Drag And Drop). Только одно окно может захватывать курсор в данное время.
Для “захватывания” курсора предназначены следующие функции:
HWND SetCapture( hWnd );
void ReleaseCapture( void );
Функция SetCapture возвращает хендл предыдущего окна, использовавшего “захват” в это время, либо NULL.
Пока что мы рассматривали приложения, имеющие только одно окно - главное окно приложения. Сейчас мы должны будем разобраться между основными типами окон и их взаимодействии друг с другом.
Окно создается с помощью функции
HWND CreateWindow(
lpszClassName, lpszTitle, dwStyle,
nX, nY, nWidth, nHeight,
hwndParent, hMenu, hInstance, lpvParam
);
Эту функцию мы рассматривали раньше, поэтому мы не будем подробно останавливаться на ее аргументах. Сейчас мы обратим внимание на различные виды окон и их взаимоотношения.
Самый общий вид окна – перекрывающиеся (overlapped). Такое окно создается, если указать стиль WS_OVERLAPPED или WS_OVERLAPPEDWINDOW при создании окна. Эти окна всегда имеют рамку (border), заголовок (title-bar) и внутреннюю область. Окна этого стиля применяются в качестве главных окон приложения, при этом настоятельно рекомендуется использовать стиль WS_OVERLAPPEDWINDOW, который описывает стандартное главное окно приложения с кнопками системного меню, максимизации и минимизации.
Близкая к перекрывающимся категория окон – всплывающие (popup) окна, задается указанием стиля WS_POPUP при создании окна. Всплывающие окна отличаются от перекрывающихся тем, что могут не иметь заголовка (title bar). В остальном их применение аналогично.
Если при создании перекрывающихся или всплывающих окон определить хендл окна – родителя, то такие окна окажутся в специфических отношениях, называемых отношениями пользователя (owner) и используемого (owned) окон.
Главная идея таких отношений связана с совместным позиционированием в Z–направлении: окно - пользователь всегда находится непосредственно под используемым окном, при перемещении одного из них в Z–направлении, другое перемещается вслед за ним. Перемещение окон в X и Y направлениях не взаимосвязано, используемые окна могут находиться вне окна‑пользователя. Кроме того, если окно‑пользователь становится невидимым или минимизируется, то используемые им окна становятся невидимыми. При уничтожении окна‑пользователя все используемые окна автоматически уничтожаются.
В Windows существует одна особенность – хотя при создании используемого окна объявляется хендл “родительского” окна, но при попытке получить хендл родительского окна (например, функцией GetParent), возвращается NULL.
Еще одна большая категория окон – дочерние (child) – окна, создаются при указании стиля WS_CHILD и хендла родительского окна (parent). Дочерние окна обязательно имеют внутреннюю область, но могут не иметь рамки и заголовка; у них не может быть меню - этот параметр функции CreateWindow задает необязательный идентификатор дочернего окна.
Отношения дочерних и родительских окон напоминают отношения пользователя и используемого, но имеют некоторые отличия:
· дочерние окна могут размещаться только во внутренней области родительского окна.
· координаты дочерних окон задаютсе относительно верхнего-левого угла внутренней области родительского окна, а не экрана.
· дочерние окна, имеющие одного родителя, называются “сестринскими” (sibling) окнами. Перекрывающие друг друга сестринские окна могут осуществлять вывод в области перекрываемого окна. Что бы четко разделить области сестринских окон Вы должны указать WS_CLIPSIBLINGS при создании дочернего окна.
· если для дочернего окна запретить получение фокуса ввода (от мыши и клавиатуры), то все соощения ввода будут направлены окну-родителю.
· родительское окно может осуществлять вывод поверх дочернего окна.
Если Вы хотите избежать этого, то Вам надо указать стиль WS_CLIPCHILDREN при создании окна‑родителя.
Зная хендл дочернего окна можно узнать хендл родителя, если воспользоваться одной из двух функций:
HWND GetParent( hwndChild );
HWND GetWindowWord( hwndChild, GWW_PARENT );
Рассмотрим еще несколько стилей, которые можно декларировать при создании окна. Обычно созданное окно может получать сообщения от мыши и клавиатуры. Вы можете указать стиль WS_DISABLED для того, что бы это запретить. Во время работы Вы можете запрещать или разрешать получение сообщений от клавиатуры с помощью функции
BOOL EnableWindow( hWnd, bEnable );
Второй стиль, на который надо обратить внимание – WS_VISIBLE – говорит о том, что создается видимое окно. Если его не указать, то созданное окно будет невидимым. Позже окно можно делать видимым или невидимым с помощью функции:
BOOL ShowWindow( hWnd, SW_HIDE );
BOOL ShowWindow( hWnd, SW_SHOW );
Окна часто создают невидимыми, если при их создании (или сразу после этого) приходится менять их размер или положение. При этом все операции выполняют над невидимым окном и показывают только в его конечном состоянии. Это не только улучшает внешний вид приложения, но и ускоряет выполнение.