1. CDC类的画线函数:
1) CDC的画线函数大致可以分为三类,一类是画直线和曲线,一类是画矩形等多边形,另一类是画圆、椭圆以及弧线等;
2) 使用CDC画线时有个非常重要的概念,就是当前绘图点,是一个客户区中的像素点,很多画线函数默认的起始画线点都是当前绘图点,用户可以通过相关函数设定当前绘图点的位置;
3) 画线函数命名时的后缀“To”,凡是带后缀“To”的函数都会自动将当前绘图点的位置设定成画线的末尾,比如LineTo、PolylineTo等,这些函数在画线结束后都会默认将当前绘图点的位置更新至画线的最后一个像素点位置,而没有后缀“To”的函数画线结束后不改变当前绘图点的位置,还有更直接的MoveTo函数,它可以直接显示设定当前绘图点的位置;
2. 画直线:
1) 使用MoveTo和LineTo函数的组合;
// 设定当前绘图点的位置,返回设定前的坐标
CPoint MoveTo(int x, int y);
CPoint MoveTo(CPoint point);
// 从当前绘图点的位置开始画线到参数指定的位置并更新当前位置,画线成功返回TRUE
// !!!注意:最后一个像素点(即参数给定的像素点)不上色!
BOOL LineTo(int x, int y);
BOOL LineTo(CPoint point);
2) 当然也可以连续使用LineTo接着上一条的末尾话而无需再重设当前位置:
dc.MoveTo(0, 0);
dc.LineTo(0, 100);
dc.LineTo(100, 100);
1) 使用Polyline函数:
// 顺序将数组lpPoints指定的前nCount个点用直线连起来形成折线
// 画线成功返回TRUE
// 直接画,画完不改变当前位置
BOOL Polyline(LPPOINT lpPoints, int nCount);
// 以当前位置作为起点画,画完后最后一个点变为当前位置
BOOL PolylineTo(LPPOINT lpPoints, int nCount);
!!!CPoint数组也可以用,POINT是从Win32继承下来的,为了兼容以前的程序以及以前的变成风格,建议使用新的CPoint。
2) 以下两者效果相同:
POINT aPoint[5] = { 0, 0, 0, 100, 100, 100, 100, 0, 0, 0 };
dc.Polyline(aPoint, 5);
POINT aPoint[4] = { 0, 100, 100, 100, 100, 0, 0, 0 };
dc.Polyline(aPoint, 4);
!!!前缀a表示数组,aPoint就是Point数组的意思,即array point。
3) 一个示例——用折线来拟合正弦曲线:
#include <math.h>
#define SEGMENTS 500
#define PI 3.1415926
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
CRect rect;
CPoint aPoint[SEGMENTS];
GetClientRect(&rect);
int nWidth = rect.Width();
int nHeight = rect.Height();
for (int i = 0; i < SEGMENTS; i++) {
aPoint[i].x = i * nWidth / SEGMENTS;
aPoint[i].y = (int)(nHeight * (1 - sin(2 * PI * i / SEGMENTS)) / 2);
}
dc.Polyline(aPoint, SEGMENTS);
}
4. 画弧:
1) 弧线即圆或椭圆周边上截取的一段曲线,使用Arc绘制:
// 先指定一个矩形框作为椭圆的外切框
// 接着指定两个点,从椭圆中心到这两个点的线段和椭圆弧的交点即为
// 最终所画的弧的起点和终点,起点到终点是逆时针的!!
// 推荐使用后者,后者更加直观
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);
2) 奇葩的ArcTo:参数返回值和Arc一模一样,只不过画之前会先从当前位置连一条直线到ptStart,然后再画弧,弧画完后将当前位置改为ptEnd,一般很少用。
1) Bezier曲线是一种光滑的曲线,有两个端点和两个控点组成,可以使用PolyBezier和PolyBezierTo函数绘制:
// POINT数组中点的顺序为:端点1、控点1、控点2、端点2
// 如果连续化多条,会以前一条的端点2作为后一条的端点1
// 直接画,点的个数nCount必须是1+3n
BOOL PolyBezier(const POINT* lpPoints, int nCount);
// 以当前位置作为第一条的端点1,nCount必须是3n
BOOL PolyBezierTo(const POINT* lpPoints, int nCount);
2) 画一个Nike的Swoosh:
POINT aPoint1[4] = {120, 100, 120, 200, 250, 150, 500, 40};
POINT aPoint2[4] = {120, 100, 50, 350, 250, 200, 500, 40 };
dc.PolyBezier(aPoint1, 4);
dc.PolyBezier(aPoint2, 4);
6. 画椭圆:
// 直接指定外接矩形即可
BOOL Ellipse(int x1, int y1, int x2, int y2);
BOOL Ellipse(LPCRECT lpRect);
7. 画矩形:
1) 普通矩形直接使用Rectangle:
// 直接指定即可
BOOL Rectangle(int x1, int y1, int x2, int y2);
BOOL Rectangle(LPCRECT lpRect);
2) 圆角矩形使用RoundRect:
// 矩形边框直接指定
// 圆角其实是一个小椭圆,最后一个参数point指定了小椭圆外接矩形的尺寸
// point.x指定了宽,point.y指定了高
BOOL RoundRect(int x1, int y1, int x2, int y2, int x3, int y3);
BOOL RoundRect(LPCRECT lpRect, POINT point);
3) 关于矩形边框上的像素问题:边框范围是x1 ~ x2,y1 ~ y2,其中x1可以大于x2,y1也可以大于y2,而x2这条线上的边和y2这条线上的边的像素点不上色!!这就是线的终点不上色延伸到了二维平面上来了!
8. 楔形(弦形)、饼:
1) 楔形就是将弧的两个端点直接用直线连接形成一个封闭图形,饼就是将椭圆中点和弧的两个端点直线连接形成的封闭图形;
2) 分别使用函数Chord和Pie完成绘制,其用法和函数原型和Arc一模一样,都是矩形框定椭圆,两个点定弧,接下里的活都是自动完成的:
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);
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);
3) 示例——制作一个饼图报表(四个季度的业绩):
void CMyWnd::OnPaint()
{
int nPerformences[4] = { 125, 376, 252, 184 };
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
// 将客户区的逻辑原点放到正中间的位置(而不是默认的左上角)
dc.SetViewportOrg(rect.Width() >> 1, rect.Height() >> 1);
int nTotal = 0;
for (int i = 0; i < 4; i++) {
nTotal += nPerformences[i];
}
int x1 = 0; // 从-y轴开始画
int y1 = -1000;
int nSum = 0;
for (i = 0; i < 4; i++) {
nSum += nPerformences[i];
double rad = (nSum * 2 * PI / nTotal) + PI;
int x2 = (int)(sin(rad) * 1000); // == cos(3π/2 - rad)
int y2 = (int)(cos(rad) * 1000 * 3 / 4); // == sin(3π/2 - rad)
dc.Pie(-200, -150, 200, 150, x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
}