1 幾何對象的結構和類
為了使用繪圖函數,應該先了解繪圖所用到的幾種表示幾何對象的結構和類。這些結構和類分別定義在頭文件windef.h和afxwin.h中。
1.點
1)點結構POINT
點數據結構POINT用來表示一點的x、y坐標:
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT;
2)點類CPoint
點類CPoint為一個沒有基類的獨立類,封裝了POINT結構,有成員變量x和y,其構造函數有5種:
CPoint( );
CPoint( int initX, int initY );
CPoint( POINT initPt );
CPoint( SIZE initSize );
CPoint( LPARAM dwPoint ); // 低字設為x、高字設為y
CPoint類還定義了4個平移和設置函數:
void Offset(int xOffset, int yOffset);
void Offset(POINT point);
void Offset(SIZE size);
void SetPoint(int X, int Y);
CPoint類還重載了+、-、+=、-=、==、!=等運算符來支持CPoint對象和CPoint、POINT、SIZE對象之間的運算。
2.大小
1)大小結構SIZE
大小(size尺寸)結構SIZE用來表示矩形的寬cx和高cy:
typedef struct tagSIZE {
LONG cx;
LONG cy;
} SIZE;
2)大小類CSize
大小類CSize也為一個沒有基類的獨立類,封裝了SIZE結構,有成員變量cx和cy,其構造函數也有5種:
CSize( );
CSize( int initCX, int initCY );
CSize( SIZE initSize );
CSize( POINT initPt );
CSize( DWORD dwSize ); // 低字設為cx、高字設為cy
CSizet類也重載了+、-、+=、-=、==、!=等運算符來支持CSize對象和CSize、POINT、SIZE、RECT對象之間的運算。
3.矩形
1)矩形結構RECT
矩形結構RECT定義了矩形的左上角與右下角的坐標:
typedef struct tagRECT {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
2)矩形類CRect
矩形類CRect也為一個沒有基類的獨立類,封裝了RECT結構,有成員變量left、top、right和bottom,其構造函數有6種:
CRect( );
CRect( int l, int t, int r, int b );
CRect( const RECT& srcRect );
CRect( LPCRECT lpSrcRect );
CRect( POINT point, SIZE size );
CRect( POINT topLeft, POINT bottomRight );
CRect類重載了=,+、-,+=、-=,==、!=,&、|,&=、|=等運算符來支持CRect對象和CRect、POINT、SIZE、RECT對象之間的運算。還定義了轉換符LPCRECT和LPRECT來自動完成CRect對象到矩形結構和類指針LPCRECT和LPRECT的轉換。
CRect類中常用的屬性和成員函數有:
int Width( ) const;
int Height( ) const;
CSize Size( ) const;
CPoint& TopLeft( );
CPoint& BottomRight( );
CPoint CenterPoint( ) const;
void SwapLeftRight();
BOOL IsRectEmpty( ) const;
BOOL PtInRect( POINT point ) const;
void SetRect( int x1, int y1, int x2, int y2 );
void SetRect(POINT topLeft, POINT bottomRight);
void OffsetRect(int x, int y);
void MoveToXY(int x, int y);
3) 判斷點是否在矩形中
有時需要判斷某點(如鼠標位置)是否在某一矩形區域中,這可以調用CRect類的PtInRect函數來做:
BOOL PtInRect( POINT point ) const;
該函數當點point在其矩形區域內時,返回真。注意,該矩形區域不包括矩形的右邊界和底邊界。例如:
CRect rect( 10, 10, 371, 267 );
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if ( rect.PtInRect( point ) ) {
... ...
}
... ...
CView::OnLButtonUp(nFlags, point);
}
2 客戶區大小和DC
在繪圖前,必須先得到客戶區大小和設備上下文DC。
1.獲得客戶區
繪圖一般都是在視圖窗口的客戶區進行,而客戶區的大小在運行時可由用戶改變,為了使繪制的圖形能隨窗口大小自動改變,必須先得到當前客戶區大小的數據(寬w和高h)。
獲取客戶區大小的方法有如下兩種:
1)在消息響應函數OnSize中獲得
利用屬性窗口的信息頁,在視圖類中添加WM_SIZE消息的響應函數OnSize。該函數在窗口第一次顯示或窗口大小被改變時會被Windows系統調用。其輸入參數中的cx和cy就是客戶區大小的寬和高,可將它們賦值給類變量(如m_iW和m_iH)供繪圖時使用。例如
void CDrawView::OnSize(UINT nType, int cx, int cy) {
CView::OnSize(nType, cx, cy);
// TODO: 在此處添加消息處理程序代碼
m_iW = cx; m_iH = cy;
}
其中,nType的值為:
<!--[if !supportLists]-->l <!--[endif]-->SIZE_MAXIMIZED(窗口已被最大化)
<!--[if !supportLists]-->l <!--[endif]-->SIZE_MINIMIZED(窗口已被最小化)
<!--[if !supportLists]-->l <!--[endif]-->SIZE_RESTORED(窗口已被改變大小)
<!--[if !supportLists]-->l <!--[endif]-->SIZE_MAXHIDE(其他窗口被最大化)
<!--[if !supportLists]-->l <!--[endif]-->SIZE_MAXSHOW(其他窗口從最大化還原)
2)調用成員函數GetClientRect得到
可在繪圖前,定義一個矩形變量rect,然后再調用CWnd類的成員函數GetClientRect:
void GetClientRect( LPRECT lpRect ) const;
得到當前客戶區矩形的數據,其中的右(right)與底(bottom)就是客戶區的寬與高(其左left與頂top都為0)。例如:
RECT rect;
GetClientRect(&rect);
int w = rect.right, h = rect.bottom;
2.DC
在Windows中,繪圖使用的是MFC的DC(Device-Context, 設備上下文)類CDC中各種繪圖函數。
0)CDC類
CDC是CObject的直接派生類,CDC類自己也有若干派生類,其中包括窗口客戶區DC所對應的CClientDC類、OnPaint和OnDraw消息響應函數的輸入參數中使用的CPaintDC類、圖元文件對應的CMetaFileDC類和整個窗口所對應的CWindowDC類。
CDC類中有許多成員函數,可以用來設置各種繪圖環境、屬性和參數,以及繪制各種圖形和圖像等,將在后面陸續加以介紹。
1)獲得DC
可以從OnDraw函數的輸入參數pDC或調用CWnd的成員函數GetDC:
CDC* GetDC( );
來獲得DC的指針。
2)釋放DC
因為Windows限制可用DC的數量,所以DC屬于稀缺的公用資源。因此,對每次獲得的DC,在使用完成后必須立即釋放。
從OnDraw函數的輸入參數pDC獲得的DC,在該函數運行結束后,系統會自動釋放。但由GetDC所獲得的DC,必須自己來釋放,這可以通過調用CWnd的成員函數ReleaseDC來完成:
int ReleaseDC( CDC* pDC ); // 成功返回非0
例如:
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
ReleaseCapture();
if (m_bLButtonDown) {
CDC* pDC = GetDC();
pDC->SelectObject(new CPen(PS_SOLID, 0, RGB(255, 0, 0)));
pDC->SelectStockObject(NULL_BRUSH);
pDC-> Ellipse (rect);
ReleaseDC(pDC);
m_bLButtonDown = FALSE;
}
CView::OnLButtonUp(nFlags, point);
}
3)類DC
每次從OnDraw函數的輸入參數或調用GetDC所獲得的DC,都是一個全新的臨時缺省DC。它不能用類變量來長期保存,而且原來選入的各種GDI對象全都被作廢,必須從頭再來。
為了使選入的各種GDI對象一直有效,必須在視圖類的PreCreateWindow函數中調用CWnd類的成員函數AfxRegisterWndClass:
LPCTSTR AFXAPI AfxRegisterWndClass( UINT nClassStyle, HCURSOR hCursor = 0,
HBRUSH hbrBackground = 0, HICON hIcon = 0 );
來修改窗口類的風格屬性中的DC為類DC:CS_CLASSDC。如
BOOL CDrawView::PreCreateWindow(CREATESTRUCT& cs) {
cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS | CS_HREDRAW |
CS_VREDRAW | CS_CLASSDC, 0,
::CreateSolidBrush(RGB(255, 255, 255)));
return CView::PreCreateWindow(cs);
}
4)安全DC句柄
也可以用CDC類的成員函數:
HDC GetSafeHdc();
來獲取CD所對應窗口(如客戶區)的安全DC句柄,該句柄在窗口存在期間一直是有效的。例如,可先定義類變量HDC m_hDC;,再在適當的地方給它賦值m_hDC = GetDC()->GetSafeHdc();,然后就可以放心地使用了。例如,可以使用CDC類的成員函數
BOOL Attach(HDC hDC); // 成功返回非0
來將CDC對象與DC句柄連接在一起。
3 設置繪圖顏色
1.顏色
Windows中的顏色一般用4個字節表示(0BGR(整數) = R G B 0(字節序) [Intel CPU低位字節在前]),Win32 API中定義了一個專門表示顏色索引值的變量類型COLORREF:(windef.h)
typedef DWORD COLORREF; // 0x00bbggrr
和一個由紅綠藍三原色構造顏色值的宏RGB:(wingdi.h)
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
其中,r、g、b為字節變量,取值范圍為0~255。其函數說明為:
COLORREF RGB(
BYTE bRed, // red component of color
BYTE bGreen, // green component of color
BYTE bBlue // blue component of color
);
例如:
COLORREF red, gray;
red = RGB(255, 0, 0);
gray = RGB(128, 128,128);
在API中還定義了由COLORREF變量獲取各個顏色分量的宏Get?Value:(wingdi.h)
#define GetRValue(rgb) (LOBYTE(rgb))
#define GetGValue(rgb) (LOBYTE(((WORD)(rgb)) >> 8))
#define GetBValue(rgb) (LOBYTE((rgb)>>16))
其中:
typedef unsigned long ULONG_PTR;
typedef ULONG_PTR DWORD_PTR;
#define LOBYTE(w) ((BYTE)((DWORD_PTR)(w) & 0xff))
它們對應的函數說明為:
BYTE GetRValue(DWORD rgb); // DWORD rgb ~ COLORREF col
BYTE GetGValue(DWORD rgb);
BYTE GetBValue(DWORD rgb);
2.點色(像素)
在Windows中,像素(pixel)的顏色是直接由設備上下文類CDC的成員函數SetPixel來設置的,該函數的原型為:
COLORREF SetPixel( int x, int y, COLORREF crColor );
COLORREF SetPixel( POINT point, COLORREF crColor );
其中,x與y分別為像素點的橫坐標與縱坐標,crColor為像素的顏色值。例如:
pDC->SetPixel(10, 10, RGB(0, 255, 0));
另外,也可以用CDC的成員函數
COLORREF GetPixel( int x, int y ) const;
COLORREF GetPixel( POINT point ) const;
來獲得指定點(x, y)或point的顏色。例如:
COLORREF col;
col = pDC->GetPixel(10, 10);
3.線色(筆)
在Windows中,線狀圖必須用筆(pen)來畫,所以線的顏色就由筆色來確定。在MFC中,筆的屬性和功能由CPen類提供(CPen是CGDIObject的派生類)。
筆的創建與使用的步驟為:
<!--[if !supportLists]-->l <!--[endif]-->創建筆對象:創建筆類CPen對象的方法有如下兩種:
<!--[if !supportLists]-->n <!--[endif]-->使用構造函數CPen
CPen( int nPenStyle, int nWidth, COLORREF crColor );
其中:
<!--[if !supportLists]-->u <!--[endif]-->nPenStyle為筆的風格,可取值:
PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT, PSDASHDOTDOT
注意:1~4號筆風格只是在筆寬=0或1時有效,筆寬>1時總為實心的。
<!--[if !supportLists]-->u <!--[endif]-->nWidth為筆寬,與映射模式有關,使用缺省映射時為像素數,若nWidth = 0,則不論什么映射模式,筆寬都為一個像素;
<!--[if !supportLists]-->u <!--[endif]-->crColor為筆的顏色值。
例如
CPen* pGrayPen = new CPen(PS_SOLID, 0, RGB(128, 128, 128));
CPen grayPen(PS_SOLID, 0, RGB(128, 128, 128));
<!--[if !supportLists]-->n <!--[endif]-->使用成員函數CreatePen
BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor );
如:
CPen grayPen;
grayPen.CreatePen(PS_SOLID, 0, RGB(128, 128, 128));
<!--[if !supportLists]-->n <!--[endif]-->缺省的筆為單像素寬的實心黑色筆
<!--[if !supportLists]-->l <!--[endif]-->將筆對象選入設備上下文:為了能使用我們所創建的筆對象,必須先將它選入設備上下文,這可以調用設備上下文類CDC的成員函數SelectObject來完成:
CPen* SelectObject( CPen* pPen );
返回值為指向原來筆對象的指針(一般將其保存下來,供下次再裝入時使用)。如
pOldPen = pDC->SelectObject(&pen);
另外,Windows中有一些預定義的筆對象,可用CDC的另一成員函數SelectStockObject將其選入DC,其函數原型為:
virtual CGdiObject* SelectStockObject( int nIndex );
預定義的筆對象有BLACK_PEN(黑色筆)、WHITE_PEN (白色筆)、NULL_PEN(空筆/無色筆)。例如:
pDC->SelectStockObject(BLACK_PEN);
<!--[if !supportLists]-->l <!--[endif]-->使用設備上下文畫線狀圖:畫線狀圖以及面狀圖的邊線,所使用的是當前設備上下文中的筆對象。線狀圖有直線、折線、矩形、(橢)圓(弧)等,詳見4)(2)
<!--[if !supportLists]-->l <!--[endif]-->將筆對象從設備上下文中放出:為了能刪除使用過的筆對象,必須先將它從設備上下文中釋放出來后,然后才能刪除。釋放的方法是裝入其他的筆對象(一般是重新裝入原來的筆對象)。例如
pDC->SelectObject(pOldPen);
<!--[if !supportLists]-->l <!--[endif]-->刪除筆對象:為了能刪除筆對象,必須先將其從設備上下文中釋放。刪除方法有如下幾種:
<!--[if !supportLists]-->n <!--[endif]-->調用筆類CDC的成員函數DeleteObject刪除筆的當前內容(但是未刪除筆對象,以后可再用成員函數CreatePen在筆對象中繼續創建新的筆內容)。如
pen.DeleteObject();
<!--[if !supportLists]-->n <!--[endif]-->使用刪除運算符delete將筆對象徹底刪除,如delete pen;
<!--[if !supportLists]-->n <!--[endif]-->自動刪除:若筆對象為局部變量,則在離開其作用域時,會被系統自動刪除
下面為一段較完整地創建與使用筆的例子代碼:
CPen pen, *pOldPen;
for (int j = 0; j <= 255; j++) {
HSLtoRGB(m_hue, m_sat, 255 - j, r, g, b); // 自定義的函數
pen.CreatePen(PS_SOLID, 0, RGB(r, g, b));
pOldPen = pDC->SelectObject(&pen);
pDC->MoveTo(0, j); pDC->LineTo(40, j);
pDC->SelectObject(pOldPen);
pen.DeleteObject();
}
4.面色(刷)
在Windows中,面狀圖必須用刷(brush)來填充,所以面色是由刷色來確定的。MFC中的刷類為CBrush(它也是CGDIObject的派生類),刷的創建與使用的步驟與筆的相似。
<!--[if !supportLists]-->l <!--[endif]-->構造函數有4個:
<!--[if !supportLists]-->n <!--[endif]-->CBrush( ); // 創建一個刷的空對象
<!--[if !supportLists]-->n <!--[endif]-->CBrush( COLORREF crColor ); // 創建顏色為crColor的實心刷
<!--[if !supportLists]-->n <!--[endif]-->CBrush( int nIndex, COLORREF crColor ); // 創建風格由nIndex指定且顏色為crColor的條紋(hatch孵化)刷,其中nIndex可取條紋風格(Hatch Styles)值:
符號常量
|
數字常量
|
風格
|
HS_HORIZONTAL
|
0
|
水平線
|
HS_VERTICAL
|
1
|
垂直線
|
HS_FDIAGONAL
|
2
|
正斜線
|
HS_BDIAGONAL
|
3
|
反斜線
|
HS_CROSS
|
4
|
十字線(正網格)
|
HS_DIAGCROSS
|
5
|
斜十字線(斜網格)
|
<!--[if !supportLists]-->n <!--[endif]-->CBrush( CBitmap* pBitmap ); // 創建位圖為pBitmap的圖案刷
如:pDC->FillRect( &rect, new CBrush( RGB(r, g, b) ) );
<!--[if !supportLists]-->l <!--[endif]-->與構造函數相對應,有多個創建不同類型刷的成員函數:
<!--[if !supportLists]-->n <!--[endif]-->BOOL CreateSolidBrush( COLORREF crColor );
<!--[if !supportLists]-->n <!--[endif]-->BOOL CreateHatchBrush( int nIndex, COLORREF crColor );
<!--[if !supportLists]-->n <!--[endif]-->BOOL CreatePatternBrush( CBitmap* pBitmap );
<!--[if !supportLists]-->n <!--[endif]-->BOOL CreateDIBPatternBrush( HGLOBAL hPackedDIB, UINT nUsage );
<!--[if !supportLists]-->n <!--[endif]-->BOOL CreateDIBPatternBrush( const void* lpPackedDIB, UINT nUsage );
<!--[if !supportLists]-->n <!--[endif]-->BOOL CreateBrushIndirect( const LOGBRUSH* lpLogBrush );
<!--[if !supportLists]-->n <!--[endif]-->BOOL CreateSysColorBrush( int nIndex );
<!--[if !supportLists]-->l <!--[endif]-->預定義的刷對象有BLACK_BRUSH(黑刷)、DKGRAY_BRUSH(暗灰刷)、GRAY_BRUSH(灰刷)、HOLLOW_BRUSH(空刷)、LTGRAY_BRUSH(亮灰刷)、NULL_BRUSH(空刷)、WHITE_BRUSH(白刷)
<!--[if !supportLists]-->l <!--[endif]-->缺省的刷為空刷
<!--[if !supportLists]-->l <!--[endif]-->與筆一樣,可以用函數SelectObject或SelectStockObject將自定義的刷或預定義的刷選入DC中,供繪面狀圖時使用。
5.文本色
可使用CDC類的成員函數SetTextColor和SetBkColor來分別設置輸出文本的前景色和背景色:(缺省的前景色為黑色,背景色空)
COLORREF GetTextColor( ) const;
virtual COLORREF SetTextColor( COLORREF crColor );
COLORREF GetBkColor( ) const;
virtual COLORREF SetBkColor( COLORREF crColor );
例如:
pDC->TextOut(10, 10, "Test text");
pDC->SetTextColor(RGB(0, 128, 0)); pDC->TextOut(10, 30, "Test text");
pDC->SetBkColor(RGB(0, 0, 128)); pDC->TextOut(10, 50, "Test text");
6.繪圖工具
1)GDI對象
Windows的圖形設備接口(GDI = graphics device interface)對象指各種繪圖工具,如筆、刷、位圖、字體、調色板、區域等,對應的MFC類為CPen、CBrush、CBitmap、CFont等。這些圖形繪制對象類都是CGDIObject的派生類,而CGDIObject則是直接從CObject類派生的抽象基類。<!--[endif]-->其中,Windws CE不支持調色板類CPalette;CRgn為區域類,對應于窗口中的一個矩形、多邊形或(橢)圓區域(region),可用于移動、拷貝、合并、判斷和裁剪。
2)選入
可用設備上下文類CDC的多態成員函數SelectObject,來將繪圖工具對象選入設備上下文,以供繪圖時使用:
CPen* SelectObject( CPen* pPen );
CBrush* SelectObject( CBrush* pBrush );
virtual CFont* SelectObject( CFont* pFont );
CBitmap* SelectObject( CBitmap* pBitmap );
int SelectObject( CRgn* pRgn );
CGdiObject* SelectObject( CGdiObject* pObject );
3)獲取
可用API函數GetCurrentObject來獲得當前在DC中的指定類型的繪圖對象:
HGDIOBJ GetCurrentObject(
HDC hdc, // handle to device context
UINT uObjectType // specifies the object-type
);
其中,參數uObjectType可取值:
OBJ_PEN // Returns the current selected pen.
OBJ_BRUSH // Returns the current selected brush.
OBJ_PAL // Returns the current selected palette.
OBJ_FONT // Returns the current selected font.
OBJ_BITMAP // Returns the current selected bitmap.
也可分別調用CDC類的下列成員函數來做同樣的事:
CPen* GetCurrentPen( ) const;
CBrush* GetCurrentBrush( ) const;
CFont* GetCurrentFont( ) const;
CBitmap* GetCurrentBitmap( ) const;
CPalette* GetCurrentPalette( ) const;
如:
HPEN hPen = (HPEN)GetCurrentObject(pDC->m_hDC, OBJ_PEN);
CPen* pPen = CPen::FromHandle(hPen);
等價于:
CPen* pPen = pDC-> GetCurrentPen( );
4 畫圖
在Windows中,繪圖一般在視圖窗口的客戶區進行,使用的是設備上下文類CDC中各種繪圖函數。
1. 映射模式與坐標系
1)默認映射模式
映射模式(map mode)影響所有的圖形和文本繪制函數,它定義(將邏輯單位轉換為設備單位所使用的)度量單位和坐標方向,Windows總是用邏輯單位來繪圖。
缺省情況下,繪圖的默認映射模式為MM_TEXT,其繪圖單位為像素(只要不打印輸出,屏幕繪圖使用該模式就夠了)。若窗口客戶區的寬和高分別為w和h像素,則其x坐標是從左到右,范圍為0 ~ w-1;y坐標是從上到下,范圍為0 ~ h-1。
2)設置映射模式
可以使用CDC類的成員函數GetMapMode和SetMapMode來獲得和設置當前的映射模式:
int GetMapMode( ) const; // 返回當前的映射模式
virtual int SetMapMode( int nMapMode ); // 返回先前的映射模式
映射模式的nMapMode取值與含義
符號常量
|
數字常量
|
x方向
|
y方向
|
邏輯單位的大小
|
MM_TEXT
|
1
|
向右
|
向下
|
像素
|
MM_LOMETRIC
|
2
|
向右
|
向上
|
0.1 mm
|
MM_HIMETRIC
|
3
|
向右
|
向上
|
0.01 mm
|
MM_LOENGLISH
|
4
|
向右
|
向上
|
0.01 in
|
MM_HIENGLISH
|
5
|
向右
|
向上
|
0.001 in
|
MM_TWIPS
|
6
|
向右
|
向上
|
1/1440 in
|
MM_ISOTROPIC
|
7
|
自定義
|
自定義
|
自定義
|
MM_ANISOTROPIC
|
8
|
自定義
|
自定義
|
自定義
|
可見,除了兩種自定義映射模式外,x方向都是向右,y方向也只有MM_TEXT的向下,其余的都是向上,與數學上一致。除了MM_ANISOTROPIC外,其他所有映射模式的x與y方向的單位都是相同的。所有映射模式的邏輯坐標的原點(0, 0)最初都是在窗口的左上角,但在CScrollView的派生類中,MFC會隨用戶滾動文檔而自動調整邏輯原點的相對位置(改變視點的原點屬性)。
3)自定義映射模式
自定義映射模式MM_ISOTROPIC(各向同性,x與y方向的單位必須相同)和MM_ANISOTROPIC(各向異性,x與y方向的單位可以不同)的單位和方向,可以通過用CDC類的成員函數G/SetWindowExt和G/SetViewportExt來獲取/設置窗口和視口的大小來確定:
CSize GetWindowExt( ) const;
virtual CSize SetWindowExt( int cx, int cy );
virtual CSize SetWindowExt( SIZE size );
CSize GetViewportExt( ) const;
virtual CSize SetViewportExt( int cx, int cy );
virtual CSize SetViewportExt( SIZE size );
其中,cx或size.cx和cy或size.cy分別為窗口/視口的寬度與高度(邏輯單位)。
還可以用CDC類的成員函數SetViewportOrg來設置坐標原點的位置:
virtual CPoint SetViewportOrg( int x, int y );
CPoint SetViewportOrg( POINT point );
例如
void CDrawView::OnDraw(CDC* pDC) {
CRect rect;
GetClientRect(rect);
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetWindowExt(1000,1000);
pDC->SetViewportExt(rect.right, -rect.bottom);
pDC->SetViewportOrg(rect.right / 2, rect.bottom /2);
pDC->Ellipse(CRect(-500, -500, 500, 500));
}
將當前的映射模式設置為各向異性自定義映射模式,窗口大小為1000個邏輯單位寬和1000個邏輯單位高,視口大小同當前客戶區,視口的坐標原點設置在當前客戶區的中央。由于使用了負數作為SetViewportExt函數的第2個參數,所以y軸方向是向上的。
可見,圓被畫成了橢圓,x與y方向上的邏輯單位不相同。
4)單位轉換
對所有非MM_TEXT映射模式,有如下重要規則:
<!--[if !supportLists]-->l <!--[endif]-->CDC的成員函數(如各種繪圖函數)具有邏輯坐標參數
<!--[if !supportLists]-->l <!--[endif]-->CWnd的成員函數(如各種響應函數)具有設備坐標參數(如鼠標位置point)
<!--[if !supportLists]-->l <!--[endif]-->位置的測試操作(如CRect的PtInRect函數)只有使用設備坐標時才有效
<!--[if !supportLists]-->l <!--[endif]-->長期使用的值應該用邏輯坐標保存(如窗口滾動后保存的設備坐標就無效了)
因此,為了使應用程序能夠正確工作,除MM_TEXT映射模式外,其他映射模式都需要進行單位轉換。下面是邏輯單位到設備單位(如像素)的轉換公式:
x比例因子 = 視口寬度 / 窗口寬度
y比例因子 = 視口高度 / 窗口高度
設備x = 邏輯x * x比例因子 + x原點偏移量
設備y = 邏輯y * y比例因子 + y原點偏移量
Windows的GDI負責邏輯坐標和設備坐標之間的轉換,這可以調用CDC類的成員函數LPtoDP和DPtoLP來進行:
void LPtoDP( LPPOINT lpPoints, int nCount = 1 ) const;
void LPtoDP( LPRECT lpRect ) const;
void LPtoDP( LPSIZE lpSize ) const;
void DPtoLP( LPPOINT lpPoints, int nCount = 1 ) const;
void DPtoLP( LPRECT lpRect ) const;
void DPtoLP( LPSIZE lpSize ) const;
例如:
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) {
CRect rect = m_rect; // 邏輯坐標
CClientDC dc(this);
dc.SetMapMode(MM_LOENGLISH);
dc.LPtoDP(rect); // 轉化成設備坐標
if (rect.PtInRect(point)) // 位置的測試操作只有使用設備坐標時才有效
......
}
void CDrawView:: OnMouseMove (UINT nFlags, CPoint point) {
float t,y;
char buf[40];
CDC* pDC = GetDC();
pDC->SetMapMode(MM_HIMETRIC);
pDC->DPtoLP(&point); // 轉化成邏輯坐標
t = t1 + (point.x * dt) / w; sprintf(buf, "%.4fs", t); pSB->SetPaneText(xV, buf);
y = (y0 - point.y) / dy; sprintf(buf, "%.4f", y); pSB->SetPaneText(yV, buf);
......
}
2. 畫像素點
畫像素點就是設置像素點的顏色,從前面3)(2)已知道這可由CDC的成員函數SetPixel來做,該函數的原型為:
COLORREF SetPixel( int x, int y, COLORREF crColor ); 或
COLORREF SetPixel( POINT point, COLORREF crColor );
其中,x與y分別為像素點的橫坐標與縱坐標,crColor為像素的顏色值。例如
pDC->SetPixel(i, j, RGB(r, g, b));
3.畫線狀圖
在Windows中,線狀圖必須用筆來畫(筆的創建與使用見前面的3)(3)),下面是CDC類中可以繪制線狀圖的常用成員函數:
<!--[if !supportLists]-->l <!--[endif]-->當前位置:設置當前位置為(x, y)或point:(返回值為原當前位置的坐標)
CPoint MoveTo( int x, int y ); 或 CPoint MoveTo( POINT point );
<!--[if !supportLists]-->l <!--[endif]-->畫線:使用DC中的筆從當前位置畫線到點(x, y)或point:(若成功返回非0值):
BOOL LineTo( int x, int y ); 或BOOL LineTo( POINT point );
<!--[if !supportLists]-->l <!--[endif]-->畫折線:使用DC中的筆,依次將點數組lpPoints中的nCount(≥2)個點連接起來,形成一條折線:
BOOL Polyline( LPPOINT lpPoints, int nCount );
<!--[if !supportLists]-->l <!--[endif]-->畫多邊形:似畫折線,但還會將最后的點與第一個點相連形成多邊形,并用DC中的刷填充其內部區域:
BOOL Polygon( LPPOINT lpPoints, int nCount );
<!--[if !supportLists]-->l <!--[endif]-->畫矩形:使用DC中的筆畫左上角為(x1, y1)、右下角為(x2, y2)或范圍為*lpRect的矩形的邊線,并用DC中的刷填充其內部區域:
BOOL Rectangle( int x1, int y1, int x2, int y2 ); 或
BOOL Rectangle( LPCRECT lpRect );
有時需要根據用戶給定的兩個任意點來重新構造左上角和右下角的點,例如:
rect = CRect(min(p0.x, point.x), min(p0.y, point.y), max(p0.x, point.x), max(p0.y, point.y));
<!--[if !supportLists]-->l <!--[endif]-->畫圓角矩形:使用DC中的筆畫左上角為(x1, y1)、右下角為(x2, y2)或范圍為*lpRect的矩形的邊線,并用寬x3或point.x高y3或point.y矩形的內接橢圓倒角,再用DC中的刷填充其內部區域:
BOOL RoundRect( int x1, int y1, int x2, int y2, int x3, int y3 );
BOOL RoundRect( LPCRECT lpRect, POINT point );
例如:
int d = min(rect.Width(), rect.Height()) / 4;
pDC-> RoundRect(rect, CPoint(d, d));
<!--[if !supportLists]-->l <!--[endif]-->畫(橢)圓:使用DC中的筆在左上角為(x1, y1)、右下角為(x2, y2)或范圍為*lpRect的矩形中畫內接(橢)圓的邊線,并用DC中的刷填充其內部區域:
BOOL Ellipse( int x1, int y1, int x2, int y2 );
BOOL Ellipse( LPCRECT lpRect );
注意,CDC中沒有畫圓的專用函數。在這里,圓是作為橢圓的(寬高相等)特例來畫的。
<!--[if !supportLists]-->l <!--[endif]-->畫弧:(x1, y1)與(x2, y2)或lpRect的含義同畫(橢)圓,(x3, y3)或ptStart為弧的起點,(x4, y4)或ptEnd為弧的終點:(逆時針方向旋轉)
BOOL Arc( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
BOOL ArcTo(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4);
BOOL ArcTo(LPCRECT lpRect, POINT ptStart, POINT ptEnd);
畫圓弧:(其中(x, y)為圓心、nRadius為半徑、fStartAngle為起始角、fSweepAngle為弧段跨角)
BOOL AngleArc(int x, int y, int nRadius, float fStartAngle, float fSweepAngle);
<!--[if !supportLists]-->l <!--[endif]-->畫弓弦:參數的含義同上,只是用一根弦連接弧的起點和終點,形成一個弓形,并用DC中的刷填充其內部區域:
BOOL Chord( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
BOOL Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
4.畫填充圖
在Windows中,面狀圖必須用刷來填充(刷的創建與使用見前面的3)(4))。上面(2)中的Polygon、Rectangle、Ellipse和Chord等畫閉合線狀圖的函數,只要DC中的刷不是空刷,都可以用來畫對應的面狀圖(邊線用當前筆畫,內部用當前刷填充)。下面介紹的是CDC類中只能繪制面狀圖的其他常用成員函數:
<!--[if !supportLists]-->l <!--[endif]-->畫填充矩形:用指定的刷pBrush畫一個以lpRect為區域的填充矩形,無邊線,填充區域包括矩形的左邊界和上邊界,但不包括矩形的右邊界和下邊界:
void FillRect( LPCRECT lpRect, CBrush* pBrush );
<!--[if !supportLists]-->l <!--[endif]-->畫單色填充矩形:似FillRect,但只能填充單色,不能填充條紋和圖案:
void FillSolidRect( LPCRECT lpRect, COLORREF clr );
void FillSolidRect( int x, int y, int cx, int cy, COLORREF clr );
<!--[if !supportLists]-->l <!--[endif]-->畫餅圖(扇形):參數含義同Arc,但將起點和終點都與外接矩形的中心相連接,形成一個扇形區域,用DC中的刷填充整個扇形區域,無另外的邊線:
BOOL Pie( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
BOOL Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
<!--[if !supportLists]-->l <!--[endif]-->畫拖動的矩形:先擦除線寬為sizeLast、填充刷為pBrushLast的原矩形lpRectLast,然后再以線寬為size、填充刷為pBrush畫新矩形lpRectLast。矩形的邊框用灰色的點虛線畫,缺省的填充刷為空刷:
void DrawDragRect( LPCRECT lpRect, SIZE size, LPCRECT lpRectLast,
SIZE sizeLast, CBrush* pBrush = NULL, CBrush* pBrushLast = NULL );
如:pDC->DrawDragRect(rect, size, rect0, size);
<!--[if !supportLists]-->l <!--[endif]-->填充區域:
<!--[if !supportLists]-->n <!--[endif]-->用當前刷從點(x, y)開始向四周填充到顏色為crColor的邊界:
BOOL FloodFill(int x, int y, COLORREF crColor); // 成功返回非0
<!--[if !supportLists]-->n <!--[endif]-->用當前刷從點(x, y)開始向四周填充:
BOOL ExtFloodFill(int x, int y, COLORREF crColor,
UINT nFillType); // 成功返回非0
<!--[if !supportLists]-->u <!--[endif]-->nFillType = FLOODFILLBORDER:填充到顏色為crColor的邊界(同FloodFill);(用于填充內部顏色不同但邊界顏色相同的區域)
<!--[if !supportLists]-->u <!--[endif]-->nFillType = FLOODFILLSURFACE:填充所有顏色為crColor的點,直到碰到非crColor顏色的點為止。(點(x, y)的顏色也必須為crColor),(用于填充內部顏色相同但邊界顏色可以不同的區域)。例如:
pDC->ExtFloodFill(point.x, point.y, pDC->GetPixel(point), FLOODFILLSURFACE);
5.清屏
Windows沒有提供專門的清屏函數,可以調用CWnd的下面兩個函數調用來完成該功能:
void Invalidate(BOOL bErase = TRUE);
void UpdateWindow( );
或調用CWnd的函數
BOOL RedrawWindow(
LPCRECT lpRectUpdate = NULL,
CRgn* prgnUpdate = NULL,
UINT flags = RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE
);
來完成。
例如(菜單項ID_CLEAR的事件處理函數):
CDrawView::OnClear() { // 調用OnDraw來清屏
//Invalidate();
//UpdateWindow( );
RedrawWindow( );
}
也可以用畫填充背景色矩形的方法來清屏,如:
RECT rect;
GetClientRect(&rect);
pDC->FillSolidRect(&rect, RGB(255, 255, 255));
6.在控件上繪圖
可以在對話框資源中放置圖片控件,并對其類型屬性選Frame。可在對話框的繪圖消息響應函數OnPaint或其他函數中,用CWnd類的函數GetDlgItem:
CWnd* GetDlgItem( int nID ) const;
來獲得圖片控件的窗口對象,再用函數GetDC:
CDC* GetDC( );
由窗口對象得到DC,然后就可以用該DC在控件中畫圖。如(在ID為IDC_HUESAT的圖片控件上畫調色板)
void CColorDlg::OnPaint()
{
if (IsIconic()) {
... ...
}
else {
CDialog::OnPaint();
int i, j;
BYTE r, g, b;
// get control window and DC of Hue&Saturation
CWnd *pWin = GetDlgItem(IDC_HUESAT);
CDC *pDC = pWin->GetDC();
// draw hue-saturation palette
for (i = 0; i < 360; i++)
for (j = 0; j <= 255; j++) {
HSLtoRGB(i, 255 - j, 128, r, g, b); // 自定義函數,見網絡硬盤的
// res目錄中的ColTrans.cpp文件
pDC->SetPixel(i, j, RGB(r, g, b));
}
... ...
}
}
在非Frame類靜態控件上繪圖,必須先按順序依次調用CWnd類的Invalidate和UpdateWindow函數后,再開始用DC畫圖。如在一個ID為IDC_COLOR的按鈕上繪圖:
void CComDlgDlg::DrawColor()
{
CWnd* pWnd = GetDlgItem(IDC_COLOR);
CDC* pDC = pWnd->GetDC();
CRect rect;
pWnd->GetClientRect(&rect);
pWnd->Invalidate();
pWnd->UpdateWindow();
pDC->FillRect(&rect, new CBrush(m_crCol));
}
若干說明:
<!--[if !supportLists]-->l <!--[endif]-->除了基于對話框的程序外,其他對話框類都需要自己添加(重寫型)消息響應函數OnInitDialog,來做一些必要的初始化對話框的工作。添加方法是:先在項目區選中“類視圖”頁,再選中對應的對話框類,然后在屬性窗口的“重寫”頁中添加該函數;
<!--[if !supportLists]-->l <!--[endif]-->為了使在運行時能夠不斷及時更新控件的顯示(主要是自己加的顯式代碼),可以將自己繪制控件的所有代碼都全部加入對話框類的消息響應函數OnPaint中。在需要時(例如在繪圖參數修改后),自己調用CWnd的Invalidate和UpdateWindow函數,請求系統刷新對話框和控件的顯示。因為控件也是窗口,控件類都是CWnd的派生類。所以在對話框和控件中,可以像在視圖類中一樣,調用各種CWnd的成員函數。
<!--[if !supportLists]-->l <!--[endif]-->一般的對話框類,缺省時都沒有明寫出OnPaint函數。可以自己在對話框類中添加WM_PAINT消息的響應函數OnPaint來進行一些繪圖工作。
<!--[if !supportLists]-->l <!--[endif]-->為了在鼠標指向按鈕時,讓按鈕上自己繪制的圖形不被消去,可以設置按鈕控件的“Owner Draw”屬性為“True”。
<!--[if !supportLists]-->l <!--[endif]-->如果希望非按鈕控件(如圖片控件和靜態文本等),也可以響應鼠標消息(如單擊、雙擊等),需要設置控件的“Notify”屬性為“True”。
<!--[if !supportLists]-->l <!--[endif]-->使用OnPaint函數在對話框客戶區的空白處(無控件的地方)繪制自己的圖形,必須屏蔽掉其中缺省的對對話框基類的OnPaint函數的調用:
//CDialog::OnPaint();
<!--[if !supportLists]-->l <!--[endif]-->對話框的背景色,可以用CWnd類的成員函數:
DWORD GetSysColor( int nIndex);
得到,其中的nIndex取為COLOR_BTNFACE。例如:
dc.SetBkColor(GetSysColor(COLOR_BTNFACE));
下面是部分例子代碼:(其中FillColor和ShowImg為自定義的成員函數)
void CSetDlg::OnBnClickedPenColor()
{
// TODO: 在此添加控件通知處理程序代碼
CColorDialog colDlg(m_crLineColor);
if (colDlg.DoModal() == IDOK) {
m_crLineColor = colDlg.GetColor();
Invalidate();
UpdateWindow();
}
}
// ……
void CSetDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此處添加消息處理程序代碼
// 不為繪圖消息調用 CDialog::OnPaint()
FillColor(IDC_PEN_COLOR, m_crLineColor);
FillColor(IDC_BRUSH_COLOR, m_crBrushColor);
if(m_pBitmap0 != NULL) ShowImg(IDC_BRUSH_IMG, m_hBmp0);
else if(m_pBitmap != NULL) ShowImg(IDC_BRUSH_IMG, m_hBmp);
}
void CSetDlg::FillColor(UINT id, COLORREF col)
{
CWnd* pWnd = GetDlgItem(id);
CDC* pDC = pWnd->GetDC();
pDC->SelectObject(new CPen(PS_SOLID, 1, RGB(0, 0, 0)));
pDC->SelectObject(new CBrush(col));
CRect rect;
pWnd->GetClientRect(&rect);
pWnd->Invalidate();
pWnd->UpdateWindow();
pDC->RoundRect(&rect, CPoint(8, 8));
}
void CSetDlg::ShowImg(UINT ID, HBITMAP hBmp)
{
CWnd* pWnd = GetDlgItem(ID);
CDC* pDC = pWnd->GetDC();
CRect rect;
pWnd->GetClientRect(&rect);
pWnd->Invalidate();
pWnd->UpdateWindow();
BITMAP bs;
GetObject(hBmp, sizeof(bs), &bs);
CDC dc;
if(dc.CreateCompatibleDC(pDC)) {
int x0, y0, w, h;
float rx = (float)bs.bmWidth / rect.right,
ry = (float)bs.bmHeight / rect.bottom;
if (rx >= ry) {
x0 = 0; w = rect.right;
h = (int)(bs.bmHeight / rx + 0.5);
y0 = (rect.bottom - h) / 2;
}
else {
y0 = 0; h = rect.bottom;
w = (int)(bs.bmWidth / ry + 0.5);
x0 = (rect.right - w) / 2;
}
::SelectObject(dc.GetSafeHdc(), hBmp);
pDC->SetStretchBltMode(HALFTONE);
pDC->StretchBlt(x0, y0, w, h, &dc, 0, 0, bs.bmWidth, bs.bmHeight, SRCCOPY);
SetDlgItemInt(IDC_W, bs.bmWidth);
SetDlgItemInt(IDC_H, bs.bmHeight);
}
}
//……
5 設置繪圖屬性
除了映射模式外,還有許多繪圖屬性可以設置,如背景、繪圖方式、多邊形填充方式、畫弧方向、刷原點等。
1.背景
1)背景色
當背景模式為不透明時,背景色決定線狀圖的空隙顏色(如虛線中的空隙、條紋刷的空隙和文字的空隙),可以使用CDC類的成員函數GetBkColor和SetBkColor來獲得和設置當前的背景顏色:
COLORREF GetBkColor( ) const; // 返回當前的背景色
virtual COLORREF SetBkColor( COLORREF crColor ); // 返回先前的背景色
// 若出錯返回0x80000000
2)背景模式
背景模式影響有空隙的線狀圖的空隙(如虛線中的空隙、條紋刷的空隙和文字的空隙)用什么辦法填充。可以使用CDC類的成員函數GetBkMode和SetBkMode來獲得和設置當前的背景模式:
int GetBkMode( ) const; // 返回當前背景模式
int SetBkMode( int nBkMode ); // 返回先前背景模式
背景模式的取值
nBkMode值
|
名稱
|
作用
|
OPAQUE
|
不透明的(缺省值)
|
空隙用背景色填充
|
TRANSPARENT
|
透明的
|
空隙處保持原背景圖不變
|
2. 繪圖模式
繪圖模式(drawing mode)指前景色的混合方式,它決定新畫圖的筆和刷的顏色(pbCol)如何與原有圖的顏色(scCol)相結合而得到結果像素色(pixel)。
1)設置繪圖模式
可使用CDC類的成員函數SetROP2 (ROP = Raster OPeration光柵操作)來設置繪圖模式:
int SetROP2( int nDrawMode );
其中,nDrawMode可取值:
繪圖模式nDrawMode的取值
符號常量
|
作用
|
運算結果
|
R2_BLACK
|
黑色
|
pixel = black
|
R2_WHITE
|
白色
|
pixel = white
|
R2_NOP
|
不變
|
pixel = scCol
|
R2_NOT
|
反色
|
pixel = ~scCol
|
R2_COPYPEN
|
覆蓋
|
pixel = pbCol
|
R2_NOTCOPYPEN
|
反色覆蓋
|
pixel = ~pbCol
|
R2_MERGEPENNOT
|
反色或
|
pixel = ~scCol | pbCol
|
R2_MERGENOTPEN
|
或反色
|
pixel = scCol | ~pbCol
|
R2_MASKNOTPEN
|
與反色
|
pixel = scCol & ~pbCol
|
R2_MERGEPEN
|
或
|
pixel = scCol | pbCol
|
R2_NOTMERGEPEN
|
或非
|
pixel = ~(scCol | pbCol)
|
R2_MASKPEN
|
與
|
pixel = scCol & pbCol
|
R2_NOTMASKPEN
|
與非
|
pixel = ~(scCol & pbCol)
|
R2_XORPEN
|
異或
|
pixel = scCol ^ pbCol
|
R2_NOTXORPEN
|
異或非
|
pixel = ~(scCol ^ pbCol)
|
其中,R2_COPYPEN(覆蓋)為缺省繪圖模式,R2_XORPEN(異或)較常用。
2)畫移動圖形
為了能畫移動的位置標識(如十字、一字)和隨鼠標移動畫動態圖形(如直線、矩形、橢圓),必須在不破壞原有背景圖形的基礎上移動這些圖形。
移動圖形采用的是異或畫圖方法,移動圖形的過程為:異或畫圖、在原位置再異或化圖(擦除)、在新位置異或畫圖、……。
如
pGrayPen = new CPen(PS_DOT, 0, RGB(128, 128, 128));
pDC->SetBkMode(TRANSPARENT);
pOldPen = pDC->SelectObject(pGrayPen);
pDC->SelectStockObject(NULL_BRUSH);
pDC->SetROP2(R2_XORPEN);
if (m_bErase) pDC->Ellipse(rect0);
pDC->Ellipse(rect);
pDC->SetROP2(R2_COPYPEN);
pDC->SelectObject(pOldPen);
rect0 = rect;
較完整的拖放動態畫圖的例子,可參照下面的“3. 拖放畫動態直線”部分。
3)其他屬性
<!--[if !supportLists]-->l <!--[endif]-->多邊形填充方式:可使用CDC類的成員函數GetPolyFillMode和SetPolyFillMode來確定多邊形的填充方式:
int GetPolyFillMode( ) const;
int SetPolyFillMode( int nPolyFillMode );
其中nPolyFillMode 可取值ALTERNATE(交替——填充奇數邊和偶數邊之間的區域,缺省值)或WINDING(纏繞——根據多邊形邊的走向來確定是否填充一區域)
<!--[if !supportLists]-->l <!--[endif]-->畫弧方向:可使用CDC類的成員函數GetArcDirection和SetArcDirection來確定Arc、Chord、Pie等函數的畫弧方向:
int GetArcDirection( ) const;
int SetArcDirection( int nArcDirection );
其中,nArcDirection可取值AD_COUNTERCLOCKWISE(逆時針方向,缺省值)和AD_CLOCKWISE(順時針方向)
<!--[if !supportLists]-->l <!--[endif]-->刷原點:可使用CDC類的成員函數GetBrushOrg和SetBrushOrg來確定可填充繪圖函數的條紋或圖案刷的起點:(缺省值為客戶區左上角的坐標原點(0, 0))
CPoint GetBrushOrg( ) const;
CPoint SetBrushOrg( int x, int y );
CPoint SetBrushOrg( POINT point );
3.拖放畫動態直線
下面是一個較完整的拖放動態畫直線的例子:
// 類變量
class CDrawView : public CView {
//……
protected:
BOOL m_bLButtonDown, m_bErase; // 判斷是否按下左鼠標鍵
//和是否需要擦除圖形的類變量
CPoint p0, pm; // 記錄直線起點和動態終點的類變量
CPen * pGrayPen, * pLinePen; // 定義灰色和直線筆
//……
}
// 構造函數
CDrawView::CDrawView() {
m_bLButtonDown = FALSE; // 設左鼠標鍵按下為假
m_bErase = FALSE; // 設需要擦除為假
pGrayPen = new CPen(PS_SOLID, 0, RGB(128, 128, 128));// 創建灰色筆
pLinePen = new CPen(PS_SOLID, 0, RGB(255, 0, 0));// 創建紅色的直線筆
}
// 鼠標消息響應函數
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) {
m_bLButtonDown = TRUE; // 設左鼠標鍵按下為真
SetCapture(); // 設置鼠標捕獲
// SetCursor(LoadCursor(NULL, IDC_CROSS)); // 設置鼠標為十字
p0 = point; // 保存矩形左上角
pm = p0; // 讓矩形右下角等于左上角
CView::OnLButtonDown(nFlags, point);
}
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) {
SetCursor(LoadCursor(NULL, IDC_CROSS)); // 設置鼠標為十字
if (m_bLButtonDown) { // 左鼠標鍵按下為真
CDC* pDC = GetDC(); // 獲取設備上下文
pDC->SelectObject(pGrayPen);// 選取灰色筆
pDC->SetROP2(R2_XORPEN);// 設置為異或繪圖方式
if (m_bErase) { // 需要擦除為真
pDC->MoveTo(p0); pDC->LineTo(pm); // 擦除原直線
}
else // 需要擦除為假
m_bErase = TRUE; // 設需要擦除為真
pDC->MoveTo(p0); pDC->LineTo(point); // 繪制新直線
pm = point; // 記錄老終點
ReleaseDC(pDC); // 釋放設備上下文
}
CView::OnMouseMove(nFlags, point);
}
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) {
ReleaseCapture(); // 釋放鼠標捕獲
if (m_bLButtonDown) { // 左鼠標鍵按下為真
CDC* pDC = GetDC(); // 獲取設備上下文
pDC->SelectObject(pGrayPen);// 選取灰色筆
pDC->SetROP2(R2_XORPEN); // 設置為異或繪圖方式
pDC->MoveTo(p0); pDC->LineTo(pm); // 擦除原直線
pDC->SelectObject(pLinePen); // 選擇直線筆
pDC->SetROP2(R2_COPYPEN);// 設置為覆蓋繪圖方式
pDC->MoveTo(p0); pDC->LineTo(point); // 繪制最終的直線
m_bLButtonDown = FALSE; // 重設左鼠標鍵按下為假
m_bErase = FALSE; // 重需要擦除為假
ReleaseDC(pDC); // 釋放設備上下文
}
CView::OnLButtonUp(nFlags, point);
}