MFC画图板程序绘制流程:


  • LButtonDown记录按下的坐标点为beginPoint,存储到vector变量中,并设置按下标志 b_MouseDown=true;

  • LButtonDbClk记录鼠标双击消息,同样为beginPoint,注意此处,很多人没有写这个消息响应会导致一些bug

  • LButtonUp记录弹起的坐标点endPoint , 修改标志为b_MouseDown=false;

  • MouseMove根据起始坐标点使用内存DC绘制图形,当然此处标志必须为true才可以执行绘图



以下为内存DC绘图的核心代码

void CPaint::OnMouseMove(UINT nFlags, CPoint point)
{
    CDC* dc;
    dc = GetDC();
    b_MosueMove = true;//标志鼠标处于移动状态
    
    //绘制对应的图形     theLine为枚举变量,对应着工具按钮
    switch(ShapeType)
    {
        case theLine:
            MoveDrawShape(theLine,point,dc);
            break;
        case theRect:
            ...//省略
    }
}

void CPaint::MoveDrawShape(int drawType, CPoint point, CDC* dc)
{
	CRect rcClient;
	GetClientRect(rcClient);

	CDC MemDC;
	MemDC.CreateCompatibleDC(NULL);
	CBitmap MemBitmap;
	MemBitmap.CreateCompatibleBitmap(dc, rcClient.Width(), rcClient.Height());//创建内存中的图像

	MemDC.SelectStockObject(NULL_BRUSH);//透明画刷

	MemDC.SelectObject(&MemBitmap);//指定内存显示设备在内存中的图像上画图
	MemDC.FillSolidRect(0, 0, rcClient.Width(), rcClient.Height(), RGB(255, 255, 255));



	//把历史图形循环画到内存dc:MemDC中去
	if (!vShapeInfo.empty())
	    ...


        //绘制当前移动状态的图形到内存DC中
	switch (drawType)
	{
	case theLine:
		MemDC.MoveTo(vShapeInfo.back().beginPoint);
		MemDC.LineTo(point);
		break;
	case theRect:
		...
	}
	
	//拷贝内存DC中的图像到正式的DC中,大小为客户区大小
	dc->BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &MemDC, 0, 0, SRCCOPY);
}


上面所实现绘制历史图形需要用到Vector向量,可以如下定义:

//枚举变量,对应工具按钮
enum {
	theLine,
	theRect,
	theCircle
	...
};

//图像信息结构体,可以根据需要自己添加
struct ShapeInfo {
	int eShape;            //对应enum枚举变量,表示线段,矩形,圆形等等
	COLORREF ShapeColor;   //图像的颜色
	CPoint beginPoint;     //起点坐标
	CPoint endPoint;       //终点坐标
	...                    
};

//保存所有图像信息的结构体向量
vector<struct ShapeInfo>vShapeInfo;


相应编写鼠标按下和弹起消息,存入起始坐标点,具体就不详细说了,如果编写无误,程序应该就能正常运行了。

但是,你会发现此时绘图程序并不完美,而且还有一些bug! Windows下mspaint有个功能:鼠标在绘制图形过程中,如果离开了窗口区域,依然可以绘制。而我们的程序鼠标移开窗口,绘图就会失效,且鼠标释放的标志也没能正确改为false,造成鼠标没有处于按下状态依然在绘制图形。

其实要解决这个问题很简单,只需要在进入OnLButtonDown开始的时候设置SetCapture(); 在OnLbuttonUp函数结束的时候设置ReleaseCapture(); 此时如果你编写无误,程序应该也能正常运行了。此时的程序比起刚才的要完美很多了。


但是,如果你细心,会发现还有一些小bug:

  1. 鼠标点击后移动过快,且在释放的时候偏移一下,然后在下一次的绘制途中,上一次的图形终点坐标会有明显改变,如果慢慢的绘图就不会发生。这是因为你的终点坐标是OnLButtonUp得到的,此时如果鼠标释放后迅速移动,该函数可能响应过慢,导致你存储到vector的终点坐标 和 MouseMove鼠标最后移动的坐标有偏差,就会出现这个问题。

  2. 鼠标原地点击释放,然后移动到零一点再原地点击释放,之后缩小程序再还原进行重绘,就会出现连线,可能是你点击两点的连线,也可能是两个点与(0,0)坐标点的连线。与(0,0)坐标点的连线是因为,当你在OnLButtonDown中存储beginPoint时,设置了终点坐标endPoint为CPoint(0),当然你可能设置的为-1或别的什么待定义坐标点以供在OnLButtonUp中进行修改,所以出现了这个连线。此时很容易猜到,原地点击释放鼠标,很可能程序只响应其中一个消息,可能为点击,也可能为释放,响应了释放就会导致没有存储起点坐标,响应了起点就会导致终点坐标为我们设置的待定坐标。这个问题只需要在OnLButtonUp中加以判断就能解决掉。两个点的连线和(0,0)坐标连线一样,所以可以一并解决。



下面直接看消息代码,解决上述所有问题,此处只需修改OnLButtonUp消息,OnLButtonDown消息则不用修改

void CPaint::OnLButtonDown(UINT nFlags, CPoint point)
{
	//开启按下开关
	isDown = true;
	
	//开启鼠标捕获
	SetCapture();
	
	//记录按下的坐标和当前的颜色值
	switch (eButtonType)
	{
	case isLine:
		struct ShapeInfo LineInfo { theLine, setColor, point, 0 };
		vShapeInfo.push_back(LineInfo);
	    break;
	
	    ...
	    ...
	}

	CWnd::OnLButtonDown(nFlags, point);
}

void CPaint::OnLButtonUp(UINT nFlags, CPoint point)
{
	isDown = false;//关闭按下开关

	if (!vShapeInfo.empty())
	{
		//设置vector迭代器为指向最后一个元素
		vector<struct ShapeInfo>::iterator iter = vShapeInfo.end() - 1;
		
		//判断是否和按下的坐标相同,否则执行存储,是则删除最后一个元素不予存储
		if(iter->beginPoint != point) 
			switch (eButtonType)
			{
			case isLine:
				iter->endPoint = ButtonUpMovePoint;//应该为鼠标移动时候的坐标点 ,如果为释放的坐标点point则会导致绘图有偏差
				break;
			case isRect:
				iter->endPoint = ButtonUpMovePoint;
				break;
			
			        ...
			        ...
			}
		else
			vShapeInfo.pop_back();
	}
	
	CWnd::OnLButtonUp(nFlags, point);
}


到此,程序应该能够正常运行了!