替换树视图中的项目时如何修复绘画伪影?
How to fix paint artifacts when replacing items in Tree View?
我在选项卡控件的内容区域中放置了一个树视图(树视图是选项卡控件的兄弟)。当我删除树视图项目、添加新的树视图项目,以及 select 其中之一时,树视图未正确绘制;新创建的+selected 项目上方的所有内容都是灰色的。有没有办法让树视图在删除和插入项目后正确绘制所有内容?
观察:
- 如果没有滚动条出现的树视图项目足够少,则树视图看起来不错。
- 如果没有与树视图相邻的选项卡控件,则树视图看起来没问题。
- 如果树视图是选项卡控件的子视图,则树视图看起来没问题(但 Tab 键无法在选项卡控件和树视图之间导航使用 GetNextDlgTabItem/IsDialogMessage).
- 如果我在插入新节点后没有 select 树视图中的任何项目,那么树视图看起来没问题。
当我将项目插入树中时,我调用 TreeView_InsertItem,然后调用 TreeView_SelectItem。 Full sample gist。在示例程序中,Ctrl+R 加速器替换了所有树节点并导致工件。
你这里有错误:
ACCEL accel[1]***; //change to accel[2]
accel[0].fVirt = FCONTROL | FVIRTKEY;
accel[0].key = 'R';
accel[0].cmd = IDM_REGENERATETREE;
accel[1].fVirt = FCONTROL | FVIRTKEY;
accel[1].key = 'S';
accel[1].cmd = IDM_SELECTRANDOM;
HACCEL haccel = CreateAcceleratorTable(accel, 2);
显示问题是在尝试保存以前的项目状态时引起的。如果从 addTreeItem
中删除 previousStates
,则不会出现显示问题。要正确保存状态,您可能需要 std::map 和每个树项的一些用户标识。至少你应该使用 std::vector 使其更容易理解。
为了获得更好的视觉效果,您可以将 TVS_LINESATROOT
添加到 TreeView 并将 WS_CLIPCHILDREN
添加到主视图 window。
编辑:
不应在 addTreeItem
中保存以前的项目状态。例如,刚刚插入的新项还没有子项,因此无法展开。简化addTreeItem
如下:
HTREEITEM addTreeItem(HWND htree, HTREEITEM par, HTREEITEM after, LPCTSTR str, LPARAM lp)
{
TVINSERTSTRUCT tvins;
tvins.hParent = par;
tvins.hInsertAfter = after;
tvins.itemex.mask = TVIF_TEXT | TVIF_PARAM;
tvins.itemex.pszText = const_cast<LPTSTR>(str);
tvins.itemex.lParam = lp;
HTREEITEM node = TreeView_InsertItem(htree, &tvins);
return node;
}
要保存以前的项目状态,每个项目都应该有一个不同的 ID。由于在这个示例中,每个节点的项目名称都不同,我们可以将其用于地图。但是如果这是一个目录结构就不行了,我们必须使用完整路径而不是节点名称。
void RootWindow::RegenerateTree()
{
if (!m_hwndTreeView) return;
if (!IsWindow(m_hwndTreeView)) return;
HWND hwnd = m_hwndTreeView;
//this will stop treeview from updating after every insert
SetWindowRedraw(hwnd, 0);
std::map<std::wstring, UINT> state;
const int maxtext = 260;
wchar_t buf[maxtext];
std::wstring selection;
UINT count = TreeView_GetCount(hwnd);
if (count)
{
for (HTREEITEM item = TreeView_GetRoot(hwnd); item; item = nextItem(hwnd, item))
{
TVITEM tv{ 0 };
tv.mask = TVIF_TEXT | TVIF_STATE;
tv.stateMask = TVIF_TEXT | TVIF_STATE;
tv.cchTextMax = maxtext;
tv.pszText = buf;
tv.hItem = item;
if (TreeView_GetItem(hwnd, &tv))
state[buf] = TreeView_GetItemState(hwnd, item,
TVIS_SELECTED | TVIS_EXPANDED);
}
}
TreeView_DeleteAllItems(hwnd);
addTreeItem...
addTreeItem...
addTreeItem...
//restore previous item state here:
if (count)
{
for (HTREEITEM item = TreeView_GetRoot(hwnd); item; item = nextItem(hwnd, item))
{
TVITEM tvitem{ 0 };
tvitem.hItem = item;
tvitem.mask = TVIF_TEXT;
tvitem.cchTextMax = maxtext;
tvitem.pszText = buf;
if (TreeView_GetItem(hwnd, &tvitem))
TreeView_SetItemState(hwnd, item, state[buf],
TVIS_SELECTED | TVIS_EXPANDED);
}
}
SetWindowRedraw(hwnd, 1);
}
我在选项卡控件的内容区域中放置了一个树视图(树视图是选项卡控件的兄弟)。当我删除树视图项目、添加新的树视图项目,以及 select 其中之一时,树视图未正确绘制;新创建的+selected 项目上方的所有内容都是灰色的。有没有办法让树视图在删除和插入项目后正确绘制所有内容?
观察:
- 如果没有滚动条出现的树视图项目足够少,则树视图看起来不错。
- 如果没有与树视图相邻的选项卡控件,则树视图看起来没问题。
- 如果树视图是选项卡控件的子视图,则树视图看起来没问题(但 Tab 键无法在选项卡控件和树视图之间导航使用 GetNextDlgTabItem/IsDialogMessage).
- 如果我在插入新节点后没有 select 树视图中的任何项目,那么树视图看起来没问题。
当我将项目插入树中时,我调用 TreeView_InsertItem,然后调用 TreeView_SelectItem。 Full sample gist。在示例程序中,Ctrl+R 加速器替换了所有树节点并导致工件。
你这里有错误:
ACCEL accel[1]***; //change to accel[2]
accel[0].fVirt = FCONTROL | FVIRTKEY;
accel[0].key = 'R';
accel[0].cmd = IDM_REGENERATETREE;
accel[1].fVirt = FCONTROL | FVIRTKEY;
accel[1].key = 'S';
accel[1].cmd = IDM_SELECTRANDOM;
HACCEL haccel = CreateAcceleratorTable(accel, 2);
显示问题是在尝试保存以前的项目状态时引起的。如果从 addTreeItem
中删除 previousStates
,则不会出现显示问题。要正确保存状态,您可能需要 std::map 和每个树项的一些用户标识。至少你应该使用 std::vector 使其更容易理解。
为了获得更好的视觉效果,您可以将 TVS_LINESATROOT
添加到 TreeView 并将 WS_CLIPCHILDREN
添加到主视图 window。
编辑:
不应在 addTreeItem
中保存以前的项目状态。例如,刚刚插入的新项还没有子项,因此无法展开。简化addTreeItem
如下:
HTREEITEM addTreeItem(HWND htree, HTREEITEM par, HTREEITEM after, LPCTSTR str, LPARAM lp)
{
TVINSERTSTRUCT tvins;
tvins.hParent = par;
tvins.hInsertAfter = after;
tvins.itemex.mask = TVIF_TEXT | TVIF_PARAM;
tvins.itemex.pszText = const_cast<LPTSTR>(str);
tvins.itemex.lParam = lp;
HTREEITEM node = TreeView_InsertItem(htree, &tvins);
return node;
}
要保存以前的项目状态,每个项目都应该有一个不同的 ID。由于在这个示例中,每个节点的项目名称都不同,我们可以将其用于地图。但是如果这是一个目录结构就不行了,我们必须使用完整路径而不是节点名称。
void RootWindow::RegenerateTree()
{
if (!m_hwndTreeView) return;
if (!IsWindow(m_hwndTreeView)) return;
HWND hwnd = m_hwndTreeView;
//this will stop treeview from updating after every insert
SetWindowRedraw(hwnd, 0);
std::map<std::wstring, UINT> state;
const int maxtext = 260;
wchar_t buf[maxtext];
std::wstring selection;
UINT count = TreeView_GetCount(hwnd);
if (count)
{
for (HTREEITEM item = TreeView_GetRoot(hwnd); item; item = nextItem(hwnd, item))
{
TVITEM tv{ 0 };
tv.mask = TVIF_TEXT | TVIF_STATE;
tv.stateMask = TVIF_TEXT | TVIF_STATE;
tv.cchTextMax = maxtext;
tv.pszText = buf;
tv.hItem = item;
if (TreeView_GetItem(hwnd, &tv))
state[buf] = TreeView_GetItemState(hwnd, item,
TVIS_SELECTED | TVIS_EXPANDED);
}
}
TreeView_DeleteAllItems(hwnd);
addTreeItem...
addTreeItem...
addTreeItem...
//restore previous item state here:
if (count)
{
for (HTREEITEM item = TreeView_GetRoot(hwnd); item; item = nextItem(hwnd, item))
{
TVITEM tvitem{ 0 };
tvitem.hItem = item;
tvitem.mask = TVIF_TEXT;
tvitem.cchTextMax = maxtext;
tvitem.pszText = buf;
if (TreeView_GetItem(hwnd, &tvitem))
TreeView_SetItemState(hwnd, item, state[buf],
TVIS_SELECTED | TVIS_EXPANDED);
}
}
SetWindowRedraw(hwnd, 1);
}