如何使用 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
,如评论中所建议的那样。
我找不到禁用 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
,如评论中所建议的那样。