MFC中的CDC 绘图

绘图一般在视图类的(屏幕/打印机)绘图消息响应函数OnDraw中进行,例如:
void CTestView::OnDraw(CDC* /*pDC*/) {
         CTestDoc* pDoc = GetDocument();
         ASSERT_VALID(pDoc);
         if (!pDoc)
                return;
         // TODO: 在此处为本机数据添加绘制代码

}
每次需要重绘窗口时(程序启动/窗口大小改变/全部或部分窗口重现/程序员调用RedrawWindow或UpdateWindow),应用程序框架都会调用该CWnd的消息响应成员函数(的覆盖)来绘制窗口客户区。
在Windows中,绘图一般在视图窗口的客户区进行,使用的是MFC的设备上下文(DC = Device-Context)类CDC中各种绘图函数。
在绘图前,必须先得到客户区大小和DC、设置绘图颜色,然后再根据文档数据或用户操作来绘制图形。
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的值为:
SIZE_MAXIMIZED(窗口已被最大化)
SIZE_MINIMIZED(窗口已被最小化)
SIZE_RESTORED(窗口已被改变大小)
SIZE_MAXHIDE(其他窗口被最大化)
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的派生类)。
笔的创建与使用的步骤为:
创建笔对象:创建笔类CPen对象的方法有如下两种:
使用构造函数CPen
CPen( int nPenStyle, int nWidth, COLORREF crColor );
其中:
nPenStyle为笔的风格,可取值:
PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT, PSDASHDOTDOT

注意:1~4号笔风格只是在笔宽=0或1时有效,笔宽>1时总为实心的。
nWidth为笔宽,与映射模式有关,使用缺省映射时为像素数,若nWidth = 0,则不论什么映射模式,笔宽都为一个像素;
crColor为笔的颜色值。
例如
CPen* pGrayPen = new CPen(PS_SOLID, 0, RGB(128, 128, 128));
CPen grayPen(PS_SOLID, 0, RGB(128, 128, 128));
使用成员函数CreatePen
BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor );
如:
CPen grayPen;
grayPen.CreatePen(PS_SOLID, 0, RGB(128, 128, 128));
缺省的笔为单像素宽的实心黑色笔
将笔对象选入设备上下文:为了能使用我们所创建的笔对象,必须先将它选入设备上下文,这可以调用设备上下文类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);
使用设备上下文画线状图:画线状图以及面状图的边线,所使用的是当前设备上下文中的笔对象。线状图有直线、折线、矩形、(椭)圆(弧)等)
将笔对象从设备上下文中放出:为了能删除使用过的笔对象,必须先将它从设备上下文中释放出来后,然后才能删除。释放的方法是装入其他的笔对象(一般是重新装入原来的笔对象)。例如
pDC->SelectObject(pOldPen);
删除笔对象:为了能删除笔对象,必须先将其从设备上下文中释放。删除方法有如下几种:
调用笔类CDC的成员函数DeleteObject删除笔的当前内容(但是未删除笔对象,以后可再用成员函数CreatePen在笔对象中继续创建新的笔内容)。如
pen.DeleteObject();
使用删除运算符delete将笔对象彻底删除,如delete pen;
自动删除:若笔对象为局部变量,则在离开其作用域时,会被系统自动删除
下面为一段较完整地创建与使用笔的例子代码:
         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的派生类),刷的创建与使用的步骤与笔的相似。
构造函数有4个:
CBrush( ); // 创建一个刷的空对象
CBrush( COLORREF crColor ); // 创建颜色为crColor的实心刷
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 斜十字线(斜网格)

CBrush( CBitmap* pBitmap ); // 创建位图为pBitmap的图案刷
如:pDC->FillRect( &rect, new CBrush( RGB(r, g, b) ) );
与构造函数相对应,有多个创建不同类型刷的成员函数:
BOOL CreateSolidBrush( COLORREF crColor );
BOOL CreateHatchBrush( int nIndex, COLORREF crColor );
BOOL CreatePatternBrush( CBitmap* pBitmap );
BOOL CreateDIBPatternBrush( HGLOBAL hPackedDIB, UINT nUsage );
BOOL CreateDIBPatternBrush( const void* lpPackedDIB, UINT nUsage );
BOOL CreateBrushIndirect( const LOGBRUSH* lpLogBrush );
BOOL CreateSysColorBrush( int nIndex );
预定义的刷对象有BLACK_BRUSH(黑刷)、DKGRAY_BRUSH(暗灰刷)、GRAY_BRUSH(灰刷)、HOLLOW_BRUSH(空刷)、LTGRAY_BRUSH(亮灰刷)、NULL_BRUSH(空刷)、WHITE_BRUSH(白刷)
缺省的刷为空刷
与笔一样,可以用函数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类派生的抽象基类。其中,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映射模式,有如下重要规则:
CDC的成员函数(如各种绘图函数)具有逻辑坐标参数
CWnd的成员函数(如各种响应函数)具有设备坐标参数(如鼠标位置point)
位置的测试操作(如CRect的PtInRect函数)只有使用设备坐标时才有效
长期使用的值应该用逻辑坐标保存(如窗口滚动后保存的设备坐标就无效了)

因此,为了使应用程序能够正确工作,除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类中可以绘制线状图的常用成员函数:
当前位置:设置当前位置为(x, y)或point:(返回值为原当前位置的坐标)
CPoint MoveTo( int x, int y ); 或 CPoint MoveTo( POINT point );
画线:使用DC中的笔从当前位置画线到点(x, y)或point:(若成功返回非0值):
BOOL LineTo( int x, int y ); 或BOOL LineTo( POINT point );
画折线:使用DC中的笔,依次将点数组lpPoints中的nCount(≥2)个点连接起来,形成一条折线:
BOOL Polyline( LPPOINT lpPoints, int nCount );
画多边形:似画折线,但还会将最后的点与第一个点相连形成多边形,并用DC中的刷填充其内部区域:
BOOL Polygon( LPPOINT lpPoints, int nCount );
画矩形:使用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)); 
画圆角矩形:使用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));
画(椭)圆:使用DC中的笔在左上角为(x1, y1)、右下角为(x2, y2)或范围为*lpRect的矩形中画内接(椭)圆的边线,并用DC中的刷填充其内部区域:
BOOL Ellipse( int x1, int y1, int x2, int y2 );
BOOL Ellipse( LPCRECT lpRect );
注意,CDC中没有画圆的专用函数。在这里,圆是作为椭圆的(宽高相等)特例来画的。
画弧:(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);
画弓弦:参数的含义同上,只是用一根弦连接弧的起点和终点,形成一个弓形,并用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类中只能绘制面状图的其他常用成员函数:
画填充矩形:用指定的刷pBrush画一个以lpRect为区域的填充矩形,无边线,填充区域包括矩形的左边界和上边界,但不包括矩形的右边界和下边界:
void FillRect( LPCRECT lpRect, CBrush* pBrush );
画单色填充矩形:似FillRect,但只能填充单色,不能填充条纹和图案:
void FillSolidRect( LPCRECT lpRect, COLORREF clr );
void FillSolidRect( int x, int y, int cx, int cy, COLORREF clr );
画饼图(扇形):参数含义同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 );
画拖动的矩形:先擦除线宽为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);
填充区域:
用当前刷从点(x, y)开始向四周填充到颜色为crColor的边界:
BOOL FloodFill(int x, int y, COLORREF crColor); // 成功返回非0
用当前刷从点(x, y)开始向四周填充:
BOOL ExtFloodFill(int x, int y, COLORREF crColor, 
UINT nFillType); // 成功返回非0
nFillType = FLOODFILLBORDER:填充到颜色为crColor的边界(同FloodFill);(用于填充内部颜色不同但边界颜色相同的区域)
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));
}

若干说明:
除了基于对话框的程序外,其他对话框类都需要自己添加(重写型)消息响应函数OnInitDialog,来做一些必要的初始化对话框的工作。添加方法是:先在项目区选中“类视图”页,再选中对应的对话框类,然后在属性窗口的“重写”页中添加该函数;
为了使在运行时能够不断及时更新控件的显示(主要是自己加的显式代码),可以将自己绘制控件的所有代码都全部加入对话框类的消息响应函数OnPaint中。在需要时(例如在绘图参数修改后),自己调用CWnd的Invalidate和UpdateWindow函数,请求系统刷新对话框和控件的显示。因为控件也是窗口,控件类都是CWnd的派生类。所以在对话框和控件中,可以像在视图类中一样,调用各种CWnd的成员函数。
一般的对话框类,缺省时都没有明写出OnPaint函数。可以自己在对话框类中添加WM_PAINT消息的响应函数OnPaint来进行一些绘图工作。
为了在鼠标指向按钮时,让按钮上自己绘制的图形不被消去,可以设置按钮控件的“Owner Draw”属性为“True”。
如果希望非按钮控件(如图片控件和静态文本等),也可以响应鼠标消息(如单击、双击等),需要设置控件的“Notify”属性为“True”。
使用OnPaint函数在对话框客户区的空白处(无控件的地方)绘制自己的图形,必须屏蔽掉其中缺省的对对话框基类的OnPaint函数的调用:
//CDialog::OnPaint();
对话框的背景色,可以用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)其他属性
多边形填充方式:可使用CDC类的成员函数GetPolyFillMode和SetPolyFillMode来确定多边形的填充方式:
int GetPolyFillMode( ) const;
int SetPolyFillMode( int nPolyFillMode );
其中nPolyFillMode 可取值ALTERNATE(交替——填充奇数边和偶数边之间的区域,缺省值)或WINDING(缠绕——根据多边形边的走向来确定是否填充一区域)
画弧方向:可使用CDC类的成员函数GetArcDirection和SetArcDirection来确定Arc、Chord、Pie等函数的画弧方向:
int GetArcDirection( ) const;
int SetArcDirection( int nArcDirection );
其中,nArcDirection可取值AD_COUNTERCLOCKWISE(逆时针方向,缺省值)和AD_CLOCKWISE(顺时针方向)
刷原点:可使用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);
}


展开阅读全文

没有更多推荐了,返回首页