友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
富士康小说网 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

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

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 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; 



                基于相同的道理,上例中的EnumObjectsProc  既然是一个成员函数,C++ 编译器也 



                会为它多准备一个隐藏参数。 



                好,问题就出在这个隐藏参数。callback 函数是给Windows 调用用的,Windows 并 



                不经由任何对象调用这个函数,也就无由传递this 指针给callback 函数,于是导至 



                堆栈中有一个随机变量会成为this 指针,而其结果当然是程序的崩溃了。 



                要把某个函数用作callback 函数,就必须告诉C++ 编译器,不要放this 指针作为 



                该函数的最后一个参数。两个方法可以做到这一点: 



                1。 不要使用类别的成员函数(也就是说,要使用全域函数)做为callback 函数。 



                2。 使用static 成员函数。也就是在函数前面加上static 修饰词。 



                第一种作法相当于在C 语言中使用callback 函数。第二种作法比较接近OO  的精神。 



                我想更进一步提醒你的是,C++  中的static 成员函数特性是,即使对象还没有产生, 



                static 成员也已经存在(函数或变量都如此) 。换句话说对象还没有产生之前你已经 



                可以调用类别的static 函数或使用类别的static 变量了。请参阅第二章。 



                也就是说,凡声明为static 的东西(不管函数或变量)都并不和对象结合在一起, 



                它们是类别的一部份,不属于对象。 



402 


…………………………………………………………Page 465……………………………………………………………

                                                            第6章    MFC 程式的生死因果 



空闲时间(idle time)的处理:OnIdle 



        为了让Hello 程序更具体而微地表现一个MFC 应用程序的水准,我打算为它加上空闲 



        时间(idle time )的处理。 



        我已经在第1章介绍过了空闲时间,也简介了Win32 程序如何以PeekMessage                       「偷闲」。 



        Microsoft 业已把这个观念落实到CWinApp  (不,应该是CWinThread)中。请你回头看 



        看本章的稍早的「CWinApp::Run 程序生命的活水源头」一节,那一节已经揭露了MFC 



        消息循环的秘密: 



        int CWinThread::Run() 

         { 

            。。。 

            for (;;) 

            { 

                while (bIdle && 

                        !::PeekMessage(&m_msgCur; NULL; NULL; NULL; PM_NOREMOVE)) 

                { 

                    // call OnIdle while in bIdle state 

                    if (!OnIdle(lIdleCount++)) 

                        bIdle = FALSE; // assume 〃no idle〃 state 

                } 

                。。。 // msg loop 

            } 

         } 



        CThread::OnIdle 做些什么事情呢?CWinApp 改写了OnIdle  函数,CWinApp::OnIdle 又 



       做些什么事情呢?你可以从THRDCORE。CPP 和APPCORE。CPP  中找到这两个函数的 



       源代码,源代码可以说明一切。当然基本上我们可以猜测OnIdle  函数中大概是做一些 



        系统(指的是MFC 本身)的维护工作。这一部份的功能可以说日趋式微,因为低优先 



       权的执行线程可以替代其角色。 



        如果你的MFC 程序也想处理idle time,只要改写CWinApp 衍生类别的OnIdle  函数即 



        可。这个函数的类型如下: 



              virtual BOOL OnIdle(LONG lCount); 



                                                                                         403 


…………………………………………………………Page 466……………………………………………………………

                  第篇    湷觥 FC  程式設計 



                   lCount 是系统传进来的一个值,表示自从上次有消息进来,到现在,OnIdle  已经被调用 



                   了多少次。稍后我将改写Hello 程序,把这个值输出到窗口上,你就可以知道空闲时间 



                   是多么地频繁。lCount 会持续累增,直到CWinThread::Run  的消息循环又获得了一个讯 



                   息,此值才重置为0 。 



                   注意:Jeff Prosise 在他的Programming Windows 95 with MFC 一书第7章谈到OnIdle 



                   函数时,曾经说过有几个消息并不会重置lCount 为0,包括鼠标消息、WM_SYSTIMER 、 



                   WM_PAINT 。不过根据我实测的结果,至少鼠标消息是会的。稍后你可在新版的Hello 程 



                   序移动鼠标,看看lCount 会不会重设为0 。 



                  我如何改写Hello 呢?下面是几个步骤: 



                  1。 在CMyWinApp 中增加OnIdle 函数的声明: 



                      class CMyWinApp : public CWinApp 

                      { 

                     public: 

                      virtual BOOL InitInstance(); // 每一个应用程序都应该改写此函数 



                      virtual BOOL OnIdle(LONG lCount); // OnIdle 用来处理空闲时间(idle time) 

                      }; 



                   2。 在CMyFrameWnd 中增加一个IdleTimeHandler  函数声明。这么做是因为我希 



                   望在窗口中显示lCount 值, 所以最好的作法就是在OnIdle 中调用 



                   CMyFrameWnd 成员函数,这样才容易获得绘图所需的DC 。 



                      class CMyFrameWnd : public CFrameWnd 

                      { 

                     public: 

                        CMyFrameWnd();            // constructor 

                        afx_msg void OnPaint();   // for WM_PAINT 

                        afx_msg void OnAbout();   // for
返回目录 上一页 下一页 回到顶部 9 10
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!