News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Adventures in Win32 programming: treeview checkboxes

Started by NoCforMe, May 05, 2024, 10:49:55 AM

Previous topic - Next topic

NoCforMe

This is my second outing with the treeview control. The project this time is an assembly-language skeleton program putter-together, and it turns out that a treeview is just perfect for this application. The idea is to show all the available pieces and parts that can be included in the source file, and the treeview is ideal. I started out placing checkbox controls for each item, but the treeview is so much easier: to add another item, no need to add a control to the dialog--just add another item to the treeview! Plus it can have nested items.

The new thing this time was adding checkboxes to the treeview. It's easy enough to get them: all you need is to add the TVS_CHECKBOXES style to the control when you create it, and they magically appear.

What's not so easy or obvious is how to use the checkboxes. The Microsoft Learn docs on the subject are pretty vague and don't tell you explicitly how to determine whether a treeview item has its checkbox checked. Actually, this part turns out to be easy: since the checkbox is an image that's added to each item, and since there are two "state" images (at least) for each item, in this case checked and not checked, it's a matter of getting the treeview item's state:
LOCAL tvi:TVITEM

; Set up TVITEM structure to pass to TVM_GETITEM:
MOV tvi._mask, TVIF_HANDLE or TVIF_STATE
MOV tvi.stateMask, TVIS_STATEIMAGEMASK
INVOKE SendMessage, TVhandle, TVM_GETITEM, 0, ADDR tvi

; tvi.state will now contain the item's checkbox state
Now this is a little tricky, since the state value is part of a larger bit-mapped field. The mask value TVIS_STATEIMAGEMASK is 0F000h, covering bits 12-15 of the state field. The value of the image state is 1-based, in this case 1 for unchecked, 2 for checked. So if you want those values from what's returned by TVM_GETITEMSTATE, you need to right-shift the value by 12. And it's confusing, since some of the messages return the unshifted value while others return just a regular ordinal number. So you need to be careful here.

OK, fine, so now I knew how to get the setting (state) of any treeview item's checkbox. But I set myself another goal: for nested items, I wanted to be able to check or uncheck all the child items by clicking on the parent's checkbox. You know, like those old program-installation wizards that gave you lists of features to install or leave out, same thing. Click on the parent item and all the child items are selected (or cleared).

The problem was finding out how to know when one of those dang checkboxes was clicked on. The treeview has the following notifications available that look like they might work here:
  • NM_CLICK (treeview)
  • TVN_ITEMCHANGED
  • TVN_ITEMCHANGING
  • TVN_SELCHANGED
  • TVN_SELCHANGING
At least these were all that I could find on this page. Well, long story short, I tried all of these: none of them worked. I could sort of tell when a checkbox was clicked on, but the results were unusable. I was trying to get the state of a parent item's checkbox after being clicked on and use that state to set all its children to the same state, checked or unchecked. Either it just didn't work at all, or it would toggle the state but in a delayed way.

After much more rooting around, I found another notification that wasn't even listed on that page:
NM_TVSTATEIMAGECHANGINGI don't know why it wasn't listed on that page which was supposed to give all treeview notifications, but it turned out that this one worked like a charm.

This gave me all the information I needed (through its NMTVSTATEIMAGECHANGING structure):
  • A handle to the treeview item, which I'd need to access that item
  • The state of the checkbox
So upon receipt of this notification, I did the following:
  • Get the handle to the treeview item from the NMTVSTATEIMAGECHANGING structure
  • Select that item using the TVM_SELECTITEM message
  • Use the TVM_GETITEM message to get info on that item, most importantly a pointer to my info structure
  • If that treeview item is a parent (there's a bit set in a flag for that), then I find all its children and set their state the same as the parent by sending them the TVM_SETITEM message

Note that because NM_TVSTATEIMAGECHANGING returns the unshifted value, you have to do a SHL 12 to get the value into the proper position for TVM_SETITEM.

Works great. Try out the attached .exe. Click on the "root" (top) item's checkbox and see what happens.

BTW, I discovered at least one page on Micro$oft Learn that is just plain wrong. It's the page about the TreeView_GetItemState macro, which basically does what I showed in my code above using TVM_GETITEM. But it says that there's no return value. WTF?!?!?!? If that were true, it would be useless. Fortunately, other sources of info, like Jose Roca's site, have the correct info (it does indeed return the state value). (Pick that macro from the list at left.)
Assembly language programming should be fun. That's why I do it.

NoCforMe

BTW (don't want to edit that post, as it messes up any tabbed text any time you edit!): neither the notification NM_TVSTATEIMAGECHANGING nor its associated structure (NMTVSTATEIMAGECHANGING) are in any of the MASM32 include files.
Assembly language programming should be fun. That's why I do it.

sinsi


NoCforMe

Yes, I know; that's what I used. (Ackshooly, I used TVM_GETITEM, which gets the state and more, but that message (TVM_GETITEMSTATE) would work. Turns out I needed more than just the state.)
I was just pointing out how wrong the documentation for that macro is.
Assembly language programming should be fun. That's why I do it.

sinsi