MFC之学习交互式绘图技术、三角形交互式实时绘制

1.笔记

1.1回显技术

回显是对图形的操作,用某种方式表达出来的技术。例如,在窗口客户区使用鼠标移动顶点时,希望能实时显示顶点坐标,这就是一种回显技术。

1.2引力域技术

绘图过程中,常常需要使用鼠标光标选择某一顶点。要准确定位光标很难,这时可以采用引力域技术。

引力域是指以某一点为中心所建立的一个矩形区域,当光标处于矩形区域之内时,就被“引力”吸引到该点上来。需要注意的是引力域大小要选择适当,太小了没有引力,太大了容易出现错误连接。

1.3橡皮筋技术

橡皮筋技术是鼠标绘图的过程连续、动态地表现出来,直到产生用户满意的结果为止的技术。例如,使用鼠标移动多边形的一个顶点,随着鼠标的移动,多边形的形状在不断变化,就像由橡皮筋构成。橡皮筋技术一般需要双缓冲机制

双缓冲机制是通过增加一个内存缓冲区,在缓冲区将图画好再一次性拷贝到显示设备上,以解决频繁擦除屏幕所导致的屏幕闪烁问题。

2.使用练习

2.1使鼠标能够移动三角形的顶点,回显顶点坐标,用橡皮筋技术动态显示移动过程。

首先在工程的视图类中定义三角形顶点及相关变量。添加消息处理函数的方法之前已经学习过,故略。

// 在Example_1View.h文件中编辑

class CExample1View : public CView{
...
protected:
	CPoint p[3];//三角形三个顶点
	BOOL bLBDown;//左键按下状态
	int nCount;//顶点计数器
public:
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);//处理左键按下消息
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);//处理左键弹起消息
	void DoubleBuffer(CDC* pDC);//双缓冲函数
	void DrawTriangle(CDC* pDC);//绘制三角形
	afx_msg void onm ouseMove(UINT nFlags, CPoint point);//处理光标移动消息
};

然后给工程的视图类进行相应的初始化。

//在Example_1View.cpp文件中编辑

CExample1View::CExample1View() noexcept
{
	// TODO: 在此处添加构造代码
	p[0] = CPoint(100, 100);//三角形三个顶点初始化
	p[1] = CPoint(300, 200);
	p[2] = CPoint(50, 300);
	bLBDown = false;//左键按下状态初始化
	nCount = 0;//顶点计数器初始化
}

重写界面绘图函数。

//在Example_1View.cpp文件中编辑

void CExample1View::OnDraw(CDC* pDC)
{
	CExample1Doc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: 在此处为本机数据添加绘制代码
	DoubleBuffer(pDC);
}

实现双缓冲函数。

//在Example_1View.cpp文件中编辑

void CExample1View::DoubleBuffer(CDC* pDC) {
	CRect rect;//获取客户区
	GetClientRect(&rect);

	CDC memDC;//缓冲区设备上下文要与显示设备上下文兼容
	memDC.CreateCompatibleDC(pDC);

	CBitmap newBitmap, * pOldBitmap;//准备好缓冲区
        //创建和显示设备上下文客户区一致的位图,用于绘图
	newBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
	pOldBitmap = memDC.SelectObject(&newBitmap);//将位图选入缓冲区
	memDC.FillSolidRect(rect, pDC->GetBkColor());//填充缓冲区背景

	DrawTriangle(&memDC);//画三角形,缓冲区绘制完后将图像拷贝回显示设备上下文
	pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(),
		&memDC, 0, 0, SRCCOPY);

	memDC.SelectObject(pOldBitmap);//恢复缓冲区设备上下文
	newBitmap.DeleteObject();//释放用毕资源
	memDC.DeleteDC();
}

实现绘制三角形函数。

//在Example_1View.cpp文件中编辑

void CExample1View::DrawTriangle(CDC* pDC) {
	pDC->SelectStockObject(GRAY_BRUSH);//选灰色画刷

	for (int i = 0; i < 3; ++i) {//3个顶点依次处理
		CString str;
		str.Format(CString("x=%d,y=%d"), p[i].x, p[i].y);
		pDC->SetTextColor(RGB(255, 0, 0));
		pDC->TextOutW(p[i].x, p[i].y, str);//输出顶点实时坐标
                
                //绘制顶点的引力域
		pDC->Rectangle(p[i].x - 5, p[i].y - 5, p[i].x + 5, p[i].y + 5);
                
                //连线,画三角形
		if (i == 0)pDC->MoveTo(p[i]);
		else pDC->LineTo(p[i]);
	}

	pDC->LineTo(p[0]);//连最后一条边
}

用鼠标拖动顶点的过程中,三角形的形状在不断变化,即要不断更新三角形的顶点信息,擦除画板重新绘制三角形。实现鼠标移动消息的处理函数。注意此处的GetDC和ReleaseDC配对出现是行规,否则容易出现内存泄漏问题。

//在Example_1View.cpp文件中编辑

void CExample1View::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	CDC* pDC = GetDC();//申请画图资源

	for(int i=0;i<3;++i)//寻找光标所在的顶点
		if (point.x<p[i].x + 5 && point.x > p[i].x - 5 &&
			point.y < p[i].y + 5 && point.y > p[i].y - 5)
		{
			//将光标形状设置为手形
                        SetCursor(LoadCursor(NULL, IDC_HAND));
			nCount = i;//标记光标所在的顶点
		}
	
	//如果鼠标左键按下,将光标当前位置的坐标赋值给顶点
	if (bLBDown)p[nCount] = point;

	ReleaseDC(pDC);//释放资源
	Invalidate(FALSE);//不擦除背景直接画,因为缓冲区是准备好背景才开始画三角形
        //Invalidate(TRUE)是擦除背景再画

	CView::OnMouseMove(nFlags, point);
}

 鼠标坐标按下、抬起的消息处理则非常简单。

//在Example_1View.cpp文件中编辑

void CExample1View::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	bLBDown = true;//鼠标左键按下,记为真

	CView::OnLButtonDown(nFlags, point);
}


void CExample1View::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	bLBDown = false;//鼠标左键抬起,记为假

	CView::OnLButtonUp(nFlags, point);
}

该案例所有需要自行编写代码如上所示,鼠标移动消息处理函数中申请绘图设备做何用,又为什么释放设备才去触发界面重绘事件,暂时还未明白,先记录之。

实现效果:

<iframe allowfullscreen="true" data-mediaembed="bilibili" id="pr7XFDvp-1627982308679" src="https://player.bilibili.com/player.html?aid=717060408"></iframe>

交互式实时绘制三角形

上一篇:MFC之学习绘制椭圆、库画刷使用


下一篇:328-TCP的拥塞机制