Date: | June 28, 2006 / year-entry #216 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20060628-05/?p=30703 |
Comments: | 8 |
Summary: | The tooltip control lets you set multiple "tools" (regions of the owner window) for it to monitor. This is very convenient when the number of tools is manageably small and they don't move around much. For example, the toolbar control creates a tool for each button. But if you have hundreds or thousands of screen... |
The tooltip control lets you set multiple "tools" (regions of the owner window) for it to monitor. This is very convenient when the number of tools is manageably small and they don't move around much. For example, the toolbar control creates a tool for each button. But if you have hundreds or thousands of screen elements with tooltips, creating a tool for each one can be quite a lot of work, especially if the items move around a lot. For example, the listview control does not create a separate tool for each listview item, since a listview can have thousands of items, and scrolling the view results in the items moving around. Updating the tool information whenever the listview control scrolls would be extremely slow, and the work would be out of proportion to the benefit. (Updating thousands of tools on the off chance the user hovers over one of them doesn't really sit well on the cost/benefit scale.) Instead of creating a tool for each item, you can instead multiplex all the tools into one, updating that one tool dynamically to be the one corresponding to the element the user is currently interacting with. We'll start with a fresh scratch program and create a few items which we want to give tooltips for. int g_cItems = 10; int g_cyItem = 20; int g_cxItem = 200; BOOL GetItemRect(int iItem, RECT *prc) { SetRect(prc, 0, g_cyItem * iItem, g_cxItem, g_cyItem * (iItem + 1)); return iItem >= 0 && iItem < g_cItems; } int ItemHitTest(int x, int y) { if (x < 0 || x > g_cxItem) return -1; if (y < 0 || y > g_cItems * g_cyItem) return -1; return y / g_cyItem; } void PaintContent(HWND hwnd, PAINTSTRUCT *pps) { COLORREF clrSave = GetBkColor(pps->hdc); for (int iItem = 0; iItem < g_cItems; iItem++) { RECT rc; GetItemRect(iItem, &rc); COLORREF clr = RGB((iItem & 1) ? 0x7F : 0, (iItem & 2) ? 0x7F : 0, (iItem & 4) ? 0x7F : 0); if (iItem & 8) clr *= 2; SetBkColor(pps->hdc, clr); ExtTextOut(pps->hdc, rc.left, rc.top, ETO_OPAQUE, &rc, TEXT(""), 0, NULL); } SetBkColor(pps->hdc, clrSave); } We merely paint a few colored bands. To make things more interesting, you can add scroll bars. I leave you to deal with that yourself, since it would be distracting from the point here, although it would also make the sample a bit more realistic.
Next, we create a tooltip control and instead of
creating a tool for each element, we create only one.
For starters, it's an empty tool with no rectangle.
The HWND g_hwndTT; int g_iItemTip; BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { g_hwndTT = CreateWindowEx(WS_EX_TRANSPARENT, TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, hwnd, NULL, g_hinst, NULL); if (!g_hwndTT) return FALSE; g_iItemTip = -1; TOOLINFO ti = { sizeof(ti) }; ti.uFlags = TTF_TRANSPARENT; ti.hwnd = hwnd; ti.uId = 0; ti.lpszText = TEXT("Placeholder tooltip"); SetRectEmpty(&ti.rect); SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&ti); return TRUE; }
You may have noticed that we do not use the The single tool for the tooltip covers our entire client rectangle. We maintain this property as the window resizes. void OnSize(HWND hwnd, UINT state, int cx, int cy) { TOOLINFO ti = { sizeof(ti) }; ti.hwnd = hwnd; ti.uId = 0; GetClientRect(hwnd, &ti.rect); SendMessage(g_hwndTT, TTM_NEWTOOLRECT, 0, (LPARAM)&ti); }
We need to keep the void UpdateTooltip(int x, int y) { int iItemOld = g_iItemTip; g_iItemTip = ItemHitTest(x, y); if (iItemOld != g_iItemTip) { SendMessage(g_hwndTT, TTM_POP, 0, 0); } }
To update the tooltip, we check
whether the mouse is over the same item as it was last time.
If not, then we update our "Which item is under the mouse now?"
variable and pop the old bubble (if any).
And we always relay the message to the tooltip so it can do its
tooltip thing.
This function also explains why we did not use the
This The easy one to take care of is the mouse motion: void RelayEvent(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) { UpdateTooltip(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); MSG msg; msg.hwnd = hwnd; msg.message = uiMsg; msg.wParam = wParam; msg.lParam = lParam; SendMessage(g_hwndTT, TTM_RELAYEVENT, 0, (LPARAM)&msg); } LRESULT CALLBACK WndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) { if ((uiMsg >= WM_MOUSEFIRST && uiMsg <= WM_MOUSELAST) || uiMsg == WM_NCMOUSEMOVE) { RelayEvent(hwnd, uiMsg, wParam, lParam); } switch (uiMsg) { ... as before ... }
If we get a mouse message, then the
You can run the program now. Observe that the program acts as if each colored band has its own tooltip, even though there is really only one tooltip that we keep recycling. We're still not done. The tooltip text is the same for each item, which is unrealistic for a real program. We'll address this next time. |
Comments (8)
Comments are closed. |
Don’t know why this blog is so light on comments lately, summer might have something to do with it I don’t know, but I still read it every day, so keep up the good work Raymond
I guess Raymond can check the number of readers without them having to comment (if he even cares about his popularity). I’m reading and enjoying this blog every day just to learn some nice details about windows although I actually never write code a this level.
The tiny details makes this interesting (as the concept is really easy) like:
1. Why do you draw the color bands with empty text and not some kind FillRectangle function?
2. Why does the tools rect change and is bigger than all of your items? I expected it to be set to the size of all your items (so just (0, 0, g_cxItem, g_cItems & g_cyItem)).
3. How does the tooltip disappear now that you won’t relay a mouse message that it’s outside of the item-rect? (It will POP because of itemTip becoming -1, but it will reappear as it’s still in the client-rect right?)
4. You say you can’t subclass because you would POP the new tooltip. How could there have been a new tooltip if you haven’t changed the tool you’re over? Could you have sent a POPUP when UpdateTooltip detects you’re over a new item to make the new bubble appear as soon as you’re over a new item?
I’m still observing the same effects:
Even without TTS_NOANIMATE, sometimes the tooltip is drawn without animation and border (at least I believe it’s a tooltip since it’s yellow). And with that flag, it’s always without animation and border. But whenever it’s drawn without border, the window is clipped to the parent (when I resize the partent window).
Is it possible to eliminate animation and still have tooltip that is not clipped? The real “big” apps (i.e. IE :) ) don’t have such problems.
Why are you creating the tooltip with WS_EX_TRANSPARENT and not WS_EX_TOPMOST ?
bvleur: ExtTextOut is faster than creating brush and passing it to FillRect (MFC uses it for its CDC::FillSolidRect method). I vaguely remember reading that it’s also faster than MoveTo/LineTo for horizontal/vertical lines.
Not that it should matter in such simple app anyway :)
PingBack from http://www.antcassidy.com/code/?p=23
PingBack from http://www.deez.info/sengelha/blog/2007/08/28/owner-drawn-tooltips/