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

  • Help For problem about SetItemData

    Posted by Legacy on 10/31/2003 12:00am

    Originally 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

    Reply
  • How can I do for freeing the memory?

    Posted by Legacy on 06/03/2003 12:00am

    Originally 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?

    Reply
  • Freeing the memory

    Posted by Legacy on 10/15/2001 12:00am

    Originally 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?


    Reply
  • Thanks

    Posted by Legacy on 08/08/2000 12:00am

    Originally posted by: Peter Kennedy

    Thanks for the explanation. I have found your technique to be very useful.

    Reply
  • A structural improvement - applying the observer pattern

    Posted by Legacy on 09/20/1999 12:00am

    Originally posted by: Tomaz Stih

    Your solution is a good and correct one but you are
    
    coupling the presentation and domain level on an
    equal basis.

    In an ideal solution domain level would know
    nothing about the presentation level.

    You can implement that by using the observer pattern.
    Every node in your domain level should be a publisher
    (if you think about it - the overhead is typically only
    two pointers=8 bytes) and every node in a tree control a
    subscriber.

    Then (from Zoran's code)

    void TFoo::Foo(CTreeCtrl& tc, TVehicleGroup *ptr)
    {
    HTREEITEM handle = tc.InsertItem(ptr->Name);
    ptr->Handle = (void*)handle;
    tc.SetItemData(handle,(DWORD)ptr);
    }

    Change it into

    void TFoo::Foo(CTreeCtrl& tc, TVehicleGroup *ptr)
    {
    HTREEITEM handle = tc.InsertItem(ptr->Name);
    ptr.Subscribe((CSubscriberData *)handle);
    tc.SetItemData(handle,(DWORD)ptr);
    }

    In your domain level have the Subscribe method storing
    a handle and then when notifying your subscriber (the tree
    control) also sending this subscriber data (so you give the
    tree control it's HTREEITEM but without knowing it
    is a HTREEITEM). Pretty smooth, a? :-)

    Regards,
    Tomaz

    Reply
  • help

    Posted by Legacy on 07/29/1999 12:00am

    Originally posted by: casey xiang

    i met the same problem, but don't how to do.

    Reply
  • How can you do this using STL container like set<>

    Posted by Legacy on 02/17/1999 12:00am

    Originally 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
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: March 27, 2014 Teams need to deliver quality software faster and need integrated agile planning, task tracking, source control, auto deploy with continuous builds and a configurable process to adapt to the way you work. Rational Team Concert and DevOps Services (JazzHub) have everything you need to build great software, integrated seamlessly together right out of the box or available immediately in the cloud. And with the Rational Team Concert Client, you can connect your …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds