MFC GDI繪圖基礎


一.關於GDI的基本概念

什么是GDI

Windows繪圖的實質就是利用Windows提供的圖形設備接口GDI(Graphics Device Interface)將圖形繪制在顯示器上。

在Windows操作系統中,動態鏈接庫C:/WINDOWS/system32/gdi32.dll(GDI Client DLL)中定義了GDI函數,實現與設備無關的包括屏幕上輸出像素、在打印機上輸出硬拷貝以及繪制Windows用戶界面功能。在Visual C++6.0中的頭文件C:/Program Files/Microsoft Visual Studio/VC98/Include/wingdi.h和Visual Studio 2005中的頭文件C:/Program Files/Microsoft Visual Studio 8/VC/PlatformSDK/Include/WinGDI.h是訪問gdi32.dll庫文件的鑰匙。下面我們大致瀏覽一下wingdi.h(included in Windows.h)頭文件:

/* Bitmap Header Definition */定義了BITMAP位圖結構

/* Mapping Modes */定義了DC中的坐標映射方式,包括以下常用函數:

SetMapMode、SetViewportExtEx、SetViewportOrgEx、 SetWindowExtEx 、SetWindowOrgEx。

/* Stock Logical Objects */系統預定義的堆(STOCK)對象,包括BRUSH、PEN和FONT對象

/* Brush Styles */定義了畫刷格式,包括SOLID、HOLLOW、HATCHED等格式

/* Hatch Styles */定義了畫刷陰影格式,包括:

HS_VERTICAL    /* ||||| */

HS_FDIAGONAL  /* ///// */

HS_BDIAGONAL  /* ///// */

HS_CROSS       /* +++++ */

HS_DIAGCROSS  /* xxxxx */

/* Pen Styles */定義了畫筆格式,包括SOLID、DASH、DOT等格式

什么是DC?

設備環境DC(Device Context),也稱為設備描述表或設備上下文。

設備環境保存了繪圖操作中一些共同需要設置的信息,如當前的畫筆、畫刷、字體和位圖等圖形對象及屬性,以及坐標映射、顏色和背景等影響圖形輸出的繪圖模式。形象的說,一個設備環境提供了一張畫布和一些繪畫的工具,我們可以使用不同格式、顏色的繪畫工具在上面塗鴉。這里,設備環境中的“設備”是指任何類型的顯示器或打印機等輸出設備,繪圖時,我們不必關心所使用設備的編程的原理和方法,所有的繪制操作必須通過設備環境進行間接的處理,Windows會自動將設備環境所描述的結構映射到相應的物理設備上。

從根本上來說,DC它是Windows內部使用的數據結構,它存儲着向設備輸出時說需要的信息,應用程序利用它定義圖形對象及其屬性,並實現應用程序、設備驅動程序和輸出設備之間繪圖命令的轉換。要想調用GDI函數向某個區域輸出文字或繪制圖形,必須先取得或建立設備環境句柄,應用程序每一次繪圖操作均按照設備環境中的設置的繪圖屬性進行。

設備環境不像其他Windows結構,在程序中不能直接存取設備環境結構,只能通過系統提供的一系列函數或使用設備環境的句柄HDC來間接地獲取或設置設備環境結構中的各項屬性,這些屬性包括顯示器高度和寬度、支持的顏色數和分辨率等。

MFC中與GDI有關的類

為了支持GDI繪圖,MFC提供了兩種重要的類:設備環境DC(Device Context)類,用於設置繪圖屬性和繪制圖形;繪圖對象類,封裝了各種GDI繪圖對象,包括畫筆、刷子、字體、位圖、調色板和區域。

在MFC中,CDC是設備環境類的基類,除了一般的窗口顯示外,還用於基於桌面的全屏幕繪制和非屏幕顯示的打印機輸出。CDC類封裝了所有圖形輸出函數,包括矢量、光柵和文本輸出。CDC的派生類包括CClientDC、CPaintDC、WindowDC、CMetaFileDC。

 

 

(1)CPaintDC類是一個來自CDC的設備環境類。它在構造期間執行CWnd::BeginPaint,在析構期間執行CWnd::EndPaint,EndPaint()除了釋放設備環境外,還負責從消息隊列中清除WM_PAINT消息。一個CPaintDC對象只在響應一個窗口重繪消息(WM_PAINT)的時候被使用,通常是在你的OnPaint消息處理成員函數中。因此,在處理窗口重畫時,必須使用CPaintDC,否則WM_PAINT消息無法從消息隊列中清除,將引起不斷的窗口重畫。

CPaintDC類成員:

數據成員

m_ps:包含了用於畫客戶區的PAINTSTRUCT 

m_hWnd: CPaintDC對象所附着的HWND 

構造函數CPaintDC:構造一個連接到指定的CWnd上的CPaintDC對象

(2)CClientDC(窗口客戶區設備環境)類用於管理窗口用戶區對應的顯示上下文,它在構造時調用了Windows函數GetDC,在析構時調用了ReleaseDC。這意味着和CClientDC對象相關的設備上下文是窗口的客戶區。一般在響應非窗口重畫消息(如鍵盤輸入時繪制文本、鼠標繪圖)繪圖時要用到它。

CClientDC類的成員:

構造函數CClientDC:構造一個連接到CWnd上的CClientDC對象數據成員

數據成員m_hWnd:所在的有效窗口的HWND

(3)CWindowDC(窗口設備環境)類用於管理與整個窗口對應的顯示上下文,包括它的結構和控件。它在構造的時候調用Windows函數GetWindowDC,在銷毀的時候調用ReleaseDC。這意味着CWindowDC對象可以訪問CWnd的全部屏幕區域(包括客戶區和非客戶區)。它用於窗口(包括窗口邊框、標題欄、控制按鈕等)的繪制,除非要自己繪制窗口邊框和按鈕(如一些CD播放程序等),否則一般不用它。

 CWindowDC類成員:

 Construction CWindowDC:構造一個CWindowDC對象

 Data Members m_hWnd:與這個CWindowDC相關聯的HWND句柄

(4)CMetaFileDC專門用於圖元文件的繪制。圖元文件記錄一組GDI命令,可以通過這一組GDI命令重建圖形輸出。使用CMetaFileDC時,所有的圖形輸出命令會自動記錄到一個與CMetaFileDC相關的圖元文件中。

(5)此外我們還可以利用Windows內存DC進行繪圖,此時涉及到屏幕DC和內存DC。把所要繪制的一切先在內存DC中進行繪制,之后全部搬到屏幕DC中,從而把所有繁瑣的繪制過程都在內存DC中完成了,我們在屏幕上看到的是一幅完整的圖畫,所以不可能出現閃爍的情況。

 

二.MFC中GDI繪圖

    GDI繪圖包括以下步驟:獲取設備環境,設置坐標映射,創建繪圖工具,調用DC繪圖函數繪圖。

 

1、獲取設備環境

(1)在SDK編程中,獲取設備環境的方法有兩種:

<1>通過API函數BeginPaint。應用程序響應WM_PAINT消息進行圖形刷新時主要通過BeginPaint函數獲取設備環境,在消息處理函數返回前調用API函數EndPaint釋放設備環境。

函數原型為:WINUSERAPI HDC WINAPI BeginPaint( HWND hWnd,LPPAINTSTRUCT lpPaint);

    //以下為Win API示例::BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);

     case WM_PAINT://窗口客戶區需要重繪

     {

         char szText[]="Hello World";

         PAINTSTRUCT ps;

         HDC hdc=::BeginPaint(hWnd,&ps);

         ::TextOut(hdc,10,10,szText,strlen(szText));

         ::EndPaint(hWnd,&ps);

         return 0;

}

MFC對BeginPaint進行了封裝:

CWnd::BeginPaint,CDC* BeginPaint( LPPAINTSTRUCT lpPaint ); 等價於::BeginPaint(CWnd::m_hWnd, LPPAINTSTRUCT lpPaint);

<2>通過API函數GetDC。在非WM_PAINT消息處理函數中,需要調用GetDC來獲取設備環境,調用API函數ReleaseDC來釋放設備環境。

函數原型為:WINUSERAPI HDC WINAPI GetDC( HWND hWnd);

(2)在MFC中,MFC提供了不同類型的DC類,每一個類都封裝了DC句柄,並且它們的構造函數自動調用獲取DC的API函數,析構函數自動調用釋放DC的API函數。因此,在程序中通過聲明一個MFC設備環境類的對象就自動獲取了一個DC,而當該對象被銷毀時就自動釋放了獲取的DC。MFC AppWizard應用程序向導創建的OnDraw()函數自動支持所獲取的DC。 

<1> CPaintDC構造函數:CPaintDC(CWnd* pWnd); 構造一個CPaintDC對象(pWnd指向一個CPaintDC對象所屬的CWnd對象),准備用於繪畫的應用程序窗口。

// BeginPaint

void CView::OnPaint()

{

         CPaintDC dc(this); // device context for painting

         // TODO: Add your message handler code here

         OnPrepareDC(&dc);

         OnDraw(&dc)

}

當我們改變了窗口尺寸、移動窗口或恢復了先前被覆蓋的部分,應用程序窗口就會收到一個Windows系統發送來的WM_PAINT消息,然后調用基類Cview的OnPaint函數或我們自己添加的消息處理函數OnPaint。我們可以在OnPaint函數中重繪窗口中重新可見的部分(),但簡單的處理辦法是重繪整個窗口。上面的代碼中,由於基類Cview的OnPaint函數調用了OnDraw函數,因此應用程序經常在OnDraw函數中繪制視圖。

<2>CClientDC構造函數:CClientDC(CWnd* pWnd); 構造一個CClientDC對象,它將存取pWnd指向的CWnd的客戶區。

// 鼠標左鍵事件處理

void CExView::OnLButtonDown(UINT nFlags, CPoint point)

{

          // TODO: 在此添加消息處理程序代碼和/或調用默認值

          CClientDC dc(this);//定義客戶區設備環境

          dc.LineTo(point);//繪制線段

}

CClientDC代表了窗口客戶區對應的顯示上下文,它在構造時調用了API函數GetDC,並將當前窗口的句柄m_hWnd作為函數參數;在析構時調用了API函數ReleaseDC。當在客戶去繪圖時,需要利用CClientDC類定義一個客戶區設備環境句柄。

有時候需要訪問與一個客戶設備環境相關聯的窗口對象,可以通過Attach函數把這個CClientDC的成員m_hWnd句柄傳遞給一個窗口對象,該窗口就是與客戶區設備環境相關聯的窗口。

CWnd::Attach,BOOL Attach( HWND hWndNew );

說明:將一個Windows窗口與CWnd對象相連接。
返回值:如果成功,則返回非零值;否則返回0。
參數:hWndNew指定了Windows窗口的句柄

<3>CWindowDC構造函數:CWindowDC( CWnd* pWnd );構造一個CWindowDC對象,它可以訪問pWnd指向的CWnd對象的整個屏幕區域(包括客戶區和非客戶區)。比如我們在做屏幕保護程序時,一般以整個屏幕區域作為繪制區域。

 

2、設置坐標映射

   (1)Windows坐標系統

Windows坐標系分為邏輯坐標系和設備坐標系兩種,GDI支持這兩種坐標系。一般而言,GDI的文本和圖形輸出函數使用邏輯坐標,而在客戶區移動或按下鼠標的鼠標位置是采用設備坐標。

<1>邏輯坐標系是面向DC的坐標系,這種坐標不考慮具體的設備類型,在繪圖時,Windows會根據當前設置的映射模式將邏輯坐標轉換為設備坐標。

<2>設備坐標系是面向物理設備的坐標系,這種坐標以像素或設備所能表示的最小長度單位為單位,X軸方向向右,Y軸方向向下。設備坐標系的原點位置(0, 0)不限定在設備顯示區域的左上角。

設備坐標系分為屏幕坐標系、窗口坐標系和客戶區坐標系三種相互獨立的坐標系。

屏幕坐標系以屏幕左上角為原點,一些與整個屏幕有關的函數均采用屏幕坐標,如GetCursorPos()、SetCursorPos()、CreateWindow()、MoveWindow()。彈出式菜單使用的也是屏幕坐標。

窗口坐標系以窗口左上角為坐標原點,它包括窗口標題欄、菜單欄和工具欄等范圍。

客戶區坐標系以窗口客戶區左上角為原點,主要用於客戶區的繪圖輸出和窗口消息的處理。鼠標消息的坐標參數使用客戶區坐標,CDC類繪圖成員函數使用與客戶區坐標對應的邏輯坐標。

(2)坐標之間的相互轉換

編程時,有時需要根據當前的具體情況進行三種設備坐標之間或與邏輯坐標的相互轉換。

MFC提供了兩個函數CDC::DPtoLP()和CDC::LPtoDP()用於設備坐標與邏輯坐標之間的相互轉換。

MFC提供了兩個函數CWnd::ScreenToClient()和CWnd::ClientToScreen()用於屏幕坐標與客戶區坐標的相互轉換。

(3)映射模式

映射模式確定了在繪制圖形時所依據的坐標系,它定義了邏輯單位的實際大小、坐標增長方向,所有映射模式的坐標原點均在設備輸出區域(如客戶區或打印區)的左上角。此外,對於某些映射模式,用戶還可以自定義窗口的長度和寬度,設置視圖區的物理范圍。

Windows定義了8種映射模式,見下表。

映射模式使得程序員可不必考慮輸出設備的具體設備坐標系,而在一個統一的邏輯坐標系中進行圖形的繪制。

映射方法(Mapping Mode)

邏輯單位

坐標軸方向

MM_TEXT(默認方式)

1 pixel

X軸正方向朝右,Y軸正方向朝下

MM_LOMETRIC

0.1 mm

X軸正方向朝右,Y軸正方向朝上

MM_HIMETRIC

0.01 mm

X軸正方向朝右,Y軸正方向朝上

MM_LOENGLISH

0.01 inch

X軸正方向朝右,Y軸正方向朝上

MM_HIENGLISH

0.001 inch

X軸正方向朝右,Y軸正方向朝上

MM_TWIPS

1/1440 inch

X軸正方向朝右,Y軸正方向朝上

MM_ISOTROPIC

自定義(X=Y)

自定義

MM_ANISOTROPIC

自定義(X!=Y)

自定義

當繪制的圖形需要隨着窗口的大小改變而自動改變的時候,一般選擇MM_ISOTROPIC和MM_ANISOTROPIC映射方式。它們的唯一區別就是前者的X軸和Y軸的邏輯單位的大小是相同的,單詞“isotropic”就是各個方向相等的意思,此映射方式適合繪制圓或正方形。而實際應用中,常常給X軸和Y軸取不同的比例,這時候選擇MM_ANISOTROPIC映射方式。單詞“anisotropic”就是各個方向相異的意思。

(4)自定義映射模式

“窗口”和“視口”的概念:

窗口(Window):對應邏輯坐標系上程序員設定的區域

視口(Viewport):對應實際輸出設備上程序員設定的區域

窗口原點是指邏輯窗口坐標系的原點在視口(設備)坐標系中的位置,視口原點是指設備實際輸出區域的原點。

除了映射模式,窗口和視口也是決定一個點的邏輯坐標如何轉換為設備坐標的一個因素。一個點的邏輯坐標按照如下式子轉換為設備坐標:

設備(視口)坐標 = 邏輯坐標 –窗口原點坐標 + 視口原點坐標

//定義坐標映射方式

WINGDIAPI int   WINAPI SetMapMode(HDC, int);

此API函數在MFC中封裝為CDC::virtual int SetMapMode(int nMapMode);

//定義邏輯窗口區域,單位為邏輯單位(Logical)

WINGDIAPI BOOL  WINAPI SetWindowExtEx (HDC, int, int, LPSIZE);

此API函數在MFC中封裝為CDC::virtual CSize SetWindowExt(int cx, int cy);

//設置邏輯窗口的原點坐標,缺省原點為(0,0)。

WINGDIAPI BOOL  WINAPI SetWindowOrgEx(HDC, int, int, LPPOINT);

此API函數在MFC中封裝為CDC::CPoint SetWindowOrg(int x, int y);

注意:SetWindowOrg(Ex) 只有在映射模式為MM_ANISOTROPIC或MM_ISOTROPIC時才有意義。

//定義視口的坐標軸方向及區域、定義域和值域,單位為像素(Pixel)

WINGDIAPI BOOL  WINAPI SetViewportExtEx(HDC, int, int, LPSIZE);

此API函數在MFC中封裝為CDC::virtual CSize SetViewportExt(int cx, int cy);

注意:SetViewportExt(Ex) 只有在映射模式為MM_ANISOTROPIC或MM_ISOTROPIC時才有意義。

//設置視口的原點坐標,缺省原點為(0,0)。

WINGDIAPI BOOL  WINAPI SetViewportOrgEx(HDC, int, int, LPPOINT);

此API函數在MFC中封裝為CDC:: virtual CPoint SetViewportOrg(int x, int y);

參考:《GDI中的坐標映射問題》http://dev.csdn.NET/article/12/12013.shtm

 

3、創建繪圖工具並選入DC

有了畫布,要繪圖我們必須有畫筆畫刷。在Windows中有HPEN、HBRUSH等GDI對象,MFC對GDI對象進行了很好的封裝,提供了封裝GDI對象的類,如CPen、CBrush、CFont、CBitmap和CPalette等,這些類都是GDI對象類CGdiObject的派生類。

    一般先創建畫筆(刷),然后調用CDC::SelectObject函數將畫筆(刷)選入設備環境最為當前繪圖工具,繪圖完畢恢復設備環境以前的畫筆(刷)對象,最后調用CGdiObject::DeleteObject函數刪除畫筆(刷)對象。

    這里需要注意的是,CGdiObject::DeleteObject函數徹底刪除底層GDI對象(CPen和CBrush類的基類)。在MFC中,當對象銷毀時會調用對象的析構函數自動刪除對象,一般不必調用CGdiObject::DeleteObject刪除GDI對象,因為如果設備環境還在使用一個GDI對象時,將引起應用程序崩潰或出現難以理解的運行錯誤。

   (1)創建畫筆

BOOL CPen::CreatePen( int nPenStyle, int nWidth, COLORREF cfColor );

nPenStyle  指定畫筆的風格。其可能取值的列表,請參見CPen構造函數中的nPenStyle參數。

nWidth 指定畫筆的寬度。如果這個值為0,則不管是什么映射模式,以設備單位表示的寬度總是一個像素。

crColor 包含畫筆的一個RGB顏色,為COLORREF結構。

此外,可通過CDC::SelectStockObject函數來調用系統預定義的庫存筆對應的CGdiObject對象。

pOldPen = (Cpen*)pDC->SelectStockObject(BLACK_PEN);

(2)創建畫刷

BOOL CBrush::CreateSolidBrush ( COLORREF crColor );

BOOL CBrush::CreateHatchBrush( int nIndex, COLORREF crColor );

參數: nIndex 指定畫刷的陰影線風格。可取的值如下:

HS_HORIZONTAL   /* ==== */

HS_VERTICAL    /* ||||| */

HS_FDIAGONAL  /* ///// */

HS_BDIAGONAL  /* ///// */

HS_CROSS       /* +++++ */

HS_DIAGCROSS  /* xxxxx */

返回值:調用成功時返回非零值,否則為0。

此外,可通過CDC::SelectStockObject函數來調用系統預定義的庫存畫刷對應的CGdiObject對象。

pOldBrush = (CBrush*)pDC->SelectStockObject(BLACK_BRUSH);

(3)將畫筆(刷)選入設備環境。

以下為MFC中默認映射方式下的GDI繪圖的模塊:

//先獲取設備環境pDC

    CPen *pOldPen,newPen;

    CBrush *pOldBrush,newBrush1,newBrush2;

    //創建寬度為pixel的白色實線畫筆

    newPen.CreatePen(PS_SOLID,1,RGB(0,0,0));

    //創建紅色實線畫刷

    newBrush1.CreateSolidBrush(RGB(255,0,0));

    //創建紅色實線度的向下(從右到左)影線的陰影畫刷

    newBrush2.CreateHatchBrush(HS_BDIAGONAL,RGB(255,0,0));

    //將newPen畫筆和newBrush1畫刷對象選入設備環境

    pOldPen = pDC->SelectObject(&newPen);

    pOldBrush = pDC->SelectObject(&newBrush1);

    //調用DC繪圖函數繪圖

    //……

    //繪圖完畢,恢復原來畫筆、畫刷

    pDC->SelectObject(pOldPen);

pDC->SelectObject(pOldBrush);

//刪除創建的畫筆、畫刷

// newPen.DeleteObject();

// newBrush1.DeleteObject();

// newBrush2.DeleteObject();

   (4)當繪制文本Text時,一般可以通過調用CDC::SetBkColor函數來設置背景顏色,調用CDC::SetTextColor函數來設置文字顏色,調用CDC::SetTextAlign函數設置文本對齊標記。

 

4、調用DC繪圖函數繪圖

GDI為提供了繪制基本圖形的成員函數,在MFC中這些函數封裝在CDC類中。

注意:繪圖函數使用的坐標都是邏輯坐標。

常用CDC繪圖函數

函數

功能

線輸出函數

GetCurrentPosition

獲取筆的當前位置(以邏輯坐標表示)

MoveTo

移動當前位置

LineTo

從當前位置到一點畫直線,但不包括那個點

Arc

畫一段橢圓弧

ArcTo

畫一段橢圓弧。除了更新當前位置以外,這個函數與Arc類似

PolyPolyline

畫多組相連線段。這個函數不使用也不更新當前位置

PolylineTo

畫一條或多條直線,並把當前位置移到最后一條直線的終點

PolyBezier

畫一條或多條Bezier樣條。不使用也不更新當前位置

PolyBezierTo

畫一條或多條Bezier樣條,並把當前位置移到最后一條Bezier樣條的終點

 

 

橢圓和多邊形函數

Chord

繪制橢圓弧(橢圓和一條線段相交圍成的閉合圖形)

DrawFocusRect

繪制用於表示焦點的風格的矩形

Ellipse

繪制橢圓

Pie

繪制餅形圖

Polygon

繪制多邊形,包含由線段連接的一個或多個點(頂點)

PolyPolygon

創建使用當前多邊形填充模式的兩個或多個多邊形,多邊形可以相互分開或疊加

Polyline

繪制多邊形,包含連接指定點的一組線段

Rectangle

使用當前筆繪制矩形,用當前畫刷填充

RoundRect

使用當前筆繪制圓角矩形,用當前畫刷填充

位圖函數

BitBlt

從指定設備上下文拷貝位圖

StretchBlt

把位圖由源矩形和設備移動到目標矩形,必要時拉伸或壓縮位圖以適合目標矩形的維數

GetPixel

獲取指定點像素的RGB顏色值

SetPixel

設置指定點像素為最接近指定色的近似值

文本函數

TextOut

用當前選取字體在指定位置寫字符串

ExtTextOut

用當前選取字體在矩形區域寫字符串

TabbedTextOut

在指定位置寫字符串,制表符擴展為制表符停止位置數組中指定值

DrawText

在指定矩形內繪制格式化文本

------------------------------詳情參考MSDN、MFC類庫詳解---------------------------

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM