高级程序设计

25 - 事件驱动的程序设计

2020-05-14 14:00 CST
2020-05-14 17:52 CST
CC BY-NC 4.0

Windows简介

Windows是一种基于图形界面的多任务操作系统。

  • 同时运行多个应用程序
  • 每个应用程序通过各自的窗口与用户进行交互
  • 用户通过鼠标、菜单和键盘输入来执行操作

Windows的功能以两种方式提供:

  • 工具(应用程序):资源管理器、记事本等,供用户使用
  • 函数库:作为Windows的应用程序接口(API),以C语言函数形式提供,供应用程序使用

Windows应用程序的类型

  • 单文档应用
  • 多文档应用
  • 对话框应用

事件驱动的程序结构

Windows应用程序采用的是一种基于事件(消息)驱动的交互式流程控制结构:

  • 程序的任何一个动作都是由某个事件激发的
  • 事件可以是用户的键盘、鼠标、菜单等操作

每个应用程序都有一个消息队列,系统会把属于各个应用程序的消息放入各自的消息队列。

大部分的消息都关联到某个窗口,每个窗口都有一个消息处理函数。

应用程序不断地从自己的消息队列中获取消息并调用相应窗口的消息处理函数来处理获得的消息。

  • 取消息-处理消息的过程构成了消息循环
  • 当取到某个特定消息(如WM_QUIT)后消息循环结束

每个消息的处理时间不宜太长,否则会造成程序“假死”现象(程序不响应其它消息)。

基于Windows API的事件驱动程序设计(过程式)

每个应用程序都必须提供一个主函数WinMain,其主要功能是:

  • 注册窗口类
    • 窗口类的名字、基本风格、消息处理函数、图标、光标、菜单等
    • 每类窗口(不是每个窗口)都需要注册
  • 创建应用程序的主窗口
  • 进入消息循环,直到接收到WM_QUIT
WinMain(...) {
  RegisterClass(...);
  CreateWindow(...);
  while (GetMessage(...)) {
    DispatchMessage(...);
  }
}

消息处理函数应是可再入的

消息处理函数还可以自己生成新的消息,方式有两种:

  • PostMessage(消息放入消息队列)
  • SendMessage(直接调用消息处理函数)

消息处理函数应该是一个可再入(重入,reentrant)的函数:

  • 函数需要的数据通过参数来传递
  • 函数不能有static存储类的局部变量
  • 函数中访问全局变量也可能导致函数不可再入

资源

每个Windows应用程序,除了程序代码外,还包含一些资源描述:

  • 菜单:菜单ID、菜单项/文字
  • 对话框:对话框ID、尺寸、文字
  • ……

资源描述有规定的格式,存储在相应的资源文件.rc中,经编译后将作为Windows应用程序的一部分被链接到应用程序的目标文件中。

基于MFC的事件驱动程序设计(面向对象)

  • 基于Windows API的事件驱动程序设计是一种基于过程抽象的程序设计范式
  • 通过调用API函数编写程序的粒度太细、太繁琐,开发效率不高

Windows应用程序中的对象

  • 窗口对象
    • 显示窗口的处理数据
    • 处理Windows的消息、实现与用户的交互
    • 窗口对象之间可以存在聚集关系
  • 文档对象
    • 管理在各个窗口中显示和处理的数据
    • 文档对象和窗口对象之间可以存在关联关系
  • 应用程序对象
    • 管理属于它的窗口对象和文档对象
    • 实现消息循环
    • 它与窗口对象及文档对象之间构成了聚集关系
  • ……

对于每一个Windows应用:

  • 应用程序对象和主窗口对象只有一个
  • 子窗口对象和文档对象可以有多个,它们在程序运行的不同时刻创建

MFC - 微软基础类库

MFC = Microsoft Foundation Class library

  • MFC提供了一些类来描述应用中对象的基本功能,应用程序可以通过继承这些类来实现各自的特殊功能
  • MFC还提供了对基于“文档-视”结构应用框架的支持

MFC提供的主要类

  • 基本窗口类(CWnd
    • 实现窗口的基本功能:消息处理、窗口大小、菜单管理……
    • 是其他窗口类的基类
  • 框架窗口类
    • CFrameWnd:单文档应用窗口
    • CMDIFrameWnd:多文档应用主窗口
    • CMDIChildWnd:多文档应用子窗口
  • 视类(CView
    • 实现程序数据的显示功能以及操作数据时与用户的交互功能
    • 视窗口位于单文档应用主窗口、多文档应用子窗口的客户区
  • 文档类(CDocument
    • 对程序要处理的数据进行管理,包括磁盘文件输入/输出
    • 一个CDocument类的对象至少要对应一个CView类的对象
  • 应用类(CWinApp
    • 提供了对Windows应用程序的各部分进行组合和管理的功能,其中包括实现消息循环等
  • 对话框类(CDialog
    • 封装了对话框的基本功能,是所有对话框类的基类
  • 绘图类
    • 实现图形绘制的基本功能
    • 包括CDCCPenCFontCBrush
  • 文件输入/输出类
    • CFile类:实现了基于字节流的I/O
    • CArchive类:实现了对基本数据类型以及MFC中从CObject继承的类对象的文件I/O
  • 常用数据类型类
  • ……

应用向导

VC++提供了一个应用程序向导(Application Wizard),可用来为程序建立一些必要的初始代码。

在应用向导建立的应用程序中有一个CMyApp类的全局对象theApp,它在WinMain执行之前创建。

为了体现“纯”面向对象特性,在应用向导创建的应用程序中隐藏了主函数WinMain,在隐藏的WinMain中:

  • 首先调用theApp的成员函数InitInstance对应用程序进行初始化;
  • 然后去调用theApp的成员函数Run进入消息循环;
  • 消息循环结束后,将会调用theApp的成员函数ExitInstance进行程序结束前的一些处理。

在应用向导建立的应用程序中对消息处理函数进行了结构化处理:

  • 通过“消息映射”机制把Windows消息与相应类名的成员函数关联起来;
  • 各个消息的处理分别由相应类的一个成员函数来实现。

类向导

类向导(Class Wizard)用于:

  • 为应用程序中从MFC派生的类增加/删除成员:
    • 消息处理成员函数
    • 可重定义的成员函数
    • 新定义数据成员(成员变量)
    • 对话框类中与控件所对应的数据成员
    • ……
  • 为应用程序增加/删除从MFC派生的类