先增加一个新的菜单项 绘图 ,然后在里面增加4个子菜单项 点 线 矩形 椭圆 ,在View类中响应各个子菜单项,为View类增加一个私有数据成员 int m_nDrawType 用来保存用户所做的选择 这个和上一篇日志的一样 所以代码不贴了,然后就是响应 OnLButtonDown 和 ONLButtonUp 消息 进行相应的绘图。我们知道当窗口大小改变或是窗口被切换的时候,程序就会发送一个WM_PAINT消息,窗口就会发生重绘。这个时候我们画的图像就会消失不见,这一次就是要就保存和重绘图像进行一些相关操作。
我们知道窗口重绘时,会调用OnDraw函数,因此我们可以在这个函数中完成图像的输出。
保存图形的方法有很多,在本例中绘制的图像,有三个要素:绘画类型、起点、终点,也就是说对于本例中的图形,只要保存这三个属性就可以了。窗口重绘的时候在OnDraw函数中根据这三个要素就可以进行图像的绘制了。由于这三个参数的类型不同,可以用结构体来保存,在C++中结构体就是一个类,因此,本例可以新建一个新类来保存呢这三个要素。通过单击[Insert/New Class] 就可以进行新类创建了 ,弹出的菜单中 类类型要选择 Generic Class,新类命名为CGraph ,然后为该类增加三个成员变量,都设为public的,后面要用到。分别是:int m_nDrawType; CPoint m_ptOrign; CPoint m_ptEnd;分别保存 绘画类型 起点 终点 三个要素。然后再为该类增加一个有这三个参数的构造函数,让允许用户在构造该类对象时,可以直接通过这三个参数给成员变量赋值:
CGraph::CGraph(int m_nDrawType,CPoint m_ptOrign,CPoint m_ptEnd)
{
this->m_nDrawType=m_nDrawType;
this->m_ptOrign=m_ptOrign;
this->m_ptEnd=m_ptEnd;
}
在绘制图像过程中,我们会画很多个图形,每一个图形都对应一个CGraph的对象,以保存该图像的三个要素。我们可以采用数组来保存这些创建的对象,但是长度未知,只可以存储一定容量的元素,这样不好。用链表的话又比较复杂。本例中,使用MFC的一个集合类:CPtrArray,它支持void 类型的指针数组,该类成员函数与CobArray类相应的函数类似,只是CObArray类的成员函数中使用CObject指针作为参数或者返回值类型的地方,在CPtrArray类中使用void 类型指针替换即可。
在本程序中我们只需要用到它的几个函数,如果要增加一个成员,就用add函数,来增加一个void类型的指针的对象,如果想取得该集合的某个元素就用GetAt方法,如果想获得集合的数目就用GetSize函数。 下面,先为View类增加一个CPtrArray类型的变量m_ptrArray.然后就在每次绘图之后,构造一个CGraph对象,并将该对象的地址保存到m_ptrArray中.
void CMyView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
CPen pen(PS_SOLID,2,RGB(255,0,0));
dc.SelectObject(&pen);
//m_dcMetaFile.SelectObject(&pen);
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
/*
if(!m_dcCompatible.m_hDC)
{
m_dcCompatible.CreateCompatibleDC(&dc);
CRect rect;
GetClientRect(&rect);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height());
m_dcCompatible.SelectObject(pBrush);
m_dcCompatible.SelectObject(&pen);
m_dcCompatible.SelectObject(&bitmap);
m_dcCompatible.BitBlt(0,0,rect.Width(),rect.Height(),&dc,0,0,SRCCOPY);
}
*/
// dc.SelectObject(pBrush);
// m_dcMetaFile.SelectObject(pBrush);
CRect rect(m_pOrign,point);
switch(m_nDrawType)
{
case 1:
dc.SetPixel(point,RGB(0,0,0));
//m_dcMetaFile.SetPixel(point,RGB(255,0,0));
//m_dcCompatible.SetPixel(point,RGB(255,0,0));
break;
case 2:
dc.MoveTo(m_pOrign);
dc.LineTo(point);
//m_dcMetaFile.MoveTo(m_pOrign);
//m_dcMetaFile.LineTo(point);
//m_dcCompatible.MoveTo(m_pOrign);
//m_dcCompatible.LineTo(point);
break;
case 3:
dc.Ellipse(&rect);
//m_dcMetaFile.Ellipse(&rect);
//m_dcCompatible.Ellipse(&rect);
break;
case 4:
dc.Rectangle(&rect);
//m_dcMetaFile.Rectangle(&rect);
//m_dcCompatible.Rectangle(&rect);
break;
}
//OnPrepareDC(&dc);
//dc.DPtoLP(&m_pOrign);
//dc.DPtoLP(&point);
CGraph *graph=new CGraph(m_nDrawType,m_pOrign,point);
m_ptrArray.Add(graph);
CScrollView::OnLButtonUp(nFlags, point);
}
其中注释的代码先不理会 后面会说到,在构建CGraph对象的时候要用指针,不可以直接构建,不然会由于是临时变量的关系,导致m_ptrArray中保存的CGraph对象地址中的东西为空。 接下来呢就要在OnDraw函数中将它们重绘的显示出来:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
pDC->SelectObject(pBrush);
for(int i=0;i<m_ptrArray.GetSize();i++)
{
switch(((CGraph*)m_ptrArray.GetAt(i))->m_nDrawType)
{
case 1:
pDC->SetPixel(((CGraph*)m_ptrArray.GetAt(i))->m_ptEnd,RGB(0,0,0));
break;
case 2:
pDC->MoveTo(((CGraph*)m_ptrArray.GetAt(i))->m_ptOrign);
pDC->LineTo(((CGraph*)m_ptrArray.GetAt(i))->m_ptEnd);
break;
case 3:
pDC->Ellipse(CRect(((CGraph*)m_ptrArray.GetAt(i))->m_ptOrign,((CGraph*)m_ptrArray.GetAt(i))->m_ptEnd));
break;
case 4:
pDC->Rectangle(CRect(((CGraph*)m_ptrArray.GetAt(i))->m_ptOrign,((CGraph*)m_ptrArray.GetAt(i))->m_ptEnd));
break;
}
}
}
因为CPtrArray类的GetAt函数返回的是一个void类型的指针,所以要进行强制的类型转换,讲其转换为CGraph类型的指针,猜可以正确访问CGraph的成员变量(因为在这里要访问,所以才在前面定义CGraph类的时候将这几个变量设为public的)。
virtual void OnDraw( CDC* pDC ) = 0;
这个是CView类的OnDraw函数的定义 是一个纯虚函数。另外在窗口重绘的时候发送的是WM_PAINT消息,如果想让图形始终可以显示出来,就可以讲图形的绘制操作放置在该消息的响应函数OnPaint中,而OnDraw函数并不是WM_PAINT消息的响应函数,但是它为什么可以在窗口发生重绘的时候被调用呢