孙鑫VC学习(第11课--图形的保存和重绘)

坐标空间

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

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

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

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

n       页面空间(逻辑空间)(窗口 逻辑坐标)到设备空间(视口,客户区设备坐标)的转换所用的是两个矩形的宽与高的比率,其中页面空间中的矩形被称为窗口,设备空间中的矩形被称为视口,Windows把窗口原点映射到视口原点,把窗口范围映射到视口范围,就完成了这种转换

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

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

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

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

n       几乎在所有的GDI函数中使用的坐标值都是采用的逻辑单位。Windows必须将逻辑单位转换为设备单位,即像素。这种转换是由映射方式、窗口和视口的原点以及窗口和视口的范围所控制的。

n       Windows对所有的消息(WM_SIZEWM_MOUSEMOVEWM_LBUTTONDOWNWM_LBUTTONUP),所有的非GDI函数和一些GDI函数(例如GetDeviceCaps函数),永远使用设备坐标。

n       窗口是基于逻辑坐标的,逻辑坐标可以是象素、毫米、英寸等单位;视口是基于设备坐标(象素)的。通常,视口和客户区是相同的。

n       缺省的映射模式为MM_TEXT。在这种映射模式下,逻辑单位和设备单位相同。

n       不管对窗口和视口原点作什么改变,设备点(0,0)始终是客户区的左上角。

n       关于解决方法的说明

n       首先我们在绘制图形之后,在保存坐标点之前,调用OnPrepareDC函数,调整显示上下文的属性,将视口的原点设置为(0,-150),这样的话,窗口的原点,也就是逻辑坐标(0,0)将被映射为设备坐标(0,-150),然后我们调用DPtoLP函数将设备坐标(680,390)转换为逻辑坐标,根据设备坐标转换为逻辑坐标的公式:

n            xWindow = xViewport-xViewOrg+xWinOrg

n            yWindow = yViewport-yViewOrg+yWinOrg,得到逻辑点的x坐标为680-0+0=680y坐标为390-(-150)+0=540,将逻辑坐标(680,540)保存起来,在窗口重绘时,会先调用OnPrepareDC函数,调整显示上下文的属性,将视口的原点设置为了(0,-150),然后GDI函数用逻辑坐标点(680,540)绘制图形,被Windows转换为设备坐标点(680,390),和原先显示图形时的设备点是一样的,当然图形就还在原先的地方显示出来。

n        

逻辑坐标和设备坐标

n       几乎在所有的GDI函数中使用的坐标值都是采用的逻辑单位Windows必须将逻辑单位转换为设备单位,即像素。这种转换是由映射方式、窗口和视口的原点以及窗口和视口的范围所控制的。

n       Windows对所有的消息(WM_SIZEWM_MOUSEMOVEWM_LBUTTONDOWNWM_LBUTTONUP),所有的非GDI函数和一些GDI函数(例如GetDeviceCaps函数),永远使用设备坐标。

n       窗口是基于逻辑坐标的,逻辑坐标可以是象素、毫米、英寸等单位;视口是基于设备坐标(象素)的。通常,视口和客户区是相同的。

n       缺省的映射模式为MM_TEXT。在这种映射模式下,逻辑单位和设备单位相同。

视口和窗口原点的改变

n       CDC中提供了两个成员函数函数SetViewportOrgSetWindowOrg,用来改变视口和窗口的原点。

n       如果将视口原点设置为(xViewOrg,yViewOrg),则逻辑点(0,0)就会被映射为设备点(xViewOrg,yViewOrg)。如果将窗口原点改变为(xWinOrg,yWinOrg),则逻辑点(xWinOrg,yWinOrg)将会被映射为设备点(0,0),即左上角。

n       不管对窗口和视口原点作什么改变,设备点(0,0)始终是客户区的左上角。

OnPrepareDC会随时根据滚动窗口的位置来调整视口的原点。

CreateCompatibleBitmap返回的位图对象只包含相应设备描述表中的位图的位图信息头,不包含颜色表和象素数据块。因此,选入该位图对象的设备描述表不能像选入普通位图对象的设备描述表一样应用,必须在SelectObject函数之后,调用BitBlt将原始设备描述表的颜色表及象素数据块拷贝到兼容设备描述表。

 

建立一个MFC的单文档程序,加上点,线,矩形,椭圆按钮。并在VIEW类中增加消息响应函数。

private:

       UINT m_dwstyle;

       CPoint m_dnpt;

 

CGraphicView::CGraphicView()

{

       // TODO: add construction code here

    m_dwstyle=0;

       m_dnpt=0;

}

 

void CGraphicView::OnDot()

{

       // TODO: Add your command handler code here

       m_dwstyle=1;

}

 

void CGraphicView::OnLine()

{

       // TODO: Add your command handler code here

       m_dwstyle=2;

}

 

void CGraphicView::OnRect()

{

       // TODO: Add your command handler code here

       m_dwstyle=3;

}

 

void CGraphicView::OnEllipse()

{

       // TODO: Add your command handler code here

       m_dwstyle=4;

}

 

void CGraphicView::OnLButtonDown(UINT nFlags, CPoint point)

{

       // TODO: Add your message handler code here and/or call default

       m_dnpt=point;

       CView::OnLButtonDown(nFlags, point);

}

 

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)

{

       // TODO: Add your message handler code here and/or call default

       CClientDC dc(this);

       CBrush *pbrush=(CBrush*)dc.SelectStockObject(NULL_BRUSH);

       switch (m_dwstyle)

       {

       case 1:

              dc.SetPixel(point,RGB(255,0,0));

              break;

       case 2:

              dc.MoveTo(m_dnpt);

              dc.LineTo(point);

              break;

       case 3:

              dc.Rectangle(CRect(m_dnpt,point));

              break;

       case 4:

              dc.Ellipse(CRect(m_dnpt,point));

              break;

       default:

              break;

 

       }

       dc.SelectObject(pbrush);

       CView::OnLButtonUp(nFlags, point);

}

此时当窗口尺寸发生变化时,画的图形不见了。先擦除背景,再重绘。窗口重绘 的时候要调用OnDraw(CDC* pDC)

新增一个通用类:CGraph。其头文件和源文件如下:

头文件:

class CGraph 

{

public:

       UINT m_dwsytle;

       CPoint m_dnpt;

       CPoint m_uppt;

       CGraph();

       CGraph(UINT style,CPoint dnpt,CPoint uppt);

       virtual ~CGraph();

      

};

源文件中:

CGraph::CGraph(UINT style,CPoint dnpt,CPoint uppt)

{

       m_dwsytle=style;

       m_dnpt=dnpt;

       m_uppt=uppt;

}

VIEW类增加:

CPtrArray m_ptrArray;

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)中的代码增加下面代码:
CGraph *pgraph=new CGraph(m_dwstyle,m_dnpt,point);

       m_ptrArray.Add(pgraph);

 

void CGraphicView::OnDraw(CDC* pDC)

{

       CGraphicDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       CClientDC dc(this);

       CBrush *pBrush=(CBrush*)dc.SelectStockObject(NULL_BRUSH);

       int count=m_ptrArray.GetSize();

       for (int i=0;i<count;i++)

       {

              switch (((CGraph*)m_ptrArray.GetAt(i))->m_dwsytle)

              {

              case 1:

                     dc.SetPixel(((CGraph*)m_ptrArray.GetAt(i))->m_dnpt,RGB(255,0,0));

                     break;

              case 2:

                     dc.MoveTo(((CGraph*)m_ptrArray.GetAt(i))->m_dnpt);

                     dc.LineTo(((CGraph*)m_ptrArray.GetAt(i))->m_uppt);

                     break;

              case 3:

                     dc.Rectangle(CRect(((CGraph*)m_ptrArray.GetAt(i))->m_dnpt,

                            ((CGraph*)m_ptrArray.GetAt(i))->m_uppt));

                     break;

              case 4:

                     dc.Ellipse(CRect(((CGraph*)m_ptrArray.GetAt(i))->m_dnpt,

                            ((CGraph*)m_ptrArray.GetAt(i))->m_uppt));

                     break;

              default:

                     break;

              }

       }

       dc.SelectObject(pBrush);

       // TODO: add draw code for native data here

}

此时发现窗口重绘的时候,图形仍在。

OnDraw是一个虚函数,当窗口重绘 的时候会被调用。窗口重绘 的时候会发送WM_PAINT消息。如果想让一个图形在窗口中始终都能看到,应该放到响应WM_PAINT的函数中。

在查询VC的源代码的时候发现:

void CView::OnPaint() //响应WM_PAINT消息

{

       // standard paint routine

       CPaintDC dc(this);

       OnPrepareDC(&dc);

       OnDraw(&dc);

}

 

如果void CGraphicView::OnPaint()有了这个消息,就不会调用OnDraw函数。(当然可以在这个里面调用OnDraw函数);

 

void CGraphicView::OnPaint()

{

       CPaintDC dc(this); // device context for painting

      

       // TODO: Add your message handler code here

       OnPrepareDC(&dc);//后面介绍为什么调用这个函数。

       OnDraw(&dc);

       // Do not call CView::OnPaint() for painting messages

}

 

下面让窗口具有滚动的能力:

将头文件和源文件中的VIEW换成SCROLLVIEW类。

void CGraphicView::OnPaint()

{

       CPaintDC dc(this); // device context for painting

      

       // TODO: Add your message handler code here

       OnPrepareDC(&dc);

       OnDraw(&dc);

       // Do not call CScrollView::OnPaint() for painting messages

}

//窗口创建完成之后第一个要调用的函数。在OnDraw函数调用之前.

void CGraphicView::OnInitialUpdate()

{

       CScrollView::OnInitialUpdate();

      

       // TODO: Add your specialized code here and/or call the base class  CSize(100, 100)

       SetScrollSizes( MM_TEXT, CSize(1000, 1000)/* GetDocument( )->GetDocSize( )*/ ); 

}

或者:

void CGraphicView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
 // TODO: Add your specialized code here and/or call the base class
 SetScrollSizes(MM_TEXT,CSize(800,600)); 
}

 

这时在OnLButtonUp中添加如下代码:

       OnPrepareDC(&dc);

       dc.DPtoLP(&m_dnpt);

       dc.DPtoLP(&point);

此操作在保存点之前做的。

设备坐标点的(0,0)始终是客户区的左上角。

下面再介绍两种保存图形和重绘图形的方式:

第一种:

CMetaFileDC包含了一系列的图形绘制命令(源文件,内存中),(绘制图形函数),在METAFILEDC里面做图,想看的时候再次打开。

private:

       CMetaFileDC m_metafl;

CTestView::CTestView()

{

       // TODO: add construction code here

 m_dwstyle=0;

 m_dnpt=0;

 m_metafl.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*)m_metafl.SelectStockObject(NULL_BRUSH);

       switch (m_dwstyle)

       {

       case 1:

              m_metafl.SetPixel(point,RGB(255,0,0));

              break;

       case 2:

              m_metafl.MoveTo(m_dnpt);

              m_metafl.LineTo(point);

              break;

       case 3:

              m_metafl.Rectangle(CRect(m_dnpt,point));

              break;

       case 4:

              m_metafl.Ellipse(CRect(m_dnpt,point));

              break;

       default:

              break;

 

       }

//    OnPrepareDC(&dc);

//    dc.DPtoLP(&m_dnpt);

//    dc.DPtoLP(&point);

//    CGraph *pgraph=new CGraph(m_dwstyle,m_dnpt,point);

//    m_ptrArray.Add(pgraph);

       m_metafl.SelectObject(pbrush);

       CScrollView::OnLButtonUp(nFlags, point);

}

 //注意,metafl不能在滚动视图中显示,因为在源文件中做图的时候,都是设备坐标,在源文件中显示时,也是以设备坐标显示的。

void CGraphicView::OnDraw(CDC* pDC)

{

       CGraphicDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

//     CClientDC dc(this);

         HMETAFILE hmetal=m_metafl.Close( );

       m_metafl.Create();//为再次绘制时做准备。

       pDC->PlayMetaFile( hmetal );

    DeleteMetaFile(hmetal);

       // TODO: add draw code for native data here

}

注意此时窗口重绘的时候图形出现,但是先前保存的图形不见了。

 

 

 

void CGraphicView::OnDraw(CDC* pDC)

{

       CGraphicDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

//     CClientDC dc(this);

       HMETAFILE hmeta=m_metafl.Close();
 m_metafl.Create();
 m_metafl.PlayMetaFile(hmeta);
 pDC->PlayMetaFile(hmeta);
 DeleteMetaFile(hmeta);

       // TODO: add draw code for native data here

}

此时在重绘的时候能重新看到图形了。

下面对图形进行保存和打开。为文件下面的打开和保存添加VIEW类中的响应函数。

 

void CGraphicView::OnFileSave()

{

       // TODO: Add your command handler code here

       HMETAFILE hmeta=m_metafl.Close();

       CopyMetaFile(hmeta,"1.wmf");//.wmf是源文件扩展名

       m_metafl.Create();

       DeleteMetaFile(hmeta);

}

 

void CGraphicView::OnFileOpen()

{

       // TODO: Add your command handler code here

       HMETAFILE hmeta=GetMetaFile("1.wmf");

       m_metafl.PlayMetaFile(hmeta);

       //m_metafl.Create();

       DeleteMetaFile(hmeta);

       Invalidate();

 

}

 

利用兼容DC

在内存中准备一幅图像,在ONDRAW函数中,COPY到目的DC中。

private: CDC m_cmpdc;

当兼容DC创建时,会选择一个单色的位图,这个位图是一个像素大小的。(214

VIEW类增加:

CDC  m_cmpdc;

此时:

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)

{

       // TODO: Add your message handler code here and/or call default

       CClientDC dc(this);

    if (!m_cmpdc.m_hDC)

    {

              m_cmpdc.CreateCompatibleDC(&dc);

              CRect rect;

              GetClientRect(&rect);

              CBitmap bp;

              bp.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height());

              //将兼容位图选到兼容DC中,从而确定兼容DC的大小。兼容DC必须要选择一幅位图。是一个内存DC

              //兼容DC做的图,在窗口当中是看不到的。

              m_cmpdc.SelectObject(&bp);

              m_cmpdc.BitBlt(0,0,rect.Width(),rect.Height(),&dc,0,0,SRCCOPY);

              CBrush *pbrush=(CBrush*)m_cmpdc.SelectStockObject(NULL_BRUSH);

    }

       switch (m_dwstyle)

       {

       case 1:

              m_cmpdc.SetPixel(point,RGB(255,0,0));

              break;

       case 2:

              m_cmpdc.MoveTo(m_dnpt);

              m_cmpdc.LineTo(point);

              break;

       case 3:

              m_cmpdc.Rectangle(CRect(m_dnpt,point));

              break;

       case 4:

              m_cmpdc.Ellipse(CRect(m_dnpt,point));

              break;

       default:

              break;

 

       }

       //m_cmpdc.SelectObject(pbrush);

       CScrollView::OnLButtonUp(nFlags, point);

}

 

void CGraphicView::OnDraw(CDC* pDC)

{

       CGraphicDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       CRect rect;

       GetClientRect(&rect);

       pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_cmpdc,0,0,SRCCOPY);

}

你可能感兴趣的:(孙鑫VC学习(第11课--图形的保存和重绘))