Creating a listview with checkboxes on some items but not others


Today's Little Program creates a listview with checkboxes on some items but not other.

The LVS_EX_CHECK­BOXES extended style is really just a convenience style. Everything it does you could have done yourself, with a bit more typing.

  • It creates a state image list consisting of an unchecked box (state 1) and a checked box (state 2). You could have done this yourself with Image­List_Create followed by a few calls to Draw­Frame­Control.

  • When you hit the space bar or click on the check box, the state image toggles between 1 and 2. You could have done this yourself by responding to LVN_KEY­DOWN (for the space bar), and the mouse notification messages for the clicks. (For the mouse notifications, see if the click was on LVHT_ON­ITEM­STATE­ICON.)

But still, it's convenient having the listview control do this grunt work for you. But what if you want to remove the check box from some items?

The listview control turns on the state image and toggles it by doing the moral equivalent of a List­View_Set­Check­State on the item, so all you have to do is respond to the LVN_ITEM­CHANGING that comes with any item change and reject the state change.

Start with our scratch program and make these changes. Remember, Little Programs do little or no error checking.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
  g_hwndChild = CreateWindow(WC_LISTVIEW, NULL,
        WS_CHILD | WS_VISIBLE | LVS_REPORT,
        0, 0, 0, 0, hwnd, (HMENU)1, g_hinst, 0);
  ListView_SetExtendedListViewStyle(g_hwndChild,
                                    LVS_EX_CHECKBOXES);
  LVCOLUMN col;
  col.mask = LVCF_TEXT | LVCF_WIDTH;
  col.cx = 200;
  col.pszText = TEXT("Name");
  ListView_InsertColumn(g_hwndChild, 0, &col);
  LVITEM item;
  item.mask = LVIF_TEXT;
  item.iSubItem = 0;
  item.pszText = TEXT("Alpha");
  ListView_InsertItem(g_hwndChild, &item);
  item.pszText = TEXT("Beta");
  ListView_InsertItem(g_hwndChild, &item);
  item.pszText = TEXT("Gamma");
  ListView_InsertItem(g_hwndChild, &item);
  item.pszText = TEXT("Delta");
  ListView_InsertItem(g_hwndChild, &item);
  return TRUE;
}

Okay, so far the program adds four items, each with a check box. But let's say we want to remove the check boxes from the even-numbered items.

LRESULT
OnNotify(HWND hwnd, int idFrom, NMHDR *pnm)
{
  if (idFrom == 1) {
    switch (pnm->code) {
    case LVN_ITEMCHANGING:
      {
        LPNMLISTVIEW pnmlv = CONTAINING_RECORD(pnm, NMLISTVIEW, hdr);
        if (pnmlv->iItem >= 0 &&
        if (pnmlv->iItem % 2 == 0 &&
            (pnmlv->uChanged & LVIF_STATE)) {
         return TRUE; // reject changes to even-numbered items
        }
      }
      break;
    }
  }
  return 0;
}

    HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);

We add a handler for LVN_ITEM­CHANGING that says, "If this is a notification for an even-numbered item, and they want to change the state, then block the state change." This ensures that nobody can turn on the state image, which means that the checkbox never shows up.

Comments (13)
  1. John Doe says:

    Isn't thing a case of a leaky abstraction? And since we're at it, wouldn't it be better to filter out only state image index 1 and 2?

    [LVS_EX_CHECKBOXES was not intended to be an abstraction. It's a convenience flag. -Raymond]
  2. John Doe says:

    Typo: thing => this.

    Since this alone is not worthy of another comment, can we copy and change the notification sent to the default message handler, instead of completely ignoring the notification, in an attempt to avoid changing the state image index if it's 1 or 2?

  3. @John Doe says:

    You didn't follow very well. Read the very last sentence again. Comprehend.

  4. John Doe says:

    @@John Doe, dear. "You didn't follow very well." I posted the second comment before the first response. "Comprehend."

    I'm a stop trolling/troll feeding now.

  5. @John Doe says:

    The response by Raymond merely repeated the second sentence of this blog entry. The point you failed to grasp is a different one, though: Your proposed answer to the problem is to prevent the checkbox from changing from the unchecked state to checked state. This results in a non-intuitive UI (having checkboxes that cannot be checked). Raymond's proposed solution prevents the checkbox from showing up altogether, which is about a thousand miles away from your attempt, hence the suggestion to re-read the very last sentence.

  6. John Doe says:

    No, my proposal was to, instead of completely filtering out a list view item notification if it tries to change the state icon index of even items, that we either (again, only for for even items):

    1) filter out only the ones that set it to 1 or 2, as to prevent only the checkbox images but nothing else

    2) when it tries to set it to 1 or 2, actually leave it with the current one, but accepting every other change in the notification

    That is, if the state icon index is either 1 or 2, make a copy of the notification. Then, make the new state icon index be the same as the current one, whatever it is. On the first notification, this should be 0, so no checkbox will ever appear. Every other change to the list view items will be accepted.

    So, this is not that far from what Raymond has showed, just a little more permissive or generalizable.

    Forwarding such notification message to the default list view WndProc is my actual question, if you may. I suppose calling DefWindowProc with the fake notification message would suffice.

    All of this has usability problems:

    – Why do some items have checkboxes and others don't?

    – If you can live with the previous point, why not let some items have icons instead of simply being empty?

    I think it's easy, by sufficiently exercising the little program, to come up with more. Note that intuition is subjective, so as is usual in UI design, a generallly problematic feature may actually be a solution in a specific situation.

    As for the rhetoric question about abstraction, it's not normal for styles to be implemented in a user-visible way. I'd expect the new style to actually extend list view items to have yet-another-field, not add two images and the developer having to know about them, their indexes and what they are, because it interferes with the program's own images.

    No matter what the documentation says, this is quite unusual. Just sayin'…

  7. @John Doe says:

    With LVS_EX_CHECKBOXES there is no need to filter anything. The state item index will either be 1 or 2. If the notification is changing it to anything else, you're dealing with a list view control that isn't using the default LVS_EX_CHECKBOXES style, but rather its own image list. You cannot make assumptions for controls you do not know. Your proposal is neither more permissive nor generalizable, but rather unnecessary code spent to implement a bug.

  8. John Doe says:

    You're right about that. So I checked, and the ListView_GetCheckState macro will fail if the state index value isn't 1 or 2.

    So, if one takes this little application and extend it for some useful purpose, remember that it's based on undocument behavior, maybe one that can change in the future (but, well, if Raymond published this little app, maybe not), and to treat the even items differently everywhere.

    Again, since we're at it, and we have:

    – An extended style that's just a convinience, it's documented to behave in a certain way, with certain images in the list view

    – A little application that messes up with that behavior, by not allowing the state index change and making ListView_GetCheckState generally break

    Why not extend the mess to actually allow the items to have other images? It wouldn't be buggier than what it is now, given that a state index other that 1 or 2 is not documented for when the LVS_EX_CHECKBOXES extended style is used.

    Probably, and in an attempt to make the discussion shorter, the better thing to do would be to replicate what this extended style does, and extend for the lack of a check-box (state index 0), and possibly (why not?) for an indeterminate checkbox (e.g. state index 3). Since we'll not be using the extended style, we'll also not use that macro.

    [The behavior is documented. Not sure what you mean about breaking ListView_GetCheckState; that remains unaffected. And yes, this is a special-purpose function which assumes you don't need other fancier states. That's why it's a Little Program, not a "here's a function you can copy and paste without understanding what it does" sample. -Raymond]
  9. John Doe says:

    @Raymond, you're right, it's documented, my bad, I missed it:

    "Setting the state image to zero removes the check box."

    But ListView_GetCheckState returns true if the state image is zero. Actually, an underflowed UINT:

    #define ListView_GetCheckState(hwndLV, i)

      ((((UINT)(SNDMSG((hwndLV), LVM_GETITEMSTATE, (WPARAM)(i), LVIS_STATEIMAGEMASK))) >> 12) -1)

    Anyway, I'd make the message handling function partially accept item change notifications, instead of throwing them out entirely.

  10. Joshua says:

    Then don't call ListView_GetCheckState on listbox items that you removed the checkbox on.

  11. John Doe says:

    @Joshua, I won't, but shouldn't the documentation reflect that it only works for items which have a checkbox?

    Right now, it's documented that it works if the LVS_EX_CHECKBOXES extended style is in use, but that's not the whole story.

  12. @John Doe says:

    The documentation says: "This [ListView_GetCheckState] should be used only for list-view controls that have the LVS_EX_CHECKBOXES style." I doesn't say that all ListView controls that happen to also have the LVS_EX_CHECKBOXES style set (like the modified version in this blog entry) will be suitable. It states, that the LVS_EX_CHECKBOXES is one requirement, but may not be the only requirement.

    If you still find the documentation lacking, simply file a defect report and see how things work out.

  13. @John Doe says:

    Also, mind the Return value description: "If this macro is applied to a list-view control that does not have check boxes enabled, the return value is not reliable."

Comments are closed.