11.1坐标空间和转换
11.1.1坐标空间和转换
Win32应用程序编程接口(API)使用四种坐标空间:世界坐标系空间、页面空间、设备空间和物理设备空间。
前两种称为逻辑空间,最后一种通常指应用程序窗口的客户区。
11.1.2转换
如果应用程序调用了SetWorldTransform函数,那么映射就从世界坐标系空间开始;否则,映射在页面空间进行。
转换是把对象从一个坐标空间复制到另一个坐标空间时改变或者转变这一对象的大小、方位和形态。
在实际绘图时,世界坐标系空间中的一个区域要先被映射到页面空间,然后再由页面空间映射到设备空间。对于设备空间来说,通常它的左上角是坐标(0,0),向右是x增加的方向,向下是y增加的方向。然后再由设备空间映射到物理设备空间(通常是屏幕)。
如果程序没有调用SetWorldTransform函数,就不会发生世界坐标空间向页面空间转换。通常在实际编程中,主要处理从页面空间到设备空间的转换。
1、页面空间到设备空间的转换
这种转换首先得确定映射方式。设定映射方式主要就是确定如何将页面空间上的一个坐标点转换成设备空间中的一个设备坐标点。
四种设备环境属性与映射方式相关:窗口原点、视口原点、窗口范围和视口范围。窗口是基于逻辑坐标的,逻辑坐标可以是像素、毫米等单位;视口是基于设备坐标的,通常视口与客户区相同。
页面控件到设备空间的转换所用的是俩个矩阵的宽与高的比例。在页面空间中的矩形称为窗口,设备空间中的矩形称为视口。Windows把窗口原点映射到视口原点,把窗口范围映射到视口范围,就完成了这种转换。
2、设备空间到物理空间的转换
设备空间到物理空间的转换首先只限于平移;其次由Windows的窗口管理部分控制,程序员没有办法设置这种转换。
3、默认转换
一旦应用程序建立了设备描述表,并调用DGI绘图或输出函数,就运用默认空间到设备空间的转换和设备空间到客户区的转换。
默认页面空间到设备空间的转换结果是一对一的映射,即页面空间上一个单位对于设备空间上的一个单位。设备空间到物理空间(客户区)的转换结果也是一对一的。
11.1.3逻辑坐标和设备坐标
几乎在所有GDI函数中使用的坐标值都采用逻辑坐标。例如:
dc->TextOut(0,100,“Text”);
这个之中的(0,100)就采用了逻辑坐标。
Windows对所有的消息,所有的非GDI函数和一些GDI函数(例如GetDeviceCaps函数)永远使用设备坐标。
1、映射模式:默认的映射模式是MM_TEXT,在此映射模式下逻辑单位和设备单位相同。这是逻辑坐标(0,100)转换为设备坐标也是(0,100)。映射模式的改变可以通过SetMapMode函数来实现。
2、逻辑坐标和设备坐标的相互转换:
窗口(逻辑)坐标转换为视口(设备)坐标的两个公式:
xViewport=(xWindow-xWinOrg)*xViewExt/xWinExt+xViewOrg
yViewport=(yWindow-yWinOrg)*yViewExt/yWinExt+yViewOrg
视口坐标转换为窗口坐标的两个公式:
xWindow=(xViewport-xViewOrg)*xWinExt/xViewExt+xWinOrg
yWindow=(yViewport-yViewOrg)*yWinExt/yViewExt+yWinOrg
在WM_TEXT模式下逻辑坐标和设备坐标的相互转换:
xViewport=xWindow-xWinOrg+xViewOrg
yViewport=yWindow-yWinOrg+yViewOrg
xWindow=xViewport-xViewOrg+xWinOrg
yWindow=yViewport-yViewOrg+yWinOrg
3、视口和窗口原点的改变
CDC中提供了两个成员函数:SetViewportOrg和SetWindowOrg。用来改变视口和窗口的原点。
注意:不管对窗口和视口原点如何改变,设备点(0,0)始终客户区的左上角。
11.2图形的保存和重绘
将上一章程序的基础上面添加图形的保存和重绘功能。然后将窗口背景恢复默认的颜色。
在窗口中绘制图形之后,调正窗口的大小,图形就消失了。原因就是窗口重绘发送了WM_PAINT消息,将之前的绘制的图形给擦除掉了;这时需要我们将图形保存起来,保存的要素有三个:起点、终点和绘制的类型;因为图形的重绘总会调用视类的OnDraw函数,可以在该函数中将图形重新呈现出来。
本例利用一个类来保存图形的三个要素。
Insert->New Class->新类的类型为Generic Class,名字为CGraph
然后为该类添加三个成员变量,类型设置为公有的,让视类能够访问:
m_nDrawType UINT 绘制类型
m_ptOrigin CPoint 起点
m_ptEnd CPoint 终点
在该类头文件类中添加一个带参数的构造函数。
class CGraph
{
public:
CPoint m_ptEnd;
CPoint m_ptOrigin;
UINT m_nDrawType;
CGraph();
CGraph(UINT m_nDrawType,CPoint m_ptOrigin,CPoint m_ptEnd);
virtual ~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;
}
因为数组存储CGraph类对象不方便,这里使用了动态存储结构。
11.2.1集合类CPtrArray
集合类CPtrArray是MFC提供的,而且它的容量可以动态增加。
我们可以利用集合类CPtrArray类存储多个对象,利用Add方法可以增加一个void指针所指向的对象;利用GetAt可以获得集合类中的某个元素;利用GetSize方法可以获得集合类元素的数目。
首先为视类增加一个CPtrArray类成员变量:m_ptArray。然后在OnLButtonUp函数绘图操作完成之后添加:
CGraph graph(m_nDrawType,m_ptOrigin,point);
m_ptArray.Add(&graph);
这里将视类的三个元素作为参数调用CGraph类的构造函数。
在视类源文件中添加:
#include “Graph.h”
然后在OnDraw函数中将保存的图形显现出来,在该函数中添加如下代码:
for(int i=0;i<m_ptArray.GetSize();i++)
{
switch(((CGraph*)m_ptArray.GetAt(i))->m_nDrawType)
{
case 1:
pDC->SetPixel(((CGraph*)m_ptArray.GetAt(i))->m_ptEnd,RGB(0,0,0));
break;
case 2:
pDC->MoveTo(((CGraph*)m_ptArray.GetAt(i))->m_ptOrigin);
pDC->LineTo(((CGraph*)m_ptArray.GetAt(i))->m_ptEnd);
break;
case 3:
pDC->Rectangle(CRect(((CGraph*)m_ptArray.GetAt(i))->m_ptOrigin,((CGraph*)m_ptArray.GetAt(i))->m_ptEnd));
break;
case 4:
pDC->Ellipse(CRect(((CGraph*)m_ptArray.GetAt(i))->m_ptOrigin,((CGraph*)m_ptArray.GetAt(i))->m_ptEnd));
break;
}
}
因为集合类的GetAt方法返回是void*类型,所以应将其强制转换为CGraph类型的指针。
但是窗口尺寸发生变化时,这些图形仍然会消失。原因就是graph是一个局部变量,当调用Add函数后,这个局部变量的地址就保存到m_ptrArray集合类对象中。
当OnLButtonUp函数析构完成后,这个局部对象就会析构。为了解决这个问题,将上述代码修改为:
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
m_ptArray.Add(pGraph);
这时对象的地址保存在堆上,而不是栈上,不会程序自动回收,需要程序员手动开辟和释放。
运行,就会发现窗口尺寸变化后,图像并不会消失。
11.2.2OnPaint与OnDraw
基类CView在响应WM_PAINT消息的函数:OnPaint中调用了OnDraw函数,所以程序视类的子类中的OnDraw函数才会调用