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

Neat Tooltip for ComboBox

0.00/5 (No votes)
30 Jul 2012 1  
Details of how to make a tool-tip for combobox
In this article, you will find out about the details of making a tool-tip for a combobox. The enhanced class CTTCombobox is derived from the MFC ComboBox class.

Introduction

I have always been perplexed by the question "which's the tool-tip associated with ComboBox?". On the internet, I searched a lot for samples about this, but the samples looked so complex, and some of the samples wrapped the ComboBox using separate controls CListbox, CEdit, CButton, requiring to generate many extra classes for the ComboBox, resulting in lots of work to maintain the ComboBox. A couple of samples used hooks for window subclassing so that all the messages go to a new WindowProc (window procedure) before going to the ComboBox, ummm..., that's very useful. But that is still not enough. Thus, based on these samples, I wrapped an enhanced class CTTCombobox. According to the expectation, they use the new WindowProc to hold the ListBox's messages and to do the tool-tip things. By the way, for the enhanced class CTTCombobox, the source files contain only TTCombobox.h and TTCombobox.cpp, which is very easy to embed into your VC++ project.

Noteworthy

The class CTTComboBox isn't wrapped using separate controls CListbox, CEdit, CButton.

To Use

  1. The class CTTComboBox is derived from an MFC class CCombobox. You can immediately use the class CTTComboBox as well as derive new classes from it.
  2. By default, tool-tip is allowed for the ComboBox (instance of the class CTTComboBox); to enable/disable the tool-tip, you need to perform the SetToolTipp function before creating or subclassing the ComboBox; for details, see the attached demo project.
  3. Use the SetToolTipDelay function to set up the appropriate delay time for showing the tool-tip window.

Implementation

As mentioned above, by default, tool-tip is allowed, means m_isEnableTool is TRUE; see the code sample given below:

void  CTTComboBox::PreSubclassWindow() 
{
 CComboBox::PreSubclassWindow();

 // If the combo-box isn't dynamically 
 // subclassed that should get rid of 
 // the codes as follows and the codes should be 
 // placed in the OnCreate function.
 if (m_isEnableTool == TRUE)
 {
  CreateTooltipWnd();
  InstallHookForListboxAndEditbox();
 }
}

We have called the CreateTooltipWnd function to create a tool-tip window. (Note: All the ComboBoxes with the same class CTTComboBox share a tool-tip window.) Now, we have to install a hook for subclassing the list-box and the edit-box portion of the ComboBox, see the InstallHookForListboxAndEditbox function given below:

void CTTComboBox::InstallHookForListboxAndEditbox()
{
 // Get the list-box/edit-box within the combobox 
 // and set up hooking with it.
 // Note: this only for Win98 or WinNT5.0 or later, 
 // However you must defined WINVER >= 0x0500 
 // within the project
 COMBOBOXINFO cbi;
 ZeroMemory(&cbi, sizeof(COMBOBOXINFO));
 cbi.cbSize = sizeof(COMBOBOXINFO);
 ::GetComboBoxInfo(m_hWnd, &cbi);
 m_rcButton = cbi.rcButton;

 if (cbi.hwndList)
 {
  m_hWndList = cbi.hwndList;

  // Set up hooking for the list-box within the combobox
  // Note: Refer to MSDN, In Windows NT/2000, You cannot 
  // change this attribute(GWL_WNDPROC) 
  // if the window(i.e, list-box) does not belong to the 
  // same process as the calling thread.
  WNDPROC oldListWndProc = (WNDPROC)::SetWindowLong(cbi.hwndList, 
                         GWL_WNDPROC, (DWORD)HookListboxWndProc);
  // Add the old hook(i.e, hook associated 
  // with window) to the map
  m_mapWndProc.SetAt(cbi.hwndList, oldListWndProc);
 }
 
 if (cbi.hwndItem)
 {
  m_hWndEdit = cbi.hwndItem;

  // Set up hooking for the edit-box within the combobox
  // Note: Refer to MSDN, In Windows NT/2000, You cannot 
  // change this attribute(GWL_WNDPROC) 
  // if the window(i.e, edit-box) does not belong to the 
  // same process as the calling thread.
  WNDPROC oldEditWndProc = (WNDPROC)::SetWindowLong(cbi.hwndItem, 
                         GWL_WNDPROC, (DWORD)HookEditboxWndProc);
  // Add the old hook(i.e, hook associated 
  // with window) to the map
  m_mapWndProc.SetAt(cbi.hwndItem, oldEditWndProc);
 }
}

The GetComboBoxInfo function gets the handles to the list-box and the edit-box, then the SetWindowLong function creates the list-box and the edit-box subclasses, causing the system to call the new window procedure instead of the previous one, a trick, that's all previous (old) window procedures have been added to the map. Later, we will reuse the old window procedure in the new window procedure, such as the window procedure - HookListboxWndProc with the list-box:

LRESULT CALLBACK CTTComboBox::HookListboxWndProc(HWND hWnd, 
        UINT message, WPARAM wParam, LPARAM lParam)
{
 CListBox* pList = (CListBox*)CWnd::FromHandle(hWnd);

 if (message == WM_MOUSEMOVE)
 {
  WORD xPos, yPos;
  xPos = LOWORD(lParam);
  yPos = HIWORD(lParam);
  CPoint point(xPos, yPos); 
  CRect rcClient;
  ::GetClientRect(hWnd, &rcClient);
  if (rcClient.PtInRect(point))
  {
   // Handle mouse move event that may 
   // show a tool-tip window...
   CTTComboBox::HandleListboxMouseMove(pList, 
                                 wParam, point);
  }

  if (!m_isEnter)
  {
   // Tracking the mouse event which 
   // are hovering and leaving.
   OnTrackMouseEvent(hWnd, TME_HOVER|TME_LEAVE);
   m_isEnter = TRUE;
  }
 }
 else if (message == WM_MOUSELEAVE)
 {
  // When the mouse cursor has been 
  // left current window, 
  // the original select of the list-box 
  // to reset LB_ERR.
  m_OriginalSel = LB_ERR;
  m_isEnter = FALSE;
 }
 else if (message == WM_CAPTURECHANGED)
 { // To omit the mouse capture changed...
  return 1;
 }

 // Get previous window procedure
 WNDPROC oldListWndProc;
 m_mapWndProc.Lookup(hWnd, oldListWndProc);
 // Call previous window procedure
 return ::CallWindowProc(oldListWndProc, 
            hWnd, message, wParam, lParam);
}

At the end of the function, we reuse the old window procedure by calling the CallWindowProc function from the map like a chain of window procedures. Some of the messages have been reprocessed for the list-box. While processing the WM_MOUSEMOVE, the HandleListboxMouseMove function determines whether or not the tool-tip window has to be shown/hidden, and the OnTrackMouseEvent function tracks the mouse event messages WM_MOUSEHOVER or WM_MOUSELEAVE, that reset the original selection of the list-box when firing up WM_MOUSELEAVE. Waiting..., the WM_MOUSELEAVE message takes a continuous place, but the mouse cursor will still be above the list-box, actually, the mouse cursor will be above the tool-tip window when the tool-tip window shows up, think over? So we must capture the list-box to prevent this, to release the mouse capture only when the tool-tip has been hidden.

The ComboBox with owner DropList or DropDown style has a little trouble: when the mouse capture is released, the list-box becomes invisible. So, I have to hold the WM_CAPTURECHANGED and ignore the message...

Usage

You are allowed to use it for free and further modify it according to your needs.

Updates

There are two extra features for the class CTTCombobox, it now supports these features:

Thanks d3m0n, for finding out some bugs in the control. I have fixed all the bugs as follows:

The updated source and demo projects now support the X64 platform and Unicode version compilation for all platforms (Win32 and X64), and a few minor issues have been fixed. The demo project gives two project solutions respectively for VC6.0 and VS2005. Naturally, you can upgrade the solution with VS2008/VS2010.

There is a new function - SetTipText(int nIndex, LPCTSTR pszTipText). The function gives a customized method to setup tip text for a specified item. Note: This will force the display of the tip text, and if the customized tip text is no longer needed for that item, you're responsible to call the function again and pass pszTipText=NULL.

  • 8th June, 2005
    • Hover over the ComboBox and the tool tip appears. Then while the tool tip is still visible, click the arrow to expand the ComboBox. The dropdown list appears and then disappears immediately.
    • While the tool tip is visible, nothing can receive the focus, e.g., try clicking the Cancel button while the tool tip is visible.
    • The size of the tool tip is sometimes too big, e.g., hover over the ListBox, wait for the tool tip to appear and disappear, now move the mouse out of the ListBox. Now move it into the edit box. The size of the tool tip is really too big.
  • 27th September, 2006
    • Allows that you align the listbox by the three modes CTTCombobox::alignLeft, CTTCombobox::alignRight, CTTCombobox::alignMiddle, using the function SetAlignStyle to do this thing.
    • To disable or enable any items in a combobox, you only simply perform the function SetDisabledItem. Note: That's not effective when the combobox does not have the CBS_OWNERDRAWFIXED style, so I suggest you should set the style. There are three methods to gain the style CBS_OWNERDRAWFIXED.
      1. You directly check the style for the combobox in the resource.
      2. If the combobox was created dynamically, then you should do the thing in OnCreate(LPCREATESTRUCT lpCreateStruct). Usually, you write the code lpCreateStruct->style |= CBS_OWNERDRAWFIXED | CBS_HASSTRINGS; as the first line in the function OnCreate(LPCREATESTRUCT lpCreateStruct).
      3. If the combobox was subclassed dynamically, then you add the code: ModifyStyle(0, CBS_OWNERDRAWFIXED | CBS_HASSTRINGS); as the first line in PreSubclassWindow().
  • 21st July, 2011: Update
  • 19th November, 2011: Update

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