Showing Tooltips/Icons for Status Bar Panes

Class to show tooltips and icons for individual panes and to allow changing of text and background color of individual panes.

Environment: VC6

Contents

Introduction

Of late, I had seen some questions going back and forth in the forums about handling tooltips for status bar panes. Hence, I sat down to put together a class to simplify this process to make it easy to support this in a MFC framework.

The class, which will do this, is MMStatusBar, derived from MFC’s CStatusBar. One can use MMStatusBar to do any of the following:

How to go about implementing each of these using the MMStatusBar class is explained below.

How to…

Miscellaneous Public Methods of MMStatusBar

Apart from the CStatusBar public methods, MMStatusBar has a few more helper methods that can be useful.

  • void SetDelayTime(UINT uDelayTime);
  • This method can be used to set the tooltip delay time. This is the time (in milliseconds) for which the mouse has to be over a pane before the tooltip can appear (if the pane has tooltip text).

  • void SetPaneTextColor(int nIndex,COLORREF crTextColor);
  • This method can be used to set the color of the text of a particular pane. It works by sending a SB_SETTEXT message to the statusbar with the SBT_OWNERDRAW set. The drawing is handled in the the MMStatusBar's DrawItem method.

  • void SetPaneTextBkColor(int nIndex,COLORREF crTextBkColor);
  • This method can be used to set the color of text background of a particular pane. It works by sending a SB_SETTEXT message to the statusbar with the SBT_OWNERDRAW set. The drawing is handled in the the MMStatusBar's DrawItem method.

Miscellaneous Public Methods of MMStatusCmdUI

  • virtual void Enable(BOOL bOn);
  • To enable or disable a pane. The pane text will not appear in that case.

  • virtual void SetCheck(int nCheck);
  • If nCheck is non-zero, will make the pane pop out.

  • virtual void SetWidth(int cxWidth);
  • Can be used to set the pane width in pixels.

MMStatusBar overrides the OnUpdateCmdUI method and uses this to pass an UPDATE_COMMAND_UI message to each of the panes.

How to Integrate MMStatusBar into Your Project

  1. Include MMStatusBar.cpp and MMStatusBar.h into your project.
  2. In the Mainframe, replace the CStatusBar with MMStatusBar.
  3. Depending on how you would want the tooltips to be set, you could use one of the approaches as listed in How to use MMStatusBar class.
  4. If you are using MMStatusBar in a dialog, the following knowledge base article will be of help: KB 123158.

Implementation Details of the MMStatusBar Class

How does MMStatusBar class do all these? Let us take it one by one.

Creation: The MMStatusBar class traps WM_CREATE and creates the m_oToolTip tooltip control and activates it.

Setting tooltip text and icons: In its SetIndicators method, MMStatusBar calls CStatusBar::Setindicators to let the baseclass do the preparation of the panes. It then checks to see whether it was passed a lpTooltipArray, and if so, adds a tool to the tooltip control specifying the pane rectangle and the ID for tooltip text as shown below. That makes the tooltip control show the tooltip whenever the mouse hovers over and stays within the rectangle.


for(int i =0;i&ltnIDCount;i++) // each (new) indicator
{
CRect oRect;
GetItemRect(i,&oRect);
UINT uID = lpIDArray[i];
UINT uIDText = lpIDToolTipArray[i];
if (uID != ID_SEPARATOR && // have indicator and
uIDText != 0) // want tooltip text for it
VERIFY(m_oToolTip.AddTool(this,uIDText,oRect,uID));
}

Icons are added in a similar way. Note that when there is an icon associated with a pane, one would have to add the icon's width to the pane width and set the new pane width as is done below.


for(int i =0;i&ltnIDCount;i++) // each (new) indicator
{
UINT uID = lpIDArray[i];
if (uID != ID_SEPARATOR && // have indicator and
lphIconArray[i] != NULL) // want icon
{
GetStatusBarCtrl().SetIcon(i,lphIconArray[i]);
int nWidth;
UINT nID,nStyle;
GetPaneInfo(i,nID,nStyle,nWidth);
SetPaneInfo(i,nID,nStyle,nWidth +
GetSystemMetrics(SM_CXSMICON));
}
}

Considerations during resizing: When the status bar is resized, the toolrectanges have to be recomputed so the tooltips appear in the proper panes. The same scenario exists for cases when the pane width changes for some reason. It may be when one sets a new pane text or when an icon is shown and removed. The MMStatusBar::Redraw method does this recomputing. It loops through all the panes, gets the pane rectangles, and sets the new tool rectangles.


void MMStatusBar::Redraw()
{
CToolInfo oToolInfo;
memset(&oToolInfo,0,sizeof(TOOLINFO));
oToolInfo.cbSize = sizeof(TOOLINFO) ;

CRect oRect;
UINT uId;
for(int i = 0 ; i < m_nCount ; i++)
{
uId = GetItemID(i);
GetItemRect(i,&oRect);

oToolInfo.hwnd = this->GetSafeHwnd();
oToolInfo.uId = uId;
oToolInfo.rect.left = oRect.left;
oToolInfo.rect.right = oRect.right;
oToolInfo.rect.top = oRect.top;
oToolInfo.rect.bottom = oRect.bottom;

m_oToolTip.SendMessage(TTM_NEWTOOLRECT,0,(LPARAM)
(TOOLINFO*)&oToolInfo);
}
}

The Redraw method is called from MMStatusCmdUI::SetWidth for this exact same reason. Any change in pane width should result in a recomputing of the tool rectangles.

How does MMStatusBar enable pane texts, tooltips, and icons to be updated through UPDATE_COMMAND_UI?

MMStatusBar overrides the OnUpdateCmdUI() virtual method. This method gets called by the framework during idle processing. So, I figured out that this would be a good place to handle updating the tooltips and pane texts. In this handler, MMStatusBar will instantiate a MMStatusCmdUI object and set its values. The most important thing here is state.m_pMenu = (CMenu*)MM_STATUS_PANE_UPDATE. This is used by the UPDATE_COMMAND_UI handler to know that this is a status bar update and so it can handle it appropriately. The reason for doing this is that if one has a menu with the same ID as the pane, UPDATE_COMMAND_UI would come in two forms for the same command ID: one when the status bar needs to be udpated, and another when the menu status for the command ID needs to be updated.


void MMStatusBar::OnUpdateCmdUI(CFrameWnd* pTarget,
BOOL bDisableIfNoHndler)
{
MMStatusCmdUI state;
state.m_pOther = this;
state.m_pMenu = (CMenu*)MM_STATUS_PANE_UPDATE;
state.m_nIndexMax = (UINT)m_nCount;
for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;
state.m_nIndex++)
{
state.m_nID = GetItemID(state.m_nIndex);

// allow the statusbar itself to have update handlers
if (CWnd::OnCmdMsg(state.m_nID, CN_UPDATE_COMMAND_UI,
&state, NULL))
continue;

// allow target (owner) to handle the remaining updates
state.DoUpdate(pTarget, FALSE);
}

// update the dialog controls added to the status bar
UpdateDialogControls(pTarget, bDisableIfNoHndler);
}

How does MMStatusBar generate WM_COMMAND on double-clicking in any pane? To acheive this, MMStatusBar overrides the virtual function OnChildNotify(). If the notification received is a NM_DBLCLK, it gets the command ID for the item and then posts a WM_COMMAND message to the statusbar's parent window.

And the one single line which makes all this possible... is below. For tooltips to show, all mouse events on the statusbar have to be relayed to the tooltip control. Otherwise, the tooltip control will not be aware of when to show tooltips. This is because we do not use the TTF_SUBCLASS flag. So, MMStatusBar overrides another virtual function, PreTranslateMessage, and relays all mouse events to the tooltip control.


BOOL MMStatusBar::PreTranslateMessage(MSG *pMsg)
{
switch(pMsg->message)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
m_oToolTip.RelayEvent (pMsg);
default :break;
}
return CStatusBar::PreTranslateMessage(pMsg);
}

I hope this provided a small insight to the inner workings of MMStatusBar. I have tried to provide as many comments as possible in the source code. The code can be used with UNICODE or non-UNICODE settings.

About the Included Demo Project

The demo project included shows a way of implementing all of those listed above.

  • Pane 1 shows a static pane text and a static tooltip.
  • Pane 2 shows a dynamic pane text and a dynamic tooltip. To change the pane text and/or tooltip, go to menu Pane->Pane 2 Properties and set the texts.
  • Pane 3 shows a static text with an icon and a dynamic tooltip. The icon can be shown and hidden by toggling the menu Pane->Icon in Pane 3.
  • On double-clicking on Pane 1, Mainframe traps the command to show a message box.
  • To change the tooltip delay time, go to menu Pane->Delay Time and set the new delay time for the tooltips.
  • To change the text color of Pane 2, go to menu Pane->Change Pane 2 Color and select the new text color for the pane.
  • To change the text background color of Pane 2, go to menu Pane->Change Pane 2 Bkgnd Color and select the new bkgnd color for the pane.

Acknowledgements

Thanks to all the CodeGuru readers for their encouraging comments and suggestions. These comments encouraged me to provide an update with any bug fixes and improvements.

  • Update 1. Thanks to Roberto Cagnetti, Mark Williams, Robin Bannister, and Jose Ramos for providing their useful comments, which I have attempted to incorporate in this update. They were mainly bug fixes.
  • Update 2. Thanks to zephyrer and matro for their suggestions. I have tried to address their concerns in this update. zephyrer's suggestion lead to a feature enhancement to the MMStatusBar class to support setting of text color and background color for individual panes.

More by Author

Previous article
Next article

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read