从鼠标中获得输入(3)

来源:互联网 发布:3d结构设计软件 编辑:程序博客网 时间:2024/06/05 20:44
关于TicTac窗口的更多说明

注册完一个WNDCLASS后,TicTac通过调用CWnd::CreateEx创建了自己的主窗口:

CreateEx(0,strWndClass,_T("Tic-Tac-Toe"),WS_OVERLAPPED|WS_SYSMENU|WS_CAPTION|WS_MINIMIZEBOX,
       CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL);


第一个参数指定扩展的窗口样式,它可以是0个或多个WS_EX位标志的结合.TicTac程序不需要扩展的窗口样式,所以这个参数的值为0.第二个参数是刚才AfxRegisterWndClass返回的WNDCLASS名称,第三个是窗口的标题.第四个参数是窗口的样式.WS_OVERLAPPED,WS_SYSMENU,WS_CAPTION和WS_MINIMIZEBOX的组合创建了一个类似于WS_OVERLAPPEDWINDOW样式的窗口,但它没有最大化按钮,并且不能随意改变大小.是什么使得这个窗口不能改变大小呢?看看定义在Winuser.h中的WS_OVERLAPPEDWINDOW(一个Visual C++附带的几个巨大头文件之一),你就会豁然开朗了.你会看到:


#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZE|WS_MAXIMIZE)

WS_THICKFRAME样式给窗口加了一个可变大小的边框,它的边沿和对角可被鼠标抓取和托动.TicTac的窗口没有这一样式,所以使用者不能随便改变它的大小.

接下来传给CWnd::CreateEx的四个参数指定了窗口的初始位置和大小不一.TicTac给这四个参数使用CW_USEDEFAULT以让Windows来代劳这件事情.然而TicTac的窗口是不能任意被改变大小的,它的大小必须与棋盘格子大小相一致,但是怎么办到呢?CreateEx后面的代码给了我们答案:

CRect rect(0,0,352,352);

CalWindowRect(&rect);
SetWindowPos(NULL,0,0,rect.Width(),rect.Height(),SWP_NOZORDER|SWP_NOMOVE|SWP_NOREDRAW);

第一条语句创建了一个CRect对象,它的值是希望得到的窗口客户区的大小--352X352像素.把这些值直接传给CreateEx是行不通的,因为CreateEx关于大小的参数指定的是整个窗口的大小,并不是仅仅指的客户区域.因为各种Windows非客户区域元素的大小(例如工具栏的高度)随着不同的视频驱动程序和显示方案的不同而不同,我们必须从窗口的客户区域的大小计算出需要的窗口大小,然后设置出合适大小的窗口.


MFC中的CWnd::CalcWindowRect是完成这项工作的理想的选择.给它传一个包含窗口客户区域顶点坐标的CRect对象指针,CalcWindowRect会为你计算出对应的窗口大小矩形.该矩形的宽度和高度然后就可以传递给CWnd::SetWindowPos来设置出合适的窗口大小.在这里,我们知道了当窗口创建完毕后需要调用CalcWindowRect,以便将窗口的非客户区域的大小考虑在内.

PostNcDestroy函数

当你从CWnd派生出自己的窗口类时,还有一点你必须注意的是一旦窗口对象被创建,它必须以某种方式被清除.正如第二章所言,一个窗口被销毁前收到的最后一个消息是WM_NCDESTROY.MFC的CWnd类包含有一个OnNcDestroy处理函数处理这个消息,它执行了一个常规的清除任务,最后调用了一个名为PostNcDestroy的虚拟函数.CFrameWnd对象通过重载PostNcDestroy函数,并使用delete this语句来使得当它们对应的窗口被销毁时它们能够删除它们自身.然而,CWnd::PostNcDestroy什么也没有干,所以一个派生自CWnd的类应该有着自己版本的包含delete this语句的PostNcDestroy函数.TicTac包含有一个小小的PostNcDestroy函数使得应用程序结束生命前CMainWindow对象能够删除自身.


void CMainWindow::PostNcDestroy()
{
    delete this;
}


"谁将担负起删除它的责任"是一个每当你从CWnd派生出一个窗口类时应当考虑的一个问题.除了重载PostNcDestroy函数,还有一种可行的方案就是重载CWinApp::ExitInstance并且对存储在m_pMainWnd的指针使用delete语句.

非客户区鼠标消息

当你的鼠标在一个窗口的非客户区域被点击或移动时,Windows给你的窗口发送一个非客户区鼠标消息.下表列出了所有的非客户区鼠标消息.

Nonclient-Area Mouse Messages

Message Sent When WM_NCLBUTTONDOWN The left mouse button is pressed. WM_NCLBUTTONUP The left mouse button is released. WM_NCLBUTTONDBLCLK The left mouse button is double-clicked. WM_NCMBUTTONDOWN The middle mouse button is pressed. WM_NCMBUTTONUP The middle mouse button is released. WM_NCMBUTTONDBLCLK The middle mouse button is double-clicked. WM_NCRBUTTONDOWN The right mouse button is pressed. WM_NCRBUTTONUP The right mouse button is released. WM_NCRBUTTONDBLCLK The right mouse button is double-clicked. WM_NCMOUSEMOVE The cursor is moved over the window's nonclient area.

注意下表中列出的客户区鼠标消息和非客户区鼠标消息的对应关系.仅有的不同就是消息ID中的字母"NC".WM_NCxBUTTONDBLCLK消息和客户区鼠标双击消息不同,它不管窗口是不是注册了CS_DBLCLKS样式,在哪种情况下它都会被传送.

正如客户区鼠标消息一样,消息映射表将消息传送到和它对应的类成员函数.下表列出和非客户区鼠标消息的消息映射宏和相关的消息处理函数.

Message-Map Macros and Message Handlers for Nonclient-Area Mouse Messages

Message Message-Map Macro Handling Function WM_NCLBUTTONDOWN ON_WM_NCLBUTTONDOWN OnNcLButtonDown WM_NCLBUTTONUP ON_WM_NCLBUTTONUP OnNcLButtonUp WM_NCLBUTTONDBLCLK ON_WM_NCLBUTTONDBLCLK OnNcLButtonDblClk WM_NCMBUTTONDOWN ON_WM_NCMBUTTONDOWN OnNcMButtonDown WM_NCMBUTTONUP ON_WM_NCMBUTTONUP OnNcMButtonUp WM_NCMBUTTONDBLCLK ON_WM_NCMBUTTONDBLCLK OnNcMButtonDblClk WM_NCRBUTTONDOWN ON_WM_NCRBUTTONDOWN OnNcRButtonDown WM_NCRBUTTONUP ON_WM_NCRBUTTONUP OnNcRButtonUp WM_NCRBUTTONDBLCLK ON_WM_NCRBUTTONDBLCLK OnNcRButtonDblClk WM_NCMOUSEMOVE ON_WM_NCMOUSEMOVE OnNcMouseMove

非客户区鼠标消息的消息处理函数的原型如下:

afx_msg void OnMsgName (UINT nHitTest, CPoint point)

再一次,point参数指定了事件在窗口中所发生的位置.但是对于非客户区鼠标消息来说,point.x和point.y包含的是屏幕坐标而不是客户区坐标.在屏幕坐标系统中,(0,0)表示的是屏幕的左上角,x轴正向向右,y轴正向向下,在任意一个方向一个单元的大小是一个像素.如果你需要,你可以使用CWnd::ScreenToClient将一个屏幕坐标转换为一个客户区坐标.nHitTest参数包含一个hit-test代码用以指明事件发生在窗口非客户区域的哪一个位置.下表列出了一些有趣的hit-test代码.你可以从WM_NCHITTEST或者CWnd::OnNcHitTest文档中取得完整的列表.

常用的Hit-Test代码

Value Corresponding Location HTCAPTION The title bar HTCLOSE The close button HTGROWBOX The restore button (same as HTSIZE) HTHSCROLL The window's horizontal scroll bar HTMENU The menu bar HTREDUCE The minimize button HTSIZE The restore button (same as HTGROWBOX) HTSYSMENU The system menu box HTVSCROLL The window's vertical scroll bar HTZOOM The maximize button


应用程序并不需要经常处理非客户区鼠标消息;一般由Windows来代替处理它们.Windows提供对非客户区鼠标消息合适的默认响应,这一般会导致更多的消息发送给窗口.例如,当Windows收到一个hit-test值为HTCAPTION的WN_NCLBUTTONDBLCLK消息时,它发送一个WM_SYSCOMMAND消息给窗口,其中wParam值等于SC_MAXIMIZE或者SC_RESTORE,以最大化或者除最大化这个窗口.你能通过在窗口类包含下面的消息处理函数来阻止双击标题栏对窗口的影响.

// In CMainWindow's message map
ON_WM_NCLBUTTONDBLCLK ()


void CMainWindow::OnNcLButtonDblClk (UINT nHitTest, CPoint point)
{
if (nHitTest != HTCAPTION)
CWnd::OnNcLButtonDblClk (nHitTest, point);
}

调用基类的OnNcLButtonDblClk处理函数会把消息传送给Windows并且允许默认的处理过程的发生.如果不调用基类的处理函数直接返回将会阻止Windows知道该双击动作的发生.你能使用其它的hit-test值来定制窗口对其它非客户区鼠标事件的响应.

WM_NCHITTEST消息

在一个窗口收到一个客户区或非客户区鼠标消息之前,它会收到一个伴随着鼠标指针屏幕坐标的WM_NCHITTEST消息.大多数的应用程序不处理WM_NCHITTEST消息而把它交给Windows处理.当Windows处理WM_NCHITTEST消息时,它使用指针坐标来确定指针正在窗口的哪一个部分上,然后产生一个客户区或者非客户区鼠标消息.

对OnNcHitTest处理函数的一个很绝妙的用法就是使用HTCAPTION hit-test代码来替换HTCLINET代码,他将会使得一个窗口可以通过拖拽其客户区域使之移动.

// In CMainWindow's message map
ON_WM_NCHITTEST ()


UINT CMainWindow::OnNcHitTest (CPoint point)
{
UINT nHitTest = CFrameWnd::OnNcHitTest (point);
if (nHitTest == HTCLIENT)
nHitTest = HTCAPTION;
return nHitTest;
}

正如这个例子所示范的,你自己不关心的WM_NCHITTEST消息应该被送往其类的处理函数去处理以使应用程序的其它操作不会受到影响.


WM_MOUSELEAVE和WM_MOUSEHOVER消息

要知道什么时候一个鼠标指针进入一个窗口或是在它上面移动是非常简单的,因为窗口会接收到WM_MOUSEMOVE消息.在Windows NT 4.0中初次亮相的::TrackMouseEvent函数使得确定什么时刻鼠标指针离开窗口或者是静止悬于窗口上变得同样的简单.该函数在Windows 98中也是支持的.通过使用::TrackMouseEvent,一个应用程序能够注册为当鼠标指针离开一个窗口时接收到WM_MOUSELEAVE消息,当鼠标指针悬于窗口上时接收到WM_MOUSEHOVER消息.

::TrackMouseEvent仅仅接收一个参数:一个指向TRACKMOUSEEVENT结构体的指针.该结构体在Winuser.h中定义为:

typedef struct tagTRACKMOUSEEVENT {
DWORD cbSize;
DWORD dwFlags;
HWND hwndTrack;
DWORD dwHoverTime;
} TRACKMOUSEEVENT;


cbSize保存者该结构体的大小.dwFlags估存着一些位标志,它指明了调用者想要干些什么:注册为接收WM_MOUSELEAVE消息(TME_LEAVE),注册为接收WM_MOUSEHOVER消息(TME_HOVER),取消WM_MOUSELEAVE和WM_MOUSEHOVER消息(TME_CANCEL),或者是让系统用当前的::TrackMouseEvent设定来填定TRACEMOUSEEVENT结构体的各个字段的值(TME_QUERY).hwndTrack是产生WM_MOUSELEAVE和WM_MOUSEHOVER消息的窗口的名柄.dwHoverTime是一个以毫秒为单位的时间值,它指明了鼠标指针必须静止多长时间才会有一个WM_MOUSEHOVER消息发送给它下面的窗口.

鼠标指针并不一定需要绝对的静止才会产生一个WM_MOUSEHOVER消息.如果鼠标指针停留在以SPI_GETMOUSEHOVERWIDTH为参数调用::SystemParametersInfo函数所得到的值为长,以SPI_GETMOUSEHOVERHEIGHT为参数调用::SystemParametersInfo函数所得到的值为宽的矩形内,并且它的停留毫秒数超过了以SPI_GETMOUSEHOVERTIME为参数调用::SystemParametersInfo所返回的值时,一个WM_MOUSEHOVER消息就会产生.如果你需要,你可以通过使用SPI_SETHOVERWIDTH,SPI_SETHOVERHEIGHT和SPI_SETHOVERTIME为参数调用::SystemParametersInfo函数来改变这些值.

::TrackMouseEvent更有意思的是,当一个WM_MOUSELEAVE或者WM_MOUSEHOVER消息产生时,它的作用便消失了.这就意味着如果你想在鼠标指针离开窗口或者是悬停于窗口上方的任意时候都收到这些消息,你必须在接收到WM_MOUSELEAVE或者WM_MOUSEHOVER消息后再一次调用::TrackMouseEvent.举例来说,下面的代码片断实现了无论何时当一个鼠标指针进入,离开或者悬停于一个窗口上时,都会向调试窗口输出"Mouse enter,""Mouse leave,"或者"Mouse hover"信息.m_bMouseOver是一个BOOL类型的CMainWindow成员变量.它应当在构造函数中被设定为FALSE.

// In the message map
ON_WM_MOUSEMOVE ()
ON_MESSAGE (WM_MOUSELEAVE, OnMouseLeave)
ON_MESSAGE (WM_MOUSEHOVER, OnMouseHover)


void CMainWindow::OnMouseMove (UINT nFlags, CPoint point)
{
if (!m_bMouseOver) {
TRACE (_T ("Mouse enter/n"));
m_bMouseOver = TRUE;

TRACKMOUSEEVENT tme;
tme.cbSize = sizeof (tme);
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.hwndTrack = m_hWnd;
tme.dwHoverTime = HOVER_DEFAULT;
::TrackMouseEvent (&tme);
}
}

LRESULT CMainWindow::OnMouseLeave (WPARAM wParam, LPARAM lParam)
{
TRACE (_T ("Mouse leave/n"));
m_bMouseOver = FALSE;
return 0;
}

LRESULT CMainWindow::OnMouseHover (WPARAM wParam, LPARAM lParam)
{
TRACE (_T ("Mouse hover (x=%d, y=%d)/n"),
LOWORD (lParam), HIWORD (lParam));

TRACKMOUSEEVENT tme;
tme.cbSize = sizeof (tme);
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.hwndTrack = m_hWnd;
tme.dwHoverTime = HOVER_DEFAULT;
::TrackMouseEvent (&tme);
return 0;
}