MFC 簡明教程

Visual C++ MFC 簡明教程
原著:Marshall Brain 編譯:張聖華

第一部分:MFC導論

Visual C++ 不僅僅是一個編譯器。它是一個全面的應用程式開發環境,使用它你充分利用具有面向對象特性的 C++ 來開發出專業級的 Windows 應用程式。為了能充分利用這些特性,你必須理解 C++ 程式設計語言。掌握了C++,你就必須掌握 Microsoft 基本類庫 (MFC) 的層次結構。該層次 結構包容了 Windows API 中的用戶界面部分,並使你能夠很容易地以面向對象的方式建立 Windows 應用程式。這種層次結構適用於所有版本的 Windows 並彼此兼容。你用 MFC 所建立的代碼是完全可移植的。
該教程將向你介紹MFC的基本概念和術語以及事件驅動程式設計方法。在本節中,你將會輸入、編譯和運行一個簡單的MFC程式。下一節中將向你詳細解釋這些代碼。第三部分討論了MFC控制和如何定製它們。第四部分將介紹訊息映射,你將會處理MFC的事件。

什麼是MFC?
如果你要建立一個 Windows 應用程式,應該如何下手?
好的開端是從設計用戶界面開始。首先,你要決定什麼樣的用戶能使用該程式並根據需要來設定相應的用戶界面對象。Windows 用戶界面有一些標準的控制,如按鈕、選單、滾動條和列表等,這對那些 Windows 用戶已經是很熟悉了。 要記住的是,作為程式設計師必須選擇一組控制並決定如何把它們安排到螢幕上。傳統上,你需要在紙上做一下用戶界面的草圖,直到對各元素感到滿意為止。這對於一些比較小的項目,以及一些大項目的早期原型階段是可以的。
下一步,是要實現代碼。為任何 Windows 平台建立應用程式時,程式設計師都有兩種選擇:C 或 C++。 使用 C,程式設計師是在 Windows 應用程式界面 ( API ) 的水平上編寫代碼。該界面是由幾百個 C 函式所組成,這些函式在Windows API 參考手冊中都有介紹。對於Windows NT, API 被稱為 “Win32 API”,以區別於其用於Windows 3.1的16位 API。
Microsoft 也提供了 C++ 庫,它位於任何 Windows API 之上,能夠使程式設計師的工作更容易。它就是Microsoft基本類庫 (MFC),該庫的主要優點是效率高。它減少了大量在建立 Windows 程式時必須編寫的代碼。同時它還提供了所有一般 C++ 編程的優點,例如繼承和封裝。MFC 是可移植的,例如,在 Windows 3.1下編寫的代碼可以很容易地移植到 Windows NT 或 Windows 95 上。因此,MFC 很值得推薦的開發Windows 應用程式的方法,在本教程自始至終使用的都是 MFC。
當是使用 MFC 時,你編寫的代碼是用來建立必要的用戶界面控制並定製其外觀。同時你還要編寫用來回響用戶操作這些控制的代碼。例如,如果用戶單擊一個按鈕時,你應該有代碼來回響。這就是事件驅動代碼,它構成了所有應用程式。一旦應用程式正確的回響了所有允許的控制,它的任務也就完成了。
你可以看出,使用 MFC 進行 Windows 編程時是一件比較容易的的過程。本教程的目的是比較詳細地教你如何快速建立專業級的應用程式的技術。Visual C++ 應用程式開發程式環境特別適合於使用 MFC (也有其它開發環境使用MFC,譯者注),所以一起學習 MFC 和 Visual C++ 能夠增強你的開發程式的能力。

Windows辭彙
在 Windows 用戶界面和軟體開發中所要用到的辭彙都是基本和唯一的。對於新接觸該環境的用戶,下面複習幾個定義以便使我們的討論更加容易。
Windows應用程式使用幾個標準的控制:
靜態文本標籤
按鈕
列表框
組合框(一種更高級的列表框)
單選按鈕
檢查按鈕
編輯框(單行和多行)
滾動條

你可以通過代碼或“資源編輯器”來建立這些控制,在資源編輯器中可以建立對話框和這些控制。在本教程中,我們將使用代碼來建立它們。
Windows支持幾種類型的應用程式視窗。一個典型的應用程式應該活動在稱為“框架視窗”中。一個框架視窗是一個全功能的主視窗,用戶可以改變尺寸、最小化、最大化等。Windows也支持兩種類型的對話框:模式和無模式對話框模式對話框一旦出現在螢幕上,只有當它退出時,螢幕上該應用程式的其餘部分才能回響。無模式對話框出現在螢幕上時,程式的其餘部分也可以作出回響,它就象浮動在上面一樣。
最簡單的 Windows 應用程式是使用單文檔界面(SDI),只有一個框架視窗。Windows 的鐘表、PIF 編輯器、記事本等都是 SDI 應用程式的例子。Windows 也提供了一種稱為多文檔界面的組織形式,它可用於更複雜的應用程式。MDI 系統允許用戶在同一應用程式中同時可以查看多個文檔。例如,一個文本編輯器可以允許用戶同時打開多個文本檔案。使用 MDI 時,應用程式有一個主視窗,在主視窗中有一些子視窗,每個子視窗中各自包含有各自的文檔。在MDI框架中,主視窗有一個主選單,它對主框架中最頂端視窗有效。各子視窗都可以縮成圖示或展開,MDI主視窗也可以變成桌面上的一個圖示。MDI界面可能會給你一種第二桌面的感覺,它對視窗的管理和刪除混亂的視窗有很大的幫助。
你所建立的沒一個應用程式都會使用它自己的一套控制、選單結構以及對話框。應用程式界面的好壞取決於你如何選擇和組織這些界面對象。Visual C++ 中的資源編輯器可以使你能容易的建立和定製這些界面對象。
事件驅動軟體和辭彙
所有基於視窗的 GUI 都包含相同的基本元素,它們的操作方式都是相同的。在螢幕上,用戶所看到的是一組視窗,每個視窗都包含有控制、圖示、對象以及一些處理滑鼠和鍵盤的元素。從用戶角度來看,各系統的界面對象都是相同的:按鈕、滾動條、圖示、對話框以及下拉選單等等。儘管這些界面元素的“外觀和感覺”可能有些不同,但這些界面對象的工作方式都是相同的。例如,滾動條對於Windows、Mac和Motif可能有些不同,但他們的作用完全是一樣的。
從程式設計師的角度來看,這些系統在概念上是相似的,儘管它們可能有很大的不同。為了建立 GUI 程式,程式設計師第一步要把所有需要的用戶界面控制都放到視窗上。例如,如果程式設計師要建立一個從攝氏到華氏的轉換的簡單程式,則程式設計師所選擇的用戶界面對象來完成並在螢幕上把結果顯示出來。在這個簡單的程式中,程式設計師可能需要用戶在一個可編輯的編輯框中輸入溫度值,在一個不可編輯的編輯框中顯示轉換結果,然後讓用戶可以單擊一個標有“退出”的按鈕來退出應用程式。
因為是用戶來操作應用程式的控制,所以程式必須作出回響。所做的回響依賴於用戶使用滑鼠或鍵盤在不同控制上的操作。螢幕上的每個用戶界面對象對事件的回響是不同的。例如,如果用戶單擊退出按鈕,則該按鈕必須更新螢幕、加亮它自己。然後程式必須回響退出。
Windows 所用的模式也是類似的。在一個典型的應用程式中,你將建立一個主視窗,並且在其中放置了一些用戶界面控制。這些控制通常被稱為子視窗——它們就象一些在主視窗中的更小更特殊的子視窗。作為程式設計師,你應該通過函式調用來傳送信息操作這些控制、通過把信息傳送給你到代碼來回響用戶的操作。
如果你從未做過事件驅動程式設計,則所有這些對你來說可能是很陌生的。但是,事件驅動程式設計方式是很容易理解的。具體的細節對不同的系統可能有些不同,但是其基本概念是類似的。在一個事件驅動界面中,應用程式會在螢幕上繪製幾個界面對象,如按鈕、文本區和選單。應用程式通常通過一段稱為事件循環的的代碼來回響用戶的操作。用戶可以使用滑鼠或鍵盤來任意操作螢幕上的對象。例如,用戶用滑鼠單擊一個按鈕。用滑鼠單擊就稱為一個事件。事件驅動系統把用戶的動作如滑鼠單擊和鍵盤操作定義為事件,也把系統操作如更新螢幕定義為事件。
在比較低級的編程方法中,如用C直接編寫Windows API應用程式,代碼量是非常大的,因為你所要照顧的細節太多了。例如,你用某種類型的結構來接收單擊滑鼠事件。你的事件循環中的代碼會查看結構中不同域,以確定哪個用戶界面對象受到了影響,然後會完成相應的操作。當螢幕上有很多對象時,應用程式會變得很大。只是簡單地處理哪個對象被單擊和對它需要做些什麼要花費大量的代碼。
幸運的是,你可以在比較高級的方法來進行編程,這就是使用MFC。在MFC中,幾乎所有的低級的細節處理都為你代辦了。如果你把某一用戶界面對象放在螢幕上,你只需要兩行代碼來建立它。如果用戶單擊一個按鈕,則按鈕自己會完成一切必要的操作,從更新螢幕上的外觀到調用你程式中的預處理函式。該函式包含有對該按鈕作出相應操作的代碼。MFC 為你處理所有的細節:你建立按鈕並告知它特定的處理函式,則當它被按下時,它就會調用相應的函式。第四部分介紹了怎樣使用訊息映射來處理事件。

例子
理解一個典型的 MFC 程式的結構和樣式的最好方法是輸入一段小程式,然後編譯和運行它。下面的程式是一段簡單的“hello world”程式。這對很多C程式設計師都是很熟悉了,讓我們看一下如何用MFC方法來實現。如果你是第一次看到這類程式,也許比較難理解。這沒關係,我們後面會詳細介紹。現在你只要用Visual C++ 環境中建立、編譯和運行它就可以了。
//hello.cpp

#include

// 說明應用程式類
class CHelloApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};

// 建立應用程式類的實例
CHelloApp HelloApp;

// 說明主視窗類
class CHelloWindow : public CFrameWnd
{
CStatic* cs;
public:
CHelloWindow();
};

// 每當應用程式首次執行時都要調用的初始化函式
BOOL CHelloApp::InitInstance()
{
m_pMainWnd = new CHelloWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}

// 視窗類的構造函式
CHelloWindow::CHelloWindow()
{
// 建立視窗本身
Create(NULL,
"Hello World!",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));

// 建立靜態標籤
cs = new CStatic();
cs->Create("hello world",
WS_CHILD|WS_VISIBLE|SS_CENTER,
CRect(50,80,150,150),
this);
}

上面的這段程式如果用C來實現,得需要幾頁的代碼。這個簡單的例子做了三件事。第一,它建立了一個應用程式對象。你所編寫的每個 MFC 程式都有一個單一的程式對象,它是處理 MFC 和 Windows 的初始細節的。第二,應用程式建立了一個視窗來作為應用程式的主視窗。最後,在應用程式的視窗中建立了一個靜態文本標籤,它包含有“hello world”幾個字。在第二部分中我們會仔細研究這段程式,以理解其結構。
啟動 VC++,如果你是剛剛安裝好,則你會在螢幕上看到一個帶有工具列的空視窗。如果 VC++ 已經在該機器上使用過了,則所顯示的視窗可能有些不同,因為 VC++ 會記憶和自動重新打開上次使用後退出時的項目和檔案。我們需要的是它沒有裝如任何項目和代碼。如果程式啟動後彈出對話框指示不能打開某些檔案,你只要單擊“No”即可。在“Window”選單中選取“Close All”選項關閉所有視窗。在“File”選單中選取“Close”選項來關閉其它視窗。現在,你就處於開始狀態了。如果你安裝VC++後,第一次運行,則螢幕應如下所示:

如果你以後不希望看到“InfoViewer Topic”視窗,你可以用按鈕把它關掉。如果以後需要的話,你還可以單擊工具列上的“主頁”按鈕來打開該視窗。
現在一切都正常了。正如你所看到的,頂部是選單和幾個工具列。左邊的視窗所顯示的是線上幫助內容,你可以雙擊某項標題來瀏覽其內容。線上幫助的內容是十分豐富的。
現在該做什麼了?你所要做的是輸入上面的程式,然後便宜並運行它。開始之前,要檢查以下你的硬碟上至少要留有5MB的剩餘空間。

建立項目和編譯代碼
為了在 Visual C++ 中編譯代碼,你必須要建立一個項目。為了這么小的程式來建立一個項目可能有點小題大作,但是,在任何實際的程式中,項目的概念是非常有用的。一個項目主要保存著下面三種不同類型的信息:
1.它可以記住建立一個可執行程式所需要的所有源程式代碼檔案。在這個簡單的例子中,檔案 HELLO.CPP 是唯一的源檔案,但是在一個大型的應用程式中,為了便於管理和維護,你可以會有許多個不同的源檔案。項目會維護這些不同檔案的列表,並當你要建立下一個新的可執行程式時,在必要時編譯它們。
2.它會記住針對你的應用程式所使用的編譯器和連線器選項。例如,它會記住把哪個庫連線到了執行程式中,你是否預編譯了頭檔案等等。
3.它會記住你想要建立的項目類型: 一個控制台應用程式,或一個視窗應用程式等等。
如果你已經對項目檔案有所了解,則會很容易明白作為機器產生的項目檔案的作用。現在,我們來建立一個簡單的項目,並用它來編譯 HELLO.CPP。
為此,首先從“File”選單中選擇“New”選項。在“Projects”標籤中,加單擊“Win32 Application”。在“Location”域中輸入一個合適的路徑名或單擊“Browse”按鈕來選擇一個。在“Project name”中輸入“hello”作為項目名稱。這時候你會看到“hello”也會出現在“Location”域中。單擊“OK”按鈕。Visual C++ 會建立一個新的稱為HELLO的目錄,並把所有的項目檔案 HELLO.OPT、HELLO.NCB、HELLO.DSP 和 HELLO.DSW 都放到該目錄中。如果你退出,以後再重新打開該項目,則可選擇 HELLO.DSW。
現在,在螢幕的左邊,出現了三個標籤。InfoView 標籤仍然在,又新出現了 ClassView 和 FileView 標籤。ClassView 標籤會把你程式中所有的類都列出來,FileView 標籤給出了項目中檔案的列表。
現在可以輸入程式的代碼了。在“File”選單中選擇“New”選項來建立一個編輯視窗。在出現的對話框中,選擇“Files”標籤和“Text File”。則會出現 Visual C++ 的智慧型編輯器,你可以用它來輸入上面的程式代碼。輸入代碼時,你會發現編輯器會自動把不同類型的文本變成不同的顏色,如注釋、關鍵字字元串等的顏色都不同。如果你要改變其顏色或關閉顏色功能,可選擇“Tools”選單中“Options”選項,然後選擇“Format”標籤和“Source Windows”選項就可以修改。
輸入完代碼後,選擇“File”選單中的“Save”選項來保存。在 Visual C++ 新建立的目錄中,把它存成 HELLO.CPP 檔案。
現在選擇在“Project”選單中選擇“Add To Project”選項,再選“Files...”。你會看到一個對話框供你選擇要添加的檔案。在本例子中,選擇 HELLO.CPP 檔案。
在螢幕的左邊,單擊 FileView 標籤,並雙擊標有 HELLO 的圖示。你會看到名為 HELLO.CPP 的檔案。單擊 ClassView 標籤,並雙擊資料夾圖示,你會看到程式中所有的類。任何時候你都可以使用 FileView 來刪除項目的檔案,你只要單擊該檔案,然後按鍵盤上的 delete 鍵。
後,此時你必須告訴項目要使用MFC庫。如果你忽略了這一步,則項目在連線時會出錯,而出錯信息對你毫無幫助。選擇“Project”選單的“Settings”。在出現的對話框中選擇“General”標籤。在“Microsoft Foundation Classes”組合框中,選擇“Use MFC in a Shared DLL”。然後關閉對話框。
我們已經建立了項目檔案,並調整了設定,你現在可以準備編譯 HELLO.CPP 程式了。在“Build”選單中,你會發現有三個不同的編譯選項:
1.Compile HELLO.CPP (只有當含有 HELLO.CPP 的視窗處於激活狀態時才可)
2.Build hello.exe
3.rebuild All
第一個選項只是編譯源檔案並形成它們的目標檔案。該選項不能完成連線任務,所以它只對快速編譯一些源檔案以檢查錯誤有用。第二個選項編譯自上次編譯後所修改的所有源檔案,並連線形成執行檔。第三個選項要重新編譯和連線所有的源檔案。
我們可以選擇“Build HELLO.EXE”來編譯和連線代碼。Visual C++ 會建立一個名為“Debug”的新子目錄,並把 HELLO.EXE 放在該目錄中。該子目錄的檔案都是可以再產生的,所以你可以任意刪除它們。
如果你發現了編譯錯誤,雙擊輸出視窗中的錯誤信息。這時編輯器會把你帶到出錯的位置處。檢查你的代碼是否有問題,如果有,就修改之。如果你看到大量的連線錯誤,則可能你在建立項目對話框中所指定的項目類型不對。你可以把該項目所在的子目錄刪除,然後再重新按上面的步驟來建立。
為了執行該程式,你可選則“Build”選單中的“Execute HELLO.EXE”選項。你就可以看到你的第一個MFC程式了 -- 出現一個帶有“hello world”的視窗。該視窗本身帶有:標題欄、尺寸縮放區、最大和最小按鈕等等。在視窗上,有一個標有“hello world”。請注意,該程式是完整的。你可以移動視窗、縮放視窗、最小化等。你只使用了很少的代碼就完成了一個完整的 Window 應用程式。這就是使用 MFC 的優點。所有的細節問題都有MFC來處理。

結論
在本講中,你已經成功地編譯和執行了你的第一個 MFC 程式。你將來會用類似的步驟來建立的應用程式。你可以為每個項目建立單獨的目錄,或建立一個單獨的項目檔案,然後再添加或刪除不同的源檔案。
在下一講中,我們將仔細研究該程式,你會更完整的理解它的結構。

第二部分:一個簡單的MFC程式
在本將中,我們將一段一段地來研究上一將中提到的 MFC 應用程式,以便能理解它的結構和概念框架。我們將先介紹 MFC,然後在介紹如何用 MFC 來建立應用程式。

MFC簡介
MFC 是一個很大的、擴展了的 C++ 類層次結構,它能使開發 Windows 應用程式變得更加容易。MFC 是在整個 Windows 家族中都是兼容的,也就是說,無論是 Windows3.x、Windows95 還是 Windows NT,所使用的 MFC 是兼容的。每當新的 Windows 版本出現時,MFC 也會得到修改以便使舊的編譯器和代碼能在新的系統中工作。MFC 也回得到擴展,添加新的特性、變得更加容易建立應用程式。
與傳統上使用 C 語言直接訪問 Windows API相反,使用 MFC 和 C++ 的優點是 MFC 已經包含和壓縮了所有標準的“樣板檔案”代碼,這些代碼是所有用 C 編寫的 Windows 程式所必需的。因此用 MFC 編寫的程式要比用C語言編寫的程式小得多。另外,MFC 所編寫的程式的性能也毫無損失。必要時,你也可以直接調用標準 C 函式,因為 MFC 不修改也不隱藏 Windows 程式的基本結構。
使用 MFC 的最大優點是它為你做了所有最難做的事。MFC 中包含了上成千上萬行正確、最佳化和功能強大的 Windows 代碼。你所調用的很多成員函式完成了你自己可能很難完成的工作。從這點上將,MFC 極大地加快了你的程式開發速度。
MFC 是很龐大的。例如,版本4.0中包含了大約200個不同的類。萬幸的是,你在典型的程式中不需要使用所有的函式。事實上,你可能只需要使用其中的十多個 MFC 中的不同類就可以建立一個非常漂亮的程式。該層次結構大約可分為幾種不同的類型的類:
應用程式框架
圖形繪製的繪製對象
檔案服務
異常處理
結構 - List、Array 和 Map
Internet 服務
OLE 2
資料庫
通用類
在本教程中,我們將集中討論可視對象。下面的列表給出了部分類:
CObject
CCmdTarget
CWinThread
CWinApp
CWnd
CFrameWnd
CDialog
CView
CStatic
CButton
CListBox
CComboBox
CEdit
CScrollBar

在上面的列表中,有幾點需要注意。第一,MFC 中的大部分類都是從基類 CObject 中繼承下來的。該類包含有大部分MFC類所通用的數據成員和成員函式。第二,是該列表的簡單性。CWinApp 類是在你建立應用程式是要用到的,並且任何程式中都只用一次。CWnd 類匯集了 Windows 中的所有通用特性、對話框和控制。CFrameWnd 類是從 CWnd 繼承來的,並實現了標準的框架應用程式。CDialog 可分別處理無模式和有模式兩種類型的對話框。CView 是用於讓用戶通過視窗來訪問文檔。最後,Windows 支持六種控制類型: 靜態文本框、可編輯文本框、按鈕、滾動條、列表框和組合框(一種擴展的列表框)。一旦你理解了這些,你也就能更好的理解 MFC 了。MFC 中的其它類實現了其它特性,如記憶體管理、文檔控制等。
為了建立一個MFC應用程式,你既要會直接使用這些類,而通常你需要從這些類中繼承新的類。在繼承的類中,你可以建立新的成員函式,這能更適用你自己的需要。你在第一講中的簡單例子中已經看到了這種繼承過程,下面會詳細介紹。CHelloApp 和 CHelloWindow 都是從已有的 MFC 類中繼承的。

設計一個程式
在討論代碼本身之前,我們需要花些工夫來簡單介紹以下 MFC 中程式設計的過程。例如,假如你要編一個程式來向用戶顯示“Hello World”信息。這當然是很簡單的,但仍需要一些考慮。
“hello world”應用程式首先需要在螢幕上建立一個視窗來顯示“hello world”。然後需要實際把“hello world”放到視窗上。我們需要但個對象來完成這項任務:
1.一個應用程式對象,用來初始化應用程式並把它掛到 Windows 上。該應用程式對象處理所有的低級事件。
2.一個視窗對象來作為主視窗。
3.一個靜態文本對象,用來顯示“hello world”。
你用 MFC 所建立的每個程式都會包含頭兩個對象。第三個對象是針對該應用程式的。每個應用程式都會定義它自己的一組用戶界面對象,以顯示應用程式的輸出和收集套用的輸入信息。
一旦你完成了界面的設計,並決定實現該界面所需要的控制,你就需要編寫代碼來在螢幕上建立這些控制。你還會編寫代碼來處理用戶操作這些控制所產生的信息。在“hello world”應用程式中,只有一個控制。它用來輸出“hello world”。複雜的程式可能在其主視窗和對話框中需要上百個控制。
應該注意,在應用程式中有兩種不同的方法來建立用戶控制。這裡所介紹的是用 C++ 代碼方式來建立控制。但是,在比較大的應用程式中,這種方法是不可行的。因此,在通常情況下要使用資源檔案的圖形編輯器來建立控制。這種方法要方便得多。

理解“hello world”的代碼
下面列出了你在上一講中已經輸入、編譯和運行的“hello world”程式的代碼。添加行號是為了討論方便。我們來一行行地研究它,你會更好的理解 MFC 建立應用程式的方式。
如果你還沒有編譯和運行該代碼,應該按上一講的方法去做。
1 //hello.cpp
2 #include
3 // Declare the application class
4 class CHelloApp : public CWinApp
5 {
6 public:
7 virtual BOOL InitInstance();
8 };
9 // Create an instance of the application class
10 CHelloApp HelloApp;

11 // Declare the main window class
12 class CHelloWindow : public CFrameWnd
13 {
14 CStatic* cs;
15 public:
16 CHelloWindow();
17 };
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
35 // Create a static label
36 cs = new CStatic();
37 cs->Create("hello world",
38 WS_CHILD|WS_VISIBLE|SS_CENTER,
39 CRect(50,80,150,150),
40 this);
41 }
你把上面的代碼看一遍,以得到一整體印象。該程式由六小部分組成,每一部分都起到很重要的作用。
首先,該程式包含了頭檔案 afxwin.h (第 2 行)。該頭檔案包含有 MFC 中所使用的所有的類型、類、函式和變數。它也包含了其它頭檔案,如 Windows API 庫等。
第 3 至 8 行從 MFC 說明的標準應用程式類 CWinApp 繼承出了新的應用程式類 CHelloApp。該新類是為了要重載 CWinApp 中的 InitInstance 成員函式。InitInstance 是一個應用程式開始執行時要調用的可重載函式
在第10行中,說明了應用程式作為全局變數的一個事例。該實例是很重要的,因為它要影響到程式的執行。當應用程式被裝入記憶體並開始執行時,全局變數的建立會執行 CWinApp 類的預設構造函式。該構造函式會自動調用在18至26行定義的 InitInstance 函式。
在第11至17中,CHelloWindow 類是從 MFC 中的 CFrameWnd 類繼承來的。CHelloWindow 是作為應用程式在螢幕上的視窗。建立新的類以便實現構造函式、析構函式和數據成員。
第18至26行實現了 InitInstance 函式。該函式產生一個 CHelloWindow 類的事例,因此會執行第27行至41行中類的構造函式。它也會把新視窗放到螢幕上。
第27至41實現了視窗的構造函式。該構造函式實際是建立了視窗,然後在其中建立一個靜態文本控制。
要注意的是,在該程式中沒有 main 或 WinMain 函式,也沒有事件循環。然而我們從上一講在執行中知道它也處理了事件。視窗可以最大或最小化、移動視窗等等。所有這些操作都隱藏在主應用程式類 CWinApp 中,並且我們不必為它的事件處理而操心,它都是自動執行、在 MFC 中不可見的。
下一節中,將詳細介紹程式的各部分。你可能不能馬上全都理解得很好: 但你最好先讀完它以獲得第一印象。在下一講中,會介紹一些特殊的例子,並偶把各片段組合在一起,有助於你能更好的理解。

程式對象
用 MFC 建立的每個應用程式都要包括一個單一從 CWinApp 類繼承來的應用程式對象。該對象必須被說明成全局的(第10行),並且在你的程式中只能出現一次。
從 CWinApp 類繼承的對象主要是處理應用程式的初始化,同時也處理應用程式主事件循環。CWinApp 類有幾個數據成員和幾個成員函式。在上面的程式中,我們只重載了一個 CWinApp 中的虛擬函式 InitInstance。
應用程式對象的目的是初始化和控制你的程式。因為 Windows 允許同一個應用程式的多個事例在同時執行,因此 MFC 把初始化過程分成兩部分並使用兩個函式 InitApplication 和 InitInstance 來處理它。此處,我們只使用了一個 InitInstance 函式,因為我們的程式很簡單。當每次調用應用程式時都會調用一個新的事例。第3至8行的代碼建立了一個稱為 CHelloApp 的類,它是從 CWinApp 繼承來的。它包含一個新的 InitInstance 函式,是從 CWinApp 中已存在的函式(不做任何事情)重載來的:
3 // Declare the application class
4 class CHelloApp : public CWinApp
5 {
6 public:
7 virtual BOOL InitInstance();
8 };
在重載的 InitInstance 函式內部,第18至26行,程式使用 CHelloApp 的數據成員 m_pMainWnd 來建立並顯示視窗:
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
InitInstance 函式返回 TRUE 表示初始化已成功的完成。如果返回了FALSE,則表明應用程式會立即終止。在下一節中我們將會看到視窗初始化的詳細過程。
當應用程式對象在第10行建立時,它的數據成員(從 CWinApp 繼承來的) 會自動初始化。例如,m_pszAppName、m_lpCmdLine 和 m_nCmdShow 都包含有適當的初始化值。你可參見 MFC 的幫助檔案來獲得更詳細的信息。我們將使用這些變數中的一個。

視窗對象
MFC 定義了兩個類型的視窗: 1) 框架視窗,它是一個全功能的視窗,可以改變大小、最小化、最大化等等; 2) 對話框視窗,它不能改變大小。框架視窗是典型的主應用程式視窗。
在下面的代碼中,從 CFrameWnd 中繼承了一個新的類 CHelloWindow:
11 // Declare the main window class
12 class CHelloWindow : public CFrameWnd
13 {
14 CStatic* cs;
15 public:
16 CHelloWindow();
17 };
它包括一個新的構造函式,同時還有一個指向程式中所使用的唯一用戶界面控制的數據成員。你多建立的每個應用程式在主視窗中都會有唯一的一組控制。因此,繼承類將有一個重載的構造函式以用來建立主視窗所需要的所有控制。典型情況下,該類會包含有一個析構函式以便在視窗關閉時來刪除他們。我們這裡沒有使用析構函式。在第四講中,我們將會看到繼承視窗類也會說明一個訊息處理函式來處理這些控制在回響用戶事件所產生的訊息。
典型地,一個應用程式將有一個主應用程式視窗。因此,CHelloApp 應用程式類定義了一個名為 m_pMainWnd 成員變數來指向主視窗。為了建立該程式的主視窗,InitInstance 函式(第18至26行)建立了一個 CHelloWindow 事例,並使用 m_pMainWnd 來指向一個新的視窗。我們的 CHelloWindow 對象是在第22行建立的:
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
只建立一個簡單的框架視窗是不夠的。還要確保視窗能正確地出現在螢幕上。首先,代碼必須要調用視窗的 ShowWindow 函式以使視窗出現在螢幕上(第23行)。其次,程式必須要調用 UpdateWindow 函式來確保視窗中的每個控制和輸出能正確地出現在螢幕上(第24行)。
你可能奇怪,ShowWindow 和 UpdateWindow 函式是在哪兒定義的。例如,如果你要查看以便了解它們,你可能要查看 MFC 的幫助檔案中的 CFrameWnd 定義部分。但是 CFrameWnd 中並不包含有這些成員函式。CFrameWnd 是從 CWnd 類繼承來的。你可以查看 MFC 文檔中的 CWnd,你會發現它包含有200多個不同的成員函式。顯然,你不能在幾分鐘內掌握這些函式,但是你可以掌握其中的幾個,如 ShowWindow 和UpdateWindow。
現在讓我們花幾分鐘來看一下 MFC 幫助檔案中的 CWnd::ShowWindow 函式。為此,你你可以單擊幫助檔案中的 Search 按鈕,並輸入“ShowWindow”。找到後,你會注意到,ShowWindow 只有一個參數,你可以設定不同的參數值。我們把它設定成我們程式中 CHelloApp 的數據成員變數 m_nCmdShow (第23行)。m_nCmdShow 變數是用來初始化應用程式啟動的視窗顯示方式的。例如,用戶可能在程式管理器中啟動應用程式,並可通過應用程式屬性對話框來告知程式管理器應用程式在啟動時要保持最小化狀態。m_nCmdShow 變數將被設定成 SW_SHOWMINIMIZED,並且應用程式會以圖示的形式來啟動,也就是說,程式啟動後,是一個代表該程式的圖示。m_nCmdShow 變數是一種外界與應用程式通訊的方式。如果你願意,你可以用不同的 m_nCmdShow 值來試驗 ShowWindow 的效果。但要重新編譯程式才能看到效果。
第22行是初始化視窗。它為調用 new 函式分配記憶體。在這一點上,程式在執行時會調用CHelloWindow的構造函式。該構造函式在每次帶類的事例被分配時都要調用。在視窗構造函式的內部,視窗必須建立它自己。它是通過調用 CFrameWnd 的 Create 成員函式來實現的(第31行):
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
建立函式共傳遞了四個參數。通過查看 MFC 文檔,你可以了解不同類型。NULL 參數表示使用預設的類名。第二個參數為出現在視窗標題欄上的標題。第三個參數為視窗的類型屬性。該程式使用了正常的、可覆蓋類型的視窗。在下一講中將詳細介紹類型屬性。第四個參數指出視窗應該放在螢幕上的位置和大小,左上角為(0,0), 初始化大小為 200×200個象素。如果使用了 rectDefault,則 Windows 會為你自動放置視窗及大小。
因為我們的程式太簡單了,所以它只在視窗中建立了一個靜態文本控制。見第35至40行。下面將詳細介紹。

靜態文本控制
程式在從 CFrameWnd 類中繼承 CHelloWindow 類時(第11至17行)時,說明了一個成員類型 CStatic及其構造函式。
正如在前面所見到的,CHelloWindow 構造函式主要做兩件事情。第一是通過調用Create函式(第31行)來建立應用程式的視窗。然後分配和建立屬於視窗的控制。在我們的程式中,只使用了一個控制。在 MFC 中建一個對象總要經過兩步。第一是為類的事例分配記憶體,然後是調用構造函式來初始化變數。下一步,調用 Create 函式來實際建立螢幕上的對象。代碼使用這兩步分配、構造和建立了一個靜態文本對象(第36至40行):
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
35 // Create a static label
36 cs = new CStatic();
37 cs->Create("hello world",
38 WS_CHILD|WS_VISIBLE|SS_CENTER,
39 CRect(50,80,150,150),
40 this);
41 }
CStatic 構造函式是在為其分配記憶體時調用的,然後就調用了 Create 函式來建立 CStatic 控制的視窗。Create 函式所使用的參數與視窗建立函式所使用的參數是類似的(第31行)。第一個參數指定了控制中所要顯示的文本內容。第二個參數指定了類型屬性。類型屬性在下一講中將詳細介紹。在次我們使用的是子視窗類型(既在別的視窗中顯示的視窗),還有它是可見的,還有文本的顯示位置是居中的。第三個參數決定了控制的大小和位置。第四參數表示該子視窗的父視窗。已經建立了一個靜態控制,它將出現在應用程式視窗上,並顯示指定的文本。

結論
第一次瀏覽該代碼,也可能不是很熟悉和有些讓人煩惱。但是不要著急。從程式設計師的觀點來看,整個程式的主要工作就是建立了 CStatic 控制(36至40行)。在下一講中,我們詳細向你介紹36至40行代碼的含義,並可看到定製 CStatic 控制的幾個選項。

第三部分:MFC樣式

控制是用來建立Windows應用程式用戶界面的用戶界面對象。你所見到的大部分Windows應用程式和對話框只不過是由一些控制所組成的、用來實現程式功能的東西。為了建立有效的應用程式,你必須完全理解在Windows應用程式中應該如何合理的使用控制。有六個基本的控制:CStatic、CButton、CEdit、CList、CComboBox和CScrollBar。另外,Windows 95又增加了15增強了的控制。你需要理解的是那個控制能做些什麼、你應該如何控制它的外表和行為以及如何讓控制能回響用戶事件。只要掌握了這些,再加上掌握了選單和對話框,你就可以建立你所想像的任何Windows應用程式。你可以象本教程這樣用程式代碼來建立控制,也可以使用資源編輯器通過資源檔案來建立。當然,對話框編輯器更方便些,它對於已經基本掌握了控制的情況下特別有用。
最簡單的控制是CStatic, 它是用來顯示靜態文本的。CStatic類沒有任何數據成員,它只有少量的成員函式:構造函式、Create函式(用於獲取和設定靜態控制上的圖示)等等。它不回響用戶事件。因為它的簡單性,所以最好把它作為學習Windows控制的開端。
在本講中,我們從CStatic著手,看一下如何修改和定製控制。在下一講中,我們將學習CButton和CScrollBar類,以理解事件處理的概念。一旦你理解和掌握了所有控制極其類,你就可以建立完整的應用程式了。

基 礎
MFC中的CStatic類是用來顯示靜態文本信息的。這些信息能夠可以作為純信息(例如,顯示在信息對話框中的錯誤訊息), 或作為小的標籤等。在Windows應用程式的檔案打開對話框中,你會發現有六個這樣的標籤。
CStatic控制還有幾種其它的顯示格式。你可以通過修改標籤的樣式來使它表現為矩形、框線或圖示等。
CStatic控制總是作為子視窗的形式出現的。典型情況下,其父視窗是應用程式的主視窗或對話框。正如上一講所介紹的,你用兩行代碼就可以建立一個靜態控制:
CStatic *cs;
...
cs = new CStatic();
cs->Create("hello world",
WS_CHILD|WS_VISIBLE|SS_CENTER,
CRect(50,80, 150, 150),
this);
這兩行代碼是典型的MFC建立所有控制的代碼。調用new來為CStatic類的事例分配記憶體,然後調用類的構造函式。構造函式是用來完成類所需要的初始化功能的。Create函式建立控制並把它放到螢幕上。
Create函式有五個參數:
lpszText: 指定了要顯示的文本。
rect: 控制文本區域的位置、大小和形狀。
pParentWnd: 指明CStatic控制的父視窗。該控制會出現在其父視窗中,且其位置是相對於其父視窗的用戶區域而言的。
nID: 整數值,表示該控制的標識符。
dwStyle: 最重要的參數。它控制著控制的外觀和行為。

CStatic樣式
所有的控制都有各種顯示樣式。樣式是在用Create函式建立控制時傳遞給它的dwStyle參數所決定的。對CStatic有效的樣式簡介如下:
從CWnd繼承來的樣式:
WS_CHILD CStatic所必須的。
WS_VISIBLE 表示該控制對用戶應該是可見的。
WS_DISABLED 表示該控制拒絕接受用戶事件。
WS_BORDER 控制的文本區域帶有框線。
CStatic固有的樣式:
SS_BLACKFRAME 該控制區域以矩形邊界顯示。顏色與視窗框架相同。
SS_BLACKRECT ? 該控制以填充的矩形顯示。顏色與當前的視窗框架相同。
SS_CENTER 文本居中。
SS_GRAYFRAME 控制以矩形框線方式顯示。顏色與當前桌面相同。
SS_GRAYRECT 該控制以填充的矩形顯示。顏色與當前的桌面相同。
SS_ICON 控制以圖示形式顯示。文本作為圖示在資源檔案的名稱。rect參數只控制位置。
SS_LEFT 文本居左顯示。文字可迴繞。
SS_LEFTNOWORDWRAP 文本居左顯示。多餘的文字被剪裁。
SS_NOPREFIX 表示字元串中的"&"字元不表示為加速前綴。
SS_RIGHT 文本居右顯示。文字可迴繞。
SS_SIMPLE 只簡單的顯示一行文本。任何CTLCOLOR信息都被其父視窗忽略。
SS_USERITEM 用戶定義項。
SS_WHITEFRAME 控制以矩形框線方式顯示。顏色與當前視窗背景顏色相同。
SS_WHITERECT 控制以填充矩形方式顯示。顏色與當前視窗背景顏色相同。
這些常數中,“SS”(Static Style)開頭的表示只能用於CStatic控制。以“WS”(Window Style)開頭的常數表示可適用於所有視窗,它們定義在CWnd對象中。CWnd中還有很多以“WS”樣式常數。你可以在MFC文檔中的CWnd::Create函式中找到它們。上面的四種是只用於CStatic對象的。
CStatic對象至少要帶有兩個樣式:WS_CHILD和WS_VISIBLE。該控制必須作為另一視窗的子視窗來建立。如果不使用WS_VISIBLE,則所建立的控制是看不見的。WS_DISABLED控制著標籤對事件的回響,因為CStatic不接收鍵盤或滑鼠事件,所以使用該項是多餘的。
所有的其它樣式選項都是可選的,它們控制著標籤的外觀。在CStatic::Create函式中使用這些控制,可以控制CStatic在螢幕上的顯示。

CStatic文本的外觀
下面的代碼對於理解CStatic是有幫助的。它與上一講中介紹的代碼類似,但是修改了CStatic的建立部分。
//static1.cpp
#include

// Declare the application class
class CTestApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};

// Create an instance of the application class
CTestApp TestApp;

// Declare the main window class
class CTestWindow : public CFrameWnd
{
CStatic* cs;
public:
CTestWindow();
};

// The InitInstance function is called
// once when the application first executes
BOOL CTestApp::InitInstance()
{
m_pMainWnd = new CTestWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}

// The constructor for the window class
CTestWindow::CTestWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CStatic Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
r.InflateRect(-20,-20);
// Create a static label
cs = new CStatic();
cs->Create("hello world",
WS_CHILD|WS_VISIBLE|WS_BORDER|SS_CENTER,
r,
this);
}
下面是視窗構造函式加上了行編號:
CTestWindow::CTestWindow()
{
CRect r;
// Create the window itself
1Create(NULL,
"CStatic Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
2GetClientRect(&r);
3r.InflateRect(-20,-20);
// Create a static label
4cs = new CStatic();
5cs->Create("hello world",
WS_CHILD|WS_VISIBLE|WS_BORDER|SS_CENTER,
r,
this);
}
首先在單擊1行中調用CTestWindow::Create函式。它是CFrameWnd對象的Create函式,因為CTestWindow從CFrameWnd繼承了其行為。所以第一行中的代碼指定了視窗大小應該為200×200個象素,視窗的左上角被初始化在螢幕的0,0位置處。常數rectDefault可用CRect參數來替代。
在第2行,調用了CTestWindow::GetClientRect,向它傳遞了&r參數。GetClientRect函式是從CWnd類繼承來的。變數r是CRect類型的,並且在函式的開頭部分被說明為局部變數。
理解這段代碼時可能會有兩個問題 1) GetClientRect函式是乾什麼的? 2) CRect變數是乾什麼的? 讓我們先回答第一個問題。當你查看MFC文檔中的CWnd::GetClientRect函式時,你會發現它返回一CRect類型,它包含了指定視窗的用戶區域矩形。它保存的是參數的地址&r。該地址指向CRect的位置。CRect類型是在MFC中定義的。用它處理矩形是非常方便的。如果你看以下MFC文檔,就會看到其中定義了30多種處理矩形的成員函式和操作符。
在我們的情況下,我們要在視窗中間顯示“Hello World”。因此,我們用GetClientRect來獲取用戶區域的矩形坐標。在第3行中調用了CRect::InflateRect,同時還可以增大或減少了矩形的尺寸(參見CRect::DeflateRect)。這裡我們對矩形的各邊減少了20個象素。如果不這樣的話,標籤周圍邊界就會超出視窗框架。
實際上,CStatic是在第4和5行建立的。樣式屬性為居中並有框線。其大小和位置由CRect參數r確定的。
通過修改不同的樣式屬性,你可以理解CStatic的不同形式。例如,下面的代碼包含有對CTestWindow構造函式進行了修改,所產生的控制有個位移:
CTestWindow::CTestWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CStatic Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
r.InflateRect(-20,-20);
// Create a static label
cs = new CStatic();
cs->Create("Now is the time for all good men to \
come to the aid of their country",
WS_CHILD|WS_VISIBLE|WS_BORDER|SS_CENTER,
r,
this);
}
上面的代碼除了所顯示的文本比較長外沒有什麼不同。運行該代碼你就可以看到,CStatic在指定的區域內的文本已經迴繞了,且沒一行都是居中的。
如果框線矩形太小不能包含所有的文本行,則文本會被剪下以適應之。你減小矩形大小或增大字元串長度就可以看到CStatic的該特性。
在我們所看到的所有代碼中,樣式SS_CENTER是用來居中文本的。CStatic也允許左對齊或右對齊。左對齊是用SS_LEFT來替代SS_CENTER屬性。同樣,右對齊是用SS_RIGHT來取代之。
SS_LEFTNOWORDWRAP屬性是用來關閉文本迴繞的。它會強迫使用左對齊屬性。

CStatic的矩形顯示模式
CStatic也支持兩種不同的矩形顯示模式:填充矩形和框架。通常用這兩種模式來把一組控制框在一起。例如,你可以把黑背景框架視窗作為一組編輯框的背景。你可以選擇六種不同的樣式: SS_BLACKFRAME、SS_BLACKRECT、SS_GRAYFRAME、SS_GRAYRECT、SS_WHITEFRAME和SS_WHITERECT。RECT形成了一個填充的矩形,而FRAME組成一框線。其中的顏色標誌,如SS_WHITERECT表示其顏色與視窗背景的顏色是相同的。儘管該顏色的預設值是白色,但你可以使用控制臺來改變,此時矩形的顏色可能就不是白色的了。
當指定了矩形或框架屬性後,CStatic的文本字元串會被忽略。典型情況是傳遞一空字元串。你可以試驗以下這些特性。

字型
你可以使用CFont類來改變CStatic的字型。MFC中的CFont類保存著特殊Windows字型的單一實例。例如,一個實例的CFont類可能保存有18點的Times字型,而另一個可能保存著10點的Courier字型。你可以調用SetFont函式來修改字型。下面的代碼給出了如何實現字型。
CTestWindow::CTestWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CStatic Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
r.InflateRect(-20,-20);
// Create a static label
cs = new CStatic();
cs->Create("Hello World",
WS_CHILD|WS_VISIBLE|WS_BORDER|SS_CENTER,
r,
this);
// Create a new 36 point Arial font
font = new CFont;
font->CreateFont(36,0,0,0,700,0,0,0,
ANSI_CHARSET,OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH|FF_DONTCARE,
"arial");
// Cause the label to use the new font
cs->SetFont(font);
}
上面的代碼開始於建立視窗和CStatic。然後建立一CFont類型對象。字型變數應作為CTestWindow的數據成員來說明“CFont *font”。CFont::CreateFont函式有15個參數,但是只有三個是最常用的。例如,36指定了以點為單位的字型大小,700指定了字型的密度(400是正常“normal”, 700為加黑“bold”,值的範圍為1到1000。FW_NORMAL和FW_BOLD的含義實際上是相同的),“arial”是所用字型的名稱。Windows 通常帶有五種True Type字型(Arial、Courier New、Symbol、Times New Roman和Wingdings),使用它們,你可以確保任何機器上都會有該字型。如果你使用了系統不知道的字型,則CFont會選擇預設字型,正如你在本教程所見到的。
要想更多的了解CFont類,可參見MFC文檔。在API線上幫助檔案中,有一篇文章對字型做了很好的概述。查找“Fonts and Text Overview”。
SetFont函式是從CWnd繼承來的。它是用來設定視窗的字型的,在我們的程式中是CStatic子視窗。你可能要問:“我怎樣知道CWnd中的哪些函式可以用於CStatic在?”你只能在實踐中來學習。花上一些時間來看一下CWnd的所有函式。你定會有所收穫,並會發現哪些函式可用於定製控制。我們在選下一講中看到CWnd類中的其它Set函式。

結論
在本教程中,我們勘察了CStatic的很多不同特性。有關從CWnd繼承來的Set函式,我們將放到下一講介紹,因為在那裡更合適。

查看Microsoft文檔中的函式
在Visual C++ 5.x中,查找你多不熟悉的函式是很簡單的。所有的MFC、SDK、Windows API和C/C++標準庫函式都繼承到同一個幫助系統中了。如果你不能確定所要的函式在哪兒,你可以使用幫助選單中的Search選項來查找。所有相關的函式都會列出來的。

編譯多個可執行程式
在本教程中,有幾個例子程式。有兩種方式來編譯和運行它們。第一種方式是把每個程式都放到自己的目錄中,然後為每個程式分別建立一個項目。使用該技術,你可以分別編譯每個程式,並且可以同時或獨立地使用他們。該方法的缺點是需要比較大的磁碟空間。
第二種方法是為所有的程式只建立一個目錄。你可以一個項目檔案。為了編譯每個程式,你可以編輯項目和改變源檔案。當你重新編譯項目時,新的可執行程式就是你所選擇的源檔案的。該方法可以使用減少磁碟空間。

第四部分:訊息映射

應用程式放在視窗中的任何用戶界面對象都具有兩種可控制的特性:1) 它的外觀,2) 它回響事件的行為。在上一講中,你已經學習了CStatic控制和如何使用樣式屬性來定製用戶界面對象的外觀。這些概念可用於MFC中的所有不同控制類。
在本講中,我們將介紹CButton控制,以理解訊息映射和簡單的事件處理。然後還要介紹使用CScrollBar控制的稍微複雜點的例子。

理解訊息映射
在第二講中,MFC程式不包括主要函式或時間循環。所有的事件處理都是作為CWinApp的一部分在後台處理的。因為它們是隱藏的,所以我們需要一種方法來告訴不可見的時間循環通告我們應用程式所感興趣的事件。這需要一種叫做訊息映射的機制。訊息映射識別感興趣的事件然後調用函式來回響這些事件。
例如,如果你要編寫一個程式,當用戶按下標有“退出”的按鈕時要退出應用程式。在程式中,你編寫代碼來建立按鈕:你指示按鈕應如何動作。然後,為其父視窗建立用戶單擊按鈕時的訊息映射,它試圖要傳遞訊息給其父視窗。為了建立父視窗的訊息,你要建立截取訊息映射的機制,並且使用按鈕的訊息。當一指定的按鈕事件發生時,訊息映射會請求MFC調用一指定的函式。在這種情況下,單擊退出按鈕就是所感興趣的事件。然後你把退出應用程式的代碼放到指定的函式中。
其它的工作就由MFC來做了。當程式執行時,用戶單擊“退出”按鈕時,按鈕就會自己加亮。然後MFC自動調用相應的函式,並且程式會終止。只使用很少的幾行代碼你就回響了用戶事件。

CButton類
在上一講中所討論的CStatic控制是唯一不回響用戶時間的控制。Windows中所有的其它控制都可回響用戶事件。第一,當用戶處理它們時,它們會自動更新其外觀(例如,當用戶單擊按鈕時,按鈕會自己加亮以給用戶一個反饋)。第二,每個不同的控制都要傳送信息給你的代碼以使程式能回響用戶的需要。例如,當單擊按鈕時,按鈕就會傳送一個命令訊息。如果你編寫代碼接收訊息,則你的代碼就能回響用戶事件。
為了理解這個過程,我們從CButton控制開始。下面的代碼說明了建立按鈕的過程:
// button1.cpp
#include
#define IDB_BUTTON 100
// Declare the application class
class CButtonApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// Create an instance of the application class
CButtonApp ButtonApp;
// Declare the main window class
class CButtonWindow : public CFrameWnd
{
CButton *button;
public:
CButtonWindow();
};
// The InitInstance function is called once
// when the application first executes
BOOL CButtonApp::InitInstance()
{
m_pMainWnd = new CButtonWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// The constructor for the window class
CButtonWindow::CButtonWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CButton Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
r.InflateRect(-20,-20);
// Create a button
button = new CButton();
button->Create("Push me",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
r,
this,
IDB_BUTTON);
}
上面的代碼與前面介紹的代碼幾乎相同。CButton類的Create函式共有5個參數。前四個與CStatic的相同。第五個參數為按鈕的資源ID。資源ID是用來標識訊息映射中按鈕的唯一整數值。常數值IDB_BUTTON已經在程式的頂部做了定義。“IDB_”是任選的,只是該常量ID是用來表示按鈕的。它的值為100,因為100以內的值都為系統所保留。你可以使用任何大於99的值。
CButton類所允許的樣式屬性與CStatic類的是不同的。定義了11個不同的“BS”(“Button Style”)常量。完整的“BS”常量列表可在用Search命令查找CButton,並選擇“button style”。這裡我們要用的是BS_PUSHBUTTON樣式,它表示我們要一正常的的按鈕方式來顯示該按鈕。我們還使用了兩個熟悉的“WS”屬性: WS_CHILD和WS_VISIBLE。我們將在後面介紹其它一些樣式。
當你運行代碼時,會注意到按鈕回響了用戶事件。既它加亮了。除此之外它沒有做任何事情,因為我們還沒有教它怎樣去做。我們需要編寫訊息映射來使按鈕做一些感興趣的事情。

建立訊息映射
下面的代碼包含有訊息映射,也包含有新的處理單擊按鈕的函式(當用戶單擊按鈕時會響一下喇叭)。它只是前面代碼的一個簡單的擴充:
// button2.cpp
#include
#define IDB_BUTTON 100
// Declare the application class
class CButtonApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// Create an instance of the application class
CButtonApp ButtonApp;
// Declare the main window class
class CButtonWindow : public CFrameWnd
{
CButton *button;
public:
CButtonWindow();
afx_msg void HandleButton();
DECLARE_MESSAGE_MAP()
};
// The message handler function
void CButtonWindow::HandleButton()
{
MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
ON_BN_CLICKED(IDB_BUTTON, HandleButton)
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CButtonApp::InitInstance()
{
m_pMainWnd = new CButtonWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// The constructor for the window class
CButtonWindow::CButtonWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CButton Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
r.InflateRect(-20,-20);
// Create a button
button = new CButton();
button->Create("Push me",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
r,
this,
IDB_BUTTON);
}
主要修改了三個方面:
1.CButtonWindow的類說明現在包含了一個新的成員函式和一個新的表示訊息映射的宏。HandleButton函式是正常的C++函式,它通過afx_msg標籤確定為訊息處理函式。該函式需要一些特殊的約束,例如,它必須是void型並且它不能接收任何參數。DECLARE_MESSAGE_MAP宏建立了訊息映射。函式和宏都必須是public型的。
2.HandleButton函式作為成員函式以同樣的方式來建立。在該函式中,我們調用了Windows API中的MessageBeep函式。
3.用宏來建立訊息映射。在代碼中,你可以看見BEGIN_MESSAGE_MAP宏接收兩各參數。第一個指定了使用訊息映射的類的名稱。第二個是基類。然後是ON_BN_CLICKED宏,接受兩個參數控制的ID和該ID傳送命令訊息時所調用的函式。最後,訊息映射用END_MESSAGE_MAP來結束。
當用戶單擊按鈕時,它向其包含該按鈕的父視窗傳送了一個包含其ID的命令訊息。那是按鈕的預設行為,這就是該代碼工作的原因。按鈕向其父視窗傳送訊息,是因為它是子視窗。父視窗截取該訊息並用訊息映射來確定所要調用的函式。MFC來安排,只要指定的訊息一出現,相應的函式就會被調用。
ON_BN_CLICKED訊息是CButton傳送的唯一感興趣的訊息。它等同於CWnd中的ON_COMMAND訊息,只是一個更簡單方便的同義詞而已。

改變大小的訊息
在上面的代碼中,由於有了訊息映射,從CFrameWnd繼承來的應用程式視窗認出按鈕有按鈕產生的單擊訊息並回響之。加入訊息映射的ON_BN_CLICKED宏指定了按鈕的ID和視窗在接收到來自按鈕的命令訊息時應調用的函式。因為只要用戶單擊了按鈕,按鈕就會自動把其ID傳送父視窗,這樣才能允許代碼正確地處理按鈕事件。
作為該應用程式的主視窗的框架視窗自己也有傳遞訊息的能力。大約有100不同的訊息可用,它們都是從CWnd類繼承來的。從MFC幫助檔案中瀏覽CWnd類的成員函式,你就會看到所有的這些訊息。查看所有以“On”開頭的成員函式。
你可能已經注意到了,至今為止所有的代碼都不能很好地處理尺寸變化。當視窗變化大小時,視窗的框架會做相應的調整,但是視窗中調的內容仍原處不動。可以通過處理尺寸變化的事件來更好的處理這一問題。任何視窗傳送的訊息之一就是變尺寸訊息。該訊息是當改變形狀時發出的。我們可以使用該訊息來控制框架中子視窗的大小,如下所示:
// button3.cpp
#include
#define IDB_BUTTON 100
// Declare the application class
class CButtonApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// Create an instance of the application class
CButtonApp ButtonApp;
// Declare the main window class
class CButtonWindow : public CFrameWnd
{
CButton *button;
public:
CButtonWindow();
afx_msg void HandleButton();
afx_msg void OnSize(UINT, int, int);
DECLARE_MESSAGE_MAP()
};
// A message handler function
void CButtonWindow::HandleButton()
{
MessageBeep(-1);
}
// A message handler function
void CButtonWindow::OnSize(UINT nType, int cx,
int cy)
{
CRect r;
GetClientRect(&r);
r.InflateRect(-20,-20);
button->MoveWindow(r);
}
// The message map
BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
ON_BN_CLICKED(IDB_BUTTON, HandleButton)
ON_WM_SIZE()
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CButtonApp::InitInstance()
{
m_pMainWnd = new CButtonWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// The constructor for the window class
CButtonWindow::CButtonWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CButton Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
r.InflateRect(-20,-20);
// Create a button
button = new CButton();
button->Create("Push me",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
r,
this,
IDB_BUTTON);
}
為了理解上面的代碼,從視窗的訊息映射開始。你會發現入口ON_WM_SIZE。該入口表示訊息映射是對來自CButtonWindow對象的變尺寸訊息發生回響。變尺寸訊息是當用戶改變視窗的大小時產生的。該訊息來自視窗本身,而不是作為ON_COMMAND訊息由按鈕向其父視窗傳送的。這是因為視窗框架不是子視窗。
要注意的是訊息映射中的ON_WM_SIZE入口沒有參數。你在MFC文檔中CWnd類,訊息映射中的ON_WM_SIZE入口總是調用OnSize函式,並且該函式必須接收三個參數。OnSize函式必須是訊息映射所屬類的成員函式,並且該函式必須用afx_msg來說明(正如上面在CButtonWindow的定義中所見到的一樣)。
如果你查看MFC文檔,就會發現CWnd中有近100名為“On...”的函式。CWnd::OnSize是其中之一。所有這些函式都在訊息映射中有形如ON_WM_對應的標籤。例如,ON_WM_SIZE對應OnSize。ON_WM_入口不接收任何參數,如ON_BN_CLICKED一樣。參數是假設的並自動傳遞給相應的如OnSize的“On...”函式。
重複一遍,因為它很重要: OnSize函式總是與訊息映射中的ON_WM_SIZE入口想對應。你必須命名處理函式OnSize, 並且它必須接收三個參數。不同的函式的參數會有所不同。
上面的代碼中在OnSize函式自身的內部,有三行代碼修改了按鈕在視窗中的尺寸。你可以在該函式中輸入任何你想要的代碼。
調用GetClientRect是為了恢復視窗用戶區域的新尺寸。該矩形會被縮小,並調用按鈕的MoveWindow函式。MoveWindow是從CWnd繼承來的,改變尺寸和移動子視窗是在一步完成的。
當你執行上面改變視窗大小的程式時,你就會發現按鈕自己能正確地改變大小。在代碼中,變尺寸事件他國訊息映射中的OnSize函式而產生一調用,它調用MoveWindow函式來改變按鈕的大小。

視窗訊息
查看MFC文檔,你可以看到主視窗處理的各種各樣的CWnd訊息。有些與我們上面介紹的類似。例如,ON_WM_MOVE訊息是當用戶移動視窗時傳送的訊息,ON_WM_PAINT訊息是當視窗的任何部分需要重畫時發出的。至今為止,我們的所有程式,重畫工作都是自動完成的,因為是控制自己來負責其外觀。如果你自己使用GDI命令來在用戶區域中繪製,應用程式就應負責重畫工作。因此ON_WM_PAINT就變得重要了。
還有一些傳送給視窗的事件訊息更深奧。例如,你可以使用ON_WM_TIMER訊息與SetTimer函式來使接收預先設定的時間間隔。下面的代碼給出了該過程。當你運行該代碼時,程式會每隔1秒鐘鳴笛一聲。你可以用其它更有用的功能來代替鳴笛。
// button4.cpp
#include
#define IDB_BUTTON 100
#define IDT_TIMER1 200
// Declare the application class
class CButtonApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// Create an instance of the application class
CButtonApp ButtonApp;
// Declare the main window class
class CButtonWindow : public CFrameWnd
{
CButton *button;
public:
CButtonWindow();
afx_msg void HandleButton();
afx_msg void OnSize(UINT, int, int);
afx_msg void OnTimer(UINT);
DECLARE_MESSAGE_MAP()
};
// A message handler function
void CButtonWindow::HandleButton()
{
MessageBeep(-1);
}
// A message handler function
void CButtonWindow::OnSize(UINT nType, int cx,
int cy)
{
CRect r;
GetClientRect(&r);
r.InflateRect(-20,-20);
button->MoveWindow(r);
}
// A message handler function
void CButtonWindow::OnTimer(UINT id)
{
MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
ON_BN_CLICKED(IDB_BUTTON, HandleButton)
ON_WM_SIZE()
ON_WM_TIMER()
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CButtonApp::InitInstance()
{
m_pMainWnd = new CButtonWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// The constructor for the window class
CButtonWindow::CButtonWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CButton Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Set up the timer
SetTimer(IDT_TIMER1, 1000, NULL); // 1000 ms.
// Get the size of the client rectangle
GetClientRect(&r);
r.InflateRect(-20,-20);
// Create a button
button = new CButton();
button->Create("Push me",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
r,
this,
IDB_BUTTON);
}
在上面的程式內部,我們建立了一個按鈕,如前所示,改變尺寸的代碼沒有變動。在視窗的構造函式中,我們添加了SetTimer函式的調用。該函式接收三個參數:時鐘的ID(可以同時使用多個時鐘,每次時鐘關閉時都會把ID傳遞給所調用的函式),時間以毫秒為單位。在這裡,我們向函式傳送了NULL,以使視窗訊息映射自己自動傳送函式。在訊息映射中,我們已經通知了ON_WM_TIMER訊息,它會自動調用OnTimer函式來傳遞已經關閉了的時鐘的ID。
當程式運行時,它每隔1毫秒鳴笛一聲。每次時鐘的時間增量流逝,視窗都會傳送訊息給自己。訊息映射選擇訊息給OnTimer函式,它鳴笛。你可以在此放置更有用的代碼。

滾動條控制
Windows用兩種不同的方式來處理滾動條。一些控制,如編輯控制和列表控制,可以帶有滾動條。在這種情況下,滾動條會被自動處理,不不要額外的代碼來處理。
滾動條也可以作為單獨的元件來使用。當這樣使用時,滾動條就擁有獨立的權力。你可以參見MFC參考手冊中有關CScrollBar的有關章節。滾動條控制的建立與前面介紹的靜態標籤和按鈕的一樣。它有四個成員函式允許你設定和獲取滾動條的位置和範圍。
下面的代碼演示了建立水平滾動條的過程和其訊息映射:
// sb1.cpp
#include
#define IDM_SCROLLBAR 100
const int MAX_RANGE=100;
const int MIN_RANGE=0;
// Declare the application class
class CScrollBarApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// Create an instance of the application class
CScrollBarApp ScrollBarApp;
// Declare the main window class
class CScrollBarWindow : public CFrameWnd
{
CScrollBar *sb;
public:
CScrollBarWindow();
afx_msg void OnHScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar);
DECLARE_MESSAGE_MAP()
};
// The message handler function
void CScrollBarWindow::OnHScroll(UINT nSBCode,
UINT nPos, CScrollBar* pScrollBar)
{
MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CScrollBarWindow, CFrameWnd)
ON_WM_HSCROLL()
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CScrollBarApp::InitInstance()
{
m_pMainWnd = new CScrollBarWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// The constructor for the window class
CScrollBarWindow::CScrollBarWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CScrollBar Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
// Create a scroll bar
sb = new CScrollBar();
sb->Create(WS_CHILD|WS_VISIBLE|SBS_HORZ,
CRect(10,10,r.Width()-10,30),
this,
IDM_SCROLLBAR);
sb->SetScrollRange(MIN_RANGE,MAX_RANGE,TRUE);
}
Windows會區分水平和垂直滾動條,同時還支持CScrollBar中一稱為尺寸盒的控制。尺寸盒是一個小方塊。它處於水平和垂直滾動條的交叉處,呀滑鼠拖動它會自動改變視窗的大小。在後面的代碼中你看到如何用Create函式的SBS_HORZ樣式來建立一水平滾動條。在建立了滾動條之後,馬上用SetScrollRange中的MIN_RANGE和MAX_RANGE龍個常數給出了滾動條的範圍0~100(它們定義在程式的頂部)。
事件處理函式OnHScroll來自CWnd類。我們使用該函式是因為該代碼建立了水平滾動條。對於垂直滾動條應使用OnVScroll。在代碼中,訊息映射與滾動函式相聯繫,並使滾動條在用戶操作時發出鳴笛聲。當你運行該程式時,你可以單擊箭頭、拖動滾動條上的小方塊等等。每次操作都會出現鳴笛聲,但是滾動條上的小方塊實際上不會移動,因為我們還沒有把它與實際的代碼相關聯。
每次滾動條調用OnHScroll時,你的代碼都要確定用戶的操作。在OnHScroll函式內部,你可以檢驗傳遞給處理函式的第一參數,如下所示。如果你與上面的代碼一起使用,滾動條的小方塊就會移動到用戶操作的位置處。
// The message handling function
void CScrollBarWindow::OnHScroll(UINT nSBCode,
UINT nPos, CScrollBar* pScrollBar)
{
int pos;
pos = sb->GetScrollPos();
switch ( nSBCode )
{
case SB_LINEUP:
pos -= 1;
break;
case SB_LINEDOWN:
pos += 1;
break;
case SB_PAGEUP:
pos -= 10;
break;
case SB_PAGEDOWN:
pos += 10;
break;
case SB_TOP:
pos = MIN_RANGE;
break;
case SB_BOTTOM:
pos = MAX_RANGE;
break;
case SB_THUMBPOSITION:
pos = nPos;
break;
default:
return;
}
if ( pos MAX_RANGE )
pos = MAX_RANGE;
sb->SetScrollPos( pos, TRUE );
}
SB_LINEUP和SB_LINEDOWN的不同常數值在CWnd::OnHScroll函式文檔中有介紹。上面的代碼首先使用GetScrollPos函式來恢復滾動條的當前位置。然後使用開關語句來確定用戶對滾動條的操作。SB_LINEUP 和SB_LINEDOWN常數值意味著垂直方向,但也可用於水平方向表示左右移動。SB_PAGEUP和SB_PAGEDOWN是用在用戶單擊滾動條時。SB_TOP和SB_BOTTOM用於當用戶移動滾動條小方塊到滾動條的頂部和底部。SB_THUMBPOSITION用於當用戶拖動小方塊到指定位置時。代碼會自動調整位置,然後確保它在設定其新位置時仍然在範圍內。一旦設定了滾動條,小方塊就會移動到適當的位置。
垂直滾動條的處理也是類似的,只是要用OnVScroll函式中的SBS_VERT樣式。

理解訊息映射
訊息映射結構只能用於MFC。掌握它和如何在你的代碼中套用它是很重要的。
可能純C++使用者會對訊息映射產生疑問: 為什麼Microsoft不用虛擬函式來替代訊息映射?虛擬函式是MFC中處理訊息映射的標準C++方式,所以使用宏DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP可能有些怪異。
MFC使用訊息映射來解決虛擬函式的基本問題。參見MFC幫助檔案中的CWnd類。它包含200多個成員函式,所有的成員函式當不使用訊息映射時都是虛擬的。現在來看一下所有CWnd類的子類。MFC中大約有近30個類是以CWnd為基類的。這包括所有的可見控制如按鈕、靜態標籤和列表。現在想像一下,MFC使用虛擬函式,並且你建立一應用程式包含有20個控制。CWnd中的200個虛擬函式中的每個都需要自己的虛擬函式表,並且一個控制的每個例程都應有一組200個虛擬函式與之關聯。則程式可能就有近4,000個虛擬函式表在記憶體中,這對記憶體有限的機器來說是個大問題。因為其中的大部分是不用的。
訊息映射複製了虛擬函式表的操作,但是它是基於需要的基礎之上的。當你在訊息映射中建立一個入口時,你是在對系統說,“當你看見一個特殊的訊息時,請調用指定的函式”。只有這些函式實際上被重載到訊息映射中,著就節省了記憶體和CPU的負擔。
當你用DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP說明訊息映射時,系統會通過你的訊息映射選擇所有的訊息。如果訊息映射處理了給定的訊息,則你的函式會被調用,卸車也就停留在此。但是,如果你的訊息映射中不包含某個訊息的入口,則系統會把該訊息傳送第二個BEGIN_MESSAGE_MAP指定的類。那個類可能會也可能不會處理它,如此重複。最後,如果沒有訊息映射處理一給定的訊息,該訊息會到由一預設的處理函式來處理。

結論
本講中所介紹的所有訊息映射處理概念可適用於Windows NT中所有的控制和視窗。在大部分情況下,你可以使用ClassWizard來安裝訊息映射的入口,它將在後面的有關ClassWizard、AppWizard和資源編輯器一文中介紹。

相關詞條

相關搜尋

熱門詞條

聯絡我們