1

I have a Tree View positioned in the content area of a Tab Control (the Tree View is a sibling of the Tab Control). When I remove tree view items, add new tree view items, and select one of them, the tree view is not painted correctly; everything above the newly created+selected item is gray. Is there any way to make the tree view paint everything properly after removing and inserting items?

Before replacing items After replacing items

Observations:

  • If there are few enough tree view items that no scroll bar appears, then the tree view looks OK.
  • If there is no tab control adjacent to the tree view, then the tree view looks OK.
  • If the tree view is a child of the tab control, then the tree view looks OK (but the Tab key can’t navigate between the Tab Control and the Tree View using GetNextDlgTabItem/IsDialogMessage).
  • If I don’t select any items in the Tree View after inserting new nodes, then the tree view looks OK.

When I insert items into the tree, I call TreeView_InsertItem followed by TreeView_SelectItem. Full sample gist. In the sample program, the Ctrl+R accelerator replaces all the tree nodes and causes the artifacts.

yonran
  • 18,156
  • 8
  • 72
  • 97

1 Answers1

1

You have error here:

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

Display problems are caused when trying to save previous item state. There are no display problems if you remove previousStates from addTreeItem. To save the state properly you may need std::map and some user identification for each tree item. At least you should use std::vector to make it easier to follow.

For better visual effects you can add TVS_LINESATROOT to TreeView and WS_CLIPCHILDREN to main window.

Edit:

Saving previous item states shouldn't be done in addTreeItem. For example new item that was just inserted won't have children yet, so it can't be expanded. Simplify addTreeItem as follows:

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

To save previous item state, each item should have a different ID. As it happens in this example item's name is different for each node, we can use that for map. But if this was a directory structure it wouldn't work, we have to use fullpath instead of node name.

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);
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Thanks! Adding WS_CLIPCHILDREN to the top-level window, adding WS_CLIPSIBLINGS to the Tab Control, and calling SetWindowPos to move the Tree View on top of the Tab Control in z-order eliminated the gray regions. Now the only problem is that the buttons at the root level disappear when I recreate the tree. – yonran May 13 '15 at 05:04
  • That only fixes minor flicker. Even if you remove the tab control the problem remains. The main problem is with `previousStates ` in `addTreeItem`, that's why items disappear. – Barmak Shemirani May 13 '15 at 05:34
  • I have now revised the example to use a std::map instead of a LocalAlloc’d block. – yonran May 13 '15 at 15:48
  • You are not using `LPARAM` here, or maybe you intend to use it later. I changed it to `std::map` for now. – Barmak Shemirani May 13 '15 at 20:06
  • Actually I am using `LPARAM` as a unique node ID in this sample. But thank you for all the help! I no longer set TVIS_EXPANDED while creating the tree item; I wait until I have added the children. I call `TreeView_Expand` and `TreeView_SelectItem` instead of setting `TVIS_EXPANDED | TVIS_SELECTED` state. The drawing artifacts are now gone. And to reduce flicker I use `SetWindowRedraw`. – yonran May 13 '15 at 21:40
  • Yes that should work nicely. You can also use `TreeView_EnsureVisible` at the very end, once, to make sure selection is not scrolled out of view. – Barmak Shemirani May 13 '15 at 23:06