Connect a list container to a tree/list control
Most user interface oriented applications need a container of some kind (for example, list) and a visual representation of this container in a tree or list control. When a programmer modifies an item in a container, it must also update this specific item in a tree/list control. Vice versa, if a user of the program modifies the contents of the item, programmer must also update the item contents in a container.
As an example, I will discuss tree control since it is more complex. Each level in a tree control can be (and usually is) represented with a different data structure or class.
Using a structure for a node item
Each structure representing a node item should be declared in such a way that its first data member is an enum type. Enum is declared separatelly and identifies different type of structures which are used in a tree control.
enum treeItems {
tiVehicleGroup,
// Planes, trucks, cars, ships etc.
tiVehicle,
// Toyota, Renault, Mercedes, Chrysler, etc.
tiPart
// Windshield, Wheels, Tyres, Engine, etc.
};
struct TVehicleGroup {
int Id;
// Initialized to tiVehicleGroup
CString Name;
.....
void *Handle;
};
struct TVehicle {
int Id;
// Initialized to tiVehicle
CString Name;
......
TVehicleGroup *Owner;
void *Handle;
};
These structures are organized into one or more containers. When populating a tree control, Name is used for item visual representation while a pointer to a data structure is associated with a tree item: Handle is initialized with a HTREEITEM handle returned from InsertItem function. It is declared as a pointer to void instead of HTREEITEM in order to allow a structure to be inserted in a tree or list control.
void TFoo::Foo(CTreeCtrl& tc, TVehicleGroup
*ptr)
{
HTREEITEM handle = tc.InsertItem(ptr->Name);
ptr->Handle = (void*)handle;
tc.SetItemData(handle,(DWORD)ptr);
}
void TFoo::Foo(CTreeCtrl& tc, TVehicle
*ptr)
{
HTREEITEM handle = tc.InsertItem(ptr->Name,
(HTREEITEM)ptr->Owner->Handle);
ptr->Handle = (void*)handle;
tc.SetItemData(handle,(DWORD)ptr);
}
Getting a pointer to a data structure given a handle to a tree item
is straightforward:
HTREEITEM handle = tc.GetSelectedItem();
TVehicle *ptr = (TVehicle*)tc.GetItemData(handle);
switch (ptr->Id) {
case tiVehicleGroup:
{
TVehicleGroup *vg = (TVehicleGroup*)ptr;
// Do something
}
break;
case tiVehicle:
{
TVehicle *vg = (TVehicleGroup*)ptr;
// Do something
}
break;
case tiPart:
....
break;
};
I can safely test an Id member of an unknown structure only if it is
assunmed that all structures associated with tree items have an Id as a
first data member. After detecting the exact type, I can safelly access
all its data members.
Using classes for node items
With classes it is not possible to rely on an in-memory representation of the class as I did with structures. Different approach is needed. First, declare a base class for all classes whose instances will be associated with tree items. This class contains all common data (name of tree item, handle to tree item etc.).
class TBaseForTreeItem : public CObject {
public:
CString Name;
void *Handle;
TBaseForTreeItem *Parent;
// ....
};
Then, concrete classes are declare using previous class as a base class.
class TVehicleGroup : public TBaseForTreeItem
{
....
};
class TVehicle : public TBaseForTreeItem {
....
};
Inserting an item into the tree control is the same as previously described. The difference is in getting the pointer to the object in a container. For this, we can use several different techniques:
- If all classes use a DECLARE_SERIAL or DECLARE_DYNAMIC macro, then rtti is enabled and we can use IsKindOf to get to the right object.
- If rtti is enabled but we don't use DECLARE_SERIAL or DECLARE_DYNAMIC macros, we can use a C++ defined dynamic_cast macro to get to the right object.
- If rtti is not enabled, we can rely on C++ inheritance and do the following:
HTREEITEM handle = tc.GetSelectedItem();
TBaseForTreeItem *obj = (TBaseForTreeItem*)tc.GetItemData(handle);
if (obj->IsKindOf(RUNTIME_CLASS(TVehicleGroup))
{
TVehicleGroup *vg = (TVehicleGroup*)obj;
// Do something
} else if (obj->IsKindOf(RUNTIME_CLASS(TVehicle))
{
TVehicle *v = (TVehicle*)obj;
// Do something
}
HTREEITEM handle = tc.GetSelectedItem();
TBaseForTreeItem *obj = (TBaseForTreeItem*)tc.GetItemData(handle);
TVehicleGroup *vg = dynamic_cast<TVehicleGroup*>(obj);
if (vg != NULL) {
// Do something
} else {
TVehicle *v = dynamic_cast<TVehicle*>(obj);
if (v != NULL) {
// Do something
}
}
- Define an enum treeItem { tiBase, tiVehicleGroup, tiVehicle, ... }.
- Add a virtual int GetId(void) member function to a TBaseForTreeItem and all derived classes.
- Implement this function in each class and return a different enum constant (based on a class type).
HTREEITEM handle = tc.GetSelectedItem();
TBaseForTreeItem *obj = (TBaseForTreeItem*)tc.GetItemData(handle);
switch (obj->GetId()) {
case tiVehicleGroup:
{
TVehicleGroup *vg = (TVehicleGroup*)obj;
// Do something
}
break;
case tiVehicle:
{
TVehicle *v = (TVehicle*)obj;
// Do something
}
break;
}
This technique can be used with all controls that allow a programmer to associate a DWORD with a control item (list boxes, combo boxes, listview etc.).

Comments
Help For problem about SetItemData
Posted by Legacy on 10/31/2003 12:00amOriginally posted by: Alberto
I have a problem....
Why when i use SetItemData with a specific item, all data items insert is replace with the new ItemData???
Tnx for help
ReplyHow can I do for freeing the memory?
Posted by Legacy on 06/03/2003 12:00amOriginally posted by: rab
Your example is very good but I have a problem whith freeing the memory of each item in the tree.
How ca I do, please?
ReplyFreeing the memory
Posted by Legacy on 10/15/2001 12:00amOriginally posted by: Coleman
I have seen different solutions to the problem of freeing the memory associated with each item in the tree control.
I've read that an appropriate place to do it is in OnTreeSelChanged(), since each item is visited when the control is destroyed. However, I've found this isn't the case.
So, I overloaded CTreeCtrl::OnDeleteItem, which is called for each item in the tree upon destruction, but alas, I'm still struggling with some memory leak issues pertaining to the lParam portion of the tree control.
Any ideas, or how has anyone else conquered this problem?
ReplyThanks
Posted by Legacy on 08/08/2000 12:00amOriginally posted by: Peter Kennedy
Thanks for the explanation. I have found your technique to be very useful.
ReplyA structural improvement - applying the observer pattern
Posted by Legacy on 09/20/1999 12:00amOriginally posted by: Tomaz Stih
Replyhelp
Posted by Legacy on 07/29/1999 12:00amOriginally posted by: casey xiang
i met the same problem, but don't how to do.
ReplyHow can you do this using STL container like set<>
Posted by Legacy on 02/17/1999 12:00amOriginally posted by: Brian Hart
How can you do this using STL container like set<>? The set<>::iterator won't cast to a void* or DWORD.
Reply