MFC中CDC的使用 绘图(三)
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);
}