9单元 WINDOWS编程 174 第9单元 WINDOWS编程 教学目标 介绍 WINDOWS编程的基本思想和MFC程序的基本结构。 学习要求 理解 WINDOWS的基本编程思想,特别是其消息传递机制,弄清MFC程序的组成及 各部分的作用。 授课内容 Microsoft windows是广泛应用的台式机计算机操作系统,具有图形用户界面和多任务、 多窗口等特点。目前 Windows已成为微机上的主流操作系统,几乎一统天下,在 Windows 平台上进行软件开发也已成为程序设计的主流 9 WINDOW编程的基本思想 Windows编程使用事件驱动的程序设计思想。在事件驱动的程序结构中,程序的控制 流程不再由事件的预定发生顺序来决定,而是由实际运行时各种事件的实际发生来触发,而 事件的发生可能是随机的、不确定的,并没有预定的顺序。事件驱动的程序允许用户用各种 合理的顺序来安排程序的流程。对于需要用户交互的应用程序来说,事件驱动的程序设计有 着传统程序设计方法无法替代的优点。事件驱动是一种面向用户的程序设计方法,在程序设 计过程中除了完成所需 要的程序功能之外,更 应用程序 多的考虑了用户可能的 各种输入(消息),并 键盘消息 有针对性地设计相应的鼠标消息 息 处理程序。事件驱动程 队 列 序设计也是一种“被动”其他消息 处理消息 式的程序设计方法,程 序开始运行时,处于等 待消息状态,然后取得 图9-1事件驱动原理 消息并对其作出相应反
第 9 单元 WINDOWS 编程 - 174 - 第 9 单元 WINDOWS 编程 教学目标 介绍 WINDOWS 编程的基本思想和 MFC 程序的基本结构。 学习要求 理解 WINDOWS 的基本编程思想,特别是其消息传递机制,弄清 MFC 程序的组成及 各部分的作用。 授课内容 Microsoft Windows 是广泛应用的台式机计算机操作系统,具有图形用户界面和多任务、 多窗口等特点。目前 Windows 已成为微机上的主流操作系统,几乎一统天下,在 Windows 平台上进行软件开发也已成为程序设计的主流。 9.1 WINDOWS 编程的基本思想 Windows 编程使用事件驱动的程序设计思想。在事件驱动的程序结构中,程序的控制 流程不再由事件的预定发生顺序来决定,而是由实际运行时各种事件的实际发生来触发,而 事件的发生可能是随机的、不确定的,并没有预定的顺序。事件驱动的程序允许用户用各种 合理的顺序来安排程序的流程。对于需要用户交互的应用程序来说,事件驱动的程序设计有 着传统程序设计方法无法替代的优点。事件驱动是一种面向用户的程序设计方法,在程序设 计过程中除了完成所需 要的程序功能之外,更 多的考虑了用户可能的 各种输入(消息),并 有针对性地设计相应的 处理程序。事件驱动程 序设计也是一种“被动” 式的程序设计方法,程 序开始运行时,处于等 待消息状态,然后取得 消息并对其作出相应反 键盘消息 鼠标消息 其他消息 图 9-1 事件驱动原理 应用程序 取 消 息 处理消息 Windows 消 息 队 列
9单元 WINDOWS编程 应,处理完毕后又返回处于等待消息的状态。使用事件驱动原理的程序的工作流程如图9-1 所示 事件驱动围绕着消息的产生与处理展开,事件驱动是靠消息循环机制来实现的。消息是 种报告有关事件发生的通知。 Windows应用程序的消息来源有以下四种 (1)输入消息:包括键盘和鼠标的输入。这一类消息首先放在系统消息队列中,然后 由 Windows将它们送入应用程序消息队列中,由应用程序来处理消息 (2)控制消息:用来与 Windows的控制对象,如列表框、按钮、检查框等进行双向通 信。当用户在列表框中改动当前选择或改变了检查框的状态时发出此类消息。这类消息一般 不经过应用程序消息队列,而是直接发送到控制对象上去 (3)系统消息:对程序化的事件或系统时钟中断作出反应。一些系统消息,象DDE 消息(动态数据交换消息)要通过 Windows的系统消息队列,而有的则不通过系统消息队 列而直接送入应用程序的消息队列,如创建窗口消息 (4)用户消息:这是程序员自己定义并在应用程序中主动发出的,一般由应用程序的 某一部分内部处理。 92MFC编程 Microsoft提供了一个基础类库MFC( Microsoft Foundation Class),其中包含用来开发 C++应用程序和 Windows应用程序的一组类。这些类用来表示窗口、对话框、设备上下文、 公共GDI对象如画笔、调色板、控制框和其他标准的 Windows部件,封装了大部分的 Windows apl( Application Programming Interface:应用程序接口)。使用MFC,可以大大 简化 Windows编程工作 MC中的类可分为两种: CObject类的派生类及非 CObject派生类。非 CObject派生类 数量不多,但大都很常用。几个常用的非 CObject派生类如 CTime, TImesPan, CString 和CFe已分别在第7单元和第8单元中介绍过 CObject派生类的基本特征为:支持序列化( Serialize,应用见第13单元)、运行时类 信息访问( Dynamic,应用见第12单元)、对象诊断输出(参看10.64:“ CObject:Dump ()成员函数”)和与集合类兼容(参看12.7:“集合类”)等 MFC将 Windows应用程序从开始运行、消息传递到结束运行所需的各步骤均封装在 WinAmp类中, CWinApp类表示MFC应用程序的应用对象。 CWinApp类从 CObject类的 子类 CWin Thread类(定义MC内的线程行为)派生。一个MFC应用程序必须有且只能有 一个从 WinAmp类派生的全局应用程序对象,此对象在运行时刻控制应用程序中所有其他对 象的活动 典型的 Windows应用程序结构有以下四种: (1)控制台应用程序:在本教程第1单元到第8单元介绍的所有程序均为控制台应用 程序。控制台应用程序结构简单,可以不使用MFC类库, 2)基于框架窗口的应用程序:某些应用程序仅需最小的用户界面和简单的窗口结构, 这时可使用基于框架窗口的方案。在此方案中,主应用程序窗口为框架窗口
第 9 单元 WINDOWS 编程 - 175 - 应,处理完毕后又返回处于等待消息的状态。使用事件驱动原理的程序的工作流程如图 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)控制台应用程序:在本教程第 1 单元到第 8 单元介绍的所有程序均为控制台应用 程序。控制台应用程序结构简单,可以不使用 MFC 类库。 (2)基于框架窗口的应用程序:某些应用程序仅需最小的用户界面和简单的窗口结构, 这时可使用基于框架窗口的方案。在此方案中,主应用程序窗口为框架窗口
9单元 WINDOWS编程 CFrameWnd派生类对象附属于应用程序的 CWinApp派生类对象的 m pMainWnd 成员。第9单元到第11单元的例题程序均为基于框架窗口的程序。 (3)基于对话框的应用程序:基于对话框的应用程序与基于框架窗口的应用程序差别 不大,只是用 CDialog派生类对象代替了 CFrameWnd派生类对象作为应用程序 的主窗口。基于对话框的应用程序框架可由suaC++的应用向导自动生成,非 常方便。第15单元介绍了基于对话框的应用程序。 (4)基于文档/视图结构的应用程序:文档/视图应用具有较复杂的结构,当然其功能 也相应增强。基于文档/视图结构的应用程序又可分为单文档界面(SDl,在第12 单元介绍)和多文档界面(MDI,在第16单元介绍),后者更复杂些。 MFC类的结构大都比较复杂,可能包含数十个至数百个成员函数,加上层次相当多的 继承关系,很难把握。MFC程序的结构也因此变得难以详细分析 学习MFC,最重要的一点是要学会抽象地把握问题,不求甚解。不要一开始学习Ⅴ isual C++就试图了解整个MFC类库,实际上那几乎是不可能的。一般的学习方法是,先大体上 对MFC有个了解,知道它的概念、组成等之后,从较简单的类入手,由浅入深,循序渐进 日积月累的学习。一开始使用MFC提供的类时,只需要知道它的一些常用的方法、外部接 口,不必要去了解它的细节和内部实现,把它当做一个模块或者说黑盒子来用,这就是一种 抽象的学习方法。在学到一定程度时,再深入研究,采用继承的方法对原有的类的行为进行 修改和扩充,派生出自己所需的类。在研究MFC的类时,要充分利用MSDN内的帮助信息。 学习MFC,很重要的一点是理解MFC应用程序的框架结构,而不是强迫记忆大量的类 成员、方法及其参数等细节。 [例91]吹泡泡程序。每当用户在窗口客户区中按下鼠标左键时即可产生一个泡泡(灰 色圆形) 设计思想:显示一个泡泡所需的数据包括其位置和大小,在MFC中可用其包含矩形表 示。可设置一数组,每当用户按下鼠标左键时,就产生一个泡泡的数据存入数组中,再由框 架窗口类的 On draw()函数显示所有的泡泡。 说明:参考98:“用Ⅴ isual O++集成开发环境开发win32应用程序”建立该项目。 程序: / Example9-1:吹泡泡程序 #include //框架窗口类 #define MAX bubble class CMy Wnd: public CFrameWn CRect m rect Bubble [MAX BUBBLE] nt m nBubblecoun CMyWndOIm nBubbleCount =0: 1 protected
第 9 单元 WINDOWS 编程 - 176 - CFrameWnd 派生类对象附属于应用程序的 CWinApp 派生类对象的 m_pMainWnd 成员。第 9 单元到第 11 单元的例题程序均为基于框架窗口的程序。 (3)基于对话框的应用程序:基于对话框的应用程序与基于框架窗口的应用程序差别 不大,只是用 CDialog 派生类对象代替了 CFrameWnd 派生类对象作为应用程序 的主窗口。基于对话框的应用程序框架可由 Visual C++的应用向导自动生成,非 常方便。第 15 单元介绍了基于对话框的应用程序。 (4)基于文档/视图结构的应用程序:文档/视图应用具有较复杂的结构,当然其功能 也相应增强。基于文档/视图结构的应用程序又可分为单文档界面(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 // 框架窗口类 #define MAX_BUBBLE 250 class CMyWnd: public CFrameWnd { CRect m_rectBubble[MAX_BUBBLE]; int m_nBubbleCount; public: CMyWnd(){m_nBubbleCount = 0;} protected:
9单元 WINDOWS编程 afx msg void OnLBut tonDown ( UINT nFlags, CPoint point) DECLARE MESSAGE MAP O //消息映射 BEGIN MESSAGE MAP(CMyWnd, CFrameWnd ON WM LBUTTONDOWN O ON WM PAINTO END MESSAGE MAPO /框架窗口类的成员函数 void CMy Wnd:: OnLButtonDown (UINT nFlags, CPoint point) if(m nBubble Count MAX BUBBLE) int r= rand O%50+10 CRect rect(point x-r, point. y-r, point x+r, point y+r) m rectBubble [m nBubble Count]= rect m nBubbleCount++ InvalidateRect(rect, FALSE void CMyWnd: OnPainto CPaintDC dc(this dc. SelectStockObject (LTGRAY BRUSH) for(int i=0; i<m nUbble Count: i++) dc Ellipse(m rectBubble[il) /应用程序类 class CMy App: public CWinApp ablie BOOL InitInstance o //应用程序类的成员函数 ooL CMyApp: InitInstar CMy Wnd *kpFrame new CMyWnd
第 9 单元 WINDOWS 编程 - 177 - 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;
9单元 WINDOWS编程 pFrame->Create(0,T("吹泡泡")); this->m pMainWnd pFr return TRUe //全局应用程序对象 输出:每在窗口客户区按一下鼠标左键,可绘出一个灰色泡泡,其大小是随机确定 的。吹泡泡程序的运行情况见图9-2。口 分析:尽管这是一个非常简单 的MFC程序,却也有几十行代码。这是 因为 Window应用程序要管理远比字符 界面(如本教程前面所用的控制台界面) 复杂的图形用户接口,而且要有多任务 并行处理的能力。但是,随着程序规模 的增大,用MFC编程的优点很快会显 露:由于结构清晰,各部分功能明确, 代码规范,编程和调试的工作量大为减 图9-2吹泡泡程序 该程序声明了两个类,一个是由应用程序类 CWinApp中派生出来的 CMy App类,一个 是从框架窗口 CFrameWnd类派生出来的 CMy Wnd类。MFC的基本类名均以字母C打头 习惯上在为使用MFC编写的应用程序中的类起名时也这么做。 除此而外,在程序中还声明了一个 CMy App类的全局对象 ThisApp 仔细阅读程序还会发现,该程序似乎不完整,其中既没有主函数(在一般的 Windows 程序中应为 Win Main()函数),也没有实现消息循环的程序段。然而,这是一种误解, 因为MFC已经把它们封装起来了。在程序运行时,MFC应用程序首先调用由框架提供的标 准的 Win Main()函数。在 WinMain()函数中,先初始化由 CMy App定义的唯一全局对 象 ThisApp(通过重载的虚函数 InitInstance(),它构造并显示应用程序的主窗口),然后 调用其由 CWinApp类继承的Run()成员函数,进入消息循环。程序结束时时调用 CWinApp 的 ExitInstance()函数退出 因此,应用程序框架不仅提供了构建应用程序所需要的类( CWinAp, CFrameWnd等) 还规定了程序的基本执行结构。所有的应用程序都在这个基本结构的基础上完成不同的功 MFC采用消息映射机制来决定如何处理特定的消息。这种消息映射机制包括一组宏, 用于标识消息处理函数、映射类成员函数和对应的消息等。在类 CMy Wnd的声明中,前面 有axmg标记的成员函数就是消息处理成员函数。如果在程序中用到了消息处理函数,那 么还需对程序执行部分所定义的消息映射进行初始化,这项工作是通过消息映射宏完成的。 消息映射宏就是程序中从 BEGIN MESSAGE MAP()到 END MESSAGE MAP()
第 9 单元 WINDOWS 编程 - 178 - pFrame->Create(0,_T("吹泡泡")); pFrame->ShowWindow(m_nCmdShow); this->m_pMainWnd = pFrame; return TRUE; } // 全局应用程序对象 CMyApp ThisApp; 输 出:每在窗口客户区按一下鼠标左键,可绘出一个灰色泡泡,其大小是随机确定 的。吹泡泡程序的运行情况见图 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() 图 9-2 吹泡泡程序
9单元 WINDOWS编程 的程序段。 BEGIN MESSAGE MAP()宏包含两个参数 CMy Wnd和 FRame Wnd,分别是 当前定义的窗口类及其父类的名称。在 BEGIN MESSAGE MAP()和 END MESSAGE MAP ()之间,含有该窗口类要处理的各种 Windows消息的入口。MFC包含了大量的预定义消 息映射宏,用来指定各种成员函数与各种形如WM××××的消息相对应,如 ON WM LBUTTONDOWN宏指定 WM LBUTTONDOWN消息的处理成员函数为 OnLButton Down()。这时,只需要写出要处理的消息就够了,不必再写出处理函数名 我们在 CMy Wnd类中声明了一个数组成员 m rectBubble,用于存放泡泡的数据(这里 是圆的包含矩形)。另外还声明了一个整型数据成员 m nUbble count,用来存放数组中泡 泡的实际数量。在框架窗口类的构造函数中该成员变量被初始化为0。由于构造函数非常简 单,所以使用了内联函数形式 OnPaint()函数根据数组 m rect Bubble的内容画出一个个泡泡。 OnPaint()函数在窗 口生成、大小改变或覆盖在其上的其他窗口被移开时被自动调用。其中语句 dc. SelectStockObject(LTGRAY BRUSH 用于选择一个库存画刷,画刷决定了所画图形(如椭圆、矩形和多边形等)内部的颜色。(参 看10.5:“库存图形对象” 在处理鼠标消息的 OnLButton Down()函数中,语句 nt 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 rect Bubble。语句 InvalidateRect(rect, FALSE) 用于通知 On Paint()函数更新窗口客户区指定区域的内容。我们记得, On Paint()函数只 在窗口生成、大小改变或覆盖其上的其他对象移动时调用,因此在数据发生变动(如增加泡 泡)时则需用CWnd类的成员函数 InvalidateRect()函数通知 On Paint()函数重画窗口客 户区中的部分区域。 93在窗口的客户区输出文字和图形 例9-1中的 On Paint()函数用于绘制客户区的内容。要完成这项任务,需要先建立 个设备环境( Device Context),这可以通过声明一个 CPaintDC类的对象dc实现。在声明语 句中,应将当前窗口对象指针this传给 CPaintDC类的构造函数,把绘制区域确定为当前窗 口的客户区。 Windows在窗口更新、移动、改变尺寸或移去覆盖在其上的其他窗口对象时
第 9 单元 WINDOWS 编程 - 179 - 的程序段。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 在窗口更新、移动、改变尺寸或移去覆盖在其上的其他窗口对象时
9单元 WINDOWS编程 均会向该窗口发送 WM PAINT消息,从而触发应用程序调用 On Paint()函数重绘窗口客 户区。 CPaintDC类中封装了大量的绘图和文字输出方法(成员函数),如: 1.文字信息显示 BOOL TextOut (int x, int y, LPCTSTR lpszString 在指定坐标(x,y)处显示字符串 lpszString的内容,显示成功返回非0值,否则返回0。坐 标原点(0,0)在客户区左上角,Y轴向下。下面各成员函数的坐标参数均同此。参数类 型 LPCTS TR和返回值类型BOOL均为 Windows的数据类型,前者意为常量字符指针,后 者为逻辑类型。下面的 COLORREF, POinT, LPPOINT, LPCRECT等均类此。关于这些 Windows类型,请参看9.7:“ Windows数据类型与变量的命名规则”。 2.画点 COLORREF SetPixel (int x, int y, COLORREF color) COLORREF SetPixel(POINT point, COLORREF color) 该函数在指定坐标(用参数x,y或点 point给出)处按给定颜色( color)画点,返回值为 原来此坐标处的颜色 3.取指定坐标点的颜色 COLORREF Get Pixel int x, int y )const COLORREF GetPixel( POINT point )const; 返回值为指定坐标处的颜色 4.画线 画线工作需经两步完成:首先确定线的起始端位置,这可通过调用成员函数 Move To完 成,其原型为: CPoint Move To( int x, inty ) CPoint Move To( POinT point ) MoveTo将绘图位置(“看不见”)移至指定坐标处,并返回移动前的绘图位置。确定了线的 起点后,即可使用成员函数Lneo画线: BOOL Line To( int x, int y) BOOL Line To( POinT point 其参数为线终点的坐标。 5.绘制矩形 绘制矩形的成员函数为: BOOL Rectangle( int xl, int yl, int x2, int y2); 实际上,Y轴的方向取决于使用的逻辑映射方式(见第10单元),这里假定使用了缺省的逻辑映射方法 MM TEXT
第 9 单元 WINDOWS 编程 - 180 - 均会向该窗口发送 WM_PAINT 消息,从而触发应用程序调用 OnPaint()函数重绘窗口客 户区。 CPaintDC 类中封装了大量的绘图和文字输出方法(成员函数),如: 1.文字信息显示 BOOL TextOut(int x, int y, LPCTSTR lpszString); 在指定坐标(x, y)处显示字符串 lpszString 的内容,显示成功返回非 0 值,否则返回 0。坐 标原点(0,0)在客户区左上角,Y 轴向下1。下面各成员函数的坐标参数均同此。参数类 型 LPCTSTR 和返回值类型 BOOL 均为 Windows 的数据类型,前者意为常量字符指针,后 者为逻辑类型。下面的 COLORREF,POINT,LPPOINT,LPCRECT 等均类此。关于这些 Windows 类型,请参看 9.7:“Windows 数据类型与变量的命名规则”。 2.画点 COLORREF SetPixel (int x, int y, COLORREF color); COLORREF SetPixel (POINT point, COLORREF color); 该函数在指定坐标(用参数 x,y 或点 point 给出)处按给定颜色(color)画点,返回值为 原来此坐标处的颜色。 3.取指定坐标点的颜色 COLORREF GetPixel ( int x, int y ) const; COLORREF GetPixel ( POINT point ) const; 返回值为指定坐标处的颜色。 4. 画线 画线工作需经两步完成:首先确定线的起始端位置,这可通过调用成员函数 MoveTo 完 成,其原型为: CPoint MoveTo ( int x, int y ); CPoint MoveTo ( POINT point ); MoveTo 将绘图位置(“看不见”)移至指定坐标处,并返回移动前的绘图位置。确定了线的 起点后,即可使用成员函数 LineTo 画线: BOOL LineTo ( int x, int y ); BOOL LineTo ( POINT point ); 其参数为线终点的坐标。 5. 绘制矩形 绘制矩形的成员函数为: BOOL Rectangle ( int x1, int y1, int x2, int y2 ); 1 实际上,Y 轴的方向取决于使用的逻辑映射方式(见第 10 单元),这里假定使用了缺省的逻辑映射方法 MM_TEXT
9单元 WINDOWS编程 l81 BOOL Rectangle( LPCRECT lpRect 其参数为需要绘制的矩形的左上角坐标(x1,yl)和右下角坐标(x2,y2)。 6.绘制椭圆 (x1,y1) 该成员函数的原型为 BOOL Ellipse (int xl, int yl, int x2, int y2) BOOL Ellipse(LPCRECT lpRect) 其参数的含义为所绘椭圆的包含矩形的左上角和右下角 坐标,如图9-3所示。 (x2,y2) 7.画多边形 图9-3椭圆的包含矩形 BOOL Polygon( LPPOINT lpPoints, int n Count ) 其中参数 lpPoints为 LPPOINT类型的指针,可用 CPoint数组(存放多边形的各顶点坐标) 作为实参。参数 n Count为顶点个数。例如 CPaintDC dc( this) point Poly[0]=CPoint(100, 100) int 1=Cpoint(200, 100) point Poly[2]=CPoint(200, 200) dc Polygon( point Poly, 3); 在窗口客户区相应位置画出一个三角形 8.其他绘图函数还有画弧Arc()、画弓形 Chord()、画扇型Pie()和画圆角矩形 InvertRect()等,具体使用方法可参看MSDN联机帮助, 9.获取客户区的坐标 为了某些绘图效果,可能需要知道框架窗口客户区当前的大小。Wnd类的成员函数 GetClientRect()可用于该目的。其调用方法为 void GetClient Rect( LPRECT lpRect ) 例9-2修改例9-1中的 OnDraw()函数,使之可在客户区中央显示一矩形框,并在 其中显示文字信息“Hell,MFC!” 说明:建立项目的方法见98:“用Ⅴ isual c++集成开发环境开发Win32应用程序”。 程序:用下面的程序段替代例9-1中的 OnPaint()函数。 ∥/ Example9-2:在窗口客户区显示矩形框和文字 void CMy Wnd:: OnPaint o CPaintdC dc(this dc. SelectStockObject(LTGRAY BRUSH)
第 9 单元 WINDOWS 编程 - 181 - BOOL Rectangle ( LPCRECT lpRect ); 其参数为需要绘制的矩形的左上角坐标(x1, y1)和右下角坐标(x2, y2)。 6.绘制椭圆 该成员函数的原型为: BOOL Ellipse(int x1, int y1, int x2, int y2); BOOL Ellipse(LPCRECT lpRect); 其参数的含义为所绘椭圆的包含矩形的左上角和右下角 坐标,如图 9-3 所示。 7. 画多边形 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); 在窗口客户区相应位置画出一个三角形。 8. 其他绘图函数还有画弧 Arc()、画弓形 Chord()、画扇型 Pie()和画圆角矩形 InvertRect()等,具体使用方法可参看 MSDN 联机帮助。 9. 获取客户区的坐标 为了某些绘图效果,可能需要知道框架窗口客户区当前的大小。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); (x1,y1) (x2,y2) 图 9-3 椭圆的包含矩形
9单元 WINDOWS编程 for(int i=0; i<m nBubbleCount: i++) dc Ellipse(m rectBubble [il) CRect rectClient, rectTitle int nWidth =100 int nHeight 40 GetClient Rect(&rectClient rectTitle left (rectClient. Widtho-nWidth)/2 rectTitle. top (rectClient. HeightO-nHeight)/2 rectTitle. right rectTitle. left+nWidth rectTitle. bot tom= rectTitle toptnHeight c. SelectStockObject(WHITE BRUSH) dc Rectangle (rectTitle dc TextOut (rectTitle. left+10, rectTitle top+10,Hello, MFC! " 输入输出:与例9-1类似,只是在窗口中央显示了一个矩形框,框中显示相应文字,见 图9-4。 分析:该程序系在例91的程序基础上改编。可以看出,这两个程序不但结构相同 而且大部分代码也完全一样。这是MFC编程的一个显著特点,即可通过修改一个通用程序 框架来快速构造自己的程序 自学内容 Hello. MFCI 94 WINDOWS的用户界 面对象 Windows支持丰富的用户界面对 图9-4新吹泡泡程序 象,包括窗口、图标、菜单、对话框等 等。程序员只需简单的几十行代码,就可以设计出一个非常漂亮的图形用户界面。下面我们 介绍几个常用的用户界面对象的术语和相关概念 941窗口
第 9 单元 WINDOWS 编程 - 182 - 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-1 的程序基础上改编。可以看出,这两个程序不但结构相同, 而且大部分代码也完全一样。这是 MFC 编程的一个显著特点,即可通过修改一个通用程序 框架来快速构造自己的程序。 自学内容 9.4 WINDOWS 的用户界 面对象 Windows 支持丰富的用户界面对 象,包括窗口、图标、菜单、对话框等 等。程序员只需简单的几十行代码,就可以设计出一个非常漂亮的图形用户界面。下面我们 介绍几个常用的用户界面对象的术语和相关概念。 9.4.1 窗口 图 9-4 新吹泡泡程序
9单元 WINDOWS编程 窗口是用户界面中最重要的部分,是屏幕上与一个应用程序相对应的矩形区域,是用户 与产生该窗口的应用程序 之间的可视界面。每当用 户开始运行一个应用程旧回幽國国圆 / Exanple91:吹泡泡积网 时,应用程序就创建并显 查找目标③: OnDra 找下一个@ 示一个窗口:当用户操作 N define HAX BUBBLE class CMy Wnd: public CR厂全字匹配) 窗口中的对象时,程序会 CRect Bubb 区分大小写c 作出相应反应。用户通过pl CNyVndoinnBubblccount = 0: 1 关闭一个窗口来终止一个 protected x_nsg void OnLButt onDovm (UINT nFlags, CPoint point) 程序的运行;通过选择相 nse void On Paint() DECLARE NESSAGE_ MAP O 应的应用程序窗口来选择 消息映射 相应的应用程序。一个典 BEGIN JE ON VM I L1:0 型的窗口外观如图95所上n 图9-5典型窗口 绝大多数窗口都有 个边框,用于指示窗口的边界,同时也用来指明该窗口是否为活动窗口,当窗口活动时,边 框的标题栏部分呈高亮显示。用户可以用鼠标拖动边框来调整窗口的大小。在程序设计时, 称窗口活动为获得焦点,非活动窗口为失去焦点。 942系统菜单 系统菜单图标位于窗口左上角,用鼠标点一下该图标(或按ALI+空格键)就可弹出系 统菜单。系统菜单提供标准的应用程序选项,包括 Restore(还原窗口原有的大小),Move (使窗口可以通过键盘上的光标键来移动其位置),Size(使用光标键调整窗口大小), Minimize(将窗口缩成图标), Maximize(最大化:使窗口充满整个屏幕)和 Close(关闭 窗口) 943标题栏 标题栏位于窗口的顶部,其中显示的文本信息用于标注应用程序,一般是应用程序的名 字,以便让用户了解哪个应用程序正在运行。标题栏颜色反映该窗口是否是一个活动窗口(活 动窗口的标题栏呈醒目颜色)。鼠标双击标题栏可使窗口在正常大小和最大化状态之间切换。 在标题栏上按下鼠标器左键可以拖动并移动该窗口,按右键弹出系统菜单。 944菜单栏 菜单栏位于标题栏下方,横跨窗口,上面列出了应用程序所支持的命令。菜单栏中的项 目一般是一类命令或操作的通称,如文件操作、编辑操作和帮助等。从菜单栏中选中某一项 通常会显示一个弹出菜单,其中的项是对应于指定命令或类中的某个任务。通过选择菜单中 的一个项(菜单项),用户可以向程序发出命令,以执行某一功能。如选择“文件操作”菜 单的“打开..”项会弹出一个打开文件对话框,让用户选择一个文件,然后打开这个文件 般来说,以“.”结尾的菜单项表明选择该项时会弹出一个对话框,让用户输入信息
第 9 单元 WINDOWS 编程 - 183 - 窗口是用户界面中最重要的部分,是屏幕上与一个应用程序相对应的矩形区域,是用户 与产生该窗口的应用程序 之间的可视界面。每当用 户开始运行一个应用程序 时,应用程序就创建并显 示一个窗口;当用户操作 窗口中的对象时,程序会 作出相应反应。用户通过 关闭一个窗口来终止一个 程序的运行;通过选择相 应的应用程序窗口来选择 相应的应用程序。一个典 型的窗口外观如图 9-5 所 示。 绝大多数窗口都有一 个边框,用于指示窗口的边界,同时也用来指明该窗口是否为活动窗口,当窗口活动时,边 框的标题栏部分呈高亮显示。用户可以用鼠标拖动边框来调整窗口的大小。在程序设计时, 称窗口活动为获得焦点,非活动窗口为失去焦点。 9.4.2 系统菜单 系统菜单图标位于窗口左上角,用鼠标点一下该图标(或按 ALT+空格键)就可弹出系 统菜单。系统菜单提供标准的应用程序选项,包括 Restore(还原窗口原有的大小),Move (使窗口可以通过键盘上的光标键来移动其位置),Size(使用光标键调整窗口大小), Minimize(将窗口缩成图标),Maximize(最大化:使窗口充满整个屏幕)和 Close(关闭 窗口)。 9.4.3 标题栏 标题栏位于窗口的顶部,其中显示的文本信息用于标注应用程序,一般是应用程序的名 字,以便让用户了解哪个应用程序正在运行。标题栏颜色反映该窗口是否是一个活动窗口(活 动窗口的标题栏呈醒目颜色)。鼠标双击标题栏可使窗口在正常大小和最大化状态之间切换。 在标题栏上按下鼠标器左键可以拖动并移动该窗口,按右键弹出系统菜单。 9.4.4 菜单栏 菜单栏位于标题栏下方,横跨窗口,上面列出了应用程序所支持的命令。菜单栏中的项 目一般是一类命令或操作的通称,如文件操作、编辑操作和帮助等。从菜单栏中选中某一项 通常会显示一个弹出菜单,其中的项是对应于指定命令或类中的某个任务。通过选择菜单中 的一个项(菜单项),用户可以向程序发出命令,以执行某一功能。如选择“文件操作”菜 单的“打开...”项会弹出一个打开文件对话框,让用户选择一个文件,然后打开这个文件。 一般来说,以“...”结尾的菜单项表明选择该项时会弹出一个对话框,让用户输入信息, 图 9-5 典型窗口