Link to home
Start Free TrialLog in
Avatar of Ruskialt
RuskialtFlag for Denmark

asked on

Developing a smarter approach to using CTreeCtrl

I am looking for strategies for using tree controls. The goal is to extend the CTreeCtrl (or maybe even build a new custom CWnd descendant tree control) in a way that makes it easy to use, and that hides away most of the uggly struct and message tweeking needed.

At init I would want to add one single node, the root, to the tree control. And as user expands nodes, the tree would ask the node to list its children. For the tree to be able to ask nodes for children, we need some kind of "class ITreeNode" interface. Implementing this interface would enable an object to be represented as a node in the tree. And the object would itself be in charge of how it should be represented in the tree.

The tree this way visualizes the object structure in memory, which is a fair approach imo.

The treenode interface could look something like this...

class ITreeNode
{
   virtual CString GetTreeLabel() { return "Default"; }
   virtual void GetTreeChildren( CTreeNodeList& L ) {}
   virtual int GetTreeImage() { return 0; }
   virtual int GetTreeImageSelected( return GetTreeImage(); }
}

Did anyone experiment with anything like this?
Avatar of Jaime Olivares
Jaime Olivares
Flag of Peru image

Don't have to use ITreeNode interface, all can be done with callback functions.
About tree nodes, you can manage it as a continued array, beeing parent info just an ID

class CTreeNode {
    int ID;
    CString label;
    union {
         DWORD data;
         void *pointer;
    }
    int ParentID;
}




Avatar of Ruskialt

ASKER

So how to have "class SomeObject" represented in the tree then? My idea was to implement ITreeNode, to let the tree have an interface to ask about object state...

class SomeObject : pulic ITreeNode
{
// Implement treenode interface. The tree will ask
// me about my label, images and children through here
};
I don't want to pollute SomeObject with CTreeCtrl specific callback code, neither do I want others to dictate my representation in the tree. A clean interface implementation could solve both these issues, leaving everything to SomeObject.
Really that is currently solved with the lParam aproach, that you can use to point your real data. That's why I have imitated it with a more useful union to cast easily.

class CTreeNode {
    int ID;
    CString label;
    union {
         DWORD data;
         void *pointer;      <--- Point to your object here
    }
    int ParentID;
    int flags;   <--- Added some status flag here
}


With your approach, how is children added to the tree, eg how does SomeObject tell the tree about its children?
Avatar of AndyAinscow
I'll leave some code up to jaime but Ruskialt you are trying to re-invent the wheel.  This is already done for you.
When the user clicks on the '+' to expand the tree gets an event.  You respond to that event and fill the children in at that point in time.  All the tree requires is the information that there are children for it to display the '+' check box.
It's all there inside the CTreeCtrl, but there's alot of housekeeping involved in dynamic tree models. This is why I want to hide the MFC specific maths inside my own CTreeCtrl descendant, once and for all.

Only one object can list SomeObject's children - SomeObject itself. Therefor it would be nice to have SomeObject implement a ITreeNode interface. Then let the tree ask for child nodes when expanding.

I could add a single ITreeNode to the tree (the root) at startup. And the tree will automatically acquire list of children for nodes that need expand i the view.

Item data at each TVITEM could hold ITreeNode*, and the tree could expand nodes asking the ITreeNode* to list chilrend.

I have this up and running, but not really satisfied with the result when it comes to notifying other views about selection and nodes being deleted..

Sources of inspiration could be

Model-View-Controller
JTree (Java's Swing implementation)

I feel I'm actually (as you nicely put it, Andy) re-inventing the wheel - someone must have made such a solution for MFC, Therefor I hoped to have others share experiences.

Now worth 75 points.. That's all I have for now, sorry..
Thanks for the tips, I've seen those from codeproject - SimpleTree goes in the right direction, but still leaves alot of work for each and every node. I'm using the waitingtreectrl already, but still I'm not confident with my solution so far.

See it this simple way, what would the tree need to know about a node to draw the node?

1) the name of the node
2) the list of children

Now, if I create an interface for all nodes to implement, that answers these two simple questions - I could draw the entire tree..
I think you just have to derive an object from CTreeCtrl
Yes, to develop a treecontrol that talks to ITreeNode's
(I still think you are trying to do something that is already done and not difficult to implement but...)

Have 2 classes.  CMyTreeCtrl based on CTreeCtrl and CMyTreeObject based on CObject (because there are lots of collections for CObject based things).
You populate your CMyTreeCtrl with CMyTreeObject based items.
When the tree needs to expand an item it has a pointer to the CMyTreeObject (some param when you add the item to the tree eg. lParam).  This calls a virtual function in the CMyTreeObject which takes a pointer to the tree, the HTREEITEM of the current object and the CMyTreeObject now adds all the items it has as children to the tree.
Yes, you get the idea.. But instead of parsing the tree and the HTREEITEM, i'd prefer to pass a a simple list that the node could add other node pointers to.

The tree control could parse a std::list<ITreeNode*> list for the node to add children into. The tree control knows how to add those children to itself, and does so after having collected node pointers from the node. The node won't have to worry about HTREEITEM's or the CTreeCtrl implementation. Also, parsing the tree ctrl and the HTREEITEM would make a strong dependency to CTreeCtrl. Besides all the ITreeNodes would have too many possibilities ruining the tree control having a treectrl pointer passed to it. The GetChildren() function in the tree node interface is supposed to add children to the tree - nothing else (therefor I don't want to see the tree parsed to me).

I know, maybe I'm answering my own question here.. I do actually have the system running, trouble comes when deleting nodes, and selecting nodes - this is a corner of the concept I haven't solved yet.

The nodes must have a way to notify the tree about changes in the child list.
When selecting an object (in another view) the tree can't fin the selected object if the parent node is closed.

I'm raising to 80 points which is now all I've got. I'll promise to reward you two for your efforts, but still wait abit to let other have a change to join the discussion. Thanks for your comments so far!
The overall goal is to put the least of work into objects that need to represented in a tree structure...

In it's simple way as metioned before:

1) Name myself
2) List my children
If you pass some item to the tree then the tree 'knows' that item.  (Store the pointer to it as both Jaime and I have suggested).  Therefore it can call a function exposed by that item to notify it of a change.


<When selecting an object (in another view) the tree can't fin the selected object if the parent node is closed.>
The node has a parent (and that has a parent...).  Work your way up the chain until the tree has an item opened.  Now work back down the change forcing the tree to expand that branch until your item is reached.
In my CTreeCtrl implementaion i use SetItemData(), to put ITreeNode pointer at each HTREEITEM. All HTREEITEM holds ITreeNode's. The tree talks to it's nodes through here.

To select nodes not yet opened, I could have to have a

ITreeNode* GetParentNode();

function added to the ITreeNode interface, and traverse the tree to backwards as you suggest. I would have to add a data member to the ITreeNode interface though, and thereby introduce a bit of house keeping to the nodes. Eg remember to SetParentNode(this) for all childnodes I add. Also I could have a pointer to a ITree interface (implemented by my CTreeCtrl descendant) at each node, and always remember to SetTree(ITree* pTree). All this introduces two additional pointers at each ITreeNode instance..

So what do we have now:

//nodes talk to the tree through this interface
class ITree
{
   virtual void NotifyChildDeleted( ITreeNode* pChild );
   virtual void NotifyChildAdded( ITreeNode* pChild );
};

//tree talks to its nodes through this interface
class ITreeNode
{
protected:
   ITree* m_pTree;
   ITreeNode* m_pParentNode;
public:
   ITreeNode( ITree* pTree , ITreeNode* pParent );
   virtual void GetTreeChildren( std::list<ITreeNode*>& L );
   virtual CString GetTreeLabel() { return "Default"; }
   virtual int GetTreeImage() { return 0; }
   virtual int GetTreeImageSelected() { return GetTreeImage(); }
}

I wasn't happy for having datamembers in my interface class, but what the hek...

How do you guys handle trees in applications, do you find the CTreeCtrl cumbersome like I do?
SOLUTION
Avatar of AndyAinscow
AndyAinscow
Flag of Switzerland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>Still don't see the need to create an ITree class

Well, if I present the CTreeCtrl pointer to the nodes - they would all get the opotuniy to mess up things in the CTreeCtrl. The CTreeCtrl has way too many functions to call, and actually nodes are only supposed to do a few trivial tasks - therefore i'd prefer to encapsulate those simple opotunities on some kind of simple interface.

>Also, in case of ITreeNode, don't see the need to use m_pTree pointer

No, this is also nagging me. I would have the same tree control pointer spread out all over.. Then again objects that need be represented in a tree are usually of some size relatively large compared to the treectrl pointer.

>All methods of ITreeNode (node methods) could be contained in the CTreeCtrl derived class

What would CMyTreeCtrl know about SomeObject's children?
Thank you both for sharing thoughts and participating. If anyone reads this far and feels for commenting please do so even though the question is closed...