Solved

Developing a smarter approach to using CTreeCtrl

Posted on 2004-10-19
20
1,281 Views
Last Modified: 2013-11-20
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?
0
Comment
Question by:Ruskialt
  • 11
  • 5
  • 4
20 Comments
 
LVL 55

Expert Comment

by:Jaime Olivares
Comment Utility
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;
}




0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
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
};
0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
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.
0
 
LVL 55

Expert Comment

by:Jaime Olivares
Comment Utility
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
}


0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
With your approach, how is children added to the tree, eg how does SomeObject tell the tree about its children?
0
 
LVL 44

Expert Comment

by:AndyAinscow
Comment Utility
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.
0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
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..
0
 
LVL 55

Expert Comment

by:Jaime Olivares
Comment Utility
0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
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..
0
 
LVL 55

Expert Comment

by:Jaime Olivares
Comment Utility
I think you just have to derive an object from CTreeCtrl
0
Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
Yes, to develop a treecontrol that talks to ITreeNode's
0
 
LVL 44

Expert Comment

by:AndyAinscow
Comment Utility
(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.
0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
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!
0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
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
0
 
LVL 44

Expert Comment

by:AndyAinscow
Comment Utility
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.
0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
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?
0
 
LVL 44

Assisted Solution

by:AndyAinscow
AndyAinscow earned 40 total points
Comment Utility
Not particulary.  It has limits but this that you want isn't one of them (IMHO).
0
 
LVL 55

Accepted Solution

by:
Jaime Olivares earned 40 total points
Comment Utility
Still don't see the need to create an ITree class, just need to derive an object from CTreeCtrl and add some members.
Also, in case of ITreeNode, don't see the need to use m_pTree pointer, it is inefficient and memory comsuming, because you will never manage a node without knowing first which treectrl object belongs to.
All methods of ITreeNode (node methods) could be contained in the CTreeCtrl derived class, just like CTreeCtrl does.

In resume, don't need to make all this mess, in my opinion, just have to make a "high level wrapper" for CTreeCtrl.
0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
>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?
0
 
LVL 2

Author Comment

by:Ruskialt
Comment Utility
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...
0

Featured Post

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

This is to be the first in a series of articles demonstrating the development of a complete windows based application using the MFC classes.  I’ll try to keep each article focused on one (or a couple) of the tasks that one may meet.   Introductio…
Introduction: Ownerdraw of the grid button.  A singleton class implentation and usage. Continuing from the fifth article about sudoku.   Open the project in visual studio. Go to the class view – CGridButton should be visible as a class.  R…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

16 Experts available now in Live!

Get 1:1 Help Now