01:MFC应用程序编程

MFC的发展

VC 1.0->VC 5.0->VC 6.0->VC2008 SP1)->VS2010

MFC基础

1 MFC 微软基础类库

    采用类的方式,将Win32 API等进行封装,形成的库.

   

2 MFC相关的头文件

   afx.h   (application framework, X)

   afxwin.h  (类似于windows.h)

   afxext.h  (MFC扩展头文件)

   ...

MFC应用程序

MFC应用程序主要分为以下三类,对比通常的应用程序,变化的地方如下:

1 MFC的控制台程序

1.1 包含afx系列的头文件

1.2 CWinApp theApp - MFC的应用程序类封装了应用程序的启动过程.

1.3 AfxWinInit MFC初始化函数将应用程序的信息初始化.

2 MFC的动态库和静态库

2.1 静态库

2.1.1 增加了MFC的支持

2.2 动态库

动态库分类如下:

2.2.1 MFC规则DLL分为如下两类:

使用MFC静态库的规则DLL

使用MFC动态库的规则DLL

2.2.2 MFC扩展DLL

动态库与C++动态库的不同点如下:

1 增加了MFC相关的afx头文件

2 包含一个继承CWinApp类, 并且声明了该类的全局变量.

3 MFC应用程序

3.1 单文档视图应用程序

3.1.1 包含一个继承CWinApp类 

3.1.2 CMainFrame类

3.1.3 CView的一个子类

3.1.4 CDocument的一个子类

3.2 多文档视图应用程序

3.2.1 包含一个继承CWinApp类

3.2.2 CMainFrame类

3.2.3 CView的一个子类

3.2.4 CDocument的一个子类

3.2.5 CChildFrame 子框架窗口

3.3 对话框应用程序

3.3.1 包含一个继承CWinApp类

3.3.2 对话框类

MFC中的类

MSDN中搜索“Hierarchy chart”就可以看到整个MFC类继承图的关系

image

 

常用的类分类如下:

1 CObject

MFC类的基础,大部分MFC类都是它的子类

CObject封装了MFC的基础的机制,比如:

1.1 new和Delete

1.2 Assert

1.3 运行式信息

1.4 动态创建

1.5 序列化

2 应用程序框架

封装了应用程序启动相关信息,以及MFC消息映射机制。

3 窗口支持类

封装了窗口操作的API,各种控件及窗口的框架.

4 绘图类

提供了绘图API的封装,以及相关的GDI设备封装.

5 MFC的集合类

提供了数组、链表、映射的数据结构的操作.

6 数据库支持类

ODBC支持类和DAO的支持类。DAO的类已作废.

7 同步类

临界区/事件/互斥/信号量的封装

8 Socket

封装了socket的编程.

9 常用数据结构

CString CRect CPoint等.

第一个MFC程序

不使用向导,自己创建一个MFC应用程序。

1.新建一个Win32 Application项目“WinMFC

image

2.选择一个简单的Win32应用程序

image

3.修改环境为MFC应用程序的环境

3.1修改stdafx.h头文件,将#include <Windows.h> 改成#include <afxwin.h>

3.2删掉WinMFC.cpp文件中的WinMain()函数

3.3进入项目的属性设置

image

4.增加应用程序类CWinApp

4.1WinMFC.cpp文件中编写继承自CWinApp类的类CMyApp

4.2CMyApp类中添加InitInstance函数

InitInstance函数是在程序启动过程中,会被调用,我们可以在这个函数中,创建窗口或者各种初始化操作.

4.3添加CMyApp的全局变量

 

//应用程序类

class CMyApp : public CWinApp

{

public:

    virtual BOOL InitInstance( );

};

 

//定义CMyApp的全局变量

CMyApp theApp;

 

//初始化函数

BOOL CMyApp::InitInstance( )

{

    AfxMessageBox("Hello App");

    return TRUE;

}

 

这样,一个基本的MFC应用程序就创建好了。编译运行,会看到如下结果:

image

 

这个MFC是怎么执行起来的呢??先别急

程序可以启动了,下面开始创建窗口

1.WinMFC.cpp文件中编写继承自CFrameWnd类的类CMyFrameWnd

2.在App的InitInstance函数定义窗口对象

3.创建窗口(Create)并显示(ShowWindow)

4.将窗口设置成App的主窗口m_pMainWnd = pWnd;

5.在CMyFrameWnd中添加窗口处理函数WindowProc,在WindowProc中处理消息

 

WinMFC.cpp完整代码

// WinMFC.cpp : Defines the entry point for the application.

//

 

#include "stdafx.h"

 

//框架窗口类

class CMyFrameWnd : public CFrameWnd

{

public:

    //窗口处理函数

    virtual LRESULT WindowProc( UINT message,

        WPARAM wParam, LPARAM lParam );

};

 

//窗口处理函数

LRESULT CMyFrameWnd::WindowProc( UINT message,

    WPARAM wParam, LPARAM lParam )

{

    switch( message )

    {

    case WM_CREATE:

        AfxMessageBox( "WM_CREATE" );

        break;

    case WM_PAINT:

        {

            PAINTSTRUCT ps = { 0 };

            HDC hDC = ::BeginPaint( m_hWnd,  &ps );

 

            CHAR szText[] = "Hello world!";

            TextOut( hDC, 100, 100,

                szText, strlen( szText ) );

 

            ::EndPaint( m_hWnd, &ps );

        }

        break;

    }

 

    return CFrameWnd::WindowProc( message,

        wParam, lParam );

}

 

//应用程序类

class CMyApp : public CWinApp

{

public:

    virtual BOOL InitInstance( );

};

 

//定义CMyApp的全局变量

CMyApp theApp;

 

//初始化函数

BOOL CMyApp::InitInstance( )

{

    //定义窗口对象

    CMyFrameWnd * pWnd = new CMyFrameWnd();

    //创建窗口

    pWnd->Create( NULL, "MyApp" );

    //显示窗口

    pWnd->ShowWindow( SW_SHOW );

    //设置主窗口

    m_pMainWnd = pWnd;

 

    return TRUE;

}

MFC应用程序的启动

MFC应用程序与Win32程序一样,都需要程序的入口函数.

1 CWinApp

应用程序类,封装了应用程序的相关信息,可以提供初始化 消息循环等处理.

CWinApp的构造函数中执行了什么:

1 将this指针保存到ThreadState中.this指针就是CMyApp对象----theApp

  pThreadState->m_pCurrentWinThread = this

2 将this指针保存到ModuleState中

  pModuleState->m_pCurrentWinApp = this

3 各种的成员变量的初始化

2 程序的入口函数

CMyApp::InitInstance()函数中加一个断点,然后运行,程序在断点处停下来。

这时候打开“Call Stack”窗口,观察程序的调用过程。

image

PS:双击调用栈里面的函数,就会跳过去~~

根据跟踪调用过程,得到如下结论:

1 MFC也拥有WinMain函数,与Win32程序一致.

2 在WinMain函数中,调用了AfxWinMain函数.

AfxWinMain函数中执行了什么:

1 调用AfxWinInit初始化应用程序

2 调用CWinApp的InitApplication初始MFC内部数据

3 调用CWinThread的InitInstance函数做初始化操作

4 调用CWinThread的Run函数进行消息循环

5 调用AfxWinTerm函数做退出处理.

3 现在,再来总结一下程序的启动过程:

1 CWinApp在构造过程中, 将自己的this指针分别保存到全局变量中.

2 在AfxWinMain执行中,首先从全局变量中将保存CWinApp的地址获取到.

3 调用CWinApp的InitInstance函数,进行初始化.我们在InitInstance函数中,创建窗口等初始化操作.

4 调用Run函数,进行消息循环.

窗口创建及窗口处理函数

1 窗口创建过程

1 窗口的参数初始化,包括窗口类,窗口风格窗口处理函数等信息.

  注意:将DefWindowProc注册成窗口处理函数.

2 设置 "创建HOOK" (钩子)

当窗口创建的时候,调用这个HOOK函数.

Wnd -> HOOK -> WndProc

3 创建窗口

CreateWindowEx

4 卸载 "创建HOOK"

将HOOK程序从当前程序中移除

2 HOOK(钩子)程序

上面说的"创建HOOK"到底做了什么:

1 使用AfxGetAfxWndProc函数获取了一个WNDPROC函数指针, 也就是AfxWndProc(AfxWndProcBase)函数地址.

2 将这个WNDPROC函数设置成当前窗口的处理函数

3 将窗口句柄和窗口类的指针保存到MFC的映射数据中(afxMapHWND函数)。这样就可以通过窗口句柄获取对应的窗口对象指针了。

总得来说,就是在窗口创建时,AfxWndProc函数设置为当前窗口的窗口过程函数。

3 AfxWndProc(Base)窗口处理函数

AfxGetAfxWndProc函数到底做了什么:

1 根据窗口句柄获取了相对应的窗口的CWnd *类型的指针,从映射数据中根据窗口句柄查找CWnd *指针.(afxMapHWND函数)

2 调用AfxCallWndProc函数

3 在AfxCallWndProc函数中,调用CWnd的WindowProc函数

  注意: 在MFC程序当中,所有窗口处理都是使用一个函数AfxWndProc函数).

4 窗口的创建及处理过程

经过上面的分析,总结窗口的创建及处理过程如下:

1 将DefWindowProc函数注册成当前窗口的处理函数

2 设置钩子函数

3 创建窗口,并执行钩子函数

4 在钩子函数将窗口类指针和窗口句柄的对应关系保存.

5 在钩子函数将AfxWndProc(Base)函数设置当前窗口的窗口处理函数

6 在AfxWndProc(Base)收到窗口消息,从窗口对应关系中,查询相应的窗口类指针.

7 调用窗口类WindowProc函数处理消息

消息映射

为了简化Win32API编程中复杂的switch...case...MFC重新封装了消息的传递方式。

下面,新建一个Win32应用程序,通过修改它实现MFC的消息映射,来观察一下。(PS:其实创建过程跟上面的过程差不多,多写几遍,写到吐为止)

image

 

image

 

1.修改stdafx.h头文件,将#include <Windows.h> 改成#include <afxwin.h>

2.

image

3.删掉MFCMsg.cpp文件中的WinMain()函数

4.

MFCMsg.cpp中定义CMsgFrameCMsgApptheApp

class CMsgFrame : public CFrameWnd

{

public:

    virtual LRESULT WindowProc( UINT nMsg,

        WPARAM wParam, LPARAM lParam);

};

 

LRESULT CMsgFrame::WindowProc( UINT nMsg,

        WPARAM wParam, LPARAM lParam)

{

    //以前这里就是很多switch来处理,现在没了

//被后面的BEGIN_MESSAGE_MAP()END_MESSAGE_MAP中间的内容取代了

    return CFrameWnd::WindowProc( nMsg,

        wParam, lParam );

}

 

class CMsgApp : public CWinApp

{

public:

        virtual BOOL InitInstance( );

};

 

CMsgApp theApp;

 

BOOL CMsgApp::InitInstance( )

{

        CMsgFrame * pWnd = new CMsgFrame( );

        pWnd->Create( NULL, "MsgApp" );

        m_pMainWnd = pWnd;

 

        m_pMainWnd->ShowWindow( SW_SHOW );

        m_pMainWnd->UpdateWindow( );

        return TRUE;

}

5.下面是跟前面不同的地方了。

CMsgFrame中增加消息映射的宏

class CMsgFrame : public CFrameWnd

{

public:

    virtual LRESULT WindowProc( UINT nMsg,

        WPARAM wParam, LPARAM lParam);

public:

    //消息映射宏定义

    DECLARE_MESSAGE_MAP( )

};

//消息映射的宏实现

BEGIN_MESSAGE_MAP( CMsgFrame, CFrameWnd )

 

END_MESSAGE_MAP( )

6.增加一个OnPaint函数作为WM_PAINT消息的响应函数,并在BEGIN_MESSAGE_MAPEND_MESSAGE_MAP之间添加消息映射

class CMsgFrame : public CFrameWnd

{

public:

    virtual LRESULT WindowProc( UINT nMsg, WPARAM wParam, LPARAM lParam);

public:

    //消息映射宏定义

    DECLARE_MESSAGE_MAP( )

public:

    //消息函数

    afx_msg LRESULT OnPaint( WPARAM wParam,LPARAM lParam );

 

};

//消息映射的宏实现

BEGIN_MESSAGE_MAP( CMsgFrame, CFrameWnd )

    //消息和消息处理函数的对应

    ON_MESSAGE( WM_PAINT, OnPaint )

END_MESSAGE_MAP( )

 

LRESULT CMsgFrame::OnPaint( WPARAM wParam, LPARAM lParam )

{

    PAINTSTRUCT ps = { 0 };

    HDC hDC = ::BeginPaint( m_hWnd, &ps );

 

    TextOut( hDC, 100, 100, "Msg", 3 );

 

    ::EndPaint( m_hWnd, &ps );

    return 0;

}

此时的运行结果:

image

7.同理,添加一个WM_CREATE消息:

class CMsgFrame : public CFrameWnd

{

public:

    virtual LRESULT WindowProc( UINT nMsg, WPARAM wParam, LPARAM lParam);

public:

    //消息映射宏定义

    DECLARE_MESSAGE_MAP( )

 

public:

    //消息函数

    afx_msg LRESULT OnPaint( WPARAM wParam, LPARAM lParam );

    afx_msg LRESULT OnCreate( WPARAM wParam, LPARAM lParam );

};

 

//消息映射的宏实现

BEGIN_MESSAGE_MAP( CMsgFrame, CFrameWnd )

    //消息和消息处理函数的对应

    ON_MESSAGE( WM_PAINT, OnPaint )

    ON_MESSAGE( WM_CREATE, OnCreate )

END_MESSAGE_MAP( )

 

LRESULT CMsgFrame::OnCreate( WPARAM wParam,

        LPARAM lParam )

{

    AfxMessageBox("OnCreate");

    return 0;

}

 

LRESULT CMsgFrame::OnPaint( WPARAM wParam,

        LPARAM lParam )

{

    PAINTSTRUCT ps = { 0 };

    HDC hDC = ::BeginPaint( m_hWnd, &ps );

 

    TextOut( hDC, 100, 100, "Msg", 3 );

 

    ::EndPaint( m_hWnd, &ps );

    return 0;

}

 

经过以上的分析,总结消息映射如下:

1 消息映射添加步骤

1 在FrameWnd添加消息宏定义

  DECLARE_MESSAGE_MAP

2 添加消息宏实现

3 添加消息处理函数

4 添加消息和处理函数的对应

2 消息宏的实现

DECLARE_MESSAGE_MAP等到底是什么呢?将这些宏展开如下:

//消息映射宏DECLARE_MESSAGE_MAP展开

private:

    static const AFX_MSGMAP_ENTRY _messageEntries[];

protected:

    static AFX_DATA const AFX_MSGMAP messageMap;

    static const AFX_MSGMAP* PASCAL _GetBaseMessageMap();

    virtual const AFX_MSGMAP* GetMessageMap() const;

//BEGIN_MESSAGE_MAP( CMsgFrame, CFrameWnd )展开

const AFX_MSGMAP* PASCAL CMsgFrame::_GetBaseMessageMap()

{

    return &CFrameWnd::messageMap;

}

const AFX_MSGMAP* CMsgFrame::GetMessageMap() const

{

      return &CMsgFrame::messageMap;

}

AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CMsgFrame::messageMap =

{    

      &CMsgFrame::_GetBaseMessageMap,

      &CMsgFrame::_messageEntries[0]

};

AFX_COMDAT const AFX_MSGMAP_ENTRY CMsgFrame::_messageEntries[] =

{

//ON_MESSAGE( WM_PAINT, OnPaint )展开

  { WM_PAINT, 0, 0, 0, AfxSig_lwl,(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&OnPaint },

//END_MESSAGE_MAP()展开

  {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }

};

 

先不要晕,将展开的代码多看几遍,分析一下就清楚了。

其中有些数据类型需要解释一下:

1 AFX_MSGMAP_ENTRY 是用于保存消息ID与对应函数指针,及相关的信息

struct AFX_MSGMAP_ENTRY{

        UINT nMessage; //消息ID

        UINT nCode;//通知代码

        UINT nID; //控件的ID

        UINT nLastID;//控件的ID范围的最后

        UINT nSig;//操作类型或pfn函数类型

        AFX_PMSG pfn;//消息处理函数的函数指针

};

2 AFX_MSGMAP 用于保存GetBaseMap的函数地址及AFX_MSGMAP_ENTRY数组的地址.

struct AFX_MSGMAP{

    //函数指针

    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();

    //AFX_MSGMAP_ENTRY类型指针

    const AFX_MSGMAP_ENTRY* lpEntries;

};

下面对上面的宏代码做一些说明

1 _messageEntries[], 静态成员,类型为 AFX_MSGMAP_ENTRY

  保存CMsgFrame中消息ID和对应的消息处理函数的数组

2 messageMap, 静态成员,类型为 AFX_MSGMAP

  保存了CMsgFrame中的_GetBaseMessageMap函数指针以及_messageEntries数组地址

3 _GetBaseMessageMap, 静态成员

  获取父类的messageMap的地址

4 GetMessageMap,虚函数

  获取自己的messageMap地址

 

下面通过“->”来捋一下几个成员直接的关系:

CMsgFrame::GetMessageMap -〉&messageMap{

      &_messageEntries[0] -> {

                          ID<->Func,

                               ID<->Func,

                               ....

                             }

  _GetBaseMessageMap -> &parent::messageMap

                        {

                       &_messageEntries[0],

                           _GetBaseMessageMap->&parent::messageMap

                        }

}

3 消息映射过程

1 消息处理函数WindowProc收到消息后,调用OnWndMsg处理消息,OnWndMsg如果不处理消息,那WindowProc将调用DefWindowProc默认处理消息并返回.

2 OnWndMsg处理消息

1 使用GetMessageMap函数获取该窗口类的messageMap变量的地址.

  const AFX_MSGMAP* pMessageMap;

  pMessageMap = GetMessageMap();

2 在messageMap中的lpEntries数组中,查找消息ID所对应的数组元素.

3 如果未找到,获取父类的messageMap指针,返回3.2.2,从父类的lpEntries数组继续查找.

4 如果找到,获取找到的数组元素的地址lpEntry,退出查找过程,执行下一步.

const AFX_MSGMAP_ENTRY* lpEntry = NULL;               

for ( ;  pMessageMap != NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())

{

        lpEntry = AfxFindMessageEntry(

        pMessageMap->lpEntries,

        message, 0, 0))

        if( lpEntry != NULL )

        {

                goto LDispatch;

        }

}

5 根据找到的lpEntry的nSig标识,调用lpEntry当中的pfn函数指针,处理消息.

LDispatch:

        union MessageMapFunctions mmf;

        mmf.pfn = lpEntry->pfn;

        int nSig;

        nSig = lpEntry->nSig;

        switch (nSig)

        {

                case AfxSig_lwl:

                        lResult = (this->*mmf.pfn_lwl)(wParam, lParam);

                break;

        }

MFC的消息分类

MFC的消息分类按照处理方式来分。主要分为4类:

1 窗口消息

例如WM_CREATE、WM_PAINT、鼠标、键盘等消息,这些消息的处理方式是直接调用消息处理函数.

这类消息使用的宏:

    ON_MESSAGE(  )

    ON_WM_XXXXX( ): ON_WM_CREATE()

消息处理时,采用上面的处理方式.

 

前面的WM_CREATE消息就不用写成ON_MESSAGE( WM_CREATE, OnCreate ),而直接写成ON_WM_CREATE即可。

函数的定义和声明也要相应地修改为:

afx_msg LRESULT OnCreate( WPARAM wParam, LPARAM lParam );

afx_msg int OnCreate();

LRESULT CMsgFrame::OnCreate( WPARAM wParam,

        LPARAM lParam )

{

    AfxMessageBox("OnCreate");

    return 0;

}

int CMsgFrame::OnCreate()

{

    AfxMessageBox("OnCreate");

    return 0;

}

 

2 命令消息 WM_COMMAND

菜单、工具栏、按钮等点击时的命令. 消息首先发送到主窗口,由主窗口逐层向子窗口派发。

这类的消息使用的宏:

     ON_COMMAND( )

     ON_COMMAND_RANGE( )

消息处理时,在OnWndMsg中调用OnCommand处理函数进行消息处理.

3 通知消息 WM_NOTIFY

子窗口对父窗口的通知消息。

  控件消息宏,例如: EDIT控件 ON_EN_CHANGEON_NOTIFY/ON_NOTIFY_RANGE消息处理时,在OnWndMsg中调用OnNotify(OnCommand)处理函数进行消息处理

4 自注册消息

用户自注册消息的处理。

用户需调用RegisterWindowMessage函数注册消息,然后在消息映射中使用.

  UINT RegisterWindowMessage(

     LPCTSTR lpString //消息名字符串

    );

返回注册成功的消息ID(0xC000-0xFFFF)

消息映射宏: ON_REGISTERED_MESSAGE

消息处理时, 与窗口消息处理类似,但是在查找消息处理函数和执行消息处理函数时不同.

自注册消息使用举例:

// RegisterMsg.cpp : Defines the entry point for the application.

//

 

#include "stdafx.h"

 

//注册消息ID

UINT g_nRegMsg = RegisterWindowMessage( "MYREGMSG" );

 

class CRegsiterFrame : public CFrameWnd

{

        DECLARE_MESSAGE_MAP( )

public:

        afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct );

        afx_msg void OnTest( );

        //消息处理函数

        afx_msg LRESULT OnRegMsg( WPARAM wParam,LPARAM lParam );

};

 

BEGIN_MESSAGE_MAP( CRegsiterFrame, CFrameWnd )

        ON_WM_CREATE( )

        ON_COMMAND( 1001, OnTest )

        //消息宏映射

        ON_REGISTERED_MESSAGE( g_nRegMsg, OnRegMsg )

END_MESSAGE_MAP( )

 

int CRegsiterFrame::OnCreate( LPCREATESTRUCT lpCreateStruct )

{        //父类的OnCreate处理

        /*if(!CFrameWnd::OnCreate( lpCreateStruct ) )

        {

                return 0;

        }*/

 

        //创建按钮

        CreateWindow( "BUTTON", "Test",

                WS_CHILD|WS_VISIBLE,

                50, 50, 200, 30, m_hWnd, (HMENU)1001,

                AfxGetApp()->m_hInstance, NULL );

 

        return 1;

}

 

void CRegsiterFrame::OnTest( )

{        //发送消息

        SendMessage( g_nRegMsg );

}

 

LRESULT CRegsiterFrame::OnRegMsg( WPARAM wParam,LPARAM lParam )

{

        AfxMessageBox( "OnRegMsg" );

        return 0;

}

 

class CRegisterApp : public CWinApp

{

public:

        virtual BOOL InitInstance( );

};

 

CRegisterApp theApp;

 

BOOL CRegisterApp::InitInstance( )

{

        CRegsiterFrame * pWnd = new CRegsiterFrame( );

        pWnd->Create( NULL, "Register Msg" );

        m_pMainWnd = pWnd;

        m_pMainWnd->ShowWindow( SW_SHOW );

        m_pMainWnd->UpdateWindow( );

        return TRUE;

}

 
原文地址:https://www.cnblogs.com/tangzhengyue/p/3153825.html