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 <afxwin. h> //框架窗口类 #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 <afxwin.h> // 框架窗口类 #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 吹泡泡程序