- Visual C++范例大全
- 孙皓等编著
- 105字
- 2021-03-29 11:12:12
第2章 消息映射与处理
在Windows中,消息分为鼠标消息、键盘消息、窗口消息、自定义消息。Visual C++把消息机制有效地封装起来,不需要写冗长的代码就可以很轻松地编写出各个不同的处理函数。本章将通过具体的例程介绍VC中相关的消息处理。
2.1 键盘消息及处理
按下一个键就会产生一条WM_KEYDOWN或WM_SYSKEYDOWN消息,并将被放到与有关键盘输入的窗口相应的线程消息队列中;释放一个键则会产生一条WM_KEYUP或WM_SYSKEYUP消息,同样也会被放到队列中。
实例22:基本键盘操作—判断按键消息
❑实例说明
本实例将演示基本的键盘消息的编程。实例实现的功能为,当用户按下了“Shift”键时,在视图窗口中显示提示信息“用户按下了Shift键!”,当用户释放了“Shift”键时,在视图窗口中显示提示信息“用户释放了Shift键!”,而当用户按下了“Shift”键后又按下了字符“B”键,在视图窗口中显示提示信息“用户同时按下Shift键和B键!”。程序的运行界面如图2.1所示。
图2.1 程序的运行界面
❑核心开发过程
(1)创建一个单文档的MFC工程,使用ClassWizard在视图类中添加WM_KEYDOWN、WM_KEYUP和WM_CHAR键盘消息映射和消息响应函数。
(2)在键盘消息响应函数中,判断按键的状态。代码如下:
void CBaseKeyDemoView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default if(nChar==VK_SHIFT) //判断"Shift"键是否被按下 { //AfxMessageBox("dd"); bShiftdown=TRUE; bShiftup=FALSE; Invalidate(TRUE); //显示信息 } CView::OnKeyDown(nChar, nRepCnt, nFlags); } void CBaseKeyDemoView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default if(nChar==VK_SHIFT) //判断"Shift"键是否被释放 { //AfxMessageBox("dd"); bShiftup=TRUE; Invalidate(TRUE); //显示信息 bShiftdown=FALSE; } CView::OnKeyUp(nChar, nRepCnt, nFlags); } void CBaseKeyDemoView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default if((nChar==98)||(nChar==66)) //判断是否敲击了字符键"B"键或"b"键 { if(bShiftdown) { bShiftB=TRUE; bShiftdown=FALSE; Invalidate(TRUE); //显示信息 } } CView::OnChar(nChar, nRepCnt, nFlags); }
(3)在视图类的OnDraw函数中,根据按键的状态,显示相关的文本。代码如下:
void CBaseKeyDemoView::OnDraw(CDC* pDC) { CBaseKeyDemoDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here if(bShiftdown) //按下了"Shift"键 { pDC->TextOut(20,20,"用户按下了Shift键!"); } if(bShiftup) //释放了"Shift"键 { pDC->TextOut(20,20,"用户释放了Shift键!"); } if(bShiftB) //同时按下了"Shift"键和"B"键 { pDC->TextOut(20,20,"用户同时按下Shift键和B键!"); bShiftB=FALSE; } }
❑要点说明
在MFC中,ClassWizard封装的键盘消息共有下面3种。
• WM_KEYDOWN:某一键被按下。
• WM_KEYUP:某一键弹起。
• WM_CHAR:某一键按下又弹起,输入了一个字符。
当用户按下了键盘中的某一个键时,首先产生WM_KEYDOWN消息,进入其消息处理函数OnKeyDown。如果该键为字符键,之后还将产生WM_CHAR消息,进入消息处理函数OnChar。OnChar函数的参数nChar并不是虚键码,而是Windows字符集的字符代码,默认的为ASCII码。常见的字符及其ASCII码值如表2.1所示。
表2.1 常见的字符及其ASCII码值
实例23:在普通视图窗口中实现键盘字符的输入
❑实例说明
本实例将实现在单文档应用程序界面中,当用户通过键盘键入字符时,在视图窗口依次显示键入的字符。当用户按下“Enter”键时,进行换行输出。程序的运行界面如图2.2所示。
❑核心开发过程
(1)在视图类的头文件“KeyInputView.h”中,定义CPoint型变量,用于记录字符在视图窗口中的输出位置。
图2.2 程序的运行界面
public:
CPoint ptCharacter; //记录字符位置
在CKeyInputView类的构造函数中,初始化ptCharacter位置为(0,0)。
CKeyInputView::CKeyInputView() { // TODO: add construction code here //初始位置设置在(0,0) ptCharacter.x=0; ptCharacter.y=0; }
(2)在WM_CHAR消息响应函数OnChar中,实现字符的显示以及换行。代码如下:
void CKeyInputView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default if(nChar==13) //按下了回车键 { //换行 ptCharacter.x=0; ptCharacter.y=ptCharacter.y+25; } else { CClientDC dc(this); dc.TextOut(ptCharacter.x,ptCharacter.y,(LPCTSTR)&nChar); //输出显示字符 CSize textsize; textsize=dc.GetTextExtent((LPCTSTR)&nChar); //获取当前字符大小 //前进到下一个字符位置 ptCharacter.x=ptCharacter.x+textsize.cx; } CView::OnChar(nChar, nRepCnt, nFlags); }
❑要点说明
本程序只是简单演示了WM_CHAR消息响应和按键字符的显示操作,并没有实现窗口的重绘。另外,实际程序如果涉及文本输入、编辑操作,可以通过使用Edit控件或者CEditView视图来实现。
实例24:创建和使用键盘插入符
❑实例说明
键盘插入符(Caret)是一个闪烁的位图(通常是一个细的垂直杠),它可使用户知道在窗口何处可进行有效的键盘输入。本实例将在前面实例的基础上,在当前键盘的输入位置显示一个自己创建的插入符,实例的运行界面如图2.3所示。
图2.3 程序的运行界面
❑核心开发过程(1)使用ClassWizard在视图类中添加WM_SETFOCUS消息映射和消息响应函数,在其中创建键盘插入符,并在当前的输入位置显示。代码如下:
void CCaretKeyDemoView::OnSetFocus(CWnd* pOldWnd) { CView::OnSetFocus(pOldWnd); // TODO: Add your message handler code here CreateSolidCaret(3, 18); //创建插入符 SetCaretPos (ptCharacter); //将插入符移到当前字符输入点 ShowCaret (); //显示插入符 }
(2)在OnChar函数中,实现在适当的时机显示、隐藏插入符。代码如下:
void CCaretKeyDemoView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default if(nChar==13) //按下了Enter键 { //换行 ptCharacter.x=0; ptCharacter.y=ptCharacter.y+25; SetCaretPos (ptCharacter); //将插入符移到键入点 ShowCaret (); //显示插入符 } else { CClientDC dc(this); HideCaret (); //隐藏插入符 dc.TextOut(ptCharacter.x,ptCharacter.y,(LPCTSTR)&nChar); //显示字符 CSize textsize; textsize=dc.GetTextExtent((LPCTSTR)&nChar); //获取当前字符大小 //前进到下一个字符位置 ptCharacter.x=ptCharacter.x+textsize.cx; SetCaretPos (ptCharacter); //将插入符移到键入点 ShowCaret (); //显示插入符 } CView::OnChar(nChar, nRepCnt, nFlags); }
❑要点说明
Windows总是把键盘消息送到拥有输入焦点的窗口。一般情况下一个应用程序有多个窗口,而键盘消息只能被一个窗口接收,接收键盘消息的窗口称为有“输入焦点”的窗口,具有输入焦点的窗口称为活动窗口。当某一个窗口成为活动窗口时,Windows会加亮显示其标题栏或窗口边框。
Windows用WM_SETFOCUS和WM_KILLFOCUS消息通知即将接收或失去输入焦点的窗口。当窗口获得键盘焦点时,就可以创建插入符了,若窗口没有焦点,就不能进行键盘输入。另外,插入符一旦创建起来,还要在窗口中对其进行定位和显示。
CWnd类提供了8个创建和管理键盘插入符的成员函数,各函数及实现的功能介绍如下。
• CreateCaret:使用用户提供的位图创建插入符。
• CreateGrayCaret:创建用户自定义大小的实心灰色插入符。
• CreateSolidCaret:创建用户自定义大小的实心黑色插入符。
• DestoryCaret:销毁插入符。
• ShowCaret:显示插入符。
• HideCaret:隐藏插入符。
• GetCaretPos:返回插入符的位置。
• SetCaretPos:移动插入符到窗口的某一位置。
实例25:使用程序模拟键盘输入
❑实例说明
键盘输入是对Windows程序的主要操作之一。对于复杂的或重复性的输入操作,需要通过程序来模拟键盘的输入。本实例将在编辑框中模拟键盘的输入,即当用户单击“模拟输入”按钮时,将创建一个定时器,间隔1秒在编辑框中模拟键盘,循环输入“hello world”字符。程序的运行界面如图2.4所示。
图2.4 程序的运行界面
❑核心开发过程
(1)在按钮的BN_CLICKED消息响应函数中,实现设置或取消定时器,并设置按钮文本。代码如下:
void CSimulateKeyDlg::OnInput() { // TODO: Add your control notification handler code here static BOOL bChangeFlag=TRUE; if(bChangeFlag) { SetTimer(1,500,NULL);//设置定时器 bChangeFlag =bChangeFlag ? FALSE:TRUE; GetDlgItem(IDC_INPUT)->SetWindowText("停止输入");//设置按钮文本 } else { GetDlgItem(IDC_INPUT)->SetWindowText("模拟输入");//设置按钮文本 KillTimer(1);//取消定时器 bChangeFlag =bChangeFlag ? FALSE:TRUE; } }
(2)使用ClassWizard,添加WM_TIMER消息响应函数,在函数中实现模拟按键操作。代码如下:
void CSimulateKeyDlg::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default static int count=0; BYTE keyname[11]={72,69,76,76,79,32,87,79,82,76,68}; //按键序列的虚拟键码 if(nIDEvent==1) { m_ctlEdit.SetFocus(); //编辑框获得输入焦点 keybd_event(keyname[count],0,0,0); //按键按下 keybd_event(keyname[count],0,KEYEVENTF_KEYUP,0); //按键抬起 count++; if(count==12) { m_ctlEdit.SetWindowText(""); //情况编辑框 UpdateData(FALSE); count=0; } } CDialog::OnTimer(nIDEvent); }
❑要点说明
使用API函数keybd_event可以触发WM_KEYDOWN或者WM_UP键盘消息,模拟键盘的输入。keybd_event函数的原型如下:
VOID keybd_event( BYTE bVk, //按键的虚拟键值 BYTE bScan, //扫描码,一般不用设置,用0代替即可 DWORD dwFlags, //选项标志,KEYDOWN设置为0即可 DWORD dwExtraInfo //附加特性,一般设置为0 );
虚拟键码是Windows内部建立的设备无关的键盘代码,在Windows中不论使用什么类型的键盘,都将扫描代码翻译成同一的虚键码,这样应用程序就不用直接同硬盘等硬件打交道。Windows中常用的虚拟键码及其对应的按键如表2.2所示。
表2.2 Windows常用的虚拟键码及其对应的按键
实例26:在对话框中实现键盘消息响应
❑实例说明
用VC完成的对话框程序,在键盘消息响应和快捷键的实现上,没有提供直接的实现方式。原因是对话框里的控件需要首先对按键作出响应,如多行编辑框必须首先处理回车,不至于回车使对话框关闭。本实例将通过重载PreTranslateMessage函数,实现在对话框中,当用户按下按键时,显示相应按键的虚拟键码。程序的运行界面如图2.5所示(按了“a”键)。
图2.5 程序的运行界面
❑核心开发过程
使用ClassWizard重载PreTranslateMessage函数,进行消息预处理。实现代码如下:
BOOL CDlgKeyMessageDlg::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if(pMsg->message == WM_KEYDOWN) //如果用户按下了按键 { CString strwParam; strwParam.Format("用户按键的虚拟键码为:%d ",pMsg->wParam); CDC* pDC = m_ctlframe.GetDC(); //获取DC pDC->TextOut(2,2,strwParam); //显示文本 ReleaseDC(pDC); //释放DC } return CDialog::PreTranslateMessage(pMsg); }
❑要点说明
在对话框中,重载PreTranslateMessage函数,进行消息预处理,然后再添加WM_KEYDOWN、WM_KEYUP等消息的消息响应函数,就能实现对话框程序的键盘消息响应和快捷键功能。如在对话框中添加了WM_KEYDOWN消息响应函数OnKeyDown,如果要调用它,就可以在PreTranslateMessage函数中添加如下代码:
BOOL CDlgKeyMessageDlg::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if(pMsg->message == WM_KEYDOWN) //如果用户按下了按键 { OnKeyDown(pMsg->wParam, LOWORD(pMsg->lParam), HIWORD(pMsg->lParam)); } return CDialog::PreTranslateMessage(pMsg); }
实例27:向其他应用程序(记事本)中发送键盘消息
❑实例说明
本实例实现向打开的记事本文件中,通过键盘消息,发送“HELLO”字符串,并通过发送“Ctrl”+“S”组合键命令保存记事本文件。实例的运行界面如图2.6所示。
图2.6 程序的运行界面
❑核心开发过程
发送字符串和保存文件的函数实现代码如下:
void CSendNotepadMsgDlg::OnSend() //发送字符串 { // TODO: Add your control notification handler code here HWND hWnd = ::FindWindow("Notepad", NULL); if(hWnd) { HWND hEdit = FindWindowEx(hWnd, NULL, "Edit", NULL); if(hEdit) ::PostMessage(hEdit, WM_CHAR, 0x48, 0); //发送'H'到notepad ::PostMessage(hEdit, WM_CHAR, 0x45, 0); //发送'E'到notepad ::PostMessage(hEdit, WM_CHAR, 0x4C, 0); //发送'L'到notepad ::PostMessage(hEdit, WM_CHAR, 0x4C, 0); //发送'L'到notepad ::PostMessage(hEdit, WM_CHAR, 0x4F, 0); //发送'O'到notepad } else { AfxMessageBox("请打开记事本文件!"); return; } } void CSendNotepadMsgDlg::OnSave() //保存文件 { // TODO: Add your control notification handler code here CWnd* pWnd =FindWindow("Notepad", NULL); if (pWnd->GetSafeHwnd()) { pWnd->ShowWindow(SW_NORMAL); pWnd->SetForegroundWindow(); keybd_event(VK_CONTROL, 0, 0, 0); //按下"Ctrl"键 keybd_event('S', 0, 0, 0); //按下"S"键 keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);//释放"Ctrl"键 keybd_event('S', 0, KEYEVENTF_KEYUP, 0); //释放"S"键 } else { AfxMessageBox("未找到打开的记事本文件!"); return; } }
❑要点说明
向其他应用程序中发送消息,查找接收消息的窗口句柄非常关键。使用FindWindow函数可以获取当前运行程序的主窗口句柄,而使用FindWindowEx函数则用于获取子窗口句柄。
2.2 鼠标消息及处理
鼠标消息是应用程序开发中常需要处理的消息,当鼠标移动、左键(右键)的按下或者松开、双击操作等都可以产生相应的鼠标消息。
实例28:基本鼠标操作—判断鼠标消息
❑实例说明
在MFC中,对常用的几种鼠标消息进行了封装,包括鼠标左键和右键的单击、释放、双击,鼠标的移动以及滚轮的滚动等操作。本实例将演示基本鼠标消息的响应,实现的功能是为当用户在视图窗口中按下鼠标左键,拖动鼠标时,在窗口中绘制一个随鼠标位置变化的椭圆,当释放鼠标键时,停止椭圆绘制。实例的运行界面如图2.7所示。
图2.7 程序的运行界面
❑核心开发过程
(1)在视图类中添加如下的成员变量:
public: BOOL bDrag; //是否在拉动 CPoint ptDown; //鼠标左键按下位置 CPoint ptUp; //鼠标左键释放位置
在视图类的构造函数中,对定义的成员变量进行初始化:
CBaseMouseDemoView::CBaseMouseDemoView() { // TODO: add construction code here //初始化成员变量 bDrag=false; ptDown.x=ptDown.y=0; ptUp.x=ptUp.y=0; }
(2)在视图类中添加绘制椭圆的成员函数DrawCircle,其实现代码如下:
void CBaseMouseDemoView::DrawCircle() { Invalidate(false); CClientDC dc(this); //获取DC CRect rect; GetClientRect(rect); //获取客户窗口区域 CBrush brush(RGB(255,255,255)); dc.FillRect(rect,&brush); //填充背景色为白色 dc.Ellipse(ptDown.x,ptDown.y,ptUp.x,ptUp.y); //绘制椭圆 }
(3)使用ClassWizard在视图类添加WM_LBUTTONDOWN、WM_LBUTTONUP和WM_MOUSEMOVE消息映射和消息处理函数。各函数的实现代码如下:
void CBaseMouseDemoView::OnLButtonDown(UINT nFlags, CPoint point) //按下鼠标左键 { // TODO: Add your message handler code here and/or call default bDrag=TRUE; ptUp=ptDown=point; //记录鼠标当前位置 CView::OnLButtonDown(nFlags, point); } void CBaseMouseDemoView::OnLButtonUp(UINT nFlags, CPoint point) //释放鼠标左键 { // TODO: Add your message handler code here and/or call default if(bDrag) { ptUp=point; DrawCircle(); //画新圆 bDrag=FALSE; } CView::OnLButtonUp(nFlags, point); } void CBaseMouseDemoView::OnMouseMove(UINT nFlags, CPoint point) //移动鼠标 { // TODO: Add your message handler code here and/or call default if(bDrag) { ptUp=point; //记录鼠标的当前位置 DrawCircle(); //画新圆 } CView::OnMouseMove(nFlags, point); }
❑要点说明
鼠标消息的响应函数,如按下鼠标左键的消息响应函数的原型如下:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point );
其中,参数nFlags表明了当前一些按键的消息,其可取值及其含义介绍如下。
• MK_LBUTTON:按下了鼠标的左键。
• MK_MBUTTON:按下了鼠标的中键。
• MK_RBUTTON:按下了鼠标的右键。
• MK_CONTROL:按下了键盘上的“Ctrl”键。
• MK_SHIFT:按下了键盘上的“Shift”键。
• WM_RBUTTONDBLCLK:双击鼠标右键。
可以通过“位与”操作进行相关检测。在实际编程中,常使用nFlags参数指出消息生成时的鼠标键以及Shift和Ctrl的状态,如当按下鼠标左键时,同时检测“Shift”键和“Ctrl”键的状态,可采用下面的代码:
void OnLButtonDown(UINT nFlags,CPoint point ) //按下了鼠标左键 { if((nFlags &MK_CONTROL)&&( nFlags &MK_SHIFT)) //"Shift"和"Ctrl"键都被按下 ... }
而参数point表示当前鼠标的设备坐标,坐标原点对应视图左上角。通过point参数可以将鼠标操作与屏幕显示对应起来。
实例29:创建并设置鼠标光标
❑实例说明
鼠标光标(Cursor)是鼠标与用户之间的接口,它指示鼠标的位置,随鼠标移动而移动,是鼠标的屏幕映像。在Visual C++创建的应用程序中,鼠标都采用系统默认的光标,用户也可以通过编程使用自己的光标。本实例将实现单击鼠标左键和右键时,分别显示自己创建的光标和系统光标。实例的运行界面如图2.8所示。
❑核心开发过程
(1)绘制光标资源
在Visual C++中,执行“Insert”→“Resource”菜单命令,在弹出的“Insert Resource”对话框中,选择“Cursor”,单击“New”按钮,即进入光标的编辑窗口。在光标编辑窗口中,可以绘制需要的光标图形。在绘制窗口中,有一个热点设置按钮,在它旁边显示了热点设置的坐标。单击这个按钮,在光标编辑器中出现一个十字光标,将十字中心放在需要设定的热点位置,单击鼠标左键即可。绘制光标窗口如图2.9所示。
图2.8 程序的运行界面
图2.9 编辑设计光标窗口
(2)在CMainFrame类的PreCreateWindow()函数中,通过API函数LoadImage函数将绘制的光标载入程序。代码如下:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here by modifying //将自定义的光标赋予m_cursor m_cursor=(HCURSOR)::LoadImage(cs.hInstance,MAKEINTRESOURCE(IDC_CURSOR1), IMAGE_CURSOR,32,32, LR_CREATEDIBSECTION); return TRUE; }
(3)在视图类添加WM_LBUTTONUP消息响应函数,设置光标为自绘光标。代码如下:
void CCursorMouseDemoView::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CMainFrame* pMainframe; pMainframe=(CMainFrame*)AfxGetMainWnd(); SetCursor(pMainframe->m_cursor); //设置光标为自绘光标 CView::OnLButtonUp(nFlags, point); }
(4)在视图类添加WM_RBUTTONUP消息响应函数,载入和使用Windows系统提供的标准光标资源IDC_CROSS。代码如下:
void CCursorMouseDemoView::OnRButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default HCURSOR cusor=AfxGetApp()->LoadStandardCursor(IDC_CROSS); //获取系统标准光标 SetCursor(cusor); //设置光标 CView::OnRButtonUp(nFlags, point); }
❑要点说明
Windows系统提供了19种标准光标,如IDC_APPSTARTING、IDC_ARROW、IDC_CROSS、IDC_WAIT等。如果要采用Windows系统提供的标准光标资源,必须首先通过函数LoadStandardCursor载入系统标准光标资源,而后通过函数SetCursor设置光标。在实际编程中,如果用户需要使光标“消失”,即隐藏光标,这时使用ShowCursor(false)语句实现。
实例30:在对话框中定义光标的热区
❑实例说明
本实例将实现为对话框中的控件定义光标热区,即当鼠标光标位于某个控件上时,改变鼠标的显示光标,并给出该控件的描述性信息。实例的运行界面如图2.10所示。
图2.10 程序的运行界面
❑核心开发过程
(1)向工程添加一手形光标资源,ID设置为IDC_MYHAND。(2)使用ClassWizard为对话框添加WM_SETCURSOR消息响应函数OnSetCursor,在该函数中根据鼠标所在窗口的ID设置相应的热区。函数实现代码如下:
BOOL CCursorHotDemoDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { // TODO: Add your message handler code here and/or call default switch(pWnd->GetDlgCtrlID()) //得到鼠标所在位置的控件的ID号 { case IDC_BUTTON1: //鼠标位于按钮空间区域内 { //设置鼠标指针为"手"形指针 SetCursor(AfxGetApp()->LoadCursor(IDC_MYHAND)); //将提示文字可见 GetDlgItem(IDC_TEXT)->ShowWindow(SW_SHOW); GetDlgItem(IDC_TEXT)->SetWindowText("光标所在位置为按钮框!"); return TRUE; } break; case IDC_EDIT1: //鼠标位于编辑框区域内 { //设置鼠标指针为"手"形指针 SetCursor(AfxGetApp()->LoadCursor(IDC_MYHAND)); // 将提示文字可见 GetDlgItem(IDC_TEXT)->ShowWindow(SW_SHOW); GetDlgItem(IDC_TEXT)->SetWindowText("光标所在位置为编辑框!"); return TRUE; } break; case IDC_MONTHCALENDAR1: //鼠标位于日历控件区域内 { // 设置鼠标指针为"手"形指针 SetCursor(AfxGetApp()->LoadCursor(IDC_MYHAND)); // 将提示文字可见 GetDlgItem(IDC_TEXT)->ShowWindow(SW_SHOW); GetDlgItem(IDC_TEXT)->SetWindowText("光标所在位置为日历控件!"); return TRUE; } break; default: //鼠标离开了"热区" { //将提示性文字隐藏 GetDlgItem(IDC_TEXT)->ShowWindow(SW_HIDE); //将鼠标指针设置为标准的鼠标指针 SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); return TRUE; } } return CDialog::OnSetCursor(pWnd, nHitTest, message); }
实例31:创建和使用鼠标提示框
❑实例说明
当鼠标指针放在程序的某个控件上时,要想得知该控件的功能,最好的办法是弹出一个提示框来显示帮助信息。本实例将实现弹出鼠标提示框,给出鼠标指针所在位置控件的相关信息。实例的运行界面如图2.11所示。
❑核心开发过程
在Internet中,可以下载一个免费的类CMFECToolTip,实现浮动的鼠标提示框。它派生自CWnd类,具体实现代码本书不作介绍。下面简单介绍程序中如何调用该类实现鼠标提示框。(1)将CMFECToolTip类的头文件和实现文件复制到工程目录下,并添加到工程中。在头文件“MouseInfoTipDlg.h”中,声明CMFECToolTip类的对象。如下:
图2.11 程序的运行界面
#include "MFECToolTip.h" ... protected: HICON m_hIcon; CMFECToolTip m_toolTip; //声明对象
(2)在对话框的初始化函数OnInitDialog()中,为各个控件设置相关提示框的内容和背景、文本颜色。实现代码如下:
BOOL CMouseInfoTipDlg::OnInitDialog() { CDialog::OnInitDialog(); ... // TODO: Add extra initialization here m_toolTip.Create( this ); //创建提示框对象 CStringArray straInfo; //"添加"按钮提示框 straInfo.RemoveAll(); straInfo.Add( "向列表框中添加记录" ); m_toolTip.AddControlInfo(IDC_ADD,straInfo,RGB(220,174,208),RGB(0,0,162) ); //退出按钮提示框 straInfo.RemoveAll(); straInfo.Add( "关闭窗口" ); m_toolTip.AddControlInfo(IDCANCEL,straInfo,RGB(220,174,208),RGB( 0,0,162)); //编辑框提示框 straInfo.RemoveAll(); straInfo.Add( "输入书籍名称" ); m_toolTip.AddControlInfo(IDC_EDIT1,straInfo,RGB(220,174,208),RGB( 0,0,162)); //列表框提示框 straInfo.RemoveAll(); straInfo.Add( "显示添加的图书记录" ); straInfo.Add( "实现自动排序:" ); m_toolTip.AddControlInfo(IDC_LIST1,straInfo,RGB(220,174,208),RGB( 0,0,162) ); return TRUE; // return TRUE unless you set the focus to a control }
(3)使用ClassWizard重载对话框的PreTranslateMessage函数,实现捕捉鼠标的WM_MOUSEMOVE消息,弹出鼠标提示框。代码如下:
BOOL CMouseInfoTipDlg::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if( pMsg->message == WM_MOUSEMOVE ) //鼠标移动 { POINT pt = pMsg->pt; ScreenToClient( &pt ); //转换为客户区坐标 m_toolTip.ShowToolTip( (CPoint)pt ); //显示提示框 } return CDialog::PreTranslateMessage(pMsg); }
实例32:在视图窗口实现捕捉鼠标
❑实例说明
通常情况下,只有鼠标光标位于某一个窗口的客户区或非客户区时,该窗口的窗口函数才能接收鼠标消息。由于鼠标的随机性,难以保证光标始终不离开某一窗口,如果要使某个窗口能不间断地捕获鼠标消息,就必须对鼠标加以捕获,从而使Windows发送的所有鼠标消息均定向到某一个窗口,而不管鼠标光标位于何处。
本实例将实现鼠标的捕捉功能,即当用户在客户区窗口中单击鼠标左键后,该窗口实现对鼠标的捕捉,并记录鼠标的位置坐标,而当双击鼠标时,则释放捕捉。实例的运行界面如图2.12所示。
图2.12 程序的运行界面
❑核心开发过程
使用ClassWizard在视图类添加WM_LBUTTONDOWN、WM_LBUTTONUP和WM_LBUTTONDBLCLK消息映射和消息处理函数。各函数的实现代码如下:
void CCaptureMouseView::OnLButtonDown(UINT nFlags, CPoint point)//实现鼠标捕捉 { // TODO: Add your message handler code here and/or call default SetCapture(); //捕捉鼠标 CString s; s.Format("用户按下鼠标左键的位置:X=%d,Y=%d",point.x,point.y); CClientDC dc(this); //获得DC dc.TextOut(30,40,s); //输出文本 CView::OnLButtonDown(nFlags, point); } void CCaptureMouseView::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CString s; s.Format("用户松开鼠标左键的位置:X=%d,Y=%d",point.x,point.y); CClientDC dc(this); //获得DC dc.TextOut(30,80,s); //输出文本 CView::OnLButtonUp(nFlags, point); } void CCaptureMouseView::OnLButtonDblClk(UINT nFlags, CPoint point) //释放鼠标捕捉 { // TODO: Add your message handler code here and/or call default ReleaseCapture(); //释放鼠标 CClientDC dc(this); //获得DC dc.TextOut(30,40,"释放了鼠标,此时可以响应客户窗口以外的鼠标命令!");//输出文本 CView::OnLButtonDblClk(nFlags, point); }
❑要点说明
使用Win32 API函数SetCapture()可以实现鼠标的捕获,其原型如下:
HWND SetCapture(HWND hWnd);
hWnd为要捕捉鼠标消息的窗口句柄,这将导致Windows向窗口句柄为hWnd的窗口发送所有的鼠标消息,此时鼠标光标的坐标仍然相对于hWnd窗口客户区的左上角,因此此时鼠标位置值有可能为负值。
一旦某个窗口捕获了鼠标,其他窗口将无法得到鼠标消息,因此,当窗口不再需要捕获鼠标消息时,应及时使用ReleaseCapture( )函数将鼠标释放。
实例33:限制鼠标的作用区域只在客户窗口
❑实例说明
在程序设计中,有时为了限制用户的某些操作,需要将鼠标的活动限定在固定的区域内。本实例将实现限制鼠标的作用区域的功能。当用户在客户区窗口中单击鼠标左键时,鼠标的移动区域就被限制在客户窗口中。当双击鼠标左键时,将取消鼠标的区域限制。实例的运行界面如图2.13所示。
图2.13 程序的运行界面
❑核心开发过程
(1)在限制鼠标的活动区域之前,需要记录当前的鼠标获取区域。在视图类的PreCreateWindow函数中,通过下面的代码来实现:
BOOL CClipCursorMouseView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs GetClipCursor(&oldrect); //获取原鼠标活动的有效区域 return CView::PreCreateWindow(cs); }
(2)使用ClassWizard在视图类添加WM_LBUTTONDOWN和WM_LBUTTONDBLCLK消息映射和消息处理函数,分别实现限制和取消鼠标的活动区域。函数实现代码如下:
void CClipCursorMouseView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default SetCapture(); //捕捉鼠标 CRect rect1; GetWindowRect(&rect1); //获取客户区窗口区域 ClipCursor(&rect1); //将鼠标的活动区域限制在客户窗口 CView::OnLButtonDown(nFlags, point); } void CClipCursorMouseView::OnLButtonDblClk(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default ReleaseCapture(); //释放鼠标 ClipCursor(&oldrect); //恢复鼠标的活动区域 CView::OnLButtonDblClk(nFlags, point); }
❑要点说明
除了前面介绍的鼠标捕捉和限制鼠标的活动区域外,还有一些常用的与鼠标相关的API函数,具体介绍如下。
• GetDoubleClickTime:获取鼠标双击有效的时间间隔。
• SetDoubleClickTime:设置鼠标双击有效的时间间隔。
• SwapMouseButton:交换鼠标左右键。
• GetCursorPos:获取鼠标的位置信息。
• SetCursorPos:设置鼠标的位置。
实例34:使用程序模拟鼠标动作
❑实例说明
对于复杂的或者重复性的鼠标输入操作,有时需要使用程序模拟鼠标的动作。这时可使用SetCursorPos函数将鼠标移动到某一位置,而后使用mouse_event函数发送鼠标动作消息。本实例将使用菜单命令触发模拟鼠标操作,即当执行“双击标题条”菜单命令时,鼠标将移动到标题条,而后执行双击操作;执行“单击关闭按钮”菜单命令时,鼠标将移动到关闭按钮,而后执行单击操作,关闭窗口。实例的运行界面如图2.14所示。
图2.14 程序的运行界面
❑核心开发过程
两个菜单项的WM_COMMAND消息响应函数的实现代码分别如下:
void CMainFrame::OnDbclicked() //"双击标题条"菜单命令 { // TODO: Add your command handler code here POINT lpPoint; CRect rect; CWnd *pParent=AfxGetApp()->GetMainWnd(); //获取主窗口指针 pParent->GetWindowRect(&rect); //获取主窗口的区域 lpPoint.x=rect.left+60; lpPoint.y=rect.top+10; SetCursorPos(lpPoint.x,lpPoint.y); //将鼠标的位置移动到标题条上 Sleep(1000); //等待1s //双击标题条 mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0); mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0); mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0); mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0); } void CMainFrame::OnLclick() //"单击关闭按钮"菜单命令 { // TODO: Add your command handler code here POINT lpPoint; CRect rect; CWnd *pParent=AfxGetApp()->GetMainWnd(); //获取主窗口指针 pParent->GetWindowRect(&rect); lpPoint.x=rect.right-10; lpPoint.y=rect.top+5; SetCursorPos(lpPoint.x,lpPoint.y); //将鼠标的位置移动到关闭按钮上 Sleep(1000); //等待1s //单击关闭按钮 mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0); mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0); }
❑要点说明
使用API函数mouse_event可以触发鼠标消息,模拟鼠标的动作。mouse_event函数的原型如下:
VOID mouse_event( DWORD dwFlags, //鼠标的动作 DWORD dx, //鼠标的绝对X位置或相对移动量 DWORD dy, //鼠标的绝对Y位置或相对移动量 DWORD dwData, //一般设置为0 ULONG_PTR dwExtraInfo //一般设置为0 );
2.3 其他消息处理
前面主要介绍了键盘消息、鼠标消息及其相关的操作,而窗口消息的使用在后面章节中会有详细的介绍,本节将给出一些其他常用的消息处理的实例。
实例35:创建和使用自定义消息
❑实例说明
在应用程序中,用户也可以创建自己的消息,在需要的时候发送和处理消息。本实例将自定义一个消息,进行消息映射,并定义相关的消息处理函数。当用户在对话框中按下按钮时,向窗口发送该消息。实例的运行界面如图2.15所示。
图2.15 程序的运行界面
❑核心开发过程
(1)在文件“OwnerMessageDlg.cpp”中,声明自定义消息,代码如下:
#define WM_MYMESSAGE (WM_USER+100) //定义自定义消息
(2)在头文件“OwnerMessageDlg.h”中,声明消息响应函数,代码如下:
afx_msg LRESULT OnMyMessage(WPARAM w,LPARAM l); //添加消息响应函数
(3)在文件“OwnerMessageDlg.cpp”中,添加消息响应宏,代码如下:
BEGIN_MESSAGE_MAP(COwnerMessageDlg, CDialog) //{{AFX_MSG_MAP(COwnerMessageDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BUTTON1, OnButton1) ON_MESSAGE(WM_MYMESSAGE,OnMyMessage) //添加消息响应宏 //}}AFX_MSG_MAP END_MESSAGE_MAP()
(4)在文件“OwnerMessageDlg.cpp”中,实现消息响应函数,代码如下:
LRESULT COwnerMessageDlg::OnMyMessage(WPARAM wParam, LPARAM lParam) { AfxMessageBox("自定义消息响应成功!"); return 1; }
(5)在按钮的BN_CLICKED消息响应函数中,触发用户自定义函数,代码如下:
void COwnerMessageDlg::OnButton1() { // TODO: Add your control notification handler code here PostMessage(WM_MYMESSAGE, IDC_BUTTON1); }
❑要点说明
Windows消息的投递有两种方式:PostMessage和SendMessage。二者的区别主要在于是否等待其他程序消息处理:
• PostMessage只是把消息放入队列,不管其他程序是否处理都继续执行;
• SendMessage必须等待其他程序处理消息后才继续执行。
这两个函数的返回值也不同,PostMessage的返回值表示PostMessage函数执行是否正确,而SendMessage的返回值表示其他程序处理消息后的返回值。
实例36:使用命令范围添加消息处理函数
❑实例说明
在MFC中,可以使用ClassWizard为每一个控件、菜单命令项、工具按钮等添加消息映射和消息处理函数。而使用VC提供的范围消息映像宏,则可以实现为ID在某一范围内的对象添加同一个消息处理函数。
本实例将使用ON_COMMAND_RANGE宏和ON_UPDATE_COMMAND_UI_RANGE宏,为在某一范围内的4个菜单命令项,分别添加同一命令处理和界面更新函数。实例的运行界面如图2.16所示。
图2.16 程序的运行界面
❑核心开发过程
(1)在视图类的头文件中,手工添加菜单项组的命令函数和界面函数,代码如下:
protected: afx_msg void OnMenuItemCommandRange(UINT nID); //命令函数 afx_msg void OnUpdateMenuItemCommandRange(CCmdUI* pCCmdUI); //界面函数
(2)在视图类的实现文件中,为菜单项组通过使用ON_COMMAND_RANGE宏和ON_UPDATE_COMMAND_UI_RANGE宏,实现消息映射。代码如下:
BEGIN_MESSAGE_MAP(CCommandRangeDemoView, CView) //{{AFX_MSG_MAP(CCommandRangeDemoView) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) //命令消息映射 ON_COMMAND_RANGE(ID_MENUITEM1,ID_MENUITEM4,OnMenuItemCommandRange) ON_UPDATE_COMMAND_UI_RANGE(ID_MENUITEM1,ID_MENUITEM4,OnUpdateMenuItem CommandRange) //界面消息映射 END_MESSAGE_MAP()
(3)在菜单项组的消息响应函数中,根据菜单ID的不同,执行不同的处理方式。代码如下:
void CCommandRangeDemoView::OnMenuItemCommandRange(UINT nID) { switch(nID) { case ID_MENUITEM1: m_radio=1; AfxMessageBox("命令1菜单项执行成功!"); break; case ID_MENUITEM2: m_radio=2; AfxMessageBox("命令2菜单项执行成功!"); break; case ID_MENUITEM3: m_radio=3; AfxMessageBox("命令3菜单项执行成功!"); break; case ID_MENUITEM4: m_radio=4; AfxMessageBox("命令4菜单项执行成功!"); break; } } void CCommandRangeDemoView::OnUpdateMenuItemCommandRange(CCmdUI* pCCmdUI) { switch(pCCmdUI->m_nID) { case ID_MENUITEM1: pCCmdUI->SetRadio(m_radio==1); break; case ID_MENUITEM2: pCCmdUI->SetRadio(m_radio==2); break; case ID_MENUITEM3: pCCmdUI->SetRadio(m_radio==3); break; case ID_MENUITEM4: pCCmdUI->SetRadio(m_radio==4); break; } }
❑要点说明
命令范围宏一般用来处理一组命令消息,这些消息处理的差别只在于被调用的命令的不同。这些宏可以大大简化类,否则类中就需要很多调用相同函数的多个消息处理函数。
实例37:使用定时器实时显示当前时间
❑实例说明
定时器(Timer)是Windows应用程序的一个有用的工具,它的主要用途是按程序员设定定时产生WM_TIMER消息,程序中可以通过处理WM_TIMER消息完成定时任务。本实例将创建一个定时器,实现每秒触发一次,在客户窗口中实时显示当前的时间。实例的运行界面如图2.17所示。
图2.17 程序的运行界面
❑核心开发过程(1)使用ClassWizard为视图类添加WM_CREATE消息响应函数,在其中设置定时器。代码如下:
int CTimerDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here SetTimer(2,1000,NULL); //设置定时器,每秒触发一次 return 0; }
(2)使用ClassWizard为视图类添加WM_TIMER消息响应函数,在其中实现显示当前的系统时间。代码如下:
void CTimerDemoView::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default if(nIDEvent==2) { CClientDC dc(this); //获得DC CFont myfont; CFont*oldfont; LOGFONT font; memset(&font,0,sizeof(LOGFONT)); //为字体结构赋初值,默认值 font.lfHeight=50; //字体高度为50 font.lfWeight=600; strcpy(font.lfFaceName,"Arial"); myfont.CreateFontIndirect(&font); //创建新字体 oldfont=dc.SelectObject(&myfont); //将新字体选入设备环境*/ CTime m_time; m_time=CTime::GetCurrentTime(); //获取当前时间日期 CString str=m_time.Format("当前时间:%H:%M:%S"); //格式化 dc.TextOut(40,70,str); //显示时间 dc.SelectObject(oldfont); //恢复设备环境中的旧字体 } CView::OnTimer(nIDEvent); }
(3)使用ClassWizard为视图类添加WM_DESTROY消息响应函数,实现销毁定时器。代码如下:
void CTimerDemoView::OnDestroy() { CView::OnDestroy(); // TODO: Add your message handler code here KillTimer(2); //销毁定时器 }
❑要点说明
用户可以通过SetTimer函数设置多个时钟,当时间到时,系统就产生WM_TIMER消息,并通过参数告诉用户哪个时钟的时间到了。使用这种方式,用户进行周期性的处理工作非常方便。
SetTimer函数为API函数,在程序的任何位置都可以直接调用。如下面的调用:
SetTimer(5,4000,NULL)
表明计时器的ID号为5,间隔4000ms,即系统每隔4秒自动发送WM_TIMER消息。
计时器属于系统资源,使用完应及时销毁。调用KillTimer函数后,消息队列中所有未处理的WM_TIMER消息都将被消除。如销毁上面创建的计时器,可采用下面的代码:
KillTimer(5);
在MFC Class Wizard中,封装了WM_TIMER消息,程序员可以使用MFC Class Wizard对话框为应用程序添加WM_TIMER消息的处理函数OnTimer。
实例38:使用定时器显示毫秒级的时间
❑实例说明
使用SetTimer函数可以设置精确到毫秒的时间控制,然而CTime::GetCurrentTime()函数只能获取精确到秒的系统时间。本实例将使用_timeb结构和_ftime函数实现毫秒级的系统时间,并实时显示。实例的运行界面如图2.18所示。
图2.18 程序的运行界面
❑核心开发过程
(1)在对话框的初始化函数OnInitDialog()中,设置定时器,每毫秒触发一次。代码如下:
BOOL CMilliSecondDemoDlg::OnInitDialog() { CDialog::OnInitDialog(); ... // TODO: Add extra initialization here SetTimer(1,1,NULL); //设置定时器,每毫秒触发一次 return TRUE; // return TRUE unless you set the focus to a control }
(2)使用ClassWizard为对话框添加WM_TIMER消息响应函数,实现显示当前的系统时间。代码如下:
void CMilliSecondDemoDlg::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default struct _timeb timebuffer; char *timeline; //获得毫秒级的时间 _ftime( &timebuffer ); timeline = ctime(&(timebuffer.time)); //格式化时间 m_strTime.Format("当前时间是:%.19s.%hu %s",timeline,timebuffer.millitm, &timeline[20]); UpdateData(FALSE); //显示时间 CDialog::OnTimer(nIDEvent); }
要使用_ftime函数,需要包含如下的头文件:
#include <sys/timeb.h> #include <time.h>
❑要点说明
_ftime函数获取的系统时间信息存储在_timeb结构体中。_timeb结构体中的time变量记录了系统时间的秒级信息,而millitm变量则记录了毫秒级的信息。