65.9K
CodeProject is changing. Read more.
Home

Fixing the toolbar button position bug for MFC CToolBar

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (5 votes)

Jul 16, 2007

2 min read

viewsIcon

85227

downloadIcon

1514

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