•GDI介紹
GDI(Graphics Device Interface), 圖形設備接口。
GDI的作用:
負責系統與繪圖程序之間的信息交換,處理Windows程序的圖形輸出。
GDI的特點:
1>. 開發者無需關心物理硬件的設備類型;
2>. 不允許應用程序直接訪問物理顯示硬件, 通過獲取顯示設備的"設備環境"句柄實現與顯示硬件的間接通信;
3>. 程序與物理顯示設備進行通信時, 必須首先獲得與特定窗口相關聯的設備環境;
4>. Windows根據設備環境的數據結構完成信息的輸出。
在第六天的學習中學習了以上的內容, 這次將較為詳細的介紹下GDI相關的知識。
GDI是由許多的函數組成的一套完善的圖形輸出系統, 這些函數大約有600個左右, 主要負責系統與外部硬件的交互工作, 例如, 顯示器上的圖形、文字的輸出, 打印機的打印工作等。應用程序開發者可以調用這些函數完成應用程序在屏幕上的信息顯示、圖形的繪制等工作, 使用GDI的一大優點就是可以做到無需關心用戶PC的顯示器等輸出硬件的實際類型, 只需要調用這些GDI函數告訴系統將要輸出就可以了, 系統會根據不同硬件中的設備驅動程序中的函數, 驅動函數再將GDI命令轉換為該硬件設備能夠理解的代碼或者命令完成輸出, 避免的應用程序直接與硬件打交道, 降低了應用程序開發的難度, 同時也避免了輸出過程中的各種繁瑣的情況。
不僅如此, Windows自身也使用GDI來顯示相關的圖形, 比如最常見的菜單、滾動條、應用程序圖標、鼠標指針等, 但是需要注意的是, GDI只是一套靜態顯示系統, 對動態快速頻繁的輸出, 例如較為復雜的游戲、視頻的播放等就顯得力不從心了, 編寫復雜的動畫輸出請參考微軟的DirectX(多媒體編程接口)或OpenGL(一個專業的跨平台圖形程序接口)。
這些GDI函數主要來自動態鏈接庫的GDI32.dll文件中, 通過將dll中的函數導出對圖形進行處理, 要查看這些函數的具體名稱可以從WINGDI.H頭文件中尋找, 或者直接通過命令將GDI32.dll中的函數名稱導出:
通過命令提示符進入到system32文件夾, 執行:
Dumpbin -exports gdi32.dll
這樣就可以在命令提示符窗口中看到相關的信息了, 不過在命令提示符窗口中是無法顯示完全的, 要全部顯示可以將信息輸出到文本中, 命令如下:
Dumpbin -exports gdi32.dll >> out.txt
這樣dll中的信息就會被導出到out.txt文件中。
在system32文件夾中同樣還有一個GDI.exe文件, 它的作用是負責16位程序的圖像輸出。
在諸多的GDI函數中從功能上來說實際上可以主要分為以下5種類型:
1>. 獲取、釋放設備環境的函數;
2>. 獲取設備環境信息的函數;
3>. 繪圖函數;
4>. 設置、獲取設備環境屬性的函數;
5>. 使用GDI"對象"的函數;
在正式開始學習繪圖之前, 還應該先了解下在屏幕上繪圖的相關流程, 首先, 獲取設備環境的句柄, 當獲取成功時就意味着你的應用程序有了在屏幕上繪圖的權限, 然后你就可以調用GDI中的繪圖函數通過設備環境句柄對屏幕進行繪制, 等繪制結束后你應該釋放這個句柄。
1. 獲取設備環境句柄
Windows提供了許多種方法用來獲取不同的設備環境句柄, 這里不能一次性講全, 當前我們需要使用的主要有以下幾種:
1>. 使用BeginPaint函數和EndPaint函數:
BeainPaint函數原型:
HDC BeginPaint( HWND hwnd, // 窗口的句柄 LPPAINTSTRUCT lpPaint // 繪制信息 );
參數二為PAINTSTRUCT類型的結構, 函數的返回值就是設備環境句柄, PAINTSTRUCT結構定義在WINUSER.H頭文件中, 如下:
typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; } PAINTSTRUCT ;
在該結構中的第一個成員HDC的值正是設備環境句柄, 函數返回的設備環境句柄也正是來源於此。
通過BeginPaint函數來獲取設備環境句柄通常用於處理WM_PAINT消息時, 一般的使用結構:
hdc = BeginPaint( hwnd, &ps ) ; [相關的處理語句] EndPaint( hwnd, &ps ) ; //釋放設備環境句柄
2>. 使用GetDC函數
GetDC函數可以用來獲取制定窗口句柄的設備環境句柄, 同時也可以獲取整個屏幕的設備環境句柄, 其函數原型為:
HDC GetDC( HWND hWnd ) ; //函數參數為窗口句柄
當函數調用成功時返回設備環境的句柄, 失敗返回NULL, 當函數參數為NULL時返回的就是整個屏幕的設備環境句柄。
當設備環境句柄使用完成后應該及時使用ReleaseDC函數釋放該設備環境句柄, 值得一提的是, GetDC和ReleaseDC函數不能使無效的客戶區變成有效, 當使用GetDC方式重繪完成后可以顯性調用ValidateRect函數使其有效, ValidateRect原型:
BOOL ValidateRect( HWND hWnd, // 窗口的句柄 CONST RECT *lpRect // 指向RECT結構的指針 ) ;
參數一為被有效化的窗口句柄, 若該參數為NULL, 系統將更新所有的窗口; 參數二為一個包含需要生效的矩形的更新區域坐標的RECT結構體, 當參數為NULL時, 整個客戶區都將有效化。
使用的一般形式:
hdc = GetDC( hwnd ) ;
[相關的處理語句]
ReleaseDC( hwnd, hdc ) ;
3>. 使用GetWindowDC
與GetDC不同, GetDC可以用來獲取窗口的客戶區部分的設備環境句柄, 而GetWindowDC是用來獲取整個窗口的設備環境句柄, 整個窗口是指包括窗口的標題欄、菜單欄、滾動條、狀態欄以及客戶區和客戶區的外緣邊框部分, 函數原型:
HDC GetWindowDC( HWND hWnd // 窗口句柄 );
在使用完成后同樣要使用ReleaseDC對設備環境句柄進行釋放, 使用的一般形式:
hdc = GetWindowDC( hwnd ) ;
[相關的處理語句]
ReleaseDC( hwnd, hdc ) ;
4>. 使用CreateDC
CreateDC的作用是通過使用指定的名字為一個設備創建一個設備環境句柄, 在使用完成后應當由DeleteDC函數進行刪除釋放, 而不是ReleaseDC。
函數原型:
HDC CreateDC( LPCTSTR lpszDriver, LPCTSTR lpszDevice, LPCTSTR lpszOutput, const DEVMODE *lpInitData );
參數一LPCTSTR lpszDriver指向一個以NULL結尾的字符串的指針, 當字符串為TEXT("DISPLAY")時,是獲取整個屏幕的設備環境, 為TEXT("WINSPOOL")則是訪問打印驅動的設備環境;
注意: 當參數為TEXT("WINSPOOL")時其他參數均為NULL。
參數二LPCTSTR lpszDevice指向一個以null結尾的字符串的指針, 該字符串指定了正在使用的特定輸出設備的名字;
參數三LPCTSTR lpszOutput在32位環境下通常被忽略, 並置為NULL, 該參數的存在主要是為了提供與16位應用程序兼容;
參數四const DEVMODE *lpInitData指向包含設備驅動程序的設備指定初始化數據的DEVMODE結構的指針, DEVMODE數據結構中包含了有關設備初始化和打印機環境的信息 , 如果設備驅動程序使用用戶指定的缺省初始化值。則lplnitData參數必須為NULL。
對於這個函數可能暫時有點難以理解, 不過暫時也用不到他, 現在只需要記住一條用法:
hdc = CreateDC( TEXT( "DISPLAY" ), NULL, NULL, NULL ) ; //獲取當前整個屏幕的設備環境句柄
在使用完成后記住要使用DeeteDC進行釋放, 而不是ReleaseDC。
2. 獲取設備環境信息
獲取設備環境信息, 也成屬性, 通常是指物理硬件的的某些信息, 比如顯示器的分辨率、尺寸、色彩能力等, 要獲取設備環境的信息首先要得到設備環境句柄, 然后通過GetDeviceCaps函數獲取, GetDeviceCaps的原型:
int GetDeviceCaps( HDC hdc, int nIndex );
參數nIndex用來指明需要獲取的信息類型, 例如當參數為HORZRES時, 函數返回以像素為單位的設備環境的寬度, HORZRES是定義在WINGDI.H中的29個標識符之一, 這29個標識符如下:
/* Device Parameters for GetDeviceCaps() */ #define DRIVERVERSION 0 /* Device driver version */ #define TECHNOLOGY 2 /* Device classification */ #define HORZSIZE 4 /* Horizontal size in millimeters */ #define VERTSIZE 6 /* Vertical size in millimeters */ #define HORZRES 8 /* Horizontal width in pixels */ #define VERTRES 10 /* Vertical height in pixels */ #define BITSPIXEL 12 /* Number of bits per pixel */ #define PLANES 14 /* Number of planes */ #define NUMBRUSHES 16 /* Number of brushes the device has */ #define NUMPENS 18 /* Number of pens the device has */ #define NUMMARKERS 20 /* Number of markers the device has */ #define NUMFONTS 22 /* Number of fonts the device has */ #define NUMCOLORS 24 /* Number of colors the device supports */ #define PDEVICESIZE 26 /* Size required for device descriptor */ #define CURVECAPS 28 /* Curve capabilities */ #define LINECAPS 30 /* Line capabilities */ #define POLYGONALCAPS 32 /* Polygonal capabilities */ #define TEXTCAPS 34 /* Text capabilities */ #define CLIPCAPS 36 /* Clipping capabilities */ #define RASTERCAPS 38 /* Bitblt capabilities */ #define ASPECTX 40 /* Length of the X leg */ #define ASPECTY 42 /* Length of the Y leg */ #define ASPECTXY 44 /* Length of the hypotenuse */ #if(WINVER >= 0x0500) #define SHADEBLENDCAPS 45 /* Shading and blending caps */ #endif /* WINVER >= 0x0500 */ #define LOGPIXELSX 88 /* Logical pixels/inch in X */ #define LOGPIXELSY 90 /* Logical pixels/inch in Y */ #define SIZEPALETTE 104 /* Number of entries in physical palette */ #define NUMRESERVED 106 /* Number of reserved entries in palette */ #define COLORRES 108 /* Actual color resolution */
關於這些標識符的詳細介紹可查閱MSDN -> GetDeviceCaps函數對參數int nIndex的解釋。
3. 繪圖函數
繪圖函數就是GDI中最重要的部分了, 其中有許多的繪圖函數, 這里同樣不能一次講完, 也不打算講完, 這些都沒有什么意義, 只需要查閱下MSDN或Google下找到這些函數並嘗試着使用它就行了, 這里僅描述幾個最基本的繪圖函數。
1>. SetPixel繪制像素點
這是一個十分不常用的函數, 如果你想拿這個來繪制一條直線, 那么效率就太低了, 這個函數為 SetPixel, 其函數的原型為:
COLORREF SetPixel( HDC hdc, int X, //x坐標 int Y, //y坐標 COLORREF crColor //顏色, 使用RGB宏 );
RGB宏定義在WINGDI.H中, 定義如下:
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
RGB宏有三個參數, 將紅(R)、綠(G)、藍(B)組合成一個無符號型的長整形用來表示顏色, 當三個參數都為0時顏色為黑色, 都為255時顏色為白色。
使用示例:
//在屏幕坐標為( 100, 100 )將該像素置為黑色 SetPixel( hdc, 100, 100, RGB( 0, 0, 0 ) ) ;
2>. LineTo繪制直線
BOOL LineTo( HDC hdc, int nXEnd, //結束x坐標 int nYEnd //結束y坐標 );
該函數需要和MoveToEx函數配合使用才能制定設備環境上的任意兩點間繪制一條直線, MoveToEx函數的作用就是規定直線的起點坐標, 其函數原型為:
BOOL MoveToEx( HDC hdc, int X, //起點x坐標 int Y, //起點y坐標 LPPOINT lpPoint //一個POINT結構, 用來接收當前位置, 為NULL表示不接收 );
使用示例:
//繪制一條起點為( 100, 100 ), 終點為( 500, 100 )的水平直線 MoveToEx( hdc, 100, 100, NULL ); LineTo( hdc, 500, 100 );
3>. Polyline繪制折線
函數原型:
BOOL Polyline( HDC hdc, const POINT *lppt, //指向POINT結構數組 int cPoints //在該組坐標中所使用的點數, 必須 >= 2 );
用法示例:
//繪制一條折線 POINT apt[5] = { {100, 100}, {300, 200}, {100, 400}, {400, 300}, {500, 200} } ; Polyline( hdc, apt, 5 ) ;
4>. PolyPolyline繪制多條折線
PolyPolyline實際上是對Polyline函數中使用的坐標組提供了分組功能, 分為幾組就意味着繪制多少條折線, PolyPolyline函數的原型:
BOOL PolyPolyline( HDC hdc, const POINT *lppt, //坐標組 const DWORD *lpdwPolyPoints, //對坐標進行分組 DWORD cCount //折線條數 );
用法示例:
//8個點的坐標組 POINT apt[8] = { { 100, 100}, {200, 200}, {300, 200}, {200, 300}, {400, 200}, {600, 300}, {300, 400}, {500, 200} } ; DWORD lpPts[] = { 4, 4 }; //將這些點分為兩組, 4個點為一組 PolyPolyline( hdc, apt, lpPts, 2 ) ; //繪制兩條折線
5>. Rectangle繪制矩形
繪制一個矩形十分簡單, 只需要提供矩形左上角坐標與右下角坐標即可, 函數原型如下:
BOOL Rectangle( HDC hdc, int nLeftRect, //左上角x坐標 int nTopRect, //左上角y坐標 int nRightRect, //右下角x坐標 int nBottomRect //右下角y坐標 );
例如我們繪制一個正方形:
Rectangle( hdc, 50, 50, 200, 200 ) ;
看下效果圖:

可以看到, 確實是一個由四條直線圍成的矩形, 但是需要提醒的是, 這個矩形是經過自動填充的, 填充的顏色就是默認的白色顏色, 在這個圖中填充顏色為白色, 和背景顏色相同, 所以我們才不易覺察到這是一個經過填充的圖形, 被填充意思上就是說你以前在該矩形里或者說經過該矩形的圖形會被白色給覆蓋掉, 這點需要注意。
6>. Ellipse繪制橢圓
函數原型:
BOOL Ellipse( HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect );
繪制橢圓的參數和繪制矩形的參數是相同的, 那么他是如何繪制的呢? 這里用圖示說明下:

示例中的矩形參數與橢圓的參數是相同的, 很明顯的可以看出橢圓是根據矩形的大小自動填充的, 需要提示的是, 它填充的是整個矩形, 而不僅僅是自身的橢圓。
7>. RoundRect畫圓角矩形
函數原型:
BOOL RoundRect( HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidth, int nHeight );
相對於畫矩形函數這里又多出兩個參數int nWidth和int nHeight, 實際上, 這兩個參數是用來描述圓角的寬和高, 如圖:

8>. Arc弧線、Chord拱形與Pie扇形
之所以將這三個函數放在一起講是因為他們三個擁有相同的參數, 他們三個的函數原型如下:
BOOL Arc ( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ; BOOL Chord( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ; BOOL Pie ( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ;
這些參數都表示什么意思呢? 用文字恐怕不太好描述, 圖示如下:

在指定好外部矩形的坐標后剩下的就是指定繪制的起點坐標與終點了, 多嘗試幾次就能掌握其使用規律。
以上的這些就是GDI種較為基礎的繪圖函數了, 更多的繪圖函數請自行查閱MSDN Library。
下面練習使用下這些函數:
WinMain函數部分, 已折疊顯示:
View Code - WinMain Function
1 #include<windows.h> 2 3 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; 4 5 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) 6 { 7 static TCHAR szAppName[] = TEXT( "DrawDemo" ) ; 8 HWND hwnd ; 9 MSG msg ; 10 WNDCLASS wndclass ; 11 12 wndclass.lpfnWndProc = WndProc ; 13 wndclass.style = CS_HREDRAW | CS_VREDRAW ; 14 wndclass.hInstance = hInstance ; 15 wndclass.lpszClassName = szAppName ; 16 wndclass.hbrBackground = (HBRUSH) GetStockObject( WHITE_BRUSH ) ; 17 wndclass.cbClsExtra = 0 ; 18 wndclass.cbWndExtra = 0 ; 19 wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ) ; 20 wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ) ; 21 wndclass.lpszMenuName = NULL ; 22 23 if( !RegisterClass( &wndclass ) ) 24 { 25 MessageBox( NULL, TEXT( "錯誤, 無法注冊窗口類!" ), TEXT( "錯誤" ), MB_OK ) ; 26 return 0 ; 27 } 28 29 hwnd = CreateWindow( szAppName, TEXT( "Demo" ), 30 WS_OVERLAPPEDWINDOW, 31 CW_USEDEFAULT, CW_USEDEFAULT, 32 CW_USEDEFAULT, CW_USEDEFAULT, 33 NULL, NULL, hInstance, NULL ) ; 34 35 ShowWindow( hwnd, iCmdShow ) ; 36 UpdateWindow( hwnd ) ; 37 38 while( GetMessage( &msg, NULL, 0, 0 ) ) 39 { 40 TranslateMessage( &msg ) ; 41 DispatchMessage( &msg ) ; 42 } 43 44 return msg.wParam ; 45 }
窗口過程函數部分:
1 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 2 { 3 HDC hdc ; 4 PAINTSTRUCT ps ; 5 POINT apn[4] = { {150, 50}, {200, 200}, {150, 300}, {150, 500} } ; //坐標組 6 POINT apt[8] = { { 200, 50}, {300, 200}, {250, 200}, {200, 300}, {250, 300}, {300, 350}, {250, 400}, {250, 500} } ; //坐標組 7 DWORD lpPts[] = { 4, 4 }; //使用PolyPolyline進行多條折線繪制時的分組 8 int i ; 9 10 switch( message ) 11 { 12 case WM_PAINT: 13 hdc = BeginPaint( hwnd, &ps ) ; 14 15 //在(50, y)方向上繪制90個點, 顏色為黑色 16 for( i = 0; i < 90; i++ ) 17 SetPixel( hdc, 50, 50 + i * 5, RGB( 0, 0, 0 ) ) ; 18 19 //畫線, 起點坐標( 100, 50 ), 終點坐標( 100, 500 ) 20 MoveToEx( hdc, 100, 50, NULL ); 21 LineTo( hdc, 100, 500 ); 22 23 //畫一條折線 24 Polyline( hdc, apn, 4 ) ; 25 26 //畫2條折線, 將apt分為2組 27 PolyPolyline( hdc, apt, lpPts, 2 ) ; 28 29 //畫橢圓弧線 30 Arc(hdc, 350, 50, 500, 500, 400, 100, 400, 500 ) ; 31 32 //畫矩形 33 Rectangle( hdc, 450, 50, 500, 500 ) ; 34 35 //畫橢圓 36 Ellipse( hdc, 550, 50, 600, 500 ) ; 37 38 //畫圓角矩形 39 RoundRect( hdc, 650, 50, 700, 500, 20, 20 ) ; 40 41 //畫扇形 42 Pie( hdc, 750, 50, 850, 150, 850, 80, 850, 160 ) ; 43 44 //畫拱形 45 Chord( hdc, 750, 400, 850, 500, 850, 450, 750, 450 ) ; 46 47 EndPaint( hwnd, &ps ) ; 48 return 0 ; 49 50 case WM_DESTROY: 51 PostQuitMessage( 0 ) ; 52 return 0 ; 53 } 54 55 return DefWindowProc( hwnd, message, wParam, lParam ) ; 56 }
編譯運行后的效果圖:

如果我們想給這些圖形填充顏色或者改變其線條的粗細該怎么做呢? 很簡單, 下一次學習畫筆的使用時就可以輕松解決這些問題。
--------------------
wid, 2012.11.07
上一篇: C語言Windows程序設計-> 第八天-> 滾動條
