上一节中鸡啄米讲了 CDC 类及其屏幕绘图函数 http://www.jizhuomi.com/software/244.html , 本节的主要内容是 GDI 对象之画笔 CPen.
GDI 对象
在 http://www.jizhuomi.com/catalog.asp?tags=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." 静态文本框 http://www.jizhuomi.com/software/179.html , 添加一个 Picture 控件 http://www.jizhuomi.com/software/193.html ,ID 设为 IDC_WAVE_DRAW.
3, 为 Picture 控件 IDC_WAVE_DRAW 添加 CStatic 变量, 名称设为 m_picDraw.
4, 在文件 Example50Dlg.h 文件中 CExample50Dlg 类声明的上面添加宏定义:
C++ 代码
#define POINT_COUNT 100
此符号常量的意义是波形的点数, 这里用 define 将其定义为符号常量 http://www.jizhuomi.com/software/70.html 是为了方便以后可能的修改, 假如我们以后想将点数改为 200, 则只改此宏定义就可以了:#define POINT_COUNT 200, 而如果没有使用符号常量, 在程序中直接使用了 100, 那么就需要将所有使用 100 的位置找出来, 并替换为 200, 这样不仅麻烦也很容易出错, 所以最好是将其定义为符号常量.
5, 在 CExample50Dlg.h 文件中为 CExample50Dlg 类添加成员数组:
C++ 代码
int m_nzValues[POINT_COUNT];
此数组用于存放波形数据.
6, 在 CExample50Dlg 类的构造函数中为数组 m_nzValues 的元素赋初值:
C++ 代码
- 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);
- }
7, 在 CExample50Dlg 对话框的初始化成员函数 CExample50Dlg::OnInitDialog() 中, 构造随机数生成器, 并启动定时器. CExample50Dlg::OnInitDialog() 修改如下:
C++ 代码
- 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
- }
8, 为 CExample50Dlg 类添加波形绘制的成员函数 CExample50Dlg::DrawWave(CDC *pDC, CRect &rectPicture), 参数分别为设备上下文指针和绘图的矩形区域.
C++ 代码
- 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();
- }
9, 有了定时器和绘图成员函数, 我们就可以在 WM_TIMER 消息的响应函数中添加对波形数据的定时处理和对波形的定时绘制了. 定时器及 WM_TIMER 消息处理函数的添加方法如果忘记了, 可以再到 VS2010/MFC 编程入门之四十四 (MFC 常用类: 定时器 Timer) http://www.jizhuomi.com/software/232.html 温习下.
WM_TIMER 消息的处理函数修改如下:
C++ 代码
- 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);
- }
10, 在对话框销毁时, 定时器应关闭. 所以为 CExample50Dlg 类添加 WM_DESTROY 消息的处理函数, 并修改如下:
C++ 代码
- void CExample50Dlg::OnDestroy()
- {
- CDialogEx::OnDestroy();
- // TODO: Add your message handler code here
- // 关闭定时器
- KillTimer(1);
- }
11, 一切准备就绪, 编译运行. 最终的效果如下图:
关于画笔, 鸡啄米就讲到这里了, 下一节将为大家简单讲讲画刷的使用. 谢谢大家的关注!
来源: http://www.bubuko.com/infodetail-2768482.html