转自:http://blog.163.com/wufanjx@126/blog/static/1706558832011484416631/
图形的保存和重绘
********************
********************
创建一个保存绘图类型m_nDrawType,鼠标按下点坐标m_ptOrigin,鼠标松开点坐标m_ptEnd的类CGraph。
Graph.h
class CGraph
{
public:
CPoint m_ptOrigin;
CPoint m_ptEnd;
UINT m_nDrawType;
CGraph();
CGraph(UINT m_nDrawType,CPoint m_ptOrigin,CPoint m_ptEnd);
virtual ~CGraph();
};
Grap.cpp
CGraph::CGraph(UINT m_nDrawType,CPoint m_ptOrigin,CPoint m_ptEnd)
{
this->m_nDrawType=m_nDrawType;
this->m_ptOrigin=m_ptOrigin;
this->m_ptEnd=m_ptEnd;
}
GraphicView.cpp
void CGraphicView::OnLButtonDown(UINT nFlags, CPoint point)
{
m_ptOrigin=point;
CScrollView::OnLButtonDown(nFlags, point);
}
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
dc.SelectObject(pBrush);
switch(m_nDrawType)
{
case 1:
dc.SetPixel(point,RGB(0,0,0));
break;
case 2:
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
break;
case 3:
dc.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
dc.Ellipse(CRect(m_ptOrigin,point));
break;
}
//CGraph graph(m_nDrawType,m_ptOrigin,point);
//m_ptrArray.Add(&graph);
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
m_ptrArray.Add(pGraph);
CScrollView::OnLButtonUp(nFlags, point);
}
void CGraphicView::OnDraw(CDC* pDC)
{
CGraphicDoc* 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_ptOrigin);
pDC->LineTo(((CGraph*)m_ptrArray.GetAt(i))->m_ptEnd);
break;
case 3:
pDC->Rectangle(CRect(((CGraph*)m_ptrArray.GetAt(i))->m_ptOrigin,
((CGraph*)m_ptrArray.GetAt(i))->m_ptEnd));
break;
case 4:
pDC->Ellipse(CRect(((CGraph*)m_ptrArray.GetAt(i))->m_ptOrigin,
((CGraph*)m_ptrArray.GetAt(i))->m_ptEnd));
break;
}
}
********************
********************
在MFC的VIEWCORE.cpp文件中:
void CView::OnPaint()
{
// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}
OnDraw是虚函数,根据多态原理,调用时会程序会寻找子类中是否有OnDraw函数,如果子类中没有,再执行父类的OnDraw函数。如果在子类中添加WM_PAINT响应函数OnPaint将不会再执行OnDraw函数。
窗口滚动:
映射方式:坐标空间
逻辑坐标点会设备坐标点的转换。
void CGraphicView::OnPaint()
{
CPaintDC dc(this); // device context for painting
OnPrepareDC(&dc);
OnDraw(&dc);
}
void CGraphicView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
SetScrollSizes(MM_TEXT,CSize(800,600));
}
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
CClientDC dc(this);
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
dc.SelectObject(pBrush);
switch(m_nDrawType)
{
case 1:
dc.SetPixel(point,RGB(0,0,0));
break;
case 2:
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
break;
case 3:
dc.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
dc.Ellipse(CRect(m_ptOrigin,point));
break;
}
OnPrepareDC(&dc);
dc.DPtoLP(&m_ptOrigin);//
dc.DPtoLP(&point);
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
m_ptrArray.Add(pGraph);
CScrollView::OnLButtonUp(nFlags, point);
}
补充1:参考OnPrepareDC函数,在VIEWSCRL.cpp文件的OnPrepareDC函数中设置了视口原点pDC->SetViewportOrg(ptVpOrg);导致图形错位。解决方法是在保存坐标点之前调用OnPrepare,然后再进行设备坐标向逻辑坐标点转换。OnPrepareDC在窗口重绘时始终调整视口的原点,设备点的原点始终为左上角(0,0)。在MM_TEXT映射模式下,视口和窗口的单位相同。
补充2:逻辑坐标和设备坐标的相互转换
窗口(逻辑)坐标转换为视口(设备)坐标的两个公式:
xViewport=(xWindow-xWinOrg)*xViewExt/xWinExt +xViewOrg
yViewport=(yWindow-yWinOrg)*yViewExt/yWinExt +yViewOrg
视口(设备)坐标转换为窗口(逻辑)坐标的两个公式:
xWindow=(xViewPort-xViewOrg)* xWinExt / xViewExt+xWinOrg
yWindow=(yViewPort-yViewOrg)* xWinExt / xViewExt +yWinOrg
在MM_TEXT映射方式下逻辑坐标和设备坐标的相互转换:
窗口(逻辑)坐标转换为视口(设备)坐标的两个公式:
xViewport = xWindow-xWinOrg+xViewOrg
yViewport = yWindow-yWinOrg+yViewOrg
视口(设备)坐标转换为窗口(逻辑)坐标的两个公式:
xWindow = xViewport-xViewOrg+xWinOrg
yWindow = yViewport-yViewOrg+yWinOrg
补充3:
关于图形错位的说明:当我们在窗口中点击鼠标左键的时候,得到的是设备坐标(680,390),在MM_TEXT的映射模式下,逻辑坐标和设备坐标是相等的,所以我们利用集合类保存的这个点的坐标是以象素为单位,坐标值为(680,390)。在调用OnDraw函数前,在OnPaint函数中调用了OnPrepareDC函数,调整了显示上下文的属性,将视口的原点设置为了(0,-150),这样的话,窗口的原点,也就是逻辑坐标(0,0)将被映射为设备坐标(0,-150),在画线的时候,因为GDI的函数使用的是逻辑坐标,而图形在显示的时候,Windows需要将逻辑坐标转化为设备坐标,因此,原先保存的坐标点(680,390)(在GDI函数中,作为逻辑坐标使用),根据转换公式
xViewport=xWindow-xWinOrg+xViewOrg
yViewport = yWindow-yWinOrg+yViewOrg,得到设备点的x坐标为680-0+0=680,设备点的y坐标为390-0+(-150)=240,于是我们看到图形在原先显示地方的上方出现了。
解决方法:首先我们在绘制图形之后,在保存坐标点之前,调用OnPrepareDC函数,调整显示上下文的属性,将视口的原点设置为(0,-150),这样的话,窗口的原点,也就是逻辑坐标(0,0)将被映射为设备坐标(0,-150),然后我们调用DPtoLP函数将设备坐标(680,390)转换为逻辑坐标,根据设备坐标转换为逻辑坐标的公式:
xWindow = xViewport-xViewOrg+xWinOrg,
yWindow = yViewport-yViewOrg+yWinOrg,得到逻辑点的x坐标为680-0+0=680,y坐标为390-(-150)+0=540,将逻辑坐标(680,540)保存起来,在窗口重绘时,会先调用OnPrepareDC函数,调整显示上下文的属性,将视口的原点设置为了(0,-150),然后GDI函数用逻辑坐标点(680,540)绘制图形,被Windows转换为设备坐标点(680,390),和原先显示图形时的设备点是一样的,当然图形就还在原先的地方显示出来。
补充4:
设备坐标(Device Coordinate)又称为物理坐标(Physical Coordinate),是指输出设备上的坐标。通常将屏幕上的设备坐标称为屏幕坐标。设备坐标用对象距离窗口左上角的水平距离和垂直距离来指定对象的位置,是以像素为单位来表示的,设备坐标的X轴向右为正,Y轴向下为正,坐标原点位于窗口的左上角。
逻辑坐标(Logical Coordinate)是系统用作记录的坐标。在缺省的模式(MM_TEXT)下, 逻辑坐标的方向和单位与设备坐标的方向和单位相同,也是以像素为单位来表示的,X轴向右为正,Y轴向下为正,坐标原点位于窗口的左上角。逻辑坐标和设备坐标即使在缺省模式下其数值也未必一致,除了在以下两种情况下:1.窗口为非滚动窗口;2.窗口为滚动窗口,但垂直滚动条位于滚动边框的最上端,水平滚动条位于最左端,但如果移动了滚动条这两种坐标就不一致了。
在VC中鼠标坐标的坐标位置用设备坐标表示,但所有GDI绘图都用逻辑坐标表示,所以用鼠标绘图时,那么必须将设备坐标转换为逻辑坐标,可以使用CDC 函数DptoLP()将设备坐标转化为逻辑坐标,同样可以用LptoDP()将逻辑坐标转化为设备坐标。
补充5: OnPrepareDC会随时根据滚动窗口的位置来调整视口的原点。
2种保存图形和重绘图形的方式
①CMetaFileDC
在CGraphicView类中增加成员变量CMetaFileDC m_dcMetafile,并在构造函数中初始化:
M_dcMetafile.Create();
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
m_dcMetaFile.SelectObject(pBrush);
switch(m_nDrawType)
{
case 1:
m_dcMetaFile.SetPixel(point,RGB(0,0,0));
break;
case 2:
m_dcMetaFile.MoveTo(m_ptOrigin);
m_dcMetaFile.LineTo(point);
break;
case 3:
m_dcMetaFile.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
m_dcMetaFile.Ellipse(CRect(m_ptOrigin,point));
break;
}
CScrollView::OnLButtonUp(nFlags, point);
}
void CGraphicView::OnDraw(CDC* pDC)
{
CGraphicDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
HMETAFILE hmetaFile;
hmetaFile=m_dcMetaFile.Close();
pDC->PlayMetaFile(hmetaFile);
m_dcMetaFile.Create();
m_dcMetaFile.PlayMetaFile(hmetaFile);
DeleteMetaFile(hmetaFile);
}
void CGraphicView::OnFileSave()
{
// TODO: Add your command handler code here
HMETAFILE hmetaFile;
hmetaFile=m_dcMetaFile.Close();
CopyMetaFile(hmetaFile,"meta.wmf");
m_dcMetaFile.Create();
DeleteMetaFile(hmetaFile);
}
void CGraphicView::OnFileOpen()
{
// TODO: Add your command handler code here
HMETAFILE hmetaFile;
hmetaFile=GetMetaFile("meta.wmf");
m_dcMetaFile.PlayMetaFile(hmetaFile);
DeleteMetaFile(hmetaFile);
Invalidate();
}
②在CGraphicView类中增加成员变量CDC m_dcCompatible。
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
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(&bitmap);
m_dcCompatible.BitBlt(0,0,rect.Width(),rect.Height(),&dc,0,0,SRCCOPY);
m_dcCompatible.SelectObject(pBrush);
}
switch(m_nDrawType)
{
case 1:
//dc.SetPixel(point,RGB(0,0,0));
m_dcCompatible.SetPixel(point,RGB(0,0,0));
break;
case 2:
//dc.MoveTo(m_ptOrigin);
//dc.LineTo(point);
m_dcCompatible.MoveTo(m_ptOrigin);
m_dcCompatible.LineTo(point);
break;
case 3:
//dc.Rectangle(CRect(m_ptOrigin,point));
m_dcCompatible.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
//dc.Ellipse(CRect(m_ptOrigin,point));
m_dcCompatible.Ellipse(CRect(m_ptOrigin,point));
break;
}
CScrollView::OnLButtonUp(nFlags, point);
}
void CGraphicView::OnDraw(CDC* pDC)
{
CGraphicDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CRect rect;
GetClientRect(&rect);
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompatible,0,0,SRCCOPY);
}