• MFC和Win32之二___CWnd类和Windows Object


    总结:

    CWnd是MFC中封装windows窗口的类,而该类对应的对象就是windows中的窗口对象

    在创建任何windows窗口前,都需要对该窗口进行注册。“窗口类”就是一组窗口所具有的共同的特征。MFC中有许多封装注册窗口类的API函数(::RegisterClass(lpWndClass),::RegisterWndClass),主要是为了隐藏细节和向下兼容。

    在win32环境下,一个窗口的属性分两个地方存放:一部分放在“窗口类”里头,如上所述的在注册窗口时指定;另一部分放在Windows Object本身。而MFC中用一个类CWnd表达所有这些窗口的共性。

    由于采用了MFC类的方式来创建窗口,因此就会有在堆上创建和栈上创建两种方法。具体使用哪种方法,要看封装该窗口类的MFC类是否自动对窗口进行了析构。在MFC中创建的窗口,必须要完成两个工作才能表明窗口的消失,一个是windows下窗口对象的句柄释放,一个是对象本身的释放。而在win32环境下,只需要对句柄进行释放即可。


     

      1. Windows Object

        用SDK的Win32 API编写各种Windows应用程序,有其共同的规律:首先是编写WinMain函数,编写处理消息和事件的窗口过程WndProc,在WinMain里头注册窗口(Register Window),创建窗口,然后开始应用程序的消息循环。

        MFC应用程序也不例外,因为MFC是一个建立在SDK API基础上的编程框架。对程序员来说所不同的是:一般情况下,MFC框架自动完成了Windows登记、创建等工作。

        下面,简要介绍MFC Window对Windows Window的封装。

         

        1. Windows的注册

    一个应用程序在创建某个类型的窗口前,必须首先注册该“窗口类”(Windows Class)。注意,这里不是C++类的类。Register Window把窗口过程、窗口类型以及其他类型信息和要登记的窗口类关联起来。

     

    1. “窗口类”的数据结构

      “窗口类”是Windows系统的数据结构,可以把它理解为Windows系统的类型定义,而Windows窗口则是相应“窗口类”的实例。Windows使用一个结构来描述“窗口类”,其定义如下:

      typedef struct _WNDCLASSEX {

      UINT cbSize; //该结构的字节数

      UINT style; //窗口类的风格

      WNDPROC lpfnWndProc; //窗口过程

      int cbClsExtra;

      int cbWndExtra;

      HANDLE hInstance; //该窗口类的窗口过程所属的应用实例

      HICON hIcon; //该窗口类所用的像标

      HCURSOR hCursor; //该窗口类所用的光标

      HBRUSH hbrBackground; //该窗口类所用的背景刷

      LPCTSTR lpszMenuName; //该窗口类所用的菜单资源

      LPCTSTR lpszClassName; //该窗口类的名称

      HICON hIconSm; //该窗口类所用的小像标

      } WNDCLASSEX;

      从“窗口类”的定义可以看出,它包含了一个窗口的重要信息,如窗口风格、窗口过程、显示和绘制窗口所需要的信息,等等。关于窗口过程,将在后面消息映射等有关章节作详细论述。

      Windows系统在初始化时,会注册(Register)一些全局的“窗口类”,例如通用控制窗口类。应用程序在创建自己的窗口时,首先必须注册自己的窗口类。在MFC环境下,有几种方法可以用来注册“窗口类”,下面分别予以讨论。

       

    2. 调用AfxRegisterClass注册

      AfxRegisterClass函数是MFC全局函数。AfxRegisterClass的函数原型:

      BOOL AFXAPI AfxRegisterClass(WNDCLASS *lpWndClass);

      参数lpWndClass是指向WNDCLASS结构的指针,表示一个“窗口类”。

      首先,AfxRegisterClass检查希望注册的“窗口类”是否已经注册,如果是则表示已注册,返回TRUE,否则,继续处理。

      接着,调用::RegisterClass(lpWndClass)注册窗口类;

      然后,如果当前模块是DLL模块,则把注册“窗口类”的名字加入到模块状态的域m_szUnregisterList中。该域是一个固定长度的缓冲区,依次存放模块注册的“窗口类”的名字(每个名字是以“\n\0”结尾的字符串)。之所以这样做,是为了DLL退出时能自动取消(Unregister)它注册的窗口类。至于模块状态将在后面第9章详细的讨论。

      最后,返回TRUE表示成功注册。

       

    3. 调用AfxRegisterWndClass注册

      AfxRegisterWndClass函数也是MFC全局函数。AfxRegisterWndClass的函数原型:

      LPCTSTR AFXAPI AfxRegisterWndClass(UINT nClassStyle,

      HCURSOR hCursor, HBRUSH hbrBackground, HICON hIcon)

      参数1指定窗口类风格;

      参数2、3、4分别指定该窗口类使用的光标、背景刷、像标的句柄,缺省值是0。

      此函数根据窗口类属性动态地产生窗口类的名字,然后,判断是否该类已经注册,是则返回窗口类名;否则用指定窗口类的属性(窗口过程指定为缺省窗口过程),调用AfxRegisterCalss注册窗口类,返回类名。

      动态产生的窗口类名字由以下几部分组成(包括冒号分隔符):

      如果参数2、3、4全部为NULL,则由三部分组成。

      “Afx”+“:”+模块实例句柄”+“:”+“窗口类风格”

      否则,由六部分组成:

      “Afx”+“:”+模块实例句柄+“:”+“窗口类风格”+“:”+光标句柄+“:”+背景刷句柄+“:”+像标句柄。比如:“Afx:400000:b:13de:6:32cf”。

      该函数在MFC注册主边框或者文档边框“窗口类”时被调用。具体怎样用在5.3.3.3节会指出。

       

    4. 隐含的使用MFC预定义的的窗口类

      MFC4.0以前的版本提供了一些预定义的窗口类,4.0以后不再预定义这些窗口类。但是,MFC仍然沿用了这些窗口类,例如:

      用于子窗口的“AfxWnd”;

      用于边框窗口(SDI主窗口或MDI子窗口)或视的“AfxFrameOrView”;

      用于MDI主窗口的“AfxMDIFrame”;

      用于标准控制条的“AfxControlBar”。

      这些类的名字就 是“AfxWnd”、“AfxFrameOrView”、“AfxMdiFrame”、 “AfxControlBar”加上前缀和后缀(用来标识版本号或是否调试版等)。它们使用标准应用程序像标、标准文档像标、标准光标等标准资源。为了使用这些“窗口类”,MFC会在适当的时候注册这些类:或者要创建该类的窗口时,或者创建应用程序的主窗口时,等等。

      MFC内部使用了函数

      BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)

      来帮助注册上述原MFC版本的预定义“窗口类”。参数fClass区分了那些预定义窗口的类型。根据不同的类型,使用不同的窗口类风格、窗口类名字等填充WndClass的域,然后调用AfxRegisterClass注册窗口类。并且注册成功之后,通过模块状态的m_fRegisteredClasses记录该窗口类已经注册,这样该模块在再次需要注册这些窗口类之前可以查一下m_fRegisteredClasses,如果已经注册就不必浪费时间了。为此,MFC内部使用宏

      AfxDeferRegisterClass(short fClass)

      来注册“窗口类”,如果m_fRegisteredClasses记录了注册的窗口类,返回TRUE,否则,调用AfxEndDeferRegisterClass注册。

      注册这些窗口类的例子:

      MFC在加载边框窗口时,会自动地注册“AfxFrameOrView”窗口类。在创建视时,就会使用该“窗口类”创建视窗口。当然,如果创建视窗口时,该“窗口类”还没有注册,MFC将先注册它然后使用它创建视窗口。

      不过,MFC并不使用”AfxMDIFrame”来创建MDI主窗口,因为在加载主窗口时一般都指定了主窗口的资源,MFC使用指定的像标注册新的MDI主窗口类(通过函数AfxRegisterWndClass完成,因此“窗口类”的名字是动态产生的)。

      MDI子窗口类似于上述MDI主窗口的处理。

      在MFC创建控制窗口时,如工具栏窗口,如果“AfxControlBar”类还没有注册,则注册它。注册过程很简单,就是调用::InitCommonControl加载通用控制动态连接库。

       

    5. 调用::RegisterWndClass。

      直接调用Win32的窗口注册函数::RegisterWndClass注册“窗口类”,这样做有一个缺点:如果是DLL模块,这样注册的“窗口类”在程序退出时不会自动的被取消注册(Unregister)。所以必须记得在DLL模块退出时取消它所注册的窗口类。

       

    6. 子类化

    子类化(Subclass)一个“窗口类”,可自动地得到它的“窗口类”属性。

     

        1. MFC窗口类CWnd

    在Windows系统里,一个窗口的属性分两个地方存放:一部分放在“窗口类”里头,如上所述的在注册窗口时指定;另一部分放在Windows Object本身,如:窗口的尺寸,窗口的位置(X,Y轴),窗口的Z轴顺序,窗口的状态(ACTIVE,MINIMIZED,MAXMIZED,RESTORED…),和其他窗口的关系(父窗口,子窗口…),窗口是否可以接收键盘或鼠标消息,等等。

    为了表达所有这些窗口的共性,MFC设计了一个窗口基类CWnd。有一点非常重要,那就是CWnd提供了一个标准而通用的MFC窗口过程,MFC下所有的窗口都使用这个窗口过程。至于通用的窗口过程却能为各个窗口实现不同的操作,那就是MFC消息映射机制的奥秘和作用了。这些,将在后面有关章节详细论述。

    CWnd提供了一系列成员函数,或者是对Win32相关函数的封装,或者是CWnd新设计的一些函数。这些函数大致如下。

    (1)窗口创建函数

    这里主要讨论函数Create和CreateEx。它们封装了Win32窗口创建函数::CreateWindowEx。Create的原型如下:

    BOOL CWnd::Create(LPCTSTR lpszClassName,

    LPCTSTR lpszWindowName, DWORD dwStyle,

    const RECT& rect,

    CWnd* pParentWnd, UINT nID,

    CCreateContext* pContext)

    Create是一个虚拟函数,用来创建子窗口(不能创建桌面窗口和POP UP窗口)。CWnd的基类可以覆盖该函数,例如边框窗口类等覆盖了该函数以实现边框窗口的创建,视类则使用它来创建视窗口。

     

    Create调用了成员函数CreateEx。CWnd::CreateEx的原型如下:

    BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,

    LPCTSTR lpszWindowName, DWORD dwStyle,

    int x, int y, int nWidth, int nHeight,

    HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)

    CreateEx有11个参数,它将调用::CreateWindowEx完成窗口的创建,这11个参数对应地传递给::CreateWindowEx。参数指定了窗口扩展风格、“窗口类”、窗口名、窗口大小和位置、父窗口句柄、窗口菜单和窗口创建参数。

    CreateEx的处理流程将在后面4.4.1节讨论窗口过程时分析。

    窗口创建时发送WM_CREATE消息,消息参数lParam指向一个CreateStruct结构的变量,该结构有11个域,其描述见后面4.4.1节对窗口过程的分析,Windows使用和CreateEx参数一样的内容填充该变量。

    (2)窗口销毁函数

    例如:

    DestroyWindow函数 销毁窗口

    PostNcDestroy( ),销毁窗口后调用,虚拟函数

     

    (3)用于设定、获取、改变窗口属性的函数,例如:

    SetWindowText(CString tiltle) 设置窗口标题

    GetWindowText() 得到窗口标题

    SetIcon(HICON hIcon, BOOL bBigIcon);设置窗口像标

    GetIcon( BOOL bBigIcon ) ;得到窗口像标

    GetDlgItem( int nID);得到窗口类指定ID的控制子窗口

    GetDC(); 得到窗口的设备上下文

    SetMenu(CMenu *pMenu); 设置窗口菜单

    GetMenu();得到窗口菜单

     

    (4)用于完成窗口动作的函数

    用于更新窗口,滚动窗口,等等。一部分成员函数设计成或可重载(Overloaded)函数,或虚拟(Overridden)函数,或MFC消息处理函数。这些函数或者实现了一部分功能,或者仅仅是一个空函数。如:

     

    • 有关消息发送的函数:

    SendMessage( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );

    给窗口发送发送消息,立即调用方式

    PostMessage(( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );

    给窗口发送消息,放进消息队列

     

    • 有关改变窗口状态的函数

    MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );

    移动窗口到指定位置

    ShowWindow(BOOL );显示窗口,使之可见或不可见

    ….

     

    • 实现MFC消息处理机制的函数:

    virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); 窗口过程,虚拟函数

     

    virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );处理命令消息

     

    • 消息处理函数:

    OnCreate( LPCREATESTRUCT lpCreateStruct );MFC窗口消息处理函数,窗口创建时由MFC框架调用

    OnClose();MFC窗口消息处理函数,窗口创建时由MFC框架调用

     

    • 其他功能的函数

    CWnd的导出类是类型更具体、功能更完善的窗口类,它们继承了CWnd的属性和方法,并提供了新的成员函数(消息处理函数、虚拟函数、等等)。

    常用的窗口类及其层次关系见图1-1。

     

        1. 在MFC下创建一个窗口对象

    MFC下创建一个窗口对象分两步,首先创建MFC窗口对象,然后创建对应的Windows窗口。在内存使用上,MFC窗口对象可以在栈或者堆(使用new创建)中创建。具体表述如下:

     

    • 创建MFC窗口对象。通过定义一个CWnd或其派生类的实例变量或者动态创建一个MFC窗口的实例,前者在栈空间创建一个MFC窗口对象,后者在堆空间创建一个MFC窗口对象。

       

    • 调用相应的窗口创建函数,创建Windows窗口对象。

    例如:在前面提到的AppWizard产生的源码中,有CMainFrame(派生于CMDIFrame(SDI)或者CMDIFrameWnd(MDI))类。它有两个成员变量定义如下:

    CToolBar m_wndToolBar;

    CStatusBar m_wndStatusBar;

    当创建CMainFrame类对象时,上面两个MFC Object也被构造。

    CMainFrame还有一个成员函数

    OnCreate(LPCREATESTRUCT lpCreateStruct),

    它的实现包含如下一段代码,调用CToolBar和CStatusBar的成员函数Create来创建上述两个MFC对象对应的工具栏HWND窗口和状态栏HWND窗口:

    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

    {

    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

    }

    }

    关于工具栏、状态栏将在后续有关章节作详细讨论。

    在MFC中,还提供了一种动态创建技术。动态创建的过程实际上也如上所述分两步,只不过MFC使用这个技术是由框架自动地完成整个过程的。通常框架窗口、文档框架窗口、视使用了动态创建。介于MFC的结构,CFrameWnd和CView及其派生类的实例即使不使用动态创建,也要用new在堆中分配。理由见窗口的销毁(2.2.5节)。

    至于动态创建技术,将在下一章具体讨论。

    在Windows窗口的创建过程中,将发送一些消息,如:

    在创建了窗口的非客户区(Nonclient area)之后,发送消息WM_NCCREATE;

    在创建了窗口的客户区(client area)之后,发送消息WM_CREATE;

    窗口的窗口过程在窗口显示之前收到这两个消息。

    如果是子窗口,在发送了上述两个消息之后,还给父窗口发送WM_PARENATNOTIFY消息。其他类或风格的窗口可能发送更多的消息,具体参见SDK开发文档。

     

        1. MFC窗口的使用

          MFC提供了大量的窗口类,其功能和用途各异。程序员应该选择哪些类来使用,以及怎么使用他们呢?

          直接使用MFC提供的窗口类或者先从MFC窗口类派生一个新的C++类然后使用它,这些在通常情况下都不需要程序员提供窗口注册的代码。是否需要派生新的C++类,视MFC已有的窗口类是否能满足使用要求而定。派生的C++类继承了基类的特性并改变或扩展了它的功能,例如增加或者改变对消息、事件的特殊处理等。

          主要使用或继承以下一些MFC窗口类(其层次关系图见图1-1):

          框架类CFrameWnd,CMdiFrameWnd;

          文档框架CMdiChildWnd;

          视图CView和CView派生的有特殊功能的视图如:列表CListView,编辑CEditView,树形列表CTreeView,支持RTF的CRichEditView,基于对话框的视CFormView等等。

          对话框CDialog。

          通常,都要从这些类派生应用程序的框架窗口和视窗口或者对话框。

           

          工具条CToolBar

          状态条CStatusBar

          其他各类控制窗口,如列表框CList,编辑框CEdit,组合框CComboBox,按钮Cbutton等。

          通常,直接使用这些类。

           

        2. 在MFC下窗口的销毁

    窗口对象使用完毕,应该销毁。在MFC下,一个窗口对象的销毁包括HWND窗口对象的销毁和MFC窗口对象的销毁。一般情况下,MFC编程框架自动地处理了这些。

    (1)对CFrameWnd和CView的派生类

    这些窗口的关闭导致销毁窗口的函数DestroyWindow被调用。销毁Windows窗口时,MFC框架调用的最后一个成员函数是OnNcDestroy函数,该函数负责Windows清理工作,并在最后调用虚拟成员函数PostNcDestroy。CFrameWnd和CView的PostNcDestroy调用delete this删除自身这个MFC窗口对象。

    所以,对这些窗口,如前所述,应在堆(Heap)中分配,而且,不要对这些对象使用delete操作。

     

    (2)对Windows Control窗口

    在它们的析构函数中,将调用DestroyWidnow来销毁窗口。如果在栈中分配这样的窗口对象,则在超出作用范围的时候,随着析构函数的调用,MFC窗口对象和它的Windows window对象都被销毁。如果在堆(Heap)中分配,则显式调用delete操作符,导致析构函数的调用和窗口的销毁。

    所以,这种类型的窗口应尽可能在栈中分配,避免用额外的代码来销毁窗口。如前所述的CMainFrame的成员变量m_wndStatusBar和m_wndToolBar就是这样的例子。

    (3)对于程序员直接从CWnd派生的窗口

    程序员可以在派生类中实现上述两种机制之一,然后,在相应的规范下使用。

    后面章节将详细的讨论应用程序退出时关闭、清理窗口的过程。

  • 相关阅读:
    Javascript 计时事件说明
    结合RibbonControl设计MDI窗体,在子窗体关闭后,顶部显示额外的控制栏残影
    交叉验证[转]
    [转载】我的数据挖掘之路 by wrchow
    《这些道理没有人告诉过你》摘记
    2017BUAA软工第0次作业
    SSM框架+slf4j 以Gradle实现
    RMQ(ST算法)
    博客园装饰
    【转载】UVa 11464 Even Parity 偶数矩阵
  • 原文地址:https://www.cnblogs.com/ghw0501/p/4733927.html
Copyright © 2020-2023  润新知