Lesson11:
如何让CDC上输出的文字、图形具有保持功能,集合类CPtrArray的使用,CPaintDC与CClientDC的区别与应用,OnPaint与OnDraw在CView中的关系及实现内幕,滚动窗口的实现,坐标空间,映射方式,设备坐标与逻辑坐标的转换。元文件设备描述表的使用,
如何利用兼容DC实现图形的保存和再现。
图形的保存和重绘
方法一:
//在建立工程的时候最后一步可以选择view的基类
1.重复上一课的4种图形的画法(4个按钮)
2.增加一个Generic Class类型的类CGraph,在类中增加个成员变量
CPoint m_ptOrigin;CPoint m_ptEnd;UINT m_nDrawType;
在CGraph里添加一个构造函数
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;
}
集合类CPtrArr支持void指针数组,使用参照COBJECTARR
在鼠标弹起事件中加入
//CGraph graph(m_nDrawType,m_ptOrigin,point);
//m_ptrArray.Add(&graph);//上2行因为局部变量,所以错误
/* OnPrepareDC(&dc);//调整上下文属性
dc.DPtoLP(&m_ptOrigin);//设备店转换为逻辑点
dc.DPtoLP(&point);//设备店转换为逻辑点
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
m_ptrArray.Add(pGraph);*/
在onDraw事件中
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;
}
}*/
/* HMETAFILE hmetaFile;
hmetaFile=m_dcMetaFile.Close();
pDC->PlayMetaFile(hmetaFile);
m_dcMetaFile.Create();
m_dcMetaFile.PlayMetaFile(hmetaFile);
DeleteMetaFile(hmetaFile);*/
CRect rect;
GetClientRect(&rect);
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompatible,0,0,SRCCOPY);
}
要包含CGraph头文件
方法一的坐标需要转换
方法二:CMetaFileDC
1.在view类增加一个 CMetaFileDC m_dcMetaFile;
2.
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));
//dc.SelectObject(pBrush);
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);
}
3.
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);
}
3.把图形保存到文件当中 CopyMetaFile;GetMetaFile
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();
}
方法三:利用兼容DC,
1)在view中增加成员变量 CDC m_dcCompatible;
2)在下面函数中先判断
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));
//dc.SelectObject(pBrush);
m_dcMetaFile.SelectObject(pBrush);
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:
m_dcCompatible.SetPixel(point,RGB(0,0,0));
break;
case 2:
m_dcCompatible.MoveTo(m_ptOrigin);
m_dcCompatible.LineTo(point);
break;
case 3:
m_dcCompatible.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
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);
}
局部对象是在栈里分配内存的,栈里的内存当一个函数执行完是,会自动析构掉,
全局对象是在堆里分配内存的,它的生命周期和应用程序一样,只要是用new分配的内存都是在椎里存储的。
CPaintDc类,在构造的时候调用BeginPaint(),在析构的时候调用EndPaint()
CClientDc类,在构造的时候调用GetDC(),在析构的时候调用ReleaseDC()
CView::OnPrepareDC();在OnDraw()和OnPaint()前调用;
基类响应WM_PAINT消息的Onpaint()函数里调用了OnDraw()函数,所以平常看起来OnDraw()是专业响应窗口重绘的函数.
当我们自己添加OnPaint()函数时,就不会再调用OnDraw()函数,因为我们添加的函数里没用调用OnDraw(),当然你也可以添加调用.
CScrollllView::SetScrollSizes(int nMapMode,SIZE sizeTotal,const SIZE& sizePage=sizeDefault,const SIZE& sizeLine=sizeDefault);
设置滚动窗口的大小,和垂直水平滚动的尺寸.
逻辑坐标和设备坐标:
我们每次用GDI函数作图用的都是逻辑坐标,当在客户区显示时,必须要通过映射方式转换成设备坐标。
默认的映射模式是MM_TEXT.在这种映射模式下,逻辑单位和设备单位相同.
窗口(逻辑)坐标转换为视口(设备)坐标公式:
xViewport=(xWindow-xWinOrg)*(xViewExt/xWinExt)+xViewOrg;
yViewport=(yWindow-yWinOrg)*(yViewExt/yWinExt)+yViewOrg
视口(设备)坐标转换为窗口(逻辑)坐标公式:
xWindow=(xViewport-xViewOrg)*(xWinExt/xViewExt)+xWinOrg;
yWindow=(yViewport-yViewOrg)*(yWinExt/xViewExt)+yWinOrg;
转换因子=xViewExt/xWinExt
在MM_TEXT模式下映射方式下逻辑坐标和设备坐标的相互转换(转换因子为1)
窗口(逻辑)坐标转换为视品(设备)坐标的公式
xViewPort=xWindow-xWinOrg+xViewOrg;
yViewport=yWindow-yWinOrg+yViewOrg;
视品(设备)坐标转换为窗口(逻辑)坐标的公式
xWindow=xViewport-xViewOrg+xWInOrg;
yWindow=yViewport-yViewOrg=yWinOrg;
在保存图像前,先调用 OnPrepareDC(),设置视口原点,好让下次图像能正常显示。
OnPrepareDC()会随时根据滚动窗口的位置来调整视口原点。
MetaFile源文件dcMetaFile
dcMetaFile.Create()
在发送完GDI命令后,要在OnDraw()里的调用HMETAFILE hmetafile
hmetafile=dcMetaFile.Close()获得一个源文件句柄,然后使用CDC::PlayMetaFile()来播放图像;
pDC->PlayMetaFile();播放完后可能用户还需要作图所以还要创建
dcMetaFile.Create
dcMetaFile.PlayMetaFile()//加上这句我们以前绘制的图像还存在;
最后删除源文件句柄:DeleteMetaFile(hemetafile);
CopyMetaFile();拷贝源文件到指定的文件中,即保存
源文件保存的是图像的绘制命令
在源文件DC中绘制的图像,必须要窗口重绘时才能显示;
可以通过创建一个兼容位图来确定兼容DC显示表面的大小:
CDC m_dccompatible
m_dccompatibel.CreateCompatibleDC(&dc)
CRect rect;
rect=GetClientRect;
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height());
m_dccompatible.SelectObject(&bitmap);
兼容位图必须要在SelectObject()后调用:m_dccompatible.BitBlt()将像素数据和颜色表等拷贝到兼容DC当中,因为CreateCompatibleBitmap返回的位图对象只包含相应设备描述表中的位图的位图信息头,不包含颜色表和像素数据块。
兼容DC是内存DC,所以在窗口中我们是看不见的。所以我们要将兼容DC拷贝到当前DC中去。要想在绘制的时候就能看见,我们可以在调用兼容DC绘制图像的同时调用当前dc。