按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
有任何参数,如何使WM_PAINT 流到OnPaint 函数呢?
MFC 把消息主要分为三大类,Message Map 机制中对于消息与函数间的对映关系也明定
以下三种:
■ 标准Windows 消息(WM_xxx )的对映规则:
宏名称 对映消息 消息处理函数(名称已由系统预设)
ON_WM_CHAR WM_CHAR OnChar
ON_WM_CLOSE WM_CLOSE OnClose
ON_WM_CREATE WM_CREATE OnCreate
ON_WM_DESTROY WM_DESTROY OnDestroy
ON_WM_LBUTTONDOWN WM_LBUTTONDOWN OnLButtonDown
ON_WM_LBUTTONUP WM_LBUTTONUP OnLButtonUp
ON_WM_MOUSEMOVE WM_MOUSEMOVE OnMouseMove
ON_WM_PAINT WM_PAINT OnPaint
。。。
395
…………………………………………………………Page 458……………………………………………………………
第篇 湷觥 FC 程式設計
■ 命令消息(WM_MAND)的一般性对映规则是:
ON_MAND(;)
例如:
ON_MAND(IDM_ABOUT; OnAbout)
ON_MAND(IDM_FILENEW; OnFileNew)
ON_MAND(IDM_FILEOPEN; OnFileOpen)
ON_MAND(IDM_FILESAVE; OnFileSave)
■ 「Notification 消息」(由控制组件产生,例如BN_xxx )的对映机制的宏分
为好几种(因为控制组件本就分为好几种),以下各举一例做代表:
控制组件 宏名称 消息处理函数
Button ON_BN_CLICKED(;) memberFxn
boBox ON_CBN_DBLCLK(;) memberFxn
Edit ON_EN_SETFOCUS(;) memberFxn
ListBox ON_LBN_DBLCLK(;) memberFxn
各个消息处理函数均应以afx_msg void 为函数型式。
为什么经过这样的宏之后,消息就会自动流往指定的函数去呢?谜底在于Message Map
的结构设计。如果你把第3章的Message Map 仿真程序好好研究过,现在应该已是成竹
在胸。我将在第9章再讨论MFC 的Message Map 。
好奇心摆两旁,还是先把实用上的问题放中间吧。如果某个消息在Message Map 中找不
到对映记录,消息何去何从?答案是它会往基础类别流窜,这个消息流窜动作称为
「Message Routing」。如果一直窜到最基础的类别仍找不到对映的处理例程,自会有预
设函数来处理,就像SDK 中的DefWindowProc 一样。
MFC 的CCmdTarget 所衍生下来的每一个类别都可以设定自己的Message Map ,因为
它们都可能(可以)收到消息。
396
…………………………………………………………Page 459……………………………………………………………
第6章 MFC 程式的生死因果
消息流动是个颇为复杂的机制,它和Document/View 、动态生成(Dynamic Creation ),
文件读写(Serialization)一样,都是需要特别留心的地方。
来龙去脉总整理
前面各节的目的就是如何将表面上看来不知所以然的MFC 程序对映到我们在SDK 程序
设计中学习到的消息流动观念,从而清楚地掌握MFC 程序的诞生与死亡。让我对MFC
程序的来龙去脉再做一次总整理。
程序的诞生:
■ Application object 产生,内存于是获得配置,初值亦设立了。
■ Afx WinMain 执行AfxWinInit,后者又调用AfxInitThread ,把消息队列尽量加大到
96。
■ Afx WinMain 执行InitApplication 。这是CWinApp 的虚拟函数,但我们通常不改
写它。
■ AfxWinMain 执行InitInstance 。这是CWinApp 的虚拟函数,我们必须改写它。
■ CMyWinApp ::InitInstance 'new' 了一个CMyFrameWnd 对象。
■ CMyFrameWnd 构造式调用Create,产生主窗口。我们在Create 参数中指定的
窗口类别是NULL , 于是MFC 根据窗口种类, 自行为我们注册一个名为
〃AfxFrameOrView42d〃 的窗口类别。
■ 回到InitInstance 中继续执行ShowWindow ,显示窗口。
■ 执行UpdateWindow ,于是发出WM_PAIN T。
■ 回到AfxWinMain,执行Run ,进入消息循环。
程序开始运作:
■ 程序获得WM_PAINT 消息(藉由CWinApp::Run 中的::GetMessage 循环)。
■ WM_PAINT 经由::DispatchMessage 送到窗口函数CWnd::DefWindowProc 中。
397
…………………………………………………………Page 460……………………………………………………………
第篇 湷觥 FC 程式設計
■ CWnd::DefWindowProc 将消息绕行过消息映射表格(Message Map )。
■ 绕行过程中发现有吻合项目,于是调用项目中对应的函数。此函数是应用程序
利用BEGIN_MESSAGE_MAP 和END_MESSAGE_MAP 之间的宏设立起来的。
■ 标准消息的处理例程亦有标准命名,例如WM_PAINT 必然由OnPaint 处理。
以下是程序的死亡:
■ 使用者选按【File/Close】,于是发出WM_CLOSE 。
■ CMyFrameWnd 并没有设置WM_CLOSE 处理例程,于是交给预设之处理例程。
■ 预设函数对于WM_CLOSE 的处理方式是调用::DestroyWindow , 并因而发出
WM_DESTRO Y。
■ 预设之WM_DESTROY 处理方式是调用::PostQuitMessage,因此发出WM_QUIT 。
■ CWinApp::Run 收到WM_QUIT 后会结束其内部之消息循环, 然后调用
ExitInstance,这是CWinApp 的一个虚拟函数。
■ 如果CMyWinApp 改写了ExitInstance , 那么CWinApp::Run 所调用的就是
CMyWinApp ::ExitInstance,否则就是CWinApp::ExitInstance 。
■ 最后回到AfxWinMain,执行AfxWinTerm,结束程序。
Callback 函数
Hello 的OnPaint 在程序收到WM_PAINT 之后开始运作。为了让〃Hello; MFC〃 字样从
天而降并有动画效果,程序采用LineDDA API 函数。我的目的一方面是为了示范消息的
处理,一方面也为了示范MFC 程序如何调用Windows API 函数。许多人可能不熟悉
LineDDA,所以我也一并介绍这个有趣的函数。
398
…………………………………………………………Page 461……………………………………………………………
第6章 MFC 程式的生死因果
首先介绍LineDDA :
void WINAPI LineDDA(int; int; int; int; LINEDDAPROC; LPARAM);
这个函数用来做动画十分方便,你可以利用前四个参数指定屏幕上任意两点的(x;y)
座
标,此函数将以Bresenham 算法(注) 计算出通过两点之直线中的每一个屏幕图素座
标;每计算出一个坐标,就通知由LineDDA 第五个参数所指定的callback 函数。这个
callback 函数的型式必须是:
typedef void (CALLBACK* LINEDDAPROC)(int; int; LPARAM);
通常我们在这个callback 函数中设计绘图动作。玩过Windows 的接龙游戏吗?接龙成
功后扑克牌的跳动效果就可以利用LineDDA 完成。虽然扑克牌的跳动路径是一条曲
线,但将曲线拆成数条直线并不困难。LineDDA 的第六个(最后一个)参数可以视应用
程序的需要传递一个32 位指针,本例中Hello 传的是一个Device Context 。
Bresenham 算法是计算机图学中为了「显示器(屏幕或打印机)系由图素构成」的这个
特性而设计出来的算法,使得求直线各点的过程中全部以整数来运算,因而大幅提升
计算速度。
{ (x1; y1) LineDDACallback(int; int; PLARAM)
{
。。。
。。。
。。。
}
}
(x2; y2)
LineDDA Bresenham 算法计算出通过两点之直线中每一个
你可以指定两个坐标点, 将以
屏幕图素的坐标。每计算出一个坐标,就以该坐标为参数,调用你所指定的callback 函数。
图6…6 LineDDA 函数说明