Click here to Skip to main content
15,895,656 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
I want to create a simple tooltip that will pop when user hovers over a button.

To do that I have studied this example[^] from MSDN.

Everything works fine when I first time hover over the button, but after that tooltip never shows up again ( I have checked return values for HWND of the tooltip and for SendMessage( ..., TTM_ADDTOOL, ... ) and there were no errors ).

I have tried to find the solution online, but have failed. The only resource I have found that might be useful is this tutorial[^] but it suggests to subclass the control in order to relay mouse messages to the tooltip control-I will not accept this type of solution because I believe what I ask for is the basic functionality tooltip control provides.

*******************************************************************
EDITED ON JANUARY 21st 2014:

Following instructions from this MSDN link[^] I was able to partially solve the problem. Now tooltip is shown after clicking on the main window's client area and then hovering back over the button.

However, after I click on the button tooltip never shows up again .

Browsing through Internet, I have found this example[^] and after adding these directives:
C++
#pragma comment( linker, "/manifestdependency:\"type='win32' \
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
    language='*'\"")

#pragma comment( lib, "comctl32.lib")

the problem seemed to disappear. Now I was able to click on a button, hover over edit control and edit control's tooltip would appear.

However, after clicking on a button, then clicking on the client area of the main window, and then hovering over the button again its tooltip did not show!

Then I have continued to search over the Internet and have found this article[^] on CodeProject and it does exactly what I need.

So I have started to analyze the source code of the first example and this article. I was unable to see the difference. However, appearances were different! It seems that the article did not use Visual Styles, and taking into the consideration MSDN article I have linked to at the very top of this edit, I start to suspect that this might be the manifest issue.

So I have tried to compile all the programs without the first pragma comment submitted above, but in my test application ( the one created as default Win32 project ) InitCommonControlsEx failed, in the example program I have got error Failed to save the updated manifest to the file ".\Debug\foosyerdoos tooltip.exe.embed.manifest". The parameter is incorrect., and the article application failed to create tooltip controls.

After creating the fresh blank project, and after copying the code from the first example-only this time without pragma comment-SendMessage failed to add both tooltips.

So, without first pragma comment submitted above, I can not use tooltip controls or so it seems.

END OF EDIT
********************************************************************

Here are the instruction for creating the minimal example that illustrates the problem:

1. Create default Win32 project in MS Visual Studio ;
2. Add the bellow WM_CREATE handler:

3. Compile and run-tooltip should show on first hover, but never again.
C++
case WM_CREATE:
    {
        HWND hButton = CreateWindowEx( 0, L"Button", L"test me!", 
            WS_CHILD | WS_VISIBLE | WS_BORDER | BS_PUSHBUTTON,
            50, 150, 150, 25, hWnd, (HMENU)8003, hInst, 0 );

        HWND hwndTip = CreateWindowEx( NULL, TOOLTIPS_CLASS, NULL,
            WS_POPUP | TTS_ALWAYSTIP,
            CW_USEDEFAULT, CW_USEDEFAULT,
            CW_USEDEFAULT, CW_USEDEFAULT,
            hWnd, NULL, hInst, NULL );

        // Associate the tooltip with the tool.
        TOOLINFO toolInfo = { 0 };
        toolInfo.cbSize = sizeof(toolInfo);
        toolInfo.hwnd = hWnd;
        toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
        toolInfo.uId = (UINT_PTR)hButton;
        toolInfo.lpszText = L"test 1";

        SendMessage( hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo );
    }
    return 0L;

I work on Windows XP, using MS Visual Studio Express 2008.

Visual Styles are enabled, I have linked comctl32.lib and have initiated INITCOMMONCONTROLSEX structure's dwICC member with ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_BAR_CLASSES.

EDIT ( January 31st, 2014 ):
***********************************

After testing this very code snippet on my laptop, which has Windows 7 I can confirm that everything works fine. This is the problem with my Windows XP I guess...

END OF EDIT
************************************

My question is really simple:

How to adjust my code so the tooltip is shown every time user hovers over the button?

Thank you.

Best regards.
Posted
Updated 30-Jan-14 14:45pm
v6
Comments
enhzflep 21-Jan-14 7:20am    
Hmm. Interesting. I just threw most of your snippet into the WM_INITDIALOG message handler of an existing project - the tip fires every time.
All I did was (a) change the oarent to my dialog, (b) Use GetDlgItem to get the hwnd of the button - so essentially nothing different, save for the fact I used a dialog app.

Tried copy/pasting it verbatim into a fresh win32 Frame-app (i.e main window has it's own class registered first) as created by Code::Blocks - same result; the tip fires every time. :confused:

EDIT: Even stranger - dropping the snippet into the default project created by VS2010 results in precisely 0 instances where the tooltip is shown.
AlwaysLearningNewStuff 21-Jan-14 18:14pm    
Please try to move pragma comment into the stdafx.h, and place it just below #include directives-follow the MSDN link I have provided in the introduction.

Your tooltip should appear now, but you will face other difficulties I have described.

Until we "speak" again, best regards.
enhzflep 22-Jan-14 6:09am    
Ahhh! (sound of a penny dropping)
I should have thought of that. I've been caught out by different behaviour before - I've a memory that the manifest ResEdit creates is different to the one that VS creates. Thanks again, I'll have a closer look when I get home. :)
Cheers.
AlwaysLearningNewStuff 24-Jan-14 11:48am    
I apologize for disturbing you, but weekend is coming, so I ask you, if you have some spare time, to try and help me with this if you can.

I have tried searching the Internet for further clues but to no avail:(

Unfortunately I have no colleagues to turn for help since I am young, and nobody I know ever dared to learn Win32.

Thank you.

Best regards.
enhzflep 24-Jan-14 12:02pm    
There's no disturbance and certainly no need for an apology, though I highly appreciate the gesture. I actually forgot to take a loser look the other day, sorry my friend.
It's nearly 4am here and I'm about to go to bed, but rest assured - I'll definitely look into it this weekend.

Win32 has a fond place in my heart - I'd much rather help-out others interested in it than be paid to work with the .NET framework. I've just put a sticky-note on my desktop and shall not allow myself to forget this time. (sorry again)

I'll let you know what I discover in the next day or 2.
You're always welcome.

Even better regards :p :grin:

hello
in my case(in MFC)
1. create tooltip variable(~.h)
CToolTipCtrl	m_toolTipCtrl;


2. initialize tooltip vaiable(~.cpp) in OnInitDialog()
C++
if (m_toolTipCtrl.m_hWnd == NULL)
	{
		m_toolTipCtrl.Create(this);
		m_toolTipCtrl.Activate(TRUE);
	}
m_toolTipCtrl.AddTool(GetDlgItem(IDC_BTN_INIT), _T("this is button."));


3. in PreTranslateMessage(MSG* pMsg)
C++
m_toolTipCtrl.RelayEvent(pMsg);


That's it.
Have a nice day.
 
Share this answer
 
Comments
H.Brydon 20-Jan-14 22:24pm    
What you are describing here is a little different. Your code captures the OnNotify message in the view/dialog class, and holds a tooltip control there, relaying the event to the control when a hit is detected. OP wants to handle this in the control (without subclassing)...
AlwaysLearningNewStuff 21-Jan-14 0:18am    
Mr.Brydon is right-I would like to do it without subclassing. I will only accept subclassing as the last option if all else fails. Still thank you for your answer, I highly appreciate it. Best regards.
One thing I can see that you are not doing is setting the tooltip control as topmost, so the tooltip might be behind the parent window.

Take a look at the creation section here: http://msdn.microsoft.com/en-us/library/windows/desktop/bb760250(v=vs.85).aspx#Tooltip_Creation[^] for an example with topmost being set.
 
Share this answer
 
As mentioned in the comments, this wont solve the problem at hand - although I did notice different behaviour of the two buttons when running under a clean install of XP SP3.

You can clearly see that SetWindowSubclass is much neater/easier to use. It also has the advantage that you can both apply it many times with different WndProcs and selectively remove any of the WndProcs in the chain. The old method however doesn't really offer a way to subclass the control several times simultaneously - the documentation explains this if I remember correctly - I think there may also be an example or two in there somewhere too.

main.cpp
C++
#define UNICODE 1
#define _WIN32_WINNT 0x0501
#define WINVER 0x0500
#include <windows.h>
#include <commctrl.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
wchar_t szClassName[ ] = L"CodeBlocksWindowsApp";

HINSTANCE hInst;

ATOM myRegClass()
{
    /* The Window structure */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */
    wincl.hInstance = hInst;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */

    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    return RegisterClassEx (&wincl);
}

int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nCmdShow)
{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */

    hInst = hThisInstance;

    if (myRegClass() == NULL)
        return -1;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindow(szClassName, L"Code::Blocks Windows App", WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        544, 375,
                        HWND_DESKTOP,NULL,hThisInstance,NULL);

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nCmdShow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        if (IsDialogMessage(hwnd, &messages) == 0)
        {
            TranslateMessage(&messages);
            /* Send message to WindowProcedure */
            DispatchMessage(&messages);
        }
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}


HWND addTip(HWND target, wchar_t *tipText)
{
    HWND parentWnd = GetParent(target);
    HWND hwndTip = CreateWindowEx( NULL, TOOLTIPS_CLASS, NULL,
        WS_POPUP | TTS_ALWAYSTIP,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        parentWnd, NULL, hInst, NULL );

    // Associate the tooltip with the tool.
    TOOLINFO toolInfo = { 0 };
    toolInfo.cbSize = sizeof(toolInfo);
    toolInfo.hwnd = parentWnd;
    toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
    toolInfo.uId = (UINT_PTR)target;
    toolInfo.lpszText = tipText;
    SendMessage( hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo );

    return hwndTip;
}
//SetWindowSubclass

void onPaint(HWND hwnd, WPARAM wParam, LPARAM lParam, COLORREF col)
{
    RECT rc;
    PAINTSTRUCT ps;
    HDC hdc;

    if (wParam == NULL)
        hdc = BeginPaint(hwnd, &ps);
    else
        hdc = (HDC)wParam;

    GetClientRect(hwnd, &rc);
    HBRUSH hbr = CreateSolidBrush(col);
    FillRect(hdc,&rc,hbr);
    DeleteObject(hbr);

    int txtLen, oldMode;
    wchar_t *wndTxt;

    txtLen = GetWindowTextLength(hwnd);
    wndTxt = new wchar_t[txtLen+2];
    GetWindowText(hwnd, wndTxt, txtLen+1);
    oldMode = SetBkMode(hdc, TRANSPARENT);
    DrawText(hdc, wndTxt, -1, &rc, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
    SetBkMode(hdc, oldMode);
    delete wndTxt;

    if (GetFocus() == hwnd)
    {
        RECT focusRect = rc;
        focusRect.left += 4;
        focusRect.top += 4;
        focusRect.right -= 4;
        focusRect.bottom -= 4;
        DrawFocusRect(hdc, &focusRect);
    }

    if (wParam == NULL)
        EndPaint(hwnd, &ps);
}

LRESULT CALLBACK Btn2SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uMsg)
    {
        case WM_ERASEBKGND:
            return 1;

        case WM_PAINT:
            onPaint(hwnd, wParam, lParam,RGB(50,140,87));
            return 0;
    }
    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK Btn1Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_ERASEBKGND:
            return 1;

        case WM_PAINT:
            onPaint(hwnd, wParam, lParam,RGB(87,140,50));
            return 0;
    }

    WNDPROC oldProc;
    oldProc = (WNDPROC)GetWindowLong(hwnd, GWL_USERDATA);
    return CallWindowProc(oldProc, hwnd, uMsg, wParam, lParam);
}

/*  This function is called by the Windows function DispatchMessage()  */
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  /* handle the messages */
    {
        case WM_CREATE:
            {
                HWND hButton = CreateWindow( L"Button", L"test me!",WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|WS_TABSTOP,50, 50, 75, 23, hWnd, (HMENU)8003, hInst, 0 );
                addTip(hButton, L"test1");
                long origProc;
                origProc = GetWindowLong(hButton, GWL_WNDPROC);
                SetWindowLong(hButton, GWL_USERDATA, origProc);
                SetWindowLong(hButton, GWL_WNDPROC, (long)Btn1Proc);

                HWND btn2 = CreateWindow(L"Button", L"btn2", WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|WS_TABSTOP, 50, 85, 75, 23, hWnd, (HMENU)8004,hInst,0);
                SetWindowSubclass(btn2,Btn2SubclassProc, 1, NULL);
                addTip(btn2, L"test2");
            }
            return 0L;

        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;

        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hWnd, message, wParam, lParam);
    }
    return 0;
}
 
Share this answer
 
Comments
AlwaysLearningNewStuff 20-Mar-14 18:11pm    
My pardon for disturbing, but I am really stuck with coloring common controls with Visual Styles enabled[^]. I think that this is something you have experience with, and I believe that it will not be too hard for you to solve.

Can you please give it a look and try to help? Again, I apologize for disturbance, but I have no one else to turn to, and I just can't figure it out.

Thank you.

Best regards.
enhzflep 23-Mar-14 20:12pm    
No problem. I have indeed played with customizing the colours of various standard controls. While you _can_ often use WM_CTLCOLORSTATIC to do it, it doesn't always work.

I've had experience with a checkbox with both a transparent background and coloured text. (You might get some utility from this old post: https://forum.tuts4you.com/topic/16536-checkbox-background-color/) tThe problem is in handling the transparent background. Usually, you can just fake it, by applying the same background colour as the parent dialog. However, as you are, I wanted to use a bitmap-based background. In my case, I got the portion of the parent's background from within the controls handler and paint it, before drawing the text and box ontop of this.
But it was messy and flickered.

Approaches worth considering include

(a) handling the WM_PRINT command. You then setup a mem DC in the main window, pass it to the control with a WM_PRINT message and then paint the returned image onto the parent. Though this is something I haven't explained well, Patrice Terrier (username: ZapSolutions) has extensive experience with subclassing standard controls to enable an alpha channel.

(b) Examining the source-code for the linux project Wine. I've used portions of that in the past to implement a control from scratch.

In short, it's a messy, painful endeavour in my experience (making std controls have transparent bkgs on XP)

Cheers. :)
AlwaysLearningNewStuff 24-Mar-14 20:43pm    
Wow... So much fuss about transparent checkbox with themes...

I apologize for being annoying, but could you help me find links for a) and b) as I was unable to find them.

Furthermore, I was able to find an article that states common controls 6 get NM_CUSTOMDRAW first, and I believe that I can solve this by properly drawing the radio button / checkbox there. I just can not find any tutorials on Visual Styles out there...

Thank you for trying to help.

Best regards.
enhzflep 25-Mar-14 3:22am    
Laughs.

Yep, it's certainly a nastier affair than so many other 'hard' problems.
Sure, sorry I didn't include links, I was too busy trying to not run late.

Here's a link to the DLL folder of the WINE source. I reckon you might find the check-box (a button) in user32, but I don't know. I typically wanted stuff in gdi32, gdiplus and commctl32
wine/tree/master/dlls @ github.com

Here's a link to Patrice's article's here: Articles by zapsolution (Patrice Terrier)
From memory, he discussed the WM_PRINT issue in some of his many posts over at the forum he frequents more often, Jose Rocca Software - Jose Rocca Software forum

Here's where you can see of Patrice's sub-forums at Jose Rocca:

C++ WinLIFT / GDImage
C++ programming (SDK style)

You need to register to download attachments, I did it by sending an email to the contact-us address, telling them that Patrice had recommended that I do that and mention his name. I've done almost nothing there, but you're welcome to tell them I referred you. Just PM me if you'd like that, and I'll tell you my name (must register with actual name there, pseudonyms are disallowed)
From memory, zSkin04 has information in the source code that discusses or demonstrates use of WM_PRINT. Though it's in (power)BASIC.

As for NM_CUSTOMDRAW - this also has some limitations. Consider the example of a trackbar (slider) when using NM_CUSTOMDRAW alone, one loses the ability to perform hot-tracking (mouse enter/leave detection) of the button. The only method I know of is to subclass the bugger. :(
I think there are other examples of controls behaving differently when using NM_CUSTOMDRAW, even when the documentation is followed. Oh! One I can think of is the pulsating glow of buttons under Win7. If you handle NM_CUSTOMDRAW, you lose the throbbing effect, you have to implement that yourself by subclassing the control - which kindof negates the whole purpose of custom-drawing.

I'd like to tell a different story, but it really is a minefield as far as I've discovered - customizing standard controls as much as each of us have wanted is a rather painful experience. Thankfully, I'm back onto some more interesting and fruitful stuff just now, image-processing. :grin:

Let me know how you get on. I've got a small (incomplete) test of using the WM_PRINT method, which I can share if you'd like it.
AlwaysLearningNewStuff 25-Mar-14 6:48am    
Thank you so much! I know you are busy, there is no problem.

I wish I could explore image processing too ( such a wonderful field to explore! ), but I need to fix some bugs and radio button/checkbox transparency under Visual Styles is one of them :(

Thank you for the info about NM_CUSTOMDRAW I did not know about that stuff but had a "hunch" something can go wrong based on some articles about progress bar.

I have ran into this article[^] that offers a solution via WM_PRINTCLIENT. Can this help you to provide some instructions for solving this problem?

At the moment I will try with WM_PRINTCLIENT, and will probably seek assistance later ( for now you are safe :smile: ).

Hopefully I will be able to solve this problem, and if I do I will post a minimal working code for others with the same problem so they can save their time and nerves.

Best regards until next time.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900