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
- 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. - 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. - 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 (m_isEnableTool == TRUE)
{
CreateTooltipWnd();
InstallHookForListboxAndEditbox();
}
}
We have called the CreateTooltipWnd
function to create a tool-tip window. (Note: All the ComboBox
es 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()
{
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;
WNDPROC oldListWndProc = (WNDPROC)::SetWindowLong(cbi.hwndList,
GWL_WNDPROC, (DWORD)HookListboxWndProc);
m_mapWndProc.SetAt(cbi.hwndList, oldListWndProc);
}
if (cbi.hwndItem)
{
m_hWndEdit = cbi.hwndItem;
WNDPROC oldEditWndProc = (WNDPROC)::SetWindowLong(cbi.hwndItem,
GWL_WNDPROC, (DWORD)HookEditboxWndProc);
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))
{
CTTComboBox::HandleListboxMouseMove(pList,
wParam, point);
}
if (!m_isEnter)
{
OnTrackMouseEvent(hWnd, TME_HOVER|TME_LEAVE);
m_isEnter = TRUE;
}
}
else if (message == WM_MOUSELEAVE)
{
m_OriginalSel = LB_ERR;
m_isEnter = FALSE;
}
else if (message == WM_CAPTURECHANGED)
{
return 1;
}
WNDPROC oldListWndProc;
m_mapWndProc.Lookup(hWnd, oldListWndProc);
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
.
- You directly check the style for the
combobox
in the resource. - 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)
. - 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