如何使用 TVS_CHECKBOXES 样式删除特定树视图项目上的复选框

How to remove checkboxes on specific tree view items with the TVS_CHECKBOXES style

我找不到禁用 TreeView 控件中特定项目的复选框的方法(实际上我只需要 启用 特定项目的复选框)。

我已阅读this, this and this答案无济于事。

在创建树视图项(不需要复选框)时,我尝试将标志设置为:

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

应该隐藏项目的复选框但 MSDN documentation

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

我正在使用

创建树视图 window 控件
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);

然后使用

创建树视图项目
// 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);

// ...

那么反过来呢?我不明白除了给它一个不同的 windows 过程之外,我的 TreeView 控件的子类化应该意味着什么。

预期的行为是只在 select 树视图项目旁边显示一个复选框。我目前有一个用于所有项目的复选框。

感谢您的见解。

以下是如何在 select 个节点上创建带有复选框和删除复选框的树视图控件。

首先创建一个 window 控件 没有 TVS_CHECKBOXES 复选框样式。例如:

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);

然后添加复选框样式:

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

现在使用插入结构为树视图准备项目,例如:

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

并通过 SendMessage(...) 调用插入节点:

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

此时节点 显示一个复选框(即使 item.state 设置为 0)所以剩下要做的就是删除它:

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);

就是这样。

这是一个演示如何实现 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;
}

它在 Windows 7 上对我有用,你所要做的就是 copy/paste 将这段代码放入 .cpp 文件,然后 运行 它在 Windows 上10.

According to the comments、NM_TVSTATEIMAGECHANGING 没有捕捉到编程更改(请参阅最底部的评论)。

如果您考虑以编程方式更改状态(例如单击按钮或其他...),您最好使用 TVN_ITEMCHANGING,如评论中所建议的那样。