Date: | June 29, 2006 / year-entry #217 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20060629-00/?p=30693 |
Comments: | 15 |
Summary: | Our multiplexed tooltip right now is displaying the same string for all items. Let's make it display something a bit more interesting so it's more obvious that what we're doing is actually working. BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { ... // ti.lpszText = TEXT("Placeholder tooltip"); ti.lpszText = LPSTR_TEXTCALLBACK; ... } LRESULT OnNotify(HWND hwnd, int idFrom,... |
Our multiplexed tooltip right now is displaying the same string for all items. Let's make it display something a bit more interesting so it's more obvious that what we're doing is actually working. BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { ...
Instead of providing fixed tooltip text,
we generate it on the fly by setting the text to
Now that you've played with the program a bit,
let's tweak it every so slightly to illustrate a point I made
last time:
We'll make the void InvalidateItems(HWND hwnd, int iItemMin, int iItemMax) { RECT rc; SetRect(&rc, 0, g_cyItem * iItemMin, g_cxItem, g_cyItem * iItemMax); InvalidateRect(hwnd, &rc, TRUE); } void UpdateTooltipFromMessagePos(HWND hwnd) { DWORD dwPos = GetMessagePos(); POINT pt = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) }; ScreenToClient(hwnd, &pt); UpdateTooltip(pt.x, pt.y); } void OnChar(HWND hwnd, TCHAR ch, int cRepeat) { switch (ch) { case TEXT('+'): g_cItems += cRepeat; InvalidateItems(hwnd, g_cItems - cRepeat, g_cItems); UpdateTooltipFromMessagePos(hwnd); break; case TEXT('-'): if (cRepeat > g_cItems) cRepeat = g_cItems; g_cItems -= cRepeat; InvalidateItems(hwnd, g_cItems, g_cItems + cRepeat); UpdateTooltipFromMessagePos(hwnd); break; } } // Add to WndProc HANDLE_MSG(hwnd, WM_CHAR, OnChar);
We have a few new helper functions.
The first invalidates the rectangle associated with a
range of items.
(Conforming to Hungarian convention, the term "Max"
refers to the first element outside the range.
In other words, "Min/Max" is
endpoint-exclusive.)
Controls that manage sub-elements will almost always have
a function like
The next helper function is
There is an important subtlety to the
|
Comments (15)
Comments are closed. |
According to the NMTTDISPINFO reference in MSDN, the pdi->szText buffer is only 80 bytes wide (enough for 40 UCS-2/UTF-16 characters, assuming none of the characters are surrogates because you need to display one of the Unicode code-points above 65535).
Now, here you’re only putting a maximum of 13 characters in it (the maximum value a 32-bit number can hold), so 26 bytes, but it seems a bit … iffy security-wise to rely on that. Wouldn’t it be better to use wsnprintf (if that’s available; MSDN’s search doesn’t know what it is, and Google doesn’t know much about it)?
Of course, maybe that would detract from the educational value of the article. But it seems that things like that get overlooked way too often in production code, where it really does matter. At least a note might have been good.
(To really do it right, you probably need to malloc a buffer in lpszText, try to wsnprintf, passing the size of the buffer, then check the return value of wsnprintf to ensure it’s < the size (accounting for the possibility that one byte != one character; I don’t know the contract of wsnprintf for sure, because MSDN doesn’t have it). If it’s equal or bigger, then the buffer’s size would be set to the return value of wsnprintf, using realloc, and then wsnprintf would be called again. If there’s a chance of something else (e.g. another control’s WndProc or something; DoEvents in VB6 causes this kind of thing) modifying g_iItemTip between the two calls to wsnprintf, then you’ll need to make this into a loop. Or (if the “something else” was another thread) add a lock around the entire wsnprintf / realloc / wsnprintf sequence, and around any modifications to g_iItemTip.)
BryanK: If you’re doing Win32-specific stuff anyway, you might as well use FormatMessage instead of wsnprintf. You can even have it alloc the buffer for you.
Thank you for this series of tooltip posts. They are great!
BTW.. like some others, I am not seeing a border drawn around the tooltip every time it is displayed. The first popup of the tooltip shows a border, then each successive popup does not show a border UNTIL I move the move the mouse around for an extended period of time, then hover again over an item. I see that tooltips have different delay times for reshow / initial popups (see TTM_GETDELAYTIME for info) . Maybe when the tooltip considers it a ‘reshow’ timeout, the border is not drawn for some reason?
The follow two pictures were taken from moving to the bottom bar, then up 1 bar and back again:
http://www.uploadfile.info/uploads/19b6a6b9c1.png
One issue per article is fine, of course, but I wouldn’t be surprised if someone grabbed the code given here and used it in a production program, with a tooltip longer than 80 bytes (40 Unicode characters, assuming no surrogates). And then got burned because of a buffer overflow.
Just a comment in the code might be nice. (“Beware: szText is only 80 bytes wide.” should be sufficient.)
Regarding FormatMessage: Yeah, that might be helpful. I don’t remember at the moment whether it can take a fixed format string, or if the format string has to be a DLL resource, but either way it’d work. (If and only if FormatMessage is allocating the buffer, anyway.)
Thanks for the tooltip stuff – is there more to come?
I want to know, mainly to see if you cover balloon help, and why NIN_BALLOONUSERCLICK in particular seems to be an entirely fictional event… :-)
(at least, on my XP system it is)
You mentioned in a previous post that you don’t have way to store images online. Your writeups are great, but for those of us who come here to glean a nugget of wisdom but who don’t have the time to sit down with your scratch app and compile in your sample code, it would be great if you could find a way to host an image or two so we could see the finished results.
Off Topic: Why don’t they allow you to upload pictures?
Given the title of this post, I was hoping for something about TTM_UPDATETIPTEXT. I seem to be in a situation which calls for it. My immediate question is that although it’s supposed to be for changing the text, I must pass an entire TOOLINFO. This leads me to wonder whether the other fields are being used, and if so whether this means one of the existing tools might be altered. (As I understand it, one tip may have many tools.)
It now looks from experimentation as if it’s not necessary to fill in all the fields of a TOOLINFO before passing it to TTM_UPDATETIPTEXT. Or, to be more scientifical about it, I’m giving TTM_UPDATETIPTEXT a NULL value for TOOLINFO.hwnd and it’s not causing any trouble even though I’ve also set TTF_IDISHWND and cleared TOOLINFO.rect. It’s a weird API, but that’s nothing new I suppose.
In fact, if I clear all fields, then set cbSize appropriately and set lpszText to LPSTR_TEXTCALLBACK, that seems to be enough. I guess this is basically what I would have expected, but it still feels obscure and roundabout.
It may not actually be obscure and roundabout because because I’ve realized my sending a TOOLINFO full of mostly zeroes along with TTM_UPDATETIPTEXT may be having no effect at all and I may see the text change for a different reason. I need to do some more work.
This will come as a huge shock, but of course you are right and I am completely wrong. So wrong, in fact, that it looks like I won’t even be using TTM_UPDATETIPTEXT because I want one-text-per-tip rather than -per-tool. In fact, that seems like such a good choke-point to me that I don’t know why people would want -per-tool instead. I’m sure it’s legit; I just don’t see why I would want it right now. I would much rather use LPSTR_TEXTCALLBACK and cache the text myself. So that’s what I’m doing. After I refresh my cache, I know whether the tip is visible by listening for TTN_SHOW and TTN_POP, and if it is visible, I send TTM_POP and TTM_POPUP. I figured it would flicker, and I would have to figure out what to do about that, but it doesn’t — or it flickers so fast that my eyes can’t see it. Now, go ahead, tell me how far my head is up my butt — I’ll be disappointed if it’s not. :-)
I, too, had problems with the tooltip border only appearing sporadically. There was another problem that appears to have been related. If you hover over the last bar so that the tooltip appears and then press + a couple of times to draw more bars, the new bars overwrite the tooltip rather than appearing underneath it.
By comparing this code with the sample code on MSDN I experimentally determined that the WS_EX_TRANSPARENT style was the source of both quirks. Removing it and passing 0 to the extended style makes the tooltips behave better, at least on my machine. (I did try turning on and off various tooltip and window animation settings but got the same results regardless.)
> It’s not mysterious at all. All you’re
> updating is the text
But you’re passing an entire structure.
In recent months, I’ve stopped experimenting to see if certain parameters can be NULL in cases where I have no useful information to point those parameters to and MSDN doesn’t say if the parameters can be null or not. If MSDN describes a pointer and a length, the length can be zero, but if MSDN doesn’t say that the pointer can be null then I declare a byte and point the pointer to it. Here is the reason for my change to such a rigorous practice:
http://blogs.msdn.com/oldnewthing/archive/2006/03/20/555511.aspx
* All parameters must be valid.
* Pointers are not NULL unless explicitly
* permitted otherwise.
[It’s clumsy having a TTUPDATETIPTEXT structure, a TTNEWTOOLRECT structure, a TTDELTOOL structure, a TTGETTEXT structure, etc. Instead, there’s just a generic TOOLINFO that’s used for multiple messages. -Raymond]
I think where I got confused is that you’re explaining dynamic content in the context of multiplexing, and the "dynamic" content is really just a side effect of that multiplexing. By contrast, I need content that’s dynamic based on some external factor, say bytes arriving from the internet. It so happens that I have only a few tools, which makes things easier on me, but that’s not the critical difference. If I had thousands of potential tools, I’d still need to provide "dynamic" content in both senses mentioned here. Pile on top of this the fact that I hadn’t used this API at all before last week and my confusion compounds. Anyway, if my strategy involving LPSTR_TEXTCALLBACK, TTN_SHOW, TTN_POP, TTM_POP, and TTM_POPUP doesn’t nauseate you, I figure I’m doing OK.