Don’t try this at home – custom control time sink

By Stephen Kellett
16 November, 2010

There are times when writing a custom control will waste your time like nothing on earth.

I’m going to share with you a particularly painful timewaster that bit my behind last night.

The custom control

I’m writing a custom control for use with Memory Validator that will take data from an arbitrary data source via a data provider and then display this data as a graph. The screenshots in this article are not from the finished control, just some mockups to test various ideas I am experimenting with. As you can see it is a graph with a pastel block colour and a harder solid outline. All configurable of course.

Graph custom control

The problem

The problem was first noticed when I wanted to change some settings in the host program. I clicked on the toolbar button to open the settings dialog and nothing happened. Button depressed, but nothing. Clicking elsewhere on the screen and I get a beep – hmmm, not allowed. GUI is totally frozen.

It felt like a deadlock. But all the code I’d written had its multithreading code locked down and everything I’m doing is on the GUI thread. No chance of a deadlock. Still I go investigating, several times in the debugger, use Thread Validator and Thread Status Monitor. No sign of a deadlock. Thats odd.

Try a different tack. Comment out the subclassing of the graph controls – effectively disabling them. Does the settings dialog display? Yes it does. OK, so it is something to do with the custom control. But what? Identify by process of elimination. Comment out all message map handlers except erase background and paint. Do I still get the problem? Yes, OK, so the problem is with erase background or paint. Turns out the problem is in paint. I’ll show you the code below.

void CSvlGraphCtrl::OnPaint()
{
    if (GetSafeHwnd() == 0)
        return;

    CRect    rCl;

    GetClientRect(&rCl);

    CClientDC    dc(this);
    CMemDC     dcMem(&dc, rCl);

    OnDraw(&dcMem);
}

Can you spot the problem?

I’ve forgotten to call CWnd::OnPaint() which sets the “I’ve been drawn bits” inside the class and causes various other drawing-related operations to happen. Without this call, the settings dialog I’ve been trying to interact with (which is displayed on top of this graph custom control in this case) does not get drawn properly – or at all. So the settings dialog which is nothing to do with the custom control is displayed but doesn’t get drawn due to an incorrect cascade of events caused by failing to call CWnd::OnPaint() from the custom control’s OnPaint() handler.

The code should look like this (added line in bold)

void CSvlGraphCtrl::OnPaint()
{
    CWnd::OnPaint();

    if (GetSafeHwnd() == 0)
        return;

    CRect    rCl;

    GetClientRect(&rCl);

    CClientDC    dc(this);
    CMemDC     dcMem(&dc, rCl);

    OnDraw(&dcMem);
}

Conclusion

Sometimes the bug isn’t what you think it is. I started thinking this was a deadlock as the GUI appeared to freeze (pressing ALT caused the display to fix itself, but I found that by accident several hours later) but the fix was a missing call to a base class method. I’ve been writing MFC for years and yet this trivial bug eluded me for some time.

Hopefully sharing this with you will help prevent you from making the same mistake.

Fully functional, free for 30 days