Programmieren mit C++

Borland C++

32-Bit-Programmierung

Strukturen in Knoten verwalten

Wie kann man in TreeViews Strukturen in den Knoten verwalten?

Frage

Die meisten Anwendungen benötigen für ihre Benutzeroberfläche Container wie beispielsweise Listen und die visuelle Repräsentation des Containers in einer TreeView oder ListView Control. Ändert das Programm einen Eintrag des Containers, muß auch der entsprechende Eintrag der Tree- oder List-Control aktualisiert werden. Umgekehrt muß das Ändern eines Eintrags im Benutzerinterface zu einer Aktualisierung der zugrunde liegenden Container-Strukturen führen.

Nachfolgend werden die grundlegenden Schritte am Beispiel einer TreeView Control aufgezeigt, wobei jeder Knoten eine andere Datenstruktur oder Klasse besitzen kann.

Lösung

Zunächst ist die Struktur für einen Knoten zu definieren. Diese Strukturen sollten so definiert sein, daß das erste Datenmember ein Aufzählungstyp ist. Diese Aufzählung muß separat definiert werden und identifiziert unterschiedliche Strukturtypen, die in der TreeView Control verwendet werden. In der Aufzählung werden daher Klassifizierungsmerkmale definiert.

Strukturen in Knoten

enum treeItems { 
  tiVehicleGroup, 
  tiVehicle,    
  tiPart 
}; 

Die hier verwendete Aufzählungsstruktur faßt die Begriffe wie nachfolgend aufgelistet zusammen.

Konstante

Bedeutung

tiVehicleGroup

Fortbewegungsmittel: Autos, Schiffe, Flugzeuge etc.

tiVehicle

konkrete Typen: Opel, BMW, VW, etc.

tiPart

Einzelteile: Windschutzscheibe, Reifen, Ruder etc.

Aufbauend auf diese Aufzählungsstruktur können konkrete Strukturen definiert werden, die auf die Aufzählungstypen Bezug nehmen und diese in ihrem ersten Feld Id aufnehmen.

Aufzählung treeItems

struct TVehicleGroup { 
  int     Id; // Inhalt: tiVehicleGroup
  CString Name; 
  void   *Handle; 
}; 
 
struct TVehicle { 
  int            Id; // Inhalt: tiVehicle 
  CString        Name; 
  TVehicleGroup *Owner; 
  void          *Handle; 
}; 

Diese Strukturen werden über einen oder mehrere Container verwaltet. Bei der Übernahme der Daten von den Containern in die TreeView Control werden die Inhalte der Felder Name für die visuelle Repräsentation verwendet, während ein Zeiger auf die zugehörige Datenstruktur des Knotens verweist: Handle wird mit dem HTREEITEM Handle, das von der Funktion InsertItem() returniert wird, belegt. Dieses Handle ist als void-Zeiger deklariert und nicht als HTREEITEM, damit Strukturen in eine Tree- oder List-Control eingefügt werden können. Eine mögliche Implementation zeigt die Beispielklasse TFoo, die entsprechende Referenzen aufbaut.

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); 
} 

Das Ermitteln eines Zeigers auf eine Datenstruktur zu einem gegebenen Handle eines Baumknotens ergibt sich dann wie folgt:

HTREEITEM handle = tc.GetSelectedItem(); 
TVehicle *ptr = (TVehicle*)tc.GetItemData(handle); 
switch (ptr->Id) { 
  case tiVehicleGroup: 
    { 
      TVehicleGroup *vg = (TVehicleGroup*)ptr; 
      // Behandlung
    } 
    break; 
  case tiVehicle: 
    { 
      TVehicle *vg = (TVehicleGroup*)ptr; 
      // Behandlung
    } 
    break; 
  case tiPart: 
       .... 
    break; 
}; 

Innerhalb der einzelnen case-Punkte können entsprechende benutzerdefinierte Aktionen erfolgen, die hier nicht weiter skizziert sind.

Die switch-Schleife zeigt dann auch nachträglich, warum das Member Id mit dem Aufzählungstyp als erstes Member in der Struktur vorhanden sein muß, da es nur so möglich ist, die Id einer unbekannten Struktur sicher zu testen. Der Cast setzt einfach voraus, daß das erste Member den Typ der Struktur offenbart, und verwendet die erhaltene Information, um auf die übrigen Member der Struktur zuzugreifen.

Definierte Strukturen

Mit Klassen ist es nicht möglich, darauf zu vertrauen, daß sie sich im Speicher so befinden, wie es bei den Strukturen möglich war, daß eine andere Vorgehensweise nötig ist. Zunächst ist eine Basisklasse zu definieren, von der alle übrigen Klassen abgeleitet werden müssen, die Elemente eines Knotens werden sollen. Diese Klasse enthält alle gemeinsamen Daten, wie Name des Eintrags, Handle zum Knoten etc.

Klassen als Knoten

class TBaseForTreeItem : public CObject { 
  public: 
     CString           Name; 
     void             *Handle; 
     TBaseForTreeItem *Parent; 
}; 

Die konkrete Klasse wird dann unter Bezugnahme auf die zuvor deklarierte Basisklasse implementiert.

Basisklasse TBaseForTreeItem

class TVehicleGroup : public TBaseForTreeItem { 
    .... 
}; 
 
class TVehicle : public TBaseForTreeItem { 
    .... 
}; 

Das Einfügen eines Eintrags in die TreeView Control erfolgt dann analog zu der zuvor aufgezeigten Vorgehensweise. Der einzige Unterschied besteht darin, den Zeiger auf ein Objekt zu ermitteln. Dazu sind verschiedene Ansätze möglich.

Benutzen alle Klassen ein DECLARE_SERIAL- oder ein DECLARE_DYNAMIC-Makro, dann ist rtti aktiviert, und die Methode IsKindOf() kann verwendet werden, um das richtige Objekt zu ermitteln.

HTREEITEM handle = tc.GetSelectedItem(); 
TBaseForTreeItem *obj = (TBaseForTreeItem*)tc.GetItemData(handle); 
if (obj->IsKindOf(RUNTIME_CLASS(TVehicleGroup)) 
{ 
   TVehicleGroup *vg = (TVehicleGroup*)obj; 
} else if (obj->IsKindOf(RUNTIME_CLASS(TVehicle)) 
       { 
         TVehicle *v = (TVehicle*)obj; 
       } 

Ist rtti aktiviert, ohne daß das DECLARE_SERIAL- oder das DECLARE_DYNAMIC-Makro verwendet wird, kann ein C++ definiertes dynamic_cast-Makro zum Erkennen des Objekts verwendet werden.

HTREEITEM handle = tc.GetSelectedItem(); 
TBaseForTreeItem *obj = (TBaseForTreeItem*)tc.GetItemData(handle); 
TVehicleGroup *vg = dynamic_cast

Ist rtti ganz deaktiviert, kann man auf die C++-Vererbung vertrauen und wie folgt vorgehen:

  • Zunächst ist eine Aufzählung treeItem { tiBase, tiVehicleGroup, tiVehicle, ... } zu deklarieren.
  • Anschließend wird eine Memberfunktion GetId(void) in TBaseForTreeItem und in alle von dieser Klasse abgeleiteten Klassen implementiert. Diese Methode muß in jeder Klasse die entsprechende enum-Konstante returnieren und damit die Klasse eindeutig festlegen.

Mit diesen Modifikationen wird es möglich, den korrekten Zeiger auf das zu einem TreeView-Eintrag gehörende Objekt zu ermitteln, indem wiederum ein Cast auf das Objekt erfolgt und über GetId() eine switch-Schleife implementiert wird.

HTREEITEM handle = tc.GetSelectedItem(); 
TBaseForTreeItem *obj = (TBaseForTreeItem*)tc.GetItemData(handle); 
switch (obj->GetId()) { 
   case tiVehicleGroup: 
     { 
       TVehicleGroup *vg = (TVehicleGroup*)obj; 
       // Behandlung
     } 
     break; 
   case tiVehicle: 
     { 
       TVehicle *v = (TVehicle*)obj; 
       // Behandlung
     } 
     break; 
}

Die hier beschriebene Technik kann mit allen Controls verwendet werden, die dem Programmierer erlauben, ein DWORD mit einem Element der Control zu verknüpfen. Somit kommen also u. a. in Frage: List- und Comboboxen, ListView und wie gezeigt TreeView.

Abgeleitete Klassen





Sachgebiet


© 2009-2012 by Alojado Publishing. Alle Rechte vorbehalten. Ausgewiesene Marken gehören ihren jeweiligen Eigentümern.
Mit der Benutzung dieser Seite erkennen Sie die Nutzungsbedingungen und die Datenschutzerklärung an. Der Betreiber übernimmt keine Haftung für den Inhalt verlinkter externer Internetseiten.
Seite erzeugt 2012-05-20 03:17:57 von textarchiv.alojado.de