第9单元 WINDOWS编程教学目标介绍WINDOWS编程的基本思想和MFC程序的基本结构。
学习要求理解WINDOWS的基本编程思想,特别是其消息传递机制,弄清MFC程序的组成及各部分的作用。
授课内容
Microsoft Windows是广泛应用的台式机计算机操作系统,具有图形用户界面和多任务、多窗口等特点。目前Windows已成为微机上的主流操作系统,几乎一统天下,在Windows平台上进行软件开发也已成为程序设计的主流。
9.1 WINDOWS编程的基本思想
键盘消息
鼠标消息
其他消息
图9-1 事件驱动原理
Windows编程使用事件驱动的程序设计思想。在事件驱动的程序结构中,程序的控制流程不再由事件的预定发生顺序来决定,而是由实际运行时各种事件的实际发生来触发,而事件的发生可能是随机的、不确定的,并没有预定的顺序。事件驱动的程序允许用户用各种合理的顺序来安排程序的流程。对于需要用户交互的应用程序来说,事件驱动的程序设计有着传统程序设计方法无法替代的优点。事件驱动是一种面向用户的程序设计方法,在程序设计过程中除了完成所需要的程序功能之外,更多的考虑了用户可能的各种输入(消息),并有针对性地设计相应的处理程序。事件驱动程序设计也是一种“被动”式的程序设计方法,程序开始运行时,处于等待消息状态,然后取得消息并对其作出相应反应,处理完毕后又返回处于等待消息的状态。使用事件驱动原理的程序的工作流程如图9-1所示。
事件驱动围绕着消息的产生与处理展开,事件驱动是靠消息循环机制来实现的。消息是一种报告有关事件发生的通知。Windows应用程序的消息来源有以下四种:
(1)输入消息:包括键盘和鼠标的输入。这一类消息首先放在系统消息队列中,然后由Windows将它们送入应用程序消息队列中,由应用程序来处理消息。
(2)控制消息:用来与Windows的控制对象,如列表框、按钮、检查框等进行双向通信。当用户在列表框中改动当前选择或改变了检查框的状态时发出此类消息。这类消息一般不经过应用程序消息队列,而是直接发送到控制对象上去。
(3)系统消息:对程序化的事件或系统时钟中断作出反应。一些系统消息,象DDE消息(动态数据交换消息)要通过Windows的系统消息队列,而有的则不通过系统消息队列而直接送入应用程序的消息队列,如创建窗口消息。
(4)用户消息:这是程序员自己定义并在应用程序中主动发出的,一般由应用程序的某一部分内部处理。
9.2 MFC编程
Microsoft提供了一个基础类库MFC(Microsoft Foundation Class),其中包含用来开发C++应用程序和Windows应用程序的一组类。这些类用来表示窗口、对话框、设备上下文、公共GDI对象如画笔、调色板、控制框和其他标准的Windows部件,封装了大部分的Windows API(Application Programming Interface:应用程序接口)。使用MFC,可以大大简化Windows编程工作。
MFC中的类可分为两种:CObject类的派生类及非CObject派生类。非CObject派生类数量不多,但大都很常用。几个常用的非CObject派生类如CTime,CTimeSpan,CString和CFile已分别在第7单元和第8单元中介绍过。
CObject派生类的基本特征为:支持序列化(Serialize,应用见第13单元)、运行时类信息访问(Dynamic,应用见第12单元)、对象诊断输出(参看10.6.4:“CObject::Dump()成员函数”)和与集合类兼容(参看12.7:“集合类”)等。
MFC将Windows应用程序从开始运行、消息传递到结束运行所需的各步骤均封装在CWinApp类中,CWinApp类表示MFC应用程序的应用对象。CWinApp类从CObject类的子类CWinThread类(定义MFC内的线程行为)派生。一个MFC应用程序必须有且只能有一个从WinApp类派生的全局应用程序对象,此对象在运行时刻控制应用程序中所有其他对象的活动。
典型的Windows应用程序结构有以下四种:
控制台应用程序:在本教程第1单元到第8单元介绍的所有程序均为控制台应用程序。控制台应用程序结构简单,可以不使用MFC类库。
基于框架窗口的应用程序:某些应用程序仅需最小的用户界面和简单的窗口结构,这时可使用基于框架窗口的方案。在此方案中,主应用程序窗口为框架窗口,CFrameWnd派生类对象附属于应用程序的CWinApp派生类对象的m_pMainWnd成员。第9单元到第11单元的例题程序均为基于框架窗口的程序。
基于对话框的应用程序:基于对话框的应用程序与基于框架窗口的应用程序差别不大,只是用CDialog派生类对象代替了CFrameWnd派生类对象作为应用程序的主窗口。基于对话框的应用程序框架可由Visual C++的应用向导自动生成,非常方便。第15单元介绍了基于对话框的应用程序。
基于文档/视图结构的应用程序:文档/视图应用具有较复杂的结构,当然其功能也相应增强。基于文档/视图结构的应用程序又可分为单文档界面(SDI,在第12单元介绍)和多文档界面(MDI,在第16单元介绍),后者更复杂些。
MFC类的结构大都比较复杂,可能包含数十个至数百个成员函数,加上层次相当多的继承关系,很难把握。MFC程序的结构也因此变得难以详细分析。
学习MFC,最重要的一点是要学会抽象地把握问题,不求甚解。不要一开始学习Visual C++就试图了解整个MFC类库,实际上那几乎是不可能的。一般的学习方法是,先大体上对MFC有个了解,知道它的概念、组成等之后,从较简单的类入手,由浅入深,循序渐进、日积月累的学习。一开始使用MFC提供的类时,只需要知道它的一些常用的方法、外部接口,不必要去了解它的细节和内部实现,把它当做一个模块或者说黑盒子来用,这就是一种抽象的学习方法。在学到一定程度时,再深入研究,采用继承的方法对原有的类的行为进行修改和扩充,派生出自己所需的类。在研究MFC的类时,要充分利用MSDN内的帮助信息。
学习MFC,很重要的一点是理解MFC应用程序的框架结构,而不是强迫记忆大量的类成员、方法及其参数等细节。
[例9-1] 吹泡泡程序。每当用户在窗口客户区中按下鼠标左键时即可产生一个泡泡(灰色圆形)。
设计思想:显示一个泡泡所需的数据包括其位置和大小,在MFC中可用其包含矩形表示。可设置一数组,每当用户按下鼠标左键时,就产生一个泡泡的数据存入数组中,再由框架窗口类的OnDraw()函数显示所有的泡泡。
说 明:参考9.8:“用Visual C++集成开发环境开发Win32应用程序”建立该项目。
程 序:
// Example 9-1:吹泡泡程序
#include <afxwin.h>
// 框架窗口类
#define MAX_BUBBLE 250
class CMyWnd,public CFrameWnd
{
CRect m_rectBubble[MAX_BUBBLE];
int m_nBubbleCount;
public:
CMyWnd(){m_nBubbleCount = 0;}
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
void CMyWnd::OnLButtonDown(UINT nFlags,CPoint point)
{
if(m_nBubbleCount < MAX_BUBBLE)
{
int r = rand()%50+10;
CRect rect(point.x-r,point.y-r,point.x+r,point.y+r);
m_rectBubble[m_nBubbleCount] = rect;
m_nBubbleCount++;
InvalidateRect(rect,FALSE);
}
}
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
dc.SelectStockObject(LTGRAY_BRUSH);
for(int i=0; i<m_nBubbleCount; i++)
dc.Ellipse(m_rectBubble[i]);
}
// 应用程序类
class CMyApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 应用程序类的成员函数
BOOL CMyApp::InitInstance()
{
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("吹泡泡"));
pFrame->ShowWindow(m_nCmdShow);
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CMyApp ThisApp;
图9-2 吹泡泡程序输 出:每在窗口客户区按一下鼠标左键,可绘出一个灰色泡泡,其大小是随机确定的。吹泡泡程序的运行情况见图9-2。
分 析:尽管这是一个非常简单的MFC程序,却也有几十行代码。这是因为Window应用程序要管理远比字符界面(如本教程前面所用的控制台界面)复杂的图形用户接口,而且要有多任务并行处理的能力。但是,随着程序规模的增大,用MFC编程的优点很快会显露:由于结构清晰,各部分功能明确,代码规范,编程和调试的工作量大为减少。
该程序声明了两个类,一个是由应用程序类CWinApp中派生出来的CMyApp类,一个是从框架窗口CFrameWnd类派生出来的CMyWnd类。MFC的基本类名均以字母C打头,习惯上在为使用MFC编写的应用程序中的类起名时也这么做。
除此而外,在程序中还声明了一个CMyApp类的全局对象ThisApp。
仔细阅读程序还会发现,该程序似乎不完整,其中既没有主函数(在一般的Windows程序中应为WinMain()函数),也没有实现消息循环的程序段。然而,这是一种误解,因为MFC已经把它们封装起来了。在程序运行时,MFC应用程序首先调用由框架提供的标准的WinMain()函数。在WinMain()函数中,先初始化由CMyApp定义的唯一全局对象ThisApp(通过重载的虚函数InitInstance(),它构造并显示应用程序的主窗口),然后调用其由CWinApp类继承的Run()成员函数,进入消息循环。程序结束时时调用CWinApp的ExitInstance()函数退出。
因此,应用程序框架不仅提供了构建应用程序所需要的类(CWinApp,CFrameWnd等),还规定了程序的基本执行结构。所有的应用程序都在这个基本结构的基础上完成不同的功能。
MFC采用消息映射机制来决定如何处理特定的消息。这种消息映射机制包括一组宏,用于标识消息处理函数、映射类成员函数和对应的消息等。在类CMyWnd的声明中,前面有afx_msg标记的成员函数就是消息处理成员函数。如果在程序中用到了消息处理函数,那么还需对程序执行部分所定义的消息映射进行初始化,这项工作是通过消息映射宏完成的。
消息映射宏就是程序中从BEGIN_MESSAGE_MAP()到END_MESSAGE_MAP()的程序段。BEGIN_MESSAGE_MAP()宏包含两个参数CMyWnd和CFrameWnd,分别是当前定义的窗口类及其父类的名称。在BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()之间,含有该窗口类要处理的各种Windows消息的入口。MFC包含了大量的预定义消息映射宏,用来指定各种成员函数与各种形如WM_××××的消息相对应,如ON_WM_LBUTTONDOWN宏指定WM_LBUTTONDOWN消息的处理成员函数为OnLButtonDown()。这时,只需要写出要处理的消息就够了,不必再写出处理函数名。
我们在CMyWnd类中声明了一个数组成员m_rectBubble,用于存放泡泡的数据(这里是圆的包含矩形)。另外还声明了一个整型数据成员m_nBubbleCount,用来存放数组中泡泡的实际数量。在框架窗口类的构造函数中该成员变量被初始化为0。由于构造函数非常简单,所以使用了内联函数形式。
OnPaint()函数根据数组m_rectBubble的内容画出一个个泡泡。OnPaint()函数在窗口生成、大小改变或覆盖在其上的其他窗口被移开时被自动调用。其中语句
dc.SelectStockObject(LTGRAY_BRUSH);
用于选择一个库存画刷,画刷决定了所画图形(如椭圆、矩形和多边形等)内部的颜色。(参看10.5:“库存图形对象”)。
在处理鼠标消息的OnLButtonDown()函数中,语句
int r = rand()%50+10;
随机确定了要画出的泡泡半径(范围为10~50),其中全局函数rand()可产生一个随机整数。而语句
CRect rect(point.x-r,point.y-r,point.x+r,point.y+r);
m_rectBubble[m_nBubbleCount] = rect;
m_nBubbleCount++;
使用鼠标位置point和半径r构造一个泡泡的包含矩形并将其存入数组m_rectBubble。语句
InvalidateRect(rect,FALSE);
用于通知OnPaint()函数更新窗口客户区指定区域的内容。我们记得,OnPaint()函数只在窗口生成、大小改变或覆盖其上的其他对象移动时调用,因此在数据发生变动(如增加泡泡)时则需用CWnd类的成员函数InvalidateRect()函数通知OnPaint()函数重画窗口客户区中的部分区域。
9.3 在窗口的客户区输出文字和图形例9-1中的OnPaint()函数用于绘制客户区的内容。要完成这项任务,需要先建立一个设备环境(Device Context),这可以通过声明一个CPaintDC类的对象dc实现。在声明语句中,应将当前窗口对象指针this传给CPaintDC类的构造函数,把绘制区域确定为当前窗口的客户区。Windows 在窗口更新、移动、改变尺寸或移去覆盖在其上的其他窗口对象时均会向该窗口发送WM_PAINT消息,从而触发应用程序调用OnPaint()函数重绘窗口客户区。
CPaintDC类中封装了大量的绘图和文字输出方法(成员函数),如:
文字信息显示
BOOL TextOut(int x,int y,LPCTSTR lpszString);
在指定坐标(x,y)处显示字符串lpszString的内容,显示成功返回非0值,否则返回0。坐标原点(0,0)在客户区左上角,Y轴向下。下面各成员函数的坐标参数均同此。参数类型LPCTSTR和返回值类型BOOL均为Windows的数据类型,前者意为常量字符指针,后者为逻辑类型。下面的COLORREF,POINT,LPPOINT,LPCRECT等均类此。关于这些Windows类型,请参看9.7:“Windows数据类型与变量的命名规则”。
画点
COLORREF SetPixel (int x,int y,COLORREF color);
COLORREF SetPixel (POINT point,COLORREF color);
该函数在指定坐标(用参数x,y或点point给出)处按给定颜色(color)画点,返回值为原来此坐标处的颜色。
取指定坐标点的颜色
COLORREF GetPixel ( int x,int y ) const;
COLORREF GetPixel ( POINT point ) const;
返回值为指定坐标处的颜色。
画线画线工作需经两步完成:首先确定线的起始端位置,这可通过调用成员函数MoveTo完成,其原型为:
CPoint MoveTo ( int x,int y );
CPoint MoveTo ( POINT point );
MoveTo将绘图位置(“看不见”)移至指定坐标处,并返回移动前的绘图位置。确定了线的起点后,即可使用成员函数LineTo画线:
BOOL LineTo ( int x,int y );
BOOL LineTo ( POINT point );
其参数为线终点的坐标。
绘制矩形绘制矩形的成员函数为:
BOOL Rectangle ( int x1,int y1,int x2,int y2 );
BOOL Rectangle ( LPCRECT lpRect );
其参数为需要绘制的矩形的左上角坐标(x1,y1)和右下角坐标(x2,y2)。
(x1,y1)
(x2,y2)
图9-3 椭圆的包含矩形
6.绘制椭圆该成员函数的原型为:
BOOL Ellipse(int x1,int y1,int x2,int y2);
BOOL Ellipse(LPCRECT lpRect);
其参数的含义为所绘椭圆的包含矩形的左上角和右下角坐标,如图9-3所示。
画多边形
BOOL Polygon ( LPPOINT lpPoints,int nCount );
其中参数lpPoints为一LPPOINT类型的指针,可用CPoint数组(存放多边形的各顶点坐标)作为实参。参数nCount为顶点个数。例如
CPaintDC dc(this);
CPoint pointPoly[3];
pointPoly[0] = CPoint(100,100);
pointPoly[1] = CPoint(200,100);
pointPoly[2] = CPoint(200,200);
dc.Polygon(pointPoly,3);
在窗口客户区相应位置画出一个三角形。
其他绘图函数还有画弧Arc()、画弓形Chord()、画扇型Pie()和画圆角矩形InvertRect()等,具体使用方法可参看MSDN联机帮助。
获取客户区的坐标为了某些绘图效果,可能需要知道框架窗口客户区当前的大小。Wnd类的成员函数GetClientRect()可用于该目的。其调用方法为:
void GetClientRect( LPRECT lpRect );
[例9-2] 修改例9-1中的OnDraw()函数,使之可在客户区中央显示一矩形框,并在其中显示文字信息“Hello,MFC!”。
说 明:建立项目的方法见9.8:“用Visual C++集成开发环境开发Win32应用程序”。
程 序:用下面的程序段替代例9-1中的OnPaint()函数。
// Example 9-2:在窗口客户区显示矩形框和文字
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
dc.SelectStockObject(LTGRAY_BRUSH);
for(int i=0; i<m_nBubbleCount; i++)
dc.Ellipse(m_rectBubble[i]);

CRect rectClient,rectTitle;
int nWidth = 100;
int nHeight = 40;
GetClientRect(&rectClient);
rectTitle.left = (rectClient.Width()-nWidth)/2;
rectTitle.top = (rectClient.Height()-nHeight)/2;
rectTitle.right = rectTitle.left+nWidth;
rectTitle.bottom = rectTitle.top+nHeight;
dc.SelectStockObject(WHITE_BRUSH);
dc.Rectangle(rectTitle);
dc.TextOut(rectTitle.left+10,rectTitle.top+10,"Hello,MFC!");
}
输入输出:与例9-1类似,只是在窗口中央显示了一个矩形框,框中显示相应文字,见图9-4。
图9-4 新吹泡泡程序
分 析:该程序系在例9-1的程序基础上改编。可以看出,这两个程序不但结构相同,而且大部分代码也完全一样。这是MFC编程的一个显著特点,即可通过修改一个通用程序框架来快速构造自己的程序。
自学内容
9.4 WINDOWS的用户界面对象
Windows支持丰富的用户界面对象,包括窗口、图标、菜单、对话框等等。程序员只需简单的几十行代码,就可以设计出一个非常漂亮的图形用户界面。下面我们介绍几个常用的用户界面对象的术语和相关概念。
9.4.1 窗口
图9-5 典型窗口窗口是用户界面中最重要的部分,是屏幕上与一个应用程序相对应的矩形区域,是用户与产生该窗口的应用程序之间的可视界面。每当用户开始运行一个应用程序时,应用程序就创建并显示一个窗口;当用户操作窗口中的对象时,程序会作出相应反应。用户通过关闭一个窗口来终止一个程序的运行;通过选择相应的应用程序窗口来选择相应的应用程序。一个典型的窗口外观如图9-5所示。
绝大多数窗口都有一个边框,用于指示窗口的边界,同时也用来指明该窗口是否为活动窗口,当窗口活动时,边框的标题栏部分呈高亮显示。用户可以用鼠标拖动边框来调整窗口的大小。在程序设计时,称窗口活动为获得焦点,非活动窗口为失去焦点。
9.4.2 系统菜单系统菜单图标位于窗口左上角,用鼠标点一下该图标(或按ALT+空格键)就可弹出系统菜单。系统菜单提供标准的应用程序选项,包括Restore(还原窗口原有的大小),Move(使窗口可以通过键盘上的光标键来移动其位置),Size(使用光标键调整窗口大小),Minimize(将窗口缩成图标),Maximize(最大化:使窗口充满整个屏幕)和Close(关闭窗口)。
9.4.3 标题栏标题栏位于窗口的顶部,其中显示的文本信息用于标注应用程序,一般是应用程序的名字,以便让用户了解哪个应用程序正在运行。标题栏颜色反映该窗口是否是一个活动窗口(活动窗口的标题栏呈醒目颜色)。鼠标双击标题栏可使窗口在正常大小和最大化状态之间切换。在标题栏上按下鼠标器左键可以拖动并移动该窗口,按右键弹出系统菜单。
9.4.4菜单栏菜单栏位于标题栏下方,横跨窗口,上面列出了应用程序所支持的命令。菜单栏中的项目一般是一类命令或操作的通称,如文件操作、编辑操作和帮助等。从菜单栏中选中某一项通常会显示一个弹出菜单,其中的项是对应于指定命令或类中的某个任务。通过选择菜单中的一个项(菜单项),用户可以向程序发出命令,以执行某一功能。如选择“文件操作”菜单的“打开...”项会弹出一个打开文件对话框,让用户选择一个文件,然后打开这个文件。
一般来说,以“...”结尾的菜单项表明选择该项时会弹出一个对话框,让用户输入信息,然后执行操作,如“打开...”。如果不以“...”结尾,则表明选择该菜单项直接执行一个动作,如“编辑”菜单下的“粘贴”菜单项。如果一个菜单项呈现灰色,则表明该菜单当前不可用。有时菜单项上还有加速键,加速键是一种键盘组合,它是菜单项的一种替代方式,可让用户通过键盘直接发出命令;在键盘上按下这一键盘组合,就等于选择了相应的菜单。如“粘贴(P) CTRL+V”,就表示粘贴操作的加速键是CTRL+V,按下CTRL+V也执行粘贴操作。
9.4.5 工具条工具条一般位于菜单栏下方,上面有一组位图按钮,代表一些最常用的命令。工具条可以显示或隐藏。让鼠标在某个按钮上停一会儿,在按钮下方会出现一个黄色的小窗口,里面显示关于该按钮的简短说明,叫做工具条提示(ToolTip)。用户还可以用鼠标拖动工具条将其放在窗口的任何一侧。
9.4.6 客户区客户区是窗口中最大的一块空白矩形区域,用于显示应用程序的输出。例如,字处理程序在客户区中显示文档的当前页面。应用程序负责客户区的绘制工作,且只有和该窗口相对应的应用程序才能向该客户区输出。
9.4.7 垂直滚动条和水平滚动条垂直滚动条和水平滚动条分别位于客户区的左侧和底部,它们各有两个方向相反的箭头和一个深色的长度可变的滚动块。可以用鼠标选中滚动条的箭头上下卷滚(选中垂直滚动条时)或水平卷滚(选中水平滚动条时)客户区的内容。滚动块的位置表示客户区中显示的内容相对于要显示的全部内容的位置,滚动块的长度表示客户区中显示的内容大小相对于全部内容大小的比例。
9.4.8 状态栏状态栏一般位于窗口底部,用于输出菜单的说明和其他一些提示信息(如鼠标器位置、当前时间、某种状态等)。
9.4.9 图标图标是一个用于提醒用户的符号,它是一个小小的图象,用于代表一个应用程序。当一个应用程序的主窗口缩至最小时,就呈现为一个图标。
9.4.10 光标
Windows的光标是显示屏上的一个位图(通常为一白色箭头),用于响应鼠标或其他定位设备的移动。程序可以通过改变光标的形状来指出系统中的变化。例如,计时沙漏形的光标用于指示用户一些漫长的操作正在进行之中;绘图程序经常改变光标来反映被绘制对象的类型,是直线还是圆或其他。
9.4.11 插入符插入符(caret)是一个很小并闪烁的位图(通常为黑色短粗竖线),作为键盘位置指示。控制键盘输入的窗口可以创建一个插入符去通知用户窗口现在可以进行键盘输入。注意插入符和光标不同,光标通常只用作指示鼠标位置。应用程序必须维护插入符。在Windows中,在同一时间只允许有一个插入符存在。因此,要使用插入符号作为键盘指针的应用程序必须在取得焦点时创建一个插入符号,并在失去焦点后删除它。
9.4.12 对话框对话框是一种特殊的窗口,它提供了一种接收用户输入、处理数据的标准方法。例如,当用户要求在被编辑的文本中查找指定内容时,可以使用文本查找对话框就(见图9-5)。如前所述,在一般情况下,在选择菜单名字后面跟着省略号(...)的菜单项通常会弹出一个对话框。
9.4.13 控件图9-4中的文件选择对话框是一个独立的窗口,它显示信息并接收用户的输入。在该对话框中,还包含了许多小的窗口,这些窗口被称为控件。控件是应用程序用来获得用户特定信息的窗口,例如要打开文件的名字等。应用程序也会通过控件获取所需的信息,以便控制程序的某种属性。
控件多与对话框连用,但也可以用在普通窗口之中。常见的控件有:按钮、编辑框、列表框、组合框、静态文本等等。
9.5鼠标消息处理无论何时移动鼠标或点击鼠标按键,Windows便产生一个或多个消息并将其发送给位于鼠标光标下的窗口。编程时常用的鼠标消息有:
WM_LBUTTONDOWN 按下鼠标左键
WM_LBUTTONUP 释放鼠标左键
WM_LBUTTONDBLCLK 双击鼠标左键
WM_RBUTTONDOWN 按下鼠标右键
WM_RBUTTONUP 释放鼠标右键
WM_RBUTTONDBLCLK 双击鼠标右键
WM_MOUSEMOVE 移动鼠标对应的Wnd类的消息处理成员函数为:
void OnLButtonDown (UINT nFlags,CPoint point);
void OnLButtonUp (UINT nFlags,CPoint point);
void OnLButtonDblClk (UINT nFlags,CPoint point);
void OnRButtonDown (UINT nFlags,CPoint point);
void OnRButtonUp (UINT nFlags,CPoint point);
void OnRButtonDblClk (UINT nFlags,CPoint point);
void OnMouseMove ( UINT nFlags,CPoint point );
其中参数point表示鼠标的位置,nFlags是几个控制键的状态,可以是下列值及其组合:
MK_CONTROL CTRL键被按下
MK_LBUTTON 鼠标左键被按下
MK_RBUTTON 鼠标右键被按下
MK_SHIFT SHIFT键被按下例如,“MK_SHIFT|MK_LBUTTON”表示同时按下了SHIFT键和鼠标左键。
通过重载上述消息处理成员函数,应用程序可对各种鼠标行为编程。
9.6 Windows数据类型与变量的命名规则
Windows API自行定义了一些关键字,用来定义Windows中函数中的有关参数和返回值的大小和意义,通常将它们看作Windows的数据类型。其中较常用的有:
关键字 类型 说明
BOOL 逻辑类型 等价于int
BOOLEAN 逻辑类型 等价于BYTE
BYTE 字节 等价于unsigned char
CHAR 字符 等价于char
DOUBLE 双精度 等价于double
DWORD 双字 等价于unsigned long
FLOAT 浮点数 等价于float
HANDLE 句柄 等价于void
INT 整数 等价于int
LONG 长整数 等价于long
SHORT 短整数 等价于short
UCHAR 无符号字符 等价于unsigned char
UINT 无符号整数 等价于unsigned int
ULONG 无符号长整数 等价于unsigned long
USHORT 无符号短整数 等价于unsigned short
VOID 空的、无定义 等价于void
WCHAR 双字节码 等价于unsigned short
wchar_t 双字节码 等价于unsigned short
WORD 字 等价于unsigned short
WPARAM 消息参数 等价于UINT
LPARAM 消息参数 等价于LONG
LRESULT 消息返回值 等价于LONG
HINSTANCE 实例句柄 等价于unsigned long
HWND 窗口句柄 等价于unsigned long
HDC 设备环境句柄 等价于unsigned long
TCHAR 字符 等价于char
LPSTR 字符指针 等价于char *
LPCSTR 常量字符指针 等价于const char *
LPTSTR 字符指针 等价于TCHAR *
LPCTSTR 常量字符指针 等价于const TCHAR *
LPVOID 无类型指针 等价于void *
LPCVOID 无类型常量指针 等价于const void *
其中句柄(handle)是Windows编程的一个关键性的概念,编写Windows应用程序总是要和各种句柄打交道。所谓句柄,就是一个4字节长的唯一的数,用以标识许多不同的对象类型,如窗口、菜单、内存、画笔、画刷、电话线路等。
由于Windows是一个多任务操作系统,它可以同时运行多个程序或一个程序的多个副本。这些运行的程序称为一个实例。为了对同一程序的多个副本进行管理,Windows引入了实例句柄。Windows为每个应用程序建立一张表,实例句柄就好象是这张表的一个索引。
Windows不仅使用句柄来管理实例,也用它来管理窗口、位图、字体、元文件、图标等系统资源。
Windows NT系统支持Unicode。所谓Unicode,即统一双字节内码。我们知道,计算机中的西文信息使用单字节内码(8位2进制码,即ASCII码)。为了处理象中文这样的大字符集,国内、台湾和日本等使用汉字的国家各自定义了不同的双字节内码标准,如大陆的GB码、台湾的BIG5码和日本的JIS码等。但不同的内码系统不能通用,例如以BIG5码输入的文字在GB码系统下不可辨认。因此,国际标准化组织提出了通用的大字符集标准,这就是Unicode码。为了在应用程序中使用Unicode码,就必须使用wchar_t或WCHAR代替char和CHAR类型处理字符。但因目前仅Windows NT支持Unicode,所以为了使所开发的应用程序具有一定的通用性,可用TCHAR类型代替CHAR或WCHAR类型。TCHAR类型可根据需要自动定义为CHAR或WCHAR类型。
除了前面介绍的基本数据类型外,在使用MFC编程时还会遇到下面这些常用的重要的数据类型。
代表坐标的结构体类型POINT和CPoint类:
typedef struct tagPOINT
{
LONG x;
LONG y;
}POINT;
类型LPPOINT为指向POINT类型的指针,等价于POINT *。在MFC中还有与POINT类型对应的类CPoint,其中除了数据成员x和y外,还提供了一些重载的运算符,如“==”(相等)、“!=”(不等)、“+”(加,可与下文的SIZE类型联用)和“(”(减,可与下文的SIZE类型联用)等。
表示长、宽尺寸的结构体类型SIZE和CSize类:
typedef struct tagSIZE
{
int cx;
int cy;
}SIZE;
在MFC中有与SIZE类型对应的类CSize。与CPoint类相似,CSize类也有一批重载的运算符,便于使用。
记录矩形区域的结构体类型RECT和CRect类:
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
}RECT;
类型LPRECT为指向RECT类型的常数指针,等价于const RECT*。在MFC中有与之相应的类CRect。CRect类除了具有重载的运算符外,还有一些成员函数,如Width()(矩形宽),Height()(矩形高),PtInRect()(测试一个点是否在矩形范围中)等。
表示颜色的类型COLORREF
COLORREF实际上是一个32位整数类型,用于表示某种颜色,其第0,1,2字节分别用于存放该颜色的红、绿和蓝色分量。如果已知某颜色的3个分量,则可使用宏RGB()构造出该颜色:
COLORREF RGB(BYTE bRed,BYTE bGreen,BYTE bBlue);
例如RGB(0,0,0)为黑色,RGB(255,255,255)为白色。
在编程时,变量、函数的命名是一个极其重要的问题。好的命名方法使变量易于记忆且程序可读性大大提高。Microsoft采用匈牙利(Hungarian)命名法来命名Windows API函数和变量。
匈牙利命名法为C++标识符的命名定义了一种非常标准化的方式,这种命名方式是以两条规则为基础的:
1.标识符的名字以一个或者多个小写字母开头,用这些字母来指定数据类型。下表列出了常用的数据类型的标准前缀:
c 字符(char)
s 短整数(short)
cb 用于定义对象(一般为一个结构)尺寸的整数
n或i 整数(integer)
sz 以0结尾的字符串
b 字节
x 短整数(坐标x)
y 短整数(坐标y)
f BOOL
w 字(WORD,无符号短整数)
l 长整数(long)
h HANDLE(无符号int)
m_ 类成员变量
fn 函数(function)
dw 双字(DWORD,无符号长整数)
2.在标识符内,前缀以后就是一个或者多个第一个字母大写的单词,这些单词清楚地指出了源程序中那个对象的用途。例如,m_szPersonName表示一个人名的类成员变量,数据类型是字符串型。
编程与调试
9.7 用Visual C++集成开发环境开发Win32应用程序基于Windows窗口的应用程序的创建方法与基于控制台的应用程序(第1单元至第8单元中的程序)不同。如果是基于框架窗口的应用程序(第9单元至第11单元中的程序均如此),请使用Visual C++集成开发环境中的菜单选项“File/New…”,并在项目(Projects)选项卡中选择“Win32 Application”,并指定项目名称(Project name)和路径“Location”来生成相应的项目。在弹出的对话框“Win32 Application – Step 1 of 1”中选择生成一个空项目(An empty project)并按下“Finish”按钮。
再次使用菜单选项“File/New…”,在“Files”选项卡中选择“C++ Source File”,指定文件名(File,可与项目同名)和路径(Location,通常就用缺省路径)为项目添加一个空白的源代码文件(文件名后缀为.CPP)。
在源代码文件中输入程序后可进行编译、连接和运行等步骤。
在编译时,还要确定应用程序的可执行程序如何使用MFC的类库。一种方法是使用共享的动态链接库(DLL)。这种链接方式显著地减小了应用程序的可执行文件的大小,并能更有效地利用系统资源。
然而,动态链接到MFC要求提供Mfcnn.dll库文件,文件名中的nn代表MFC的版本号。该文件通常是在Windows System或System32文件夹下。如果一个应用程序动态链接到MFC,但该应用程序通常是用在那些可能没有Mfcnn.dll库的计算机系统上,则应把这个库文件作为应用程序包的一部分提供给用户。Microsoft允许程序员自由地把这些库文件附在应用程序中。应用程序的安装程序可以搜索用户硬盘上已有的MFC库文件,并把文件复制到System文件夹中(如果文件夹中还没有的话)。如果没有Msvcrt.dll文件,则还要复制该文件,因为MFC使用C运行库的共享版。
也可以选择将应用程序静态链接到MFC。静态链接意味着,应用程序不依赖于MFC库文件的存在(但仍然可能需要Msvcrt.dll文件)。静态链接的代价是可执行文件更大,而且,对内存的利用可能不够充分。
通过Developer Studio的菜单选项“project/Settings…”调出“Project settings”对话框,在对话框右方的“General”选项卡中通过组合框“Microsoft Foundation Classes”选择使用MFC类库的方法。可选项有三种,分别为“Not Using MFC(不使用MFC)”、“Use MFC in a Shared DLL(以动态链接库方式用MFC)”和“Use MFC in a Static Library(以静态库方法使用MFC)”,应在后两项中根据具体情况选用。
程序设计举例
[例9-3] 用键盘移动窗口客户区中的一个气球(椭圆)。
设计思想:在框架窗口的成员函数OnPaint()中可根据数据(如椭圆的包含矩形)绘图。因此,为了用键盘控制物体的移动,可重载框架窗口的键盘消息响应函数,并在其中根据按键情况修改被控物体的数据。
说 明:建立项目的方法见9.8:“用Visual C++集成开发环境开发Win32应用程序”。
程 序:
// Example 9-3:用键盘移动窗口客户区中的气球
#include <afxwin.h>
// 框架窗口类
class CMyWnd,public CFrameWnd
{
CRect m_rectBody;
public:
CMyWnd(){m_rectBody = CRect(100,100,150,180);}
protected:
afx_msg void OnKeyDown(UINT nChar,UINT nRepCnt,UINT nFlags);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// 消息映射
BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd)
ON_WM_KEYDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
// 框架窗口类的成员函数
void CMyWnd::OnKeyDown(UINT nChar,UINT nRepCnt,UINT nFlags )
{
CRect rectClient;
GetClientRect(&rectClient);
InvalidateRect(m_rectBody,TRUE);
switch(nChar)
{
case VK_UP:
if(m_rectBody.top>rectClient.top)
{
m_rectBody.top -= 5;
m_rectBody.bottom -= 5;
}
break;
case VK_DOWN:
if(m_rectBody.bottom<rectClient.bottom)
{
m_rectBody.top += 5;
m_rectBody.bottom += 5;
}
break;
case VK_LEFT:
if(m_rectBody.left>rectClient.left)
{
m_rectBody.left -= 5;
m_rectBody.right -= 5;
}
break;
case VK_RIGHT:
if(m_rectBody.right<rectClient.right)
{
m_rectBody.left += 5;
m_rectBody.right += 5;
}
break;
}
InvalidateRect(m_rectBody,FALSE);
}
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
dc.SelectStockObject(LTGRAY_BRUSH);
dc.Ellipse(m_rectBody);
}
// 应用程序类
class CMyApp,public CWinApp
{
public:
BOOL InitInstance();
};
// 应用程序类的成员函数
BOOL CMyApp::InitInstance()
{
CMyWnd *pFrame = new CMyWnd;
pFrame->Create(0,_T("Move Ellipse With Keyboard"));
pFrame->ShowWindow(m_nCmdShow);
pFrame->UpdateWindow();
this->m_pMainWnd = pFrame;
return TRUE;
}
// 全局应用程序对象
CMyApp ThisApp;
输 出:在窗口客户区显示一个气球,用上下左右方向键可以改变其位置。
分 析:本例依然使用例9-1的程序框架。在框架窗口派生类中说明了一个数据成员m_rectBody存放一个椭圆的包含矩形。为了设置该数据成员的初值,重载了框架窗口类的构造函数。
在框架窗口类中重载了消息响应函数OnKeyDown(),用于处理消息WM_KEYDOWN(按下按键)。该函数的原型为
afx_msg void OnKeyDown( UINT nChar,UINT nRepCnt,UINT nFlags );
其中参数nChar为用户按键代码。常用按键代码有:
代码 说明
VK_0 ~ VK_9 数字键0 ~ 9(不在数字键盘上)
VK_A ~ VK_Z 字母键A ~ Z
VK_BACK BACKSPACE键
VK_CONTROL CTRL键
VK_DELETE DELETE键
VK_DOWN 向下方向键
VK_END END键
VK_ESCAPE ESC键
VK_F1 ~ VK_F10 F1 ~ F10键
VK_HOME HOME键
VK_INSERT INSERT键
VK_LEFT 左方向键
VK_MENU ALT键
VK_NEXT PAGE DOWN键
VK_PRIOR PAGE UP键
VK_RIGHT 右方向键
VK_SHIFT 左右SHIFT键
VK_SNAPSHOT PRINT SCREEN键
VK_SPACE 空格键
VK_TAB TAB键
VK_UP 向上方向键参数nRepCnt为按键重复次数,nFlags为扫描码、转换键码和按键组合状态等。在编写消息处理部分程序时,要注意几个地方:一是框架窗口类中的消息函数说明;一是消息映射部分的消息映射宏;最后才是重载的消息映射函数的内容。在该映射函数中,我们仅对用户按下的4个方向键感兴趣,用以修改被控物体的位置。
函数GetClientRect()用于取窗口客户区的参数,防止被控物体移出窗口客户区。
由于被控物体的位置被修改,还要通知OnPaint()函数更新窗口客户区。应更新的区域有两个,一个是物体原来的位置,一个是物体的新位置。
一般来说,在窗口客户区移动一个物体的步骤为:
在框架窗口类中设置存放物体位置的数据成员并初始化;
通知OnPaint()函数更新物体原来位置的内容(擦除);
修改物体的位置参数;
通知OnPaint()函数更新物体新位置的内容;
和上例一样,OnPaint()函数根据数据成员m_rectBody在窗口客户区作图。
单元上机练习题目编一程序在窗口客户区绘制一幅风景画(应包括太阳、山、房屋等,只用黑、白灰等颜色即可)。
编写一个围棋(五子棋)程序。用鼠标左键下黑子,鼠标右键下白子;或仅用鼠标左键下子,在框架窗口派生类中添一个数据成员记录当前应下白子或黑子。此时应重载框架窗口类的构造函数,并在其中加上该数据成员的初始化代码。
利用本单元学到的知识(包括自学部分和程序设计举例部分的内容),自行设计一个游戏程序并实现。