dcsimg

Connect a list container to a tree/list control

WEBINAR:
On-Demand

Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame


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



Comments

  • There are no comments yet. Be the first to comment!

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date