Rss Feed
Tweeter button
Facebook button
Technorati button
Reddit button
Myspace button
Linkedin button
Webonews button
Delicious button
Digg button
Flickr button
Stumbleupon button
Newsvine button

Problems when subclassing controls

By , November 19, 2010 2:56 pm

I’m developing a graph control for use in our Memory Validator software tool.

Graph custom control

Tracking a mouse
One of the requirements was that we need to monitor mouse move messages on the control so that we can update some on-the-fly statistics related to where the mouse cursor is in the graph. No problem, add a ON_WM_MOUSEMOVE() message map entry and an OnMouseMove() method to the class (I’m using MFC) and that should be it. Easy.

Transparent hit tests
That’s what I thought until it didn’t work. Very confusing. Checked some other controls we’ve written and they all work OK. What is different about this control? After some head scratching and testing I decided to run Spy++ to monitor the windows messages being sent to our custom control. What a surprise that was. The only messages the custom control was generating were WM_NCHITTEST messages to see if the mouse cursor was in the control. Rather than return HTCLIENT or something similar the control was returning HTTRANSPARENT. Why would it do that?

Why is it transparent?
The reason the control is returning HTTRANSPARENT is because I’ve used a CStatic frame control to place my control in the resource editor and then I subclass that CStatic with my custom control. If I don’t override OnNcHitTest() method and add WM_ON_NCHITTEST() to the message map then I get the hit testing functionality from the CStatic. The CStatic’s job is to display a frame and let other control be displayed inside it (even though they are not child controls). For CStatic to do that it needs to return HTTRANSPARENT.

Getting mouse move messages
To fix this problem is straightforward.

  • Add a WM_ON_NCHITTEST() entry to the custom control message map.
  • Add a OnNcHitTest() method to the custom control and make it return HTCLIENT;

Once these are in place, the hit test works and then mouse move messages are sent to the custom control.

Conclusion
Sometimes the reason you are not getting messages is because something else is setup incorrectly. In this case the lack of correct hit testing was affecting many other messages, of which the mouse move message was one. I hope this article will help prevent you wasting any time on a bug like this.

Share

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

By , November 16, 2010 11:44 am

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

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

The custom control
I’m writing a custom control 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 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.

Share

Panorama Theme by Themocracy