1.创建图形
新建一个MFC单文档应用程序,仍然增加一个菜单,命名绘图,再添加几个菜单项:
IDM_POINT(点)、IDM_LINE(直线)、IDM_ RECTANGLE(矩形)、IDM_ ELLIPSE (椭圆),并分别对这四个菜单项添加命令响应,并在CGraphic2View类中添加一个私有的成员变量UINT m_nDrawType;
并在构造方法中初始化:
CGraphic2View::CGraphic2View()
{
// TODO: add construction code here
m_nDrawType=0;
m_ptOrigin=0;
m_dcMetaFile.Create();//创建一个内存的CMetaFileDC对象
}
再编辑四个菜单项的命令响应:
void CGraphic2View::OnPoint()
{
// TODO: Add your command handler code here
m_nDrawType=1;
}
void CGraphic2View::OnLine()
{
m_nDrawType=2;
}
void CGraphic2View::OnRectangle()
{
m_nDrawType=3;
}
void CGraphic2View::OnEllipse()
{
m_nDrawType=4;
}
再在CGraphic2View类中添加一个私有的成员变量m_ptOrigin用来保存原点坐标,并在构造函数中初始化,上面已列出。
接着再给CGraphic2View类添加两个WM_LBUTTONDOWN和WM_LBUTTONUP消息响应函数,编辑:
void CGraphic2View::OnLButtonDown(UINT nFlags, CPoint point)
{
m_ptOrigin=point;//将这个点保存起来
CView::OnLButtonDown(nFlags, point);
}
void CGraphic2View::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;
}
CView::OnLButtonUp(nFlags, point);
}
运行可以发现当窗口尺寸发现变化是,所绘制的图形也消失了!
2.图形重绘
新建一个Generic Class (name:CGraph),并在这个类当中增加3个成员变量,同时为了对这3个成员进行赋值,定义一个带参数的构造函数,如下:
public:
CPoint m_ptOrigin;
CPoint m_ptEnd;
UINT m_nDrawType;
CGraph();
CGraph(UINT m_nDrawType,CPoint m_ptOrigin,CPoint m_ptEnd);
并在源文件Graph.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;//图形终点
}
在CGraphic2View类当中,新增加一个成员变量CPtrArray m_ptrArray; 这个集合类型的变量用来保存CGraph对象,并在CGraphic2View::OnLButtonUp添加:
CGraph graph(m_nDrawType,m_ptOrigin,point);//构造一个对象
//注:此时graph是一个局部变量,为它分配的内存在栈中,当这个函数结束时,对象也会被析构
m_ptrArray.Add(&graph);//将graph对象保存到集合类的对象m_ptArray当中
接下来在CGraphic2View::OnDraw函数中添加:
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;
default:
break;
}
}
运行,当改变窗口大小时,图像仍然消息了,下面继续修改。
在CGraphic2View::OnLButtonUp中修改:
//CGraph graph(m_nDrawType,m_ptOrigin,point);//构造一个对象
//注:此时graph是一个局部变量,为它分配的内存在栈中,当这个函数结束时,对象也会被析构
//m_ptrArray.Add(&graph);//将graph对象保存到集合类的对象m_ptArray当中
CGraph *pGraph;//这也是一个局部的指针变量,内存在栈中
pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
//这一句调用它的构造函数为pGraph指针类型的变量在堆中分配一个内存空间,构造一个CGraph类的对象
//因为用new所分配的内存都是在堆中分配的,在堆中分配的对象的内存,如果不去显示的调用Delete去释放
//这个对象内存,那么这个对象的生命周期是和应用程序保持一致的
m_ptrArray.Add(pGraph);//将之前在堆中构造的CGraph类的对象地地址索引保存到集合类的对象m_ptArray当中
运行,绘制图形,改变窗口大小,图形没有消失,OK!
3.将图形输出放置到WM_PAINT消息响应函数中
在CGraphic2View类上添加一个WM_PAINT消息处理,编辑:
void CGraphic2View::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
OnPrepareDC(&dc); //调整显示上下文的属性
OnDraw(&dc);//调用OnDraw
// Do not call CView::OnPaint() for painting messages
}
4.给窗口添加滚动条
首先将CGraphic2View的基类CView手动改成CScorllView,在CGraphic2View的头文件中和源文件中将所有的CView都替换成CScrollView,编译运行,会发现出现了非法操作,
先介绍一下坐标空间的转换,下图
Coordinate Spaces and Transformations
Applications use coordinate spaces and transformations to scale, rotate, translate, shear, and reflect graphics output. A coordinate space is a planar space that locates two-dimensional objects by using two reference axes that are perpendicular to each other. There are four coordinate spaces: world, page, device, and physical device (client area, desktop, or page of printer paper).
A transformation is an algorithm that alters ("transforms") the size, orientation, and shape of objects. Transformations also transfer a graphics object from one coordinate space to another. Ultimately, the object appears on the physical device, which is usually a screen or printer.
一个例子:
Using Coordinate Spaces and Transformations
This section contains an example that demonstrates the following tasks:
- Drawing graphics with predefined units.
- Centering graphics in the application's client area.
- Scaling graphics output to half its original size.
- Translating graphics output 3/4 of an inch to the right.
- Rotating graphics 30 degrees.
- Shearing graphics output along the x-axis.
- Reflecting graphics output about an imaginary horizontal axis drawn through its midpoint.
The following example was used to create the illustrations that appear earlier in this overview.
void TransformAndDraw(int iTransform, HWND hWnd)
{
HDC hDC;
XFORM xForm;
RECT rect;
// Retrieve a DC handle for the application's window.
hDC = GetDC(hWnd);
// Set the mapping mode to LOENGLISH. This moves the
// client area origin from the upper left corner of the
// window to the lower left corner (this also reorients
// the y-axis so that drawing operations occur in a true
// Cartesian space). It guarantees portability so that
// the object drawn retains its dimensions on any display.
SetGraphicsMode(hDC, GM_ADVANCED);
SetMapMode(hDC, MM_LOENGLISH);
// Set the appropriate world transformation (based on the
// user's menu selection).
switch (iTransform)
{
case SCALE: // Scale to 1/2 of the original size.
xForm.eM11 = (FLOAT) 0.5;
xForm.eM12 = (FLOAT) 0.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) 0.5;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case TRANSLATE: // Translate right by 3/4 inch.
xForm.eM11 = (FLOAT) 1.0;
xForm.eM12 = (FLOAT) 0.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) 1.0;
xForm.eDx = (FLOAT) 75.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case ROTATE: // Rotate 30 degrees counterclockwise.
xForm.eM11 = (FLOAT) 0.8660;
xForm.eM12 = (FLOAT) 0.5000;
xForm.eM21 = (FLOAT) -0.5000;
xForm.eM22 = (FLOAT) 0.8660;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case SHEAR: // Shear along the x-axis with a
// proportionality constant of 1.0.
xForm.eM11 = (FLOAT) 1.0;
xForm.eM12 = (FLOAT) 1.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) 1.0;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case REFLECT: // Reflect about a horizontal axis.
xForm.eM11 = (FLOAT) 1.0;
xForm.eM12 = (FLOAT) 0.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) -1.0;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
case NORMAL: // Set the unity transformation.
xForm.eM11 = (FLOAT) 1.0;
xForm.eM12 = (FLOAT) 0.0;
xForm.eM21 = (FLOAT) 0.0;
xForm.eM22 = (FLOAT) 1.0;
xForm.eDx = (FLOAT) 0.0;
xForm.eDy = (FLOAT) 0.0;
SetWorldTransform(hDC, &xForm);
break;
}
// Find the midpoint of the client area.
GetClientRect(hWnd, (LPRECT) &rect);
DPtoLP(hDC, (LPPOINT) &rect, 2);
// Select a hollow brush.
SelectObject(hDC, GetStockObject(HOLLOW_BRUSH));
// Draw the exterior circle.
Ellipse(hDC, (rect.right / 2 - 100), (rect.bottom / 2 + 100),
(rect.right / 2 + 100), (rect.bottom / 2 - 100));
// Draw the interior circle.
Ellipse(hDC, (rect.right / 2 -94), (rect.bottom / 2 + 94),
(rect.right / 2 + 94), (rect.bottom / 2 - 94));
// Draw the key.
Rectangle(hDC, (rect.right / 2 - 13), (rect.bottom / 2 + 113),
(rect.right / 2 + 13), (rect.bottom / 2 + 50));
Rectangle(hDC, (rect.right / 2 - 13), (rect.bottom / 2 + 96),
(rect.right / 2 + 13), (rect.bottom / 2 + 50));
// Draw the horizontal lines.
MoveToEx(hDC, (rect.right/2 - 150), (rect.bottom / 2 + 0), NULL);
LineTo(hDC, (rect.right / 2 - 16), (rect.bottom / 2 + 0));
MoveToEx(hDC, (rect.right / 2 - 13), (rect.bottom / 2 + 0), NULL);
LineTo(hDC, (rect.right / 2 + 13), (rect.bottom / 2 + 0));
MoveToEx(hDC, (rect.right / 2 + 16), (rect.bottom / 2 + 0), NULL);
LineTo(hDC, (rect.right / 2 + 150), (rect.bottom / 2 + 0));
// Draw the vertical lines.
MoveToEx(hDC, (rect.right/2 + 0), (rect.bottom / 2 - 150), NULL);
LineTo(hDC, (rect.right / 2 + 0), (rect.bottom / 2 - 16));
MoveToEx(hDC, (rect.right / 2 + 0), (rect.bottom / 2 - 13), NULL);
LineTo(hDC, (rect.right / 2 + 0), (rect.bottom / 2 + 13));
MoveToEx(hDC, (rect.right / 2 + 0), (rect.bottom / 2 + 16), NULL);
LineTo(hDC, (rect.right / 2 + 0), (rect.bottom / 2 + 150));
ReleaseDC(hWnd, hDC);
}
逻辑坐标与设备坐标的相互转换
DMM_TEXT映射方式下逻辑坐标与设备坐标的相互转换
接下来,在CGraphic2View类上添加一个虚函数,编辑:
void CGraphic2View::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
SetScrollSizes(MM_TEXT,CSize(1000,800));//设置一个滚动条
}
运行,可以发现多了滚动条,当把窗口滚动条拖到最下方时,再画一图形,切换窗口,再切回来,发现图形上移,下面解决这个问题。
视口和窗口原点的改变
关于图形错位的说明
解决方法
在CGraphic2View::OnLButtonUp中编辑:
//CGraph graph(m_nDrawType,m_ptOrigin,point);//构造一个对象
//注:此时graph是一个局部变量,为它分配的内存在栈中,当这个函数结束时,对象也会被析构
//m_ptrArray.Add(&graph);//将graph对象保存到集合类的对象m_ptArray当中
OnPrepareDC(&dc);//调整图形设备显示
dc.DPtoLP(&m_ptOrigin);//将设备点转换成逻辑点
dc.DPtoLP(&point);//将设备点转换成逻辑点
CGraph *pGraph;//这也是一个局部的指针变量,内存在栈中
pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
//这一句调用它的构造函数为pGraph指针类型的变量在堆中分配一个内存空间,构造一个CGraph类的对象
//因为用new所分配的内存都是在堆中分配的,在堆中分配的对象的内存,如果不去显示的调用Delete去释放
//这个对象内存,那么这个对象的生命周期是和应用程序保持一致的
m_ptrArray.Add(pGraph);//将之前在堆中构造的CGraph类的对象地地址索引保存到集合类的对象m_ptArray当中
说明:OnPrepareDC会随时根据滚动窗口的位置来调整视口的原点。
5.保存图形和重绘图形的另两种方式
先在CGraphic2View类上添加一个私有的成员变量CMetaFileDC m_dcMetaFile; ,
然后在构造函数当中:
CGraphic2View::CGraphic2View()
{
m_nDrawType=0;
m_ptOrigin=0;
m_dcMetaFile.Create();//创建一个内存的CMetaFileDC对象
}
然后在CGraphic2View::OnLButtonUp函数中修改:
void CGraphic2View::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:
//dc.SetPixel(point,RGB(0,0,0));
m_dcMetaFile.SetPixel(point,RGB(0,0,0));//m_dcMetaFile元文件
//m_dcCompatible.SetPixel(point,RGB(0,0,0));//m_dcCompatible兼容DC
break;
case 2:
//dc.MoveTo(m_ptOrigin);
//dc.LineTo(point);
m_dcMetaFile.MoveTo(m_ptOrigin);
m_dcMetaFile.LineTo(point);
//m_dcCompatible.MoveTo(m_ptOrigin);
//m_dcCompatible.LineTo(point);
break;
case 3:
//dc.Rectangle(CRect(m_ptOrigin,point));
m_dcMetaFile.Rectangle(CRect(m_ptOrigin,point));
//m_dcCompatible.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
//dc.Ellipse(CRect(m_ptOrigin,point));
m_dcMetaFile.Ellipse(CRect(m_ptOrigin,point));
//m_dcCompatible.Ellipse(CRect(m_ptOrigin,point));
break;
}
CView::OnLButtonUp(nFlags, point);
}
并在CGraphic2View::OnDraw函数中修改:
void CGraphic2View::OnDraw(CDC* pDC)
{
CGraphic2Doc* 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;
default:
break;
}
}
*/
HMETAFILE hmetaFile;
hmetaFile=m_dcMetaFile.Close();//关闭元文件DC,并获得它的句柄
pDC->PlayMetaFile(hmetaFile);//播放元文件
m_dcMetaFile.Create();//再次创建一个元文件
m_dcMetaFile.PlayMetaFile(hmetaFile);//去播放先前的元文件,在元文件DC中绘制
DeleteMetaFile(hmetaFile);//删除元文件
}
运行,绘制图形,改变窗口大小后,图形显示出来了。
6.将绘制的元文件保存到元文件当中和打开保存的元文件
方式一:利用元文件
先给IDR_MAINFRAME菜单的保存和打开子项添加命令响应,编辑:
void CGraphic2View::OnFileSave()
{
//保存元文件
HMETAFILE hmetaFile;
hmetaFile=m_dcMetaFile.Close();
CopyMetaFile(hmetaFile,"meta.wmf");//拷贝元文件
m_dcMetaFile.Create();//重新创建元文件,以便下一次绘画
DeleteMetaFile(hmetaFile);//删除元文件
}
void CGraphic2View::OnFileOpen()
{
//打开元文件
HMETAFILE hmetaFile;
hmetaFile=GetMetaFile("meta.wmf");//得到元文件句柄
m_dcMetaFile.PlayMetaFile(hmetaFile);//播放元文件
DeleteMetaFile(hmetaFile);//删除元文件句柄
Invalidate();//引起窗口的重画
}
方式二:利用兼容DC
首先在CGraphic2View类当中增加一个私有成员变量 CDC m_dcCompatible; 然后在CGraphic2View::OnLButtonUp函数中修改:
void CGraphic2View::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)//判断兼容DC是否被创建
{
m_dcCompatible.CreateCompatibleDC(&dc);//创建一个与当前DC兼容的兼容DC
CRect rect;
GetClientRect(&rect);//获取客户区域大小
CBitmap bitmap;//创建一个bitmap对象
bitmap.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height());//创建兼容位图
m_dcCompatible.SelectObject(&bitmap);//将位图选进兼容DC中
m_dcCompatible.BitBlt(0,0,rect.Width(),rect.Height(),&dc,0,0,SRCCOPY);//将源DC的设备表拷贝到兼容DC
m_dcCompatible.SelectObject(pBrush);//将透明的画刷选到兼容DC中
}
switch(m_nDrawType)
{
case 1:
//dc.SetPixel(point,RGB(0,0,0));
//m_dcMetaFile.SetPixel(point,RGB(0,0,0));//m_dcMetaFile元文件
m_dcCompatible.SetPixel(point,RGB(0,0,0));//m_dcCompatible兼容DC
break;
case 2:
//dc.MoveTo(m_ptOrigin);
//dc.LineTo(point);
//m_dcMetaFile.MoveTo(m_ptOrigin);
//m_dcMetaFile.LineTo(point);
m_dcCompatible.MoveTo(m_ptOrigin);
m_dcCompatible.LineTo(point);
break;
case 3:
//dc.Rectangle(CRect(m_ptOrigin,point));
//m_dcMetaFile.Rectangle(CRect(m_ptOrigin,point));
m_dcCompatible.Rectangle(CRect(m_ptOrigin,point));
break;
case 4:
//dc.Ellipse(CRect(m_ptOrigin,point));
//m_dcMetaFile.Ellipse(CRect(m_ptOrigin,point));
m_dcCompatible.Ellipse(CRect(m_ptOrigin,point));
break;
}
CView::OnLButtonUp(nFlags, point);
}
并在CGraphic2View::OnDraw函数中编辑:
void CGraphic2View::OnDraw(CDC* pDC)
{
CGraphic2Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
/*
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;
default:
break;
}
}
*/
/*
HMETAFILE hmetaFile;
hmetaFile=m_dcMetaFile.Close();//关闭元文件DC,并获得它的句柄
pDC->PlayMetaFile(hmetaFile);//播放元文件
m_dcMetaFile.Create();//再次创建一个元文件
m_dcMetaFile.PlayMetaFile(hmetaFile);//去播放先前的元文件,在元文件DC中绘制
DeleteMetaFile(hmetaFile);//删除元文件
*/
CRect rect;
GetClientRect(&rect);//得到客户的大小
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompatible,0,0,SRCCOPY);
//将位图贴到客户区当中
}
运行, OK!! ^_^