Displaying infotips for folded and unfolded listview items

Date:December 13, 2006 / year-entry #412
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20061213-00/?p=28733
Comments:    13
Summary:When displaying infotips for listview items, you have to deal with both the folded and unfolded case. "Folded" is the term used to describe a listview item in large icon mode whose text has been truncated due to length. When the user selects the item, the full text is revealed, a process known as "unfolding"....

When displaying infotips for listview items, you have to deal with both the folded and unfolded case. "Folded" is the term used to describe a listview item in large icon mode whose text has been truncated due to length. When the user selects the item, the full text is revealed, a process known as "unfolding".

Take our scratch program and make the following changes:

#include <strsafe.h>

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
  g_hwndChild = CreateWindow(WC_LISTVIEW, NULL,
                             WS_CHILD | WS_VISIBLE | LVS_ICON,
                             0, 0, 0, 0,
                             hwnd, (HMENU)1, g_hinst, 0);
  if (!g_hwndChild) return FALSE;

  ListView_SetExtendedListViewStyleEx(g_hwndChild,
                             LVS_EX_INFOTIP,
                             LVS_EX_INFOTIP);

  LVITEM item;
  item.iItem = 0; // added 9pm
  item.iSubItem = 0;
  item.mask = LVIF_TEXT;
  item.pszText = TEXT("Item with a long name that will be truncated");
  if (ListView_InsertItem(g_hwndChild, &item) < 0)
    return FALSE;

  return TRUE;
}

void OnGetInfoTip(HWND hwnd, NMLVGETINFOTIP *pit)
{
 if (!pit->cchTextMax) return;
 if (pit->dwFlags & LVGIT_UNFOLDED) {
  pit->pszText[0] = TEXT('\0');
 } else {
  StringCchCat(pit->pszText, pit->cchTextMax, TEXT("\r\n"));
 }
 StringCchCat(pit->pszText, pit->cchTextMax, TEXT("Here is an infotip"));
}

LRESULT OnNotify(HWND hwnd, int idCtrl, NMHDR *pnm)
{
 if (idCtrl == 1) {
  switch (pnm->code) {
  case LVN_GETINFOTIP:
   OnGetInfoTip(hwnd, (NMLVGETINFOTIP*)pnm);
   break;
  }
 }
 return 0;
}

  HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);

We create our listview, enable infotips, and add a single item with a rather long name. When you run the program, observe that the item's text is truncated at two lines if it is not selected, but it expands to full size when you selected it.

When the listview notifies us that it's time to display the infotip, we check whether the item is folded or unfolded. If it is unfolded, then we set the buffer to an empty string so that our StringCchCat at the end will merely copy the infotip text into the buffer. On the other hand, if the item is folded, then we append a line terminator because we want the infotip to contain the full text of the item, followed by the tip text.

When you run this program, hover over the item both when it is folded and unfolded, and observe that the folded infotip includes the name of the item. This is a detail of infotips that is called out in the documentation but which many programs fail to observe.


Comments (13)
  1. anonymous says:

    I found it helpful to insert:

    memset(&item, 0, sizeof(LVITEM));

    after:

    LVITEM item;

  2. I think this is related – I’ve seen some applications (like the SQL 2000 DTS Import/Export wizard) where the infotip displays correctly on the primary monitor, but if you move the app over to the second monitor, it doesn’t display the infotip at all.

    It’s a bug somewhere, but I don’t know if it’s Windows, video drivers or the DTS wizard.

    -dave

  3. “I found it helpful to insert:

    memset(&item, 0, sizeof(LVITEM));”

    You could get the same effective result with the following line:

    LVITEM item = {0};

    This would still initialize the structure to null values.

    PMP

    [Fixed; thanks. -Raymond]
  4. Norman Diamond says:

    StringCchCat

    Those can be pretty dangerous functions.

    For StringCbCat, MSDN says that it counts bytes, which is true.

    However for StringCchCat, MSDN says that it counts characters, which is only true sometimes.  It’s true more often for projects started with Visual Studio 2005’s default settings than for projects started under earlier versions of Visual Studio, but it’s still something you can’t rely on.

    If someone who understands TCHARs can define APIs with names like StringCtcCat and if MSDN can say that they count TCHARs, the result should be less dangerous.

    In Windows CE fortunately we can pretty much assume that a character is a character, but some of those APIs don’t port from ordinary Windows to Windows CE the way they were supposed to.

  5. Alex Feinman says:

    Those can be pretty dangerous functions.

    For StringCbCat, MSDN says that it counts bytes, which is true.

    However for StringCchCat, MSDN says that it counts characters, which is only true sometimes

    How is it true "sometimes"? The xxxCchyyy strsafe functions count buffer size in TCHARs. The StringCchCat is defined as StringCchCatA or StringCchCatW depending on the UNICODE being defined. Consequently the buffer size measured in characters is treated correctly whatever the unicode setting is.

  6. Norman Diamond says:

    Thursday, December 14, 2006 2:08 AM by Alex Feinman

    How is it true "sometimes"?

    You answered your own question.

    The xxxCchyyy strsafe functions count buffer

    size in TCHARs.

    You’re right.  But MSDN says that it counts characters.  Sometimes characters are TCHARs and sometimes they aren’t.  That’s also why I pointed out that default compilation environments were different before Visual Studio 2005.  The default used to be ANSI, in which each character occupied 1 or 2 TCHARs (where each TCHAR was 1 byte).

    Consequently the buffer size measured in

    characters is treated correctly whatever the

    unicode setting is.

    That’s true whatever the Unicode setting is if the Unicode setting is on.  It isn’t true when Unicode is off.  In ANSI code pages used in the world’s most populous country and several of its neighbours, many thousands of characters do not fit in one byte per character.

    Thank you for providing another example of why additions to Windows string handling APIs and MSDN need more input from people who understand TCHARs.

  7. Doug says:

    Norman:

    You’re picking a different definition of "character" than the developer of the function. I don’t think your definition is any more correct.

    What do I mean by character?

    C++: smallest addressable unit of storage.

    Most Win32 APIs: One byte or one unsigned short, or (occasionally) one DBCS char, depending on Unicode settings.

    Unicode: One code point.

    Typeography: One glyph.

    In Win32 APIs, the vast majority mean "either CHAR or WCHAR, depending on whether or not _UNICODE is defined". And the StrSafe functions match that definition. These are character buffer manipulation functions, and the only semantics they assign to the character values is "0 means the end". These are not meant to be MBCS-aware.

  8. The API with strsafe.h is a bright idea from Microsoft. It first appeared in 2002 (4 years ago) and it’s part of the SDK. I’ve been using it in my developements since then and it’s safe and fast. It is recommended to use it in a very interesting book called "Writing Secure Code" by Michael Howard and David LeBlanc (MS Press 2003).

  9. Tim Smith says:

    Norman,

    Even with UCS2/UCS4/UTF32 there is a difference between basic storage count, code point count and character count.

  10. Neil says:

    Thanks to the blog’s stylesheet designers the code samples appear to me as e.g.

    item.pszText = TEXT("Item with a long name that will be truncat

    Now if only I could unfold that line…

  11. Norman Diamond says:

    Thursday, December 14, 2006 5:04 AM by Doug

    > Norman:

    > You’re picking a different definition of

    > “character” than the developer of the

    > function.

    Obviously.  And that is exactly the reason why I think development of APIs that handle “characters” and “strings” needs more input from people who understand TCHARs.

    > I don’t think your definition is any more

    > correct.

    In some ways you’re right.  Maybe (within an order of magnitude) 49% of the time when MSDN says character it means character, as in counting how many characters the user sees in a document or in a line of text or on a page in a published book.  Maybe (within an order of magnitude) 49% of the time when MSDN says character it means TCHAR.  In recent years some wording has been added to some MSDN pages to repeat some of the ground rules of programming with characters in Windows, to explain what TCHARs mean.  Obviously more pages need it.

    Maybe (within an order of magnitude) 2% of the time when MSDN says character it means C++ character.  Right, when MSDN talks about the C standard or the C++ standard, it has to use words the way the standards used them.  (One time Mr. Chen reminded me of this fact and I thank him.)

    > What do I mean by character?

    > C++: smallest addressable unit of storage.

    Then I recommend to you to use the word “char” or “byte” instead.  And stick to functions like StringCbCat instead of StringCchCat, because StringCbCat counts bytes exactly the way its name and MSDN page say and they will be safe for you to use.

    Thursday, December 14, 2006 8:26 AM by Tim Smith

    > Even with UCS2/UCS4/UTF32 there is a

    > difference between basic storage count, code

    > point count and character count.

    I think you mean UTF16/UTF32.  But either way, it shows yet again that if a function would be named StringCtcCat and would count TCHARs and would be documented as counting TCHARs then it would be correct.

    If you meant that in response to my assertion “In Windows CE fortunately we can pretty much assume that a character is a character” then you’re right, so maybe my words “pretty much” needed more details.  Maybe Windows CE will get support for surrogate pairs.

    [Thanks, Norman, for hijacking another thread. Please everybody could you hold off on this topic until January 5th, when I’ll devote an entire day to the subject? Thanks. -Raymond]
  12. Alex Lite says:

    I have ListView in Report-style. Your code works, but only for label (first subitem) of list item.

    So I have 2 questions:

    1. How to make the tip react on mouse hover  not only on the label, but also on 2nd, 3rd and so on item?

    2. Is it possible by standard methods to transform this default tip into baloon tip?

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index