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));
int nHit = CControlBar::OnToolHitTest(point, pTI);
if (nHit != -1)
return nHit;
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;
++rect.right;
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;
}
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