应用MFC开发高级应用程序(下)

来源: 作者: 2007-12-03 出处:pcdog.com

操作系统  
       
  六、使用自定义消息

  1、MFC的消息映射机制

  Windows是一个典型的消息驱动的操作系统,程序的运行是靠对各种消息的响应来实现的,这些消息的来源非常广泛,既包括Windows系统本身,如WM_CLOSE、WM_PAINT、WM_CREATE和WM_TIMER等常用消息,又包括用户菜单选择、键盘加速键以及工具条和对话框按钮等等,如果应用程序要与其它程序协同工作,那么消息的来源还包括其它应用程序发送的消息,串行口和并行口等硬件发送的消息等等。总之,Windows程序的开发是围绕着对众多消息的合理响应和实现来实现程序的各种功能的。使用过C语言来开发Windows程序的人都知道,在Windows程序的窗口回调函数中需要安排Switch语句来响应大量的消息,同时由于消息的间断性使得不同的消息响应之间信息的传递是通过大量的全局变量或者静态数据来实现的。

  人们常用的两种类库OWL和MFC都提供了消息映射机制用以加速开发速度,使用者只需要按规定定义好对应消息的处理函数自身即可,至于实际调用由类库本身所提供的机制进行,或采用虚函数,或采用消息映射宏。为了有效节约内存,MFC并不大量采用虚函数机制,而是采用宏来将特定的消息映射到派生类中的响应成员函数。这种机制不但适用于Windows自身的140条消息,而且适用于菜单命令消息和按钮控制消息。MFC提供的消息映射机制是非常强大的,它允许在类的各个层次上对消息进行控制,而不简单的局限于消息产生者本身。在应用程序接收到窗口命令时,MFC将按如下次序寻找相应的消息控制函数:

  SDI应用

  MDI应用

  视口

  视口

  文档

  文档

  SDI主框架

  MDI子框架

  应用

  MDI主框架

  应用

  大多数应用对每一个命令通常都只有一个特定的命令控制函数,而这个命令控制函数也只属于某一特定的类,但是如果在应用中对同一消息有多个命令控制函数,那么只有优先级较高的命令控制函数才会被调用。为了简化对常用命令的处理,MFC在基类中提供并实现了许多消息映射的入口,如打印命令,打印预览命令,退出命令以及联机帮助命令等,这样在派生类中就继承了所有的基类中的消息映射函数,从而可以大大简化编程。如果我们要在自己派生类中实现对消息的控制,那么必须在派生类中加上相应的控制函数和映射入口。
 
  2、使用自己的消息

  在程序设计的更深层次,人们常常会发现只依赖于菜单和命令按钮产生的消息是不够的,常常因为程序运行的逻辑结构和不同视口之间数据的同步而需要使用一些自定义的消息,这样通过在相应层次上安排消息响应函数就可以实现自己的特殊需要。比如如果我们要在特定的时间间隔内通知所有数据输出视口重新取得新数据,要依靠菜单命令和按钮命令实现不够理想,比较理想的解决办法是采用定时器事件进行特定的计算操作,操作完成后再采用SendMessage发送自己的特定消息,只有当这一消息得到处理后才会返回主控程序进行下一时间计算。通过在文档层次上安排对消息的响应取得最新计算数据,而后通过UpdateAllViews()成员函数来通知所有相关视口更新数据的显示。视口通过重载OnUpdate()成员函数就可以实现特定数据的更新显示。

  如果用户能够熟练使用SendMessage()函数和PostMessage()函数,那么要发送自定义消息并不难,通常有两种选择,其一是发送WM_COMMAND消息,通过消息的WORD wParam参数传递用户的命令ID,举例如下:

  SendMessage(WM_COMMAND,IDC_GETDATA,0); //MFC主框架发送

  然后在文档层次上安排消息映射入口:

  ON_COMMAND(IDC_GETDATA, OnGetData)

  同时在文档类中实现OnGetData()函数:

void CSimuDoc::OnGetData()
{
TRACE("Now in SimuDoc,From OnGetData ");
UpdateAllViews(NULL);


  注意在上中的消息映射入口需要用户手工加入,Visual C++提供的ClassWizard并不能替用户完成这一工作。上中例子没有使用PostMessage函数而使用SendMessage函数的原因是利用了SendMessage函数的特点,即它只有发送消息得到适当处理后方才
返回,这样有助于程序控制。另一种发送自定义消息的办法是直接发送命令ID,在控制层次上采用ON_MESSAGE来实现消息映射入口,注意这时的命令控制函数的原型根据Windows本身消息处理的规定必须如下:

afx_msg LONG OnCaculationOnce(WPARAM wParam,LPARAM lParam); 

  相对来讲,这种机制不如上述机制简单,也就不再赘述。


  七、使用不带文挡-视结构的MFC应用

  文档-视结构的功能是非常强大的,可以适合于大多数应用程序,但是有时我们只需要非常简单的程序,为了减少最终可执行文件尺寸和提高运行速度,我们没有必要使用文挡-视结构,典型的有简单SDI应用和基于对话框的应用。

  1、简单SDI应用

  此时只需要使用CWinApp和CFrameWnd两个类就完全可以了。由于CWinApp类封装了WinMain函数和消息处理循环,因此任何使用MFC进行编程的程序都不能脱离开该类。实际上使用CWinApp类非常简单,主要是派生一个用户自己的应用类,如CMyApp,然后只需重载CWinApp类的InitInstance()函数:

BOOL CMyApp::InitInstance()
{
 m_pMainWnd=new CMainFrame();
 ASSERT(m_pMainWnd!=NULL); //error checking only
 m_pMainWnd->ShowWindow(m_nCmdShow);
 m_pMainWnd->UpdateWindow();
 return TRUE;


  至于所需要的主框架类,则可以直接使用ClassWizard实用程序生成,该类的头文件与实现代码可以与CMyApp类的头文件和实现代码放在一起。注意,这里由一个技巧,由于ClassWizard的使用需要有相应的CLW文件存在,而收工建代码时没有对应的CLW文件,因此不能直接使用,解决办法是进入App Studio实用工具后使用ClassWizard,此时系统会发觉不存在相应的CLW文件,系统将提示你重建CLW文件并弹出相应对话框,这时候你不需要选择任何文件就直接选择OK按钮,这样系统将为你产生一个空的CLW文件,这样就可以使用ClassWizard实用工具了。为了将CWinApp和CFrameWnd的派生类有机地结合在一起,只需在CFrameWnd派生类的构造函数中进行窗口创建即可。典型代码如下:

CMainFrame::CMainFrame()
{
Create(NULL,"DDE Client Application",WS_OVERLAPPEDWINDOW,rectDefault, NULL,MAKEINTRESOURCE(IDR_MAINFRAME));


  采用ClassWizard实用程序生成相关类代码后,所有的类的其它实现和维护就同普通由AppWizard实用程序产生的代码一样了。

  2、基于对话框的程序

  有些主要用于数据的输入和输出等的应用在使用时没有必要改变窗口大小,典型的如各种联机注册程序,这些使用对话框作为应用的主界面就足够了,而且开发此类应用具有方便快捷的特点,代码也比较短小,如果直接采用各种控制类生成所需要的控制就特别麻烦。在Visual C++ 4.x版本中使用AppWizard就可以直接生成基于对话框的应用。在Visual 1.x中没有此功能,因此这类应用需要程序员自己实现。实际上使用MFC实现基于对话框的应用非常简单,同样只使用两个MFC类作为基类,这两个类为CWinApp类和CDialog类。所使用的对话框主界面同样可以先用App Studio编辑对话框界面,再使用ClassWizard产生相应代码框架,然后修改CMyApp类的声明,增加一个该对话框类的成员变量m_Mydlg,最后修改CMyApp类的InitInstance()函数如下:

BOOL CMyApp::InitInstance()
{
m_Mydlg.DoModal();
return TRUE;


  八、MFC应用的人工优化

  使用C/C++编写Windows程序的优点就是灵活高效,运行速度快,Visual C++编译器本身的优化工作相当出色,但这并不等于不需要进行适当的人工优化,为了提高程序的运行速度,程序员可以从以下几方面努力:

  1) 减少不必要的重复显示

  相对来讲,Windows的GDI操作是比较慢的,因此在程序中我们应该尽可能地控制整个视口的显示和更新,如果前后两此数据不发生变化,那么就不要重新进行视口的GDI图形操作,尤其对于背景图显示时非万不得已时不要重绘,同时不要经常五必要的刷新整个窗口。

  2) 在视口极小化时不要进行更新屏幕操作

  在窗口处于极小化时没有必要继续进行视口更新工作,这样可以显著提高速度。为此需要在子窗口一级捕获上述信息(视口不能捕获该类信息),再在视口中进行相应操作。如下代码片段所示:

  首先在子窗口类中添加如下程序段:

void CMyChild::OnSysCommand(UINT nID,LPARAM lparam)
{
CMDIChildWnd::OnSysCommand(nID,lparam);
if(nID==SC_MINIMIZE)
{
RedrawFlag=0;
}
else
RedrawFlag=1;


  再在视口更新时中修改如下:

void CMyChart::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )
{
 if(pChild->RedrawFlag)
 {
  InvalidateRect(&r,FALSE);
  TRACE("Now In CMyChart::OnUpdate ");
 }


  至于上中pChild指针可以在视口创建的例程中获取:

  pChild=(CMyChild*)GetParent();

  3) 使用永久性的资源

  在频繁进行GDI输出的视口中,如在监控软件中常常使用的趋势图显示和棒图显示等等,应该考虑在类层次上建立频繁使用的每种画笔和刷子,这可以避免频繁的在堆中创建和删除GDI对象,从而提高速度。

  4) 使用自有设备描述句柄

  亦即在创建视口时通过指定WM_OWNDC风格来拥有自己的显示设备句柄,这虽然会多消耗一些内存,一个DC大约占800字节的内存,但是这避免了每次进行GDI操作前创建并合理初始化显示设备句柄这些重复操作。特别是要自定义坐标系统和使用特殊字体的视口这一点尤其重要。在16M机器日益普遍的今天为了节约一点点内存而降低速度的做法并不可取。

  5) 优化编译时指定/G3选项和/FPix87选项

  /G3选项将强迫编译器使用386处理器的处理代码,使用嵌入式协处理器指令对那些频繁进行浮点运算的程序很有帮助。采用这两种编译开关虽然提高了对用户机型的要求,但在386逐渐被淘汰,486市场大幅度萎缩,586市场日益普及的今天上述问题已经不再成为问题了。

  九、结束语

  总体上讲,使用Visual C++和MFC类库进行Windows编程是非常方便的,本文中所提到的一些看法只代表本人的观点,经验也只是笔者根据近年使用MFC进行Windows编程的总结,在此写出来是希望对那些使用VC和MFC进行Windows编程的同行有所帮助,如有不同看法欢迎与笔者联系讨论。

上一篇:应用MFC开发高级应用程序(中)
下一篇:VC++与MATLAB混合编程及其应用