Stingray® Foundation : Chapter 10 GDI Classes : GDI Objects
GDI Objects
The Win32 API defines the following types of GDI objects:
Pen
Brush
Bitmap
Font
Region
Palette
Similarly, SFL defines a correspondent wrapper class for each of those GDI object types:
CGDIPen
CGDIBrush
CGDIBitmap
CGDIFont
CGDIRgn
CGDIPalette
All GDI Object wrappers in SFL derive from the CGDIObject<> class. This class is templatized by the specific handle type of the object it is wrapping. So for example, CGDIPen specializes CGDIObject<HPEN>, whereas CGDIRgn derives from CGDIObject<HRGN>.
CGDIObject<> encapsulates the common functionality applicable to all types of GDI objects.
Creation and Destruction
Each GDI object type has its own set of API calls used for creation of a new object. Each one of them takes its own set of creation parameters: the parameters necessary to create a new pen are different than the parameters required for a new font. For this reason, the declaration of the creation methods in the SFL GDI object wrappers differs from one class to another.
For example, the code in Example 87 creates a new font based on the font information given by the user using the font common dialog:
Example 87 – Creating a new font based on font information from font common dialog
LOGFONT m_lf;
CFontDialog dlg(&m_lf);
if (dlg.DoModal() != IDCANCEL) {
CGDIFont font;
font.CreateFontIndirect(&m_lf);
// Use font here to display some text
}
An effort has been made to make the name of the functions equivalent to their counterparts in the Win32 API. This allows you to use the Win32 API reference as a reference for the GDI wrapper classes.
Lifetime Management
CGDIObject<> derives from the class CHandleWrapper<>. This class controls the lifetime of the underlying handle.
SFL follows a simple ownership model for the relationship between instances of classes derived from CHandleWrapper and the handles they encapsulate. Under this model, multiple instances can encapsulate the same handle value, but only one of them should be considered the “owner” of the handle. It is the owner’s responsibility to destroy the handle appropriately when it is destroyed itself. When they are destroyed, objects that encapsulate a handle without ownership on it should not take any action at all with respect to the handle.
CHandleWrapper<> offers methods for attaching and detaching a handle from the wrapper instance. The attachment methods take an optional “ownership” boolean parameter, which determines whether this instance should take ownership of the handle being attached.
Consider the following code snippet:
 
CGDIPen myPen(hSomePen, true);
CGDIPen anotherPen(hSomePen, false);
Here, the variable myPen() takes ownership of the pen handle. When the instance referenced by that variable is destroyed, so is the GDI object. On the other hand, anotherPen() does not take ownership. It will only serve as a wrapper to invoke functions on the handle without affecting its lifetime.
It is the responsibility of the programmer to make sure that only one wrapper object has ownership of a handle at a given time. Under some circumstances, the SFL code has no way of knowing whether you have assigned ownership of a handle to more than one instance. This behavior is by design: keeping track of ownership outside of the instances would require having some kind of global map, an option we discarded in order to keep SFL lean. However, this means that the programmer must be aware of this possibility and take the necessary steps to avoid its occurrence. For example, if in the previous code snippet, the ownership parameter in the second line is true, it will cause a conflict of ownership between both instances of CGDIPen.
In general, the default for the ownership parameter is true. This is useful for those cases when an implicit attachment occurs, as in the following example.
 
CGDIPen AttachPen(HPEN hpen)
{
CGDIPen myPen(hpen);
return myPen;
}
When you return the CGDIPen object by value, a new temporary instance of the object is created and its copy constructor is invoked. Since the default parameter of the copy constructor instructs the temporary object to take ownership of the handle, the handle is not destroyed when the myPen variable goes out of scope at the end of the routine.
It is important to notice that the assignment operator (operator=) does not take an ownership parameter, since its signature is predetermined by the C++ language. Therefore, the convention has been adopted that direct assignment always transfers ownership of the handle.
Examples
The usage of GDI objects is very simple. Use these wrapper objects wherever you would traditionally use a plain handle such as HPEN or HBRUSH. You usually follow this process:
1. Create the object by passing the adequate parameters to one of its creation methods.
2. Select the object in a device context.
3. Call some GDI functions to generate some output.
4. Restore the old objects to the device context.
If your instance has ownership of the GDI objects you are using, you don’t have to worry about releasing the handle. This will occur automatically when the object goes out of scope.
For example, the code in Example 88 paints a line in the Highlight color designated by the user:
Example 88 – Painting a line in a user-designated color
void DrawHilite (
CGraphicsContext& dc,
CRect rcDraw
)
{
CGDIPen m_penHilite;
m_penHilite.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_BTNHIGHLIGHT));
CGDIPen penOld = dc.SelectObject(m_penHilite);
dc.MoveTo(rcDraw.left, rcDraw.top + 1);
dc.LineTo(rcDraw.right, rcDraw.top + 1);
dc.SelectObject(penOld);
}
Alternatively, you can create and initialize some objects as data members of some other object (for example, a window), and cache them there for use in the painting operations. This technique is helpful when you want to speed up the painting operations by creating all GDI objects at once at the beginning, or when your application has painting code spread over multiple routines, as in Example 89.
Example 89 – Initializing and caching objects as data members of another object
class CHighlighter
{
<...>
CGDIPen m_penHilite;
<...>
};
 
CHighlighter::CHighlighter ()
{
m_penHilite.CreatePen(PS_SOLID, 1,
::GetSysColor(COLOR_BTNHIGHLIGHT));
}
 
CHighlighter::Draw (
CGraphicsContext& dc,
CRect rcDraw
)
{
CGDIPen penOld = dc.SelectObject(m_penHilite);
dc.MoveTo(rcDraw.left, rcDraw.top + 1);
dc.LineTo(rcDraw.right, rcDraw.top + 1);
dc.SelectObject(penOld);
}