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