c++winapitreeviewitem

Collapse all nodes in treeview, except the last expanded one


INTRODUCTION AND RELEVANT INFORMATION:

I need to implement the following scenario:

  1. User expands a node, e.g. Node 1;

  2. User expands another node, e.g. Node 2;

  3. Collapse previous node ( Node 1 );

To visually explain what I mean, we shall use the following example image :

enter image description here

Now when user clicks on Assembly 1 node or its child node Components, I need to collapse every other node. An example picture below illustrates this :

enter image description here

MY EFFORTS TO IMPLEMENT THIS BEHAVIOR:

Browsing through Internet, and with a little thinking of my own, I was able to write helper function that collapses node and its children :

void CollapseNode( HWND hTree, HTREEITEM hti )
{
    if( TreeView_GetChild( hTree, hti ) != NULL )
    {
        TreeView_Expand( hTree, hti, TVE_COLLAPSE );
        hti = TreeView_GetChild( hTree, hti );
        do
        {
            CollapseNode( hTree, hti );
        }
        while( ( hti = TreeView_GetNextSibling( hTree, hti ) ) != NULL  );
    }
}

Reading through MSDN documentation I found TVN_ITEMEXPANDING and TVN_ITEMEXPANDED messages that might be useful.

I have also found NM_CLICK notification that seems interesting since I can use TVM_HITTEST message to test if click was on +/- button.

Also I have found TVN_KEYDOWN message that will help me with expanding when user presses left arrow or right arrow keys.

PROBLEM:

I can not think of an algorithm for using the above messages to solve my task.

QUESTION:

Can you please suggest me an algorithm for handling the above messages so I can call my CollapseNode(..) function ?

Something like this:

Hittest in NM_CLICK and then call your function or Store the last expanded item in a variable and collapse it in response to TVN_ITEMEXPANDED would be good for a start.

Thank you.


Solution

  • Not quite sure this is what you're after. From remarks made in the comments, I think the specs dictate that both Backgrounds and Assemblies should be able to remain open simultaneously, though the question seems to indicate that it should be an either/or situation.

    In any case, have a look at this.

    Basically, when I determine that a node is expanded, I find its ancestor that is a child of the root node. If it is the root node or one of the root-node's children, I do nothing. Otherwise, I call your CollapseNode function on all of the expanded-node's siblings.

    (I realize my comments are er, somewhat lacking. I'd be happy to clarify as needed)

    I should probably also draw your attention to the different behaviour observed when manually closing a node with expanded children VS calling CollapseNode on a node with expanded children.

    Lastly, you'll have to examine and change as necessary the control ID (IDC_TREEVIEW1) of the tree-view in the onNotify function.

    HTREEITEM getTopLevelParent(HWND treeWnd, TV_ITEM curItem)
    {
        HTREEITEM treeRoot, tmpItem;
        treeRoot = TreeView_GetRoot(treeWnd);
    
        tmpItem = curItem.hItem;
        if (tmpItem != treeRoot)
        {
            while (TreeView_GetParent(treeWnd, tmpItem) != treeRoot)
            {
                tmpItem = TreeView_GetParent(treeWnd, tmpItem);
            }
            /*
            TV_ITEM topLevelParent;
            wchar_t itemText[100];
            topLevelParent.hItem = tmpItem;
            topLevelParent.cchTextMax = 100;
            topLevelParent.pszText = itemText;
            topLevelParent.mask = TVIF_TEXT;
            TreeView_GetItem(treeWnd, &topLevelParent);
            wprintf(L"TopLevelParent (rootChild) Text: %s\n", itemText);
            */
            return tmpItem;
        }
        return NULL;
    }
    
    void CollapseNode( HWND hTree, HTREEITEM hti )
    {
        if( TreeView_GetChild( hTree, hti ) != NULL )
        {
            TreeView_Expand( hTree, hti, TVE_COLLAPSE );
            hti = TreeView_GetChild( hTree, hti );
            do
            {
                CollapseNode( hTree, hti );
            }
            while( ( hti = TreeView_GetNextSibling( hTree, hti ) ) != NULL  );
        }
    }
    
    void collapseAllChildrenExcept(HWND treeWnd, HTREEITEM parent, HTREEITEM dontClose)
    {
        HTREEITEM curNode;
    
        curNode = TreeView_GetChild(treeWnd, parent);
        if (curNode != NULL)
        {
            if (curNode != dontClose)
                    CollapseNode(treeWnd, curNode);
    
            while ((curNode = TreeView_GetNextSibling(treeWnd, curNode)) != NULL)
            {
                if (curNode != dontClose)
                    CollapseNode(treeWnd, curNode);
            }
        }
    }
    
    
    void onNotify(HWND hwnd, WPARAM wParam, LPARAM lParam)
    {
        if (wParam == IDC_TREEVIEW1)
        {
            LPNMHDR nmHdr = (LPNMHDR) lParam;
    
            if (nmHdr->code == TVN_ITEMEXPANDED)
            {
                NM_TREEVIEW FAR *pnmtv = (NM_TREEVIEW FAR *) lParam;
                if (pnmtv->action == TVE_COLLAPSE)
                    printf("TVE_COLLAPSE:\n");
                else if (pnmtv->action == TVE_EXPAND)
                {
                    printf("TVE_EXPAND: ");
    
                    HWND treeWnd = nmHdr->hwndFrom;
                    TV_ITEM curItem = pnmtv->itemNew;
    
    /*
                    curItem.mask = TVIF_TEXT;
                    curItem.cchTextMax = 100;
                    wchar_t itemText[100];
                    curItem.pszText = itemText;
                    TreeView_GetItem(treeWnd, &curItem);
                    wprintf(L"%s\n", curItem.pszText);
    */
    
                    HTREEITEM rootChild = getTopLevelParent(treeWnd, curItem);
                    if (rootChild != NULL)
                    {
    //                    printf("Need to close other nodes\n");
                        HTREEITEM parent, dontCloseMe;
                        parent = TreeView_GetParent(treeWnd, curItem.hItem);
                        dontCloseMe = curItem.hItem;
                        collapseAllChildrenExcept(treeWnd, parent, dontCloseMe);
                    }
    
    //                else
    //                    printf("Node requires no action to other nodes.\n");
                }
            }
        }
    }
    
    
    //  This function is called by the Windows function DispatchMessage()
    LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)                  // handle the messages
        {
            case WM_DESTROY:
                PostQuitMessage (0);       // send a WM_QUIT to the message queue
                break;
    
            case WM_NOTIFY:
                onNotify(hwnd, wParam, lParam);
                break;
    
            default:                      // for messages that we don't deal with
                return DefWindowProc (hwnd, message, wParam, lParam);
        }
        return 0;
    }