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:

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