八宝书库 > 文学其他电子书 > 深入浅出MFC第2版(PDF格式) >

第82部分

深入浅出MFC第2版(PDF格式)-第82部分

小说: 深入浅出MFC第2版(PDF格式) 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!






                 为什么经过这样的宏之后,消息就会自动流往指定的函数去呢?谜底在于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 函数说明 



                                                                                          399 


…………………………………………………………Page 462……………………………………………………………

                  第篇    湷觥 FC  程式設計 



                   LineDDA  并不属于任何一个MFC 类别,因此调用它必须使用C++  的〃scope operator〃 



                    (也就是::): 



                     void CMyFrameWnd::OnPaint() 

                      { 

                      CPaintDC dc(this); 

                      CRect rect; 



                        GetClientRect(rect); 



                        dc。SetTextAlign(TA_BOTTOM | TA_CENTER); 



                        ::LineDDA(rect。right/2; 0; rect。right/2; rect。bottom/2; 

                            (LINEDDAPROC) LineDDACallback; (LPARAM) (LPVOID) &dc); 

                      } 



                     其中LineDDACallback  是我们准备的callback 函数,必须在类别中先有声明: 



                      class CMyFrameWnd : public CFrameWnd 

                      { 

                      。。。 

                     private: 

                        static VOID CALLBACK LineDDACallback(int;int;LPARAM); 

                      }; 



                     请注意,如果类别的成员函数是一个callback 函数, 你必须声明它为〃static〃,才能把 



                     C++ 编译器加诸于函数的一个隐藏参数this 去掉(请看方块批注) 。 



                              以类别的成员函数作为 Windows callback 函数 



                      虽然现在来讲这个题目,对初学者而言恐怕是过于艰深,但我想毕竟还是个好机会 



                     ……我可以在介绍如何使用callback 函数的场合,顺便介绍一些C++  的重要观念。 



                     首先我要很快地解释一下什么是callback 函数。凡是由你设计而却由Windows 系 



                      统调用的函数,统称为callback 函数。这些函数都有一定的类型,以配合Windows 



                      的调用动作。 



                     某些Windows API  函数会要求以callback 函数作为其参数之一,这些API 例如 



400 


…………………………………………………………Page 463……………………………………………………………

                                                    第6章    MFC 程式的生死因果 



 SetTimer 、LineDDA、EnumObjects 。通常这种API 会在进行某种行为之后或满足某种 



 状态之时调用该callback 函数。图6…6  已解释过LineDDA调用callback 函数的时机; 



 下面即将示范的EnumObjects 则是在发现某个Device Context 的GDI object 符合我们 



 的指定类型时,调用callback 函数。 



 好,现在我们要讨论的是,什么函数有资格在C++ 程序中做为callback 函数?这个 



 问题的背后是:C++ 程序中的callback  函数有什么特别的吗?为什么要特别提出讨论? 



是的,特别之处在于,C++ 编译器为类别成员函数多准备了一个隐藏参数(程序代码 



中看不到),这使得函数类型与Windows callback  函数的预设类型不符。 



假设我们有一个CMyclass 如下: 



class CMyclass { 

  private : 

    int nCount; 

    int CALLBACK _export 

        EnumObjectsProc(LPSTR lpLogObject; LPSTR lpData); 

  public : 

    void enumIt(CDC& dc); 

} 

void CMyclass::enumIt(CDC& dc) 

{ 



  // 注册callback 函数 

  dc。EnumObjects(OBJ_BRUSH; EnumObjectsProc; NULL); 



} 



C++ 编译器针对CMyclass::enumIt 实际做出来的码相当于: 

void CMyclass::enumIt(CDC& dc) 

{ 



  // 注册callback 函数 

  CDC::EnumObjects(OBJ_BRUSH; EnumObjectsProc; 

                      NULL; (CDC *)&dc); 

   

} 



 你所看到的最后一个参数,(CDC *)&dc,其实就是this 指针。类别成员函数靠着this 



                                                                                  401 


…………………………………………………………Page 464……………………………………………………………

              第篇    湷觥 FC  程式設計 



                指针才得以抓到正确对象的资料。你要知道,内存中只会有一份类别成员函数, 



                但却可能有许多份类别成员变量……每个对象拥有一份。 



                C++  以隐晦的this 指针指出正确的对象。当你这么做: 



                nCount = 0; 



                其实是: 



                this…》nCount = 0; 



                基于相同的道理,

返回目录 上一页 下一页 回到顶部 0 0

你可能喜欢的