现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

MFC绘图总结(4):画图

2012-12-24 23:13 工业·编程 ⁄ 共 10505字 ⁄ 字号 暂无评论
在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);

       }
}
//……

给我留言

留言无头像?