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

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

本文关键字:视图 项目 复选框 TVS 何使用 checkboxes 删除 样式      更新时间:2023-10-16

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

我读过这个,这个和这个答案,都没有用。

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

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

本应隐藏项目的复选框,但MSDN文档中显示

版本5.80。即使没有图像与关联,也会显示复选框项目。

我正在使用创建树状视图窗口控件

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

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

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

感谢您的真知灼见。

以下是如何创建一个带有复选框的树视图控件,并删除选择节点上的复选框。

首先创建一个不带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;
}

它在Windows7上对我有效,你所要做的就是将这些代码复制/粘贴到.cpp文件中,并在Windows10上运行。

根据评论,NM_TVSTATEIMAGECHANGING没有捕捉到程序更改(请参阅最底部的评论)。

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