我们在开发GDI程序时,会实现简单的画图功能,当我们放大或缩小窗口时,我们所绘制的图形元素就消失了。因为窗口在变化过程中发生了重绘,导致原先的图案消息,这里将解决这个问题。我们利用兼容DC完成图形重新绘制,从而保持图形不消失;
这里将要绘制矩形,椭圆形,直线这三个图形为例;完成一个图形绘制需要三个关键信息:起点、终点、绘画类型,这里用CGraph这个类进行描述,Graph.h是类声明信息,Graph.cpp是实现部分,具体代码如下:
//Graph.h绘图三要素信息
class CGraph
{
public:
enum
{
EN_RECT = 0,
EN_ELLIPSE,
EN_LINE,
};
CGraph(CPoint ptBegin, CPoint ptEnd, int DrawType);
CGraph(void);
~CGraph(void);
public:
void SetDrawType(int nType);
void SetBeginPoint(CPoint ptBegin);
void SetEndPont(CPoint ptEnd);
CPoint GetBeginPoint();
CPoint GetEndPont();
int GetDrawType();
private:
CPoint m_ptBegin;
CPoint m_ptEnd;
int m_DrawType;
};
//Graph.cpp
#include "StdAfx.h"
#include "Graph.h"
CGraph::CGraph(void)
{
m_DrawType = 0;
}
CGraph::CGraph(CPoint ptBegin, CPoint ptEnd, int DrawType)
{
this->m_ptBegin = ptBegin;
this->m_ptEnd = ptEnd;
this->m_DrawType = DrawType;
}
CGraph::~CGraph(void)
{
}
void CGraph::SetDrawType(int nType)
{
m_DrawType = nType;
}
void CGraph::SetBeginPoint(CPoint ptBegin)
{
m_ptBegin = ptBegin;
}
void CGraph::SetEndPont(CPoint ptEnd)
{
m_ptEnd = ptEnd;
}
CPoint CGraph::GetBeginPoint()
{
return m_ptBegin;
}
CPoint CGraph::GetEndPont()
{
return m_ptEnd;
}
int CGraph::GetDrawType()
{
return m_DrawType;
}
主要原理:我们创建一个兼容DC,并在兼容DC上进行画图操作,然后在OnDraw函数中将兼容DC保存的图形复制到目的窗口中。
1) 创建一个Graphic的单文档工程
2)在菜单栏中增加“绘图”子菜单,并为其增加三个菜单项,其ID分别是IDM_RECT、IDM_ELLIPSE、IDM_LINE;
3)在CGraphicView类中添加这三个菜单项增加事件响应函数;
4) 在CGraphicView类中增加LBUTTONDOWN 、LBUTTONUP消息响应函数;
5)为CGraphicView类添加CGraph类成员函数m_GrahpInfo、CDC成员函数m_dcCompatible;
CGraphicView类中的主要代码实现如下:
// CGraphicView 绘制
/****************************************************************
*函数名称:
*功 能:图形兼容DC的画图显示到屏幕
*作 者:Jin
*日 期:2017年2月12日
****************************************************************/
void CGraphicView::OnDraw(CDC* pDC)
{
CGraphicDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
CRect rect;
GetClientRect(&rect);
//将兼容DC的显示表面拷贝到目的DC,即显示到屏幕
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompatible,0,0,SRCCOPY);
}
// CGraphicView 消息处理程序
/****************************************************************
*函数名称: 设置画矩形类型
*功 能:
*作 者:Jin
*日 期:2017年2月12日
****************************************************************/
void CGraphicView::OnRect()
{
// TODO: 在此添加命令处理程序代码
m_GrahpInfo.SetDrawType(CGraph::EN_RECT);
}
/****************************************************************
*函数名称:设置椭圆形类型
*功 能:
*作 者:Jin
*日 期:2017年2月12日
****************************************************************/
void CGraphicView::OnEllipse()
{
// TODO: 在此添加命令处理程序代码
m_GrahpInfo.SetDrawType(CGraph::EN_ELLIPSE);
}
/****************************************************************
*函数名称:设置画直线类型
*功 能:
*作 者:Jin
*日 期:2017年2月12日
****************************************************************/
void CGraphicView::OnLine()
{
// TODO: 在此添加命令处理程序代码
m_GrahpInfo.SetDrawType(CGraph::EN_LINE);
}
/****************************************************************
*函数名称:设置画图起点
*功 能:
*作 者:Jin
*日 期:2017年2月12日
****************************************************************/
void CGraphicView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_GrahpInfo.SetBeginPoint(point);
CView::OnLButtonDown(nFlags, point);
}
/****************************************************************
*函数名称: 主要完成兼容DC图形绘制
*功 能:
*作 者:Jin
*日 期:2017年2月12日
****************************************************************/
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_GrahpInfo.SetEndPont(point);
//开始绘图操作流程
CClientDC dc(this);
CBrush* pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
if (!m_dcCompatible.m_hDC)
{
//1)创建兼容DC且仅创建一个
m_dcCompatible.CreateCompatibleDC(&dc);
CRect DrawRangle;
GetClientRect(&DrawRangle);
//2)创建兼容位图
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&dc,DrawRangle.Width(), DrawRangle.Height());
//3)选人位图,来确定显示表面大小,从而可以在兼容DC上画图
m_dcCompatible.SelectObject(&bitmap);
//4)将原始DC中的颜色表和像素数据块复制到兼容DC;
m_dcCompatible.BitBlt(0,0,DrawRangle.Width(),DrawRangle.Height(),&dc,0,0,SRCCOPY);
m_dcCompatible.SelectObject(pBrush);
}
//画图选择
switch (m_GrahpInfo.GetDrawType())
{
case CGraph::EN_RECT :
{
CRect rect(m_GrahpInfo.GetBeginPoint(), m_GrahpInfo.GetEndPont());
m_dcCompatible.Rectangle(&rect);
break;
}
case CGraph::EN_LINE :
{
m_dcCompatible.MoveTo(m_GrahpInfo.GetBeginPoint());
m_dcCompatible.LineTo(m_GrahpInfo.GetEndPont());
break;
}
case CGraph::EN_ELLIPSE :
{
CRect Ellipse(m_GrahpInfo.GetBeginPoint(), m_GrahpInfo.GetEndPont());
m_dcCompatible.Ellipse(&Ellipse);
break;
}
default:
break;
}
//窗口无效,引发重绘操作
Invalidate(TRUE);
//立即显示绘画内容
UpdateWindow();
CView::OnLButtonUp(nFlags, point);
}
为了能够屏幕整个区域都能绘制图形,我们将应用程序初始启动时以最大化形式;我们将Graphic.cpp中的ShowWindow参数由SW_SHOW改为SW_MAXIMIZE,如下所示:
// 唯一的一个窗口已初始化,因此显示它并对其进行更新
m_pMainWnd->ShowWindow(SW_MAXIMIZE/*SW_SHOW*/);
运行效果:
我们变化窗口时,先前所绘制的图形不会消息;
总结
本文主要利用兼容DC和兼容位图的方式实现图形的保存,当窗口发生重绘时会调用OnDraw函数,我们在该函数中将兼容DC上的内容复制到屏幕上,从而保存窗口上的内容不消失;
在窗口中显示BMP图片这篇博文中也提到了兼容DC;所不同的是现在加载的不是真正的位图,而是兼容位图,和兼容DC是一样的道理,都是一个内存;
需要特别说明的是,CreateCompatibleBitmap函数返回的位图对象只包含相应设备描述表中的位图信息头,不包含该位图的颜色表和像素块;因此,我们在调用SelectObj操作将兼容位图选人到兼容DC之后,还需要调用BitBlt函数将原始设备描述表的颜色表和像素数据块复制到兼容设备描述表中;