設備對象屬性決定着繪圖方式,當使用GDI函數繪圖時,所繪製的圖形和文本的顏色、大小和位置等由設備對象的當前屬性決定,應用程序可以使用GDI函數改變設備對象的當前屬性。本章介紹設備對象的一些主要屬性和用於改變這些屬性的有關函數,其它的設備對象屬性在後面的章節中陸續介紹。
3.1、圖形設備接口 在第二章,我們一直在使用圖形設備接口(GDI),GDI的主要目標之一是支持在輸出設備(例如顯示器、打印機)上建立與設備無關的圖形輸出。Windows的圖形大部分是由GDI.EXE(Windows系統的一個模塊,稱GDI模塊)中的函數處理的。GDI模塊通過調用在不同設備驅動程序模塊中的例程來控制輸出設備。例如,顯示設備驅動程序用於訪問與視頻顯示器有關的硬件。通過GDI,Windows可以確定驅動程序能夠進行什麼工作,並且由於應用程序只與GDI打交道。這樣,GDI通過將應用程序與不同的輸出設備隔離,使應用程序可以在支持Windows的任何圖形輸出設備上工作。 圖形輸出設備可以分爲兩組:光柵設備和矢量設備。光柵設備將圖象表示爲點(象素)的圖案。這類輸出設備包括顯示設備、點陣打印機和激光打印機。向量設備用線段來繪製圖象,例如繪圖儀。Windows的GDI是一種隔離了硬件具體特性的圖形語言。雖然輸出設備用象素來表示圖形,但GDI卻可以被用作一個高級的向量繪圖系統,也可以被用來進行較低級的象素操作。 在編寫Windows應用程序時,程序員不必爲顏色過分擔心,如果在應用程序中使用的一種顏色不能被該顯示器所表示,Windows或爲應用程序選擇一種最直接的純顏色(顯示設備可以表示的顏色),或通過將幾種純顏色相混合來表示這種顏色。當在彩色顯示器上開發的程序運行在單色顯示器上時,Windows將使用灰度來表示顏色。應用程序也可以在程序中確定輸出設備的有關特性,例如,可表示的顏色數目、設備的顯示區的尺寸等,以便最大限度地發揮硬件的能力。
3.2、設備對象屬性 設備對象具有許多決定GDI函數在設備對象上如何工作的當前屬性。例如,在使用函數TextOut()時,只需要在函數中說明設備對象的句柄、繪製字符的起始座標、文本和文本長度,而不用說明字體、文本顏色、背景顏色和字符間距等,因爲這些特徵由設備對象的屬性決定。每種設備對象都賦有缺省的屬性,可以使用GDI函數改變這些屬性中的某一個。表3-1給出了顯示設備各個屬性的缺省值,當使用GetDC()和BeginPaint()等函數初次得到一個顯示設備對象時,該對象的屬性具有缺省值。
屬性 |
缺省值 |
視區原點 |
(0, 0) |
視區範圍 |
(1, 1) |
窗口原點 |
(0, 0) |
窗口範圍 |
(1, 1) |
背景顏色 |
白色 |
背景方式 |
OPAQUE |
位圖 |
任意值 |
刷子 |
WHITE_BRUSH |
刷子原點 |
(0, 0) |
裁剪區 |
用戶區/無效矩形區/子窗口區 |
調色板 |
DEFAULT_PALETTE |
|
|
屬性 |
缺省值 |
筆的當前位置 |
(0, 0) |
筆的顏色 |
BLACK_PEN |
文本顏色 |
黑 |
設備的原點 |
用戶區的左上角 |
繪圖方式 |
R2_COPYPEN |
字體 |
SYSTEM_FONT |
字符間距 |
0 |
映射方式 |
MM_TEXT |
多邊形填充方式 |
ALTERNATE |
相對一絕座標 |
ABSOLUTE |
縮放方式 |
BLACKONWHITE |
|
在本章以後的各節中將介紹其中一些設備屬性,其它屬性在以後章節中介紹。
3.3、設備座標系 爲在輸出設備上定位和繪製圖形對象,必須引入一種座標系。Windows的各種不同類型設備所使用的座標稱爲設備座標。它們使用笛卡爾座標系,在這些設備座標系中,單位都以象素的個數表示(稱爲設備單位)。x軸上的值自左向右增加,y軸上的值自頂向下增加,見圖3-1。
|
圖3-1 Windows的設備座標系 |
本節以視頻顯示設備爲例介紹Windows的設備座標系,其中的許多內容也適合用於象打印機等硬拷貝輸出設備。 在Windows環境中,視頻顯示設備是一個共享設備,即在同一時刻,顯示設備上可以同時顯示多個應用程序的輸出信息。爲了保護一個程序顯示的信息不被其他程序破壞,Windows通過將顯示區看作不同的設備對象來限制應用程序輸出信息的範圍。 一個應用程序可獲取三種不同的顯示設備對象句柄,每種句柄所標識的設備對象代表屏幕上的不同區域。我們可以將這三個句柄所標識的對象視作三個不同的抽象顯示設備,都帶有如圖3-1所示的設備座標系,但對不同的抽象設備,座標原點不一樣的。這樣,當使用相同的起始座標而使用不同的設備對象句柄調用GetDC函數(例如TextOut())進行繪圖時,信息顯示的位置不一樣。 第一抽象設備是用戶區對象,它的座標原點在用戶區的左上角。定位該區域的設備座標系稱爲用戶區座標系。使用函數GetDC()或BeginPaint()得到的句柄是標識用戶區的句柄。當使用該句柄標識該區域的句柄作爲GDI函數的參數時,GDI函數所使用的座標值是相對於用戶區座標系。 第二個抽象設備是全窗口對象。它包括標題欄、選單、滾動杆和窗口框架等。定位這個區域的座標系稱爲全窗口座標系,它的原點的左上角。使用函數GetWindowsDC()可以獲得該設備對象的句柄,然後通過該句柄使用全窗口座標系在該區域中繪圖。應用程序一般不在這個區域中繪圖。 第三個抽象設備是整個屏幕對象,其座標原點在屏幕的左上角,定位該區域的設備座標系稱爲屏幕座標系。使用語句:
HDC hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
可以獲得該設備對象的句柄,使用該句柄的GDI函數所使用的座標是相對於屏幕座標系。 這三種座標系方便了程序在不同的區域繪製圖形的需要。例如,由於使用用戶區座標系,即使窗口在屏幕上被移動到其他位置,但用戶區中顯示的信息相對於用戶座標系而言其座標值不變。 使用座標系,解決了顯示對象的定位的問題的。但以象素單位所建立的座標系不符合用戶(或程序員)的習慣,而且在不同分辨率的輸出設備上,一個象素的大小也不相同的,這樣,應用程序的輸出在不同機器上會得到不同的結果。爲解決這個問題,Windows引入了邏輯座標系。它使得用戶可以按照自己習慣使用的尺寸(毫米、英寸等)來描述客體,或繪製圖形。用戶在程序中使用邏輯座標系中的座標值,而由Windows根據映射方式將邏輯座標系中的座標值轉算成設備座標系中的座標值(對於顯示設備而言,具體映射到三個座標系中哪一個取決於GDI函數中所使用的設備對象句柄)。映射方式也同時決定着邏輯座標值的單位(簡稱邏輯單位)和座標軸的正方向。在前面介紹的TextOut和DrawText()使用的是邏輯單位,而函數CreateWindow()使用的是屏幕座標系中的座標(設備單位,及象素)。程序員應該清楚什麼時候在使用邏輯單位。一般而言,當映射方式起作用時,使用的是邏輯單位,而映射方式起作用的唯一時候是使用了那些需要將設備對象句柄作爲參數的GDI函數的時候,但並非總是這樣,因此,讀者應注意這方面的問題。 表3-2給出的兩個函數用於進行用戶區座標和屏幕座標(注意是設備單位)之間的轉換。
表3-2-1 ClientToScreen 函數
用 途 |
將一個點的用戶區座標轉換爲屏幕座標。 |
原 型 |
VOID ClientToScreen( |
|
HWND hWnd, |
與用戶區相關聯的窗口句柄 |
LPPOINT lpPoint |
指向一個POINT結構類型的變量,其中一個點的用戶區座標值,並被轉換後的屏幕座標值取代。 |
); |
|
|
表3-2-2 ScreenToClient 函數
用 途 |
將給定點的屏幕座標轉換爲用戶區座標。 |
原 型 |
VOID ScreenToClient( |
|
HWND hWnd, |
與用戶區相關聯的窗口句柄 |
LPPOINT lpPoint |
所指向的變量包含一個點的屏幕座標值,並被轉換後的用戶區座標值取代 |
); |
|
|
3.4、映射方式 影響在用戶區繪圖的一個主要設備對象屬性是“映射方式”。它定義了Windows如何將GDI函數中使用的邏輯座標映射爲設備座標。其他四個設備對象屬性(窗口原點、視區原點、窗口範圍和視區範圍)都與映射方式密切相關。當Windows將邏輯單位轉換爲設備單位或象素時,映射方式、窗口原點和視區原點、窗口範圍和視區反問決定着這種轉換。映射方式也隱含了x軸座標和y軸的原點和方向。在介紹各種映射方式之前,我們先介紹窗口與視口關係。
3.4.1 窗口與視口 視口是計算機屏幕上一塊顯示區域,隨着在GDI中所使用的設備對象句柄的不同,該區域可是用戶區、全窗口區或整個屏幕區,視區中的圖形一設備單位定義。與視區中顯示的圖形相對應的原始圖形區域稱爲窗口。注意,在這裏使用的術語“窗口”不是指屏幕上顯示的可視窗口對象。這裏的“窗口”是從現實世界角度所看到圖形,而“視區”是從數據世界角度而言的,是屏幕上的象素形成的圖形。在Windows中,視區不是一個裁剪區。圖3-2和圖3-3說明了窗口和視口的關係。
|
|
|
圖3-3 視區中顯示的曲線 |
|
映射方式指的是從“窗口”(邏輯座標)到“視區”(設備座標)的變換。“視區”採用設備座標(象素數)。“窗口”採用邏輯座標,它可以是象素數、毫米、英寸或任何其他單位。GDI函數使用邏輯座標。 將“窗口”(邏輯)座標轉換爲“視區”(設備)座標,使用如下兩個映射方式:
xViewport = (xWindow - xWinOrg) * xViewExt / xWinExt + xViewOrg
|
yViewport = (yWindow - yWinOrg) * yViewExt / yWinExt + yViewOrg
|
該公式將邏輯座標系中的點(xWindow, yWindow)變換爲設備座標系中的點(xViewport, yViewport),其中,點(xWinOrg, yWinOrg)是以邏輯單位表示的“窗口”原點,而點(xViewOrg, yViewOrg)是以設備座標表示的“視區”原點。在缺省情況下,這兩個點被設置爲(0, 0),但它們可以被改變。注意,邏輯點(xWinOrg, yWinOrg)總是被映射爲設備點(xViewOrg, yViewOrg)。 改變上述的公式爲:
xWindow = (xViewport - xViewOrg) * xWinExt / xViewExt + xWinOrg
|
yWindow = (yViewport - yViewOrg) * yWinExt / xViewExt + yWinOrg
|
該公式可以將視區座標轉換爲窗口座標。
表3-3-1 DptoLP 函數
用 途 |
將設備點變換爲邏輯點。 |
原 型 |
BOOL DPtoLP( |
|
HDC hDC, |
設備對象句柄 |
LPPOINT lpPoints, |
指向POINT類型的變量的指針 |
int nCount |
要進行變換的點的數目 |
) |
|
|
返回值 |
若變換成功,返回非零。 |
表3-3-2 LPtoDP 函數
用 途 |
將邏輯點變換爲設備點。 |
原 型 |
BOOL LPtoDP( |
|
HDC hDC, |
設備對象句柄 |
LPPOINT lpPoints, |
指向POINT類型的變量的指針 |
int nCount |
欲進行變換的點的數目 |
) |
|
|
返回值 |
若所有的點被變換,返回非零。 |
表3-3給出了兩個函數,用於進行設備座標和邏輯座標之間的相互變換。例如,函數GetClientRect獲取的擁護區域的大小總是以設備單位表示的,若想使用邏輯單位表示用戶區大小,可以使用函數DPtoLP();
RECT rect; GetClient(hWnd, &rect); DPtoLP(hDC, (LPPOINT)&rect, 2);
3.4.2 Windows的映射方式 Windows定義了八種映射方式,見表3-4。
表3-4 Windows的映射方式
映射方式 |
邏輯單位單位 |
x軸方向 |
y軸方向 |
MM_TEXT |
象素數 |
向右 |
向下 |
MM_LOMETRIC |
0.1mm |
向右 |
向上 |
MM_HIMETRIC |
0.01mm |
向右 |
向上 |
MM_LOENGLISH |
0.01英寸 |
向右 |
向上 |
MM_HIENGLISH |
0.001英寸 |
向右 |
向上 |
MM_TWIPS |
1/1440英寸 |
向右 |
向上 |
MM_ISOTROPIC |
自定義(x=y)(即x和y的邏輯單位大小一樣) |
由比例因子決定若爲正,向右。否則,向左 |
由比例因子決定若爲正,向下。否則,向上 |
MM_ANISOTROPIC |
自定義(x!=y)(即x和y的邏輯單位大小不一樣) |
同上 |
同上 |
注:Twips表示“一個點的二十分之一”,在GDI中,一個點的大小定爲1/72英寸,所以一個twips是1/1440英寸。 在缺省時,設備對象使用的映射方式是MM_TEXT,即邏輯單位等於物理單位。例如:
TextOut(hDC, 4, 6, "Hello", 5);
在用戶區向右偏移4個象素,向下偏移6個象素的位置開始顯示信息“Hello”。 可以使用函數SetMapMode()設置映射方式,或使用函數GetMapMode()獲取一個設備對象當前的映射方式,見表3-5,例如,語句:
SetMapMode(hDC, MM_LOMETRIC); TextOut(hDC, 100, -200, "Hello", 5);
首先設置MM_LOMETRIC映射方式,然後在離用戶區原點向右1釐米、向下2釐米的位置顯示信息“Hello”。這裏使用了負的座標值,因爲根據窗口原點變換到視口原點的原則,只有窗口座標的y座標爲負值的點纔可以變換到用戶區中,y座標爲正值的點落在用戶區之外。表3-5-1 SetMapMode 函數
用 途 |
設置設備對象的映射方式。 |
原 型 |
int SetMapMode( |
|
HDC hDC, |
設備對象句柄 |
int nMapMode |
所欲設置的映射方式,使用表3-4中常量之一 |
) |
|
|
返回值 |
先前的映射方式。 |
表3-5-1 GetMapMode 函數
用 途 |
檢索當前的映射方式。 |
原 型 |
int GetMapMode( |
|
HDC hDC, |
設備對象句柄 |
) |
|
|
返回值 |
當前的映射方式。 |
Windows函數中指定的所有座標值必須在-32768到32767之間的值(這是C語言的int類型的數據的值域)。 在任何映射方式下,邏輯座標和物理座標的缺省原點都爲(0, 0),即邏輯座標的點(0, 0)映射到設備座標的點(0, 0)。
3.4.3 改變視區和窗口的原點 表3-6說明的函數可用於改變視區和窗口的原點,但這兩個函數不能同時使用。
表3-6-1 SetViewportOrg 函數
用 途 |
設置視區的座標原點。 |
原 型 |
DWORD SetViewportOrg( |
|
HDC hDC, |
設備對象句柄 |
int x, |
指定視區座標原點的x座標值(設備單位)。 |
int y, |
指定視區座標原點的y座標值(設備單位)。 |
) |
|
|
返回值 |
返回以前視區座標原點(設備單位),高位字爲y座標,低位字爲x座標。 |
表3-6-2 SetWindowOrg 函數
用 途 |
設置窗口座標的原點。 |
原 型 |
DWORD SetWindowOrg( |
|
HDC hDC, |
設備對象句柄 |
int x, |
指定窗口座標原點的x座標值(邏輯單位)。 |
int y, |
指定窗口座標原點的y座標值(邏輯單位)。 |
) |
|
|
返回值 |
返回以前視區座標原點(設備單位),高位字爲y座標,低位字爲x座標。 |
無論怎樣改變窗口和視區的原點,Windows都將窗口原點變換到視區原點,並按同樣的映射算法(即使用當前給出的映射公式)變換其餘的點。 我們先介紹MM_TEXT映射方式下這兩個函數的工作原理。因爲這種映射方式的x座標和y座標方向與我們閱讀文本時的方向一致。因此,MM_TEXT映射方式又稱爲“文本”映射方式。
|
圖3-4 |
現假定用戶區的寬度爲xClient個象素,高爲yClient給象素,則語句:
SetViewportOrg(hDC, xClient/2, yClient/2);
將邏輯點(0, 0)確定在(映射到)用戶區的中心。
|
圖3-5 |
這時,若使用語句:
TextOut(hDC, 0, 0, "Hello", 5);
將在用戶區中心位置開始顯示信息。而語句:
TextOut(hDC, -xClient/2, -yClinent/2, "Hello", 5);
在用戶區的左上角(設備點(0, 0))處開始顯示文本。 通過使用函數SetWindowOrg可以得到與使用函數SetViewpostOrg相同的結果,例如:
SetWindowOrg(hDC, -xClient/2, -yClient/2);
這使得邏輯點(-xClient/2, -yClient/2)被映射爲設備點(0, 0)。 函數GetViewportOrg和GetWindowOrg可分爲別用於獲取當前視區和窗口的原點,見表3-7。
表3-7-1 GetViewportOrg 函數
用 途 |
獲取當前的視區原點。 |
原 型 |
DWORD GetViewportOrg( |
|
HDC hDC, |
設備對象句柄 |
) |
|
|
返回值 |
視區的原點(設備座標),y座標在高位字中,x座標在低位字中。 |
表3-7-2 GetWindowOrg 函數
用 途 |
獲取當前的視區原點。 |
原 型 |
DWORD GetWindowOrg( |
|
HDC hDC, |
設備對象句柄 |
) |
|
|
返回值 |
窗口的原點,y座標在高位字中,x座標在低位字中。 |
Windows的映射方式中,MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH和MM_TWIPS是以物理度量單位表示邏輯座標的映射方式。這五種映射方式被稱爲公制映射方式,因爲它們在X軸和Y軸上的邏輯座標被映射爲等量的物理單位,所以這些映射方式便於繪製圓或正方形。 當初次設置這些映射方式之一時,座標系爲:
|
圖3-6 |
因此,在用戶區中顯示信息必須使用負的Y座標值。例如:
SetMapMode(hDC, MM_LOENGLISH); TexOut(hDC, 100, -100, "Hello", 5);
將在用戶區向右偏移1英寸,向下偏移1英寸的位置顯示“Hello”。 下面的語句將邏輯點(0, 0)設置在用戶區的左下角:
SetViewportOrg(hDC, 0, yClient);
|
圖3-7 |
而下面的語句將邏輯點(0, 0)設置在用戶區的中心點。
SetViewportOrg(hDC, xClient/2, yClient/2);
|
圖3-8 |
在Windows提供的八種映射方式中,唯有MM_ISOTROPIC和MM_ANISOTROIC允許用戶改變窗口和視口的範圍(其他六種的範圍是固定的,用戶不能改變)。表3-7列出了與窗口範圍和視區範圍有關的函數。
表3-7-3 SetWindowExt 函數
用 途 |
設置窗口的x和y座標範圍。 |
原 型 |
DWORD SetWindowExt( |
|
HDC hDC, |
設備對象句柄 |
int x, |
窗口的x座標範圍(邏輯單位) |
int y |
窗口的y座標範圍(邏輯單位) |
) |
|
|
返回值 |
以前的座標範圍(邏輯單位),y座標範圍在高位字中,x座標範圍在低位字中。 |
表3-7-4 GetWindowExt 函數
用 途 |
檢索窗口的x和y座標範圍。 |
原 型 |
DWORD GetWindowExt( |
|
HDC hDC, |
設備對象句柄 |
) |
|
|
返回值 |
y範圍在高位字中,x範圍在低位字中。 |
表3-7-5 SetViewportExt 函數
用 途 |
設置視區的x和y範圍。 |
原 型 |
DWORD SetViewportExt( |
|
HDC hDC, |
設備對象句柄 |
int x, |
視區的x座標範圍(設備單位) |
int y |
視區的y座標範圍(設備單位) |
) |
|
|
返回值 |
以前的視區的座標範圍(設備單位),y在高位字中,x在低位字中。 |
表3-7-6 GetViewportExt 函數
用 途 |
設置視區的x和y範圍。 |
原 型 |
DWORD GetViewportExt( |
|
HDC hDC, |
設備對象句柄 |
) |
|
|
返回值 |
視區的X和Y座標範圍(設備單位),y在高位字中,x在低位字中。 |
窗口和視區的範圍不具有任何裁剪區的意義,它們用於確定將邏輯座標系中的單位放大或縮小多少以適合設備座標系中的單位。例如,如果窗口的X座標範圍是2,而視區的X座標範圍是4,則GDI將兩個邏輯單位(按X軸計算)映射爲一個設備單位。 座標範圍也同樣決定了兩個座標系(窗口和視區)的X軸和Y軸的相對定向關係。如果相關聯的窗口和視區範圍符號一致,則座標軸方向相同,否則方向相反。例如,如果窗口和視區的X座標範圍分別是2和4,則GDI將邏輯座標系的X軸的正半軸映射到該設備座標系統的X軸正半軸;如果窗口和視區的Y座標範圍分別爲2和-1,則GDI將邏輯座標系統的Y軸的正半軸映射到設備座標系的Y軸的負半軸。 對於MM_ISOTROPIC映射方式,詞“isotropic”表示在所有的方向都“相等”,即該方式的用途是在兩個軸上保持相等的邏輯單位。我們舉一例子來說明該方式的設置方法。 我們設置這樣一個座標系,(0, 0)點位於用戶區域的左下角,寬度範圍從0到32767,高度範圍也從0到32767:
|
圖3-9 |
並使用下列程序段設置映射方式和範圍:
SetMapMode(hDC, MM_ISOTROPIC); SetWindowExt(hDC, 32767, 32767); SetViewportExt(hDC, xClient, yClient); // xClient和yClient是用戶區的寬度和高度 SetViewportOrg(hDC, 0, yClient);
這時,Windows將調整範圍,使兩個軸上的邏輯單位表示相同的物理距離。如果用戶區的寬度比高要長,則Windows調整x範圍。這時座標位置如下圖:
|
圖3-10 |
邏輯窗口將位於用戶區左側,並且由於Windows的座標值不能大於32767,就無法在用戶區的右側部分顯示任何圖形。如果用戶的高度比寬度要長,則Windows調整y的範圍,這時候座標位置如下圖:
|
圖3-11 |
同樣的原因,在用戶的頂部也無法顯示任何圖形,因爲這時需要大於32767邏輯單位的y座標。 由此可見,對於MM_ISOTROPIC映射方式,程序員可以調用函數SetViewportExt設置範圍,Windows通過調整範圍,使得兩個軸上每一個邏輯單位表示相同的物理距離,這對於繪製圓和正方形等是方便的。 對函數SetViewportExt的調用必須在對函數SetViewportExt的調用之前進行,這可以最有效地使用用戶區中的空間。若不是這樣的話,當Windows調整範圍時,爲了使邏輯窗口置於物理視區內,可能導致用戶區域的某一部分在邏輯窗口的外面。 窗口和視區的範圍不表示裁剪區,我們分析下面的程序片斷所建立的座標系:
SetMapMode(hDC, MM_ISOTROPIC); SetWindowExt(hDC, 1000, 1000); SetViewportExt(hDC, xClient/2, -yClient/2); SetViewportExt(hDC, xClient/2, yClient/2);
我們分析用戶區寬度大於高度的情況:
|
圖3-12 |
對於上圖所示的情況,若點的X座標小於-1000或大於+1000,則這個點可能落在用戶區內,也可能落在用戶區之外;但對於Y座標小於-1000或大於+1000的點,這個點一定落在用戶區之外。 最後,我們介紹MM_ANISOTROPIC映射方式,單詞“anisotropic”的含義是“在所有的方向上不相等”。對於MM_ANISOTROPIC,Windows對所設置的範圍不進行調整,這表明,對於MM_ANISOTROPIC,兩個座標軸上的邏輯單位具有不同的物理尺寸。例如,對於上面的例子,如果設置爲MM_ANISOTROPIC映射方式。
SetMapMode(hDC, MM_ANISOTROPIC); SetWindowExt(hDC, 1000, 1000); SetViewportExt(hDC, xClient/2, -yClient/2); SetViewportExt(hDC, xClient/2, yClient/2);
則所建立的座標系如下圖所示:
|
圖3-13 |
下面的程序實例是使用MM_ANISOTROPIC映射方式重新設計的2.5節的程序。
// 3-1.c #include <stdio.h> #include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int PASCAL WinMain( HINSTANCE hInstance, // 應用程序的實例句柄 HINSTANCE hPrevInstance, // 該應用程序前一個實例的句柄 LPSTR lpszCmdLine, // 命令行參數串 int nCmdShow ) // 程序在初始化時如何顯示窗口 { char szAppName[] = "DispText"; HWND hwnd; MSG msg; WNDCLASS wndclass;
if (!hPrevInstance) { // 該實例是程序的第一個實例,註冊窗口類 wndclass.style = CS_VREDRAW | CS_HREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(hInstance, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass)) // 如果註冊失敗 return FALSE; }
// 對每個實例,創建一個窗口對象 hwnd = CreateWindow( szAppName, "Display Text", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
while( GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
return msg.wParam; }
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int xChar, yChar; int line; char szBuffer[256]; HDC hDC; char ch; PAINTSTRUCT ps; TEXTMETRIC tm; FILE *fp;
switch(message) { case WM_CREATE: hDC = GetDC(hwnd); GetTextMetrics(hDC, &tm); xChar = tm.tmAveCharWidth; yChar = tm.tmHeight + tm.tmExternalLeading; ReleaseDC(hwnd, hDC); return 0L;
case WM_PAINT: hDC = BeginPaint(hwnd, &ps); SetMapMode(hdc, MM_ANISOTROPIC); SetWindowExt(hdc, 1, 1); SetViewportExt(hdc, xChar, yChar); line = 0; if((fp = fopen("disptext.cpp", "r")) != NULL) { while(!feof(fp)) { int i = 0; while((ch = fgetc(fp)) != '/n' && ch != EOF) szBuffer[i++] = (char)ch; TextOut(hDC, xChar, line*yChar, szBuffer, i); line++; } fclose(fp); } EndPaint(hwnd, &ps); return 0L;
case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }3.5、設備信息 一個設備對象通常與一個物理顯示設備(例如顯示器和打印機等)相關聯,這些設備的有關信息可以通過調用GetDeviceCaps函數來獲取。表3-8給出了該函數的一些使用說明,在本指南中,當需要用該函數的其他功能時,再進行有關的討論。
表3-8 GetDeviceCaps 函數
用 途 |
獲取一個設備的設備信息。 |
原 型 |
int GetDeviceCaps( |
|
HDC hDC, |
設備對象句柄 |
int nIndex |
|
); |
|
|
返回值 |
返回所需要項的值。 |
表3-8-1 nIndex 值
常量 |
說明 |
HORZSIZE |
物理顯示器的寬度(mm) |
VERTSIZE |
物理顯示器的高度(mm) |
HORZRES |
物理顯示器的寬度(象素) |
VERTRES |
物理顯示器的高度(象素,嚴格說是光柵行數) |
LOGPIEXLSX |
沿顯示寬度方向每邏輯英寸的象素數 |
LOGPIEXLSY |
沿顯示高度方向每邏輯英寸的象素數 |
BITSPIEXL |
表示每個象素所需要的位數目 |
PLANES |
色平面數 |
NUMBRUSHES |
刷子的數目 |
NUMPENS |
筆的數目 |
NUMFONTS |
字體的數目 |
NUMCOLORS |
設備的顏色表中的項目數 |
ASPECTX |
一個象素的相對寬度 |
ASPECTY |
一個象素的相對高度 |
ASPECTXY |
象素的相對對角線寬度 |
LOGPIXELSX和LOGPIXELSY的值分別是水平和垂直方向每一“邏輯寸”中的象素數。一個邏輯寸大小約是1.5英寸,這取決於顯示設備的分辨率。邏輯寸實際上放大顯示,以便將所顯示的文本放大到一個適當的大小。
3.6、顏色的使用 關於顏色的定量表示,有許多方法,其中一種方法分別使用0到255的數值來表示一種顏色中紅(R)、綠(G)和藍(B)三基色的相對強度,這中方法被稱爲RGB顏色模型。Windows用一個無符號長整數來表示顏色,其中最低三字節分別指定0到255範圍內的紅、綠和藍三基色的相對強度。在Windows中,使用COLORREF類型(或DWORD類型)的數據表示RGB顏色值。 在Windows.h中定義的宏RGB將三個表示紅、綠、藍的值組成一個RGB顏色值。例如:RGB(0,0,0)表示黑色,RGB(255,255,255)表示白色,其他常見的RGB顏色如下:
類型 |
說明 |
RGB(255, 0, 0) |
紅色 |
RGB(0, 255, 0) |
綠色 |
RGB(0, 0, 255) |
藍色 |
RGB(255, 0, 255) |
紫色 |
RGB(255, 255, 0) |
黃色 |
RGB(0, 255, 255) |
青色 |
宏GetRValue、GetGValue和GetBValue從RGB值中提取無符號單字節的基色值。 Windows還可以通過灰度比方式來顯示附加的顏色,即將不同的單色象素組合成象素圖案,不同的紅、綠、藍顏色組成不同的灰度比圖案。 另外,Windows爲了對各個顯示元素進行着色,還維護着19種系統顏色(見表示表3-9),程序員可以使用函數GetSysColor或SetSysColors來獲取或設置這些系統顏色(見表3-10)。這些顏色的缺省值是由設備驅動程序提供的。
表3-9 Windows的缺省系統顏色
常量 |
說明 |
COLOR_SCROLLBAR |
滾動槓的灰色區域 |
COLOR_BACKGROUND |
桌面的背景(屏幕背景) |
COLOR_ACTIVECAPTION |
活動窗口的標題欄 |
COLOR_INACTIVECAPTION |
非活動窗口的標題欄 |
COLOR_MENU |
選單背景 |
COLOR_MENUTEXT |
選單文本 |
COLOR_WINDOW |
窗口的背景 |
COLOR_WINDOWFRAME |
窗口的框架 |
COLOR_CAPTIONTEXT |
標題欄中的文本 |
COLOR_ACTIVEBORDER |
活動窗口的邊框 |
COLOR_APPWORKSPACE |
多文檔界面應用程序的背景 |
COLOR_HIGHLIGHT |
選項 |
COLOR_HIGHLIGHTTEXT |
選項中的文本 |
COLOR_BTNFACE |
按鈕的邊緣 |
COLOR_BTNTEXT |
按鈕上的文本 |
COLOR_GRAYTEXT |
按鈕(灰色文本) |
表3-10-1 SetSysColors 函數
用 途 |
該函數爲一個或多個顯示元素設置系統顏色(顯示元素指的是系統和窗口的各個不同的部分。 |
原 型 |
VOID SetSysColors( |
|
int nChanges, |
需改變的系統顏色數目。 |
LPINT lpSysColor, |
int類型的指針,所指向的變量中指定了將要改變的元素,可用的值見表3-9。 |
DWORD FAR *lpColorValue |
所指向的變量中包含了每個元素新的RGB顏色值。 |
); |
|
|
注 意 |
該函數將改變當前正運行的所有應用程序的窗口顏色。該函數將各窗口發送WM_SYSCOLORCHANG消息,通知顏色的變化。 |
表3-10-2 GetSysColor 函數
用 途 |
檢索指定的顯示元素的當前色。 |
原 型 |
VOID GetSysColors( |
|
int nIndex, |
指定要檢索其顏色的顯示元素,見表3-9。 |
); |
|
|
返回值 |
所指定的顯示元素的RGB值,對單色顯示器,系統顏色可解釋爲灰度圖案。 |
3.7、使用刷子 刷子用於擦除用戶區中顯示的內容,也用於填充GDI圖形函數建立的封閉圖形。Windows提供的可供程序員使用的庫存刷子有:
常量 |
說明 |
WHITE_BRUSH |
白色刷子 |
LTGRAY_BRUSH |
淺灰色刷子 |
GRAY_BRUSH |
灰色刷子 |
DKGRAY_BRUSH |
深灰色刷子 |
BLACK_BRUSH |
黑色刷子 |
HOLLOW(or NULL)_BRUSH |
空刷子 |
一個刷子由HBRUSH類型的句柄標識。庫存刷子的句柄可由函數GetStockObject函數獲得。例如,下面的語句獲得一個灰色刷子,並將其句柄存於hBrush中。
HBRUSH hBrush = GetStockObject(GRAY_BRUSH);
使用函數SelectObject將一個刷子選入一個設備對象中,這樣,在以後填充封閉圖形時,使用選入到設備對象中的刷子。例如,將上面的語句所獲得的庫存刷子選入hDC標識的設備對象中,使用語句:
hOldBrush = SelectObject(hDC, hBrush);
函數SelectObject返回設備對象中先前已存在的刷子,在上例中,將它保存在HBRUSH類型的變量hOldBrush中。程序員可以使用函數CreateSolidBrush和CreateHatchBrush創建自己的刷子,表3-11給出了這兩個函數的說明。
表3-11-1 CreateSolidBrush 函數
用 途 |
創建一個指定顏色的邏輯刷子。 |
原 型 |
HBRUSH CreateSolidBrush |
|
COLORREF crColor |
RGB顏色值。 |
); |
|
|
注 意 |
如果調用成功,返回一個刷子句柄,否則返回NULL。 |
表3-11-2 CreateHatchBrush 函數
用 途 |
創建一個具有指定陰影圖案和顏色的邏輯刷子。 |
原 型 |
HBRUSH CreateHatchBrush |
|
int nIndex, |
刷子的陰影類型,見後面的說明。 |
COLORREF crColor |
刷子影線的顏色。 |
); |
|
|
返回值 |
如果調用成功,返回一個刷子句柄,否則返回NULL。 |
常量 |
說明 |
HS_HORIZONTAL |
- - - - 水平影線 |
HS_VERTICAL |
| | | | 垂直影線 |
HS_FDIAGONAL |
/ / / / 45度向上影線 |
HS_BDIAGONAL |
/ / / / 45度向下影線 |
HS_CROSS |
+ + + + 水平和垂直交叉影線 |
HS_DIAGCROSS |
X X X X 45度交叉影線 |
例如,下列程序段分別創建一個綠色刷子和一個紅色影線刷子。
hGreenBrush = CreateSolidBrush(RGB(0,255,0)); hRedHatchedBrush = CreateHatchBrush(HS_CROSS, RGB(255,0,0));
程序員創建的刷子在不再使用時一定要刪除(使用函數DeleteObject)。刪除庫存刷子是非法的。
DeleteObject(hGreenBrush); DeleteObject(hRedHatchedBrush);
下面的程序建立一個背景色爲交叉影線的窗口。
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBRUSH OrgBrush, hBrush; HDC hDC; char msg[256] = "Hello, Welcome to cgd.gamedoor.net!"; PAINTSTRUCT ps;
switch(message) { case WM_CREATE: OrgBrush = GetClassWord(hwnd, GCW_HBRBACKGROUND); hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255,255,0)); SetClassWord(hwnd, GCW_HBRBACKGROUND, hBrush); InvalidateRect(hwnd, NULL, TRUE); UpdateWidnow(hwnd); return 0;
case WM_PAINT: hDC = BeginPaint(hwnd, &ps); SetBkMode(hDC, TRANSPARENT); TextOut(hDC, 0, 0, msg, sizeof(msg)-1); ValidateRect(hwnd, NULL); EndPaint(hwnd, &ps); return 0;
case WM_DESTROY: SetClassWord(hwnd, GCW_HBRBACKGROUND, OrgBrush); DeleteObject(hBrush); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
在程序中,語句:
GetClassWord(hwnd, GCW_HBRBACKGROUND);
獲取在類所指定的刷子的句柄,語句:
SetClassWord(hwnd, GCW_HBRBACKGROUND, hBrush);
重新將類刷子設置爲程序員所創建的刷子。爲了用新刷子清楚用戶區,語句:
InvalidateRect(hwnd, NULL, TRUE);
使整個用戶區無效。程序中使用的函數SetBkMode見3.9節。
3.8、使用筆 GDI函數使用筆畫線或繪製圖形的輪廓。Windows庫存的筆包括:
常量 |
說明 |
WHITE_PEN |
白色筆 |
BLACK_PEN |
黑色筆 |
NULL_PEN |
空筆 |
筆有類型爲HPEN的句柄來標識,庫存的筆可用函數GetStockObject來獲取:
HPEN hPen = GetStockObject(WHITE_PEN);
它返回標識一個庫存筆的句柄。設備對象中缺省的筆是BLACK_PEN。程序員也可以使用函數GreatePen創建定製的筆,如表3-12。
表3-12-1 GreatePen 函數
用 途 |
創建一支具有指定類型、寬度和顏色的邏輯筆。 |
原 型 |
HPEN CreatePen( |
|
int nPenStyle, |
筆的類型,見後面的說明 |
int nWidth, |
以邏輯單位定義的筆的寬度 |
COLORREF crColor |
RGB顏色值 |
) |
|
|
返回值 |
如果函數成功,則返回值是標識一個邏輯筆對象的句柄,否則爲NULL。 |
表3-12-2 GreatePenIndirect 函數
用 途 |
創建一支邏輯筆。 |
原 型 |
HPEN CreatePenIndirect( |
|
LOGPEN FAR *lpLogPen |
指向包含邏輯筆類型、寬度和顏色的一個LOGPEN類型的變量 |
) |
|
|
返回值 |
返回一個邏輯筆對象的句柄,如果不成功,返回NULL。 |
類型LOGPEN的說明爲:
typedef struct tagLOGPEN { WORD lopnStyle; // 筆的類型,見後表的說明。 POINT lopnWidth; // 筆的寬度(邏輯單位),若要爲0,則寬度爲一個象素寬。 COLORREF lopnColor; // 筆的顏色,爲RGB顏色值。 } LOGPEN;
常量 |
說明 |
PS_SOLID |
_______ |
PS_DASH |
------- |
PS_DOT |
....... |
PS_DASHDOT |
_._._._ |
PS_DASHDOTDOT |
_.._.._.._ |
PS_NULL |
空筆 |
PS_INSIDEFRAME |
________ |
筆的類型由上列常量定義
|
注意:如果筆的寬大於1,並且筆的類型爲PS_INSIDEFRAME,則線條被畫在多邊形基本框架裏邊;如果筆的寬度大小或等於1,則類型PS_INSIDEFRAME與類型PS_SOLIDE完全相同;如果筆的顏色不能與可利用的RGB值相匹配,則PS_INSIDEFRAME類型的筆的顏色使用邏輯顏色,它通過將幾種純顏色混合形成所需要的顏色。物理寬度大於1象素的筆總爲空或實類型的筆。 下面的程序片段使用函數CreatePen創建一支紅色筆,並選入到設備對象中:
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255,0,0)); hPen = SelectObject(hDC, hPen);
當程序創建的筆不再使用時,應使用函數DeletObject將其刪除:
DeleteObject(hPen);
但刪除庫存筆是非法的。庫存筆和程序員創建的任何寬度爲0的筆的實際寬度爲1個象素,這在高分辨率的顯示器上表示爲極細的線。如果當前的映射方式是MM_TEXT方式,最好通過調用帶有SM_CXBORDER和SM_CYBORDER索引的GetSystemMetrics(見表3-13)來獲得單線窗口邊框的寬度,將這些值用作筆寬。也可使用其他映射方式爲程序中創建的筆設置特定的物理寬度。
表3-13 GetSystemMetrics 函數
用 途 |
檢索系統度量。系統度量是Windows顯示的各種元素的寬和高。 |
原 型 |
int GetSystemMetrics( |
|
int nIndex |
指定要檢索的系統度量,見後面的說明 |
) |
|
|
返回值 |
返回所要求的以象素給出的系統度量。 |
常量 |
說明 |
SM_CXSCREEN |
屏幕的寬度 |
SM_CYSCREEN |
屏幕的高度 |
SM_CXFRAME |
能縮放窗口的邊框的寬度 |
SM_CYFRAME |
能縮放窗口的邊框的高度 |
SM_CXVSCROLL |
垂直滾動槓箭頭的位圖的寬度 |
SM_CYVSCROLL |
垂直滾動槓箭頭的位圖的高度 |
SM_CXHSCROLL |
水平滾動槓箭頭的位圖的寬度 |
SM_CYHSCROLL |
水平滾動槓箭頭的位圖的高度 |
SM_CXBORDER |
不能縮放的窗口的邊框的寬度 |
SM_CYBORDER |
不能縮放的窗口的邊框的高度 |
SM_CXDLGFRAME |
具有WS_DLGFRAME窗口的邊框的寬度 |
SM_CYDLGFRAME |
具有WS_DLGFRAME窗口的邊框的高度 |
SM_CXICON |
圖標的寬度 |
SM_CYICON |
圖標的高度 |
SM_CXCURSOR |
光標的寬度 |
SM_CYCURSOR |
光標的高度 |
SM_CXFULLSCREEN |
全屏幕窗口的窗口區寬度 |
SM_CYFULLSCREEN |
全屏幕窗口的窗口區高度 |
3.9、填充空隙 在點劃線、短劃線筆或影線刷子的使用中,點和短劃線之間的空隙以及刷子中的陰影間隙的着色取決於設備對象中定義的背景方式和背景顏色。缺省的背景方式是OPAQUE,即Windows用背景色填充空隙,缺省的背景顏色是白色,即與大多數程序在窗口類別中用作清除窗口背景的WHITE_BRUSH型刷子相一致。可以通過調用函數SetBkColor設背景色,使用函數GetBkColor獲得設備對象中定義的當前背景色(見表3-14)。
表3-14-1 SetBkColor 函數
用 途 |
設置當前的背景色。 |
原 型 |
DWORD SetBkColor( |
|
HDC hDC, |
設備對象句柄 |
COLORREF crColor |
新的背景色,RG顏色值 |
) |
|
|
返回值 |
返回作爲一種顏色值的當前的背景色。若小於0,則出錯。 |
註釋:如果背景方式爲OPAQUE,那麼GDI就用背景色填充設計的行距、筆、刷子中陰影間隙,以及各字符中的間隙。如果設備不能表示由crColor參數指定的RGB顏色值,此函數將當前背景色設置爲最接近的物理色(純顏色),並返回之。
表3-14-2 GetBkColor 函數
用 途 |
返回指定設備的當前背景色。 |
原 型 |
DWORD GetBkColor( |
|
HDC hDC |
設備對象句柄 |
) |
|
|
返回值 |
當前背景色的RGB顏色值。 |
當前背景方式決定Windows是否填充空隙。函數SetBkMode及GetBkMode用於設置和獲取背景方式,如表3-15。
表3-15-1 SetBkMode 函數
用 途 |
設置與文本和線型一起使用的背景方式。背景方式決定在顯示文本、刷子或非實線的普通筆型之前,GDI是否要將設備顯示錶面已有顯示內容用背景色清除。 |
原 型 |
int SetBkMode( |
|
HDC hDC |
設備對象句柄 |
int nBkMode |
背景方式。見後面說明 |
) |
|
|
返回值 |
返回OPAQUE或TRANSPARENT,以說明先前的背景方式。 |
常量 |
說明 |
OPAQUE |
在文本、刷子或筆繪圖之前,用當前背景色填充設備顯示錶面 |
TRANSPARENT |
背景保留不變 |
表3-15-2 GetBkMode 函數
用 途 |
返回設備的背景方式。 |
原 型 |
int GetBkMode( |
|
HDC hDC |
設備對象句柄 |
) |
|
|
返回值 |
返回當前背景方式(OPAQUE或TRANSPARENT)。 |
3.10、設置文本屬性 有一些設備對象屬性影響文本。缺省時設備對象所繪製的文本顏色爲黑色。可以使用函數SetTextColor或函數GetTextColor設置或獲取設備對象中的文本顏色,如表3-17。
表3-16-1 SetTextColor 函數
用 途 |
設置文本顏色。 |
原 型 |
DWORD SetTextColor( |
|
HDC hDC |
設備對象句柄 |
COLORREF crColor |
指定的文本顏色(RGB值) |
) |
|
|
返回值 |
返回先前的文本顏色(RGB值)。 |
註釋:如果設備不支持指定的顏色,則設置成最接近的物理顏色。
表3-16-2 GetTextColor 函數
用 途 |
設置當前的文本顏色。 |
原 型 |
DWORD GetTextColor( |
|
HDC hDC |
設備對象句柄 |
) |
|
|
返回值 |
當前使用的文本顏色(RGB值)。 |
下面的程序是對3.4及3.7節的程序修改後所形成的一個示例程序,這個程序演示了在程序中設置文本屬性方法。
// 3-10.c #include <stdio.h> #include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int PASCAL WinMain( HINSTANCE hInstance, // 應用程序的實例句柄 HINSTANCE hPrevInstance, // 該應用程序前一個實例的句柄 LPSTR lpszCmdLine, // 命令行參數串 int nCmdShow ) // 程序在初始化時如何顯示窗口 { ... ... }
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBRUSH OrgBrush, hBrush; static int xChar, yChar; int line; char szBuffer[256]; HDC hDC; char ch; PAINTSTRUCT ps; TEXTMETRIC tm; FILE *fp;
switch(message) { case WM_CREATE: OrgBrush = GetClassWord(hwnd, GCW_HBRBACKGROUND); hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255, 255, 0) SetClassWord(hwnd, GCW_HBRBACKGROUND, hBrush); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); hDC = GetDC(hwnd); GetTextMetrics(hDC, &tm); xChar = tm.tmAveCharWidth; yChar = tm.tmHeight + tm.tmExternalLeading; ReleaseDC(hwnd, hDC); return 0;
case WM_PAINT: hDC = BeginPaint(hwnd, &ps); SetBkMode(hDC, OPAQUE); SetTextColor(hDC, RGB(255, 0, 0)); SetBkColor(hDC, RGB(0, 255, 0));
SetMapMode(hdc, MM_ANISOTROPIC); SetWindowExt(hdc, 1, 1); SetViewportExt(hdc, xChar, yChar);
line = 0; if((fp = fopen("3-10.c", "r")) != NULL) { while(!feof(fp)) { int i = 0; while((ch = fgetc(fp)) != '/n' && ch != EOF) szBuffer[i++] = (char)ch; TextOut(hDC, xChar, line*yChar, szBuffer, i); line++; } fclose(fp); } EndPaint(hwnd, &ps); return 0L;
case WM_DESTROY: SetClassWord(hwnd, GCW_HBRBACKGROUND, OrgBrush); DeleteObject(hBrush); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
修改這個程序,將背景方式設置爲TRANSPARENT,觀察程序的運行結果。
3.11、公用和私用顯示設備 在前面,我們曾介紹說,Windows維護着五個公用的顯示設備對象,當窗口對象需要繪製其用戶區時,使用函數GetDC()或BeginPaint()從Windows那裏借用。一般而言,函數GetDC()和BeginPaint()所借用來的公用顯示設備對象的初始屬性爲表3-1中所給出的缺省屬性,窗口對象可以使用有關的函數改變這些屬性,以滿足其繪製要求。在繪製工作完成以後,窗口對象應將顯示設備對象歸還給Windows。但注意,當調用ReleaseDC()或EndPaint()函數將公用顯示設備對象歸還Windows後,對顯示設備對象的屬性所做的改變也就丟失了。這樣,當窗口對象再次借用顯示設備對象時,顯示設備對象的初始屬性仍爲缺省屬性,窗口對象在每次使用非缺省屬性進行繪製之前,都必須使用有關的函數設置顯示設備對象的屬性。 另外的一種情況是當窗口對象在一段程序中改變了一些設備對象屬性,在使用改變後的設備對象屬性工作之後,窗口對象又要使用原先的設備對象屬性繼續工作,對這種情況,一種比較方便的方法是通過使用函數SaveDC()和RestoreDC來簡化對設備對象的管理,這兩個函數的使用說明見表3-17。
表3-16-1 SaveDC 函數
用 途 |
該函數用於保存其參數所標識的設備對象的當前屬性信息,它通過將這些屬性信息拷貝到一個設備對象環境棧中來實現。 |
原 型 |
int SaveDC( |
|
HDC hDC |
設備對象句柄 |
) |
|
|
返回值 |
返回一個序數值來反映被保存的設備對象,如果該函數在執行中出錯,則返回0。 |
註釋:該函數可被調用多次來分別保存多個設備對象的多個屬性信息。
表3-16-2 RestoreDC 函數
用 途 |
恢復設備對象的屬性信息。 |
原 型 |
BOOL RestoreDC( |
|
HDC hDC |
設備對象句柄 |
int nSaveDC |
指定要被恢復的屬性信息,可以是函數SaveDC()返回的值或爲-1,若爲-1,則設備對象被恢復爲設備對象環境棧頂所保存的屬性信息。 |
) |
|
|
返回值 |
非零表示恢復成功,零表示失敗。 |
註釋:該函數通過拷貝設備對象環境棧中保存的屬性信息來恢復指定的設備對象屬性。如果由參數nSaveDC指定的設備對象信息不處於環境棧的棧頂,那以函數RestoreDC()會不斷地刪除棧頂信息,直到找到由參數nSaveDC所指定的設備對象信息爲止,這些被刪除的信息永遠被丟失了。 下面的程序片段用於演示函數SaveDC()和RestoreDC()使用方法:
// ...... int nSaveID = SaveDC(hDC); SetBkMode(hDC, OPAQUE); SetTextColor(hDC, RGB(255, 0, 0)); SetBkColor(hDC, RGB(0, 255, 0));
SetMapMode(hDC, MM_ANISOTROPIC); SetWindowsExt(hDC, 1, 1); SetViewportExt(hDC, xChar, yChar); // ...... RestoreDC(nSaveID); // ......
使用公用顯示設備對象的優點是,這些顯示設備對象爲系統中的所有應用程序共享。但不足之處在於存在與借用、設置和歸還顯示設備對象有關的許多工作,對一些複雜的繪圖程序,或使用非缺省屬性的應用程序,使用公用顯示設備對象會增加程序的編碼量,並有可能影響到應用程序的效率。對這些情況,程序員可以爲一個或多個窗口對象指定使用私用顯示設備對象,當在註冊窗口類時,爲該窗口類指定CS_OWNDC風格參數,這時,由這種窗口類所創建的窗口對象都擁有自己的私用顯示設備對象。 使用私用顯示設備對象的優點在於,由於私用的顯示設備對象僅供一個窗口對象專用,這樣,窗口對象在一條消息處理完備以後從窗口函數返回之前也不需要歸還顯示設備對象。窗口對象可以將顯示設備對象的句柄保存在一個靜態生存期的變量中,供自己使用。例如,窗口對象在處理WM_CREATE消息時設置設備的屬性,而在處理WM_PAINT或其他消息時直接使用所設置對象屬性。
// 3-11.c #include <stdio.h> #include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { char szAppName[] = "DispText"; HWND hwnd; MSG msg; WNDCLASS wndclass; HBRUSH hBrush; HDC hDC; int xChar, yChar; TEXTMETRIC tm;
hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255, 255, 0)); if (!hPrevInstance) { wndclass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = hBrush; wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass)) return FALSE; }
hwnd = CreateWindow( szAppName, "Display Text", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
hDC = GetDC(hwnd); GetTextMetricse(hDC, &tm); xChar = tm.tmAveCharWidth; yChar = tm.tmHeight + tm.tmExternalLeading;
SetBkMode(hDC, OPAQUE): SetTextColor(hDC, RGB(255, 0, 0)); SetBkColor(hDC, RGB(0, 255, 0)); SetMapMode(hDC, MM_ANISOTROPIC); SetWindowExt(hDC, 1, 1) SetViewportExt(hDC, xChar, yChar); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
while( GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } ReleaseDC(hwnd, hDC); DeleteObject(hBrush);
return msg.wParam; }
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { int line; char szBuffer[256]; HDC hDC; char ch; PAINTSTRUCT ps; FILE *fp;
switch(message) { case WM_PAINT: hDC = BeginPaint(hwnd, &ps);
line = 0; if((fp = fopen("3-11.c", "r")) != NULL) { while(!feof(fp)) { int i = 0; while((ch = fgetc(fp)) != '/n' && ch != EOF) szBuffer[i++] = (char)ch; TextOut(hDC, xChar, line*yChar, szBuffer, i); line++; } fclose(fp); } EndPaint(hwnd, &ps); return 0;
case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }
在一個窗口對象使用私用顯示設備對象的情況下,函數GetDC()和BeginPaint()返回的句柄標識的設備對象是窗口的私用顯示設備對象。從這個程序還可以看出,私用的顯示設備對象保持當前設置的設備屬性,對私用的顯示設備對象,不存在“借用”和“歸還”之說。但使用CS_OWNDC隻影響到從GetDC和BeginPaint檢索到的設備對象,而不會影響到使用別的函數(例如GetWindowDC()等函數)檢索到的設備對象。 一個私用顯示設備對象大約佔用200字節,所有的字節都從GDI模塊的局部堆中分配,而這個堆由系統中當前正運行的所有應用程序共享。如果系統中的所有應用程序都使用私用顯示設備對象,那將會用完GDI的局部堆,導致系統崩潰。因此,除非必要,應用程序一般不使用私用顯示設備對象。 介於使用公用和私用顯示設備對象之間的是使用類顯示設備對象。若在註冊窗口類時指定了CS_CLASSDC風格參數,則該窗口類擁有一個可供該類所有窗口對象(包括在該應用程序的其它實例中所創建的窗口對象)共用的顯示設備對象。這種類型的顯示設備對象被稱爲類顯示設備對象。類顯示設備對象由於被該類的所有窗口對象共享,因此,一個窗口對象在使用類顯示設備對象時需要從類中借用,並在使用完之後歸還給類,否則,該類的其它對象就無法使用類顯示設備對象。但有一點應引起注意,類顯示設備對象同私用顯示設備對象一樣,也保持當前設置的屬性,即使在歸還類的情況下也是如此。這就是說,如果該類的一個窗口對象改變了該顯示設備對象的屬性,那麼,也要影響到基於該窗口類所創建的所有窗口對象(包括在該應用程序的其它實例中所創建的窗口對象)。因此,類顯示設備對象較難使用,但它相對於私用顯示設備對象而言,可以節省一些內存空間。
|