Windows編程 第五回 GDI初窺

來源:互聯網
上載者:User

-----路過的朋友,若發現錯誤或有好的建議,歡迎在下面留言,謝謝!-----

終於又見面了

      隔了好一陣子,終於又和大家見面了。最近我有點忙,忙得已經好幾周沒看過電影了,不過我喜歡這種感覺,這讓我過的充實,過的問心無愧。我最近喜歡寫東西,因為每當我提筆寫字或在鍵盤上碼字時,我就能靜下浮躁的心來學習思考,看不下去的書可以看得下去。我總是靠寫讀書筆記來迫使我自己讀書,感覺這方法不錯,如果你看不下書或感到浮躁時,你可以試試。書這東西不管你喜不喜歡,還是要多讀的。有句話叫什麼來的?書到用時方恨少!

GDI?何方神聖?

      GDI是Graphics Device Interface(圖形裝置介面)的簡稱,當Windows應用程式需要顯示點、線、映像、文字等內容,在顯示器或印表機輸出這些內容時,就需要使用到GDI。圖形裝置介面是Windows圖形介面的基礎。正如你所認為的那樣,GDI是Windows非常重要的部分。當然GDI不是可以實現這種功能的唯一程式設計介面,GDI只是其中最基本的。除了GDI外,還有GDI+、OpenGL、DirectX、Windows Image Acquisition等可以實作類別似或更進階的功能。

GDI的意義在於將程式對圖形介面的操作和硬體裝置隔絕開來,即支援與裝置無關的圖形,在程式中可以將所有的圖形裝置都看成是虛擬設備,包括視頻顯示器和印表機等,然後通過GDI函數用同樣的方法去操作它們,由Windows負責將函數調用轉化成針對具體硬體的操作。只要一個裝置提供了和Windows相容的驅動程式,它就可以被看做是一個標準的裝置。GDI的出現使程式員無需要關心硬體裝置及裝置驅動,就可以將應用程式的輸出轉化為硬體裝置上的輸出,實現了程式開發人員與硬體裝置的隔離,大大方便了開發工作。

三探GDI

      從程式員的觀點來看,GDI由很多個函數調用和一些相關的資料類型、宏和結構組成。不幸的是,如果要對GDI進行全面的講述,將需要一大本書的內容。為此,我們對GDI操作可以從3個方面去簡要瞭解——When, Where和How:

When——指的是進行圖形操作的時機,究竟什麼時刻最適合程式進行圖形操作呢?

Where——指的是圖形該往哪裡畫,既然Windows隔離了硬體圖形裝置,那麼該把什麼地方當做“下筆”的地方呢?

How——瞭解了上面兩個問題後,最後還要知道“如何畫”,這就涉及如何使用大部分GDI函數的問題了。

一、When——WM_PAINT訊息

1、客戶區的重新整理

     正如上面所說的,這裡討論的是“When”的問題,讀者可能會問:為什麼會有這個問題,如果要向視窗輸出圖形,程式想在什麼時候輸出那就是什麼時候,難道這個時刻還有規定不成?

     是的,在Windows作業系統中,螢幕是多個程式“公用”的,使用者程式不要指望輸出到視窗中的內容經過一段時間後還會保留在那裡,它們可能被別的東西覆蓋,如其他視窗、滑鼠箭頭或下拉的菜單等。在Windows中,恢複被覆蓋內容的責任大部分屬於使用者程式自己,理由很簡單:Windows是個多任務的作業系統,假如程式B覆蓋了程式A的視窗內容,覆蓋掉的內容由程式B負責恢複的話,它就必須儲存它覆蓋掉的內容,但是在它將儲存的內容恢複之前,程式A也在運行,並可能在程式B恢複以前已經向它自己的視窗輸出新的內容,結果當程式B恢複它儲存的視窗內容時,儲存的內容可能是過時的。

      Windows系統採用的方法是:當Windows檢測到視窗被覆蓋的地方需要恢複的時候,它會向使用者程式發送一個WM_PAINT訊息,然後由使用者程式來決定如何恢複被覆蓋的內容。

      如果程式因為忙於處理其他事務以至於無法及時響應WM_PAINT訊息,那麼視窗客戶區原先被覆蓋的地方可能暫時會被Windows畫成一塊白色(或者背景色)的矩形,或者根本就是保留被覆蓋時的情形,直到程式有時間去響應WM_PAINT訊息為止。

      所以對於“When”這個問題,答案是:程式應該在Windows要求的時候繪製客戶區,也就是在收到WM_PAINT訊息的時候。處理WM_PAINT訊息要求程式員改變自己向顯示器輸出的思維方式,僅當“有需求”——即Windows給視窗過程發送WM_PAINT訊息時才進行繪製。如果程式在其它時間需要更新其客戶區,它可以強制Windows產生一個WM_PAINT訊息(例如可以通過調用InvalidateRect等函數引發一條WM_PAINT訊息)。這看來似乎是在螢幕上顯示內容的一種捨近求遠的方法,但是請相信,你的程式結構會從中受益的。

2、系統何時發送WM_PAINT訊息?

      大多數Windows程式在WinMain中進入訊息迴圈之前的初始化期間都要調用函數UpdateWindow。Windows利用這個機會給視窗過程發送第一個WM_PAINT訊息。這個訊息通知視窗過程:必須繪製客戶區。

      此後,視窗過程應在任何時刻都準備好處理其它WM_PAINT訊息,必要的話,甚至重新繪製視窗的整個客戶區。當然Windows並不是在任何情況下都發送WM_PAINT訊息的,下面是幾種不同的情況介紹:

i  當滑鼠游標移過視窗客戶區以及表徵圖拖過客戶區這兩種情況,Windows總是自己儲存被覆蓋的地區並恢複它,並不需要發送WM_PAINT訊息通知使用者程式。

ii  當視窗客戶區被自己的下拉式菜單覆蓋,或者被自己彈出的對話方塊、訊息框覆蓋後,Windows會嘗試儲存被覆蓋的地區並在以後恢複它,如果因為某種原因無法儲存並恢複的話,Windows會發送一個WM_PAINT訊息通知程式。

iii  當使用者移動視窗或顯示視窗,視窗中先前被隱藏的地區重新可見,比如其他的視窗覆蓋程式客戶區後移開或程式從最小化的狀態恢複;使用者改變了視窗的大小(如果視窗類別風格具有CS_HREDRAW和CS_VREDRAW設定);使用者按動捲軸;程式調用UpdateWindow,InvalidateRect以及InvalidateRgn等函數。在這些情況下,Windows會向視窗發送WM_PAINT訊息。

3、無效矩形與有效矩形

      儘管視窗過程一接收到WM_PAINT訊息之後,就準備更新整個客戶區,但它經常只需要更新一個較小的地區(最常見的是客戶區中的矩形地區)。顯然,當對話方塊覆蓋了部分客戶區時,情況即是如此。在擦除對話方塊之後,需要重新繪製的只是先前被對話方塊遮住的矩形地區。這個地區稱為“無效地區”或“更新地區”。正是客戶區內無效地區的存在,才會讓Windows將一個WM_PAINT訊息放在應用程式的訊息佇列中。只有在客戶區的某一部分無效時,視窗才會接收WM_PAINT訊息。(可見“無效”才是產生WM_PAINT訊息的根本誘因)

      Windows內部為每個視窗儲存一個“繪圖資訊結構”①,這個結構包含了包圍無效地區的最小矩形的座標以及其它資訊,這個矩形就叫做“無效矩形”。如果在視窗過程處理WM_PAINT訊息之前客戶區中的另一個地區變為無效,則Windows計算出一個包圍兩個地區的新的無效地區(以及一個新的無效矩形),並將這種變化後的資訊放在上面提到的繪圖資訊結構中。Windows不會將多個WM_PAINT訊息都放在訊息佇列中。

      視窗過程可以通過調用InvalidateRect使客戶區內的矩形無效。如果訊息佇列中已經包含一個WM_PAINT訊息,Windows將計算出新的無效矩形。否則,它將一個新的WM_PAINT訊息放入訊息佇列中。

 

二、where——裝置描述表(Device Context,簡稱DC)

      解決了“When”的問題後,再考慮一下“Where”的問題。在Windows中,GDI把程式和硬體分隔出來,那麼,究竟該往哪裡輸出圖形呢——這就是“Where”的問題。答案是:通過“裝置描述表”來輸出圖形。

什麼是裝置描述表?

      在Windows中,所有與圖形相關的操作都是用統一的方法來完成的(不然就不能稱為“圖形裝置介面”了)。不管是繪畫螢幕上的一個視窗,還是把圖形輸出到印表機,或者對一幅位元影像進行繪製,使用的繪圖函數都是相同的,為了實現方法上的統一,必須將所有的繪圖物件看成是一個虛擬裝置,這些裝置可能有不同的屬性,如黑白印表機和彩色螢幕的色彩深度是不同的,不同印表機的尺寸和解析度可能是不同的,繪圖器只支援向量而不支援位元影像等。不同裝置的不同屬性就構成了一個繪圖的“環境”,這個繪圖的“環境”就是Win32編程中圖形操作的對象,把它叫做裝置描述表②。裝置描述表又稱為裝置上下文,或者裝置環境。

      在Windows應用程式中,裝置描述表與繪圖物件共同工作,協同完成繪圖顯示工作。就像畫家繪畫一樣,裝置描述表好比是畫家的畫布,繪圖物件好比是畫家的畫筆。用畫筆在畫布上繪畫,不同的畫筆將畫出不同的畫來。選擇合適的繪圖對象和繪圖物件,才能按照要求完成繪圖任務。

      在實際使用中,通過裝置描述表可以操作的對象很廣泛,除了可以是印表機或繪圖器等硬體裝置外,也可以是視窗的客戶區,包括大大小小的所有可以被稱為視窗的按鈕與控制項等的客戶區,也可以是一個位元影像。總之,任何需要用到圖形操作的東西都可以通過裝置描述表進行繪圖。

三、How——見例子吧

      大家在對以上兩個問題有了初步瞭解後,我們就最後來看看“How”的問題吧。GDI函數還真不少,它們的具體用法估計可以寫一本厚書了,當然我們也沒必要涉及到每個函數。在後面我們再具體展開一些常用的GDI函數,現在嘛我們來解決一下“曆史遺留問題”。

我們就先來看一個簡單顯示文本例子吧:

62 case WM_PAINT:
63 HDC hDC;
64 PAINTSTRUCT ps;
65 hDC=BeginPaint(hwnd,&ps);
66 TextOut(hDC,0,0,"Hello World!",strlen("Hello World!"));
67 EndPaint(hwnd,&ps);
68 break;

     眼熟吧,這就是第二回代碼的一部分,其功能是在視窗客戶區的左上方顯示"Hello World!",這部分我一直沒有講,就是為了等到這兒再來告訴大家。

      行63定義了一個裝置描述表控制代碼變數hDC,行64就是定義一個上面曾提到的“繪圖資訊結構”變數,行65來把申請的裝置描述表的控制代碼賦給變數hDC,行66 表示我們就可以在這個申請的這個“環境”上操作了,即在螢幕視窗客戶去輸出文本,行67釋放裝置描述表控制代碼。

      通過這個例子我們可以更好的瞭解到裝置描述表,只是“環境”而不是真正的“裝置”,這個“環境”特定的顯示裝置(本例中的顯示器)相關。我們只是在這個“環境”上表達我們要做什麼(如這個例子,我們要輸出文本"Hello World!"),具體這個“環境”怎麼讓裝置(顯示器)去做,這就是系統的事了,我們就不管了,這不是簡化了程式員的工作了嗎。

 

      再補充一點,裝置描述表中的有些值是圖形化的“屬性”,這些屬性定義了一些GDI繪圖函數工作情況的特殊內容。例如,對於 TextOut(hdc,x,y,psText,iLength),裝置描述表的屬性確定了文本的顏色、文本的背景色、TextOut函數的 x 座標和 y 座標映射到視窗的客戶區的方式,以及顯示文本時 Windows 使用的字型。其實裝置描述表實際上是一個資料結構,結構中儲存的就是裝置的屬性,當對裝置描述表進行圖形操作的時候,Windows可以根據這些屬性找到對應的裝置進行相關的操作。

 

      由此推廣開來這個例子還告訴我們:當你想在一個映像輸出裝置(諸如螢幕或者印表機)上繪圖時,你首先必須獲得一個裝置描述表(或者DC)的控制代碼。在擷取該控制代碼後,Windows用預設的屬性值填充內部裝置描述表結構。在後面文章中你會看到,可以通過調用不同的GDI函數改變這些預設值。利用其它的GDI函數可以取得這些屬性的當前值。當然,還有其它的GDI函數能夠真正地繪圖。接著,我們就是調用GDI函數在當前裝置描述表上繪圖來完成我們的任務呀。最後繪圖完畢後,必須釋放裝置描述表控制代碼。控制代碼被釋放後就不再有效,且不能再被使用。程式必須在處理單個訊息期間擷取和釋放控制代碼。
      可能你對這一系列繁瑣的操作感到反感,我勸你還是忍受吧,既然你享用Windows提供的便利,就要無條件地遵守它的“規則”。

 

      讀到此我想“How”的問題你大概已經有所瞭解了,從三個方面瞭解GDI的任務我們已經基本完成了,可以放鬆一下了,我們下回再見吧。

 

①Windows為每個視窗儲存一個“繪圖資訊結構”,這就是PAINTSTRUCT,定義如下:

typedef struct tagPAINTSTRUCT {

  HDC hdc;

  BOOL fErase;

  RECT rcPaint;

  BOOL fRestore;

  BOOL fIncUpdate;

  BYTE rgbReserved[32];

  } PAINTSTRUCT, *PPAINTSTRUCT;         

在程式調用BeginPaint(下回講)時,Windows會適當填入該結構的各個欄位值。使用者程式只使用前三個欄位,其它欄位由Windows內部使用。

hdc欄位是裝置描述表控制代碼。

fErase欄位記錄Windows是否已經擦除了無效矩形的背景,在大多數情況下,如果被標誌為FALSE(0),這意味著Windows已經擦除了無效矩形的背景。(Windows使用WNDCLASS結構的hbrBackground欄位指定的畫刷來擦除背景,這個WNDCLASS結構是程式在WinMain初始化期間登入視窗類別時使用的。許多Windows程式使用白色畫刷。以下顯示了程式設定視窗類別結構欄位的語句:

wndcls.hbrBackground=(HBRUSH)GetStockObject(WHILE_BRUSH); 
//我們之前寫過的,還記得嗎       
不過,如果程式通過調用Windows函數InvalidateRect使客戶區中的矩形失效,該函數的最後一個參數會指定是否擦除背景。如果這個參數為FALSE(即0),則Windows將不會擦除背景,並且在調用完BeginPaint後PAINTSTRUCT結構的fErase欄位將為TRUE(非零)。)

PAINTSTRUCT結構的rcPaint欄位是RECT類型的結構。

rect結構定義了一個矩形框左上方以及右下角的座標

  typedef struct _RECT {

  LONG left;

  LONG top;

  LONG right;

  LONG bottom;

  } RECT, *PRECT;

成員  left : 指定矩形框左上方的x座標

      top: 指定矩形框左上方的y座標

      right: 指定矩形框右下角的x座標

bottom:指定矩形框右下角的y座標

PAINTSTRUCT結構的rcPaint欄位定義了無效矩形的邊界,。這些值均以圖素為單位,並相對於顯示地區的左上方。無效矩形是應該重畫的地區。PAINTSTRUCT中的rcPaint矩形不僅是無效矩形,它還是一個剪裁矩形。(繪圖可以限制在客戶區的某一部分中,這就是所謂的剪裁,剪裁地區可以是矩形或非矩形)

                       

②為方便大家對裝置描述表的理解這裡還有兩種敘述,供大家參考。

a裝置描述表是一個定義一組繪圖物件及其屬性、影響輸出的圖形方式(資料)結構。windows提供裝置描述表,用於應用程式和物理裝置之間進行互動,從而提供了應用程式設計的平台無關性。裝置描述表又稱為裝置上下文,或者裝置環境。
  裝置描述表是一種資料結構,它包括了一個裝置(如顯示器和印表機)的繪製屬性相關的資訊。所有的繪製操作通過裝置描述表進行。裝置描述表與大多WIN32結構不同,應用程式不能直接存取裝置描述表,只能由各種相關API函數通過裝置描述表的控制代碼間接訪問該結構。
  裝置描述表總是與某種系統硬體裝置相關。比如螢幕裝置描述表與顯示裝置相關,印表機裝置描述表與列印裝置相關等等。
  螢幕裝置描述表,一般我們簡單地稱其為裝置描述表。它與顯示裝置具有一定的對應關係,在windows GDI介面下,它總是相關與某個視窗或這視窗上的某個顯示地區。通常意義上視窗的裝置描述表,一般指的是視窗的客戶區,不包括標題列、功能表列所佔有的地區,而對於整個視窗來說,其裝置描述表嚴格意義上來講應該稱為視窗裝置描述表,它包含視窗的全部顯示地區。二者的操作方法完全一致,所不同的僅僅是可操作的範圍不同而已。
  windows 視窗一旦建立,它就自動地產生了與之相對應的裝置描述表資料結構,使用者可運用該結構,實現對視窗顯示地區的GDI操作,如劃線、寫文本、繪製位元影像、填充等,並且所有這些操作均要通過裝置描述表控制代碼了進行。

b 孫鑫:我們可以用一個形象的比喻來說明它的作用。現在有一個美術老師,他讓他的學生畫一幅森林的映像,有的學生採用素描,有的學生採用水彩畫,有的學生採用油畫,每個學生所作的圖都是森林,然而表現形式卻各不相同。如果讓我們來畫圖,老師指定了一種畫法(例如用水彩畫),我們就要去學習它,然後才能按照要求畫出圖形。如果畫法(工具)經常變換,我們就要花大量的時間和精力去學習和掌握它。在這裡,畫法就相當於電腦中的圖形裝置及其驅動程式。我們要想作一幅圖,就要掌握我們所用平台的圖形裝置和它的驅動程式,調用驅動程式的介面來完成圖形的顯示。不同圖形裝置的裝置驅動程式是不一樣的,對於程式員來說,要掌握各種不同的驅動程式,工作量就太大了。因此,Windows 就給我們提供了一個裝置描述表,讓我們從學生的角色轉變為老師的角色,只要下命令去畫森林這幅圖,由裝置描述表 去和裝置驅動程式打交道,完成圖形的繪製。至於圖形的效果,就要由所使用的圖形裝置來決定了。對於老師來說,只要畫出的是森林映像就可以了。對於程式員來說,充當老師的角色,只需要擷取裝置描述表的控制代碼,利用這個控制代碼去作圖就可以了。

:部分內容援引自羅雲彬《Windows環境下32位組合語言程式設計》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.