The Wayback Machine - https://web.archive.org/web/20100213121943/http://www.codeguru.com:80/Cpp/controls/treeview/misc-advanced/article.php/c739

    Connect a list container to a tree/list control

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

    • 1

    IT Offers






    The Network for Technology Professionals

    About Internet.com

    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers