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

来源:互联网 发布:手机工程制图软件 编辑:程序博客网 时间:2024/05/01 07:56

在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内部分配的其他资源释放掉了。

 

原创粉丝点击