Connect a list container to a tree/list control

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

.

 

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.
  • 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

    }

     


  • 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.
  • 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


        }

    }

     


  • If rtti is not enabled, we can rely on C++ inheritance and do the following:




    • 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.).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read