.
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).
With this modifications, it is possible to get the correct pointer to an
object associated with a tree item using the following:
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;
}
Conclusion
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.).