Home
Fractals
Tutorials
Books
My blog
My LinkedIn Profile

BOOKS i'm reading

Napoleon Hill Keys to Success: The 17 Principles of Personal Achievement, Napoleon Hill, ISBN: 978-0452272811
The 4-Hour Workweek: Escape 9-5, Live Anywhere, and Join the New Rich (Expanded and Updated), Timothy Ferriss, ISBN: 978-0307465351
The Fountainhead, Ayn Rand, ISBN: 0452273331

Alternative to MFC for GDI programming

Olivier Langlois, IT superstar coach, Dominate LinkedIn Formula author
by Olivier Langlois

Contents





OLIGDI features highlight

OLIGDI consists of the following classes:

  • ODrawObj
  • OBitmap
  • OPen
  • OBrush
  • OFont
  • ORgn
  • OIC
  • ODisplayInfo
  • ODC
  • OClientDC
  • OWindowDC
  • OPaintDC
  • OMemDC
  • OFlickerFreeDC
  • OMetaFileDC

OLIGDI does not claim to be a complete solution as only a small percentage of the hundreds of the GDI functions have been implemented. It would have been too tedious to implement wrappers for every function just to try out the concept my experiment wants to verify. However, the framework is laid out, and it should be very easy to add functions as needed.

The primary design requirement for this new class set is to keep the same API as MFC for compatibility purposes. With this requirement, it is easy to modify code from using MFC objects to OLIGDI objects. All that is needed is to change the object types in the variable declaration statements and recompile. The second requirement is to remove all the unwanted features from MFC. That includes:

  • m_hAttribDC
  • virtual functions
  • handle maps

Also, OLIGDI introduces two new features borrowed from the book Windows++ written by Paul Dilascia. The first improvement is that, with MFC, if you want to reuse an object to store a different GDI handle, you must first explicitly call DeleteObject(). This is error prone as if you forget to make this call, it will create a GDI resource leak. In OLIGDI, this is done explicitly in every creation function:

class OIC
{
public:
    /*
     * Each type of drawing object has an ID, used as offset to store
     * handle in a table.
     */
    enum WHICHOBJ { SELPEN=0, SELFONT, SELBRUSH, SELBITMAP,
                    NDRAWOBJ };
protected:
    HDC m_hDC;                  // Windows handle to DC
    BOOL m_del;

    HANDLE m_origObj[NDRAWOBJ]; // original drawing objects
    int    m_anySelected;       // whether any new objects are selected
// Other stuff omitted
};

/*
 * OIC::select function
 *
 * Protected method to select a display object
 * Destroys old selected object if required.
 * "which" specifies whether object is a pen, brush, etc.
 * "del" specifies whether to delete this object.
 */
HGDIOBJ OIC::select(WHICHOBJ which, HGDIOBJ h)
{
    HGDIOBJ old;

    WINASSERTD(h);
    old = ::SelectObject(m_hDC, h);
    WINASSERTD(old && old != HGDI_ERROR);

    if( m_origObj[which] == NULL )
    {
        m_origObj[which] = old;
        m_anySelected++;
        WINASSERTD( m_anySelected <= NDRAWOBJ );
    }
    else if( m_origObj[which] == h )
    {
        m_origObj[which] = NULL;
        m_anySelected--;
        WINASSERTD( m_anySelected >= 0 );
    }

    return old;
}

OIC::~OIC()
{
    if (m_hDC)
    {
        restoreSelection();
        if( m_del )
        {
            LASTERRORDISPLAYD(::DeleteDC(m_hDC));
        }
    }
}

/*
 * OIC::restoreSelection function
 *
 * Restore selected display objects (pens, brushes, etc.).
 */
void OIC::restoreSelection(void)
{
    for (int i = 0; m_anySelected && i < NDRAWOBJ; i++)
    {
        restoreSelection((WHICHOBJ)i);
    }
}

inline void OIC::restoreSelection(WHICHOBJ which)
{
    if( m_origObj[which] )
    {
        WINASSERTD(m_hDC != NULL);
        ::SelectObject(m_hDC, m_origObj[which]);
        m_origObj[which] = NULL;        // don't restore twice!
        m_anySelected--;
        WINASSERTD( m_anySelected >= 0 );
    }
}

There is one situation you have to be cautious about. If the DC object and the selected GDI objects are located on the stack, I believe that the destructors call order will be the reversed from which the variables have been declared:

void foo(void)
{
    OPen p(PS_SOLID,1,RGB(255,0,0));
    OClientDC dc(hwnd);
    dc.SelectObject(&p);
    // Ok, OClientDC destructor will be called first
}

void foo2(void)
{
    OClientDC dc(hwnd);
    OPen p(PS_SOLID,1,RGB(255,0,0));
    dc.SelectObject(&p);
    // Boom, the pen will be destructed before being unselected
}

To avoid this type of problems, restoreSelection() can be called explicitly at the end of the function.

A word of warning, it is not a good idea to use OLIGDI if you are planning to share code between the painting routine and the print code. Unless you write your own print previewing code on top of OLIGDI, MFC provides a special class called CPreviewDC that substantially alters the CDC behavior for print previewing, and if you want to use MFC in that area, you will not be able to reuse the code written for OLIGDI. That being said, it might still be advisable to use OLIGDI if you have painting performance problems, if you consider that the window will be painted much more often than the number of times a document will be printed.



Page 1 2 3

Home :: Fractals :: Tutorials :: Books :: My blog :: My LinkedIn Profile :: Contact