MFC應用程序設計(第二版)學習筆記

第一章, Windows應用開發基礎

基本概念

windows API函數按功能大體分爲以下:

窗口管理函數 WUI

圖形設備管理函數 GUI

系統服務函數 SUI

 

早期開發使用軟件開發工具包sdk,softwaredevelopment kit 就是直接使用API函數來開發應用程序

自面向對象發展以來,人們對WindowsAPI進行了封裝,從而使Windows應用程序結構和開發工具都發生了很大的變化

 

MFC::對API進行類封裝

 

Winsows的一個特殊類型——句柄

常用句柄類型

HWND / HINSTANCE / HCURSOR光標 / HFONT字體 / HPEN / BRUSH / HDC / HBITMAP / HICON圖標 / HMENU菜單 / HFILE

 

Windows自定義的數據類型都要大寫

數據類型:

Typedef unsigned long         DWORD

Typedef unsigned int            UINT

Typedef unsigned short WORD

 

消息與消息循環

typedefstruct tagMSG {

  HWND hwnd;

  UINT message;  //標識碼比如WM_CLOSE/ WM_CREATE

  WPARAM wParam;

  LPARAM lParam;

  DWORD time;

  POINT pt;

} MSG;

當發生某事件時,系統會把該事件的信息填寫到MSG中,併發到應用程序,應用根據MSG中的hwndmessage來確定哪個窗口的哪一段代碼來處理(窗口函數中的switch對應)

 

常用Winsows消息的消息標識碼

WM_CREATE 由CreateWindow產生的消息

WM_DESTROY 消除窗口,單擊右上角關閉,調用了API函數PostQuitMessage()

WM_CLOSE  關閉窗口

WM_LBUTTONDOWN 按下鼠標左鍵

 

系統爲每個應用程序建立了一個消息隊列的存儲空間

    while (GetMessage(&msg, NULL, 0, 0))  // 獲取消息--->隊列消息(鍵盤鼠標等的以及進程消息隊列)

{

       if (!TranslateAccelerator(msg.hwnd,hAccelTable, &msg))

       {

           TranslateMessage(&msg); // 把鍵盤消息翻譯成字符消息

           DispatchMessage(&msg);  // 把消息派發給系統而不是應用程序,系統再傳給窗口函數,窗口函數是由系統調用

       }

    }

還有一種消息是非隊列消息(一般是系統產生的),它直接給了窗口函數

另外,窗口函數在運行過程中也會發出消息,由getmessage獲得

 

windows系統把應用程序分成兩部分:一個是以消息循環爲主的獲取和發送消息的部分,另一個是專門處理消息的窗口函數部分。這兩部分都是系統調用的函數,這是與DOS系統應用程序的區別

 

Wsindows應用程序結構

intWINAPI WinMain(HINSTANCE hInstance,

                   HINSTANCE hPrevInstance, // 前一個應用程序的句柄

                   LPTSTR    lpCmdLine, // 指向命令行的指針

                   int       nCmdShow // 應用程序窗口顯示方式的標誌

);

系統調用用戶編寫的函數過程叫做回調WINAPI CALLBACK

主函數的兩個任務是建立應用程序的窗口建立消息循環,至少調用四個+三個=七個API:

RegisterClass(&wc);// 參數是窗口類。作用:把定義好的窗口屬性向系統登記

CreateWindow(szWindowClass(窗口類型的名字), szTitle標題, WS_VISIBLE窗口風格, CW_USEDEFAULT左上角x, CW_USEDEFAULT左上角y, CW_USEDEFAULT寬度, CW_USEDEFAULT高度, NULL父窗口句柄, NULL主菜單句柄, hInstance應用程序實例, NULL設爲NULL);

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

 

GetMessage(&msg, NULL, 0, 0)

TranslateMessage(&msg);

DispatchMessage(&msg);

 

 

typedefstruct _WNDCLASS {

  UINT style;               //窗口樣式,一般爲0

  WNDPROC lpfnWndProc;  //窗口函數 // 一個窗口只能有一個窗口函數,多個窗口可以共用一個窗口函數

  int cbClsExtra;  // 0

  int cbWndExtra;  // 0

  HANDLE hInstance; // 應用程序實例句柄

  HICON hIcon;  // 窗口圖標

  HCURSOR hCursor; //窗口光標

  HBRUSH hbrBackground; //窗口背景顏色

  LPCTSTR lpszMenuName;  // 窗口菜單資源名

  LPCTSTR lpszClassName; // 窗口類名字

 } WNDCLASS;

例如:

       wc.style         = CS_HREDRAW | CS_VREDRAW;

       wc.lpfnWndProc   = WndProc; //  同一個窗口過程可以被多個窗口類型使用

       wc.cbClsExtra    = 0;

       wc.cbWndExtra    = 0;

       wc.hInstance     = hInstance;

       wc.hIcon         = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_T683));

       wc.hCursor       = 0;

       wc.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);

       wc.lpszMenuName  = 0;

       wc.lpszClassName = szWindowClass; // 窗口類型的名字

 

不同的窗口類型可以使用相同的窗口函數

每個窗口都要有一個窗口類型, 窗口在CreateWindow的時候把窗口類型的名字作爲第一個參數傳進去

一個窗口類型可以用來創建多個窗口,CreateWindow的第一個參數相同就行,第二個參數是窗口實例的標題(通過它來區別),每個窗口實例都要showwindow和updatewindow

 

窗口函數

LRESULTCALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,LPARAM lParam)  // 附加參數32位

switch (message) { // 消息標識碼

        case WM_COMMAND:

            wmId    = LOWORD(wParam);// 低兩字節

            wmEvent = HIWORD(wParam);

            // 分析菜單選擇:

            switch (wmId)

            {

                case IDM_HELP_ABOUT:

                    DialogBox(g_hInst,(LPCTSTR)IDD_ABOUTBOX, hWnd, About);

                    break;

                case IDM_OK:

                    SendMessage (hWnd,WM_CLOSE, 0, 0);             

                    break;

                default:

                    return DefWindowProc(hWnd, message, wParam, lParam);

            }

            break;

        case WM_CREATE:

            ..........

        case WM_PAINT:

            hdc = BeginPaint(hWnd, &ps);

           ..............

            EndPaint(hWnd, &ps);

            break;

        case WM_DESTROY:

            CommandBar_Destroy(g_hWndMenuBar);

            PostQuitMessage(0);

            break;

        default:

            return DefWindowProc(hWnd,message, wParam, lParam); // 對沒有處理的消息進行默認處理   

}

 

windows系統,主函數,窗口函數間的關係 

主函數,窗口函數都是由Wiindows系統來調用的函數,只不過主函數是程序啓動之後,系統首先調用的函數,而窗口函數是主函數在獲得消息並把消息發給系統之後,由系統依據產生事件的窗口(MSG中記錄的hwnd所使用的窗口類&wc提供的函數指針WndProc調用的函數

 

使用函數封裝Windows程序

ATOM          MyRegisterClass(HINSTANCE, LPTSTR);

BOOL          InitInstance(HINSTANCE, int);

LRESULTCALLBACK  WndProc(HWND, UINT, WPARAM,LPARAM);

INT_PTRCALLBACK  About(HWND, UINT, WPARAM,LPARAM);

WinMain函數的返回值 return (int)msg.wParam;

見原書代碼1-3

 

窗口函數的另一種結構:使用消息映射表見自已寫的代碼d:\mfc_projects 下的1-4

structdecodeUINT {                            

    UINT nMessage;                                    // 消息標識

    LRESULT (*pfn)(HWND, UINT, WPARAM, LPARAM);   // 消息處理的函數指針

};

structdecodeUINT MainMessages[] = {

    WM_CREATE, DoCreateMain, // 成對出現的

    WM_COMMAND, DoCommandMain,

    MYMSG_ADDLINE, DoAddLineMain,

    WM_DESTROY, DoDestroyMain,

};

窗口函數可以這樣設計:

#definedim(x) (sizeof(x) / sizeof(x[0]))

LRESULCALLBACK WndProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam){

for (int i = 0; i <dim(MainMessages); i++) {

        if (message== MainMessages[i].nMessage) {

(*MainMessages[i].pfn)(hWnd,wMsg, wParam, lParam);

}  

}

return DefWindowProc (hWnd, wMsg,wParam, lParam);

}

可以將MainMessages和填寫消息映射表薦的代碼寫成宏的形式,見原書代碼1-4

如下:

#defineDECLARE_MESSAGE_MAP( ) \

structMSGMAP_ENTRY  _messageEntres[ ];\ //接着下一行

 

#defineBEGIN_MESSAGE_MAP( ) \

structMSGMAP_ENTRY  _messageEntres[ ] = \

{ \ // 接着下一行

 

#defineON_WM(messageID,msgFuc) \

    messageID,msgFuc,

#defineEND_MESSAGE_MAP( ) \

};\   // 接着下一行

 

填寫表薦內容:

DECLARE_MESSAGE_MAP()

BEGIN_MESSAGE_MAP()

ON_WM(WM_LBUTTONDOWN,On_LButtonDown)

ON_WM(WM_PAINT,On_Paint)

ON_WM(WM_DESTROY,On_Destroy)

END_MESSAGE_MAP()

第二章,Windows應用程序的類封裝

MFC:將大多windowsAPI和相應的數據進行封裝

最大優點是將程序問題分解成若干對象,將大型程序模塊化。 利用類的繼承,能快速完成程序設計

程序框架:應用程序類+窗口類     

應用程序類中含有一個窗口類對象的指針CFrameWnd* m_pMainWnd

應用程序類對象是一個全局對象CWinApp theApp;

本書1-3的程序用類封裝後見程序2-1

看一下應用程序類的構造函數

BOOLCWinApp::InitInstance(int nCmdShow)

{

    m_pMainWnd=new CFrameWnd;

    m_pMainWnd->Create(NULL,"封裝的Windows程序");

    m_pMainWnd->ShowWindow(nCmdShow);

    m_pMainWnd->UpdateWindow();

    return TRUE;

}

成員函數的定義

voidCFrameWnd::ShowWindow(int nCmdShow)

{

    ::ShowWindow(hWnd, nCmdShow); // 前面的::說明是系統函數,不屬於任何類

}

 

程序2-3(2-2不用看了)

應用程序的派生類

通過上面的類封裝,增強了代碼複用性,減少了程序員的負擔。

更牛X的,MFC希望程序設計嚮導自動生成主函數,自動生成的標誌符就不變的。

下面糾結的是,主函數中標誌符的不變性與全局變量theApp名字可變性的矛盾。主函數中不能使用全局對象theApp的名字。

我們約定,在主函數中使用指針pApp,怎麼也讓它指向那個名字可以隨意變的全局對象呢?

解決辦法: 在應用程序類CWinApp中增加一個protected *m_pCurrentApp指針變量,在構造函數中將this賦給它。在WinMain函數中使用全局函數pApp = AfxGetApp();  定義如下

CWinApp*AfxGetApp(){

    return myApp.m_pCurrentWinApp;

}

 

利用C++的繼承和多態  豐富和完善基類 滿足用戶多樣性的要求

程序2-2只是在2-1的CWinApp中派生CMyApp  而將父類CWinApp的InitInstance成員函數設爲virtual

程序2-3包括了2-2且解決了上面糾結的問題。

消息映射表

順着思路去想:爲了封裝,可不可以把窗口函數封裝在窗口類中,然後在WndProc中調用這個成員函數?

帶來的問題:只有窗口類和其派生類纔有消息處理的能力。但實際中,按鈕,對話框等都有消息處理能力。所以最好封裝在單獨的一個類CCmdTarget中,作爲高端類,以後凡是有消息處理能力的類都從它來派生。

這種封裝參考2-4

 

繼續討論。上面帶來的問題有兩個,一是消息處理函數通常不在CCmdTarget中,而是在它的某個派生類中。另一個是可讀性差。前一個問題可以通過虛函數解決,但需要維護一個大的虛函數表。可讀性需要使用消息映射來解決。

MFC要求在定義一個有消息處理能力的類時,要從CCmdTarget類或其基類繼承,然後聲明消息處理函數,比如
afx_msg void OnDraw();

afx_msgvoid OnLButtonDown();

而類的消息映射表項結構如下:

struct AFX_MESSAGE_ENTRY { // 多了好幾個成員

    UINT nMessage; // 消息標識碼

    UINT nCode; // 控制消息的通知碼

    UINT nID; //WindowsControlID

    UINT nLastID; // 指定消息被映射的範圍

    UINT nSig; // 消息的動作標識

    AFX_PMSGpfn; // 指向CCmdTarget或其派生類的成員函數,就是上面的OnDraw等

};

每個具有消息處理能力的類都有struct AFX_MSGMAP_ENTRY_messageEntries[]這個數組(在類外填表內容),而且,爲了形成總表,要外加兩個指針pBaseMap(指向父類對象)和lpEntries(指向數組_messageEntries[])。

 

形成一個總表,不管CCmdTarget類簇中另個類對象接到消息,都可以在總表中尋找消息處理函數。A派生B,如果兩個類有相同的消息函數,B收到消息只會執行它自己的那個消息處理函數,此乃多態。

 

注意到,MFC在解決類似消息映射表這種包含在類中,又具有全局意義的信息時,多用使用了這種表結構。見筆記本第1頁。

 

宏定義負責填寫_messageEntries的各項內容,並負責建立鏈表中的結點,把類的消息映射表加入到鏈表中。在類的聲明文件(頭文件)中聲明消息映射。下文會再細說

 

MFC把消息分爲三類:

(1)標準消息WM_XXX

就是最先認識的那些WM_XXX類的消息。Switch case判斷的那種。

消息處理函數是OnXXX. 而宏格式爲ON_WM_XXX

舉個例子:消息WM_LBUTTONDOWN   函數afx_msg void OnLButtonDown() 宏ON_WM_LBUTTONDOWN()沒有參數

(2)命令消息 WM_COMMAMD

WM_COMMAMD來自菜單,另外還有工具條,加速鍵等用戶接口對象的消息。屬於應用程序自己定義的消息,系統沒有默認的消息處理函數。

宏格式:ON_COMMAND(IDM_FILENEW, OnFileNew)

ON_COMMAND(IDM_FILEOPEN, OnFileOpen)

(3)Notification消息

由按鈕、文本編輯框等控件產生的消息。由於控件的種類很多,因此宏格式不盡相同。

按鈕的宏格式:ON_BN_CLICKED(<消息標識>, <對應的消息處理函數>)

組合控件(ComboBox)雙擊事件的宏格式:ON_CBN_DBLCLK(<消息標識>, <對應的消息處理函數>)

文本編輯控件(Edit)的雙擊事件的宏格式爲:ON_EBN_DBLCLK(<消息標識>, <對應的消息處理函數>)

 

參考2-5:

classCMyWnd:public CFrameWnd

{

private:

    char*ShowText;           //聲明一個字符串爲數據成員

public:

    afx_msg void OnPaint();//聲明WM_PAINT消息處理函數

    afx_msg void OnLButtonDown();//聲明鼠標左鍵按下消息處理函數

    DECLARE_MESSAGE_MAP()    //聲明消息映射,在類中

};

//消息映射的實現,在類外----------------------------------------------------------------------

BEGIN_MESSAGE_MAP(CMyWnd,CFrameWnd) // 類名稱,基類名稱

       ON_WM_PAINT()

       ON_WM_LBUTTONDOWN()

END_MESSAGE_MAP( )

第三章 MFC應用程序框架

早期應用程序框架

如MFCexp2_3.cpp,主函數如下:

intAPIENTRY WinMain(...) {

    int ResultCode=-1;

    CWinApp *pApp;

    pApp=AfxGetApp();

    pApp->InitInstance(nCmdShow); // 實例化

    return ResultCode=pApp->Run(); // 運行

}

MFC希望把程序的主函數的函數體也做作一個對象來處理 CWinApp, 筆記本第3頁

從第3頁圖中知道,CWnd *m_pMainWnd定義在了CWinThread中,在2-3程序中,此成員是在CWinCpp中,CFrameWnd * m_pMainWnd;

CWinApp這個類有三個virtual成員函數InitApplication、InitInstance和Run。其中InitInstance是爲程序創建和顯示窗口所設置的。因此在設計程序時,必須在CWinApp類的基礎上派生自己的應用程序類,並對函數InitInstance進行重寫。

使用早期MFC應用程序框架類設計的一個最簡單的Windows應用程序:MFCexp3_1.cpp 裏面作了筆記

 

文檔/視圖結構

使用Visual C++生成框架,根據需要添加代碼。

    原來的應用程序主窗口對象任務繁重,所以被拆分成了窗口框架類CFrameWnd對象視圖類CView對象文檔類CDocument對象三個對象

1)  窗口框架類CFrameWnd對象: 窗口框大小,標題,菜單,狀態條的窗框部分,總之,只管邊框

2)  視圖類CView對象: 數據顯示與用戶交互部分,總之,窗口用戶區

CView把原來窗口框架類承擔數據顯示和接受用戶對用戶區操作的代碼單獨分出來,形成的一個單獨的類,這的對象是應用程序與用戶進行交互的界面

3)  文檔類CDocument對象: 數據存儲與管理、運算

 

以上三個對象由文檔模板對象統一創建和管理

一庫多店的結構: CFrameWnd相當於窗框,CView相當於窗框上的玻璃,而CDocument相當於室內物品

 

單文檔界面SDI和多文檔界面MDI(MultipleDocument Interface)結構

    多文檔界面要提供一個Close用於關閉當前打開的文檔

   

 

三個對象的派生類:

CFrameWnd的派生類CMainFrame

使用MFC App Wizard來創建應用程序,嚮導會爲程序員自動從CFrameWnd派生一個叫做CMainFrame的派生類。

classCMainFram: public CFrameWnd // 這個類基本不需要用戶進行編碼,任務都分給下面兩個類了

CFrameWnd的部分成員函數如下:

ClassCFrameWnd: public CWnd {

    DELCARE_DYNCREATE(CFrameWnd)

Public:

    CWnd* CreateView();

 

    Virtual CDocument *GetActiveDocument();

    CView* GetActiveVirw();

    Virtual CFrameWnd* GetActiveFrame();

 

    Void SetActiveView();// 設置活動視圖

    ...

};

CView的派生類CMyView(工程名My)

class CMyView: public CView

{

protected:

    CMyView();

    DECLARE_DYNCREATE(CYmView);

public:

    CMyDoc* GetDocument(); // 獲得文檔類對象的指針,與文檔類聯繫的通道

    virtual void OnDraw(CDC *pDC); // 重畫函數,當容器出現及其大小發生變化時,系統會自動調用它,進行重畫。pDC相當於工具箱

    ……

protected:

    DECLARE_MESSAGE_MAP();// 類的定義,最後一個沒有分號,加不加都行

};

例:

VoidCMyView::OnDraw(CDC *pDC) {

    CRect rt(50,50,150,150);

    pDC->Rectangle(&rt);

//如果用到文檔對象則使用CMyDoc* pDoc = GetDocument();

}

CDocument的派生類CMyDoc(工程名My)

    Class CMyDoc:public CDocument // 該文檔類派生自CCmdTarget類,可以接收來自菜單或工具條發來的命令消息:WM_COMMAMD

    {

Private:

    IntArray[5]; // 數據存儲

protected:

    CMyDoc();

    DLARE_DYNCREATE(CMyDoc);

public:

    voidSetMem(int I, int x); //自己定義的

    intGetMem(int i);

public:

    virtualBOOL OnNewDocument();

    virtualvoid Serialize(CArchive& ar); //這是用戶編碼多的地方。當用戶菜單對文件進行新建,打開,保存等操作時,應用程序會自動調用這個函數。它既負責由文件讀數據也負責向文件寫數據

    virtual~CMyDoc();

    DECLARE_MESSAGE_MAP();

};

CMyDoc::CMyDoc(){

    for(int I = 0; i < 3; ++i)

       Array[i]= 0;

}

void CMyDoc::SetMem(int I, int x) {

    Array[i]= x;

}

int GetMem(int i) {

    returnArray[i];

}

文檔模板類CDocTemplate

CDocTemplate把視圖對象,框架窗口對象和文檔對象組裝在一起並統一進行管理

分爲單文檔模板類CSingleDocTemplate和多文檔模板類CMultiDocTemplate, 筆記本第5頁

CSingleDocTemplate有個成員CDocument *m_pOnlyDoc //文檔指針

CMultiDocTemplate有個成員CPtrList m_docList //文檔鏈表,可以同時打開多個文檔

使用MFC App Wizard來創建應用程序,嚮導會爲你生成一個合適的,用戶基本不用編碼

 

應用程序的派生類

需要一個應用程序類對象作爲上面各類對象的容器

classCMyApp: public CWinApp // 定義自己的應用程序對象

{

public:

    CMyApp();

public:

    virtual BOOL InitInstance(); // 生成和顯示窗口

    afx_msg void OnAppAbout();

    DECLARE_MESSAGE_MAP();

};

 

BOOLCMyApp::InitInstance()

{

    CSingleDocTemplate* pDocTemplate;

    pDocTemplate = newCSingleDocTemplate(IDR_MAINFRAME,  // 模板使用的資源ID

RUNTIME_CLASS(CMyDoc), //使用創建文檔對象

RUNTIME_CALSS(CMainFrame),

RUNTIME_CLASS(CMyView));

    AddDocTemplate(pDocTemplate); // 將該模板加入模板鏈表

    ……

    m_pMainWnd->ShowWindow(SW_SHOW);

    m_pMainWnd->UpdateWindow();

    return TRUE;

}

 

程序員的工作

使用MFC App Wizard來創建應用程序,嚮導會自動生成各個派生類,並同時定義應用程序全局對象和生成主函數,程序員做如下工作
1)重寫CWinApp派生類的虛函數InitInstance(),按自己的需要創建和顯示窗口

2)在CDocument的派生類中,聲明程序所需的數據和對這些數據進行必要操作的接口

3) 在CView的派生爲中編寫消息處理的代碼,如果消息處理需要用到文檔中的數據,則通過GetDocument來獲取文檔對象指針,然後操作數據接口函數; 在CView中編寫OnDraw重繪代碼

4) 用宏實現類的消息映射表BEGIN_MESSAGE_MAP(類名稱,基類名稱)

 

MFC文檔/視圖應用程序框架中各個對象的關係

各對象創建的順序

全局應用程序對象在WinMain函數之前創建,程序啓動後,主函數首先調用應用全局對象的InitInstance(),在該函數中創建文檔模板對象

    pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,  // 模板使用的資源ID

RUNTIME_CLASS(CMyDoc), //靜態對象成員的地址 形參類型一定是CRuntimeClass*

RUNTIME_CALSS(CMainFrame), // &CMainFrame::classCMainFrame

RUNTIME_CLASS(CMyView));    //&CMyView::classCMyView

注:宏定義體#define RUNTIME_CLASS(class_name) (CRuntimeClass*)(&class_name::class##class_name)

    這裏,用MFC的宏RUNTIME_CLASS傳遞了文檔類,框架窗口類,視圖類的類信息表,構造函數根據資源和類信息表動態地創建文檔,視圖,窗口框架三個對象,其中視圖對象是由框架窗口對象創建並管理,各對象創建順序見第8頁

應用程序各對象之間的關係圖,筆記本第7頁

QQ日誌中:VC的應用程序框架中各類之間的訪問方法

SDI應用程序框架對象之間的聯繫方法

解釋:

(1)   應用程序類對象

CExamlpleApp*p = (CExamlpleApp*)AfxGetApp(); 

(2)   窗口框架對象CMainFrame

通過AfxGetMainWnd或通過應用類的成員函數GetMainWnd()   成員函數或  m_pMainWnd   成員變量獲得指針

對於MID, 通過子框架窗口對象的GetMIDFrame

獲得指針需要強制轉換

 

GetActiveDocument是獲得活動視圖的文檔對象

框架窗口對象調用CView::OnActiveView()使一個視圖對象爲活動視圖

(3)   文檔對象

獲得視圖鏈表中的對象first next

更新所有視圖

通過GetDocTemplate獲得文檔模板對象

(4)   視圖對象

視圖類對象是由主邊框窗口(SDI   應用程序)或子邊框窗口(MDI   應用程序)構造生成

三個get

另外還有一個get: getDocTemplate()獲得文檔模板對象

CView::OnUpdate()更新一個視圖對象的顯示

 

應用類對象通常只生成一個文檔模板類對象   ,不過用戶可以自己生成多個文檔模板類對象,從而使SDI  也可以打開多個文檔,具有了MDI的特徵.所有的文檔模板類對象組   成了一個鏈表,應用類m_pDocManger   成員變量指向該鏈表。  
用戶可以通過應用類的成員函數  GetFirstDocTemplatePosition()和GetNextDocTemplate(POSITION   &   pos)來訪問該鏈表:  
                         POSITION   pos =GetFirstDocTemplatePosition();  
                         CDocTemplate   *pDocTemplate =GetNextDocTemplate(pos);  
         pDocTemplate即是指向第一個文檔模板類對象的指針,用戶還可以繼續調用 GetNextTemplate()得到下一個文檔模板類對象. 

MDI應用程序框架對象之間的聯繫方法

(1)   子框架窗口對象

主邊框窗口類的  CFrameWnd   *   GetActiveFrame()   得到指向該對象的指針

(2)   單文檔模板類只能生成一個文檔類對象,並 用成員變量   m_pOnlyDoc   指向該對象。多文檔模板類可以生成多個文檔類對象,另用成 員變量   m_docList   指向文檔對象組成的鏈表

用戶可以通過多文檔模板類對象的成員函數  GetFirstDocPosition()  和 GetNextDoc(POSITION   &pos)   來訪問文檔對象組成的鏈表:  
                         POSITION   pos =GetFirstDocPosition();  
                         CDocument   *pDoc =GetNextDoc(pos);  
              另外,文檔類還可以通過其成員函數   CDocTemplate  *   GetDocTemplate()   返過來 訪問文檔模板類對象

QQ那篇文章最後寫道:(以後再理解)

對於頻繁性地訪問某一固定對象或對運行速度要求較高的場合,我們可以 通過保存該對象的窗口句柄(只限於派生於 CWnd   的類),在需要的時候,通過函數 CWnd::FromHandle(HWND   hwnd)   來得到該對象的指針.

對象的動態創建

MDI程序可以建立多個文檔模板,在程序啓動後,應該建立哪種類型的文檔模板對象呢? 打開或新建時向用戶提出詢問,得到答案後,按要求創建模板對象.

每個需要動態創建對象的類定義一個可以創建本類對象的創建函數

CObject*CreateObject() { return new Myclass; } // 要求Myclass必須要有無參構造函數

然後再製作一個全局表,凡是需要動態創建對象的類都在這個表中佔一項,把類名稱對象創建函數關聯起來,當程序拿到類名後,可以根據類名在表中找到與其對應的對象構建函數指針,通過它來調用對象構建函數創建對象.

 

例子3-5演示了一個動態創建Myclass類對象。  注意虛函數的使用

類信息表

MFC在許多不同場合要用到類的其他信息,MFC就把這些信息統統放在上面所說的映射表項中,並叫作類信息表.

structCRuntimeClass

{

    LPCSTR m_lpszClassName; // 類名字

    ……

    CObject* (PASCAL* m_pfnCreateObject)();

    CObject* _stdcall CreateObject();

    ……

    CRuntimeClass* m_pBaseClass; // 向上查詢,該表就類族譜系表(有助於辨別一個對象是哪個類族的——運行期對象類型識別RTTI)

    CRuntimeClass* m_pNextClass; // 沿着查詢,該表就是類信息表 指針

};

 

使用DECLARE_DYNCREATE封裝了類信息表的聲明代碼,使用IMPLEMENT_DYNCREATE封裝了類信息表及其鏈表的實現代碼。處理有動態創建對象能力的類時,必須要在類中使用這兩個宏:

class A:puclic B

{

public:

    DECLARE_DYNCREATE(A)

};

IMPLEMENT_DYNCREATE(A,B);

 

例子3-5中,通過

    CRuntimeClass* p=Myclass::GetRuntimeClass();

CRuntimeClass*Myclass::GetRuntimeClass() {

    return &Myclass::classMyclass;  //返回靜態對象成員的地址

}

來獲得對象所屬類的類信息表指針,而在MFC中,這部分代碼封裝成了宏:

#defineRUNTIME_CLASS(class_name) \

    ((CRuntimeClass*)(&class_name::class##class_name))

而這個宏在上面CSingleDocTemplate的構造函數中使用

第四章 圖形

Windows爲圖形顯示設備進行了軟件封裝,用戶在這個虛擬設備上繪圖,而把它轉化爲物理設備圖形的任務由系統去完成。

圖形設備描述表DC,可以看成是一個畫板

應用程序->DC / 圖形設備驅動 -> 物理顯示設備

 

把DC的相關操作封裝成函數:GDI (graphical device interface)

繪圖工具:畫筆,畫刷,字體Font,位圖,調色板Palette(繪圖時的顏色集 )

 

windows要求繪圖環境在任何時候都應該存有一套完整的繪圖工具,這意味着不能從繪圖環境中刪除工具,只能用一個換掉另一個

 

把DC與GDI封裝成一個類,就是CDC類。

它派生了幾種特定的設備描述環境類:

CClientDC         窗口客戶區的,用在WM_PAINT消息之外的消息處理函數中, OnDraw之外的地方也可以使用

CMetaFileDC       圖元文件的,在創建可以回放的圖像時使用

CPaintDC          窗口用戶區的,在OnDraw的函數中處理WM_PAINT消息。它的對象作實參調用OnDraw(CDC* pDC)

CWindowDC         在整個窗口內(而不只是用戶區)繪圖的

 

Cpen類

繼承: CObject -> CGdiObject ->CPen

 

PS_NULL: 筆畫不可見的畫筆

 

4-1例子就在視圖類的OnDraw中調用了一句TextOut // OnDraw函數是自動調用的

4-2在視圖類中使用SelectObject MoveTo LineTo畫了八條逐粗的線

4-3同樣在OnDraw畫了幾條線,注意DASH與DOT的區別

        CPennewPen(style[s],1,RGB(0,0,0));

       CPen*oldPen=pDC->SelectObject(&newPen);

       pDC->MoveTo(20,row);

       pDC->LineTo(300,row);

       pDC->SelectObject(oldPen);

 

CBrush類

繼承: CObject -> CGdiObject ->CBrush

 

· HS_BDIAGONAL    左下到右上45度填充

· HS_CROSS         十字交叉

· HS_DIAGCROSS      交叉的45度線

· HS_FDIAGONAL    左上到右下45度

· HS_HORIZONTAL     Horizontal hatch

· HS_VERTICAL    Vertical hatch

以上的使用參見

4-4例, 也是在視圖類的OnDraw中

4-5中,是用系統默認的畫筆及畫刷來畫圖

 

繪圖模式SetROP2

intSetROP2(int nDrawMode); // 返回值是原來的繪圖模式

R2_BLACK               無論畫筆顏色怎樣,只用黑色繪圖

R2_WHITE              無論畫筆顏色怎樣,只用白色繪圖

R2_NOP               無論畫筆顏色怎樣,只用無色繪圖

R2_COPYPEN          用畫筆顏色繪圖

R2_NOT               用與背景色的反色繪圖

R2_NOTCOPYPEN      用畫筆的反色繪圖

R2_XORPEN        用畫筆色與背景色異或後的顏色進行畫圖

 

文本和CFont類

4-6:文本顏色與背景顏色(僅僅是字的背景)

    pDC->SetTextColor(RGB(255,0,0)); // 紅字

    pDC->TextOut(130,30,"文本的顏色");

 

    pDC->SetTextColor(RGB(255,255,255)); // 白字

    pDC->SetBkColor(RGB(0,0,255)); // 藍底

    pDC->TextOut(230,30,"文本的顏色");

 

還有一個獲得當前背景色的成員函數:

COLOREFGetBkColor()const;

 

4-7: 字符間距

       pDC->SetTextCharacterExtra(s*4);

       pDC->TextOut(20,20+s*20,"文本字符的間距");

 

獲得此值:

intGetTextCharacterExtra() const;

 

4-8: 文本對齊方式

pDC->SetTextAlign(TA_LEFT);

還有TA_RIGHT, TA_CENTER, TA_TOP, TA_BOTTOM,TA_BASELINE(基線對齊)

 

字體信息:TEXTMETRIC結構

OnDraw中

TEXTMETRICtm;

pDC->GetTextMetrics(&tm);

 

CreateFont

使用預存的字體格式創建Font: CreateFontIndirect(const LOGFONT lpLogFont)

 

CClientDC類

4-9: 在OnLButtonDown中使用CClientDC類

    CClientDCdc(this);      //定義一個CClientDC的對象dc

    CRectrc;                //定義一個描述矩形的對象rc

    GetClientRect(&rc);      //獲得用戶區的尺寸,並存入rc

    //以下是繪製菱形的代碼

    dc.MoveTo(0,(rc.bottom+rc.top)/2);// 畫線的起點位置

    dc.LineTo((rc.right+rc.left)/2,0);

    dc.LineTo(rc.right,(rc.bottom+rc.top)/2);

    dc.LineTo((rc.right+rc.left)/2,rc.bottom);

    dc.LineTo(0,(rc.bottom+rc.top)/2);

    CView::OnLButtonDown(nFlags,point); // 調用父類中同名函數,上面是對父類同名函數重寫而擴展的部分

CMetaFileDC類

有些圖形是需要經常重複顯示的,這種圖形事先繪製好一個文件放在內在中,用的時候直接打開就行了。這種圖形文件叫用圖元文件

製作這種文件需要CMetaFileDC類

一般地,在視圖類的OnCreate中創建圖元文件

過程是:

1)先創建一個CMetaFileDC對象,再調用成員函數Create(LPCTSTR lpszFilenam=NULL), 參數是文件名,再調用繪製函數繪製圖形,最後調用Close函數返回HMETAFILE類型的句柄給m_hMetaFile數據成員。

2)要顯示時,調用CDC的成員函數PlayMetaFile(m_hMetaFile);

3)最後,不再使用的時候要::DeleteMetaFile(m_hMetaFile);進行刪除

以上實例見4-10。在視圖類的OnCreate中創建並保存到m_hMetaFile中;在OnLButtonDown函數中顯示PlayMetaFile;在OnDestroy()中刪除DeleteMetaFile

 

 

第五章 MFC的通用類

CPoint與CRect都有重載的+=, -=, !=, ==

尺寸類CSize(int initCX,int initCY), 有一個x寬和y高

 

5-1是對Cstring類的使用,並調用 了AfxMessageBox();

5-2單會出現一個隨機大小的矩形, 每次重畫都要畫出所有先前的矩形,那麼,就要用數組存放之前生成的所有矩形

    1)類成員:CArray<CRect,CRect&> m_Rectag;

    2)OnLButtonDown 中每單擊一下就要把生成的Ret加入m_Rectag.Add(Ret);並且InvalidateRect(Ret,FALSE);//觸發OnDraw()函數

    3)在OnDraw中for顯示:

for(inti=0;i<m_Rectag.GetSize();i++)

           pDC->Rectangle(m_Rectag[i]);

5-3上面的例子把數據放在了視圖類中,不合理,本例中把數組放在文檔類中,OnDraw中通過GetDocument來獲取文檔類的指針

    在文檔類的構造函數中定義數組的大小  m_Rectag.SetSize(256,256);

 

第六章 界面設計

6-1關於框架類中的PreCreateWindow,設置樣式style

6-2拆分窗口的同步更新

void CMFCexp6_2View::OnLButtonDown(UINT nFlags, CPoint point)

{

    CMFCexp6_2Doc*pDoc=GetDocument();//獲取文檔指針

    intr=rand()%50+5;

    CRectRet(point.x-r,point.y-r,point.x+r,point.y+r);

    pDoc->m_Rectag.Add(Ret);//向文檔中數組添加元素

    pDoc->UpdateAllViews(NULL);//使用了更新所有視圖的函數

    CView::OnLButtonDown(nFlags,point);

}

函數原型:

voidUpdateAllViews(

   CView* pSender,// 該函數的調用者

   LPARAM lHint = 0L,

   CObject* pHint= NULL

);

6-3提高效率,

void CMFCexp6_3View::OnLButtonDown(UINT nFlags, CPoint point)

{

    CMFCexp6_3Doc*pDoc=GetDocument();//獲取文檔指針

    intr=rand()%50+5;

    CRectRet(point.x-r,point.y-r,point.x+r,point.y+r);

    pDoc->m_Rectag.Add(Ret);//向文檔中數組添加元素

    m_ViewDrRect->m_DrawRect=Ret;//將矩形參數賦予重繪區類成員

    InvalidateRect(Ret,FALSE);//觸發OnDraw()函數,傳遞無效區類對象

    pDoc->UpdateAllViews(this,0L,m_ViewDrRect);   //傳遞this則重繪其他視圖

    CView::OnLButtonDown(nFlags,point);

}

分析1)只對變化的區域重繪:InvalidateRect(LPCRECT lpRect, BOO. bErase =TRUE),是觸發OnDraw對屏幕進行重繪,第一個參數是指定無效區域,只對這個區域進行重繪。

2)通知其他區域也只對變化的區域重繪:voidUpdateAllViews(CView* pSender, LPARAM lHint = 0L,CObject* pHint = NULL),它調用了各視圖對象的OnUpdate(CView*pSender, LPARAM lHint, CObject* pHint)方法,而且第三個參數對應第三個參數傳遞,OnUpdate是個虛函數,重寫OnUpdate,在裏面調用InvalidateRect(Rect,FALSE);這個Rect通過第三個參數帶着無效區域進來的,這第三個參數通過定義一個CObject的子類CDrawRect(在CDrawRect.h頭文件),這個子類中有CRect m_DrawRect數據成員。CDrawRect在視圖類中作爲對象成員:CDrawRect* m_ViewDrRect; 它是以指針形式存在,它的初始化在視圖類的構造函數中。最後在OnDraw中進行繪圖,for中畫的橢圓pDC->Ellipse(pDoc->m_Rectag[i]);

本程序其他細節:

在StdAfx.h中加#include <afxtempl.h>

 

書中沒有給出6-4的例子,經過多次調試,終於搞出來了,搞的是6-5名字

新建一個類CDrawRect類: 兩種方法可以做到,一種是在MFCexp6_5 classes右擊,new class新建一個類,在VC6.0中彈出的對話框中,新建類爲MFC類時父類選擇下拉中沒有CObject,所以不能滿足需要,經實驗,在VS2005中是可以的。另一種方法是自己新加一個頭文件DrawRect.h,裏面要有一行: #pragma once

    關於它的解釋:只include一次,加快編譯的速度,跟ifndef define endif並不多,好像能在VC的編譯器上使用

另外,爲了能響應單擊消息,添加如下黑體字部分

BEGIN_MESSAGE_MAP(CMFCexp6_5View,CScrollView)

    //{{AFX_MSG_MAP(CMFCexp6_5View)

       // NOTE - the ClassWizard will add andremove mapping macros here.

       //   DO NOT EDIT what you see in these blocks of generated code!

    ON_WM_LBUTTONDOWN()

    //}}AFX_MSG_MAP

    // Standard printing commands

    ON_COMMAND(ID_FILE_PRINT,CScrollView::OnFilePrint)

    ON_COMMAND(ID_FILE_PRINT_DIRECT,CScrollView::OnFilePrint)

    ON_COMMAND(ID_FILE_PRINT_PREVIEW,CScrollView::OnFilePrintPreview)

END_MESSAGE_MAP()

最後一點,在重寫OnUpdate與OnLButtonDown的時候要保證在類中先在頭文件的類中聲明(這樣才能在類視圖中列出來),源文件中實現

還有,視圖類是從CScrollView繼承來的,有個虛成員函數OnInitialUpdate,在它裏面加以下

    CSize sizeTotal(3000, 2000);

    CSize sizeLine(10, 10);

    CSize sizePage(50, 50);

    SetScrollSizes(MM_TEXT, sizeTotal, sizePage,sizeLine);

6-5有了,現在的問題是:先使用滾動條把窗口向右移動後,要在圓1的右上角畫一個圓,而在圓1的左上角卻出現了這個圓。原因中移動滾動條後,窗口向右發生移動,文檔的原點已不再與視圖的原點重合。視圖類不能對此自動修正。程序還當作跟原來的重合,故而錯誤

爲了解決這個問題,就要在畫圖時,把視圖的座標轉換成文檔的座標,而在顯示文檔數據時則需要所文檔的坐再轉換爲視圖的座標

用到DC類的成員函數:

voidDPtoLP(): 設備座標(視圖)轉邏輯座標(文檔座標)

voidLPtoDP()

修改OnLButtonDown與OnUpdate

 

OnInitUpdateOnDraw之前調用,因此象ScrollView等設置可在此完成

一般在OnPrepareDC裏設置DC映射方式然後在函數裏這樣調用

CClientDC dc(this);
OnPrepareDC(&dc);

當在SCrollView裏設置映射方式時,OnPrepareDC設置將無效

 

 

第七章 鼠標和鍵盤

7-1 給一類加比如左WM_LBUTTONDOWN消息時,在類視圖下找到視圖類,右擊Add windows message handler, 選WM_LBUTTONDOWN,然後add and edit,就會加入這個函數的聲明以及實現,並在BEGIN_MESSAGE_MAP() END_MESSAGE_MAP()之間加入相應代碼

 

7-2 鼠標移動過的地方畫線, 在OnMouseMove中添加東西,其中一細節

文檔類中定義成員CPoint m_Point; // 沒有在構造函數中初始化,它是默認初始化的。使用在OnLButtonDown中單擊點位置初始化它

我把它改成如下了,(單擊初始點和終止點畫點),單擊移動,放開後成線

//現在又明白了,不只是OnDraw裏才能畫圖,清除上次畫線的過程就是在這個函數中畫線完成的

 

 

7-3是非用戶區鼠標消息

windowS不希望用戶使用這種消息,系統對這些消息都預置了默認的處理,用戶最好不要干涉

WM_NCLBUTTONDOWN

, 需要手動添加,即三步走,第一步在類中聲明方法,第二步在BEGIN_MESSAGE_MAP中添加ON_WM_NCMOUSEMOVE(),第三步實現方法

 

7-4

SetCapture();// 有與沒有區別就在於鼠標是否出了用戶區

它一旦調用,則應用程序窗口將是鼠標消息去向的唯一目的地

 

7-5處理鍵盤消息

鍵盤消息總是傳給帶有輸入焦點的窗口

每鍵有個編碼,叫做掃描碼,是與設備相關的,爲此window設計了虛擬碼,與設備無關。編程直接使用虛擬碼

VK_ADD

VK_CONTROL等等

7-5畫一個圓,按下向左或向右,它就移動

響應的是ON_WM_KEYDOWN()

afx_msgvoid OnKeyDown(UINT nChar, UINTnRepCnt, UINT nFlags);

需要使用switch case判斷 另外還要判斷以防移出邊界

 

一般鍵盤消息

WM_CAHR//

afx_msg voidOnChar(

   UINT nChar, // 是字符對應的ASCII

   UINT nRepCnt, // 鍵重複的次數

   UINT nFlags // 32

);

WM_KEYDOWN

WM_KEYUP

 

 

7-6增加一功能,按R或L,可使向左向右移動

 

系統按鍵消息,是ALT+其他鍵。

一般用戶不處理,如果處理,則還要在後面添加DefWindowsProc以便不影響系統的處理

 

窗口焦點

獲得焦點發出WM_SETFOCUS消息,失去時發出WM_KILLFOCUS消息

7-7

獲得與失去焦點,只有在有焦點的時候,按鍵纔有消息,這裏只是讓它響一聲

 

第八章 資源

除了文檔中的數據,用戶界面UI還需要一些特殊數據,不一定在任何時候都用的上,一般在磁盤上,只有當需要時才載入內存

在世界範圍內發佈的軟件,UI就有各種不同版本,以適應不同國家民族不同語言圖標光標和風格需要

一個完善的程序要有多套UI數據。

資源就是一種可供window應用程序利用,可單獨編輯,並可動態加載的數據

資源頭文件Resourc.h + 資源描述文件(資源腳本文件)

 

資源標識符的前綴

IDR_       主菜單,工具欄,加速鍵表和應用程序圖標

IDD_       對話框

IDC_       控件和光標

IDS_       字符串

IDP_       消息框提示符

ID_        菜單命令

 

發佈Release版本

在VC6.0下,工具,定製,顯示出編譯工具欄,然後選擇release   重新編譯就行

 

CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(), 等等) // 加載所有標識符爲它的各種資源

 

res文件

對於位圖,圖標,鼠標光標等這類圖形數據,放在res文件夾中,在資源描述文件中只說明它們的名字和存儲路徑

在編譯時,把上兩個文件,生成一個二進制res文件。

res文件夾+ .lib庫文件 + .obj文件 --- > 生成exe文件

 

8-1增加

MENUITEM"打印字符(&p)\tCtrl+P",         ID_FILE_PRT 菜單項

 

8-2增加IDR_MAINFRAME1 MENU PRELOADDISCARDABLE //資源名稱

在應用程序類的InitInstance中

    pDocTemplate = new CSingleDocTemplate(

       IDR_MAINFRAME1,

       RUNTIME_CLASS(CMFCexp8_2Doc),

       RUNTIME_CLASS(CMainFrame),       // main SDI frame window

       RUNTIME_CLASS(CMFCexp8_2View));

加快捷鍵 8-2

IDR_MAINFRAME1ACCELERATORS PRELOAD MOVEABLE PURE

BEGIN

    "Z",            ID_ZOOM,            VIRTKEY,CONTROL

    "R",            ID_RE,           VIRTKEY,CONTROL

END

菜單MENU的選項

IDR_MAINFRAME1MENU PRELOAD DISCARDABLE

 

DISCARDABLE   應用程序不需要時可以丟棄

FIXEDP        保存在內存的固定位置

LOADONCALL    只有在應用程序需要時才加載菜單

MOVEABLE      在內存中可以移動位置

PRELOAD       主即加載

 

POPUP的選項

POPUP"選項(&F)" GRAYED

MENUBARBREAK  菜單縱向分隔標誌

CHECKED       菜單項帶有選中標誌

INACTIVE      無效

GRAYED        無效且灰

 

8-3菜單命令選項的動態修改

選項前加了對號 

ON_COMMAND(ID_XUANX1,OnXuanx1)

    ON_UPDATE_COMMAND_UI(ID_XUANX1,OnUpdateXuanx1)

函數的聲明就不說了,實現如下:

voidCMFCexp8_3View::OnXuanx1()

{

    m_nOption1=!m_nOption1;

}

voidCMFCexp8_3View::OnUpdateXuanx1(CCmdUI *pCmdUI)

{

    pCmdUI->SetCheck(m_nOption1);

}

其中OnUpdateXuanx1(CCmdUI *pCmdUI)是菜單選項的UI函數,參數是CCmdUI類的對象指針,這個類的四個方法如下:

Enable    

SetCheck// 標記

SetRadio   //參數爲TURE,出現選中標記,其他不出現

SetText 設置文本

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章