上一节中讲了文本输出的知识,本节的主要内容是CDC类及其屏幕绘图函数。

CDC类简介

CDC类是一个设备上下文类。

CDC类提供了用来处理显示器或打印机等设备上下文的成员函数,还有处理与窗口客户区关联的显示上下文的成员函数。使用CDC的成员函数可以进行所有的绘图操作,包括处理绘图工具、GDI对象的选择、颜色和调色板的处理、获取和设置绘图属性、映射、窗口范围、坐标转换、剪切以及绘制直线、简单图形、椭圆和多边形等,另外它还为文本输出、处理字体、使用打印机跳转和滚动等提供了成员函数。

如上所述,CDC类几乎封装了所有的Windows GDI函数,另外,MFC中还有几个由CDC类派生的子类,包括CWindowDC、CPaintDC、CClientDC、CMetaFileDC,它们用来进行一些特定的绘图操作。

一般我们在使用完CDC对象后要记得删除它,否则会有内存泄露。很多情况下我们可以调用CWnd::GetDC()函数来获取设备上下文指针,即CDC指针,这个时候记得用完后调用CWnd::ReleaseDC()函数释放设备上下文。

CDC类的屏幕绘图成员函数

CDC类有很多成员函数,鸡啄米在这里只大概讲下比较常用的绘图函数,包括绘制点、直线、矩形、椭圆、多边形、文本以及位图等的成员函数。

COLORREF SetPixel(int x,int y,COLORREF crColor);
       COLORREF SetPixel(POINT point,COLORREF crColor);
  • 1.
  • 2.

上面两个成员函数用来将指定坐标点的像素设置为指定的颜色,这样就实现了画点功能。参数x为点的逻辑x坐标;参数y为点的逻辑y坐标;参数crColor为要为点设置的颜色;参数point指定点的逻辑x坐标和逻辑y坐标,可以为其传入POINT结构体变量或者CPoint对象。

CPoint MoveTo(int x,int y);
       CPoint MoveTo(POINT point);
  • 1.
  • 2.

将当前点移动到指定位置。参数x指定新位置的逻辑x坐标;参数y指定新位置的逻辑y坐标;参数point指定新位置的逻辑x坐标和逻辑y坐标,可以为其传入POINT结构体变量或者CPoint对象。

BOOL LineTo(int x,int y);
       BOOL LineTo(POINT point);
  • 1.
  • 2.

绘制一条从当前点到指定点(不包括指定点)的直线。参数x为指定点的逻辑x坐标;参数y为指定点的逻辑y坐标;参数point为指定点的逻辑x坐标和逻辑y坐标。一般我们绘制直线时就可以先调用MoveTo函数移动当前点到某个位置,然后调用LineTo画直线。

BOOL Rectangle(int x1,int y1,int x2,int y2);
       BOOL Rectangle(LPCRECT lpRect);
  • 1.
  • 2.

使用当前画笔绘制矩形。参数x1指定矩形左上角的x坐标;参数y1指定矩形左上角的y坐标;参数x2指定矩形右下角的x坐标;参数y2指定矩形右下角的y坐标;以上坐标均为逻辑单位。参数lpRect为矩形对象的指针,可以为其传入CRect对象或RECT结构体变量的指针。

BOOL Ellipse(int x1,int y1,int x2,int y2);
       BOOL Ellipse(LPCRECT lpRect);
  • 1.
  • 2.

绘制椭圆。参数x1指定椭圆的包围矩形左上角的x坐标;参数y1指定椭圆的包围矩形左上角的y坐标;参数x2指定椭圆的包围矩形右下角的x坐标;参数y2指定椭圆的包围矩形右下角的y坐标;以上坐标均为逻辑单位。参数lpRect指定椭圆的包围矩形,可以传入CRect对象或RECT结构体变量的指针。

BOOL Polyline(LPPOINT lpPoints,int nCount);
  • 1.

由指定的多边形顶点绘制多边形。参数lpPoints为指向一个POINT结构体变量数组或CPoint对象数组的指针,其中的POINT结构体变量或CPoint对象代表了多边形顶点的坐标;参数nCount为数组中点的个数,至少为2。

virtual BOOL TextOut(int x,int y,LPCTSTR lpszString,int nCount);
       BOOL TextOut(int x,int y,const CString& str);
  • 1.
  • 2.

使用当前选择的字体在指定位置输出文本。 参数x指定文本起始点的x坐标;参数y指定文本起始点的y坐标;参数lpszString为要输出的文本字符串;参数nCount指定字符串中的字节个数;参数str为包含要输出的字符的CString对象。这两个函数在上一节中其实已经讲到了。

BOOL BitBlt(
              int x,
              int y,
              int nWidth,
              int nHeight,
              CDC* pSrcDC,
              int xSrc,
              int ySrc,
              DWORD dwRop
       );
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

从源设备上下文拷贝一幅位图到当前设备上下文。参数x指定目标矩形区域左上角的逻辑x坐标;参数y指定目标矩形区域左上角的逻辑y坐标;参数nWidth指定目标矩形区域和源位图的宽度(逻辑单位);参数nHeight指定目标矩形区域和源位图的高度(逻辑单位);参数pSrcDC为指向源设备上下文的CDC对象的指针,如果dwRop指定了一个不包含源的光栅操作,那么pSrcDC可以为NULL;参数xSrc指定源位图左上角的逻辑x坐标;参数ySrc指定源位图左上角的逻辑y坐标;参数dwRop指定要执行的光栅操作,光栅操作码定义了GDI如何将当前画刷颜色、源位图颜色和目标位图颜色组合形成新的颜色,下面是一些常用的光栅操作码及含义:

BLACKNESS:表示使用与物理调色板的索引0相关的色彩来填充目标矩形区域,(对缺省的物理调色板而言,该颜色为黑色)。
       DSTINVERT:表示使目标矩形区域颜色取反。 
       MERGECOPY:表示使用布尔型的AND(与)操作符将源矩形区域的颜色与特定模式组合一起。
       MERGEPAINT:通过使用布尔型的OR(或)操作符将反向的源矩形区域的颜色与目标矩形区域的颜色合并。
       NOTSRCCOPY:将源矩形区域颜色取反,于拷贝到目标矩形区域。
       NOTSRCERASE:使用布尔类型的OR(或)操作符组合源和目标矩形区域的颜色值,然后将合成的颜色取反。
       PATCOPY:将特定的模式拷贝到目标位图上。 
       PATPAINT:通过使用布尔OR(或)操作符将源矩形区域取反后的颜色值与特定模式的颜色合并。然后使用OR(或)操作符将该操作的结果与目标矩形区域内的颜色合并。
       PATINVERT:通过使用XOR(异或)操作符将源和目标矩形区域内的颜色合并。 
       SRCAND:通过使用AND(与)操作符来将源和目标矩形区域内的颜色合并。
       SRCCOPY:将源矩形区域直接拷贝到目标矩形区域。
       SRCERASE:通过使用AND(与)操作符将目标矩形区域颜色取反后与源矩形区域的颜色值合并。 
       SRCINVERT:通过使用布尔型的XOR(异或)操作符将源和目标矩形区域的颜色合并。
       SRCPAINT:通过使用布尔型的OR(或)操作符将源和目标矩形区域的颜色合并。
       WHITENESS:使用与物理调色板中索引1有关的颜色填充目标矩形区域。(对于缺省物理调色板来说,这个颜色就是白色)。
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

好了,本节就讲到这里了,主要就是大概讲了讲CDC类,又介绍了CDC类一些常用的绘图函数。如果想了解更多的资料可以查阅MSDN。

上一节中讲了CDC类及其屏幕绘图函数,本节的主要内容是GDI对象之画笔CPen。

GDI对象

在MFC中,CGdiObject类是GDI对象的基类,通过查阅MSDN我们可以看到,CGdiObject类有六个直接的派生类,GDI对象主要也是这六个,分别是:CBitmap、CBrush、CFont、CPalette、CPen和CRgn。

在这六个GDI对象中,最常用的莫过于画笔和画刷了,即CPen类和CBrush类。本文就主要讲解画笔的使用。

画笔的应用实例

在这里直接通过一个波形图的实例,来详细讲解画笔的使用方法。

首先介绍此实例要实现的功能:在对话框上有一个Picture控件,将此控件的背景填充为黑色;启动一个定时器,每次定时器到时,所有波形数据都前移一个单位,并获取一个80以内的随机数作为波形的最后一个数据,然后以绿色画笔在绘图控件上绘制波形。这样就实现了波形的绘制及动态变化。

下面是具体实施步骤:

1、创建一个基于对话框的MFC工程,名字设为“Example50”。

2、在自动生成的对话框模板IDD_EXAMPLE50_DIALOG中,删除“TODO: Place dialog controls here.”静态文本框,添加一个Picture控件,ID设为IDC_WAVE_DRAW。

3、为Picture控件IDC_WAVE_DRAW添加CStatic变量,名称设为m_picDraw。

4、在文件Example50Dlg.h文件中CExample50Dlg类声明的上面添加宏定义:

#define POINT_COUNT 100
  • 1.

此符号常量的意义是波形的点数,这里用define将其定义为符号常量是为了方便以后可能的修改,假如我们以后想将点数改为200,则只改此宏定义就可以了:#define POINT_COUNT 200,而如果没有使用符号常量,在程序中直接使用了100,那么就需要将所有使用100的位置找出来,并替换为200,这样不仅麻烦也很容易出错,所以最好是将其定义为符号常量。

5、在CExample50Dlg.h文件中为CExample50Dlg类添加成员数组:

int   m_nzValues[POINT_COUNT];
       此数组用于存放波形数据。
  • 1.
  • 2.

6、在CExample50Dlg类的构造函数中为数组m_nzValues的元素赋初值:

CExample50Dlg::CExample50Dlg(CWnd* pParent /*=NULL*/)   
    : CDialogEx(CExample50Dlg::IDD, pParent)   
{   
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);   
   
    // 将数组m_nzValues的元素都初始化为0    
    memset(m_nzValues, 0, sizeof(int) * POINT_COUNT);   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

7、在CExample50Dlg对话框的初始化成员函数CExample50Dlg::OnInitDialog()中,构造随机数生成器,并启动定时器。CExample50Dlg::OnInitDialog()修改如下:

BOOL CExample50Dlg::OnInitDialog()   
{   
    CDialogEx::OnInitDialog();   
   
    // Add "About..." menu item to system menu.   
   
    // IDM_ABOUTBOX must be in the system command range.   
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);   
    ASSERT(IDM_ABOUTBOX < 0xF000);   
   
    CMenu* pSysMenu = GetSystemMenu(FALSE);   
    if (pSysMenu != NULL)   
    {   
        BOOL bNameValid;   
        CString strAboutMenu;   
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);   
        ASSERT(bNameValid);   
        if (!strAboutMenu.IsEmpty())   
        {   
            pSysMenu->AppendMenu(MF_SEPARATOR);   
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);   
        }   
    }   
   
    // Set the icon for this dialog.  The framework does this automatically   
    //  when the application's main window is not a dialog   
    SetIcon(m_hIcon, TRUE);         // Set big icon   
    SetIcon(m_hIcon, FALSE);        // Set small icon   
   
    // TODO: Add extra initialization here   
   
    // 以时间为种子来构造随机数生成器   
    srand((unsigned)time(NULL));   
   
    // 启动定时器,ID为1,定时时间为200ms   
    SetTimer(1, 200, NULL);   
   
    return TRUE;  // return TRUE  unless you set the focus to a control   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

8、为CExample50Dlg类添加波形绘制的成员函数CExample50Dlg::DrawWave(CDC *pDC, CRect &rectPicture),参数分别为设备上下文指针和绘图的矩形区域。

void CExample50Dlg::DrawWave(CDC *pDC, CRect &rectPicture)   
{   
    float fDeltaX;     // x轴相邻两个绘图点的坐标距离   
    float fDeltaY;     // y轴每个逻辑单位对应的坐标值   
    int nX;      // 在连线时用于存储绘图点的横坐标   
    int nY;      // 在连线时用于存储绘图点的纵坐标   
    CPen newPen;       // 用于创建新画笔   
    CPen *pOldPen;     // 用于存放旧画笔   
    CBrush newBrush;   // 用于创建新画刷   
    CBrush *pOldBrush; // 用于存放旧画刷   
   
    // 计算fDeltaX和fDeltaY   
    fDeltaX = (float)rectPicture.Width() / (POINT_COUNT - 1);   
    fDeltaY = (float)rectPicture.Height() / 80;   
   
    // 创建黑色新画刷   
    newBrush.CreateSolidBrush(RGB(0,0,0));   
    // 选择新画刷,并将旧画刷的指针保存到pOldBrush   
    pOldBrush = pDC->SelectObject(&newBrush);   
    // 以黑色画刷为绘图控件填充黑色,形成黑色背景   
    pDC->Rectangle(rectPicture);   
    // 恢复旧画刷   
    pDC->SelectObject(pOldBrush);   
    // 删除新画刷   
    newBrush.DeleteObject();   
   
    // 创建实心画笔,粗度为1,颜色为绿色   
    newPen.CreatePen(PS_SOLID, 1, RGB(0,255,0));   
    // 选择新画笔,并将旧画笔的指针保存到pOldPen   
    pOldPen = pDC->SelectObject(&newPen);   
   
    // 将当前点移动到绘图控件窗口的左下角,以此为波形的起始点   
    pDC->MoveTo(rectPicture.left, rectPicture.bottom);   
    // 计算m_nzValues数组中每个点对应的坐标位置,并依次连接,最终形成曲线   
    for (int i=0; i < POINT_COUNT; i++)   
    {   
        nX = rectPicture.left + (int)(i * fDeltaX);   
        nY = rectPicture.bottom - (int)(m_nzValues[i] * fDeltaY);   
        pDC->LineTo(nX, nY);   
    }   
   
    // 恢复旧画笔   
    pDC->SelectObject(pOldPen);   
    // 删除新画笔   
    newPen.DeleteObject();   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.

9、有了定时器和绘图成员函数,我们就可以在WM_TIMER消息的响应函数中添加对波形数据的定时处理和对波形的定时绘制了。定时器及WM_TIMER消息处理函数的添加方法如果忘记了,可以再到VS2010/MFC编程入门之四十四(MFC常用类:定时器Timer)温习下。

WM_TIMER消息的处理函数修改如下:

void CExample50Dlg::OnTimer(UINT_PTR nIDEvent)   
{   
    // TODO: Add your message handler code here and/or call default   
    CRect rectPicture;   
   
    // 将数组中的所有元素前移一个单位,第一个元素丢弃   
    for (int i=0; i<POINT_COUNT-1; i++)   
    {   
        m_nzValues[i] = m_nzValues[i+1];   
    }   
    // 为最后一个元素赋一个80以内的随机数值(整型)   
    m_nzValues[POINT_COUNT-1] = rand() % 80;   
   
    // 获取绘图控件的客户区坐标   
    // (客户区坐标以窗口的左上角为原点,这区别于以屏幕左上角为原点的屏幕坐标)   
    m_picDraw.GetClientRect(&rectPicture);   
   
    // 绘制波形图   
    DrawWave(m_picDraw.GetDC(), rectPicture);   
   
    CDialogEx::OnTimer(nIDEvent);   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

10、在对话框销毁时,定时器应关闭。所以为CExample50Dlg类添加WM_DESTROY消息的处理函数,并修改如下:

void CExample50Dlg::OnDestroy()   
{   
    CDialogEx::OnDestroy();   
   
    // TODO: Add your message handler code here   
   
    // 关闭定时器   
    KillTimer(1);   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

11、一切准备就绪,编译运行。最终的效果如下图:

MFC---CDC类及其屏幕绘图函数、画笔(CPen)、画刷(CBrush)(MFC常用类)_成员函数

MFC---CDC类及其屏幕绘图函数、画笔(CPen)、画刷(CBrush)(MFC常用类)_stm32_02

关于画笔,就讲到这里了,下一节将为大家简单讲讲画刷的使用。

上一节中主要讲的是画笔CPen的用法,前面也说了,GDI对象中最常用的就是画笔和画刷,本节就讲讲画刷CBrush。

依然是通过实例的方式来说明画刷的用法。此实例要实现的功能是,对话框上有一个按钮控件和一个图片控件,点击按钮弹出颜色对话框,然后在颜色对话框中选择颜色并点击“确定”后,图片控件中将显示选择的颜色。

其实此实例的功能,还可以通过重载对话框WM_CTLCOLOR消息的响应函数等方法来实现,但为讲解画刷的CBrush的使用,采用了下面代码中的方法。

以下是此实例的具体实施步骤:

1、创建一个基于对话框的MFC工程,名字设为“Example51”。

2、在自动生成的对话框模板IDD_EXAMPLE51_DIALOG中,删除“TODO: Place dialog controls here.”静态文本框,添加一个按钮控件和一个Picture控件,ID分别设为IDC_COLOR_SEL_BUTTON和IDC_COLOR_SHOW_STATIC,按钮控件的Caption属性设为“选择颜色”,此时对话框模板如下图所示:

MFC---CDC类及其屏幕绘图函数、画笔(CPen)、画刷(CBrush)(MFC常用类)_stm32_03

3、为Picture控件IDC_COLOR_SHOW_STATIC添加CStatic变量,名称设为m_picColor。

4、在对话框模板中双击“选择颜色”按钮,为其添加点击消息的响应函数CExample51Dlg::OnBnClickedColorSelButton(),修改该函数的实现如下:

void CExample51Dlg::OnBnClickedColorSelButton()   
{   
    // TODO: Add your control notification handler code here   
    COLORREF color = RGB(255, 0, 0);    // 颜色对话框的初始颜色   
    CColorDialog colorDlg(color);       // 构造颜色对话框,初始颜色为红色   
    CRect rectPicture;       // 图片控件的矩形区域坐标   
    CBrush newBrush;         // 创建的新画刷   
    CBrush *pOldBrush;       // 旧画刷的指针   
    CClientDC clientDC(this); // 构造客户区的设备上下文对象   
        
    if (IDOK == colorDlg.DoModal())    // 显示颜色对话框   
    {   
        // 如果点击了颜色对话框的“确定”按钮,则执行以下操作   
   
        // 获取颜色对话框中选择的颜色   
        color = colorDlg.GetColor();   
        // 用选择的颜色创建新画刷   
        newBrush.CreateSolidBrush(color);   
   
        // 获取图片控件矩形区域的屏幕坐标   
        m_picColor.GetWindowRect(&rectPicture);   
        // 将图片控件矩形区域的屏幕坐标转换为其父窗口即对话框的客户区坐标   
        ScreenToClient(&rectPicture);   
   
        // 选择新画刷,并保存旧画刷的指针到pOldBrush   
        pOldBrush = clientDC.SelectObject(&newBrush);   
        // 以新画刷为图片控件填充颜色   
        clientDC.Rectangle(rectPicture);   
   
        // 恢复旧画刷   
        clientDC.SelectObject(pOldBrush);   
        // 删除新画刷   
        newBrush.DeleteObject();   
    }   
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

代码中已经加了详细的注释,不过觉得有必要再简单介绍下CClientDC类。CClientDC类是CDC类的派生类,它生成的设备上下文对应于窗口的客户区,另一个类CWindowDC也是CDC类的派生类,它对应的则包括客户区和非客户区,也就是说,CClientDC只能在客户区绘图,而CWindowDC可在客户区和非客户区绘图。

讲到这里,有人会问,到底什么是客户区,什么是非客户区?非客户区包括窗口中的标题栏、菜单栏、状态栏、滚动条、边框等区域,客户区就是除去非客户区之外的区域。

5、编译运行程序,在结果对话框中,点击“选择颜色”按钮弹出颜色对话框,并选择颜色后,效果如下图:

MFC---CDC类及其屏幕绘图函数、画笔(CPen)、画刷(CBrush)(MFC常用类)_mfc_04

本节教程除讲了画刷CBrush的使用外,还让大家简单温习了颜色对话框的用法。

提醒大家,如果有什么函数或类的用法不是很清楚,均可以查阅MSDN。