孙鑫C++视频笔记(11)图形的保存和重绘

编写画图代码,设定一个标识,在OnLButtonDown中保存鼠标按下去的点,在OnLButtonUp中捕获鼠标弹起的点,利用switch语句分别画图。

当拖动窗口时,先前所画图像会消失,这是因为拖动窗口会引起窗口重绘。解决办法是用数组类CPtrArray保存三个变量:m_nDrawType,m_ptOrigin,m_ptEnd。

新建一个普通类名为CGraph,构造一个带参数的构造函数 
CGraph(UINT m_nDrawType,CPoint m_pOrigin,CPoint m_pEnd);用来接收必须的三个变量

在CGraph中增加三个成员变量UINT m_nDrawType,CPoint m_pOrigin,CPoint m_pEnd,并在不带参数构造函数中初始化。再带参数构造函数中接收变量,代码如下:
CGraph::CGraph(UINT m_nDrawType,CPoint m_pOrigin,CPoint m_pEnd)
{
 this->m_nDrawType=m_nDrawType;
 this->m_pOrigin=m_pOrigin;
 this->m_pEnd=m_pEnd;
}

在OnLButtonUp中把变量存入数组中,注意:
不可以用这样保存:
CGraph graph(m_nDrawType,m_ptOrigin,point)
m_ptrArray.Add(&graph);
这是因为数组中保存的是对象地址,当发生析构时对象地址就无效了,虽然保存了地址,但当我们索引对象时无效。
解决办法:
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
对象仍会析构,但只是去掉了对象与地址的联系,而new所建立的对象是在堆上的,在堆中分配的对象如果不用delete释放,对象的生命周期和应用程序是一样的。执行析构函数时,变量的内存被释放,但变量的值仍保存在堆中。

窗口重绘会调用OnDraw函数,所以要在此函数中增加画图代码

利用m_ptrArray.GetSize()从得到数组长度,构造for循环

用((CGraph*)m_ptrArray.GetAt(i))->得到CGraph类的各变量,其中m_ptrArray.GetAt(i)返回CObject*,所以要强制转换成CGraph*。

源代码如下:

void CGraphyicView::OnDraw(CDC* pDC)
{
 CGraphyicDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);
 // TODO: add draw code for native data here
 CClientDC dc(this);
 CPen pen(PS_SOLID,1,RGB(255,0,255));
 pDC->SelectObject(&pen);
 
 CBrush *brush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
 pDC->SelectObject(brush);

 for(int i=0;i<m_ptrArray.GetSize();i++)
 {
  switch(((CGraph*)m_ptrArray.GetAt(i))->m_nDrawType)
  {
  case 0:
   pDC->SetPixel(((CGraph*)m_ptrArray.GetAt(i))->m_pEnd,RGB(255,255,0));
   break;
  case 1:
   pDC->MoveTo(((CGraph*)m_ptrArray.GetAt(i))->m_pOrigin);
   pDC->LineTo(((CGraph*)m_ptrArray.GetAt(i))->m_pEnd);
   break;
  case 2:
   pDC->Rectangle(CRect(((CGraph*)m_ptrArray.GetAt(i))->m_pOrigin,
    ((CGraph*)m_ptrArray.GetAt(i))->m_pEnd));
   break;
  case 3:
   pDC->Ellipse(CRect(((CGraph*)m_ptrArray.GetAt(i))->m_pOrigin,
    ((CGraph*)m_ptrArray.GetAt(i))->m_pEnd));
   break;
  }
 }
}


OnDraw函数不是WM_PAINT的相应函数,为什么能在窗口重绘过程中被调用?

void CView::OnPaint()
{
 // standard paint routine
 CPaintDC dc(this);
 OnPrepareDC(&dc);
 OnDraw(&dc);
}
在响应WM_PAINT时候,如果要得到dc的句柄,只能利用BeginPaint(),如果要释放dc句柄的话调用EndPaint()
The CPaintDC class is a device-context class derived from CDC. It performs a CWnd::BeginPaint at construction time and CWnd::EndPaint at destruction time.
CPaintDC只能在OnPaint中使用。

CClientDC objects encapsulate working with a device context that represents only the client area of a window. The CClientDC constructor calls the GetDC function, and the destructor calls the ReleaseDC function. CWindowDC objects encapsulate a device context that represents the whole window, including its frame.
如果要在OnPaint以外地方获得dc句柄的话,调用GetDC ,释放用ReleaseDC

如果想要在文件中替换所有想要替换的一个字或词,可选Edit->Replace,再选择
Match whole word only

MicroSoft Windows 下的程序运用坐标空间和转换来对图形输出进行缩放,平移,旋转,斜切和反射。
一个坐标空间是一个二维空间,通过使用连个相互垂直并且长度相等的轴来定义二维对象

坐标空间
API使用四种坐标空间:世界坐标系空间,页面空间,设备空间,和物理设备空间。应用程序运用世界坐标系空间对图形输出进行旋转、斜切或者反射。

Win32 API把世界坐标系空间和页面空间称为逻辑空间;最后一种坐标空间(即物理设备空间)通常指应用程序窗口的客户区;但是他也包括整个桌面、完整的窗口(包括框架、标题栏和菜单栏)或打印机的一页或绘图仪的一页纸。物理设备的尺寸随显示器、打印机、绘图仪的所设置的尺寸而改变。

转换
如果要在一个物理设备上绘制输出,Windows把一个矩形区域从一个坐标空间拷贝到(或映射到)另一个坐标空间,直至最终完整的输出呈现在物理设备上(通常是屏幕或打印机)

如果该应用程序调用了SetWorldTransForm函数,那么映射就从应用程序的世界坐标系空间开始;否则,映射在页面空间中进行。在Windows把矩形区域的每一点从一个空间拷贝到另一个空间时,他采用一种被称作转换的算法,转换是把对象从一个坐标空间拷贝到另一个坐标空间时改变(或转变)这一对象的大小,方位,和形态,尽管转换把对象看成一个整体,但他也作用与对象的每一个点或每一条线。

页面空间到设备空间的转换
页面空见到设备空间的转换是原Windows程序接口的一部分。这种转换确定与一特定设备描述表相关的所有图形输出的映射方式。
所谓映射方式是指确定用于绘图操作的单位大小的一种量度转换,映射方式是一种影响几乎任何客户区绘图的设备环境属性。另外还有四种设备环境属性:窗口原点,视口原点,窗口范围和视口范围,这四种属性与映射关系密切相关。
页面空间到设备空间的转换所用的是两个矩形的宽与高的比率,其中页面空间中的矩形被称为窗口,设备空间中的矩形被称为视口。Windows把窗口原点映射到视口原点,把窗口范围映射到视口范围,就完成了这种转换。

设备空间到物理空间的转换
设备空间到物理空间的转换:只限于平移,并由Windows窗口管理部分控制,这种转换的唯一用途是确保设备空间的原点被映射到物理设备上的适当点上。没有函数能设置这种转换,也没有函数能获取有关数据

默认转换
一旦应用程序建立了设备描述表,并立即开始调用GDI绘图或输出函数,则运用默认页面空间到设备空间的转换和设备空间到客户区的转换,(在应用程序调用SetWorldTransform之前不会世界坐标空间到页面空间的转换。

默认页面空间到设备空间的转换是一对一的映射;即页面空间上给出的一点映射到设备空间上的一个点。这种转换没有以矩阵指定,而是通过把视口宽除以窗口宽,把视口高除以窗口高而得出的,在默认情况下,视口尺寸为1*1像素,窗口尺寸为1*1页单位。

设备空间到物理设备(客户区,桌面和打印机),得转换结果总是一对一的;既设备空间上的一个单位总是与客户区,桌面,和打印机上的一个单位对应。这一转换的唯一用途是平移,无论窗口移到桌面的什么位置,它永远取保输出能够正确无误地出现在窗口上。

默认装换的一个独特之处是设备空间与应用程序窗口的y轴方向。在默认的状态下,y轴正向朝下,-y方向朝上。

逻辑坐标和设备坐标
几乎在所有GDI函数中使用的坐标值都是逻辑单位,Windows必须将逻辑坐标值转换为“设备单位”,即像素。这种转换是由映射方式,窗口和视口的原点以及窗口和视口的范围决定的。
Windows对所有消息(如WM_SIZE,WM_MOUSEMOVE,WM_LBUTTONDOWN,WM_LBUTTONUP),所有的非GDI函数和一些GDI函数(GetDeviceCaps函数),永远使用设备坐标。

窗口是基于逻辑坐标的,逻辑坐标可以是像素,毫米,英寸等单位,使口是基于设备坐标(像素)的。通常,视口和客户区是相同的。
缺省的映射模式是MM_TEXT,在这种映射模式下,逻辑单位和设备单位相同。

窗口(逻辑)坐标和视口(设备)坐标的转换
xViewPort=(xWindow-xWinOrg)*xViewExt/xWinExt+xViewOrg;
yViewPort=(yWindow-yWinOrg)*yViewExt/yWinExt+yViewOrg;
视口(设备)坐标和窗口(逻辑)坐标的转换与上面相反;

在MM_TEXT映射方式下窗口(逻辑)坐标和视口(设备)坐标的转换:
xViewPort=xWindow-xWinOrg+xViewOrg;
yViewPort=yWindow-yWinOrg+yViewOrg;
视口(设备)坐标和窗口(逻辑)坐标的转换与上面相反;

OnInitialUpdate()是在窗口创建完成之后第一个调用的函数,也在OnDraw之前。
要使窗口具有滚动功能,较简单的方法是把视类中涉及视类的基类全部改成CScrollView
并添加虚函数OnInitialUpdate(),并设置滚动窗口
void CGraphyicView::OnInitialUpdate()
{
 CScrollView::OnInitialUpdate();
 
 // TODO: Add your specialized code here and/or call the base class
 SetScrollSizes(MM_TEXT,CSize(800,600));
}
void SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault );
Call SetScrollSizes when the view is about to be updated. Call it in your override of the OnUpdate member function to adjust scrolling characteristics when, for example, the document is initially displayed or when it changes size.

CDC中提供两个成员函数SetViewpoitOrg和SetWindowOrg,用来改变视口和窗口的原点。
如果将视口原点设为(xViewOrg,yViewOrg),则逻辑点(0,0)就会被映射为设备点(xViewOrg,yViewOrg),如果将窗口原点改变为(xWinOrg,yWinOrg),则逻辑点(xWinOrg,yWinOrg)就会被映射为设备点(0,0),即左上角。
不管对窗口和视口原点作什么改变,设备点(0,0)始终是客户区的左上角。

在调用OnDraw函数前,在OnPaint函数中调用了函数OnPrepareDC函数,调整了显示上下文的属性将视口的原点设置为(0,-150),根据公式yViewPort=yWindow-yWinOrg+yViewOrg;得到设备点y坐标出现在原先显示地方的上方。
解决办法:
绘制图形之后保存坐标点之前调用OnPrepareDC调整显示上下文的属性,将视口原点设置为(0,-150),然后调用设备坐标转换逻辑坐标函数DPtoLP将设备坐标(680,390)转换为逻辑坐标,根据公式
yWindow=yViewport-yViewOrg+yWinOrg;
得到y坐标为540。窗口重绘时会先调用OnPrepareDC,调整显示上下文的属性,将视口的原点设置为(0,-150)然后GDI函数用逻辑坐标点(680,540)绘制图形,被转换为(680,390)
 OnPrepareDC(&dc);
 dc.DPtoLP(&m_ptOrigin); //视口(设备)坐标和窗口(逻辑)
 dc.DPtoLP(&point);

当滚动条在最上端,窗口发生重绘时OnPrepareDC调整显示上下文,会将视口的原点设置为(0,0),而不是(0,-150)
OnPrepareDC会随时根据滚动窗口的位置来调整视口的原点

CMetaFileDC设备元文件,用来保存图形,CDC::PlayMetaFile用来重复播放元文件
CMetaFileDC  m_dcMetaFile;
在构造函数中初始化为 m_dcMetaFile.Create();
Points to a null-terminated character string. Specifies the filename of the metafile to create. If lpszFilename is NULL, a new in-memory metafile is created.
画图时我们调用m_dcMetaFile的绘图函数:
  m_dcMetaFile.Rectangle(CRect(m_ptOrigin,point));
把绘图所用命令保存进m_dcMetaFile。
窗口重绘时
 HMETAFILE hMetaFile;
 hMetaFile=m_dcMetaFile.Close();  //保存绘图命令
 pDC->PlayMetaFile(hMetaFile);  //用窗口DC播放元文件
 m_dcMetaFile.Create();    //新建元文件
 m_dcMetaFile.PlayMetaFile(hMetaFile);//把先前的图形重新绘制加到新图形中
 
 DeleteMetaFile(hMetaFile);   //释放元文件

为“打开”,“保存”添加命令相应函数。
掉用 CopyMetaFile(hMetaFile,"sp.hmf");
和 hMetaFile=GetMetaFile("sp.hmf");
 m_dcMetaFile.PlayMetaFile(hMetaFile);
来保存和打开图形。
HMETAFILE CopyMetaFile(
  HMETAFILE hmfSrc,  // handle to a Windows-format metafile
  LPCTSTR lpszFile   // pointer to a filename string
);
HENHMETAFILE GetEnhMetaFile(
  LPCTSTR lpszMetaFile   // pointer to metafile name
);
CopyMetaFile,GetMetaFile函数已经被废弃,现在使用增强的函数CopyEnhMetaFile,GetEnhMetaFile。用法相同,老函数仍能使用。

利用兼容DC保存图形
构造兼容DC对象:
CDC  m_dcCompatible
源码:
 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(brush);
 }
CreateCompatibleBitmap返回的位图对象只包含相应设备描述表中的位图信息头,不包含颜色表和像素数据块。因此,选入该位图对象的设备描述表不能像选入普通对象的设备描述表一样使用,必须在SelectObject函数之后,调用BitBlt将原始设备描述表的颜色表以及像素数据块拷贝到兼容设备描述表。

如果我们想在保存图象的同时显示图像,可以在调用
  m_dcCompatible.MoveTo(m_ptOrigin);
  m_dcCompatible.LineTo(point);
的同时调用
 dc.MoveTo(m_ptOrigin);
 dc.LineTo(point);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值