在画点或画线时,系统使用当前DC中的画笔,所以想画出不同颜色、线型和粗细的直线,需要使用新画笔。
在创建画笔后必须将其选入DC才会在绘图时产生效果。
MFC 中定义了一些 Windows 的图形设备界面(GDI)对象类,即绘图工具类,它们是 作图的笔、给图形涂色的画刷,以及字体、位图、区域和调色板等影响绘图的工具。
绘图工具类主要有:CGdiObject、CPen、CBrush、CFont、CRgn 和 CBitmap 类等。 其中 CGdiObject 类是 CObject 类的派生类,它是绘图工具类的基类。
画笔 CPen 用于绘制直线、矩形、圆等几何图形,其属性主要有:线宽、线型和色 彩等。
画刷 CBrush 用于填充绘图区域,其属性有填充图案和色彩。
在生成设备描述表类对象时,如果没有指定笔和画刷,那么默认笔为线宽 1 个像素的黑色实线;默认的画刷为单一白色图案,即图形内部填充白色。
CPen 画笔主要用于绘制直线、矩形、圆等几何图形。
CPen 的构造函数如下:
CPen( int nPenStyle, int nWidth, COLORREF crColor );
其中参数 nPenStyle 为线型、nWidth 为线宽、crColor 为色彩。
其创建画笔的成员函数 CreatePen 的参数与构造函数相同。
1)定义一个 CPen 对象,并用其成员函数 CreatePen 构造画笔。
CPen p;
p.CreatePen(PS_SOLID, 1, RGB(255,0,0));
2)使用 CPen 构造函数建立一个画笔对象,并定义其参数 。
CPen p(PS_SOLID, 1, RGB(255,0,0));
3) 用 CPen 指针对象的形式动态创建一个画笔
CPen *pPen;
pPen=new CPen(PS_SOLID, 1, RGB(255,0,0));
使用完毕后必须释放给画笔分配的内存空间,即删除画笔:
delete pPen;
线型 PS_DASH、PS_DOT、PS_DASHDOT 和 PS_DASHDOTDOT 只有在画笔宽度为 1 个像素时才能使用。如果线宽 nWidth 值大于 1 个像素,则画出给定宽度的实 线(PS_SOLID)。
pen.DeleteObject();
在前面程序的基础上,为绘图程序MyDrawSystem增加”设置|线型参数”命令,点击该命令后调用“线型参数”对话框完成“线型”、“线宽”、“线颜色”的设置。
运行程序可以完成不同颜色、线型和粗细的直线的绘制。
2. 创建“线型参数”对话框。
(1)添加新的对话框资源,ID为IDD_DLG_LINETYPE,Caption为 “设置线型参数”。添加静态控件、列表框控件IDC_LIST_LINETYPE(注意:不勾选列表框控件的Sort属性)、编辑框控件IDC_EDIT_LINEWIDTH和按钮控件IDC_BUTTON_LINECOLOR。
(2)为该对话框添加新类CLineTypeDlg。在CLineTypeDlg类中添加公有成员变量如下。
COLORREF m_LineColor;
//用于保存用户在颜色对话框中选中的颜色
int m_nLineType;
//用于保存用户在线型列表框中选中的线型
(3)添加各控件的成员变量,如图所示。
(4)下面对“线型”列表框控件进行初始化。先添加对话框类的初始化成员函数OnInitDialog()。
在OnInitDialog()函数中添加初始化代码如下。
BOOL CLineTypeDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: 在此添加额外的初始化
m_List_LineType.AddString("实线"); //PS_SOLID
m_List_LineType.AddString("虚线"); //PS_DASH
m_List_LineType.AddString("点线"); //PS_DOT
m_List_LineType.AddString("点划线"); //PS_DASHDOT
m_List_LineType.AddString("双点划线"); //PS_DASHDOTDOT
m_List_LineType.SetCurSel(0);
m_nLineType = 0;
m_nLineWidth = 1;
m_LineColor = RGB(0, 0, 0);
UpdateData(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
(5)添加”LBN_SELCHANGE”消息的响应函数。该响应函数的代码如下。
该响应函数的代码如下。
void CLineTypeDlg::OnLbnSelchangeListLinetype()
{
// TODO: Add your control notification handler code here
int nIndex = m_List_LineType.GetCurSel();
switch(nIndex)
{
case 0: m_nLineType = PS_SOLID; break;
case 1: m_nLineType = PS_DASH; break;
case 2: m_nLineType = PS_DOT; break;
case 3: m_nLineType = PS_DASHDOT; break;
case 4: m_nLineType = PS_DASHDOTDOT; break;
default: m_nLineType = PS_SOLID;
}
}
函数GetCurSel()用来返回当前被选择项的索引。
(6) 添加“颜色对话框”按钮IDC_BUTTON_LINECOLOR的消息响应函数。添加代码如下。
void CLineTypeDlg::OnButtonLinecolor()
{
// TODO: Add your control notification handler code here
CColorDialog ColorDlg;
if(ColorDlg.DoModal() == IDOK)
m_LineColor = ColorDlg.GetColor();
}
int m_nPenType; //线型
int m_nPenWidth; //线宽
COLORREF m_PenColor; //线颜色
并在视图类中初始化上述线型参数变量。代码如下。
CMyDrawSystemView::CMyDrawSystemView()
{ // TODO: add construction code here
m_nMouseStep = 0; //鼠标单击次数初始为0
m_nFigureType= 0; //无图元类型
m_nPenType = 0; //线型
m_nPenWidth = 1; //线宽
m_PenColor = RGB(0,0,0); //线颜色
}
#include "LineTypeDlg.h"
void CMyDrawSystemView::OnLineType()
{ // TODO: Add your command handler code here
CLineTypeDlg LineTypeDlg;
if(LineTypeDlg.DoModal() == IDOK)
{
m_nPenType = LineTypeDlg.m_nLineType;
m_nPenWidth = LineTypeDlg.m_nLineWidth;
m_PenColor = LineTypeDlg.m_LineColor;
}
}
switch(m_nMouseStep) //鼠标单击次数
{ case 0: //第一次单击鼠标左键
m_StartPos = m_EndPos = point; //获取线段起点
m_nMouseStep++;
break;
case 1: //第二次单击鼠标左键
CPen *pPen,*pOldPen; //定义两个画笔指针变量
pPen=new CPen(m_nPenType,m_nPenWidth,m_PenColor);
pOldPen=(CPen *)pDC->SelectObject(pPen);
m_EndPos = point; //获得线段终点
…………
pDoc->m_FigureList.AddTail(pLine); //将新生成的直线存入图元链表
pDC->SelectObject(pOldPen); //恢复旧画笔
delete pPen; //删除动态创建的画笔
} //switch(m_nMouseStep)
break;
class CLine :public CObject
{ CPoint m_Start;
CPointm_End; //直线的起点和终点
int m_nLineType;
int m_nLineWidth;
COLORREF m_LineColor;
public:
CLine(void);
CLine(CPoint start, CPoint end,int linetype=PS_SOLID,int linewidth=1,COLORREF linecolor=RGB(0,0,0));
~CLine(void);
void Draw (CDC *pDC);
};
CLine::~CLine()
{}
void CLine::Draw (CDC *pDC) //绘制直线段的成员函数
{
CPen *pPen,*pOldPen; //定义两个画笔指针变量
pPen=new CPen(m_nLineType,m_nLineWidth,m_LineColor);
pOldPen=(CPen *)pDC->SelectObject(pPen);
pDC->MoveTo(m_Start);
pDC->LineTo(m_End);
pDC->SelectObject(pOldPen); //恢复旧画笔
delete pPen; //删除动态创建的画笔
}
CLine * pLine = new CLine(m_StartPos, m_EndPos);
pLine->Draw(pDC);
pDoc->m_FigureList.AddTail(pLine);
//将新绘制的直线存入图元链表
修改之后代码是:
CLine *pLine = new CLine(m_StartPos, m_EndPos, m_nPenType, m_nPenWidth,m_PenColor);
pLine->Draw(pDC);
pDoc->m_FigureList.AddTail(pLine);
//将新绘制的直线存入图元链表
参考Text的序列化过程自己完成。
对于自由曲线、多段线这样由数量不确定的直线段构成的图形,在编写程序的过程中主要解决的问题是:
在绘制过程中如何收集这种不定数量的图形数据,因为不能像直线那样“画完”了再生成其对象来保存数据。
而是需要使用合适的动态数据结构,做到一边“移动鼠标”一边“记录和保存坐标”;
并且还要考虑如何在自由曲线类、文档类、视图类这几个类当中使用这个动态的数据结构。
【编程步骤】
(1)增加自由曲线类的定义。自由曲线实际上是由许多条短的直线段连接构成,由于构成自由曲线的线段的数量不确定,因此要想将整条曲线保存起来,需要选用合适的动态数据结构,在这里使用MFC集合类中的数组较为合适。
其实,自由曲线可以看做是由许多个距离很近的点连线而成的,可以将这些点保存到动态数组中。在这里使用一般模板类CArray类来定义这个动态数组。
使用CArray类对象可以创建数组,并提供了很多对数组元素进行操作的成员函数。由于这是一个类模板,因此可以创建任何数据类型的数组。CArray类的声明如下:
template
class CArray : public CObject
CArray类派生自CObject类,有两个模板参数,第一个模板参数TYPE指定了存储在数组中的对象的类型。后一个模板参数ARG_TYPE指定了用于访问存储在数组中对象的参数类型。
(1)为了能够使类层次更加清晰,定义基类CLineType
在LineType.h中添加代码如下。
class CLineType : public CObject
{
protected:
COLORREF m_LineColor; //图元颜色
int m_nLineType; //图元的线型
int m_nLineWidth; //图元的线宽
public:
CLineType();
CLineType(int linewidth, int linetype, COLORREF linecolor);
virtual ~CLineType();
};
在LineType.cpp中添加代码如下。
CLineType::CLineType()
{
m_LineColor = RGB(0,0,0); //图元颜色为黑
m_nLineType = PS_SOLID; //图元的线型为实线
m_nLineWidth =1 ; //图元的线宽为1
}
CLineType:: CLineType(int linewidth, int linetype, COLORREF linecolor)
{
m_nLineWidth = linewidth;
m_nLineType = linetype;
m_LineColor = linecolor;
}
(2)在LineType.h中添加代码如下。
class CLineType : public CObject
{ ……
public:
……
virtual void Draw(CDC* pDC)=0;
//纯虚函数——画图函数
};
(3) 修改Cline类在Line.h中添加代码如下。
#include "LineType.h"
class CLine : public CLineType
{
protected:
CPoint m_Start,m_End; //直线的起点和终点
public:
CLine();
CLine(CPoint start, CPoint end, int linewidth=1,
int linetype=PS_SOLID,
COLORREF linecolor=RGB(0,0,0));
virtual ~CLine();
void Draw (CDC *pDC);
};
在Line.cpp中添加代码如下。
CLine::CLine()
{
m_Start.x=m_Start.y=0;
m_End.x=m_End.y=0;
}
CLine::CLine(CPoint start, CPoint end, int linewidth, int linetype, COLORREF linecolor):CLineType(linewidth,linetype,linecolor)
{
m_Start = begin;
m_End = end;
}
(4)Curve.h文件中的代码如下。
#include "LineType.h"
#include "Line.h"
#include //使用MFC类模板需要包含该头文件
class CCurve : public CLineType
{
protected:
CArray<CPoint,CPoint&> m_CurveArray;
//存放曲线中每个点的动态数组
public:
CCurve(int linewidth, int linetype, COLORREF linecolor,CPoint point);
virtual ~CCurve();
void AddPoint(CPoint point); //向动态数组中添加新的点
void Draw(CDC* pDC);
};
Curve…cpp文件中的代码如下。
CCurve::CCurve(int linewidth, int linetype, COLORREF linecolor,CPoint point):CLineType(linewidth, linetype,linecolor)
{
m_CurveArray.SetAtGrow(0,point);
}
CCurve::~CCurve()
{
m_CurveArray.RemoveAll();
}
在构造函数中使用了SetAtGrow()函数第一次为数组增添元素,同时为下标为0的元素指定了值。该函数功能是在指定的索引上设置数组元素。如果必要,数组自动增长(调整上界以接纳新元素)。
void CCurve::AddPoint(CPoint point) //添加新的点
{
m_CurveArray.Add(point); //将该点加到动态数组中
}
void CCurve::Draw(CDC *pDC) //绘制自由曲线的成员函数
{
CPen *pPenOld,PenNew;
SetCurrentPen(PenNew); //生成新画笔
pPenOld = pDC->SelectObject(&PenNew); //将新画笔选入DC
int nIndex = m_CurveArray.GetSize();
for(int i=0;i<nIndex-1;i++)
{
pDC->MoveTo(m_CurveArray.GetAt(i));
pDC->LineTo(m_CurveArray.GetAt(i+1));
}
pDC->SelectObject(pPenOld); //恢复DC中原来的画笔
PenNew.DeleteObject(); //删除用完的画笔
}
(2)在视图类中添加成员变量表示自由曲线的起始点和当前鼠标的绘图状态,以及一个自由曲线对象的指针变量。
BOOL m_BDrawing ; //绘图标记
CCurve *pCurve; //自由曲线对象指针
在MyDrawSystemView.h文件中添加包含命令如下:
#include "Curve.h"
在视图类CMyDrawSystemView的构造函数中初始化m_BDrawing为FALSE。
(3)为菜单项“绘图”|“自由曲线”添加相应的消息响应函数。将该命令映射到视图类。代码如下。
void CMyDrawSystemView::OnCurveMenu() //菜单命令“绘图”|“自由曲线”
{
// TODO: Add your command handler code here
m_nFigureType=3; //3代表图元为自由曲线
//单击菜单命令后获得十字光标句柄
m_hCursor=AfxGetApp()->
LoadStandardCursor(IDC_CROSS);
}
(4)当按下鼠标左键进入拖曳状态,鼠标光标变成十字光标,开始绘制自由曲线。
考虑到单击了“自由曲线”命令之后,用户可能绘制多条自由曲线,而一条自由曲线对应一个自由曲线对象,因此:
每次按下鼠标左键,都生成一个自由曲线对象,并且该自由曲线的起点即按下鼠标左键时的鼠标位置;
移动鼠标时,绘制线段,并且保存当前线段的起点和终点坐标;
抬起鼠标左键则拖曳结束,释放鼠标,还原鼠标形状,绘制结束,将生成的自由曲线对象添加到图元链表中。
添加如下代码到三个鼠标响应函数中。
void CMyDrawSystemView::OnLButtonDown(UINT nFlags, CPoint point)
{ CMyDrawSystemDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
switch(m_nFigureType) //根据图元类型进行分支处理
{…
case 3: //绘制自由曲线
SetCapture(); //捕获鼠标
::SetCursor(m_hCursor); //设置十字光标
m_BDrawing = TRUE; //设置拖曳标记
//生成自由曲线对象
pCurve=new
CCurve(m_nPenWidth,m_nPenType,m_PenColor,point);
break;
…
}// switch(m_nFigureType)
CView::OnLButtonDown(nFlags, point);
}
void CMyDrawSystemView::OnMouseMove(UINT nFlags, CPoint point)
{
CDC* pDC = GetDC(); //获取指定窗口的客户区设备环境
switch(m_nFigureType) //根据图元类型进行分支处理
{ case 1: //绘制直线
…
case 3: //绘制自由曲线
if(m_BDrawing)
{
pCurve->AddPoint(point); //加入点到数组
pCurve->Draw(pDC); //绘制曲线
}
break;
…
}// switch(m_nFigureType)
…
CView::OnMouseMove(nFlags, point);
}
void CMyDrawSystemView::OnLButtonUp(UINT nFlags, CPoint point)
{
CMyDrawSystemDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CDC* pDC = GetDC(); //获取指定窗口的客户区的设备环境
switch(m_nFigureType) //根据图元类型进行分支处理
{
case 1: //绘制直线
…
case 3: //绘制自由曲线
if(m_BDrawing)
{
m_BDrawing = FALSE ; //清除绘图标记
ReleaseCapture(); //释放鼠标,还原鼠标形状
pDoc->m_FigureList.AddTail(pCurve);
//将新绘制的自由曲线存入图元链表
}
break;
…
}// switch(m_nFigureType)
ReleaseDC(pDC); //释放设备环境
CView::OnLButtonUp(nFlags, point);
}
(6)OnDraw函数修改为:
POSITION pos = pDoc->m_FigureList.GetHeadPosition();
while(pos != NULL)
{
CLineType* pFigure = (CLineType*) pDoc->
m_FigureList.GetNext(pos);
pFigure->Draw(pDC);
}
(7)在关闭窗口或点击“File|New”命令时要删除自由曲线图元。MyDrawSystemDoc.cpp文件中的函数RemoveList()代码不变。
编译、连接、运行程序,可以绘制各种线型、颜色和线宽的曲线和直线。