Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

A Generic Tool Tip Class


April 2001/A Generic Tool Tip Class


You might call them tool tips, fly-by hints, balloon help, or something else. Whatever you call them, one of the most useful user interface additions of the last several years is the ability to display a small amount of explanatory text when the mouse hovers over each part of the user interface. Tool tip text can explain what a toolbar button does, explain what will happen when you press the OK button, and so on. That short text hint is often enough so that the user can proceed without looking something up in online help, or consulting a manual.

CToolBarCtrl is a good example of an attempt to automate the coding for tool tip display. If you’re using third-party user interface code, however, you may face some extra work in supporting tool tips. At a minimum, you will have to study the third-party documentation or, ideally, hope to find a sample. Then you may have to carefully insert scattered processing in your code to trap messages, fill structures, relay events, etc. A software manufacturer may or may not provide tool tip support. But if it does, it probably has its own tool tip function, like that in Stingray’s Objective Grid.

This is the situation I met recently while developing a slightly larger application, where we needed to put tool tips on a GPS’s map view, on a Stingray Grid, and on some controls without tool tip support. So, I decided to create a generic CTipWnd class for all these uses. CTipWnd works in an intuitive way to show tips without you having to care about messages and structures. After attaching a CTipWnd object to its owner window, I just make one call to set a tip text wherever I want, even without passing coordinates. CTipWnd can set tip attributes such as font, color, and duration, and provides hit testing. You can pick it up and use it with just a few minutes work.

About Tool Tip Programming

A tool tip is a small popup window that usually displays a single line of text describing the purpose of a tool in an application. A tool tip appears when a user positions the mouse cursor on a tool and leaves it there for a certain interval. The tip disappears when a given duration expires or the user moves the cursor off the tool. In this context, a “tool” can be a window, a control, or a usually rectangular object within a window’s client area. The tool tip is available under Windows 95, NT 3.51, and later.

MFC supports three classes for tool tip programming: CWnd, CToolBarCtrl, and CToolTipCtrl. To use tool tip support in CWnd, couple its member functions with your CWnd-derived class for enabling, message filtering, canceling, or hit testing. You must handle the TTN_NEEDTEXT notification with its TOOLTIPTEXT structure. But much worse, all these are not clearly documented, and you’ll need trial and error to get familiar with them. If you’re not careful, these separated tooltip handlers can mess up your tidy object-oriented design. CToolTipCtrl does encapsulate the tool tip attributes and operations in one class, but its main use seems to lie in Windows common controls.

Third-party software classes like Stingray’s CGXGridCore often provide their own version of tool tip support, with proprietary functions and definitions. That’s another reason programs can end up with similar, but not identical, tool tip code spread across multiple classes and modules. It would be helpful if a single generic tool tip class could handle all these cases.

My Solution

In this article, I’ll supply a class called CTipWnd that lets you add tool tips to your application easily. Figure 1 shows a sample using CTipWnd in different ways. I made four tips appear simultaneously, just for demonstration, as I can set a long display duration or even let a tip stay displayed after the cursor leaves the “tool.” The demonstration program shown in Figure 1 mainly demonstrates three separate features.

The first tip shown (“This is the Second Demo Guide...”) is related to a CTipWnd object bound to the dialog window and applies to any area where WM_MOUSEMOVE can be trapped. To display this tip, move the mouse over the top three static controls or the group box.

The second approach pertains to the other two tips, which are the tip shown coordinates (10, 20) and the tip below the Disable Date button. They represent the case where the parent window won’t get the necessary information, because the child window is eating the WM_MOUSEMOVE messages. Some Windows controls and third party components work this way. Here, I illustrated this condition by using two derived classes from CListBox and CButton, each with an embedded CTipWnd member.

The tip shown with the date at the bottom is created by a CTipWnd object without a timer. You can press the Date button to enable or disable this tip, like turning on or off a message box. The similarity in all three approaches is that you just call CTipWnd’s member Create() at initialization and later call SetTipText() anywhere that’s convenient.

Implementing CTipWnd

tipwnd.h (Listing 1) and tipwnd.cpp (Listing 2) show how CTipWnd is implemented. Most of its members have self- explanatory names. The four tip attributes (font, background color, duration, and style) can be manipulated by the four corresponding “set” functions. The default attributes are defined in tipwnd.h. As for style, I define TWS_SHOWABOVE to show a tip above the cursor and TWS_ROURDRECT to draw a round rectangle; the default is a tip displayed below the cursor and in a normal rectangle. You could extend this code to add functions to inspect the given attributes, or to add more attributes and styles.

I decided to keep the tip window around for the lifetime of the corresponding CTipWnd object; I make the tip appear or disappear by showing or hiding the tip window. However, I only create the associated timer when the tip window is actually visible, which happens in CTipWnd::SetTipText(). I kill the timer when I hide the tip window, which can happen in OnTimer(), OnClose(), or CancelTipWnd(). m_nSecCount is a seconds counter and also acts as a flag that indicates whether the tip window is currently visible or hidden.

m_idTimer contains the timer ID. I chose to require the caller to pass in a timer ID when calling CTipWnd::Create(), in addition to the owner window pointer. This is extra work for the caller, but lets you use an existing timer if you have created one already. You would most likely create a single timer that gets used sequentially by any number of CTipWnd objects. This parameter defaults to zero, and in that case, I create a tool tip without a timer. Create() sets the default attributes and creates a tip window. I could let AfxRegisterWndClass() generate a tip window class name, but for simplicity, I use the predefined “STATIC” class.

CTipWnd offers three overloaded functions named SetTipText() for displaying tips. SetTipText(CString strTip, CPoint point) implements all the basic processing. It simply returns if the cursor position has not changed; note that you can get more than one WM_MOUSEMOVE message even if the mouse is not moving. If the CTipWnd object has an associated timer, this version of SetTipText() sets the timer in seconds and gets the next duration. It then calls Invalidate(), which in turn invokes OnPaint() to do all the drawing chores, and then moves the tip window to the desired position, on top and non-active, where m_size is the tip window size calculated in OnPaint().

The second version, SetTipText(CString strTip, CWnd* pWnd), is called to set tips without passing coordinates, since I can retrieve the cursor position from Win32’s GetCursorPos(). The second parameter, pWnd, often points to a child window, and thus I combine the hit testing with a Boolean value returned. I make pWnd default to NULL to bypass the position checking if you don’t need it.

The third version, SetTipText(CWnd* pWnd), can be called without supplying a tip text, and it uses pWnd’s window text instead. Table 1 summarizes the four different ways you might invoke SetTipText().

Using CTipWnd

The source code to the demonstration program shown in Figure 1 is in tipdemo.h and tipdemo.cpp, included with this month’s code archive. As I mentioned before, in the main dialog’s OnInitDialog(), I first create two CTipWnd objects by:

m_tipDlg.Create(this, 1);
m_tipDate.Create(this);

where m_tipDlg with the timer ID 1 is for the static controls and the group box, and the non-timer m_tipDate is used to toggle the date tip. A small trick is that I call m_tipDate.SetTipText() in OnMove() instead of in OnDateBtn() directly so that I can make the date tip stick on the dialog when it is moved.

I derive the coordinate tracking window CPositionWnd from CListBox and dynamically subclass it ready for processing the WM_MOUSEMOVE message. This is a good example of how to use CTipWnd in such Windows controls or third party components. I let m_tipPos be the member of CPositionWnd, and in its constructor call:

m_tipPos.Create(this, 3);
m_tipPos.SetTipStyle(TWS_SHOWABOVE);
m_tipPos.SetTipDuration(1);
m_tipPos.SetTipBkClr(RGB(255, 255, 0));

which gives m_tipPos the timer ID 3, duration one second, background dark yellow, and style of a tip above the cursor. In CPositionWnd::OnMouseMove(), I do the following:

if (m_tipPos.PtInOwnerWnd(point, 1))
    m_tipPos.SetTipText(str, point);
else
    m_tipPos.CancelTipWnd();

where point is the position passed by OnMouseMove(). You may wonder why I did hit testing here. Note that the owner of m_tipPos is the object of CPositionWnd, rather than the dialog parent, so that CPositionWnd::OnMouseMove() only can detect the mouse cursor in this tip owner’s window. Therefore, I leave a one-pixel margin around this window for border checking in PtInOwnerWnd().

Go back to tipwnd.cpp (Listing 2) and take a look at the two hit testing functions. PtInOwnerWnd(CPoint point, int iDeflate) checks whether a position is within the tip’s owner window. Its second parameter, iDeflate(default to zero) can be used to deflate the window rectangle a few pixels as I did above. Another function, PtInChildWnd(CWnd* pWnd, CPoint point, BOOL bClient) can be used by the parent window to check whether a cursor is in its child window pointed by the first parameter pWnd. As an example, I have called this function in SetTipText(CString strTip, CWnd* pWnd).

Conclusion

I have described the implementation of a generic tool tip class CTipWnd. You can easily decorate CTipWnd with more attributes or styles to suit your own purposes. More advanced tinkering could yield multiple-line text, hit testing for different shapes, processing mouse-click messages, and loading icons or bitmaps, depending on your requirements.

One point I haven’t spent much time on is how to determine the exact size of a cursor. Figure 2 shows a bitmap of arrow1.cur. The size of a cursor is usually 32 pixels by 32 pixels which is also the result from GetSystemMetrics() by SM_CXCURSOR and SM_CYCURSOR. In fact, the lower end of the cursor display does not always fit the bottom of its bitmap. This wasn’t the main point of the article, so to keep things simple, I simply subtract 12 from the vertical size of the cursor if a tip should be below it; refer to the icyOffset assignment in SetTipText() in tipwnd.cpp (Listing 2).

Finally, as a bonus, CTipWnd can be easily adapted to a 16-bit version so that adding the tool tip support to a Win16 application becomes much simpler. Except for a few functions like CFont::CreatePointFont() and CRect::DeflateRect(), most others are compatible with the 16-bit version of VC++.

Zuoliu Ding works at Comarco Wireless Technologies in Irvine, CA, with field measurement software for communication networks. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.