posts - 195, comments - 34, trackbacks - 0, articles - 1

          『VC++技術(shù)內(nèi)幕』(第四版)讀書筆記

          關(guān)鍵字:VC++
          原作者姓名:loose_went
          文章原出處:vczx.com

          寫在前面:

            站長所看的『VC++技術(shù)內(nèi)幕』版本為--潘愛民和王國印譯清華大學(xué)出版的第四版,因有時(shí)工作忙碌,不能及時(shí)更新,請大家見諒!


          第一天 Windows的編程模式 第二天 MFC應(yīng)用程序框架
          第三天 消息映射和視圖類 第四天 資源和編譯
          第五天 基本事件處理 第六天 映射模式
          第七天 滾動(dòng)視窗 第八天 社備環(huán)境類
          第九天 GDI對象 第十天 windows顏色映射

          第一天 Windows的編程模式

            Windows程序中必須要有WinMain函數(shù),因?yàn)樵摵瘮?shù)最重要的任務(wù)是創(chuàng)建該應(yīng)用程序的主窗口。Windows程序與基于MS-DOS程序的最大差別就在于:MS-DOS程序是通過調(diào)用操作系統(tǒng)的功能來獲得用戶輸入的,而Windows程序是通過操作系統(tǒng)發(fā)送的消息來處理用戶輸入的。Windows消息都是經(jīng)過嚴(yán)格定義的,并且適用于所有的程序。
            WINDOWS提供通用的圖形設(shè)備接口(GUI),我們通過調(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é)合起來生成可執(zhí)行程序。資源文件可以包括位圖、圖標(biāo)、菜單定義、對話框設(shè)計(jì),甚至可以包含用戶自己定義的格式。
            Windows程序允許動(dòng)態(tài)的連接目標(biāo)模塊,并且多個(gè)應(yīng)用程序可以共享同一個(gè)動(dòng)態(tài)連接庫。
          VC++的源程序?yàn)g覽器能夠使我們從類或函數(shù)的角度來了解或編輯程序,而不是直接從文件入手。在看別人的源代碼時(shí)如果能熟練的使用源代碼瀏覽器將會(huì)事半功倍。源程序?yàn)g覽器主要的查看狀態(tài)有以下幾種:
          Definitions and References--選擇任何函數(shù)、變量、類型、宏定義可以看到它在項(xiàng)目中的定義,并且在何處和什么地方用到它。
          Call Graph/Caller Graph--對于所選擇的函數(shù),給出它的調(diào)用與被調(diào)用函數(shù)的圖示。
          Derived Class Graph/Base Class Graph--給出類層次關(guān)系的圖形表示,可以看到所選擇的類的派生類和基類以及成員。
          File Outline--對于所選的文件,列出文件中的類、函數(shù)和數(shù)據(jù)成員,同時(shí)還顯示它們定義的位置和使用位置。
            可見Source Brower比起Class View來功能多了很多也更加好用。
            對于本章學(xué)習(xí)loose_went建議大家在VC++6中用AppWizard生成一個(gè)空的程序,然后試著看看都有哪些文件,和他們的類層次、函數(shù)、宏、結(jié)構(gòu)的定義,我就是這樣干的,學(xué)編程不動(dòng)手是不行的。


          Top of Page

          第二天 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庫應(yīng)用程序框架的功能非常豐富。
            以上說的都是MFC庫的優(yōu)點(diǎn),雖然說MFC有著這樣多的優(yōu)點(diǎn),但我個(gè)人認(rèn)為不能盲目的學(xué)習(xí)它,要想學(xué)好,那么您必須先掌握C++,這是毋庸置疑的。可能剛開始的時(shí)候,您覺得收獲很大,也很有趣,但要進(jìn)一步提高,沒有C++基礎(chǔ)是很難的。所以站長建議大家學(xué)習(xí)的時(shí)候要有先有后,這樣才能學(xué)好!
          應(yīng)用程序框架是一種類庫的超集。
            我們現(xiàn)在先來看一個(gè)例子,看看MFC有多么強(qiáng)大!您只需加一行代碼,甚至一行都不用加只需要點(diǎn)幾下鼠標(biāo)就可以創(chuàng)建一個(gè)windows 程序,不信,試一下:
          1、打開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
          }
            完了,就這么簡單。編譯運(yùn)行。看到了嗎?這個(gè)程序具備WINDOWS程序的所有特性,例如有菜單、工具條、狀態(tài)欄、最大化、關(guān)閉、甚至還有關(guān)于對話框、打印預(yù)覽.....全了,這就是AppWizard通過MFC動(dòng)態(tài)創(chuàng)建的一個(gè)應(yīng)用程序。從這個(gè)小例子可以看出用VC/MFC設(shè)計(jì)WINDOWS程序多么方便。

          下面我們看看書上的例子,以便更進(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對象
          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ò)。為什么呢?原來還沒有添加MFC的支持,在Project Setting選項(xiàng)General屬性頁選擇"Use MFC in a Static Library"
          5、再按Ctrl+F5,怎么樣,簡單吧?
          讓我們看看這個(gè)程序中的一些元素。
          ①WinMain函數(shù):Windows總是要求每個(gè)應(yīng)用程序都要有WinMain函數(shù)的,您之所以看不見,是因?yàn)樗呀?jīng)隱藏在應(yīng)用程序框架內(nèi)部了。
          ②CMyApp類:CMyApp類的對象代表一個(gè)應(yīng)用程序,CWinApp基類決定它的大部分行為。
          ③應(yīng)用程序的啟動(dòng):當(dāng)開始運(yùn)行應(yīng)用程序時(shí)WINDOWS會(huì)調(diào)用WinMain函數(shù),WinMain會(huì)查找該應(yīng)用程序的全局對象theApp。
          ④CMyApp::InitInstance成員函數(shù):發(fā)現(xiàn)theApp后自動(dòng)調(diào)用重載的虛函數(shù)InitInstance來完成主窗口的構(gòu)造和顯示工作。
          ⑤CWinApp::Run成員函數(shù):WinMain在調(diào)用InitInstance之后緊接著調(diào)用Run函數(shù),它被隱藏在基類中負(fù)責(zé)傳遞應(yīng)用程序的消息給相映的窗口。
          ⑥CMyFrame類:此類的對象代表著應(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!"被顯示,你可以試著將語句:
          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!"不見了。
          ⑧關(guān)閉應(yīng)用程序:用戶關(guān)閉應(yīng)用程序時(shí)會(huì)有一系列事件發(fā)生。首先CMyFrame對象被刪除,然后退出Run,進(jìn)而退出WinMain,最后刪除CMyApp對象。
            通過上面的示例我們看見程序的大部分功能包含在基類CWinApp和CFrameWnd中,我們只寫了很少的函數(shù),便可以完成很復(fù)雜的功能。所以應(yīng)用程序框架不僅僅是一種類庫,它還定義了應(yīng)用程序的結(jié)構(gòu),除了基類外還包括WinMain函數(shù),以及用來支持消息處理、診斷、DLL、等都包含在應(yīng)用程序框架中。


          Top of Page

          第三天 消息映射和視圖類

            MFC庫應(yīng)用程序框架沒有采用虛函數(shù)來處理windows消息,而是通過宏將消息映射到派生類相應(yīng)的成員函數(shù)上。文檔-視圖結(jié)構(gòu)是應(yīng)用程序框架的核心,它把數(shù)據(jù)從用戶對數(shù)據(jù)的觀察中分離出來,這樣做最大的好處就是同一個(gè)數(shù)據(jù)可以對應(yīng)多個(gè)視圖。比如同一個(gè)股票報(bào)價(jià)數(shù)據(jù),既可以有報(bào)表觀察窗口,也可以有圖形觀察窗口,明白了否?
            視圖簡單來說就是一個(gè)普通的窗口,對于程序員來說就是一個(gè)從MFC庫中Cview類派生出來的類的一個(gè)對象。視圖類分為兩個(gè)源文件模塊:頭文件(H)和源代碼文件(CPP)。
          用Appwizard創(chuàng)建一個(gè)SDI應(yīng)用程序,產(chǎn)生了如下文件(假設(shè)工程名為Exc01):
          Exc01.dsp 項(xiàng)目文件,Visual Studio用它來創(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)目的哪些文件是打開的,又是如何排序的
          Readme.txt 用來解釋所產(chǎn)生的所有文件的文本文件
          Resource.h 包含#define常量定義的頭文件
            從Exc01View.cpp和Exc01View.h的代碼中可以看出,這兩個(gè)文件已經(jīng)完全定義了CExc01View類,而該類正是此應(yīng)用程序的核心。CExc01View類的對象與應(yīng)用程序的視窗相關(guān)聯(lián),應(yīng)用程序的所有"動(dòng)作"都會(huì)在這個(gè)視窗中顯示出來。
            CExc01View類的兩個(gè)最重要的基類是CWnd和CView類。CWnd類提供了CExc01View的窗口屬性,而CView類則提供了它和應(yīng)用程序框架的其它部分之間的聯(lián)系,特別是和文檔以及框架窗口之間的聯(lián)系。這一點(diǎn)一定要記住。
            下面我們來看一下如何在視窗內(nèi)繪圖。最重要的一個(gè)函數(shù)是OnDraw()函數(shù),它是一個(gè)虛函數(shù),每次窗口被重畫時(shí),應(yīng)用程序都要先調(diào)用這個(gè)函數(shù)。注意:盡管可以隨時(shí)對窗口繪制,但最好還是等變化內(nèi)容積累到一定程度后再教給OnDraw()函數(shù)處理,這樣效率會(huì)高一些。
          在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));
          再編譯運(yùn)行,看到了什么?
            TextOut和Ellipse都是設(shè)備環(huán)境類CDC的成員函數(shù),MFC庫提供了一個(gè)用來表示windows矩形的類CRect,在這里CRect的一個(gè)臨時(shí)對象被作為參數(shù)傳遞給 了Ellipse函數(shù),當(dāng)外接矩形的寬和高相等時(shí),Ellipse函數(shù)就畫出個(gè)圓。


          Top of Page

          第四天 資源和編譯

            資源文件(就是以應(yīng)用程序名和擴(kuò)展名是.rc的文件)很大程度上決定了應(yīng)用程序的用戶界面。在VC++中資源文件包括以下內(nèi)容:
          Accelerator //模擬菜單和工具欄選擇的鍵盤定義
          Dialog //對話框的布局及內(nèi)容
          Icon //圖標(biāo)有兩種一種是16X16一種是32X32。
          Menu //應(yīng)用程序的主菜單及所屬的彈出式菜單
          String table //一些字符串,不屬于C++源代碼部分
          Toolbar //工具條。
          Version //程序的描述、版本號(hào)、支持語言信息。
            除了以上信息,.rc文件還包含了以下語句: #include "afxres.h" #include "afxres.rc" 它們的作用是把適合于所有應(yīng)用程序的一些通用MFC庫資源包含進(jìn)來,其中包括字符串、圖形按鈕以及打印所需的一些元素。
            關(guān)于資源編輯器的使用就不多說了,因?yàn)樗牟僮骱芎唵危枰⒁獾氖请m然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不對源代碼進(jìn)行調(diào)試,不考慮MFC的診斷宏,使用的是MFC Release庫,編譯十對應(yīng)用程序的速度進(jìn)行優(yōu)化,而Debug Build則正好相反,它允許對源代碼進(jìn)行調(diào)試,可以定義和使用MFC的診斷宏,采用MFC Debug庫,對速度沒有優(yōu)化。所以我們應(yīng)該在Debug模式下開發(fā)應(yīng)用程序,然后在Release模式下發(fā)布應(yīng)用程序。在我們的工程文件夾下會(huì)有一個(gè)Debug文件夾和一個(gè)Release文件夾分別存放輸出文件和中間文件。
            診斷宏是我們編譯程序時(shí)檢測程序狀態(tài)的有利工具,例如上兩篇用到的TRACE宏,可以在Debug窗口獲得你需要的診斷信息,而不用設(shè)置對話框之類的方法,在發(fā)布時(shí)Release會(huì)自動(dòng)濾掉此信息。
            為了更好的管理項(xiàng)目,最好理解系統(tǒng)是如何處理預(yù)編譯頭文件的。VC++有兩個(gè)預(yù)編譯系統(tǒng):自動(dòng)的和手工的。這一部分筆者就不多說了,建議讀者好好看看。


          Top of Page

          第五天 基本事件處理

            用戶在視窗中的任何一個(gè)操作,都會(huì)引起Windows自動(dòng)發(fā)送一個(gè)消息給該視窗。我們以一個(gè)例子來說明:比如我們在視窗中按下鼠標(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
            最后,在類庫頭文件中包含如下語句:
          DECLARE_MESSAGE_MAP()
            以上這些步驟,我們都可以借助于ClassWizard來完成。這就是消息映射的過程。

            MFC庫對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)建完成,不可見,因此在消息控制函數(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)閉窗口時(shí),系統(tǒng)會(huì)發(fā)送WM_CLOSE消息。如果派生類重新定義了OnClose函數(shù),就可以完全控制關(guān)閉過程,可以將提醒用戶存盤之類的工作放在這里完成。我們可以通過重載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ù)對消息進(jìn)行處理。在它之后應(yīng)該是WM_ENDSESSION 消息。
          WM_DESTROY
            在Windows發(fā)送WM_CLOSE消息后,緊接著會(huì)發(fā)送WM_DESTROY消息,雖然窗口已經(jīng)Close但實(shí)際上并沒有完全清除,在任務(wù)管理器中還可以看見應(yīng)用程序的進(jìn)程(我想很多木馬或病毒都是無窗口的程序,它們的做法是生成了已經(jīng)活動(dòng)狀態(tài)的窗口但不顯示出來),利用這個(gè)消息控制函數(shù)便可以對依賴于當(dāng)前窗口存在的東西做清除工作,不過一定要注意,應(yīng)該調(diào)用基類的OnDestroy函數(shù),而不能在用戶自己的視圖的OnDestroy函數(shù)中終止窗口的析構(gòu)過程,終止析構(gòu)過程應(yīng)該在OnClose函數(shù)中。
          WM_NCDESTROY
            當(dāng)窗口被取消所發(fā)送的最后一個(gè)消息就是這個(gè)消息。我們可以在OnNcDestroy函數(shù)中做一些不依賴該窗口是否處于活動(dòng)狀態(tài)的最后的處理工作,(我實(shí)在想不出還需要做什么?那位朋友能給個(gè)例子),注意一定要調(diào)用基類中的OnNcDestroy函數(shù)。

            MFC庫中非靜態(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í)在處理過程中,還需要對每個(gè)類指定窗口過程。每次應(yīng)用程序調(diào)用CreateWindow建立一個(gè)窗口時(shí),都要指定一個(gè)窗口類作為參數(shù),這樣就把新建立的窗口和窗口過程函數(shù)連接起來了,每次windows給窗口發(fā)送消息的時(shí)候,這個(gè)函數(shù)就會(huì)被調(diào)用,以檢查用參數(shù)傳進(jìn)來的消息碼。

          Top of Page

          第六天 映射模式

            所謂映射模式,說白了就是坐標(biāo)系。在默認(rèn)情況下,Windows所繪圖像單位為像素,這是因?yàn)樵O(shè)備環(huán)境用了默認(rèn)的映射模式MM_TEXT,所以如下語句所繪圖形為長和寬都為200像素的方塊:   pDC->Rectangle(CRect(0,0,200,200));
            那么我們要繪制一個(gè)長和寬都是4厘米的方塊該怎么做呢?這就必須改變設(shè)備環(huán)境的默認(rè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ù)來改變坐標(biāo)原點(diǎn)的位置,下面的代碼就是把坐標(biāo)原點(diǎn)設(shè)在了(100,100)處,畫了一個(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ī)則,而且我們無法將其改變。固定比例模式之間唯一的差別就在于實(shí)際的比例因子。下表列出了影視模式和比例因子的對應(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的比例因子,即圓可以變成扁圓。
            以上就是常見的映射模式,筆者建議:我們沒必要死記住這些模式,只是到用的時(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ù)
          ③ 所有選中測試都應(yīng)該選用設(shè)備坐標(biāo),區(qū)域的定義應(yīng)采用設(shè)備坐標(biāo),某些像CRect::PtInRect之類的函數(shù)只有采用設(shè)備坐標(biāo)才能有正確的結(jié)果
          ④ 將一些長期使用的值用邏輯坐標(biāo)來保存,如果用設(shè)備坐標(biāo),那么只要用戶對窗口進(jìn)行一下滾動(dòng),坐標(biāo)就不再有效了
            一般情況下,我們在CView的虛函數(shù)OnPrepareDC中設(shè)置映射模式,應(yīng)用程序框架在調(diào)用OnDraw函數(shù)之前調(diào)用這個(gè)虛函數(shù)。


          Top of Page

          第七天 滾動(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)視窗的初始化最合適。
            下面我們就來創(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)生對消息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ù)。不過對于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ù),就可以對依賴于當(dāng)前窗口的所有東西作清除工作,不過一定要記住,應(yīng)該用基類的OnDestroy而不能在自己視圖中的OnDestroy中"終止"窗口的析構(gòu)過程,終止析構(gòu)的處理應(yīng)該在OnClose函數(shù)中。
          5、 WM_NCDESTROY消息
          當(dāng)窗口被取消時(shí)發(fā)送的最后一個(gè)消息就是這個(gè)消息,由于此時(shí)所有的窗口都被關(guān)閉,所以我們可以在OnNcDestroy函數(shù)中做一些不依賴于窗口是否處于激活狀態(tài)的最后處理工作,不過一定要調(diào)用基類的OnNcDestroy函數(shù)。不要在OnNcDestroy中取消動(dòng)態(tài)申請的窗口對象,這一工作是由CWnd的一個(gè)特殊虛函數(shù)PostNcDestroy來完成的,它是由基類的OnNcDestroy來調(diào)用的。何時(shí)取消窗口對象最為合適呢,去看MFC的聯(lián)機(jī)文檔吧!

          Top of Page

          第八天 設(shè)備環(huán)境類

            任何程序在畫圖時(shí)都需要調(diào)用圖形設(shè)備接口( GDI )函數(shù), GDI 包含了一些繪制點(diǎn)、線、矩形、橢圓、位圖以及文本的函數(shù)。 Windows 的設(shè)備環(huán)境是 GDI 的關(guān)鍵元素,它代表了物理設(shè)備,每一個(gè) C++ 設(shè)備環(huán)境對象都有與之對應(yīng)的 Windows 設(shè)備環(huán)境,并通過一個(gè) 32 位的 HDC 句柄來標(biāo)識(shí)。

            MFC 中的基類 CDC 包含了繪圖所需要的所有成員函數(shù),并且除了 CMetaFileDC 類外,所有的派生類都只有構(gòu)造函數(shù)和析構(gòu)函數(shù)不同。對于顯示器來說,常用的派生類有 CClientDC 和 CWindowDC 。

            顯示設(shè)備環(huán)境的類 CClientDC 和 CWindowDC , CClientDC 類繪圖只局限于客戶區(qū)域內(nèi),即不包含邊框、菜單欄和標(biāo)題欄,而 CWindowDC 類可以。簡單來說,如果創(chuàng)建 CclientDC 對象,點(diǎn)( 0,0 )指客戶區(qū)域的左上角,如果創(chuàng)建的是 CWindowDC 對象,則點(diǎn)( 0,0 )指整個(gè)屏幕的左上角。

            在創(chuàng)建 CDC 對象的時(shí)候,不要忘記在合適的時(shí)候?qū)⑺鼊h除,不然程序在退出之前有小部分內(nèi)存就會(huì)丟失。要保證設(shè)備環(huán)境對象能夠被適時(shí)的刪除,可以有兩種方法:

          一種是在堆棧中構(gòu)造對象,比如在 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

          另一種是通過調(diào)用 CWnd 的成員函數(shù) GetDC 來獲得設(shè)備環(huán)境指針,但此時(shí)必須要調(diào)用 RleaseDC 來釋放設(shè)備環(huán)境。

          void CMyView::OnLButtonDown(UINT nFlags,CPoint point){

          CRect rect;

          CDC *pDC=GetDC();

          pDC->GetClipBox(rect);

          ReleaseDC(pDC); // 不要忘了這句

          }

          注意:千萬不要?jiǎng)h除作為參數(shù)以指針形式傳遞給 OnDraw 函數(shù)的 CDC 對象,應(yīng)用程序框架會(huì)自動(dòng)控制它的刪除。

          在繪圖時(shí)我們離不開設(shè)備環(huán)境,那么在繪圖時(shí)我們就要依賴于設(shè)備環(huán)境的當(dāng)前狀態(tài),這種狀態(tài)包括:

          •  被選中的 GDI 繪圖對象,如筆、刷子和字體等

          •  繪圖時(shí)的縮放尺寸的映射模式

          •  其他各種細(xì)節(jié),如文本的對齊方式,多邊形的填充狀態(tài)

            創(chuàng)建設(shè)備環(huán)境對象時(shí),通常會(huì)有些默認(rèn)的特性,而其他特性都是通過 CDC 類的成員函數(shù)來設(shè)定的,可以通過重載 SelectObject 函數(shù)來將 GDI 對象選進(jìn)設(shè)備環(huán)境中。

            如果我們要重新編寫 OnPaint 函數(shù),就需要使用 CPaintDC 類,這個(gè)類是比較特殊的,它的構(gòu)造函數(shù)和析構(gòu)函數(shù)所完成的工作都是針對顯示用的,當(dāng)我們一旦獲得一個(gè) CDC 指針,就可以把它當(dāng)成任何設(shè)備環(huán)境指針來用。

          Top of Page

          第九天 GDI對象

            所有 GDI 對象類都是由抽象基類 CGdiObject 派生出來的。下面是 GDI 派生類列表:

            CBitmap - 位圖是一種位矩陣,每一個(gè)顯示像素都對應(yīng)一個(gè)或多個(gè)位,我們可以用位圖來表示圖像,也可以用它來創(chuàng)建刷子。

          CBrush - 刷子定義了一種位圖形式的像素,用它可以對區(qū)域內(nèi)部填充顏色。

          CFont - 字體是一種具有某種風(fēng)格和尺寸的所有字符的集合。

          CPalette - 調(diào)色板是一種顏色映射接口。

          CPen - 筆是一種畫線和有形邊框的工具,可以指定畫線的寬度,以及畫虛線,實(shí)線等。

          CRgn - 區(qū)域是一種范圍,可以用它來填充、裁剪以及鼠標(biāo)點(diǎn)中測試。

            我們只需要構(gòu)造 CGdiObject 類的派生類對象,而無需構(gòu)造它的對象,有些 GDI 派生類允許構(gòu)造函數(shù)一步完成創(chuàng)建對象的任務(wù),如 CPen 和 CBrush 。而有些派生類的對象要兩步,如 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è)它的派生類的對象,則在程序退出之前要將其刪除,為了刪除它,要先將其從設(shè)備環(huán)境中分離出來。那么如何分離呢?其實(shí), CDC 類的 SelectObject 成員函數(shù)在將 GDI 對象選進(jìn)設(shè)備環(huán)境的同時(shí),它已經(jīng)從設(shè)備環(huán)境中分離出來了,但在未選中新的對象前,還不能將舊的對象分離。所以在選進(jìn)自己的 GDI 對象時(shí),將原來的 GDI 對象也保存起來,任務(wù)完成后,再將其恢復(fù),這樣就可以將自己的 GDI 對象分離并刪除了。下面看一個(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)刪除

            對于一些庫存的 GDI 對象,由于它們是 windows 系統(tǒng)的一部分,因此我沒有必要?jiǎng)h除它們。 MFC 庫函數(shù) SelectStockObject 可以將一個(gè)庫存對象選進(jìn)設(shè)備環(huán)境中,并返回原先被選中對象的指針,同時(shí)使該對象被分離。在上例中,我們就可以用庫存對象代替“舊”對象:

          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)刪除

            對于顯示設(shè)備環(huán)境來說,在每個(gè)消息控制函數(shù)的入口處,設(shè)備環(huán)境都是未被初始化的,因此每次都必須從頭開始設(shè)置設(shè)備環(huán)境,由于 SelectObject 返回的 GDI 對象指針的臨時(shí)性,而應(yīng)用程序框架在函數(shù)返回時(shí)會(huì)刪除 C++ 臨時(shí)對象指針,所以不能簡單地將設(shè)備環(huán)境指針保存在類的數(shù)據(jù)成員中,而要借助于 GetSafeHandle 成員函數(shù)來將它轉(zhuǎn)換為 windows 句柄(唯一能夠持久存在的 GDI 標(biāo)識(shí))。

            注意,當(dāng)刪除由 SelectObject 返回的指針?biāo)赶虻膶ο髸r(shí),一定要當(dāng)心,如果該對象是我們自己申請的,可以刪除,如果是臨時(shí)的,則不能隨便刪除。


          主站蜘蛛池模板: 麻江县| 卢氏县| 鄱阳县| 左贡县| 霍林郭勒市| 琼结县| 新源县| 镇宁| 喀什市| 银川市| 三台县| 从江县| 荆门市| 桓台县| 平谷区| 镇巴县| 新蔡县| 桓仁| 合阳县| 铜山县| 黄陵县| 乌拉特前旗| 全州县| 靖州| 杭锦旗| 阳山县| 嘉义县| 鄂尔多斯市| 陆川县| 丹棱县| 晋州市| 赞皇县| 大化| 泾源县| 陕西省| 从江县| 泰安市| 姜堰市| 沿河| 新龙县| 永康市|