1

I can't find a way to disable checkboxes in my TreeView control on specific items (actually I only need to enable checkboxes on specific items).

I have read this, this and this answer to no avail.

When creating the treeview items (that don't need checkboxes) I tried to set flags to :

tvinsert.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM; // attributes
tvinsert.item.stateMask = TVIS_STATEIMAGEMASK;
tvinsert.item.state = INDEXTOSTATEIMAGEMASK(0);

which are supposed to hide an item's checkbox but MSDN documentation says

Version 5.80. Displays a check box even if no image is associated with the item.

I am creating the treeview window control with

g_WindowHandleTreeView = CreateWindow(
    WC_TREEVIEW,
    "", //caption not required
    TVS_TRACKSELECT | WM_NOTIFY | WS_CHILD | TVS_HASLINES | TVS_LINESATROOT | WS_VISIBLE/* | TVS_CHECKBOXES*/,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    300,
    550,
    g_WindowHandlePannelStructure,
    NULL,
    (HINSTANCE)GetWindowLong(g_WindowHandlePannelStructure, GWL_HINSTANCE),
    NULL);

DWORD dwStyle = GetWindowLong(g_WindowHandleTreeView, GWL_STYLE);
dwStyle |= TVS_CHECKBOXES;
SetWindowLongPtr(g_WindowHandleTreeView, GWL_STYLE, dwStyle);

and then creating treeview items with

// Clear the treeview
TreeView_DeleteAllItems(hwnd);

// Tree items
std::vector<HTREEITEM> root_sub;
std::vector<HTREEITEM> mesh_items;
std::vector<HTREEITEM> mesh_items_sub;

TV_INSERTSTRUCT tvinsert = { 0 }; // struct to config the tree control
tvinsert.hParent = TVI_ROOT; // top most level Item
tvinsert.hInsertAfter = TVI_LAST; // root level item attribute.                            
tvinsert.item.mask = TVIF_TEXT | TVIF_PARAM; // attributes
tvinsert.item.stateMask = TVIS_STATEIMAGEMASK;
tvinsert.item.state = INDEXTOSTATEIMAGEMASK(0);
// ^^^ here trying to disable the checkbox but only prior to Version 5.80. ?

// Create root item
std::string rootTxt = "Model";
tvinsert.item.pszText = (LPSTR)rootTxt.c_str();
tvinsert.item.lParam = ID_MESH_ALL;
HTREEITEM Root = (HTREEITEM)SendMessage(hwnd, TVM_INSERTITEM, 0, (LPARAM)&tvinsert);

// Create path item
std::string pathTxt = std::string("Path : ") + pModel->objPath;
tvinsert.hParent = Root;
tvinsert.item.pszText = (LPSTR)pathTxt.c_str();
tvinsert.item.lParam = 0;
root_sub.push_back((HTREEITEM)SendMessage(hwnd, TVM_INSERTITEM, 0, (LPARAM)&tvinsert));

// More items....................

// Now attempting to change flags to ENABLE+CHECK the checkbox (which are always enabled anyways...)
tvinsert.item.state = INDEXTOSTATEIMAGEMASK(2);

// Create mesh header
std::string meshTxt = std::string("Mesh #") + std::to_string(mesh_items.size() + 1) + std::string(" - ") + std::to_string(mesh.v.size()) + std::string(" vertices");
tvinsert.hInsertAfter = mesh_root;
tvinsert.hParent = mesh_root;
tvinsert.item.pszText = (LPSTR)meshTxt.c_str();
tvinsert.item.lParam = ID_MESH_0 + mesh_items.size();
mesh_items.push_back((HTREEITEM)SendMessage(hwnd, TVM_INSERTITEM, 0, (LPARAM)&tvinsert));

// Disable flags
tvinsert.item.state = INDEXTOSTATEIMAGEMASK(0);

// ...

So what's the other way around ? I don't understand what subclassing my TreeView control is supposed to mean apart from giving it a different windows proc.

Expected behavior is having a checkbox displayed only next to select treeview items. I currently have a checkbox for all items.

Thanks for your insight.

Community
  • 1
  • 1
PinkTurtle
  • 6,942
  • 3
  • 25
  • 44
  • What is the expected behavior and what is the observed behavior? – IInspectable Dec 08 '15 at 17:51
  • Expected behavior is checkboxes displaying only near selected items. Currently I do have a checkbox for every treeview item. – PinkTurtle Dec 08 '15 at 18:37
  • 1
    Reading the documentation seems to imply, that you must assign an image if you want to prevent the checkbox image, that's automatically applied if no image is set (`INDEXTOSTATEIMAGEMASK(0)`). At a guess I would assume that you could replace the default state image list with one that has an additional transparent entry (see [Tree-View Image Lists](https://msdn.microsoft.com/en-us/library/windows/desktop/bb760017.aspx#tv_image_lists)), and use that extra image where you do not want any checkboxes to appear. – IInspectable Dec 08 '15 at 18:54
  • Remove `WM_NOTIFY` from `CreateWindow`.In order to remove checkbox you must first insert item normally and store its handle -> then you set `TVITEM` struct appropriately -> then you use `SendMessage` with `TVM_SETITEM`. If you want to have items checked when treeview is first displayed you need to set `TVINSERTSTRUCT` appropriately. This means you need to add extra flag before you insert item (add `TVIF_STATE`), set state the way you did, and then insert item. You send messages to treeview (`g_WindowHandleTreeView`) not your `hwnd`. Study code from my posts carefully. If help needed ask. – AlwaysLearningNewStuff Dec 14 '15 at 14:40
  • Thanks @AlwaysLearningNewStuff. Will check as soon as I can (probably tomorrow). – PinkTurtle Dec 14 '15 at 17:34
  • @IInspectable did try it as you suggested with a 3-images image list with index 0 being blank image, index 1 unchecked box and index 2 checked box. The problem stays exactly the same tho as when using index 0 ("no" checkbox), the default unchecked box is displayed instead (index 1). – PinkTurtle Dec 14 '15 at 17:40
  • @AlwaysLearningNewStuff removing the `TVIF_STATE` tag just doesn't work. Checkboxes are still displayed everywhere... Can you look at this gist : https://gist.github.com/anonymous/1d81177d60552510a93a ? This is my complete code for the treeview. Maybe you could point me to a working code example ? Thanks. – PinkTurtle Dec 16 '15 at 18:14
  • Dude it worked! All I was missing is a `TreeView_SetItem` call as you said with proper flags =) I thought `TreeView_SetItem` is for creating (set) an item but it seems it's used to modify an already created item. The `hItem` member of the `TVITEM` struct then needs to be set to the `HTREEITEM` to be modified (set). – PinkTurtle Dec 16 '15 at 18:43
  • So you have managed to solve all problems now??? – AlwaysLearningNewStuff Dec 16 '15 at 18:44
  • At least for displaying checkboxes yeah. Now I need to handle click events on said checkboxes :) – PinkTurtle Dec 16 '15 at 18:44
  • 1
    That can be solved quite easily if you do not plan to support Windows XP. You are looking for [NM_TVSTATEIMAGECHANGING](https://msdn.microsoft.com/en-us/library/windows/desktop/bb775572(v=vs.85).aspx). If you need to support XP too, then you will need to handle `NM_CLICK`, which gets tricky. I will try to help you with that but I am tired at the moment. If you need help leave a comment, but add @AlwaysLearningNewStuff in front of text so I can be automatically notified. – AlwaysLearningNewStuff Dec 16 '15 at 18:58
  • Will check that and no I don't care about XP :) – PinkTurtle Dec 16 '15 at 19:00
  • @AlwaysLearningNewStuff how/where do you catch the `NM_TVSTATEIMAGECHANGING` message ? – PinkTurtle Dec 16 '15 at 20:02
  • @PinkTurtle: You need to handle `WM_NOTIFY` message. In its handler, you must check if the notification is `NM_TVSTATEITMAGECHANGING` and then check if checkbox is checked or unchecked. You can also fix the bug I talked about in my answer to another question, when removed checkbox reappears. As I have said, I am tired so you will have to wait for tomorrow until i try and post an answer. For now, try to figure out how to handle `WM_NOTIFY` the way I described... – AlwaysLearningNewStuff Dec 16 '15 at 21:01
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/98178/discussion-between-pinkturtle-and-alwayslearningnewstuff). – PinkTurtle Dec 17 '15 at 00:48

2 Answers2

3

Here is how to create a treeview control with checkboxes and removing checkboxes on select nodes.

First create a window control without the TVS_CHECKBOXES checkbox style. For example :

g_WindowHandleTreeView = CreateWindow(
    WC_TREEVIEW,
    "",
    TVS_TRACKSELECT | WS_CHILD | TVS_HASLINES | TVS_LINESATROOT | WS_VISIBLE | TVS_HASBUTTONS,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    300,
    550,
    g_WindowHandlePannelStructure, // is the parent window control
    NULL,
    (HINSTANCE)GetWindowLong(g_WindowHandlePannelStructure, GWL_HINSTANCE),
    NULL);

Then add the checkbox style :

DWORD dwStyle = GetWindowLong(g_WindowHandleTreeView, GWL_STYLE);
dwStyle |= TVS_CHECKBOXES;
SetWindowLongPtr(g_WindowHandleTreeView, GWL_STYLE, dwStyle);

Now preparing an item for the treeview with an insert struct such as :

TV_INSERTSTRUCT tvinsert = { 0 }; // struct to config the tree control
tvinsert.hParent = TVI_ROOT; // root item
tvinsert.hInsertAfter = TVI_LAST; // last current position
tvinsert.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_STATE; // attributes
tvinsert.item.stateMask = TVIS_STATEIMAGEMASK;
tvinsert.item.state = 0;
tvinsert.item.pszText = (LPSTR)"Root node";
tvinsert.item.lParam = SOME_ID; // ID for the node

And inserting the node with a SendMessage(...) call :

HTREEITEM Root = (HTREEITEM)SendMessage(hwnd, TVM_INSERTITEM, 0, (LPARAM)&tvinsert);

The node will display a checkbox at this point (even with item.state set to 0) so all that's left to do is removing it :

TVITEM tvi;
tvi.hItem = Root; // The item to be "set"/modified
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0; // setting state to 0 again
TreeView_SetItem(hwnd, &tvi);

That's it.

AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
PinkTurtle
  • 6,942
  • 3
  • 25
  • 44
1

Here is a small demo that shows how to implement NM_TVSTATEIMAGECHANGING:

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>

#pragma comment( linker, "/manifestdependency:\"type='win32' \
                         name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
                         processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
                         language='*'\"")

#pragma comment( lib, "comctl32.lib")

// control IDs
#define IDC_TREEVIEW    2000

// init treeview
BOOL InitTreeView(HWND hwndTV)
{
    // enable checkboxes, the way it was recommended in MSDN documentation
    DWORD dwStyle = GetWindowLong(hwndTV, GWL_STYLE);
    dwStyle |= TVS_CHECKBOXES;
    SetWindowLongPtr(hwndTV, GWL_STYLE, dwStyle);

    TVINSERTSTRUCT tvis = { 0 };

    tvis.item.mask = TVIF_TEXT | TVIF_STATE;
    tvis.hInsertAfter = TVI_FIRST;
    tvis.hParent = NULL;
    tvis.item.pszText = L"Root item";

    HTREEITEM hti = (HTREEITEM)TreeView_InsertItem(hwndTV, &tvis);

    if (NULL == hti)
        return FALSE;

    tvis.hParent = hti;
    tvis.item.pszText = L"Second child node";
    tvis.item.stateMask = TVIS_STATEIMAGEMASK;
    tvis.item.state = 0 << 12;

    HTREEITEM htiChild = TreeView_InsertItem(hwndTV, &tvis);

    if (NULL == htiChild)
        return FALSE;

    tvis.item.pszText = L"First child node";
    tvis.item.stateMask = TVIS_STATEIMAGEMASK;
    tvis.item.state = 0 << 12;

    htiChild = TreeView_InsertItem(hwndTV, &tvis);

    if (NULL == htiChild)
        return FALSE;

    // remove checkbox
    TreeView_SetItemState(hwndTV, htiChild, 0, TVIS_STATEIMAGEMASK);
    // expand the root node
    TreeView_Expand(hwndTV, hti, TVE_EXPAND);

    // if we came all the way here then all is fine, report success
    return TRUE;
}

// main window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    switch (msg)
    {
    case WM_CREATE:
    {
        //================ create controls
        RECT rec = { 0 };
        GetClientRect(hwnd, &rec);

        HWND hwndTV = CreateWindowEx(0, WC_TREEVIEW, L"TreeView",
            WS_CHILD | WS_VISIBLE | WS_BORDER |
            TVS_FULLROWSELECT | TVS_HASBUTTONS |
            TVS_HASLINES | TVS_LINESATROOT |
            TVS_DISABLEDRAGDROP,
            10, 10, 200, 200,
            hwnd, (HMENU)IDC_TREEVIEW,
            ((LPCREATESTRUCT)lParam)->hInstance, NULL);

        // initialize treeview
        if (!InitTreeView(hwndTV))
            return -1;
    }
        return 0L;
    case WM_NOTIFY:
    {
        switch (((LPNMHDR)lParam)->code)
        {
        case NM_TVSTATEIMAGECHANGING:
        {
            // if item did not have checkbox, prevent state image change
            // NOTE: this approach does not work if you programatically change item's state !!!
            return (((LPNMTVSTATEIMAGECHANGING)lParam)->iOldStateImageIndex == 0);
        }
            break;
        default:
            break;
        }
    }
        break;
    case WM_CLOSE:
        ::DestroyWindow(hwnd);
        return 0L;
    case WM_DESTROY:
    {
        ::PostQuitMessage(0);
    }
        return 0L;
    default:
        return ::DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
    int nCmdShow)
{

    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    // register main window class
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"Main_Window";
    wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        // simple error indication
        MessageBeep(0);
        return 0;
    }

    // initialize common controls
    INITCOMMONCONTROLSEX iccex;
    iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    iccex.dwICC = ICC_TREEVIEW_CLASSES | ICC_LISTVIEW_CLASSES | ICC_STANDARD_CLASSES;
    InitCommonControlsEx(&iccex);

    // create main window
    hwnd = CreateWindowEx(0, L"Main_Window", L"Demonstration App",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, 0);

    if (NULL == hwnd)
    {
        // simple error indication
        MessageBeep(0);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    return Msg.wParam;
}

It worked for me on Windows 7, all you have to do is copy/paste this code into .cpp file and run it on Windows 10.

According to the comments, NM_TVSTATEIMAGECHANGING does not catch programmatic changes ( see the comment at the very bottom).

You might be better off with TVN_ITEMCHANGING, as suggested in the comments if you think of programmatically changing the state (on button click for example or whatever...).

AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
  • Thanks a lot. I will try this as soon as I can (next week). – PinkTurtle Dec 20 '15 at 14:17
  • 1
    @PinkTurtle: No problem, I will try to update my post with handling of TVN_ITEMCHANGING, whixh is safer way of handling "checkbox bug". Best regards until then, wish you good New Year time... – AlwaysLearningNewStuff Dec 20 '15 at 17:22
  • your example works perfect. I can't accept it as answer tho because it is off topic to my OP - nevertheless, thanks a lot for your help! =) It will serve as a great complement to the answer I posted here. I wish I could upvote you more than once :] I still haven't implemented it my code but I'll keep you up to date as to what was causing my problem. – PinkTurtle Dec 21 '15 at 15:01
  • 1
    Its OK, no problem. I am glad it helped. Still, I will try to adapt the above snippet to work with `TVN_ITEMCHANGING` since it is safer solution. I will leave a comment when I do that. Best regards until then. – AlwaysLearningNewStuff Dec 21 '15 at 15:03
  • Note `#pragma comment( linker, "/manifestdependency:\"type='win32' \ name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \ language='*'\"")` is all I was missing. – PinkTurtle Dec 27 '15 at 13:14