Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Fixing the toolbar button position bug for MFC CToolBar

0.00/5 (No votes)
16 Jul 2007 1  
An article on a bug of MFC CToolBar that may cause incorrect tooltips to be shown for a toolbar button

Screenshot - ScreenShot.png

Introduction

There is a bug in MFC's CToolBar class while showing tooltips for toolbar buttons. When calculating which button is to show the tooltip, the positions for toolbar buttons are moved 1 pixel to the right. As a result, when the mouse cursor is pointed to the left-most pixel of a toolbar button, the tooltip and status bar tip for the button on its left are actually shown!

It is quite easy to reproduce this bug. Create an MFC project, either SDI or MDI, which by default includes a toolbar. Leave all other project settings at their default values. Run the program and move the mouse cursor carefully to the left-most pixel of the "Open" toolbar button. CToolBar will show the tooltip for the "New" button, as demonstrated in the image above. The bug is quite misleading.

A number of well-known, MFC-based pieces of software are affected by this bug, including Spy++ and Dependency Walker.

Background

After a dig into the CToolBar source codes, I found the cause of this bug. When a tooltip may be needed for the toolbar, the framework calls CToolBar::OnToolHitTest() to determine which button the cursor is on. The source code of this method is shown below, copied from VC++ 6.0. The VC++ 8.0 code is nearly unchanged except that the return type int is changed into INT_PTR:

int CToolBar::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
    ASSERT_VALID(this);
    ASSERT(::IsWindow(m_hWnd));

    // check child windows first by calling CControlBar

    int nHit = CControlBar::OnToolHitTest(point, pTI);
    if (nHit != -1)
        return nHit;

    // now hit test against CToolBar buttons

    CToolBar* pBar = (CToolBar*)this;
    int nButtons = (int)pBar->DefWindowProc(TB_BUTTONCOUNT, 0, 0);
    for (int i = 0; i < nButtons; i++)
    {
        CRect rect;
        TBBUTTON button;
        if (pBar->DefWindowProc(TB_GETITEMRECT, i, (LPARAM)&rect))
        {
            ++rect.bottom;    // Buggy line

            ++rect.right;    // Buggy Line

            if (rect.PtInRect(point) &&
                pBar->DefWindowProc(TB_GETBUTTON, i, (LPARAM)&button) &&
                !(button.fsStyle & TBSTYLE_SEP))
            {
                int nHit = GetItemID(i);
                if (pTI != NULL && pTI->cbSize >= sizeof(AFX_OLDTOOLINFO))
                {
                    pTI->hwnd = m_hWnd;
                    pTI->rect = rect;
                    pTI->uId = nHit;
                    pTI->lpszText = LPSTR_TEXTCALLBACK;
                }
                // found matching rect, return the ID of the button

                return nHit != 0 ? nHit : -1;
            }
        }
    }
    return -1;
}

The lines involved with the bug are commented "Buggy Line." There seems to be no reason to increase rect.bottom and rect.right by 1 pixel, and MFC provided no comments for doing so. These lines cause incorrect button RECT to be retrieved and so wrong tooltips are shown. :(

Using the code

To fix the bug, simply create a class derived from CToolBar and overwrite the CToolBar::OnToolHitTest() method. Copy the original implementation of CToolBar::OnToolHitTest() and comment out the two buggy lines. Then replace CToolBar with the new class everywhere it is used in your project. The attached CFixedToolBar is an example.

There are still some things to do. If you compile the new class you will now get an error saying that AFX_OLDTOOLINFO is undefined. It is actually defined in <afximpl.h>. The code containing that is to make sure that a compatible version of TOOLINFO is passed in. So, instead of providing a definition of AFX_OLDTOOLINFO, it is sufficient to simply change sizeof(AFX_OLDTOOLINFO) to 40, i.e. the size of the AFX_OLDTOOLINFO structure. Now everything is OK. Enjoy. :)

History

  • 16 July, 2007 -- Original version posted

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