Search code examples
winapitreeviewcomctl32

How to fix paint artifacts when replacing items in Tree View?


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.


Solution

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