1

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.

dan1st
  • 12,568
  • 8
  • 34
  • 67
AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
  • +100 points for perseverance! I saw your CP msg yesterday then found you'd solved that hurdle only to be faced with ClearText + XP issues. I sure don't envy you just now! I've got an idea, though I misread your question and need to re-think. I'd just climb back up the tree until you find the node whose parent is the root. I'd then close all other children of the root. I'll see if I can't bash some code out. :) – enhzflep May 15 '14 at 06:41
  • @enhzflep: I was scared that you "bailed on me", but then again responsibilities just keep coming when you are employed. I had various problems since we last "spoke" but managed to successfully solve them, except ClearType ( I doubt that one will be solved since the algorithm for creating transparent treeview is the problem. If I could draw nodes transparent with custom draw then things would be fine ). Thank you for trying to help! Best regards, it sure is a pleasure to hear you again :) – AlwaysLearningNewStuff May 15 '14 at 06:56
  • Sorry about that. Indeed, when it's not raining, its pouring! Hey, I'm thinking about your question some more, and, it seems that you only wish to have one grand-child (& its descendants) of TestProject open at one time. Is that correct? If so, couldn't you simply close other nodes (only) whenever a node was expanded? I mean, the first image wouldn't be possible unless the tree was created with all nodes expanded, would it? To show Components, you'd have to expand Assembly1, 2 or 3 - so I wonder if you actually only need to handle TVN_EXPANDED notifications? – enhzflep May 15 '14 at 07:08
  • @enhzflep: That was just an example picture. Indeed when created trees nodes are all collapsed. I thought to save the last expanded node handle and when user expands new node to just collapse saved handle and afterwards store the new node into that variable. I just have problems implementing this... In short, your conclusion is correct. – AlwaysLearningNewStuff May 15 '14 at 07:19
  • Ahhh! Okay then, I think I've got a nearly workable solution. I'll finish it up after dinner. – enhzflep May 15 '14 at 07:25

1 Answers1

2

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;
}
enhzflep
  • 12,927
  • 2
  • 32
  • 51
  • 1
    I gave a look at [`TVITEM structure`](http://msdn.microsoft.com/en-us/library/windows/desktop/bb773456%28v=vs.85%29.aspx) and saw `lParam` member. I could store `int` or `bool` value that can indicate whether item should be collapsed or not and use [`TreeView_SetItem`](http://msdn.microsoft.com/en-us/library/windows/desktop/bb760091%28v=vs.85%29.aspx) to update `lParam` on `TVN_ITEMEXPANDED` or `NM_CLICK`. [It seems I do not need pointers for that](http://msdn.microsoft.com/en-us/library/windows/desktop/hh298347%28v=vs.85%29.aspx) (I haven't tested your code yet, I will do so soon hopefully ). – AlwaysLearningNewStuff May 21 '14 at 18:16
  • Sounds like a good idea to me. I may also consider setting the lParam member to hold a pointer to a struct that contained other data as well, should that provide any benefit - it could make for an easy way to access any other data that pertains to the particular item. I commonly use this approach with ListViews, for instance. – enhzflep May 22 '14 at 00:31
  • If you make any headway, would you consider editing your post with that approach as well? – AlwaysLearningNewStuff May 22 '14 at 08:23
  • Yes, of course. I'm not sure exactly what would be considered headway though. I'll add any other thoughts either to the answer of as a comment below, if I'm able to ascertain what constitutes an improvement, or if you tell me yourself what would make the solution better. – enhzflep May 22 '14 at 20:30
  • It has a small visual artifact since my tree is transparent, but it does not matter. I will try to persuade my employers to ditch the "single expansion" altogether since it is inefficient ( it was a workaround for visual artifacts transparent treeview had when scrolling occurred, but I have fixed that ). Upvoted and officially accepted! Best regards :) – AlwaysLearningNewStuff May 26 '14 at 15:20
  • Ahhhh. I'd wondered about the purpose of automatically collapsing the tree. Now it makes perfect sense. Agreed, I'd been too frightened to perform a desk-check, on account of the amount of redundant work performed any time a node was opened - I'd feared being shocked. You could also argue that many users would find the way the control behaves as confusing since this behaviour is entirely non-standard (and to hide a bug, rather than add 'real' functionality) I read a post the other day that discussed the order of okay and cancel buttons - same concern (non standard UI). Oh, and you're welcome. – enhzflep May 27 '14 at 00:32
  • I am sorry for being a pain, but can you please help me with [this question](http://stackoverflow.com/questions/23892594/show-tooltip-on-invalid-input-in-edit-control)? I have tried everything in my power to solve it and got stuck... Thank you. Best regards :) – AlwaysLearningNewStuff May 27 '14 at 16:16
  • @AlwaysLearningNewStuff - sure, I'll have a try. I read the question earlier at CP. Just haven't fired up an IDE yet today, I've been in JS/HTML mode. Cheers. :) – enhzflep May 27 '14 at 16:18
  • I have made a significant progress, see the update here on SO. I am currently updating CP post... As soon as I finish this project I would like to learn JS/HTML, they seem as a better solution for GUI... Good luck with your coding :) – AlwaysLearningNewStuff May 27 '14 at 16:22
  • Thought so, I only read each quite quickly, I'm reading the SO one currently. Indeed, js and canvas are often where I do graphics prototyping - so very much quicker to make and test changes. Native c/c++ is often only double the speed for things I work on. Thanks mate, you too. :) – enhzflep May 27 '14 at 16:27