您的位置:首页 > 其它

浅谈如何在MFC中对CDC进行二次封装

2008-04-17 11:57 447 查看
在MFC中进行绘图操作,必须借助Device Context。MFC也善解人意的对它进行了一些封装,提供了一个CDC类,以及由CDC继承而来的CClientDC、CPaintDC和CWndowDC等。这里面最常用的就是CClientDC类,用它可以在窗口的客户区进行绘图。

一个典型的应用代码如下:

void CMycdcView::OnTest()
{
CClientDC dc(this);
CRect rc;
GetClientRect(&rc);
rc.DeflateRect(50,50,50,50);

CPen pen(1,0,RGB(255,0,0));
CPen *pOldPen = dc.SelectObject(&pen);

CBrush brush(2,RGB(128,0,128));
CBrush *pOldBrush = dc.SelectObject(&brush);

dc.Ellipse(&rc);

dc.SelectObject(pOldPen);
dc.SelectObject(pOldBrush);

}

这段代码首先选择了画笔和画刷,然后在客户窗口画了一个椭圆。这段代码唯一不爽的地方,就是每次绘图完成后,都要把老的资源,例如画笔画刷等再选择回Device Context中去。这样不仅繁琐,而且容易遗漏。

对于程序员来讲,有时候偷懒是一种美德。偷懒可以推动软件技术的发展:)

下面就让我们来偷懒一下,对CClientDC进行二次封装。

首先我们看到老的代码需要使用者自己保存CClientDC中资源的使用状态,并最后恢复之。假如能让CClientDC自己做这件事情,那无疑可以减轻使用者的工作量。

所以我们来设计一个新的类CMyClientDC,该类从CClientDC继承而来:

class CMyClientDC : public CClientDC
{
DECLARE_DYNCREATE(CMyClientDC)
public:
CMyClientDC(CWnd* pWnd);
virtual ~CMyClientDC();
public:
void SelectPen(CPen *pPen);
void SelectBrush(CBrush *pBrush);
void SelectFont(CFont *pFont);
void SelectBitmap(CBitmap *pBitmap);

private:
CPen *m_pPen;
CBrush *m_pBrush;
CFont *m_pFont;
CBitmap *m_pBitmap;
};

这个类最大的特点就是“有状态”的,也就是它可以自己记住资源的使用情况。这里通过几个私有成员变量,来保存各种资源:Pen、Brush、Font 和Bitmap。假如以后有新的资源,也很容易加进去。

看看这个类的实现:

CMyClientDC::CMyClientDC(CWnd* pWnd) : CClientDC(pWnd)
{
m_pPen = NULL;
m_pBrush = NULL;
m_pFont = NULL;
m_pBitmap = NULL;
}

CMyClientDC::~CMyClientDC()
{
if(m_pPen)
{
SelectObject(m_pPen);
m_pPen = NULL;
}
if(m_pBrush)
{
SelectObject(m_pBrush);
m_pBrush = NULL;
}
if(m_pFont)
{
SelectObject(m_pFont);
m_pFont = NULL;
}
if(m_pBitmap)
{
SelectObject(m_pBitmap);
m_pBitmap = NULL;
}
}

void CMyClientDC::SelectPen(CPen *pPen)
{
m_pPen = SelectObject(pPen);
}

void CMyClientDC::SelectBrush(CBrush *pBrush)
{
m_pBrush = SelectObject(pBrush);
}

void CMyClientDC::SelectFont(CFont *pFont)
{
m_pFont = SelectObject(pFont);
}

void CMyClientDC::SelectBitmap(CBitmap *pBitmap)
{
m_pBitmap = SelectObject(pBitmap);
}

在构造函数中把各指针初始化,在析构函数中进行判断,假如指针不为NULL,意味着已经选择了资源,所以必须恢复之。CMyClientDC类中还增加了几个选择资源的函数,这只是对CDC函数的简单封装,并把老的资源指针保留起来。

代码很简单,一看就清楚。下面我们就可以这样来绘图了:

void CMycdcView::OnTest()
{
CMyClientDC dc(this);
CRect rc;
GetClientRect(&rc);
rc.DeflateRect(50,50,50,50);

CPen pen(1,0,RGB(255,0,0));
dc.SelectPen(&pen);
CBrush brush(2,RGB(128,0,128));
dc.SelectBrush(&brush);

dc.Ellipse(&rc);

}

是不是简洁了一些?我们也可以偷懒写少一点代码了。

但这样是不是就万事大吉了?非也。在某些应用情况下,这个封装类存在资源泄漏的危险。请参考第2部分。。。

(说明:这篇文章只是我兴之所至写的,可能不全面有错漏,也可能有更好的封装方法,请多指教。谢谢。)



在第1部分中,我简单的描述了对CClientDC的二次封装,使得调用起来比较方便。但却有个问题,就是在某些使用情况下会引起资源泄漏,呵呵。

这种情况就是连续调用GDI选择函数,比如这样:

CPen pen(1,0,RGB(255,0,0));
dc.SelectPen(&pen);
dc.Ellipse(&rc);

CPen pen2(2,0,RGB(128,128,128));
dc.SelectPen(&pen2);
dc.Rectangle(100,100,300,300);

原先的代码有个缺陷,就是在处理这种情况时会导致GDI资源泄漏。

我们知道要使用自定义的资源,例如Pen、Brush等,必须先创建它,然后调用CDC::SelectObject()函数,让GDI子系统选择它,GDI会在内部分配相应的资源(内存、句柄等)。在绘图完毕后,必须再调用CDC::SelectObject()函数恢复系统的缺省配置资源(缺省画笔、画刷等),才能够把这部分资源释放掉。

因为CMyClientDC中保存资源的指针,例如m_pPen、m_pBrush等在最后析构时,指向的资源并不是系统的缺省资源,而是最近一次被选中的资源。这样的话,GDI内部分配的资源就无法得到释放,因此造成资源泄漏。

所以必须加以改进,改进后的代码为:

CMyClientDC::CMyClientDC(CWnd* pWnd) : CClientDC(pWnd)
{
m_pPen = NULL;
m_pBrush = NULL;
m_pFont = NULL;
m_pBitmap = NULL;
}

CMyClientDC::~CMyClientDC()
{
if(m_pPen)
{
SelectObject(m_pPen);
m_pPen = NULL;
}
if(m_pBrush)
{
SelectObject(m_pBrush);
m_pBrush = NULL;
}
if(m_pFont)
{
SelectObject(m_pFont);
m_pFont = NULL;
}
if(m_pBitmap)
{
SelectObject(m_pBitmap);
m_pBitmap = NULL;
}
}

void CMyClientDC::SelectPen(CPen *pPen)
{
CPen *ptmpPen = SelectObject(pPen);
if(!m_pPen)
m_pPen = ptmpPen;
}

void CMyClientDC::SelectBrush(CBrush *pBrush)
{
CBrush *ptmpBrush = SelectObject(pBrush);
if(!m_pBrush)
m_pBrush = ptmpBrush;
}

void CMyClientDC::SelectFont(CFont *pFont)
{
CFont *ptmpFont = SelectObject(pFont);
if(!m_pFont)
m_pFont = ptmpFont;
}

void CMyClientDC::SelectBitmap(CBitmap *pBitmap)
{
CBitmap *ptmpBitmap = SelectObject(pBitmap);
if(!m_pBitmap)
m_pBitmap = ptmpBitmap;
}

现在在调用SelectXXX()时,要判断是不是第一次分配,是的话就保存起来,这个就是系统的缺省资源。在最后析构时,这个缺省资源选回去,这样就把GDI内部分配的其他资源释放掉了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: