MFC画图板的点、线、曲线、矩形、椭圆、多边形的实现

 

程序源码: https://github.com/JeffChen95/Draw_Image  

 

  画图时,需要准备好画纸、画笔、颜料、刷子等工具,与此对应,在MFC中需要做的准备就是

CClientDC dc(this);     //创建dc,设备描述表也就是设备环境,可理解成画纸
CPen pen(m_nLineStyle, m_nLineWidth, m_color);  //创建绘制的画笔,画笔中有风格、宽度、颜色CBrush *pbrush = new CBrush();     //创建画刷
pbrush->CreateSolidBrush(m_color);	//创建绘制时颜料填充的画刷(实心)
//pbrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); //创建绘制时透明的画刷

        找到笔和颜料,要把他拿到画纸跟前放一起,这就是dc选入画笔和刷子。

dc.SelectObject(&pen); //将画刷和画笔选入设备描述表中
dc.SelectObject(pbrush);

        在画图中,由主要三个关键动作:鼠标按下、鼠标移动、鼠标抬起。分别对应有三个消息映射函数

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

        所以要画出点线和形状时,无非就是对三个动作内容的完善。

        1.第一个动作就是鼠标按下,要画图,按下的时候就要保存按下时的坐标点,这是实现这几个功能中这个函数主要的内容,声明如下:(这里需要注意多边形需要记录多个点,所以还需要一个记录点个数的变量)

CPoint m_ptOrigin;		//起始点坐标
CPoint m_ptOld;			//上一次的旧点
CPoint m_ptPolyFirst;	//折线图中的第一个点
CPoint m_ptPolyLast;	//折线图中最后点
CPoint m_arrayP[255];	//存储折线图中所有点
int m_PolyCount;		//折线的点的计数

        以上是变量的声明,下面是在OnLButtonDown函数中对他们的操作,point是函数的传入参数,表示当前鼠标位置。

m_ptOrigin = point;
m_ptOld = point;
if (m_PolyCount == 0)
{
	m_ptPolyFirst = point;
}

       2.记录好点之后,接下来的动作是鼠标移动,对应OnMouseMove函数,在这个函数中,就是实现画图的核心部分。我采用的是枚举的方法区别不同的绘图类型。

enum DTYPE		//画图形类型
{
	DLINE,		//画线
	DCURVE,		//画曲线
	DRECT,		//画矩形
	DRRECT,		//画圆角矩形
	DARC,		//画圆弧
	DELLI,		//画椭圆
	DCIRCLE,	//画圆
	DPOLY,		//画多边
};

       同样,在这个方法中,也需要创建dc,创建并选入画笔、刷子等设备。然后再根据不同的绘图类型实现不同的方法操作。

//针对画直线、曲线、多边形来说,核心主要靠两句
dc.MoveTo(m_ptOrigin);
dc.LineTo(m_ptOld);		//擦去上一次的线

//针对矩形,就靠一句
dc.Rectangle(CRect(m_ptOrigin, m_ptOld));

//针对圆,也是靠一句
dc.Ellipse(CRect(m_ptOrigin, m_ptOld));

      在这里要注意,如果直接这样写,会出现很多问题,比如密集画线等,如图

     解决这个问题,我采用的是WindowsAPI中的SetROP2方法,原型如下

int SetROP2( HDC hdc, int fnDrawMode);        //函数原型

     在MFC中,第一个参数已经设置,只需要传入第二个参数,此处要选“R2_NOTXORPEN”宏作为输入参数,表示使用当前画笔的反色绘图,所以上述的操作完善后应该是:

case DLINE:    //直线
	dc.SetROP2(R2_NOTXORPEN);		//逆转当前屏幕颜色来画线的绘图方式
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(m_ptOld);		//擦去上一次的线
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);		//绘制当前的临时线
	m_ptOld = point;
	break;
case DRECT:    //矩形
	dc.SetROP2(R2_NOTXORPEN);		//逆转当前屏幕颜色来画线的绘图方式
	dc.Rectangle(CRect(m_ptOrigin, m_ptOld));
	dc.Rectangle(CRect(m_ptOrigin, point));
	m_ptOld = point;
	break;
case DELLI:    //椭圆
	dc.SetROP2(R2_NOTXORPEN);		//逆转当前屏幕颜色来画线的绘图方式
	dc.Ellipse(CRect(m_ptOrigin, m_ptOld));
	dc.Ellipse(CRect(m_ptOrigin, point));
	m_ptOld = point;
	break;
case DCURVE:    //曲线
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);
	m_ptOrigin = point;
	break;
case DPOLY:    //折线
	if (m_PolyCount != 0)
	{
		dc.SetROP2(R2_NOTXORPEN);		//逆转当前屏幕颜色来画线的绘图方式
		dc.MoveTo(m_ptPolyLast);
		dc.LineTo(point);		
		dc.MoveTo(m_ptPolyLast);
		dc.LineTo(point);		//绘制当前的临时线
	}
	else
	{
		dc.SetROP2(R2_NOTXORPEN);		//逆转当前屏幕颜色来画线的绘图方式
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(m_ptOld);		//擦去上一次的线
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);		//绘制当前的临时线
	}
	m_ptOld = point;
	break;

        3.三步操作中最后一步就是鼠标按键抬起,对应OnLButtonUp函数,初始化的操作与上都相似,包括创建、选入和复制,

CClientDC dc(this); //创建dc
CPen pen(m_nLineStyle, m_nLineWidth, m_color); //创建绘制的画笔
CBrush *pbrush = new CBrush();
if (m_flagFullPaint == true)
{
	pbrush->CreateSolidBrush(m_color);		//创建绘制时填充的画刷
}
else
{
	pbrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); //创建绘制时填充的画刷
}
dc.SelectObject(&pen); //将画刷和画笔选入设备描述表中
dc.SelectObject(pbrush);

if (!m_pMDC->m_hDC)
{
	m_pMDC->CreateCompatibleDC(&dc);
	m_pMDC->SetViewportOrg(-m_scRollpt);
}

       在对各种绘图类型的操作实现中也与MouseMove中的方法类似,关键注意抬起时要擦除上一条记录,保留抬起时的记录作为显示在屏幕上的图像。

case DLINE:		//画线
	dc.SetROP2(R2_NOTXORPEN);		//逆转当前屏幕颜色来画线的绘图方式
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(m_ptOld);		//擦去上一次的线
	dc.SetROP2(R2_COPYPEN);		//缺省绘图模式,像素为画笔颜色
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);		//绘制固定线
	m_pMDC->MoveTo(m_ptOrigin);
	m_pMDC->LineTo(m_ptOld);		//在内存中备份
	break;
case DRECT:		//画矩形
	dc.SetROP2(R2_NOTXORPEN);		//逆转当前屏幕颜色来画线的绘图方式
	dc.Rectangle(CRect(m_ptOrigin, m_ptOld));
	dc.SetROP2(R2_COPYPEN);		//缺省绘图模式,像素为画笔颜色
	dc.Rectangle(CRect(m_ptOrigin, point));
	m_pMDC->Rectangle(CRect(m_ptOrigin, m_ptOld));
	break;
case DELLI:		//画椭圆
	dc.SetROP2(R2_NOTXORPEN);		//逆转当前屏幕颜色来画线的绘图方式
	dc.Ellipse(CRect(m_ptOrigin, m_ptOld));
	dc.SetROP2(R2_COPYPEN);		//缺省绘图模式,像素为画笔颜色
	dc.Ellipse(CRect(m_ptOrigin, point));
	m_pMDC->Ellipse(CRect(m_ptOrigin, m_ptOld));
	break;
case DCURVE:	//画曲线
	dc.MoveTo(m_ptOrigin);
	dc.LineTo(point);
	m_ptOrigin = point;
	break;
case DPOLY:		//画多边形
	m_arrayP[m_PolyCount++] = point;
	m_ptPolyLast = point;
	if (m_PolyCount > 1)	//点超过1个时,画折线
	{
		dc.Polyline(m_arrayP, m_PolyCount);
		m_pMDC->Polyline(m_arrayP, m_PolyCount);
	}
	break;

    可能你们会发现上述多了一句m_pMDC的操作,因为你画完图或者移动改变窗口时需要重绘,重绘就需要把内存中的图像显示在屏幕上,所以相对应的在鼠标抬起时,要将屏幕上的内容保存到内存DC中,以便绘图时可以取出来。

     相对于多边形来说,还需要最后一步,就是“封口”,将最后画完的点与第一个点连接起来,这样才是一个完整的图形,我是在右键抬起的操作中实现的这部分,核心代码如下。

case DPOLY:
	if (m_PolyCount != 0)
	{
		dc.MoveTo(m_ptPolyLast);
		dc.LineTo(point);
		dc.MoveTo(point);
		dc.LineTo(m_ptPolyFirst);
		m_pMDC->MoveTo(m_ptPolyLast);
		m_pMDC->LineTo(point);
		m_pMDC->MoveTo(point);
		m_pMDC->LineTo(m_ptPolyFirst);
		m_PolyCount = 0;
	}
	break;

       这三步都实现了,图像基本就可以显示了。当然后续还有一些操作才能更完善。

       我的效果如下图

    除此之外,我还实现了很多画图板的功能,包括画图板上没有的功能,大致功能如下:

 有兴趣的朋友,可以看看,欢迎补充:https://github.com/JeffChen95/Draw_Image

  • 25
    点赞
  • 187
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值