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