『VC++技術(shù)內(nèi)幕』(第四版)讀書(shū)筆記
關(guān)鍵字:VC++
原作者姓名:loose_went
文章原出處:vczx.com
寫在前面:
站長(zhǎng)所看的『VC++技術(shù)內(nèi)幕』版本為--潘愛(ài)民和王國(guó)印譯清華大學(xué)出版的第四版,因有時(shí)工作忙碌,不能及時(shí)更新,請(qǐng)大家見(jiàn)諒!
第一天 Windows的編程模式
Windows程序中必須要有WinMain函數(shù),因?yàn)樵摵瘮?shù)最重要的任務(wù)是創(chuàng)建該應(yīng)用程序的主窗口。Windows程序與基于MS-DOS程序的最大差別就在于:MS-DOS程序是通過(guò)調(diào)用操作系統(tǒng)的功能來(lái)獲得用戶輸入的,而Windows程序是通過(guò)操作系統(tǒng)發(fā)送的消息來(lái)處理用戶輸入的。Windows消息都是經(jīng)過(guò)嚴(yán)格定義的,并且適用于所有的程序。
WINDOWS提供通用的圖形設(shè)備接口(GUI),我們通過(guò)調(diào)用(GDI)函數(shù)和硬件打交道,不必理會(huì)設(shè)備環(huán)境,WINDOWS會(huì)自動(dòng)將設(shè)備環(huán)境結(jié)構(gòu)映射到相應(yīng)的物理設(shè)備。
Windows程序設(shè)計(jì)中所需要的數(shù)據(jù)是存儲(chǔ)在資源文件中的,這樣,連接器就可以把編譯好的二進(jìn)制代碼和二進(jìn)制資源文件結(jié)合起來(lái)生成可執(zhí)行程序。資源文件可以包括位圖、圖標(biāo)、菜單定義、對(duì)話框設(shè)計(jì),甚至可以包含用戶自己定義的格式。
Windows程序允許動(dòng)態(tài)的連接目標(biāo)模塊,并且多個(gè)應(yīng)用程序可以共享同一個(gè)動(dòng)態(tài)連接庫(kù)。
VC++的源程序?yàn)g覽器能夠使我們從類或函數(shù)的角度來(lái)了解或編輯程序,而不是直接從文件入手。在看別人的源代碼時(shí)如果能熟練的使用源代碼瀏覽器將會(huì)事半功倍。源程序?yàn)g覽器主要的查看狀態(tài)有以下幾種:
Definitions and References--選擇任何函數(shù)、變量、類型、宏定義可以看到它在項(xiàng)目中的定義,并且在何處和什么地方用到它。
Call Graph/Caller Graph--對(duì)于所選擇的函數(shù),給出它的調(diào)用與被調(diào)用函數(shù)的圖示。
Derived Class Graph/Base Class Graph--給出類層次關(guān)系的圖形表示,可以看到所選擇的類的派生類和基類以及成員。
File Outline--對(duì)于所選的文件,列出文件中的類、函數(shù)和數(shù)據(jù)成員,同時(shí)還顯示它們定義的位置和使用位置。
可見(jiàn)Source Brower比起Class View來(lái)功能多了很多也更加好用。
對(duì)于本章學(xué)習(xí)loose_went建議大家在VC++6中用AppWizard生成一個(gè)空的程序,然后試著看看都有哪些文件,和他們的類層次、函數(shù)、宏、結(jié)構(gòu)的定義,我就是這樣干的,學(xué)編程不動(dòng)手是不行的。
第二天 MFC應(yīng)用程序框架
?MFC是C++的Microsoft Windows API
?MFC產(chǎn)生的應(yīng)用程序使用了標(biāo)準(zhǔn)化的結(jié)構(gòu)。
?MFC產(chǎn)生的應(yīng)用程序短而運(yùn)行速度快。
?VC++工具降低了編碼的復(fù)雜性,這當(dāng)然了,很多代碼都由它代勞了,呵呵。
?MFC庫(kù)應(yīng)用程序框架的功能非常豐富。
以上說(shuō)的都是MFC庫(kù)的優(yōu)點(diǎn),雖然說(shuō)MFC有著這樣多的優(yōu)點(diǎn),但我個(gè)人認(rèn)為不能盲目的學(xué)習(xí)它,要想學(xué)好,那么您必須先掌握C++,這是毋庸置疑的。可能剛開(kāi)始的時(shí)候,您覺(jué)得收獲很大,也很有趣,但要進(jìn)一步提高,沒(méi)有C++基礎(chǔ)是很難的。所以站長(zhǎng)建議大家學(xué)習(xí)的時(shí)候要有先有后,這樣才能學(xué)好!
應(yīng)用程序框架是一種類庫(kù)的超集。
我們現(xiàn)在先來(lái)看一個(gè)例子,看看MFC有多么強(qiáng)大!您只需加一行代碼,甚至一行都不用加只需要點(diǎn)幾下鼠標(biāo)就可以創(chuàng)建一個(gè)windows 程序,不信,試一下:
1、打開(kāi)VC++6從菜單選擇NEW,給項(xiàng)目命名為"MyApp "。
2、選擇MFC AppWizard[exe] 選項(xiàng),除STEP 1選擇單文檔外其他STEP缺省。
3、在Class View選擇CMyAppView類的OnDraw()成員函數(shù)雙擊會(huì)在C++編譯器看到以下內(nèi)容
void CMyAppView::OnDraw(CDC* pDC)
{
CMyAppDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
在 // TODO: add draw code for native data here的位置增加一行代碼
void CMyAppView::OnDraw(CDC* pDC)
{
CMyAppDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC->TextOut(10,10,"愿vc在線能成為您學(xué)習(xí)vc最好的朋友!"); //增加的一行
// TODO: add draw code for native data here
}
完了,就這么簡(jiǎn)單。編譯運(yùn)行。看到了嗎?這個(gè)程序具備WINDOWS程序的所有特性,例如有菜單、工具條、狀態(tài)欄、最大化、關(guān)閉、甚至還有關(guān)于對(duì)話框、打印預(yù)覽.....全了,這就是AppWizard通過(guò)MFC動(dòng)態(tài)創(chuàng)建的一個(gè)應(yīng)用程序。從這個(gè)小例子可以看出用VC/MFC設(shè)計(jì)WINDOWS程序多么方便。
下面我們看看書(shū)上的例子,以便更進(jìn)一步了解應(yīng)用程序框架。
1、先建立一個(gè)Win32 Application的應(yīng)用程序。
2、選擇Project->Add to project->Files,分別創(chuàng)建一個(gè)名為MyApp.h和一個(gè)名為MyApp.cpp的文件。
3、添加代碼:(最好照敲一下代碼到編譯器,別用Ctrl+C/Ctrl+V)
//***********************************************
// MyApp.h
//
class CMyApp:public CWinApp
{
public:
virtual BOOL InitInstance();
};
class CMyFrame:public CFrameWnd
{
public:
CMyFrame();
protected:
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
//*****************************************************
// MyApp.cpp
//
#include "afxwin.h"
#include "myapp.h"
CMyApp theApp;//建立一個(gè)CMyAPP對(duì)象
BOOL CMyApp::InitInstance ()
{
m_pMainWnd=new CMyFrame();
m_pMainWnd->ShowWindow (m_nCmdShow);
m_pMainWnd->UpdateWindow ();
return TRUE;
}
BEGIN_MESSAGE_MAP(CMyFrame,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()
CMyFrame::CMyFrame(){
Create(NULL,"MYAPP Application");
}
void CMyFrame::OnLButtonDown (UINT nFlags,CPoint point)
{
TRACE("Entering CMyFrame::OnLButtonDown - %lx,%d,%d\n",
(long)nFlags,point.x ,point.y);
}
void CMyFrame::OnPaint ()
{
CPaintDC dc(this);
dc.TextOut (0,0,"Hello World!");
}
4、編譯運(yùn)行,報(bào)錯(cuò)。為什么呢?原來(lái)還沒(méi)有添加MFC的支持,在Project Setting選項(xiàng)General屬性頁(yè)選擇"Use MFC in a Static Library"
5、再按Ctrl+F5,怎么樣,簡(jiǎn)單吧?
讓我們看看這個(gè)程序中的一些元素。
①WinMain函數(shù):Windows總是要求每個(gè)應(yīng)用程序都要有WinMain函數(shù)的,您之所以看不見(jiàn),是因?yàn)樗呀?jīng)隱藏在應(yīng)用程序框架內(nèi)部了。
②CMyApp類:CMyApp類的對(duì)象代表一個(gè)應(yīng)用程序,CWinApp基類決定它的大部分行為。
③應(yīng)用程序的啟動(dòng):當(dāng)開(kāi)始運(yùn)行應(yīng)用程序時(shí)WINDOWS會(huì)調(diào)用WinMain函數(shù),WinMain會(huì)查找該應(yīng)用程序的全局對(duì)象theApp。
④CMyApp::InitInstance成員函數(shù):發(fā)現(xiàn)theApp后自動(dòng)調(diào)用重載的虛函數(shù)InitInstance來(lái)完成主窗口的構(gòu)造和顯示工作。
⑤CWinApp::Run成員函數(shù):WinMain在調(diào)用InitInstance之后緊接著調(diào)用Run函數(shù),它被隱藏在基類中負(fù)責(zé)傳遞應(yīng)用程序的消息給相映的窗口。
⑥CMyFrame類:此類的對(duì)象代表著應(yīng)用程序的主窗口。它的構(gòu)造函數(shù)調(diào)用基類CFrameWnd的Create函數(shù)創(chuàng)建具體的窗口結(jié)構(gòu)。
⑦CMyFrame::OnLButtonDown函數(shù):演示消息處理機(jī)制,當(dāng)鼠標(biāo)坐鍵被按下這一事件被映射到CMyFrame的OnLButtonDown函數(shù)上,如果你選擇F5進(jìn)行編譯運(yùn)行的話可以在調(diào)試窗口看到TRACE宏顯示的類似下面的信息
Entering CMyFrame::OnLButtonDown - 1,309,119
Entering CMyFrame::OnLButtonDown - 1,408,221
⑧CMyFrame::OnPaint函數(shù):應(yīng)用程序每次重新繪制窗口都需要調(diào)用此函數(shù),將顯示"Hello World!"放在這里是因?yàn)槊看未翱诎l(fā)生變化時(shí)保證"Hello World!"被顯示,你可以試著將語(yǔ)句:
CPaintDC dc(this);
dc.TextOut (0,0,"Hello World!");
寫在別出,例如寫在
void CMyFrame::OnLButtonDown (UINT nFlags,CPoint point)
{
TRACE("Entering CMyFrame::OnLButtonDown - %lx,%d,%d\n",
(long)nFlags,point.x ,point.y);
CPaintDC dc(this);
dc.TextOut (0,0,"Hello World!");
}
運(yùn)行后當(dāng)點(diǎn)擊左鍵時(shí)顯示"Hello World!",但當(dāng)窗口最小化再最大化時(shí)"Hello World!"不見(jiàn)了。
⑧關(guān)閉應(yīng)用程序:用戶關(guān)閉應(yīng)用程序時(shí)會(huì)有一系列事件發(fā)生。首先CMyFrame對(duì)象被刪除,然后退出Run,進(jìn)而退出WinMain,最后刪除CMyApp對(duì)象。
通過(guò)上面的示例我們看見(jiàn)程序的大部分功能包含在基類CWinApp和CFrameWnd中,我們只寫了很少的函數(shù),便可以完成很復(fù)雜的功能。所以應(yīng)用程序框架不僅僅是一種類庫(kù),它還定義了應(yīng)用程序的結(jié)構(gòu),除了基類外還包括WinMain函數(shù),以及用來(lái)支持消息處理、診斷、DLL、等都包含在應(yīng)用程序框架中。
第三天 消息映射和視圖類
MFC庫(kù)應(yīng)用程序框架沒(méi)有采用虛函數(shù)來(lái)處理windows消息,而是通過(guò)宏將消息映射到派生類相應(yīng)的成員函數(shù)上。文檔-視圖結(jié)構(gòu)是應(yīng)用程序框架的核心,它把數(shù)據(jù)從用戶對(duì)數(shù)據(jù)的觀察中分離出來(lái),這樣做最大的好處就是同一個(gè)數(shù)據(jù)可以對(duì)應(yīng)多個(gè)視圖。比如同一個(gè)股票報(bào)價(jià)數(shù)據(jù),既可以有報(bào)表觀察窗口,也可以有圖形觀察窗口,明白了否?
視圖簡(jiǎn)單來(lái)說(shuō)就是一個(gè)普通的窗口,對(duì)于程序員來(lái)說(shuō)就是一個(gè)從MFC庫(kù)中Cview類派生出來(lái)的類的一個(gè)對(duì)象。視圖類分為兩個(gè)源文件模塊:頭文件(H)和源代碼文件(CPP)。
用Appwizard創(chuàng)建一個(gè)SDI應(yīng)用程序,產(chǎn)生了如下文件(假設(shè)工程名為Exc01):
Exc01.dsp 項(xiàng)目文件,Visual Studio用它來(lái)創(chuàng)建應(yīng)用程序
Exc01.dsw 工作空間文件,包含一個(gè)項(xiàng)目Exc01.dsp
Exc01.rc ASCII碼資源描述文件
Exc01View.cpp 包含CExc01View類成員函數(shù)和視圖類文件
Exc01View.h 包含CExc01View類定義的視圖類頭文件
Exc01.opt 二進(jìn)制文件,告訴Developer Studio本項(xiàng)目的哪些文件是打開(kāi)的,又是如何排序的
Readme.txt 用來(lái)解釋所產(chǎn)生的所有文件的文本文件
Resource.h 包含#define常量定義的頭文件
從Exc01View.cpp和Exc01View.h的代碼中可以看出,這兩個(gè)文件已經(jīng)完全定義了CExc01View類,而該類正是此應(yīng)用程序的核心。CExc01View類的對(duì)象與應(yīng)用程序的視窗相關(guān)聯(lián),應(yīng)用程序的所有"動(dòng)作"都會(huì)在這個(gè)視窗中顯示出來(lái)。
CExc01View類的兩個(gè)最重要的基類是CWnd和CView類。CWnd類提供了CExc01View的窗口屬性,而CView類則提供了它和應(yīng)用程序框架的其它部分之間的聯(lián)系,特別是和文檔以及框架窗口之間的聯(lián)系。這一點(diǎn)一定要記住。
下面我們來(lái)看一下如何在視窗內(nèi)繪圖。最重要的一個(gè)函數(shù)是OnDraw()函數(shù),它是一個(gè)虛函數(shù),每次窗口被重畫(huà)時(shí),應(yīng)用程序都要先調(diào)用這個(gè)函數(shù)。注意:盡管可以隨時(shí)對(duì)窗口繪制,但最好還是等變化內(nèi)容積累到一定程度后再教給OnDraw()函數(shù)處理,這樣效率會(huì)高一些。
在MFC中,設(shè)備環(huán)境是由C++的CDC類對(duì)象來(lái)表示的,該對(duì)象被作為參數(shù)傳給Ondraw()函數(shù),這樣,我們就可以調(diào)用CDC的許多成員函數(shù)來(lái)完成各種繪制了。
找到OnDraw()函數(shù),用以下語(yǔ)句替換函數(shù)原來(lái)的內(nèi)容:
pDC->TextOut( 0, 0, "Hello World!" );
pDC->Ellipse(CRect(0,20,100,120));
再編譯運(yùn)行,看到了什么?
TextOut和Ellipse都是設(shè)備環(huán)境類CDC的成員函數(shù),MFC庫(kù)提供了一個(gè)用來(lái)表示windows矩形的類CRect,在這里CRect的一個(gè)臨時(shí)對(duì)象被作為參數(shù)傳遞給 了Ellipse函數(shù),當(dāng)外接矩形的寬和高相等時(shí),Ellipse函數(shù)就畫(huà)出個(gè)圓。
第四天 資源和編譯
資源文件(就是以應(yīng)用程序名和擴(kuò)展名是.rc的文件)很大程度上決定了應(yīng)用程序的用戶界面。在VC++中資源文件包括以下內(nèi)容:
Accelerator //模擬菜單和工具欄選擇的鍵盤定義
Dialog //對(duì)話框的布局及內(nèi)容
Icon //圖標(biāo)有兩種一種是16X16一種是32X32。
Menu //應(yīng)用程序的主菜單及所屬的彈出式菜單
String table //一些字符串,不屬于C++源代碼部分
Toolbar //工具條。
Version //程序的描述、版本號(hào)、支持語(yǔ)言信息。
除了以上信息,.rc文件還包含了以下語(yǔ)句: #include "afxres.h" #include "afxres.rc" 它們的作用是把適合于所有應(yīng)用程序的一些通用MFC庫(kù)資源包含進(jìn)來(lái),其中包括字符串、圖形按鈕以及打印所需的一些元素。
關(guān)于資源編輯器的使用就不多說(shuō)了,因?yàn)樗牟僮骱芎?jiǎn)單,需要注意的是雖然resource.h是一個(gè)ASCII碼文件可以用文本編輯器進(jìn)行編輯,但如果使用文本編輯器進(jìn)行編輯的話,下次再使用資源編輯器時(shí)所做的修改有可能丟失,所以我們應(yīng)該在盡量在資源編輯器中編輯應(yīng)用程序的資源,新增的資源內(nèi)容回自動(dòng)的添加在我們的程序相應(yīng)位置,例如resource.h而不用我們操心。
編譯在VC++中有兩種模式,一種是Release Build另一種是Debug Build。它們之間的區(qū)別在于,Release Build不對(duì)源代碼進(jìn)行調(diào)試,不考慮MFC的診斷宏,使用的是MFC Release庫(kù),編譯十對(duì)應(yīng)用程序的速度進(jìn)行優(yōu)化,而Debug Build則正好相反,它允許對(duì)源代碼進(jìn)行調(diào)試,可以定義和使用MFC的診斷宏,采用MFC Debug庫(kù),對(duì)速度沒(méi)有優(yōu)化。所以我們應(yīng)該在Debug模式下開(kāi)發(fā)應(yīng)用程序,然后在Release模式下發(fā)布應(yīng)用程序。在我們的工程文件夾下會(huì)有一個(gè)Debug文件夾和一個(gè)Release文件夾分別存放輸出文件和中間文件。
診斷宏是我們編譯程序時(shí)檢測(cè)程序狀態(tài)的有利工具,例如上兩篇用到的TRACE宏,可以在Debug窗口獲得你需要的診斷信息,而不用設(shè)置對(duì)話框之類的方法,在發(fā)布時(shí)Release會(huì)自動(dòng)濾掉此信息。
為了更好的管理項(xiàng)目,最好理解系統(tǒng)是如何處理預(yù)編譯頭文件的。VC++有兩個(gè)預(yù)編譯系統(tǒng):自動(dòng)的和手工的。這一部分筆者就不多說(shuō)了,建議讀者好好看看。
第五天 基本事件處理
用戶在視窗中的任何一個(gè)操作,都會(huì)引起Windows自動(dòng)發(fā)送一個(gè)消息給該視窗。我們以一個(gè)例子來(lái)說(shuō)明:比如我們?cè)谝暣爸邪聪率髽?biāo)左鍵,Windows就會(huì)發(fā)送ON_LBUTTONDOWN消息給視窗,那么在視窗類中就必須包含下面的成員函數(shù):
Void CmyView::OnLButtonDown(UINT nFlags, Cpoint point)
{
//event processing code here
}
在類頭文件中也要包含相應(yīng)的函數(shù)聲明:
afx_msg void OnLButtonDown(UINT nFlags, Cpoint point)
在代碼文件中還要有一個(gè)消息映射宏,用于將OnLButtonDown函數(shù)和應(yīng)用程序框架聯(lián)系在一起:
BEGIN_MESSAGE_MAP(CmyView, CView)
ON_WM_LBUTTONDOWN()
// other message map entries
END_MESSAGE_MAP
最后,在類庫(kù)頭文件中包含如下語(yǔ)句:
DECLARE_MESSAGE_MAP()
以上這些步驟,我們都可以借助于ClassWizard來(lái)完成。這就是消息映射的過(guò)程。
MFC庫(kù)對(duì)140種windows消息直接提供了消息控制函數(shù),并且我們還可以自己定義自己的消息,下面列出的五種消息是我們應(yīng)該特別注意的(MSDN上有更詳細(xì)的內(nèi)容)。
WM_CREATE
該消息是Windows發(fā)給視圖的第一個(gè)消息。當(dāng)應(yīng)用程序框架調(diào)用create函數(shù)時(shí)該消息便會(huì)被發(fā)送,此時(shí)窗口還未創(chuàng)建完成,不可見(jiàn),因此在消息控制函數(shù)OnCreate內(nèi)不能調(diào)用那些依賴窗口處于完全激活狀態(tài)的Windows函數(shù)。如果需要可以在重載的OnInitialUpdate函數(shù)內(nèi)調(diào)用。不過(guò)注意在SDI應(yīng)用程序OnInitialUpdate函數(shù)可能被多次調(diào)用。
WM_CLOSE
當(dāng)用戶關(guān)閉窗口時(shí),系統(tǒng)會(huì)發(fā)送WM_CLOSE消息。如果派生類重新定義了OnClose函數(shù),就可以完全控制關(guān)閉過(guò)程,可以將提醒用戶存盤之類的工作放在這里完成。我們可以通過(guò)重載CDocument::SaveModified虛函數(shù)達(dá)到相同的目的。
WM_QUERYENDSESSION
從字面的意思看就可以看出,當(dāng)用戶退出Windows時(shí),或者調(diào)用了ExitWindows 函數(shù)時(shí)。Windows會(huì)發(fā)送WM_QUERYENDSESSION消息給所有的正在運(yùn)行的應(yīng)用程序,由OnQueryEndSession消息映射函數(shù)對(duì)消息進(jìn)行處理。在它之后應(yīng)該是WM_ENDSESSION 消息。
WM_DESTROY
在Windows發(fā)送WM_CLOSE消息后,緊接著會(huì)發(fā)送WM_DESTROY消息,雖然窗口已經(jīng)Close但實(shí)際上并沒(méi)有完全清除,在任務(wù)管理器中還可以看見(jiàn)應(yīng)用程序的進(jìn)程(我想很多木馬或病毒都是無(wú)窗口的程序,它們的做法是生成了已經(jīng)活動(dòng)狀態(tài)的窗口但不顯示出來(lái)),利用這個(gè)消息控制函數(shù)便可以對(duì)依賴于當(dāng)前窗口存在的東西做清除工作,不過(guò)一定要注意,應(yīng)該調(diào)用基類的OnDestroy函數(shù),而不能在用戶自己的視圖的OnDestroy函數(shù)中終止窗口的析構(gòu)過(guò)程,終止析構(gòu)過(guò)程應(yīng)該在OnClose函數(shù)中。
WM_NCDESTROY
當(dāng)窗口被取消所發(fā)送的最后一個(gè)消息就是這個(gè)消息。我們可以在OnNcDestroy函數(shù)中做一些不依賴該窗口是否處于活動(dòng)狀態(tài)的最后的處理工作,(我實(shí)在想不出還需要做什么?那位朋友能給個(gè)例子),注意一定要調(diào)用基類中的OnNcDestroy函數(shù)。
MFC庫(kù)中非靜態(tài)數(shù)據(jù)成員的名字以m_為前綴。
一個(gè)窗口具有一個(gè)矩形的"客戶區(qū)域",CWnd中的GetClient成員函數(shù)可以給出客戶區(qū)域的大小,只允許在客戶區(qū)域內(nèi)繪圖。
標(biāo)準(zhǔn)的windows應(yīng)用程序會(huì)首先登記一個(gè)窗口類,這不同于C++類,同時(shí)在處理過(guò)程中,還需要對(duì)每個(gè)類指定窗口過(guò)程。每次應(yīng)用程序調(diào)用CreateWindow建立一個(gè)窗口時(shí),都要指定一個(gè)窗口類作為參數(shù),這樣就把新建立的窗口和窗口過(guò)程函數(shù)連接起來(lái)了,每次windows給窗口發(fā)送消息的時(shí)候,這個(gè)函數(shù)就會(huì)被調(diào)用,以檢查用參數(shù)傳進(jìn)來(lái)的消息碼。
第六天 映射模式
所謂映射模式,說(shuō)白了就是坐標(biāo)系。在默認(rèn)情況下,Windows所繪圖像單位為像素,這是因?yàn)樵O(shè)備環(huán)境用了默認(rèn)的映射模式MM_TEXT,所以如下語(yǔ)句所繪圖形為長(zhǎng)和寬都為200像素的方塊: pDC->Rectangle(CRect(0,0,200,200));
那么我們要繪制一個(gè)長(zhǎng)和寬都是4厘米的方塊該怎么做呢?這就必須改變?cè)O(shè)備環(huán)境的默認(rèn)映射模式為MM_HIMETRIC,它的圖像單位為1/100mm,而不是像素了。它的y軸方向和MM_TEXT的相反,它的向下為遞減的,因此用如下語(yǔ)句就可以繪出4×4cm的方塊了:
pDC->SetMapMode( MM_HIMETRIC);
pDC->Rectangle(CRect(0,0,4000,-4000));
下面我們?cè)賮?lái)了解一下Windows都提供了哪些映射模式。
1、MM_TEXT映射模式
這種模式下,繪圖單位為像素,x軸向右遞增,y軸向下遞增,我們可以用CDC的SetViewPortOrg和SetWindowOrg函數(shù)來(lái)改變坐標(biāo)原點(diǎn)的位置,下面的代碼就是把坐標(biāo)原點(diǎn)設(shè)在了(100,100)處,畫(huà)了一個(gè)200×200像素的方塊,此時(shí)邏輯坐標(biāo)點(diǎn)(100,100)被映射到了設(shè)備坐標(biāo)點(diǎn)(0,0)處,下一篇的滾動(dòng)窗口使用的就是這種變換。
Void CmyView::OnDraw( CDC *pDC ){
pDC->SetMapMode(MM_TEXT);
pDC->SetWindowOrg(Cpoint(100,100));
pDC->Rectangle(CRect(100,100,200,200));
}
2、固定比例映射模式
Windows提供了一組非常重要的固定比例影視模式,所有這種模式都遵循x軸向右遞減,y軸向下遞減的規(guī)則,而且我們無(wú)法將其改變。固定比例模式之間唯一的差別就在于實(shí)際的比例因子。下表列出了影視模式和比例因子的對(duì)應(yīng)情況:
映射模式 |
邏輯單位 |
MM_LOENGLISH |
0.01英寸 |
MM_HIENGLISH |
0.001英寸 |
MM_LOMETRIC |
0.1mm |
MM_HIMETRIC |
0.01mm |
MM_TWIPS |
1/1440英寸 |
MM_TWIPS模式常用于打印機(jī)。
3、可變比例映射模式
Windows還提供了兩種映射模式MM_ISOTROPIC和MM_ANISOTROPIC,這兩種模式允許我們修改比例因子和坐標(biāo)原點(diǎn)。在MM_ISOTROPIC模式下,縱橫比總是1:1,就像改變圖像時(shí)鎖定比例一樣,而MM_ANISOTROPIC模式則可以獨(dú)立的改變x和y的比例因子,即圓可以變成扁圓。
以上就是常見(jiàn)的映射模式,筆者建議:我們沒(méi)必要死記住這些模式,只是到用的時(shí)候會(huì)用就可以了,哪怕查查MSDN,這個(gè)東東真好!
在設(shè)置了映射模式和相應(yīng)參數(shù)之后,我們可以用CDC的LPtoDP函數(shù)將邏輯坐標(biāo)轉(zhuǎn)換為設(shè)備坐標(biāo),用DptoLP函數(shù)將設(shè)備坐標(biāo)轉(zhuǎn)換為邏輯坐標(biāo)。那么我們什么時(shí)候用什么樣的坐標(biāo)呢?有一些規(guī)則如下:
① 可以認(rèn)為CDC的所有成員函數(shù)都以邏輯坐標(biāo)為參數(shù)
② 可以認(rèn)為CWnd的所有成員函數(shù)都以設(shè)備坐標(biāo)為參數(shù)
③ 所有選中測(cè)試都應(yīng)該選用設(shè)備坐標(biāo),區(qū)域的定義應(yīng)采用設(shè)備坐標(biāo),某些像CRect::PtInRect之類的函數(shù)只有采用設(shè)備坐標(biāo)才能有正確的結(jié)果
④ 將一些長(zhǎng)期使用的值用邏輯坐標(biāo)來(lái)保存,如果用設(shè)備坐標(biāo),那么只要用戶對(duì)窗口進(jìn)行一下滾動(dòng),坐標(biāo)就不再有效了
一般情況下,我們?cè)贑View的虛函數(shù)OnPrepareDC中設(shè)置映射模式,應(yīng)用程序框架在調(diào)用OnDraw函數(shù)之前調(diào)用這個(gè)虛函數(shù)。
第七天 滾動(dòng)視窗
CView類并不直接支持窗口滾動(dòng),如要實(shí)現(xiàn)窗口滾動(dòng),就要用到CView的派生類CScrollView類,CScrollView的成員函數(shù)能夠處理滾動(dòng)條并發(fā)送給視圖WM_HSCROLL和WM_VSCROLL消息,從而實(shí)現(xiàn)窗口的滾動(dòng)。
在文檔-視圖結(jié)構(gòu)中,視圖窗口建立以后,框架最先調(diào)用OnInitialUpdate虛函數(shù),在框架第一次調(diào)用OnDraw函數(shù)前也是先調(diào)用OnInitialUpdate函數(shù),因此在OnInitialUpdate函數(shù)中設(shè)置滾動(dòng)視窗的初始化最合適。
下面我們就來(lái)創(chuàng)建一個(gè)滾動(dòng)示例程序a:
1、 用AppWizard創(chuàng)建一個(gè)文檔-視圖程序a,注意在第六步時(shí)設(shè)置CAView的基類應(yīng)為CScrollView而不是CView。
2、 在CAView中加入數(shù)據(jù)成員m_rectEllipse和m_nColor。
3、 修改OnInitialUpdate函數(shù)如下:
void CAView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal( 20000, 30000 ); //邏輯窗口大小20×30cm
CSize sizePage( sizeTotal.cx/2, sizeTotal.cy/2 );
CSize sizeLine( sizeTotal.cx/50, sizeTotal.cy/50 );
SetScrollSizes( MM_HIMETRIC, sizeTotal, sizePage, sizeLine );
}
4、 用ClassWizard產(chǎn)生對(duì)消息WM_KEYDOW控制的OnKeyDown函數(shù),并編輯代碼如下:
void CAView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
switch( cChar ){
case VK_HOME:
OnVScroll( SB_TOP, 0, NULL );
OnHScroll( SB_LEFT, 0, NULL );
break;
case VK_END:
OnVScroll( SB_BOTTOM, 0, NULL );
OnHScroll( SB_RIGHT, 0, NULL );
break;
case VK_UP:
OnVScroll( SB_LINEUP, 0, NULL );
break;
case VK_DOWN:
OnVScroll( SB_LINEDOWN, 0, NULL );
break;
case VK_PRIOR:
OnVScroll( SB_PAGEUP, 0, NULL );
break;
case VK_NEXT:
OnVScroll( SB_PAGEDOWN, 0, NULL );
break;
case VK_LEFT:
OnHScroll( SB_LINELEFT, 0, NULL );
break;
case VK_RIGHT( SB_LINERIGHT, 0, NULL );
break;
default:
break;
}
}
5、 編輯構(gòu)造函數(shù)和OnDraw函數(shù)如下:
CAView::CAView():m_rectEllipse( 0, 0, 4000, -4000 )
{
// TODO: add construction code here
m_nColor = GRAY_BRUSH;
}
…
void CAView::OnDraw(CDC* pDC)
{
pDC->SelectStockObject( m_nColor );
pDC->Ellipse( m_rectEllipse );
}
6、 映射WM_LBUTTONDOWN消息并編輯消息處理函數(shù)OnLButtonDown如下:
void CAView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc( this );
OnPrepareDC( &dc );
CRect rectDevice = m_rectEllipse;
dc.LPtoDP( rectDevice );
if( rectDevice.PtInRect( point ) ){
if( m_nColor = GRAY_BRUSH )
m_nColor = WHITE_BRUSH;
else
m_nColor = GRAY_BRUSH;
}
InvalidateRect( rectDevice );
}
編譯并運(yùn)行看看結(jié)果吧。
另外,我們要特別注意下面五種比較特殊的windows消息:
1、 WM_CREATE消息
該消息是windows發(fā)給視圖的第一個(gè)消息,由于應(yīng)用程序框架調(diào)用Create函數(shù)時(shí)該消息就會(huì)被發(fā)送,而此時(shí)窗口創(chuàng)建還未完成,因此在Create函數(shù)內(nèi)不能調(diào)用那些依賴于窗口處于完全激活狀態(tài)的windows函數(shù)。不過(guò)對(duì)于SDI應(yīng)用程序,在視圖生存期間,OnInitialUpdate函數(shù)可以被調(diào)用多次。
2、 WM_CLOSE消息
當(dāng)用戶從系統(tǒng)菜單中關(guān)閉窗口或者父窗口被關(guān)閉時(shí),windows會(huì)發(fā)送WM_CLOSE消息。
3、 WM_QUERYENDSESSION消息
當(dāng)用戶退出windows時(shí),windows就會(huì)發(fā)送WM_QUERYENDSESSION消息給正在運(yùn)行的程序,處理這個(gè)消息的映射函數(shù)為OnQueryEndSession。
4、 WM_DESTROY消息
Windows在發(fā)送完WM_CLOSE消息后,緊接著就發(fā)送WM_DESTROY消息,消息映射函數(shù)為OnDestroy。當(dāng)程序接收到該消息時(shí),它將假定此時(shí)視窗已經(jīng)消失,但仍處于活動(dòng)狀態(tài)。利用這個(gè)消息控制函數(shù),就可以對(duì)依賴于當(dāng)前窗口的所有東西作清除工作,不過(guò)一定要記住,應(yīng)該用基類的OnDestroy而不能在自己視圖中的OnDestroy中"終止"窗口的析構(gòu)過(guò)程,終止析構(gòu)的處理應(yīng)該在OnClose函數(shù)中。
5、 WM_NCDESTROY消息
當(dāng)窗口被取消時(shí)發(fā)送的最后一個(gè)消息就是這個(gè)消息,由于此時(shí)所有的窗口都被關(guān)閉,所以我們可以在OnNcDestroy函數(shù)中做一些不依賴于窗口是否處于激活狀態(tài)的最后處理工作,不過(guò)一定要調(diào)用基類的OnNcDestroy函數(shù)。不要在OnNcDestroy中取消動(dòng)態(tài)申請(qǐng)的窗口對(duì)象,這一工作是由CWnd的一個(gè)特殊虛函數(shù)PostNcDestroy來(lái)完成的,它是由基類的OnNcDestroy來(lái)調(diào)用的。何時(shí)取消窗口對(duì)象最為合適呢,去看MFC的聯(lián)機(jī)文檔吧!
第八天 設(shè)備環(huán)境類
任何程序在畫(huà)圖時(shí)都需要調(diào)用圖形設(shè)備接口( GDI )函數(shù), GDI 包含了一些繪制點(diǎn)、線、矩形、橢圓、位圖以及文本的函數(shù)。 Windows 的設(shè)備環(huán)境是 GDI 的關(guān)鍵元素,它代表了物理設(shè)備,每一個(gè) C++ 設(shè)備環(huán)境對(duì)象都有與之對(duì)應(yīng)的 Windows 設(shè)備環(huán)境,并通過(guò)一個(gè) 32 位的 HDC 句柄來(lái)標(biāo)識(shí)。
MFC 中的基類 CDC 包含了繪圖所需要的所有成員函數(shù),并且除了 CMetaFileDC 類外,所有的派生類都只有構(gòu)造函數(shù)和析構(gòu)函數(shù)不同。對(duì)于顯示器來(lái)說(shuō),常用的派生類有 CClientDC 和 CWindowDC 。
顯示設(shè)備環(huán)境的類 CClientDC 和 CWindowDC , CClientDC 類繪圖只局限于客戶區(qū)域內(nèi),即不包含邊框、菜單欄和標(biāo)題欄,而 CWindowDC 類可以。簡(jiǎn)單來(lái)說(shuō),如果創(chuàng)建 CclientDC 對(duì)象,點(diǎn)( 0,0 )指客戶區(qū)域的左上角,如果創(chuàng)建的是 CWindowDC 對(duì)象,則點(diǎn)( 0,0 )指整個(gè)屏幕的左上角。
在創(chuàng)建 CDC 對(duì)象的時(shí)候,不要忘記在合適的時(shí)候?qū)⑺鼊h除,不然程序在退出之前有小部分內(nèi)存就會(huì)丟失。要保證設(shè)備環(huán)境對(duì)象能夠被適時(shí)的刪除,可以有兩種方法:
一種是在堆棧中構(gòu)造對(duì)象,比如在 OnLButtonDown 函數(shù)中,它的析構(gòu)函數(shù)在函數(shù)返回時(shí)自動(dòng)被調(diào)用。
void CMyView::OnLButtonDown(UINT nFlags,CPoint point){
CRect rect;
CClientDC dc(this); //constructs dc on the stack
…
} //dc automatically destroyed
另一種是通過(guò)調(diào)用 CWnd 的成員函數(shù) GetDC 來(lái)獲得設(shè)備環(huán)境指針,但此時(shí)必須要調(diào)用 RleaseDC 來(lái)釋放設(shè)備環(huán)境。
void CMyView::OnLButtonDown(UINT nFlags,CPoint point){
CRect rect;
CDC *pDC=GetDC();
pDC->GetClipBox(rect);
ReleaseDC(pDC); // 不要忘了這句
}
注意:千萬(wàn)不要?jiǎng)h除作為參數(shù)以指針形式傳遞給 OnDraw 函數(shù)的 CDC 對(duì)象,應(yīng)用程序框架會(huì)自動(dòng)控制它的刪除。
在繪圖時(shí)我們離不開(kāi)設(shè)備環(huán)境,那么在繪圖時(shí)我們就要依賴于設(shè)備環(huán)境的當(dāng)前狀態(tài),這種狀態(tài)包括:
• 被選中的 GDI 繪圖對(duì)象,如筆、刷子和字體等
• 繪圖時(shí)的縮放尺寸的映射模式
• 其他各種細(xì)節(jié),如文本的對(duì)齊方式,多邊形的填充狀態(tài)
創(chuàng)建設(shè)備環(huán)境對(duì)象時(shí),通常會(huì)有些默認(rèn)的特性,而其他特性都是通過(guò) CDC 類的成員函數(shù)來(lái)設(shè)定的,可以通過(guò)重載 SelectObject 函數(shù)來(lái)將 GDI 對(duì)象選進(jìn)設(shè)備環(huán)境中。
如果我們要重新編寫 OnPaint 函數(shù),就需要使用 CPaintDC 類,這個(gè)類是比較特殊的,它的構(gòu)造函數(shù)和析構(gòu)函數(shù)所完成的工作都是針對(duì)顯示用的,當(dāng)我們一旦獲得一個(gè) CDC 指針,就可以把它當(dāng)成任何設(shè)備環(huán)境指針來(lái)用。
第九天 GDI對(duì)象
所有 GDI 對(duì)象類都是由抽象基類 CGdiObject 派生出來(lái)的。下面是 GDI 派生類列表:
CBitmap - 位圖是一種位矩陣,每一個(gè)顯示像素都對(duì)應(yīng)一個(gè)或多個(gè)位,我們可以用位圖來(lái)表示圖像,也可以用它來(lái)創(chuàng)建刷子。
CBrush - 刷子定義了一種位圖形式的像素,用它可以對(duì)區(qū)域內(nèi)部填充顏色。
CFont - 字體是一種具有某種風(fēng)格和尺寸的所有字符的集合。
CPalette - 調(diào)色板是一種顏色映射接口。
CPen - 筆是一種畫(huà)線和有形邊框的工具,可以指定畫(huà)線的寬度,以及畫(huà)虛線,實(shí)線等。
CRgn - 區(qū)域是一種范圍,可以用它來(lái)填充、裁剪以及鼠標(biāo)點(diǎn)中測(cè)試。
我們只需要構(gòu)造 CGdiObject 類的派生類對(duì)象,而無(wú)需構(gòu)造它的對(duì)象,有些 GDI 派生類允許構(gòu)造函數(shù)一步完成創(chuàng)建對(duì)象的任務(wù),如 CPen 和 CBrush 。而有些派生類的對(duì)象要兩步,如 CFont 和 CRgn ,首先要調(diào)用默認(rèn)的構(gòu)造函數(shù),然后還要調(diào)用相應(yīng)的創(chuàng)建函數(shù),如 CreateFont 、 CreatePolygonRgn 等。
CGdiObject 類有一個(gè)虛析構(gòu)函數(shù),如果構(gòu)造了一個(gè)它的派生類的對(duì)象,則在程序退出之前要將其刪除,為了刪除它,要先將其從設(shè)備環(huán)境中分離出來(lái)。那么如何分離呢?其實(shí), CDC 類的 SelectObject 成員函數(shù)在將 GDI 對(duì)象選進(jìn)設(shè)備環(huán)境的同時(shí),它已經(jīng)從設(shè)備環(huán)境中分離出來(lái)了,但在未選中新的對(duì)象前,還不能將舊的對(duì)象分離。所以在選進(jìn)自己的 GDI 對(duì)象時(shí),將原來(lái)的 GDI 對(duì)象也保存起來(lái),任務(wù)完成后,再將其恢復(fù),這樣就可以將自己的 GDI 對(duì)象分離并刪除了。下面看一個(gè)例子:
void CMyView::OnDraw( CDC *pDC ){
CPen newPen( PS_DASHDOTDOT, 2, (COLORREF)0); //black 2 pixels wide
CPen * pOldPen = pDC->SelectObject( &newPen );
pDC->MoveTo( 10, 10 );
pDC->LineTo( 110, 10 );
pDC->SelectObject( pOldPen ); //newPen 被分離
} //newPen 在函數(shù)退出時(shí)自動(dòng)刪除
對(duì)于一些庫(kù)存的 GDI 對(duì)象,由于它們是 windows 系統(tǒng)的一部分,因此我沒(méi)有必要?jiǎng)h除它們。 MFC 庫(kù)函數(shù) SelectStockObject 可以將一個(gè)庫(kù)存對(duì)象選進(jìn)設(shè)備環(huán)境中,并返回原先被選中對(duì)象的指針,同時(shí)使該對(duì)象被分離。在上例中,我們就可以用庫(kù)存對(duì)象代替“舊”對(duì)象:
void CMyView::OnDraw( CDC *pDC ){
CPen newPen( PS_DASHDOTDOT, 2, (COLORREF)0); //black 2 pixels wide
pDC->MoveTo( 10, 10 );
pDC->LineTo( 110, 10 );
pDC->SelectStockObject( BLACK_PEN ); //newPen 被分離
} //newPen 在函數(shù)退出時(shí)自動(dòng)刪除
對(duì)于顯示設(shè)備環(huán)境來(lái)說(shuō),在每個(gè)消息控制函數(shù)的入口處,設(shè)備環(huán)境都是未被初始化的,因此每次都必須從頭開(kāi)始設(shè)置設(shè)備環(huán)境,由于 SelectObject 返回的 GDI 對(duì)象指針的臨時(shí)性,而應(yīng)用程序框架在函數(shù)返回時(shí)會(huì)刪除 C++ 臨時(shí)對(duì)象指針,所以不能簡(jiǎn)單地將設(shè)備環(huán)境指針保存在類的數(shù)據(jù)成員中,而要借助于 GetSafeHandle 成員函數(shù)來(lái)將它轉(zhuǎn)換為 windows 句柄(唯一能夠持久存在的 GDI 標(biāo)識(shí))。
注意,當(dāng)刪除由 SelectObject 返回的指針?biāo)赶虻膶?duì)象時(shí),一定要當(dāng)心,如果該對(duì)象是我們自己申請(qǐng)的,可以刪除,如果是臨時(shí)的,則不能隨便刪除。 |