MFC编程1(写给MFC初级者的速成宝典)

开发技术类 专栏收录该内容
4 篇文章 0 订阅

[转载]MFC编程1(写给MFC初级者的速成宝典)

原作者姓名 http://www.aidelphi.com/ShowTo
正文
MFC编程1
  微软基础类库(MFC: Microsoft Foundation
Class)是微软为Windows程序员提供的一个面向对象的Windows编程接口,它大大简化了Windows编程工作。使用MFC类库的好处是:首先,MFC提供了一个标准化的结构,这样开发人员不必从头设计创建和管理一个标准Windows应用程序所需的程序,而是“站在巨人肩膀上”,从一个比较高的起点编程,故节省了大量的时间;其次,它提供了大量的代码,指导用户编程时实现某些技术和功能。
  对用户来说,用MFC开发的最终应用程序具有标准的、熟悉的Windows界面,这样的应用程序易学易用;另外,新的应用程序还能立即支持所有标准Windows特性,而且是用普通的、明确定义的形式。事实上,也就是在Windows应用程序界面基础上定义了一种新的标准——MFC标准。
  一、MFC类库概念和组成
  类库是一个可以在应用中使用的相互关联的C++类的集合。Microsoft提供了一个基础类库MFC,其中包含用来开发C++和C++
Windows应用程序的一组类。基础类库的核心是以C++形式封装了大部分的Windows
API。类库表示窗口、对话框、设备上下文、公共GDI对象如画笔、调色板、控制框和其他标准的Windows部件。这些类提供了一个面向Windows中结构的简单的C++成员函数的接口。
  MFC可分为两个主要部分:(1)基础类(2)宏和全程函数。
  1、MFC基础类:MFC中的类按功能来分可划分为以下几类:
    基类 应用程序框架类 应用程序类 命令相关类 文档/视类
    线程类 可视对象类 窗口类 视类 对话框类
    属性表 控制类 菜单类 设备描述表 绘画对象类
    通用类 文件 诊断 异常 收集
    模板收集 其他支持类    OLE2类 OLE基类 OLE可视编辑包装程序类
    OLE可视编辑服务器程序类 OLE数据传输类 OLE对话框类 其他OLE类 数据库类
  2、宏和全局函数:若某个函数或变量不是某个类的一个成员,那么它是一个全程函数或变量。Microsoft基本宏和全程函数提供以下功能:
    数据类型运行时刻对象类型服务诊断服务异常处理
    CString格式化及信息框显示消息映射应用消息和管理对象连接和嵌入(OLE)服务
    标准命令和Windows IDs  
  3、约定:全程函数以“Afx”为前缀,所有全程变量都是以“afx”为前缀,宏不带任何特别前缀,但是全部大写。
  常见的全局函数和宏有:AfxGetApp,AfxGetMainWnd,AfxMessageBox,DEBUG_NEW等,我们会在后面的章节中用到并对它们进行介绍。
  从继承关系来看,又可将MFC中的类分成两大类:大多数的MFC类是从CObject继承下来;另外一些类则不是从CObject类继承下来,这些类包括:字符串类CString,日期时间类CTime,矩形类CRect,点CPoint等,它们提供程序辅助功能。
  由于MFC中大部分类是从CObject继承下来的,CObject类描述了几乎所有的MFC中其他类的一些公共特性,因此我们有必要理解CObject类。
  我们首先查看一下CObject类的定义,CObject类定义如下清单2.1所示:
  清单2.1CObject类的定义
  // class CObject is the root of all compliant(顺从的、适应的) objects
  class CObject
  {
  public:
  // Object model (types, destruction, allocation)
  virtual CRuntimeClass* GetRuntimeClass() const;
  virtual ~CObject(); // virtual destructors are necessary
  // Diagnostic(诊断的) allocations(配置)
  void* PASCAL operator new(size_t nSize);
  void* PASCAL operator new(size_t, void* p);
  void PASCAL operator delete(void* p);
  #if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
  // for file name/line number tracking using DEBUG_NEW
  void* PASCAL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
  #endif
  // Disable the copy constructor and assignment by default so you will get
  // compiler errors instead of unexpected behaviour if you pass objects
  // by value or assign objects.
  protected:
  CObject();
  private:
  CObject(const CObject& objectSrc); // no implementation(执行)
  void operator=(const CObject& objectSrc); // no implementation
  // Attributes
  public:
  BOOL IsSerializable() const;
  BOOL IsKindOf(const CRuntimeClass* pClass) const;
  // Overridables
  virtual void Serialize(CArchive& ar);
  // Diagnostic Support
  virtual void AssertValid() const;
  virtual void Dump(CDumpContext& dc) const;
  // Implementation
  public:
  static const AFX_DATA CRuntimeClass classCObject;
  #ifdef _AFXDLL
  static CRuntimeClass* PASCAL _GetBaseClass();
  #endif
  };
  CObject类为派生类提供了下述服务:
  1、对象诊断。MFC提供了许多诊断特性,它可以:
  输出对象内部信息:CDumpContext类与CObject的成员函数Dump配合,用于在调试程序时输出对象内部数据。
  对象有效性检查:重载基类的AssertValid成员函数,可以为派生类的对象提供有效性检查。
  运行时访问类的信息:MFC提供了一个非常有用的特性,它可以进行运行时的类型检查。如果从CObject派生出一个类,并使用了以下三个宏(IMPLEMENT_DYNAMIC,IMPLEMENT_DYNCREATE或IMPLEMENT_SERIAL)之一,就可以:
  运行时访问类名
  安全可靠的把通用的CObject指针转化为派生类的指针
  比如,我们定义一个主窗口类
  CMyFrame:public CFrameWnd
  {
  ......
  }
  然后我们使用这个类:
  CMyFrame *pFrame=(CMyFrame*)AfxGetMainWnd();
  pFrame->DoSomeOperation();
  AfxGetMainWnd是一个全局函数,返回指向应用程序的主窗口的指针,类型为CWnd*,因此我们必须对它进行强制类型转换,但我们如何知道是否转换成功了呢?我们可以使用CObject的IsKindOf()成员函数检查pFrame的类型,用法如下:
  ASSERT(pFrame->IsKindOf(RUN_TIMECLASS(CMyFrame)));
  将上一语句插入到pFrame->DoSomeOperation()之前,就可以在运行时作类型检查,当类型检查失败时,引发一个断言(ASSERT[断言声称]),中断程序执行。
  2、对象持续性。
  通过与非CObject派生的档案类CArchive相结合,提供将多个不同对象以二进制形式保存到 磁盘文件(Serilization)中以及根据磁盘文件中的对象状态数据在内存中重建对象(Deserilization)的功能。
  然而,MFC不仅仅是一个类库,它还提供了一层建立在Windows API的C++封装上的附加应用程序框架。该框架提供了Windows程序需要的多数公共用户界面。
  所谓应用程序框架指的是为了生成一般的应用所必须的各种软组件的集成。应用框架是类库的一种超集。一般的类库只是一种可以用来嵌入任何程序中的、提供某些特定功能(如图象处理、串行通信)的孤立的类的集合,但应用框架却定义了应用程序的结构,它的类既相互独立,又相互依赖,形成一个统一的整体,可以用来构造大多数应用程序。中国用户熟悉的Borland C++的DOS下的Turbo Vision和Windows下OWL(Object Windows Language)都是应用框架的例子。
  下面我们举个具体的例子来说明MFC所提供的应用程序框架,程序如清单2.2。
  清单2.2应用程序框架示例
  #include<afxwin.h>
  //derived(起源) an application class
  class CMinMFCApp:public CWinApp
  {
  public:
  BOOL InitInstance();
  };
  //Derive the main window class
  class CMainWindow:public CFrameWnd
  {
  public:
  CMainWindow();
  DECLARE_MESSAGE_MAP()
  };
  BEGIN_MESSAGE_MAP(CMainWindow,CFrameWnd)
  END_MESSAGE_MAP()
  /*CMinMFCApp Member Functions*/
  BOOL CMinMFCApp::InitInstance()
  {
  m_pMainWnd=new CMainWindow();
  m_pMainWnd->ShowWindow(m_nCmdShow);
      m_pMainWnd->UpdateWindow();
  return TRUE;
  }
  /*CMainWindow member functions*/
  CMainWindow::CMainWindow()//constructor
  {
  Create(NULL,"Min MFC Application",WS_OVERLAPPEDWINDOW,rectDefault,NULL,NULL);
  }
  /*an instance of type CMinMFCApp*/
  CMinMFCApp ThisApp;
  清单2.2程序段定义了一个最小的MFC应用程序所需的框架程序。其中声明了CMinMFCApp类,它是从应用程序类CWinApp中派生下来的;和窗口CMainWindow类,它是从框架窗口CFrameWnd类派生出来。我们还用CMinMFCApp定义了一个全局对象ThisApp。读者也许会问,为什么没有WinMain函数?因为MFC已经把它封装起来了。在程序运行时,MFC应用程序首先调用由框架提供的标准的WinMain函数。在WinMain函数中,首先初始化由CMinMFCApp定义的唯一的实例,然后调用CMinMFCApp继承CWinApp的Run成员函数,进入消息循环。退出时调用CWinApp的ExitInstance函数。
  由上面的说明可以看到,应用程序框架不仅提供了构建应用程序所需要的类(CWinApp,CFrameWnd等),还定义了程序的基本执行结构。所有的应用程序都在这个基本结构基础上完成不同的功能。
  MFC除了定义程序执行结构之外,还定义了三种基本的主窗口模型:单文档窗口,多文档窗口和对话框作为主窗口。
  Visual C++提供了两个重要的工具,用于支持应用程序框架,它们就是前面提到AppWizard和ClassWizard。AppWizard用于在应用程序框架基础上迅速生成用户的应用程序基本结构。ClassWizard用于维护这种应用程序结构。
  二、MFC的优点
  Microsoft MFC具有以下不同于其它类库的优势:
       完全支持Windows所有的函数、控件、消息、GDI基本图形函数,菜单及对话框。类的设计以及同API函数的结合相当合理。
       使用与传统的Windows API同样的命名规则,即匈牙利命名法。
       进行消息处理时,不使用易产生错误的switch/case语句,所有消息映射到类的成员函数,这种直接消息到方法的映射对所有的消息都适用。它通过宏来实现消息到成员函数的映射,而且这些函数不必是虚拟的成员函数,这样不需要为消息映射函数生成一个很大的虚拟函数表(V表),节省内存。
       通过发送有关对象信息到文件的能力提供更好的判定支持,也可确认成员变量。
       支持异常错误的处理,减少了程序出错的机会
       运行时确定数据对象的类型。这允许实例化时动态操作各域
       有较少的代码和较快的速度。MFC库只增加了少于40k的目标代码,效率只比传统的C Windows程序低5%。
       可以利用与MFC紧密结合的AppWizard和ClassWizard等工具快速开发出功能强大的应用程序。
       另外,在使用MFC时还允许混合使用传统的函数调用。
  三、MFC对消息的管理
  Windows消息的管理包括消息发送和处理。为了支持消息发送机制,MFC提供了三个函数:SendMessage、PostMessage和SendDlgItemMessage。而消息处理则相对来说显得复杂一些。MFC采用了一种新的机制取代C语言编程时对Windows消息的Switch/Case分支,简化了Windows编程,使程序可读性、可维护性大大提高。
  1、MFC对消息的处理
  MFC采用一种消息映射机制来决定如何处理特定的消息。这种消息映射机制包括一组宏,用于标识消息处理函数、映射类成员函数和对应的消息等。其中,用afx_msg放在函数返回类型前面,用以标记它是一个消息处理成员函数。类若至少包含了一个消息处理函数,那么还需要加上一个DECLARE_MESSAGE_MAP()宏,该宏对程序执行部分所定义的消息映射进行初始化。清单2.3演示了消息处理函数的例子:
  清单2.3 消息处理函数例子
  class CMainFrame:CFrameWnd{
  public:
  CMainFrame();
  protected:
  //{{AFX_MSG(CMainFrame)
  afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
  afx_msg void OnEditCopy();
  afx_msg void OnClose();
  //}}AFX_MSG
  DECLARE_MESSAGE_MAP()
  };
  成员函数OnCreate,OnEditCopy,OnClose分别用来处理消息WM_CREATE、ID_EDIT_COPY和WM_CLOSE。其中,WM_CREATE和WM_CLOSE是系统预定义消息,包含在Windows.h中。而ID_EDIT_COPY是菜单Edit->Copy的标识,也就是用户选择Edit->Copy菜单项时产生的消息,一般在 资源文件头文件中定义。在类的实现部分给出这三个成员函数的定义,以及特殊的消息映射宏。上面的例子的消息映射宏定义如下:
  BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)
  ON_WM_CREATE()
  ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
  ON_WM_CLOSE()
  END_MESSAGE_MAP()
  消息映射宏由BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()。其中,BEGIN_MESSAGE_MAP宏包含两个参数CMainFrame类和CFrameWnd,分别代表当前定义的类和它的父类。在BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()之间,包含了主窗口要处理的各个Windows消息的入口。在本例中,包含三个消息。其中ON_WM_CREATE被用来指定缺省的成员函数OnCreate与WM_CREATE相对应。在MFC中,包含了大量的预定义消息映射宏,用来指定各种成员函数与各种形如WM_XXXX消息相对应。如ON_WM_CLOSE宏指定了WM_CLOSE消息的处理成员函数为OnClose。这时侯,只需要写出要处理的消息就够了,不必再写出处理函数。消息映射宏ON_COMMAND则被用来将菜单项和用户自定义的命令同它们的处理成员函数联系起来。在上例中,用户选择Edit->Copy菜单项时,系统执行OnEditCopy()函数。ON_COMMAND宏的一般定义形式如下:
  ON_COMMAND(command,command_function)
  其中,command为菜单消息或用户自定义消息,command_function为消息处理函数。MFC允许用户自定义消息,常量WM_USER和第一个消息值相对应,用户必须为自己的消息定义相对于WM_USER的偏移值,偏移范围在0~0x3FFF之间,这对绝大多数程序来说都是够用的。用户可以利用#define语句直接定义自己的消息:
  #define WM_USER1 (WM_USER+0)
  #define WM_USER2 (WM_USER+1)
  #define WM_USER3 (WM_USER+2)
  下表列出了Windows95中Windows消息值的范围。
    常 量值消息值范围意      义
    WM_USER0x04000x0000-0x03FFWindows消息
      0x0400-0x7FFF用户自定义的消息
      0x8000-0xBFFFWindows保留值
      0xC000-0xFFFF供应用使用的字符串消息

  为了说明如何使用用户自定义消息,我们看一个例子,见程序清单2.4:
  清单2.4 使用用户自定义消息
  #include<afxwin.h>
  #define CM_APPLE (WM_USER+0)
  #define CM_ORANGE (WM_USER+1)
  class CMainFrame:CFrameWnd{
  public:
  CMainFrame();
  protected:
  afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
  afx_msg void OnClose();
  //handle user select apple
  afx_msg LRESULT CMApple(WPARAM wParam, LPARAM lParam);
  //handle user select orange
  afx_msg LRESULT CMOrange(WPARAM wParam, LPARAM lParam);
  DECLARE_MESSAGE_MAP()
  };
  //相应的消息映射如下:
  BEGIN_MESSAGE_MAP(CMainFrame,CFrameWnd)
  ON_WM_CREATE()
  ON_MESSAGE(CM_APPLE, CMApple)
  ON_MESSAGE(CM_ORANGE,CMOrange)
  ON_WM_CLOSE()
  END_MESSAGE_MAP()
  第一个ON_MESSAGE宏用于指定 CM_APPLE 命令消息的处理成员函数为CMApple,而第二个ON_MESSAGE宏用于指定CM_ORANGE命令消息的处理函数为CMOrange。
  2、消息的发送
  Windows应用程序允许应用程序向自己发送消息、向其他应用程序发送消息,甚至可以向Windows操作系统本身发送消息(比如要求关闭操作系统或重新启动操作系统)。Windows提供了三个API函数用于发送消息,这三个函数是:SendMessage、PostMessage和SendDlgItemMessage。
  SendMessage用于向窗口发送消息,该函数说明如下:
  LRESULT SendMessage(HWND hWnd, //消息要发往的窗口的句柄
  UINT Msg, //要发送的消息
  WPARAM wParam, //消息的第一个参数
  LPARAM lParam //消息的第二个参数);
  该函数调用目标窗口的窗口函数,直到目标窗口处理完该消息才返回。
  PostMessage函数同SendMessage类似,它把消息放在指定窗口创建的线程的消息队列中,然后不等消息处理完就返回,而不象SendMessage那样必须等到消息处理完毕才返回。目标窗口通过GetMessage或PeekMessage从消息队列中取出并处理。PostMessage函数说明如下:
     BOOL PostMessage(HWND hWnd, //消息发往的窗口
  UINT Msg, //要发送的消息
  WPARAM wParam, //消息的第一个参数
  LPARAM lParam //消息的第二个参数);
  SendDlgItemMessage函数用于向对话框的某个控制发送消息,函数声明如下:
  LONG SendDlgItemMessage(HWND hDlg, //对话框句柄
  int nIDDlgItem, //对话框控件的ID
  UINT Msg, //要发送的消息
  WPARAM wParam, //消息的第一个参数
  LPARAM lParam //消息的第二个参数);
  MFC将这三个函数封装为CWnd类的成员函数,隐藏了窗口句柄和对话框句柄。这三个成员函数用于向本窗口发送消息,函数的说明如下:
  LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
  BOOL PostMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
  LRESULT SendDlgItemMessage( int nID, UINT message, WPARAM wParam=0, LPARAM lParam=0);
  四、学习MFC的方法
  首先要对Windows API有一定的了解,否则无法深入学习MFC。至少要知道Windows对程序员来说意味着什么,它能完成什么工作,它的一些常用数据结构等。
  另一点是不要过分依赖于Wizards。Wizards能做许多工作,但同时掩饰了太多的细节。应当看看AppWizard和ClassWizard为你所做的工作。在mainfrm.cpp中运行调试器来观察一下MFC运行的流程。除非你理解了生成的代码的含义,否则无法了解程序是如何运行。
  还有很重要的一点就是要学会抽象的把握问题,不求甚解。许多人一开始学习Visual
C++就试图了解整个MFC类库,实际上那几乎是不可能的。一般的学习方法是,先大体上对MFC有个了解,知道它的概念、组成、基本约定等。从最简单的类入手,由浅入深,循序渐进、日积月累的学习。一开始使用MFC提供的类时,只需要知道它的一些常用的方法、外部接口,不必要去了解它的细节和内部实现,把它当做一个模块或者说黑盒子来用,这就是一种抽象的学习方法。在学到一定程度时,再可以深入研究,采用继承的方法对原有的类的行为进行修改和扩充,派生出自己所需的类。
  学习MFC,最重要的一点是理解和使用MFC类库,而不是记忆。
  AppWizard所创建的文件
  根据可选项,AppWizard所创建的文件会略有不同。标准的AppWizard文件包括:
·  工作区文件、项目文件和make文件
·  应用程序源文件和头文件
·  资源文件
·  预编译头文件
·  按可选项增加的AppWizard文件
  一、工作区、项目文件和make文件
  Hello.dsw:这是MFC自动生成的工作区文件,它包含当前工作区所包含的项目的信息。
  Hello.dsp:这是MFC生成的项目文件,它包含当前项目的设置、所包含的文件等信息。
  Hello.MAK:这是MFC项目的项目文件,这也是与NMAKE兼容的文件。如果选择了External make文件可
  选项,则可人工对它编辑,但不能利用Visual C++许多项目编辑特性。
  Hello.CLW:这个文件含有被ClassWizard用来编辑现有类或增加新类的信息。ClassWizard还用这个文件
  来保存创建和编辑消息映射和对话框数据所需的信息,或是创建虚拟成员函数所需的信息。
  二、应用程序源文件和头文件
  根据应用程序的类型—单文档、多文档或基于对话框的,AppWizard将创建下述应用程序源文件和头文件中的某些文件。在本例中,AppWizard生成了如下文件:
  Hello.h:这是应用程序的主头文件,它含有所有全局符号和用于包含其它头文件的#include伪指令。
  Hello.CPP:这个文件是应用程序的主源文件。它将创建CHelloApp类的一个对象(从CWinApp派生),
  并覆盖InitInstance成员函数。
  MainFrm.cpp,MainFrm.h:
 这两个文件将从CFrameWnd(SDI应用程序)或CMDIFrameWnd(MDI应用程序)派生CMainFrame类。如果在AppWizard的Application
Options页(6步中的第4步)中选择了对应的可选项的话,CMainFrame类将处理工具条按钮和状态条的创建。MAINFRM.CPP文件还含有MFC应用程序提供的默认工具条按钮的对象ID——叫做buttons数组。
  HelloDoc.cpp,HelloDoc.h
 这些文件从CDocument类派生并实现名为CHelloDoc的文档类,并含有用于初始化文档、串行化(保存和装入)文档和用于调试诊断的一些成员函数的框架。
  HelloView.cpp,HelloView.h
 这些文件派生并实现名为CHelloView的视类,用于显示和打印文档数据。CHelloView类是从CView或它的派生类派生出来的,含有绘制视和用于调试诊断的一些成员函数框架。
  三、资源文件:AppWizard会创建一些与资源相关的文件。
  Hello.RC,RESOURCE.H,Hello.rc2
 这是项目的头文件及其资源文件。资源文件含有一般MFC应用程序的默认菜单定义和加速键表、字符串表。它还指定了缺省的About对话框和一个 图标文件(RES/Hello.ICO)。资源文件了标准的MFC类的资源。如果指定了支持工具条,它还将指定工具条位图文件(RES/TOOLBAR.BMP)。Hello.rc2用于存放Visual Studio不可直接编辑的资源。
  四、预编译头文件:STDAFX.CPP,STDAFX.H
  这两个文件用于建立一个预编译的头文件Hello.PCH和一个预定义的类型文件STDAFX.OBJ。由于MFC体系结构非常大,包含许多头文件,如果每次都编译的话比较费时。因此,我们把常用的MFC头文件都放在stdafx.h中,如afxwin.h、afxext.h、afxdisp.h、afxcmn.h等,然后让stdafx.cpp包含这个stdafx.h文件。这样,由于编译器可以识别哪些文件已经编译过,所以stdafx.cpp就只编译一次,并生成所谓的预编译头文件(因为它存放的是头文件编译后的信息,故名)。如果读者以后在编程时不想让有些MFC头文件每次都被编译,也可以将它加入到stdafx.h中。采用预编译头文件可以加速编译过程。
  应用程序执行机制
  一、WinMain函数
  在DOS下,程序的执行是从main函数开始的。在Windows下,对应的函数是WinMain。但是,如果浏览Hello程序的所有的方法和全局函数,是找不到WinMain函数的。MFC考虑到典型的Windows程序需要的大部分初始化工作都是标准化的,因此把WinMain函数隐藏在应用程序的框架中,编译时会自动将该函数链接到可执行文件中。程序员可以重写WinMain函数,但一般不需要这么做。
 下面的程序清单3-1给出了WinMain函数的代码。其中,_tWinMain函数在/DevStudio/Vc/Mfc/src/AppModul.cpp中定义,它所调用的AfxWinMain函数在同一目录下的WinMain.cpp中定义。名字是_tWinMain函数而不是WinMain,是考虑到对不同字符集的支持,在tchar.h中有_tWinMain的宏定义。在ANSI字符集下编译时,_tWinMain就变成WinMain,在Unicode下编译时,_tWinMain就变成wWinMain。
  提示:Unicode是具有固定宽度、统一的文本和字符的编码标准。由于Unicode采用的是16位编码,因此可以包含世界各地的书写系统的字符和技术符号(如中文也在Unicode之中),从而克服了ASCII码在表示多语言文本上的不足之处,扩大了ASCII码7位编码方案的好处。Unicode同等地对待所有的字符,并且在表示各种语言的任何字符时既不需要换码序列(escape)也不需要控制代码。Win32和Visual
C++很好的支持Unicode字符集。
  清单3-1 _tWinMain函数定义
  // export WinMain to force(强制) linkage(联接) to this module
  extern int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  LPTSTR lpCmdLine, int nCmdShow);
  #ifdef _MAC
  extern "C" int PASCAL
  #else
  extern "C" int WINAPI
  #endif
  _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int
nCmdShow)
  {
  // call shared/exported WinMain
  return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
  }
  AfxWinMain函数定义:
  // Standard WinMain implementation(执行)
  // Can be replaced as long as &#39;AfxWinInit&#39; is called first
  int AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
  LPTSTR lpCmdLine, int nCmdShow)
  {
  ASSERT(hPrevInstance == NULL);
      int nReturnCode = -1;
  CWinApp* pApp = AfxGetApp();
  // AFX internal initialization
  if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
  goto InitFailure;
      // App global initializations (rare)
  ASSERT_VALID(pApp);
  if (!pApp->InitApplication())
  goto InitFailure;
  ASSERT_VALID(pApp);
      // Perform specific initializations
  if (!pApp->InitInstance())
  {
  if (pApp->m_pMainWnd != NULL)
  {
  TRACE0("Warning: Destroying non-NULL m_pMainWnd/n");
  pApp->m_pMainWnd->DestroyWindow();
  }
  nReturnCode = pApp->ExitInstance();
  goto InitFailure;
  }
  ASSERT_VALID(pApp);
      nReturnCode = pApp->Run();
  ASSERT_VALID(pApp);
  InitFailure:
  #ifdef _DEBUG
  // Check for missing AfxLockTempMap calls
  if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
  {
  TRACE1("Warning: Temp map lock count non-zero (%ld)./n",
  AfxGetModuleThreadState()->m_nTempMapLock);
  }
  AfxLockTempMaps();
  AfxUnlockTempMaps(-1);
  #endif
  AfxWinTerm();
  return nReturnCode;
  }
  应用程序执行时,Windows自动调用应用程序框架内部的WinMain函数。如清单3-1所示,WinMain函数会查找该应用程序的一个全局构造对象,这个对象是由CWinApp派生类构造的,有且只有一个。它是一个全局对象,因此在程序启动时,它就已经被构造好了。
  随后,WinMain将调用这个对象的InitApplication和InitInstance成员函数,完成应用程序实例的初始化工作。随后,WinMain调用Run成员函数,运行应用程序的消息循环。在程序结束时,WinMain调用AfxWinTerm函数,做一些清理工作。
  二、应用程序类
  每个应用程序必须从CWinApp派生出自己的应用程序类,并定义一个全局的对象。该应用程序类包含了Windows下应用程序的初始化、运行和结束过程。基于框架建立的应用程序必须有一个(且只能有一个)从CWinApp派生的类的对象。在Hello程序中,我们从CWinApp中派生出一个CHelloApp类,并定义了一个全局对象theApp。CHelloApp类在hello.cpp中定义。
  要访问应用程序类构造的对象,可以调用全局函数AfxGetApp()。AfxGetApp()返回一个指向全局对象的指针。可以通过对它进行强制类型转换,转换为我们派生的应用程序类。
  比如:CHelloApp* pApp=(CHelloApp*)AfxGetApp();
  在CHelloApp应用程序类中,我们还重载了CWinApp的成员函数InitInstance。InitInstance函数主要完成以下工作:设置注册数据库,载入标准设置(最近打开文件列表等)、注册文档模板。其中注册文档模板过程中隐含地创建了主窗口。接着,处理命令行参数,显示窗口,然后返回、进入消息循环。下面的程序清单3.2给出了Hello程序的InitInstance函数代码。
  清单3.2 InitInstance函数
  BOOL CHelloApp::InitInstance()
  {
  AfxEnableControlContainer();
  // Standard initialization
  // If you are not using these features and wish to reduce the size
  // of your final executable, you should remove from the following
  // the specific initialization routines you do not need.
  #ifdef _AFXDLL
  Enable3dControls(); // Call this when using MFC in a shared DLL
  #else
  Enable3dControlsStatic(); // Call this when linking to MFC statically
  #endif
  // Change the registry key under which our settings are stored.
  // You should modify this string to be something appropriate
  // such as the name of your company or organization.
  SetRegistryKey(_T("Local AppWizard-Generated Applications"));
      LoadStdProfileSettings(); // Load standard INI file options (including MRU)
  // Register the application&#39;s document templates. Document templates
  // serve as the connection between documents, frame windows and views.
  CSingleDocTemplate* pDocTemplate;
  pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CHelloDoc),
  RUNTIME_CLASS(CMainFrame), // main SDI frame window
  RUNTIME_CLASS(CHelloView));
  AddDocTemplate(pDocTemplate);
  // Parse command line for standard shell commands, DDE, file open
  CCommandLineInfo cmdInfo;
  ParseCommandLine(cmdInfo);
  // Dispatch commands specified on the command line
  if (!ProcessShellCommand(cmdInfo))
  return FALSE;
  // The one and only window has been initialized, so show and update it.
  m_pMainWnd->ShowWindow(SW_SHOW);
  m_pMainWnd->UpdateWindow();
  return TRUE;
  }
  在CWinApp的派生类中,必须重载InitInstance函数,因为CWinApp并不知道应用程序需要什么样的窗口,它可以多文档窗口、单文档窗口,也可以是基于对话框的。
  Run成员函数
  WinMain在初始化应用程序实例后,就调用Run函数来处理消息循环。Run成员函数不断执行消息循环,检查消息队列中有没有消息。如果有消息,Run将其派遣,交由框架去处理,然后返回继续消息循环。如果没有消息,Run将调用OnIdle来做用户或框架可能需要在空闲时才做的工作,象后面我们讲到的用户接口更新消息处理等。如果既没有消息要处理,也没有空闲时的处理工作要做,则应用程序将一直等待,直到有事件发生。当应用程序结束时,Run将调用ExitInstance。消息循环的流程图如图3-10所示。
  图3-10 Run成员函数的消息循环
  关闭应用程序
  用户可以通过选择File-Exit菜单或点主窗口的关闭按钮,关闭主框架窗口,来终止应用程序。此时,应用程序类首先删除m_pMainWnd主框架窗口对象,然后退出Run函数,进而退出WinMain,在退出WinMain后删除TheApp对象。
  几种窗口类型
  一、框架窗口
  框架窗口为应用程序的用户界面提供结构框架,它是应用程序的主窗口,负责管理其包容的窗口,一个应用程序的最顶层的框架窗口是应用程序启动时创建的第一个窗口。
  MFC提供三种类型的框架窗口:单文档窗口,多文档窗口(MDI),对话框。MFC单文档窗口一次只能打开一个文档框架窗口,而MDI应用程序运行时,在应用程序的一个实例中打开多个文档框架窗口,这些窗口称作子窗口(Child
Window)。这些文档可以是同一类型的,也可以是不同类型的。如Visual
Studio就可以打开资源文件窗口和源程序窗口等不同类型的窗口。此时,激活不同类型的MDI子窗口,菜单也将相应变化。
  MFC提供了三个类CFrameWnd、CMDIFrameWnd、CMDIChildWnd和CDialog 分别用于支持单文档窗口、多文档窗口和对话框。
  CFrameWnd
  用于SDI框架窗口,形成单个文档及其视的边框。框架窗口既是应用程序的主框架窗口,也是当前文档对应的视图的边框。
  CMDIFrameWnd
  用于MDI应用程序的主框架窗口。主框架窗口是所有MDI文档窗口的容器,并与它们共享菜单条。MDI框架窗口是出现在 桌面中的顶层窗口。
  CMDIChildWnd
  用于在MDI主框架窗口中显示打开的各个文档。每个文档及其视图都有一个MDI子框架窗口,子框架窗口包含在MDI主框架窗口中。子框架窗口看起来类似一般的框架边框窗口,但它是包含在主框架窗口中,而不是位于桌面的,并且为主窗口所裁剪。而且MDI子窗口没有自己的菜单,它与主MDI框架窗口共享菜单。
  CDialog
  对话框是一种特殊类型的窗口,它的边框一般不可以调整,而且内部包含一些控件窗口。
  要生成一个单文档窗口,主窗口就必须从CFrameWnd派生;要生成一个多文档窗口,主窗口就必须从CMDIFrameWnd派生,而且其中的子窗口必须从CMDIChildWnd派生出来;而基于对话框的窗口程序就要从CDialog派生出主窗口类。
  子窗口
  子窗口就是具有WS_CHILD风格的窗口,且一定有一个父窗口。所有的控件都是子窗口。子窗口可以没有边框。子窗口被完全限制在父窗口内部。
  父窗口
  父窗口就是拥有子窗口的窗口。
  弹出式窗口
  具有WS_POPUP风格,它可以没有父窗口。这种窗口几乎什么都没有,可看作一个矩形区域。
  二、窗口的创建
  窗口的创建分为两步:第一步是用new创建一个C++的窗口对象,但是此时只是初始化窗口的数据成员,并没有真正创建窗口(这一点与一般的对象有所不同)。
  第一步:创建一个C++对象,其中CMainFrame是从CFrameWnd派生的对象。
  CMainFrame* pMyFrame=new CMainFrame();//用new操作符创建窗口对象
  或
  CMainFrame MyFrame;//定义一个窗口对象,自动调用其构造函数
  第二步:创建窗口。CFrameWnd的Create成员函数把窗口给做出来,并将其HWND保存在C++对象的公共
  数据成员m_hWnd中。
  pMyFrame->Create(NULL,“My Frame Window”);
  或
  MyFrame.Create(NULL,“My Frame Window”);
  Create函数的原形如下:
  BOOL Create( LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle =
WS_OVERLAPPEDWINDOW, const RECT& rect = rectDefault, CWnd* pParentWnd = NULL,
LPCTSTR lpszMenuName = NULL, DWORD dwExStyle = 0, CCreateContext* pContext =
NULL );
  Create函数第一个参数为窗口注册类名,它指定了窗口的图标和类风格。这里我们使用NULL做为其值,表明使用缺省属性。第二个参数为窗口标题。其余几个参数指定了窗口的风格、大小、父窗口、菜单名等。
  这个函数看起来比较复杂,对于CFrameWnd派生出来的窗口,我们可以使用LoadFrame从资源文件中创建窗口,它只需要一个参数。
  pMyFrame->LoadFrame(IDR_FRAME);
  LoadFrame使用该参数从资源中获取许多默认值,包括主边框窗口的标题、图标、菜单、加速键等。但是,在使用LoadFrame时,必须确保标题字符串、图标、菜单、加速键等资源使用同一个ID标识符。
  提示:在Hello程序的InitInstance中我们看不到创建窗口的过程。实际上,在
  pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CHelloDoc),
  RUNTIME_CLASS(CMainFrame), // main SDI frame window
  RUNTIME_CLASS(CHelloView));
  AddDocTemplate(pDocTemplate);
程序片段中,我们看到,CSingleDocTemplate构造函数的第二个参数就是IDR_MAINFRAME。在构造函数内部,已经通过调用m_pMainWnd->LoadFrame(IDR_MAINFRAME),完成了应用程序主窗口的创建过程。
 在InitInstance中,创建完窗口后,窗口调用ShowWindow成员函数来显示窗口。ShowWindow带一个参数,指示窗口以何种方式显示(最大化、最小化或一般)。缺省方式为SW_SHOW,但实际上我们经常希望应用程序启动时窗口最大化,此时可以将该参数该为SW_SHOWMAXMIZED,即调用m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
  在MainFrm.cpp中,我们还看到CMainFrame类有一个OnCreate方法。OnCreate成员函数定义如清单3.3。当调用Create或CreateEx时,操作系统会向窗口发送一条WM_CREATE消息。这一函数就是用来响应WM_CREATE消息的。
  清单3.3 OnCreate成员函数定义
  int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
  {
  if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
  return -1;
  if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
  {
  TRACE0("Failed to create toolbar/n");
  return -1; // fail to create
  }
  if (!m_wndStatusBar.Create(this) ||!m_wndStatusBar.SetIndicators(indicators,
  sizeof(indicators)/sizeof(UINT)))
  {
  TRACE0("Failed to create status bar/n");
  return -1; // fail to create
  }
  // TODO: Remove this if you don&#39;t want tool tips or a resizeable toolbar
  m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
  CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
  // TODO: Delete these three lines if you don&#39;t want the toolbar to be dockable
  m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
  EnableDocking(CBRS_ALIGN_ANY);
  DockControlBar(&m_wndToolBar);
  return 0;
  }
  在OnCreate函数中,首先调用CFrameWnd的缺省处理方法OnCreate完成窗口创建工作。后面是应用程序主窗口的特定工作,在上面程序中,创建了工具条和状态栏。可以在此处加入一些初始化工作,如从INI文件中载入设置,显示Splash
Window(启动画面)等。
  三、注册窗口
  在传统的Windows
C程序中,送给一个窗口的所有消息是在它的窗口函数中处理的。把一个窗口同它的窗口函数联系起来的过程称为注册窗口类。注册窗口包括对窗口指定一个窗口函数(给出窗口函数的指针)以及设定窗口的光标、背景刷子等内容。一个注册窗口类可以被多个窗口共享。注册窗口通过调用API函数RegisterClass来完成。
  在MFC下,框架提供了缺省的自动窗口注册过程。框架仍然使用传统的注册类,而且提供了几个标准的注册类,它们在标准的应用程序初始化函数中注册。调用AfxRegisterWndClass全局函数就可以注册附加的窗口类,然后把已经注册的类传给CWnd的Create成员函数。用户可以定制自己的注册过程,以提供一些附加的特性。比如设置窗口的图标、背景、光标等。下面是注册窗口的例子。
  BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
  {
  // TODO: Modify the Window class or styles here by modifying  the CREATESTRUCTcs
  UINT ClassStyle=CS_VREDRAW|CS_HREDRAW;
  cs.style=cs.style&(~FWS_ADDTOTITLE);//指定窗口标题风格,关闭自动添加文档标题的功能
  //指定窗口使用箭头光标、背景刷子使用比窗口颜色标号大一的颜色、图标使用IDR_MAINFRAME标识符指定的图标
  cs.lpszClass = AfxRegisterWndClass(ClassStyle,
  AfxGetApp()->LoadStandardCursor(IDC_ARROW),
  (HBRUSH)(COLOR_WINDOW+1),//for brush
  AfxGetApp()->LoadIcon(IDR_MAINFRAME));
  return TRUE;
  }
  注册窗口在CFrameWnd的PreCreateWnd方法中完成。从成员函数名字PreCreateWindow中就可以看出来,注册窗口的工作必须在调用Create函数创建窗口之前完成。
  四、关闭和销毁窗口
  框架窗口不仅维护窗口的创建,还管理着窗口的关闭和销毁过程。关闭窗口时,操作系统依次向被关闭的窗口发送WM_CLOSE和WM_DESTROY消息。WM_CLOSE消息的缺省处理函数OnClose将调用DestroyWindow来销毁窗口;最后,框架调用窗口的析构函数作清理工作并删除C++窗口对象。
 不要使用C++的delete操作符来销毁框架窗口,而应当采用CWnd的DestroyWindow成员函数来销毁。DestroyWindow首先删除子窗口,再删除窗口本身。若窗口以变量方式产生(即在堆栈上分配内存),该窗口对象会被自动清除。若对象是用new操作符创建的(也就是在堆上分配内存的),则需要用户自己处理。
  OnClose()常用功能:保存窗口的一些状态、工具条状态,提示保存未保存的数据等等。
  void CMainFrame::OnClose()
  {
  SaveBarState( "MyDockState" );//保存工具条状态
  CFrameWnd::OnClose();
  }
  五、窗口激活
  活动窗口必定是一个没有父窗口的顶层窗口,包括框架窗口和对话框。当顶层窗口被激活时,Windows向窗口发送WM_ACTIVATE消息,对此消息的缺省处理是将活动窗口设为有输入焦点。
 输入焦点用于表示哪个窗口有资格接收键盘输入消息。带有输入焦点的窗口或是一个活动窗口,或者是该活动窗口的子窗口。当一个顶层窗口获得输入焦点时,Windows向该窗口发送WM_SETFOCUS消息,此窗口可将输入焦点重定位到它的子窗口上。子窗口不会自动获得输入焦点。失去输入焦点的窗口会收到WM_KILLFOCUS消息。当子窗口拥有输入焦点时,父窗口就不会处理键盘输入了。
  使用菜单
  现在我们要在主窗口中加入自己的菜单。菜单编程一般分三步:
  1.编辑菜单资源,设置菜单属性(包括菜单名和ID);
2.用ClassWizard自动映射菜单消息和成员函数;
3. 手工编辑成员函数,加入菜单消息处理代码。
  一、编辑菜单资源
    仍然使用我们前面生成的Hello程序,编辑由AppWizard自动生成的菜单资源。要编辑菜单资源:
    菜单名菜单ID菜单提示(Prompt)
    Say &HelloID_SAY_HELLOSay hello to you!
    &RedID_SELECT_REDThe color is red.
    &BlueID_SELECT_BLUEThe color is blue.
    &YellowID_SELECT_YELLOWThe color is yellow.
  提示:如果菜单中要使用中文,则除了在菜单名一项中输入中文外,还要将菜单资源的语言属性设置为中文。方法是:鼠标右键单击资源视图的菜单资源IDR_MAINFRAME,弹出快捷菜单,选择Properties,弹出整个菜单资源的属性对话框,在Languages下拉列表框中选择Chinese
(P.R.C.)。这样以后菜单就可以正确使用和显示中文了。如果其他资源如对话框或字符串要使用中文,也要将该资源的语言属性改为Chinese(P.R.C)。
  现在关闭菜单编辑器窗口。我们要为Say
Hello菜单增加一个加速键CTRL+H。要编辑加速键,选择Accelerator资源类型,双击打开IDR_MAINFRAME加速键资源。要删除加速键,可以直接按Del键。要增加加速键,可以按Ins键,弹出加速键属性对话框。在ID下拉列表框中选择ID_SAY_HELLO,在Key一栏中输入H,完成加速键设置。
  二、用ClassWizard自动映射菜单消息和成员函数
  首先介绍一下ClassWizard的用法。
  1、用ClassWizard管理类和Windows消息
  ClassWizard有助于创建Windows消息和命令处理函数、创建和管理类、创建类成员变量、创建OLE Automation的方法和属性、创建数据库类以及其他一些工作。
 ClassWizard也有助于覆盖MFC类中的虚函数。先选类,再选择需要覆盖的虚函数。该过程的其余部分与消息处理是类似的。
  2、启动ClassWizard应用程序
  从View菜单或源程序编辑窗口右键菜单中选择ClassWizard(快捷键:Ctrl +W),Developer Studio将弹出MFC ClassWizard对话框。该对话框包含几个标签页,提供以下选项:
  Message Maps:它管理消息和成员函数之间的映射关系。
  Member Variables:它可以让用户加进一些数据成员,以便和各种控制进行数据交换。
  Automations:它提供了各种特性支持OLE2.0,包括为OLE Automation增加属性、方法以及处理事件。
  ActiveX Events:为ActiveX控件增加属性、方法以及为ActiveX控件事件增加处理函数。
  Class Info:它可以让用户创建新类,以便支持对话框和各种可视类(包括控制、窗口等)。还可以从类库文件导入类到当前工程中。
  3、Message Maps选项
  可以让用户加入成员函数来处理消息,删除成员函数以及对成员函数进行编辑。Message Maps页包括如下控制选项:
  Projects组合框:允许用户选择当前工作区中包含的工程。
  Class Name组合框:允许用户选择当前工程中的类。
  Objects IDs列表框:列出当前选中的类名及相关的ID。对窗口和视来说,这些ID为菜单标识符;对对话框来说,这些ID为控制框的ID。
  Messages列表框:列出当前所选类的可重载的虚方法以及可接收到的消息。
  Member Functions列表框:列出ClassName组合框中当前所选的项中所包含的所有成员函数。用户可以增加、删除成员函数,也可以对成员函数进行编辑。
  Add Class...按钮:它允许用户往工程里添加新类。在按钮右边有一个向下的小箭头,表明按此按钮将弹出一个菜单。菜单包含两项:  New...可以新建一个类;
  From a type Lib用于从一个类库中导入类。
  Add Function按钮:它允许用户往Member Functions列表框中加进一个新的消息处理成员函数,该新增成员函数被用来响应Message Maps列表中当前所选中的消息。
  Delete Function按钮:用于删除Member Functions列表框中所选中的项。
  Edit Code按钮:它允许用户对Member Functions中所选中的项进行编辑,此时Visual Studio 将关闭MFC ClassWizard对话框,并打开相应文件,并将光标定位在函数定义的开头处。
  消息框函数
  AfxMessageBox用来弹出一个消息框,它的函数原型有两种版本:
  int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );
  int AFXAPI AfxMessageBox( UINT nIDPrompt, UINT nType = MB_OK, UINT nIDHelp = (UINT) –1 );
  在第一种形式中,lpszText表示在消息框内部显示的文本,消息框的标题为应用程序的可执行文件名(如Hello)。在第二种形式中,nIDPrompt为要显示的文本字符串在字符串表中的ID。函数调用时会自动从字符串表中载入字符串并显示在消息框中。nType为消息框中显示的按钮风格和图标风格的组合,可以采用|(或)操作符组合各种风格。
  按钮风格
  MB_ABORTRETRYIGNORE 消息框中显示Abort、Retry、Ignore按钮
  MB_OK 显示OK按钮
  MB_OKCANCEL 显示OK、Cancel按钮
  MB_RETRYCANCEL 显示Retry、Cancel按钮
  MB_YESNO 显示Yes、No按钮
  MB_YESNOCANCEL 显示Yes、No、Cancel按钮
  图标风格
  MB_ICONINFORMATION 显示一个i图标,表示提示
  MB_ICONEXCLAMATION 显示一个惊叹号,表示警告
  MB_ICONSTOP 显示手形图标,表示警告或严重错误
  MB_ICONQUESTION 显示问号图标,表示疑问
  比如:要在消息框中显示一个问号、一个“Yes”按钮、一个“No”按钮,可以以下面的方式调用AfxMessageBox。
  AfxMessageBox(“Are you sure?”,MB_YESNO|MB_ICONQUESTION);
  还有一个与AfxMessageBox类似的函数MessageBox,它是CWnd的类成员函数:
  int MessageBox( LPCTSTR lpszText,LPCTSTR lpszCaption = NULL,UINT nType = MB_OK);
  与AfxMessageBox不同的是,它多了一个lpszCaption参数,从名字上就可以推断出它表示消息框的标题,这样就可以设置消息框的标题,而不必采用可执行文件名作为标题了。
 两个函数的区别:AfxMessageBox比MessageBox简单一些,因为它是一个全局函数所以不需要对应的一个窗口类,但是不能控制消息框标题,常用于调试程序时的内部数据输出或警告;MessageBox比较正式,常用在要提交的应用程序版本中,可以控制标题内容而不必采用含义不明的可执行文件名为标题。
更新命令用户接口(UI)消息
  一般情况下,菜单项和工具条按钮都不止一种状态,我们经常需要根据应用的内部状态来对菜单项和工具条按钮作相应的改变。例如,有效或无效,是否有检查标记。为此,MFC应用程序框架引入了更新命令用户接口消息来简化这一工作。
 UPDATE_COMMAND_UI就是更新命令用户接口消息,专门用于处理菜单项和工具条按钮的更新。每一个菜单命令都对应于一个更新命令用户接口消息。可以为更新命令用户接口消息编写消息处理函数来处理用户接口(包括菜单和工具条按钮)的更新。如果一条命令有多个用户接口对象(比如一个菜单项和一个工具条按钮),两者都被发送给同一个处理函数。这样,对于所有等价的用户接口对象来说,可以把用户接口更新代码封装在同一地方。
  一、用户接口更新原理
  为了理解用户接口更新机制,我们来看一下应用框架是如何实现用户接口更新的。当我们选择Edit菜单时,将产生一条WM_INITMENUPOPUP消息。框架的更新机制将在菜单拉下之前集体更新所有的项,然后再显示该菜单。
 为了更新所有的菜单项,应用框架按标准的命令发送路线把该弹出式菜单中的所有菜单项的更新命令都发送出去。通过匹配命令和适当的消息映射条目(形式为ON_UPDATE_COMMAND_UI),并调用相应的更新处理器函数,就可以更新任何菜单项。比如,Edit菜单下有Undo、Cut、Copy、Paste等四个菜单项,就要发送四条用户接口更新命令。如果菜单项的命令ID有一个更新处理器,它就会被调用进行更新;如果不存在,则框架检查该命令ID的处理函数是否存在,并根据需要使菜单有效或无效。
 如果在命令发送期间找不到对应于该命令的ON_UPDATE_COMMAND_UI项,那么框架就检查是否存在一个命令的ON_COMMAND项,如果存在,则使该菜单有效,否则就使该菜单无效(灰化)。这种更新机制仅适用于弹出式菜单,对于顶层菜单象File和Edit菜单,就不能使用这种更新机制。
 按钮的命令更新机制与菜单的命令接口更新机制类似,只是工具条按钮的命令接口更新在空闲循环时完成。
  二、用户接口更新机制编程
  当框架给处理函数发送更新命令时,它给处理函数传递一个指向CCmdUI对象的指针。这个对象包含了相应的菜单项或工具条按钮的指针。更新处理函数利用该指针调用菜单项或工具条的命令接口函数来更新用户接口对象(包括灰化,使,使能,选中菜单项和工具条按钮等)。下面我们使用前面的例子演示如何使用用户接口更新机制:
  1.按Ctrl+W激活ClassWizard,选择Message Map选项页。
  2.在Object IDs列表中选择 ID_SELECT_RED,在Messages列表中双击ON_UPDATE _COMMAND_UI条目,弹出Add Member Function对话框,缺省函数名为OnUpdateSelect Red,按OK按钮接收此函数名。
  3.现在手工编辑刚才生成的成员函数:
  清单3.5
  void CMainFrame::OnUpdateSelectRed(CCmdUI* pCmdUI)
  {
  pCmdUI->SetCheck(m_nColor==RED);   //RED菜单被选中
  }
  类似的,要根据某个状态开关菜单,也可以为菜单生成命令接口更新成员函数。比如,在Edit菜单中,如果当前剪贴板没有内容,Paste(粘贴)菜单应当设为无效,程序可以这么写:
  void CMainFrame::OnUpdateEditPaste(CCmdUI* pCmdUI)
  {
  pCmdUI->Enable(!IsClipboardEmpty());
  }
  其中IsClipboardEmtpy()是读者自己编写的函数,用于判断剪贴板中是否有内容
  快捷菜单
  在任何一个对象上按鼠标右键,就会弹出一个与所选当前对象相关的菜单,菜单中列出了一组针对当前对象的操作。现在我们来 讨论如何使用Visual C++为应用程序增加右键菜单。
  使用Visual Studio提供的构件工具Component Gallery(组件画廊)向框架窗口添加快捷菜单。选择Project->Add to
Project->Component and Controls菜单,弹出Component and Controls
Gallery对话框,选择Developer Studio Components目录,在该目录下选择Popup Menu构件。
  按Insert按钮。弹出Poup Menu对话框,在Add popup menu to下拉列表框中选择CMainFrame,点OK按钮,关闭Popup
Menu对话框。按Close按钮关闭Component and Controls
Gallery对话框。编译运行Hello,弹出窗口后按右键,就弹出如图3-16所示的快捷菜单。菜单中包含三项:cut、copy、paste。因为没有对应的消息矗立函数,所有这些菜单都是灰色的、非活动的。
  图 3-16
  现在,我们看看Component Gallery是如何实现快捷菜单的。首先看资源视图的菜单资源,Component Gallery在其中增加了一个ID为CG_IDR_POPUP_MAIN_FRAME的菜单,菜单中包含了刚才我们看到的三个菜单项:cut、copy、paste。切换到类视图,浏览CMainFrame类,可以看到CMainFrame增加了一个OnContextMenu的成员函数,它是CWnd的一个方法,用于处理鼠标右键单击消息,原型如下:
  afx_msg void OnContextMenu(CWnd* pWnd,CPoint point);
  其中pWnd指向右键单击的窗口,它可以是一个本窗口的一个子窗口。比如,我们在工具条上单击右键时也弹出同样的菜单,工具条就是框架窗口的一个子窗口。OnContextMenu函数定义如清单3.6所示。
  清单3.6 右键菜单
  void CMainFrame::OnContextMenu(CWnd*  pWnd, CPoint point)
  {
  // CG: This block was added by the Pop-up Menu component
  {
  if (point.x == -1 && point.y == -1){
  //如果是键盘激活的快捷菜单,则在窗口左上角5,5的位置显示快捷菜单
  CRect rect;
  GetClientRect(rect);
  ClientToScreen(rect);
  point = rect.TopLeft();
  point.Offset(5, 5);
  }
  //载入快捷菜单资源
  CMenu menu;
  VERIFY(menu.LoadMenu(CG_IDR_POPUP_MAIN_FRAME));
  //取得本菜单的第一个子菜单
  CMenu* pPopup = menu.GetSubMenu(0);
  ASSERT(pPopup != NULL);
  CWnd* pWndPopupOwner = this;
  //如果当前窗口是一个子窗口,取其父窗口作为弹出菜单的拥有者
  while (pWndPopupOwner->GetStyle() & WS_CHILD)
  pWndPopupOwner = pWndPopupOwner->GetParent();
  //在point.x,point.y处显示弹出式菜单并跟踪其选择项
  pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x,point.y,pWndPopupOwner);
  }
  一般的,我们都可以使用Component Gallery的Popup menu构件为某个窗口、对话框、视图等增加快捷菜单而无需手工编程。我们要做的只是编辑修改缺省的菜单为我们自己的快捷菜单,并用ClassWizard生成必要的成员函数,再加入自己的代码。如果确实要手工做的话,首先应当用菜单编辑器增加一个菜单,然后为对应的窗口添加OnContextMenu方法,OnContextMenu的定义可以参考上面的程序。
 Component Gallery的功能远不止向程序添加快捷菜单这一项,它还可以增加启动画面(Splash Window)、多页式对话框、口令检查对话框等多种功能。
  工具条和状态栏
  在上一课中,同学们已经学到了一些基本的界面设计技术。这一课将指导大家如何设计实现工具条和状态栏,并进一步加深对消息驱动机制的理解。
  在一个标准的Windows应用程序中,工具条的大部分按钮执行的命令与菜单命令相同,这样做的目的是能同时提供形象和抽象的用户接口,以方便用户的使用。
  状态栏位于主框架窗口的底部,主要用来显示一些提示信息,可细分为几个窗格。状态栏的主要功能是简要解释被选中菜单命令或工具条按钮命令,并显示SCROLL LOCK、NUM LOCK等键的状态。
  一、工具条的可视化设计
  在MFC中,工具条的功能由类CToolBar实现。工具条资源和工具条类CToolBar是工具条的两个要素。创建工具条的基本步骤是:
  1.创建工具条资源。
  2.构建一个CToolBar对象。
  3.调用CToolBar::Create函数创建工具条窗口。
  4.调用CToolBar::LoadToolBar载入工具条资源。
  使用缺省配置时,AppWizard会自动创建一个工具条。这个工具条包含一些常用按钮,如打开文件、存盘、打印等等。用户可以修改这个工具条,去掉无用的按钮,加入自己需要的按钮。如果用户需要创建两个以上的工具条,则不能完全依赖AppWizard,需要自己手工创建之。
  1、利用AppWizard自动创建
  自动创建工具条很简单,请读者按以下步骤操作:
  1).选择 File->New命令。
  2).在弹出的标签式对话框中选Projects页,然后在该页中选中MFC AppWizard (exe)项,并在Project name一栏中输入Record以创建一个名为Record的工程。按回车或用鼠标点击Create按钮后就进入了MFC AppWizard对话框。
  3).用鼠标点击Finish按钮,并在接着的对话框中按OK按钮。
  完成以上操作后,工程Record被创建并被自动载入Developer Studio中。将项目工作区切换到资源视图,并展开资源,就会发现其中有一个名为IDR_MAINFRAME的Toolbar(工具条)资源。用鼠标双击“IDR_MAINFRAME”,Developer Studio会打开一个功能强大的工具条资源编辑窗口,如图4.2所示。该窗口的上部显示出了工具条上的按钮,当用户用鼠标选择某一按钮时,在窗口的下部会显示该按钮的位图。
  在修改工具条以前,首先要修改菜单资源。请按以下几步修改菜单资源:
  1).将项目工作区切换至资源视图,选择并打开menu(菜单)资源类型,双击名为IDR_MAINFRAME的菜单资源。
  2).删除Edit菜单。
  3).删除File菜单中除Exit以外的所有菜单项。
  4).在File菜单后插入一个名为&Record的新菜单,并在该菜单中插入&Start和St&op两个菜单项,它们的命令ID(标识符)分别为ID_RECORD_START和ID_RECORD_STOP。Start表示开始录音,而Stop表示停止录音。
  接下来的任务是修改工具条资源,具体步骤是:
  1).选择并打开Toolbar(工具条)资源类型,双击名为IDR_MAINFRAME的工具条资源以打开相应的资源编辑窗口。
  2).删除“?”按钮前面的所有按钮,删除的方法是用鼠标将要删除的按钮拖出工具条即可。
  3).先选中“?”按钮后面的空白按钮,然后在该按钮的放大位图上用红色画一个实心圆圈,以表示开始录音功能。再选中空白按钮,并用黑色在放大位图上画一个实心矩形,以表示停止功能。通过用鼠标拖动按钮调整按钮的位置。
  4).分别为两个新加的按钮指定命令ID为ID_RECORD_START和ID_RECORD_STOP。指定ID的方法是先选中一个按钮,接着按回车键,在弹出的属性对话框中输入ID(或从ID下拉列表中分别选择ID_RECORD_START和ID_RECORD_STOP)。注意到这两个按钮的ID与Record菜单中的两个菜单项Start和Stop的ID相同,这样同样的命令既可以通过菜单执行,也可以通过工具条执行。
  5).为两个新加的按钮指定命令提示。请分别在两个按钮的属性对话框中的Prompt栏内输入Start record/nStart和Stop record/nStop。命令提示实际上是作为字符串保存在String Table字符串资源中的。命令提示用来解释命令的意义,分状态栏提示和工具提示两种,在Prompt栏中,二者由/n分隔开。
  提示:如果觉得按钮太小,读者可以用鼠标拖动围绕按钮放大位图的虚框的右下角,把按钮放大些。注意工具条内的所有按钮都将被放大
 修改完后,读者可以编译并运行Record,来看看修改的结果。读者很快会注意到Start和Stop菜单项及按钮都是灰色的。有趣的是工具条可以被拖动,并且工具条是可以浮动的,即当用鼠标双击工具条的空白处时,工具条变成了一个浮动窗口,可被拖动到屏幕上的任意地方。
  2、手工创建
  如果想要再加一个工具条,那么AppWizard就无能为力了,必须手工创建。假设Record程序的声音采样频率有11KHZ和44KHZ两档选择,现在我们的任务是再创建一个工具条,可让用户对这两种档次进行选择。本来这样的功能应该位于第一个工具条内,但为了演示工具条的手工创建,这里不妨来个多此一举。
  首先要对原来的菜单进行修改。打开IDR_MAINFRAME菜单资源,加入两个菜单项,其属性如表4.1所示。
  CaptionIDPrompt
    &Low qualityID_LOW_QUALITYLow quality(11k)/n11k
    &High qualityID_HIGH_QUALITYHigh quality(44k)/n44k
 接着要创建一个新的工具条资源,请按以下步骤进行:
  选择Insert->Resource命令,然后在Insert Resource对话框中选中Toolbar。按了OK按钮后,在Toolbar资源类下就会出现一个ID为IDR_TOOLBAR1的新资源。
  在新工具条中加入两个按钮。
  分别为两个新加的按钮指定命令ID为ID_LOW_QUALITY和ID_HIGH_QUALITY。
  要把这个新设计的工具条加入到程序中,需要在程序中加入一些与创建有关的 源代码。在创建第一个工具条时,AppWizard在程序中自动加入了创建源代码,通过仿制这些代码,我们很容易创建出第二个工具条。
  在MFC中,工具条的功能由类CToolBar实现。工具条实际上是主框架窗口的子窗口,因此工具条对象应该依附于主框架窗口对象。在AppWizard创建的MFC程序中,主框架窗口的类名是CMainFrame,该类是MFC标准类CFrameWnd类的派生类。将项目工作区切换至类视图并展开CMainFrame类,读者会发现该类有一个名为m_wndToolbar的成员。双击该成员,则Developer Studio会自动打开类CMainFrame所在的头文件,并将光标停在对m_wndToolbar成员的定义处。CToolBar m_wndToolBar;
  由此可见m_wndToolBar是一个CToolBar对象,它是CMainFrame的成员。现在请紧接着该成员加入一个新的成员:CToolBar m_wndToolBar1;
  m_wndToolBar1代表第二个工具条。读者不要以为给CMainFrame加入一个CToolBar对象就完事了。实际的创建工具条的工作不会在构造CToolBar对象时完成,只有调用了类CToolBar的一些成员函数后,创建工作才能结束。
  对工具条的实际创建工作在CMainFrame::OnCreate函数中完成。OnCreate函数是在创建窗口时被调用的,这时窗口的创建已部分完成,窗口对象的m_hWnd成员中存放的HWND句柄也已有效,但窗口还是不可见的。因此一般在OnCreate函数中作一些诸如创建子窗口的初始化工作。
  找到CMainFrame::OnCreate函数,对该函数进行一些修改,修改的部分如清单4.1的黑体字所示。在以后,凡是程序中手工修改的部分,一般都会用黑体显示。
  清单4.1 修改后的CMainFrame::OnCreate函数
  int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
  {
   if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
      return -1;
   if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
   {
      TRACE0("Failed to create toolbar/n");
      return -1; // fail to create
   }
   if (!m_wndToolBar1.Create(this) ||!m_wndToolBar1.LoadToolBar(IDR_TOOLBAR1))
   {
      TRACE0("Failed to create toolbar/n");
      return -1; // fail to create
   }
   if (!m_wndStatusBar.Create(this) ||!m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT)))
   {
      TRACE0("Failed to create status bar/n");
      return -1; // fail to create
   }
   // TODO: Remove this if you don&#39;t want tool tips or a resizeable toolbar
   m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
   m_wndToolBar1.SetBarStyle(m_wndToolBar1.GetBarStyle()|CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
   // TODO: Delete these three lines if you don&#39;t want the toolbar to be dockable
   m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
   m_wndToolBar1.EnableDocking(CBRS_ALIGN_ANY);
   EnableDocking(CBRS_ALIGN_ANY);
   DockControlBar(&m_wndToolBar);
   DockControlBar(&m_wndToolBar1);
   return 0;
  }
  注意在CMainFrame::OnCreate函数首先调用了CFrameWnd::OnCreate。这行代码是AppWizard自动加入的,但有必要解释一下。CMainFrame是CFrameWnd类的继承类,在CMainFrame::OnCreate中首先要调用基类CFrameWnd的OnCreate函数,因为基类也要进行一些初始化工作,而基类的OnCreate函数不会自动调用,因此需要在继承类的OnCreate函数中显式调用。OnCreate实际上是WM_CREATE消息的消息处理函数,读者可能要问,为什么是派生类的OnCreate处理WM_CREATE消息,而不是基类的OnCreate呢。如果读者仔细观察OnCreate函数在CMainFrame类头文件中的说明,就会发现在该函数前有一个afx_msg前缀。afx_msg的作用与virtual关键字类似,它使得被说明的函数有虚拟函数的特性,即由继承类而不是基类的处理函数来处理消息。
  对第二个工具条的创建代码的解释是:首先,调用CToolBar::Create以创建工具条窗口,注意Create函数的参数是this指针,这是因为主框架窗口是工具条的父窗口。接着调用CToolbar::LoadToolBar(IDR_TOOLBAR1)以载入工具条资源。然后调用CToolBar::SetBarStyle指定工具条的风格,在调用该函数时先调用CToolBar::GetBarStyle取得工具条的风格,然后在原有风格的基础上又指定了CBRS_TOOLTIPS、
CBRS_FLYBY和CBRS_SIZE_DYNAMIC风格,这使得工具条可显示工具提示,并可以动态改变尺寸。接着调用CToolBar::EnableDocking(CBRS_ALIGN_ANY)使工具条是可以停泊的,但还需调用CFrameWnd::EnableDocking(CBRS_ALIGN_ANY),只有这样才能实现可停泊的工具条。最后调用CFrameWnd::DockControlBar以停泊工具条。
  编译并运行Record看看,现在Record程序已经拥有两个工具条了。至此创建工具条的任务已经完成,下面需要对工具条编程,以使其能够发挥执行命令的功能。

正文完
  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值