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;

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM