Introduction
There are some problems with maintaining resources for drawing, such as pens, brushes, etc. When you select an object into a DC, that object becomes undeletable. You might exit the context in which it was allocated, and its destructor is called, and DeleteObject
is ultimately called on it, but, surprise!, it hasn't really gone away. Because it was selected into a DC, there is a flag saying it is in active use, and therefore your DeleteObject
can't work. I don't know the actual details of how this works internally, but I know the external manifestation. The pen, brush, etc. simply stays in the "GDI heap".
If you are working on one of those horrid 16-bit windows-kludge-on-top-of-MS-DOS systems like Windows 3.1, Windows 3.11, Win98, Win98, or WinME (and if you think that Win9x/ME is a 32-bit OS, you have (a) not read the API documentation carefully (b) believed Microsoft's consumer-level documentation and thought it was technical documentation), there is a very limited system-wide pool out of which all pens and brushes are allocated, and eventually not even your desktop can allocate the resources it needs to draw. On a real, 32-bit operating system such as NT/2000/XP, the GDI heap is much larger, and is per-process, so eventually your program won't be able to draw, but the rest of the system will work fine, since you are only allowed to shoot yourself in the foot.
Here's a classic case:
void CMyView::OnPaint()
{
CPaintDC dc(this);
CFont f;
f.CreateFont(...);
dc.SelectObject(&f);
dc.TextOut(...);
}
Looks pretty good, right? Wrong. Look at what happens. The destructors are called when the context exits. This means that the DC will be freed (in the case of a CPaintDC
, this means that ::EndPaint
will be called), and the destructor for the CFont
will be called, which means that a ::DeleteObject
will be called. (This has other implications, for example, if you are doing a CWnd::SetFont
call, where the font has to have a lifetime beyond the lifetime of the variable; see my essay on this topic).
Strictly speaking, there is no specified order in which the destructors are known to be called. I checked the C++ standard, and although all sorts of issues are specified in order of execution of destructors, the order in which they are called for auto
variables (that is, ordinary stack variables) seems to be unspecified. In practice, it appears to be in the inverse order of their declaration, that is, the font, which is declared after the DC, will be destroyed first, then the DC will be destroyed. This loses, because when the font is destroyed, it is still selected into the font. (You may suspect that by declaring your fonts before the CPaintDC
will solve this. I would consider this an egregious programming blunder. For one thing, I'm not sure that deleting the DC first properly sets the values in the font so that it knows it is no longer in a DC (it could be selected into several DCs). I would never attempt this.
The proper thing to do is to restore the state of the DC before destroying it. Typically, this is done by saving the contents of the DC when you do a SelectObject
and then restoring them, for example:
void CMyView::OnPaint()
{
CPaintDC dc(this);
CFont f;
f.CreateFont(...);
CFont * oldfont = dc.SelectObject(&f);
dc.TextOut(...);
dc.SelectObject(oldfont);
}
This will now work properly. When the destructor for the font is called, it is no longer in a DC, and it will be deleted.
In fact, the principle that hikers follow, "Leave gates as you found them", is important in DC management. When you write a function that takes an incoming DC, you have to make sure that every change you make is restored. Note that every operation that modifies a DC returns an object (or value) that can be used to restore the state of the DC (the MFC calls wrap certain values, such as HFONT
values, in a corresponding "wrapper class", such as CFont
, and return a pointer to that MFC object). However, there are a lot of parameters to a DC, and this means you end up, in a serious drawing routine, having a lot of odd variables to keep track of.
However, it is simpler than this. There is a serious underappreciated function, ::SaveDC
, and its partner, ::RestoreDC
, which are available as methods CDC::SaveDC
and CDC::RestoreDC
. Using these, you can now change the code to avoid the use of any gratuitous variables:
void CMyView::OnPaint()
{
CPaintDC dc(this);
CFont f;
f.CreateFont(...);
int save = dc.SaveDC();
dc.SelectObject(&f);
dc.TextOut(...);
dc.RestoreDC(save);
}
Actually, it doesn't matter how many changes you made in the DC; when you do the RestoreDC
call, the DC is restored to whatever state it was just before the SaveDC
. This means that all the objects selected into it after the SaveDC
, all the changes in the text color, background color, styles, etc. are all erased.
There are interesting ways of using the integers to SaveDC
in creative ways, but I'm not going to go into those, primarily because I don't do them myself. Instead, I'm going to show a class that makes all this very easy. There's no download for this code; it is so simple you can copy-and-paste it into your own file.
SaveDC.h
class CSaveDC {
public:
CSaveDC(CDC & dc) { sdc = &dc; saved = dc.SaveDC(); }
CSaveDC(CDC * dc) { sdc = dc; saved = dc->SaveDC(); }
virtual ~CSaveDC() { sdc->RestoreDC(saved); }
protected:
CDC * sdc;
int saved;
};
That's all there is to it! Note that it has two constructors, one if you have a CDC *
and one if you have a CDC
or CDC &
. All you do is declare a dummy variable. But there's an important trick, illustrated below.
void CMyView::OnPaint()
{
CPaintDC dc(this);
CFont f;
f.CreateFont(...);
{
CSaveDC sdc(dc);
dc.SelectObject(&f);
dc.TextOut(...);
}
}
Note that the save context you create by using the CSaveDC
class must be in a scope that is smaller than the objects selected into the DC. Thus the /* save context */
block guarantees that the CSaveDC
destructor is guaranteed to be called before the destructor for the font. All you have to do is declare your fonts, pens, brushes, and regions outside the /* save context */
block and you can be guaranteed that their destructors will be called in a context where they are not selected into the active DC.
Note that CSaveDC
s can nest (because the ::SaveDC
can nest). The nesting can be static or dynamic. For example:
void CMyView::OnPaint()
{
CPaintDC dc(this);
CFont f;
f.CreateFont(12,...);
{
CSaveDC sdc(dc);
dc.SelectObject(&f);
drawboxes(dc);
dc.TextOut(...);
}
void CMyView::drawboxes(CDC & dc)
{
CPen RedPen(PS_SOLID, 0, RGB(255, 0, 0));
CBrush GreenBrush(RGB(0, 255, 0);
CFont f;
f.CreateFont(6, ...);
{
dc.SelectObject(&RedPen);
dc.SelectObject(&GreenBrush);
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT);
...
}
}
Note that the save context in drawboxes
contains lots of changes; these will be undone when you leave the save context, so that when drawboxes
returns to OnPaint
, the DC will have the correct font (12-pixel), background mode, etc.
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.
Copyright © 1999
<!--webbot bot="Substitution" s-variable="CompanyLongName" startspan -->CompanyLongName
<!--webbot bot="Substitution" endspan i-checksum="22147" --> All Rights Reserved.
www.flounder.com/mvp_tips.htm
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.