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>交互式实时绘制三角形