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.).
|
Partners
More for Developers
Top Authors
- Voted: 13 times.
- Voted: 11 times.
- Voted: 11 times.
- Voted: 10 times.
- Voted: 9 times.
- Paul Kimmel 78 articles
- Tom Archer - MSFT 75 articles
- Zafir Anjum 61 articles
- Bradley Jones 43 articles
- Marius Bancila 31 articles



