Grafische Interfaces VIII: Die Baumansicht (Kapitel 2)

Anatoli Kazharski | 14 September, 2016


Inhalt

 

 

Einführung

Detailliertere Informationen über den Zweck dieser Bibliothek findet sich ab dem ersten Artikel — Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1). Eine Liste der Kapitel mit den Verweisen findet sich am Ende der Artikel der einzelnen Teile. Sie können von da auch die komplette, aktuelle Version der Bibliothek herunterladen. Die Dateien müssen in die gleichen Verzeichnissen kopiert werden, wie sie sich in dem Archiv befinden.

Das vorherige Kapitel VIII der grafischen Schnittstellen hat sich mit den Elementen eines statischen und eines Dropdown-Kalenders beschäftigt. Das zweite Kapitel beschäftigt sich mit einem nicht weniger komplexen Element — der Baumansicht, die Teil aller kompletten Bibliotheken graphischer Schnittstellen ist. Die Baumansicht in diesem Artikel beinhaltet mehrere flexible Einstellungen und Modi und erlaubt daher die Elemente ganz Ihren Zielen anzupassen. 

Die Umsetzung andere nützlicher Klassen zur Erstellung von Mauspointer wird auch in diesem Artikel behandelt. Darüber hinaus zeigen wir auch die Verwendung dieser Elemente in einer Baumansicht.

 

 

Element der Baumansicht

Anders als eine einfache Liste erlaubt die Baumansicht, die Elemente unbegrenzt in verschachtelten Ebenen zu arrangieren. Jedes Element (oder Knoten), das eine Liste anderer Elemente (1 oder mehr) enthält, ist ausgestattet mit einem Steuerelement, das die lokale Liste ausblendet oder anzeigt. Abgesehen von einer Beschreibung durch einen Text kann jedem Element auch ein Piktogramm zur Vereinfachung für den Nutzer zugewiesen werden. Endpunkte (ohne lokale Listen) können Gruppen von Steuerelementen enthalten, die in einer grafischen Benutzeroberfläche einer Anwendung die Durchführung einer bestimmten Funktion ermöglichen.

Die Baumansicht kann verwendet werden, Kataloge zu erstellen, deren Elemente durch hierarchische Beziehungen verbunden sind. Zum Beispiel könnte man damit einen Dateinavigator ähnlich dem Windows Explorer erstellen. MetaTrader und MetaEditor verfügen ebenfalls über Navigationsfenster, die eine Baumansicht verwenden. 

Der MetaTrader zeigt/verbirgt das Navigationsfenster (siehe Bild unten) durch den Tastendruck ‘Ctrl+N’: 

 

Fig. 1. Das Fenster des "Navigators" im MetaTrader.

Der MetaEditor zeigt/verbirgt das Navigationsfenster (siehe Bild unten) durch den Tastendruck ‘Ctrl+D’: 

 

Fig. 2. Das Fenster des "Navigators" im MetaEditor.

 

 

Entwicklung der Klasse CTreeItem zur Erstellung der Baumansicht

Bevor wir aber die Klasse einer Baumansicht schreiben können, müssen wir erst zwei Elemente erzeugen. Das Element der Baumansicht ist der Schlüssel. Seine Struktur ähnelt zwar teilweise dem Kontextmenü (CMenuItem), das wir im Artikel Grafische Interfaces II: Das Menu-Item-Element (Kapitel 1) beschrieben haben, es hat jedoch einzigartige Eigenschaften und erfordert daher eine eigene Klasse. 

Das zweite Element ist ein Mauspointer. Er soll anzeigen, ob die Breite der Listenansicht zu ändern geändert werden kann. Um diese Art von Element zu erstellen wird eine eigene Klasse CPointer geschrieben, die auch von anderen Elementen verwendet werden kann. Die Klasse CPointer wird im Folgenden erläutert.

Zuerst müssen wir die Objekte bestimmen, die die Elemente einer Baumansicht benötigen.

Diese Elemente sind unten aufgelistet.

  1. Hintergrund
  2. Signal der Anwesenheit der lokalen Listeneinträge. Pfeile und Plus-/Minus-Piktogramme zeigen den Status (offen/minimiert) der Liste.
  3. Element-Namen. Es könnte zum Beispiel erforderlich sein, um visuell ein Objekt einer bestimmten Kategorie zuzuordnen. 
  4. Beschreibung des Elementes als Text.



 

Fig. 3. Komponenten einer Baumansicht.

Wir erstellen eine Datei namens TreeItem.mqh und laden sie in die Bibliothek (WndContainer.mqh):

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "TreeItem.mqh"

In der Datei TreeItem.mqh erstellen wir die Klasse CTreeItem mit den Standardmethoden für alle Elemente der Bibliothek (siehe den Quellcode unten):

//+------------------------------------------------------------------+
//|                                                     TreeItem.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| Klasse zur Erstellung eines Elementes der Baumansicht           |
//+------------------------------------------------------------------+
class CTreeItem : public CElement
  {
private:
   //--- Pointer auf das zugeordnete Element.
   CWindow          *m_wnd;
   //---
public:
                     CTreeItem (void);
                    ~CTreeItem (void);
   //--- Sichern des Pointers
   void              WindowPointer(CWindow &object)             { m_wnd=::GetPointer(object);            }
   //---
public:
   //--- Handhabung der Ereignisse des Charts
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void) {}
   //---Verschiebung eines Elementes
   virtual void      Moving(const int x,const int y);
   //--- (1) Zeigen, (2) Ausblenden, (3) Rücksetzen, (4) Löschen
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Installieren, (2) Rücksetzen der Klick-Prioritäten der linken Maustaste
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Rücksetzen der Farbe
   virtual void      ResetColors(void);
  };

Die folgenden Eigenschaften für die Einstellung seiner Erscheinung werden vor der Erstellung des Elements verfügbar sein.

  • Farben des Hintergrunds des Elements für die verschiedenen Zustände
  • Element-Namen
  • Pfeile, die den aktuellen Status (offen/minimiert) der Liste der Elemente anzeigen
  • Abstand des Τext-Namens
  • Textfarbe der verschiedenen Zustände
class CTreeItem : public CElement
  {
private:
   //--- Hintergrundfarbe der verschiedenen Zustände
   color             m_item_back_color;
   color             m_item_back_color_hover;
   color             m_item_back_color_selected;
   //--- Bildzeichen der Element-Pfeile
   string            m_item_arrow_file_on;
   string            m_item_arrow_file_off;
   string            m_item_arrow_selected_file_on;
   string            m_item_arrow_selected_file_off;
   //--- Element-Name
   string            m_icon_file;
   //--- Abstand des Text-Namens
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- Textfarbe der verschiedenen Zustände des Elementes
   color             m_item_text_color;
   color             m_item_text_color_hover;
   color             m_item_text_color_selected;
   //---
public:
   //--- Farbe des Element-Hintergrundes
   void              ItemBackColor(const color clr)                   { m_item_back_color=clr;                    }
   void              ItemBackColorHover(const color clr)              { m_item_back_color_hover=clr;              }
   void              ItemBackColorSelected(const color clr)           { m_item_back_color_selected=clr;           }
   //--- (1) Farbe des Elementes, (2) Symbolzeichen des Element-Pfeils
   void              IconFile(const string file_path)                 { m_icon_file=file_path;                    }
   void              ItemArrowFileOn(const string file_path)          { m_item_arrow_file_on=file_path;           }
   void              ItemArrowFileOff(const string file_path)         { m_item_arrow_file_off=file_path;          }
   void              ItemArrowSelectedFileOn(const string file_path)  { m_item_arrow_selected_file_on=file_path;  }
   void              ItemArrowSelectedFileOff(const string file_path) { m_item_arrow_selected_file_off=file_path; }
   //--- (1) Liefere den Element-Namen, (2) Abstand des Text-Namens
   string            LabelText(void)                            const { return(m_label.Description());            }
   void              LabelXGap(const int x_gap)                       { m_label_x_gap=x_gap;                      }
   void              LabelYGap(const int y_gap)                       { m_label_y_gap=y_gap;                      }
   //--- Textfarbe der verschiedenen Zustände
   void              ItemTextColor(const color clr)                   { m_item_text_color=clr;                    }
   void              ItemTextColorHover(const color clr)              { m_item_text_color_hover=clr;              }
   void              ItemTextColorSelected(const color clr)           { m_item_text_color_selected=clr;           }
  };

Wir benötigen 4 "private" und 1 "public" Methoden um die Baumansicht zu erstellen. Beachten Sie, dass das graphische Objekt CEdit für den Text-Namen verwendet wird. Wir werden in Kürze festlegen, was für diesen Zweck erforderlich ist.

class CTreeItem : public CElement
  {
private:
   //--- Objekte zur Erstellung der Baumansicht
   CRectLabel        m_area;
   CBmpLabel         m_arrow;
   CBmpLabel         m_icon;
   CEdit             m_label;
   //---
public:
   //--- Methode zur Erstellung der Baumansicht
   bool              CreateTreeItem(const long chart_id,const int subwin,const int x,const int y,const ENUM_TYPE_TREE_ITEM type,
                                    const int list_index,const int node_level,const string item_text,const bool item_state);
   //---
private:
   bool              CreateArea(void);
   bool              CreateArrow(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
  };

Werte der Parameter, die für die Positionierung eines Elementes in der Liste unter Berücksichtigung der anderen verwendet werden, müssen der "public" Methode CTreeItem::CreateTreeItem() übergeben werden. Einige von ihnen werden Teil des Namens des graphischen Objektes, aus denen das Element zusammengesetzt ist. Mit den Werten werden die relevanten Felder der Klasse initialisiert, um dann durch die "private" Methoden die Element-Objekte zu erstellen. 

Hier sind die wichtigsten Eigenschaften nachfolgend aufgelistet

  • Der Typ des Element kann zwischen zwei Optionen wählen: (1) einfach (TI_SIMPLE) ohne weitere Elemente, eine Endknoten, oder (2) mit weiteren Elementen (TI_HAS_ITEMS). Daher wird der Enums.mqh die Enumeration ENUM_TYPE_TREE_ITEM hinzugefügt:  
//+------------------------------------------------------------------+
//| Enumeration der Typen der Elemente der Baumansicht               |
//+------------------------------------------------------------------+
enum ENUM_TYPE_TREE_ITEM
  {
   TI_SIMPLE    =0,
   TI_HAS_ITEMS =1
  };
  • Gesamtindex der Liste.
  • Ebene (Nummer) des Knotens.
  • Anzeigetext (Beschreibung).
  • Status des Elements.

Achten Sie darauf, wie der Abstand der Pfeile berechnet wird (Signal für eine lokale Liste in einem Element). Anschließend wird jeder Knoten in dem Baum zum vorherigen Knoten.  

class CTreeItem : public CElement
  {
private:
   //--- Abstand der Pfeile (Signal)
   int               m_arrow_x_offset;
   //--- Typ des Elements
   ENUM_TYPE_TREE_ITEM m_item_type;
   //--- Index des Elements in der Liste
   int               m_list_index;
   //--- Ebene des Knotens
   int               m_node_level;
   //--- Anzeigetext
   string            m_item_text;
   //--- Status der Liste (offen/minimiert)
   bool              m_item_state;
  };
//+------------------------------------------------------------------+
//| Erstelle ein Element des Typs des Baums                                   |
//+------------------------------------------------------------------+
bool CTreeItem::CreateTreeItem(const long chart_id,const int subwin,const int x,const int y,const ENUM_TYPE_TREE_ITEM type,
                               const int list_index,const int node_level,const string item_text,const bool item_state)
  {
//--- Verlassen, wenn der Pointer ungültig ist
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Before creating a list item, we need to send "
              "a pointer to the form CTreeItem::WindowPointer(CWindow &object)") to the class list;
      return(false);
     }
//--- Initialisierung der Variablen
   m_id             =m_wnd.LastId()+1;
   m_chart_id       =chart_id;
   m_subwin         =subwin;
   m_x              =x;
   m_y              =y;
   m_item_type      =type;
   m_list_index     =list_index;
   m_node_level     =node_level;
   m_item_text      =item_text;
   m_item_state     =item_state;
   m_arrow_x_offset =(m_node_level>0)? (12*m_node_level)+5 : 5;
//--- Abstand vom Ankerpunkt
   CElement::XGap(CElement::X()-m_wnd.X());
   CElement::YGap(CElement::Y()-m_wnd.Y());
//--- Erstellen eines Menüelementes
   if(!CreateArea())
      return(false);
   if(!CreateArrow())
      return(false);
   if(!CreateIcon())
      return(false);
   if(!CreateLabel())
      return(false);
//--- Element ausblenden, wenn das Dialogfenster minimiert ist
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

Aus den folgenden Komponenten wird der Namen des Elementes gebildet (siehe den Code unten):

  • Programmname
  • Elementindex
  • Signal der Zugehörigkeit zum Element («treeitem»)
  • Signal der Zugehörigkeit zum Teil eines Elementes
  • Gesamtindex des Baumes
  • Element-Kennung

Als Beispiel verwenden wir eine "private" Methode, die die Elemente CTreeItem::CreateArrow() verbindet. Die Bibliothek enthält standardmäßig bereits Symbole für eine Dropdown-Liste. Sie können sie mit den angehängten Dateien am Ende des Artikels herunterladen. Sie können ggf. vor Erstellen eines Elements neu bestimmt werden. 

Der Abstand von der linken Kante des Elementes dieses Objektes wird mit dem Parameter m_node_level (Knotenebene) in der Hauptmethode berechnet, die ein Elemente vor den Elementobjekten erstellt. Stell sich heraus, dass es ein Element einfachen Typs (TI_SIMPLE) ist, wird die Methode verlassen. Beachten Sie, dass die Koordinaten dieses Objektes für die Prüfung des Elementtyps gesichert werden müssen (vor dem möglichen Verlassen der Methode), da sie für die Berechnung der Koordinaten der folgenden Elementobjekten benötigt werden. Das selbe Prinzip verwendet die Methode CTreeItem::CreateIcon(), um die Namen der Elemente zu bilden. 

Hier wird unmittelbar nach seiner Erstellung durch die Hauptmethode mit dem Wert von item_state der Status gesetzt, mit dessen Hilfe festgelegt wird, welche Elemente der Baumansicht nach der Erstellung geöffnet sind.  

//+------------------------------------------------------------------+
//| Erstelle einen Pfeil (Signal der Dropdown-Liste)                 |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_white.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_black.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_white.bmp"
//---
bool CTreeItem::CreateArrow(void)
  {
//--- Berechnung der Koordinaten
   int x =CElement::X()+m_arrow_x_offset;
   int y =CElement::Y()+2;
//--- Sichern der Koordinaten zur Berechnung der Koordinaten der weiteren Elementobjekte
   m_arrow.X(x);
   m_arrow.Y(y);
//--- Verlassen, wenn es keine weitere Dropdown-Liste gibt
   if(m_item_type!=TI_HAS_ITEMS)
      return(true);
//--- Bilden des Objektnamens
   string name=CElement::ProgramName()+"_"+(string)CElement::Index()+"_treeitem_arrow_"+(string)m_list_index+"__"+(string)CElement::Id();
//--- Bestimme das Standard-Symbolzeichen
   if(m_item_arrow_file_on=="")
      m_item_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_black.bmp";
   if(m_item_arrow_file_off=="")
      m_item_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp";
   if(m_item_arrow_selected_file_on=="")
      m_item_arrow_selected_file_on="Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_white.bmp";
   if(m_item_arrow_selected_file_off=="")
      m_item_arrow_selected_file_off="Images\\EasyAndFastGUI\\Controls\\RightTransp_white.bmp";
//--- Erstelle das Objekt
   if(!m_arrow.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Setze die Eigenschaften
   m_arrow.BmpFileOn("::"+m_item_arrow_file_on);
   m_arrow.BmpFileOff("::"+m_item_arrow_file_off);
   m_arrow.State(m_item_state);
   m_arrow.Corner(m_corner);
   m_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_arrow.Selectable(false);
   m_arrow.Z_Order(m_arrow_zorder);
   m_arrow.Tooltip("\n");
//--- Abstand zum Ankerpunkt
   m_arrow.XGap(x-m_wnd.X());
   m_arrow.YGap(y-m_wnd.Y());
//--- Sichere den Pointer auf das Objekt
   CElement::AddToArray(m_arrow);
   return(true);
  }

Wir benötigen Methoden, die die Größe (Breite) und die Farbe der Baumansicht nach der Erstellung steuern. Die Baumansicht wurde in der Art realisiert, dass, unabhängig vom Bereich der hierarchischen Liste, es eine Möglichkeit gibt, rechts des Baumes den gewählten Inhalt anzeigen zu lassen. Mit anderen Worten, es gibt eine Liste von Elementen als Teil eines gewählten Elementes der Baumansicht in dem Bereich. Wenn der Mauszeiger über der Grenze der beiden Bereiche schwebt und die linke Maustaste gedrückt wird, können (im selben Moment) die Breite der Baumansicht und den Inhalt der Liste ändern. 

Wird die Breite des Bereiches geändert, werden (1) die X Koordinaten des Objektes mit der Liste der Elemente und (2) die Breite des Objektes von beiden Listen aktualisiert. Daher wird der Objekttyp CEdit statt CLabel für die Textanzeige des Elementes verwendet. Wenn man Objekte vom Typ CLabel verwenden würde, könnte man bei der Änderung des Bereiches der Liste durch Texte, die über die Grenzen des Fensters hinausragen würden.

Um die X Koordinaten zu aktualisieren, schreiben wir die Methode CTreeItem::UpdateX(). Diese X Koordinaten müssen der Methode übergeben werden. In diesem Fall werden alle Berechnungen in der Klasse der Baumansicht durchgeführt. Zu diesem Punkt kehren wir in Kürze zurück. 

class CTreeItem : public CElement
  {
public:
   void              UpdateX(const int x);
  };
//+------------------------------------------------------------------+
//| Aktualisiere die X Koordinate                                    |
//+------------------------------------------------------------------+
void CTreeItem::UpdateX(const int x)
  {
//--- Aktualisiere die gemeinsamen Koordinaten und den Abstand vom Ankerpunkt
   CElement::X(x);
   CElement::XGap(CElement::X()-m_wnd.X());
//--- Abstabd der Koordinaten und des Hintergrundes
   m_area.X_Distance(CElement::X());
   m_area.XGap(CElement::X()-m_wnd.X());
//--- Koordinaten und der Pfeilabstand
   int l_x=CElement::X()+m_arrow_x_offset;
   m_arrow.X(l_x);
   m_arrow.X_Distance(l_x);
   m_arrow.XGap(l_x-m_wnd.X());
//--- Koordinaten und Symbolabstand
   l_x=m_arrow.X()+17;
   m_icon.X(l_x);
   m_icon.X_Distance(l_x);
   m_icon.XGap(l_x-m_wnd.X());
//--- Koordinaten und Abstand der Textanzeige
   l_x=(m_icon_file=="")? m_icon.X() : m_icon.X()+m_label_x_gap;
   m_label.X(l_x);
   m_label.X_Distance(l_x);
   m_label.XGap(l_x-m_wnd.X());
  }

Um die Breite der Listenelemente zu ändern, wird die Methode CTreeItem::UpdateWidth() verwendet: 

class CTreeItem : public CElement
  {
public:
   void              UpdateWidth(const int width);
  };
//+------------------------------------------------------------------+
//| Aktualisiere die Breite                                          |
//+------------------------------------------------------------------+
void CTreeItem::UpdateWidth(const int width)
  {
//--- Breite des Hintergrundes
   CElement::XSize(width);
   m_area.XSize(width);
   m_area.X_Size(width);
//--- Breite der Textanzeige
   int w=CElement::X2()-m_label.X()-1;
   m_label.XSize(w);
   m_label.X_Size(w);
  }

Zusätzlich zur Aktualisierung der X Koordinaten und der Elementbreite benötigen wir eine Methode, die Y Koordinate zu aktualisieren. Die Umsetzung der beiden Elemente der Liste, impliziert die Umsetzung des Elements (CTreeView), die ihrerseits aller Elemente erstellt und nicht nur deren Nummer angezeigt, wie das von den in früheren Artikeln in dieser Serie diskutiert Typen CListView, CLabelsTable und CTable getan wird. Diese Technik wird hier angewendet, wenn Elemente, die nicht in den Bereich der Liste passen, ausgeblendet werden. Statt einer regulären Aktualisierung mehrerer Parameter der Objekte, wenn Sie durch die Liste scrollen oder eine Teilliste öffnen/minimieren, werden nur die sichtbaren Elemente bearbeitet. Wenn es ausreicht, die momentan nicht notwendigen Elemente auszublenden, dann sollte die Y Koordinate im Moment der Anzeige aktualisiert werden. Zu diesem Zweck schreiben wir die Methode CTreeItem::UpdateY() mit dem unten stehenden Code. 

class CTreeItem : public CElement
  {
public:
   void              UpdateY(const int y);
  };
//+------------------------------------------------------------------+
//| Aktualisiere die Y Koordinate                                    |
//+------------------------------------------------------------------+
void CTreeItem::UpdateY(const int y)
  {
//--- Update common coordinates and offset from the extreme point
   CElement::Y(y);
   CElement::YGap(CElement::Y()-m_wnd.Y());
//--- Abstand von Koordinaten und Hintergrund
   m_area.Y_Distance(CElement::Y());
   m_area.YGap(CElement::Y()-m_wnd.Y());
//--- Koordinaten und Abstand des Pfeils
   int l_y=CElement::Y()+2;
   m_arrow.Y(l_y);
   m_arrow.Y_Distance(l_y);
   m_arrow.YGap(l_y-m_wnd.Y());
//--- Koordinaten und Abstand des Symbols
   l_y=CElement::Y()+2;
   m_icon.Y(l_y);
   m_icon.Y_Distance(l_y);
   m_icon.YGap(l_y-m_wnd.Y());
//--- Koordinaten und Abstand des Textnamens
   l_y=CElement::Y()+m_label_y_gap;
   m_label.Y(l_y);
   m_label.Y_Distance(l_y);
   m_label.YGap(l_y-m_wnd.Y());
  }

Die Farbe der Elemente wird in der Klasse der Baumansicht (CTreeView) kontrolliert und verlangt folgende Methoden:

  • Änderung der Farbe des Elementes abhängig vom Status;
  • Änderung der Farbe des Elementes abhängig von der Bewegung des Maus.

Der Code beider Methoden ist: 

class CTreeItem : public CElement
  {
public:
   //--- Änderung der Farbe des Elementes abhängig vom Status der Markierung
   void              HighlightItemState(const bool state);
   //--- Änderung der Farbe des Elementes abhängig von der Mausposition
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Änderung der Elementfarbe abhängig vom Status der Markierung     |
//+------------------------------------------------------------------+
void CTreeItem::HighlightItemState(const bool state)
  {
   m_area.BackColor((state)? m_item_back_color_selected : m_item_back_color);
   m_label.BackColor((state)? m_item_back_color_selected : m_item_back_color);
   m_label.BorderColor((state)? m_item_back_color_selected : m_item_back_color);
   m_label.Color((state)? m_item_text_color_selected : m_item_text_color);
   m_arrow.BmpFileOn((state)? "::"+m_item_arrow_selected_file_on : "::"+m_item_arrow_file_on);
   m_arrow.BmpFileOff((state)? "::"+m_item_arrow_selected_file_off : "::"+m_item_arrow_file_off);
  }
//+------------------------------------------------------------------+
//| Änderung der Farbe des Elementes abhängig von der Mausposition   |
//+------------------------------------------------------------------+
void CTreeItem::ChangeObjectsColor(void)
  {
   if(CElement::MouseFocus())
     {
      m_area.BackColor(m_item_back_color_hover);
      m_label.BackColor(m_item_back_color_hover);
      m_label.BorderColor(m_item_back_color_hover);
      m_label.Color(m_item_text_color_hover);
     }
   else
     {
      m_area.BackColor(m_item_back_color);
      m_label.BackColor(m_item_back_color);
      m_label.BorderColor(m_item_back_color);
      m_label.Color(m_item_text_color);
     }
  }

Als nächstes betrachten wir die Klasse für die Erstellung eines Zeigers der Maus in der grafischen Oberfläche. 

 


Die Klasse CPointer für die Verwendung der Maus

Ein Element der Baumansicht besteht aus zwei Flächen. Eine hierarchische Liste ist auf der linken Seite. Auf der rechten Seite befindet sich der Inhalt der markierte Teiles der Baumansicht. Mehr Details weiter unten, zunächst aber konzentrieren wir uns auf die Änderung der Größe (Weite) dieser Flächen, so wie es der Windows Explorer zeigt. Bewegt sich die Maus über die Grenze der Flächen der Baumansicht und der Fläche des Inhalts, verändert sich das Symbol der Maus in einen zweiseitigen Pfeil. Erstellen wir die Klasse CPointer mit den Funktionen, die eine Handhabung des Pointers erlauben. Der Systemcursor der Maus kann durch MQL in der aktuellen Version des Terminals nicht verändert werden, aber wir können ein Nutzerzeichen ergänzen, um es nutzerfreundlicher zu machen.

Die Klasse CPointer, wie auch die verbleibenden Elemente der Bibliothek, erben wir von der Klasse CElement. Sie können zu diesen Elementen verbinden, die verwendet werden. Es braucht keinen Zeiger darauf, da diese Objekte mit nichts verbunden sind. Er muss auch nicht der Basis der Elemente übergeben werden und die Kontrolle über dieses Element hat ausschließlich die Klasse, mit der er verbunden ist.

In dem Verzeichnis, in dem auch die Dateien und Bibliotheken liegen, erstellen wir die Datei Pointer.mqh mit der Klasse CPointer. Um einen Pointer zu erzeugen verwenden wir ein Objekt vom Typ CBmpLabel. Die Klasse CBmpLabel ist Teil der Datei Objects.mqh, dessen Inhalt bereits im ersten Teil dieses Artikels beschrieben.

//+------------------------------------------------------------------+
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
//+------------------------------------------------------------------+
//| Klasse zum Erstellen des Pointers des Mauscursors                |
//+------------------------------------------------------------------+
class CPointer : public CElement
  {
private:
   //--- Objekt zur Erstellung des Elementes
   CBmpLabel         m_pointer_bmp;
   /---
public:
                     CPointer(void);
                    ~CPointer(void);
   //---
public:
   //--- Bewegungen des Elementes
   virtual void      Moving(const int x,const int y);
   //--- (1) Zeigen, (2) Ausblenden, (3) Rücksetzen, (4) Löschen
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPointer::CPointer(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CPointer::~CPointer(void)
  {
  }

In der aktuellen Version der Bibliothek können wir eine von vier Arten von Zeigern für die Mauscursor festlegen. Der Bequemlichkeit zu Liebe fügen wir die Enumeration ENUM_MOUSE_POINTER zur Datei Enums.mqh (siehe den Quellcode unten). Abgesehen von den vier vorgegebenen Typen können wir auch einen Nutzertyp (MP_CUSTOM) wählen. 

//+------------------------------------------------------------------+
//| Enumeration der Indikatortypen                                   |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM     =0,
   MP_X_RESIZE   =1,
   MP_Y_RESIZE   =2,
   MP_XY1_RESIZE =3,
   MP_XY2_RESIZE =4
  };

Die Beschreibung der obigen Liste::

  • MP_CUSTOM — Nutzer-Typ.
  • MP_X_RESIZE — Wechsel der horizontalen Größe.
  • MP_Y_RESIZE — Wechsel der vertikalen Größe.
  • MP_XY1_RESIZE — Wechsel der Größe auf der Diagonalen 1.
  • MP_XY2_RESIZE — Wechsel der Größe auf der Diagonalen 2.

Vor der Erstellung eines Zeiger für den Mauscursor genügt es, seinen Typ zu bestimmen, die entsprechenden Symbole werden dann in dem Grafik-Objekt automatisch aus der Menge der vorgegebenen Ressourcen des Elements gewählt. Das Archiv mit diesen Symbolen ist am Ende dieses Artikels beigefügt, für Sie zum Herunterladen. 

//--- Resources
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_resize.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_resize_blue.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_resize.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_resize_blue.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize_blue.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize_blue.bmp"

Wenn Sie Ihre eigenen Symbole für den Pointer verwenden wollen, wählen Sie Typ MP_CUSTOM aus der Enumeration ENUM_MOUSE_POINTER und verwenden die Methoden CPointer::FileOn() und CPointer::FileOff() für den Pfad zu den Symbolen. 

class CPointer : public CElement
  {
private:
   //--- Symbole des Pointers
   string            m_file_on;
   string            m_file_off;
   //--- Typ des Pointers
   ENUM_MOUSE_POINTER m_type;
   //---
public:
   //--- Benennung des Pointers
   void              FileOn(const string file_path)       { m_file_on=file_path;         }
   void              FileOff(const string file_path)      { m_file_off=file_path;        }
   //--- Rückgabe und Bestimmung des Pointertyps
   ENUM_MOUSE_POINTER Type(void)                    const { return(m_type);              }
   void              Type(ENUM_MOUSE_POINTER type)        { m_type=type;                 }
  };

Es werden aber auch Methoden für die Aktualisierung der Koordinaten und Rückgabe/Setzen der Zustände des Zeigers benötigt: 

class CPointer : public CElement
  {
public:
   //--- Rückgabe/Setzen der Zustände des Pointers
   bool              State(void)                    const { return(m_pointer_bmp.State()); }
   void              State(const bool state)              { m_pointer_bmp.State(state);    }
   //--- Aktualisieren der Koordinaten
   void              UpdateX(const int x)                 { m_pointer_bmp.X_Distance(x);   }
   void              UpdateY(const int y)                 { m_pointer_bmp.Y_Distance(y);   }
  };

Der Konstruktor der Klasse des Pointers besitzt standardmäßig den Typen MP_X_RESIZE

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CPointer::CPointer(void) : m_file_on(""),
                           m_file_off(""),
                           m_type(MP_X_RESIZE)
  {
  }

Wir brauchen eine Methode, die, bevor das Element erstellt wurde, das Symbol für einen Zeiger bestimmt, je nachdem, welche Typ gewählt wurde Wir schreiben die Methode CPointer::SetPointerBmp() zu diesem Zweck. Passiert es, dass der Nutzer den Typ (MP_CUSTOM) gewählt hat, aber der Pfad zu den Symbolen nicht angegeben wurde, erscheint eine entsprechende Nachricht im Log.  

class CPointer : public CElement
  {
private:
   //--- Bestimme das Symbol für den Pointer des Mauscursors
   void              SetPointerBmp(void);
  };
//+------------------------------------------------------------------+
//| Symbolbestimmung des Pointers auf Basis des Typs                 |
//+------------------------------------------------------------------+
void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      case MP_X_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_resize_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_resize.bmp";
         break;
      case MP_Y_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_resize_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_resize.bmp";
         break;
      case MP_XY1_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize.bmp";
         break;
      case MP_XY2_RESIZE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize_blue.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize.bmp";
         break;
     }
//--- Wenn Nutzer-Typ (MP_CUSTOM) gewählt wurde
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Both icons should be set for a cursor pointer!");
  }

Wir benötigen noch eine "public" Methode CPointer::CreatePointer(), um ein Element zu erzeugen, der Code ist unten angeführt. Der Elementindex wird Teil des Namens des graphischen Objektes, dessen Wert wird durch den Konstruktor der Klasse CPointer erstellt und ist standardmäßig Null. Dies ist erforderlich, damit mehrere Objekte des Typs CPointer für verschiedene Zwecke innerhalb eines Elements mit dem Zeiger verbunden werden können. 

class CPointer : public CElement
  {
public:
   //--- Erstelle den Pointer
   bool              CreatePointer(const long chart_id,const int subwin);
  };
//+------------------------------------------------------------------+
//| Erstelle den Pointer                                             |
//+------------------------------------------------------------------+
bool CPointer::CreatePointer(const long chart_id,const int subwin)
  {
//--- Bilde den Objektnamen
   string name=CElement::ProgramName()+"_pointer_bmp_"+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- Bestimme die Symbole des Pointers
   SetPointerBmp();
//--- Erstelle ein Objekt
   if(!m_pointer_bmp.Create(m_chart_id,name,m_subwin,0,0))
      return(false);
//--- Einstellungen festlegen
   m_pointer_bmp.BmpFileOn("::"+m_file_on);
   m_pointer_bmp.BmpFileOff("::"+m_file_off);
   m_pointer_bmp.Corner(m_corner);
   m_pointer_bmp.Selectable(false);
   m_pointer_bmp.Z_Order(0);
   m_pointer_bmp.Tooltip("\n");
//--- Ausblenden des Objektes
   m_pointer_bmp.Timeframes(OBJ_NO_PERIODS);
   return(true);
  }

Jetzt haben wir alle Elemente einer Baumansicht erstellt und können uns jetzt mit der Klasse CTreeView beschäftigen, bevor wir versuchen sie selbst zu erzeugen. 

 


Entwicklung der Klasse CTreeView zur Erstellung der Baumansicht

Wir haben die Datei TreeView.mqh der Klasse CTreeView mit den Standardmethoden erstellt, so wie wir das mit allen Elementen der Bibliothek gemacht haben, und dann in die Datei WndContainer.mqh geladen:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "TreeView.mqh"

Listen wir einmal die Komponenten der Elemente einer Baumansicht auf.

  1. Hintergrund der Baumansicht
  2. Liste der Elemente der Baumansicht
  3. Vertikale Bildlaufleiste der Baumansicht
  4. Hintergrund der Inhaltsliste
  5. Liste der Elemente der Inhaltsliste
  6. Horizontale Bildlaufleiste der Inhaltsliste
  7. Pointer auf den Mauscursor, um eine Änderung der Breite der Baumansicht und Inhaltsfläche zu erfassen



 

Fig. 4. Komponenten der Baumansicht.

Symbol für den Pointer auf den Mauscursor wird ausgeblendet, nachdem es erstellt wurde. Er erscheint nur nahe der Grenze zweier Bereiche, wenn sich die Maus darüber befindet

Um ein Element der Baumansicht zu erstellen benötigen wir sieben "private" und eine "public" Methode: 

class CTreeView : public CElement
  {
private:
   //--- Objekte zur Erstellung des Elementes
   CRectLabel        m_area;
   CRectLabel        m_content_area;
   CTreeItem         m_items[];
   CTreeItem         m_content_items[];
   CScrollV          m_scrollv;
   CScrollV          m_content_scrollv;
   CPointer          m_x_resize;
   //---
public:
   //--- Methoden zur Erstellung der Baumansicht
   bool              CreateTreeView(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateContentArea(void);
   bool              CreateItems(void);
   bool              CreateScrollV(void);
   bool              CreateContentItems(void);
   bool              CreateContentScrollV(void);
   bool              CreateXResizePointer(void);
  };

Als Beispiel besprechen wir hier aber nur einen von ihnen – die Methode CTreeView::CreateXResizePointer() zur Erstellung eines Pointers des Mauscursors (siehe den Quellcode unten). Wir richten unsere Aufmerksamkeit auf folgende Details:

  • Wenn eine dieser beiden Bedingungen erfüllt ist, wird kein Pointer erstellt:
    • der Modus, der die Änderung der Breite der Liste erlaubt ist deaktiviert. 
    • der tabellarische Modus ist aktiviert.
  • Der Abstand des Pointers wird hier berechnet vom Systemcursor der Maus (dies Element ist nicht an das Fenster gebunden wie die anderen Elemente).
  • Wenn mehrere Pointer im Element benötigt werden, muss jedes ein eigenes Index-Element haben. Da die aktuelle Version der Baumansicht nur einen Pointer verwendet, können wir diese Möglichkeit hier ignorieren und die standardmäßige Initialisierung mit 0 akzeptieren.
  • Es ist notwendig, die Kennung des Elements anzugeben, die mit dem Pointer verbunden ist. Es ist darauf zu achten, dass potentielle Konflikte von Namen grafischer Objekte verschiedener Elemente vermieden werden, wenn Pointer von Cursor verwendet werden. 
//+------------------------------------------------------------------+
//| Erzeuge Pointer auf Cursor zur Änderung der Breite               |
//+------------------------------------------------------------------+
bool CTreeView::CreateXResizePointer(void)
  {
//--- Verlassen, wenn die Breite des Inhaltsbereiches nicht geändert werden muss oder
//    der tabellarische Modus aktiviert is
   if(!m_resize_content_area_mode || m_tab_items_mode)
      return(true);
//--- Festlegen von Eigenschaften
   m_x_resize.XGap(12);
   m_x_resize.YGap(9);
   m_x_resize.Id(CElement::Id());
   m_x_resize.Type(MP_X_RESIZE);
//--- Erstelle das Element
   if(!m_x_resize.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

Bevor wir andere Methoden der Klasse CTreeView beschreiben, schauen wir erstmal welche Funktionen die Baumansicht braucht. Wir realisieren die Klasse zur Erstellung dieses Elements so, dass es später möglich wird, hierarchische Listen für verschiedene Zwecke zu erstellen. 

Zum Beispiel sollten Sie Merkmale eines Designs zukünftiger Klassen von Dateinnavigatoren berücksichtigen. Bedenken Sie wie der Windows Explorer die Dateien präsentiert. Wenn eine Baumansicht auf der linken Seite des Fensters gezeigt wird (in Windows 7 ist das der Navigationsbereich), sehen Sie nur Verzeichnisse, keine Dateien. Alle Dateien werde im Inhaltsbereich des Windows Explorer gezeigt (siehe das Bild unten).


Fig. 5. Dateinavigator in Windows 7. Baumansicht links, Inhalt rechts.

Jedoch kann es in einer Baumansicht notwendig sein, zusätzlich zu den Ordnern Dateien anzuzeigen. Beispielsweise können Sie es so einrichten, dass bei der Auswahl einer Datei in einer Baumansicht im rechten Bereich ihr Inhalt angezeigt wird. Daher sollte, wenn die Klasse des Elements von Typ CTreeView für das Erstellen eines Dateinavigators verwendet wird, einem Nutzer die Möglichkeit gegeben werden, aus zwei Optionen zu wählen: (1) Anzeige von Verzeichnissen und Dateien oder (2) nur Verzeichnisse in der Baumansicht. Daher wird der Enums.mqh die Enumeration ENUM_FILE_NAVIGATOR_MODE hinzugefügt (siehe den Quellcode unten).

//+------------------------------------------------------------------+
//| Enumeration für den Modus eines Dateinavigators                  |
//+------------------------------------------------------------------+
enum ENUM_FILE_NAVIGATOR_MODE
  {
   FN_ALL          =0,
   FN_ONLY_FOLDERS =1
  };

Es ist nicht immer erforderlich, im Inhaltsbereich den Inhalt des Elements anzuzeigen, daher benötigen wir eine Option, das zu deaktivieren. So könnte es beispielsweise sinnvoll sein, die Baumansicht wie einen Tabellenreiter zu verwenden, der in der Bibliothek die Steuerelemente gruppiert, wie es im Artikel Grafische Interfaces VII: Das Tab-Control (Kapitel 2) beschrieben wird. 

Darüber hinaus bieten wir auch zusätzlichen Modi für eine genauere Einstellung der Baumansicht. Unten ist die komplette Liste der Modi der aktuellen Version.

  • Modus Dateinavigator
  • Modus Hervorheben, wenn der Mauscursor darüber ist
  • Modus Anzeige des Inhalts im Inhaltsbereich
  • Modus Änderung der Breite des Inhaltsbereiches
  • Modus tabellarische Objekte

Um die Modi der Elemente vor ihrer Erstellung festzulegen, sollten wir die unten angeführten Methoden verwenden: 

class CTreeView : public CElement
  {
private:
   //--- Modus Dateinavigator
   ENUM_FILE_NAVIGATOR_MODE m_file_navigator_mode;
   //--- Modus Hervorheben, wenn der Mauscursor darüber ist
   bool              m_lights_hover;
   //--- Modus Anzeige des Inhalts im Inhaltsbereich
   bool              m_show_item_content;
   //--- Modus Änderung der Breite des Inhaltsbereiches
   bool              m_resize_list_area_mode;
   //--- Modus Tab-Elemente
   bool              m_tab_items_mode;
   //---
public:
   //--- (1) Modus Dateinavigator, (2) Modus Hervorheben, wenn der Mauscursor darüber ist, 
   //    (3) Modus Anzeige des Inhalts im Inhaltsbereich, (4) Modus Änderung der Breite des Inhaltsbereiches, (5) Modus Tab-Elemente
   void              NavigatorMode(const ENUM_FILE_NAVIGATOR_MODE mode) { m_file_navigator_mode=mode;                   }
   void              LightsHover(const bool state)                      { m_lights_hover=state;                         }
   void              ShowItemContent(const bool state)                  { m_show_item_content=state;                    }
   void              ResizeListAreaMode(const bool state)               { m_resize_list_area_mode=state;                }
   void              TabItemsMode(const bool state)                     { m_tab_items_mode=state;                       }
  };

Die folgende Liste enthält Eigenschaften, die für die Einstellung der Darstellung des Elements verfügbar sind.

  • Die Breite der Baumansicht
  • Hintergrundfarbe
  • Farbe der Grenze des Hintergrundes
  • Breite des Inhaltsbereichs
  • Höhe der Elemente in einer Liste
  • Hintergrundfarbe des Elements in verschiedenen Zuständen
  • Textfarbe in verschiedenen Zuständen
  • Symbole als Anzeige für Inhalte des Elementes (das Symbol ist standardmäßig ein Rechtspfeil).
class CTreeView : public CElement
  {
private:
   //--- Die Breite der Baumansicht
   int               m_treeview_area_width;
   //--- Farbe des Hintergrunds und der Grenzen
   color             m_area_color;
   color             m_area_border_color;
   //--- Breite des Inhaltsbereichs
   int               m_content_area_width;
   //--- Höhe der Elemente in einer Liste
   int               m_item_y_size;
   //--- Farbe des Elements in verschiedenen Zuständen
   color             m_item_back_color_hover;
   color             m_item_back_color_selected;
   //--- Textfarbe in verschiedenen Zuständen
   color             m_item_text_color;
   color             m_item_text_color_hover;
   color             m_item_text_color_selected;
   //--- Symbole der Pfeile
   string            m_item_arrow_file_on;
   string            m_item_arrow_file_off;
   string            m_item_arrow_selected_file_on;
   string            m_item_arrow_selected_file_off;
   //---
public:
   //--- (1) Elementhöhe, (2) Breite der Baumansicht und und (3) Inhaltsliste
   void              ItemYSize(const int y_size)                        { m_item_y_size=y_size;                         }
   void              TreeViewAreaWidth(const int x_size)                { m_treeview_area_width=x_size;                 }
   void              ContentAreaWidth(const int x_size)                 { m_content_area_width=x_size;                  }
   //--- Farbe des Hintergrunds und der Grenzen
   void              AreaBackColor(const color clr)                     { m_area_color=clr;                             }
   void              AreaBorderColor(const color clr)                   { m_area_border_color=clr;                      }
   //--- Farbe des Elements in verschiedenen Zuständen
   void              ItemBackColorHover(const color clr)                { m_item_back_color_hover=clr;                  }
   void              ItemBackColorSelected(const color clr)             { m_item_back_color_selected=clr;               }
   //--- Textfarbe in verschiedenen Zuständen
   void              ItemTextColor(const color clr)                     { m_item_text_color=clr;                        }
   void              ItemTextColorHover(const color clr)                { m_item_text_color_hover=clr;                  }
   void              ItemTextColorSelected(const color clr)             { m_item_text_color_selected=clr;               }
   //--- Symbole der Pfeile
   void              ItemArrowFileOn(const string file_path)            { m_item_arrow_file_on=file_path;               }
   void              ItemArrowFileOff(const string file_path)           { m_item_arrow_file_off=file_path;              }
   void              ItemArrowSelectedFileOn(const string file_path)    { m_item_arrow_selected_file_on=file_path;      }
   void              ItemArrowSelectedFileOff(const string file_path)   { m_item_arrow_selected_file_off=file_path;     }
  };

Wir benötigen Felder für die Indices der markierten Elemente in der Baumansicht und in der Liste der Inhaltsbereiches. Vor der Erzeugung einer Baumansicht mit der Methode CTreeView::SelectedItemIndex(), können wir ein Element auswählen, das markiert wird gleich nach der Erstellung. 

class CTreeView : public CElement
  {
private:
   //--- Indices der ausgewählten Elemente der Liste
   int               m_selected_item_index;
   int               m_selected_content_item_index;
   //---
public:
   //--- (1) wählt das Element des Index, und (2) liefert den Index des ausgewählten Elements
   void              SelectedItemIndex(const int index)                 { m_selected_item_index=index;              }
   int               SelectedItemIndex(void)                      const { return(m_selected_item_index);            }
  };

 


Die Parameter der Liste der Elemente

Vor dem Erstellen eines Element vom Typ CTreeView müssen wir zuerst die Baumansicht erstellen. Wir benötigen eine Methode, um der Liste Elemente mit bestimmten Parametern hinzuzufügen. Sie werden benötigt, um eine Reihenfolge der Anordnung der Elemente für die Verwendung der Baumansicht zu erstellen. Ohne bestimmte Parameter kann keine hierarchische Reihenfolge korrekt erstellt werden. Eine vollständige Liste dieser Parameter wird im Folgenden vorgestellt.

  • Gesamtindex der Liste

Alle Elemente der Baumansicht werden in einer Schleife nacheinander aufgereiht, und der Index der aktuellen Iteration wird jedem Element zugewiesen. Beim Zugriff auf ein Element, egal wie sich die Baumansicht dem Nutzer augenblicklich präsentiert, wird der Gesamtindex jedes Elementes nach seiner Initialisierung nicht mehr geändert. 

Zum Beispiel sind für die Elemente A, B, C, D, E und F einer Liste ihre jeweiligen Indices 0, 1, 2, 3, 4 und 5 . Die Elemente B und C gehören zu Element A, und Element E zu D. Zur Veranschaulichung werden unten zwei Optionen der Baumansicht gezeigt. Links im Bild sieht man (1) eine erweiterte Liste in ununterbrochener Reihung . Rechst (2) dagegen sieht man die Elemente A und D, die - ausgeblendet - B, C und E enthalten, aber ihren ursprünglichen Index aus der Initialisierung behalten haben.

 

Fig. 6. Links 1) – vollständige, geöffnete Liste. Rechts 2) – tw. ausgeblendete Liste

  • Der Gesamtindex der vorherigen Liste der Knoten

Im Beispiel haben die Elemente des Wurzelverzeichnisses keine Vorgängerknoten, sie bekommen daher den Wert -1. Hat das Element einen Inhalt (lokale Liste von Elementen), erhalten alle seine Elemente den Index des Elementes dem sie zugeordnet sind. 

Da Bild zeigt die Elemente A, D und H, denen als Wurzelverzeichnis der Wert -1 zugewiesen wurde. Den Knoten B und C wurde der Gesamtindex [0] ihres Vorgängerknotens A, und den Knoten E, F und G der Gesamtindex [3] ihres Vorgängerknotens D zugewiesen.

 

Fig. 7. Allen untergeordneten Elementen wurde der jew. Gesamtindex zugeordnet.

  • Beschreibung des Elements (Anzeige von Text des Elementes)

Es können die Namen der Ordner und Dateien sein (im Falle eines Dateinavigators) oder die Namen von Kategorien und Unterkategorien oder Gruppen von Elementen (wenn Tab-Elemente erstellt werden).


  • Anzahl der Knotenebenen

Die Zählung beginnt bei Null. D.h. Knoten des Wurzelverzeichnisses erhalten den Wert 0. Ferner wird bei verschachtelten Elementen der Wert eines verschachtelten Element um 1 erhöht. Für ein besseres Verständnis werfen Sie einen Blick auf das Bild unten. Die oberen Zahlen entsprechen der Nummer ihrer Ebene. Die Ebene der Knoten A, D und J erhalten den Wert 0, die der Elemente B, C, E, I und K die Zahl 1, und F, G und H die 2.


 

Fig. 8. Die Ebenennummern der Knoten gemäß ihrer Verschachtelung.


  • Lokaler Index

Wenn ein Element einen Inhalt (eine lokale Liste von Elementen) hat, dann hat erhält diese Liste eine eigene Indizierung, die bei Null beginnt. Die Indizes der lokalen Listen sind die blau dargestellten Zahlen im Bild unten.

 

Fig. 9. Indizierung einer lokalen Liste.


  • Der lokale Index eines Vorgängerknotens

Es wird hier das gleiche Prinzip wie beim Gesamtindex der Vorgängerknoten angewendet. Elemente in einem Wurzelverzeichnis haben keine Vorgängerknoten, also erhält es den Wert -1. Elemente mit einem Vorgängerknoten erhalten dessen lokale Indexnummer. In der Abbildung unten werden die lokalen Indices der Vorgängerknoten rot dargestellt.

 

Fig. 10. Die lokalen Indices der Vorgängerknoten.


  • Knotenanzahl (Größe des Arrays der lokale Knotenliste). 

Der Typs (aus der Enumeration ENUM_TYPE_TREE_ITEM) wird abhängig von der Knotenanzahl dem Feld der Klasse CTreeItem zugewiesen. Das Bild zeigt die Anzahl der Elemente einer lokalen Liste als Zahl. Es zeigt, die Elemente A, D, E und J haben einen Inhalt (2, 2, 3 bzw. 1), die Knoten B, C, F, G, H, I und K aber sind leer (0).

 

Fig. 11. Anzahl der Elemente im lokalen Listen von Knoten.

  • Status des Elements. 

Eine lokale Liste (Inhalt) ist offen oder minimiert. Damit kann vor der Erstellung der Baumansicht festgelegt werden, welche Elemente geöffnet dargestellt werden sollen.

Im Bild unten sind alle Schlüsselparameter eines Elementes einer Baumansicht in einer einzigen Tabelle gezeigt. Für die eindeutige Kennzeichnung der Werte aller Parameter werden sie in unterschiedlichen Farben dargestellt. 


 

Fig. 12. Übersichtstabelle der (wichtigsten) Schlüsselparameter der Elemente einer Baumansicht.

Bei der Entwicklung einer Klasse für die Erstellung eines Dateinavigators, werden alle diese Parameter durch das Lesen der hierarchischen Struktur der allgemeinen und lokalen Verzeichnisse des Terminals automatisch berechnet. Das wird aber im Detail im nächsten Kapitel des Teils 8 beschrieben, dennoch können bereits hier einige Aspekte erwähnt werden, um die angepassten Methoden der Klasse (CTreeView) der Baumansicht des Dateinavigators zu erläutern. Mit Ausnahme der Anzahl der Elemente in der Liste der lokale Knoten, müssen, falls ein Dateinavigator erstellt wird, auch die Zahl der Elemente, die Ordner sind, angegeben werden. Ebenso verlangt jedes Element einen Parameter, der angibt, ob es ein Verzeichnis ist oder nicht. Im Kontext eines Dateinavigators bedeutet eine fehlende lokale Liste noch nicht, dass es dann eine Datei sein muss.

Aber bei der Erstellung einer einfachen Baumansicht sollte seine Struktur selbstständig sich bilden. Auf jeden Fall wird dafür die selbe Methode CTreeView::AddItem() in beiden Fällen verwendet. Das erlaubt, ein Element mit speziellen Parametern, die als Argumente übergeben werden, einer Baumansicht hinzuzufügen. Der Code unten zeigt diese Methode und die Liste von Arrays, die alle Parameter aufnehmen. Vergessen Sie nicht, Sie können für jedes Element ein eigenes Icon festlegen.

class CTreeView : public CElement
  {
private:
   //--- Arrays aller Elemente der Baumansicht (allgemeine Listen)
   int               m_t_list_index[];
   int               m_t_prev_node_list_index[];
   string            m_t_item_text[];
   string            m_t_path_bmp[];
   int               m_t_item_index[];
   int               m_t_node_level[];
   int               m_t_prev_node_item_index[];
   int               m_t_items_total[];
   int               m_t_folders_total[];
   bool              m_t_item_state[];
   bool              m_t_is_folder[];
   //---
public:
   //--- Hinzufügen eines Elementes zur Baumansicht
   void              AddItem(const int list_index,const int list_id,const string item_name,const string path_bmp,const int item_index,
                             const int node_number,const int item_number,const int items_total,const int folders_total,const bool item_state,const bool is_folder=true);
  };
//+------------------------------------------------------------------+
//| Hinzufügen eines allg. Arrays zur Baumansicht                    |
//+------------------------------------------------------------------+
void CTreeView::AddItem(const int list_index,const int prev_node_list_index,const string item_text,const string path_bmp,const int item_index,
                        const int node_level,const int prev_node_item_index,const int items_total,const int folders_total,const bool item_state,const bool is_folder)
  {
//--- Vergrößere die Größe der Arrays um ein Element
   int array_size =::ArraySize(m_items);
   m_items_total  =array_size+1;
   ::ArrayResize(m_items,m_items_total);
   ::ArrayResize(m_t_list_index,m_items_total);
   ::ArrayResize(m_t_prev_node_list_index,m_items_total);
   ::ArrayResize(m_t_item_text,m_items_total);
   ::ArrayResize(m_t_path_bmp,m_items_total);
   ::ArrayResize(m_t_item_index,m_items_total);
   ::ArrayResize(m_t_node_level,m_items_total);
   ::ArrayResize(m_t_prev_node_item_index,m_items_total);
   ::ArrayResize(m_t_items_total,m_items_total);
   ::ArrayResize(m_t_folders_total,m_items_total);
   ::ArrayResize(m_t_item_state,m_items_total);
   ::ArrayResize(m_t_is_folder,m_items_total);
//--- Sichern der übergebenen Werte
   m_t_list_index[array_size]           =list_index;
   m_t_prev_node_list_index[array_size] =prev_node_list_index;
   m_t_item_text[array_size]            =item_text;
   m_t_path_bmp[array_size]             =path_bmp;
   m_t_item_index[array_size]           =item_index;
   m_t_node_level[array_size]           =node_level;
   m_t_prev_node_item_index[array_size] =prev_node_item_index;
   m_t_items_total[array_size]          =items_total;
   m_t_folders_total[array_size]        =folders_total;
   m_t_item_state[array_size]           =item_state;
   m_t_is_folder[array_size]            =is_folder;
  }

Vor der Erzeugung eines Elementes gibt es eine Gelegenheit, ihm seine Parameter und die Anfangsbreiten der Fenster der Baumansicht und des Inhaltsbereiches zu bestimmen. Standardmäßig wird der Wert des Feldes für die Breite des Inhaltsbereiches der Klasse initialisiert mit WRONG_VALUE (siehe den Quellcode unten). Das bedeutet, wenn die Breite des Inhaltsbereiches nicht gesetzt werden kann, werden drei Komponenten des Elementes nicht erstellt: (1) der Hintergrund des Inhaltsbereiches, (2) die Arrays der Elemente dieses Bereiches und (3) die Bildlaufleiste dieses Bereiches. Das passiert nicht, selbst wenn der Modus "Zeige den Inhalt des markierten Elementes in der Baumansicht" gewählt wurde. Daher kann man die Darstellung der Liste unter Beibehalt des Hintergrundes deaktivieren, aber nicht andersherum.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTreeView::CTreeView(void) : m_treeview_area_width(180),
                             m_content_area_width(WRONG_VALUE),
                             m_item_y_size(20),
                             m_visible_items_total(13),
                             m_tab_items_mode(false),
                             m_lights_hover(false),
                             m_show_item_content(true)
  {
//--- Sichere den Namen der KLasse des Elements in der Basisklasse
   CElement::ClassName(CLASS_NAME);
//--- Bestimme die Prioritäten der Klicks der linken Maustaste
   m_zorder=0;
  }

Ein eigene Liste dynamische Arrays wird für die Liste der Elemente des Inhaltsbereiches benötigt. Sie sind nicht so groß wie für die Baumansicht, da eine hierarchische Ordnung nicht notwendig ist. Um eine solche Liste zu erstellen, werden nur drei Parameter benötigt.

  • Der Gesamtindex der Liste der Inhaltsbereiche
  • Der Gesamtindex der Baumansicht
  • Beschreibung des Artikels (Anzeigetext)
class CTreeView : public CElement
  {
private:
   //--- Arrays für die Liste der der Elemente des gewählten Elementes in der Baumansicht (gesamte Liste)
   int               m_c_list_index[];
   int               m_c_tree_list_index[];
   string            m_c_item_text[];
  };

Das Setzen der Größe und die Initialisierung dieser Arrays geschieht in der Methode, die den Inhalt der Liste erstellt CTreeView::CreateContentItems(). Alle Elemente werden den Arrays hinzugefügt, unabhängig davon, ob sie dem Wurzelverzeichnis angehören, da es keinen übergeordneten Knoten gibt, der in einer Baumansicht ausgewählt werden könnte. Hier am Anfang der Methode erkennt man eine Prüfung der Modi, von denen die Erstellung des Inhaltsbereiches abhängt.

//+------------------------------------------------------------------+
//| Erstelle die Inhaltsliste des gewählten Elementes                |
//+------------------------------------------------------------------+
bool CTreeView::CreateContentItems(void)
  {
//--- Verlassen, wenn der Inhalt nicht anzuzeigen ist oder
//    wenn der Inhaltsbereich anzuzeigen ist
   if(!m_show_item_content || m_content_area_width<0)
      return(true);
//--- Koordinaten und Breite
   int x =m_content_area.X()+1;
   int y =CElement::Y()+1;
   int w =m_content_area.X2()-x-1;
//--- Zähler der Elemente
   int c=0;
//--- 
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
     {
      //--- Elemente des Wurzelverzeichnisses können nicht Teil der Liste werden, 
      //    daher, wenn die Knotenebene kleiner als 1 ist, machen wir folgendes
      if(m_t_node_level[i]<1)
         continue;
      //--- Erhöhe die Arraygröße um 1 Element
      int new_size=c+1;
      ::ArrayResize(m_content_items,new_size);
      ::ArrayResize(m_c_item_text,new_size);
      ::ArrayResize(m_c_tree_list_index,new_size);
      ::ArrayResize(m_c_list_index,new_size);
      //--- Berechnung der Y Koordinate
      y=(c>0)? y+m_item_y_size-1 : y;
      //--- Übergabe des Objektes
      m_content_items[c].WindowPointer(m_wnd);
      //--- Einstellungen vor der Erzeugung
      m_content_items[c].Index(1);
      m_content_items[c].Id(CElement::Id());
      m_content_items[c].XSize(w);
      m_content_items[c].YSize(m_item_y_size);
      m_content_items[c].IconFile(m_t_path_bmp[i]);
      m_content_items[c].ItemBackColor(m_area_color);
      m_content_items[c].ItemBackColorHover(m_item_back_color_hover);
      m_content_items[c].ItemBackColorSelected(m_item_back_color_selected);
      m_content_items[c].ItemTextColor(m_item_text_color);
      m_content_items[c].ItemTextColorHover(m_item_text_color_hover);
      m_content_items[c].ItemTextColorSelected(m_item_text_color_selected);
      //--- Koordinaten
      m_content_items[c].X(x);
      m_content_items[c].Y(y);
      //--- Abstand vom Ankerpunkt des Fensters
      m_content_items[c].XGap(x-m_wnd.X());
      m_content_items[c].YGap(y-m_wnd.Y());
      //--- Erzeuge das Objekt
      if(!m_content_items[c].CreateTreeItem(m_chart_id,m_subwin,x,y,TI_SIMPLE,c,0,m_t_item_text[i],false))
         return(false);
      //--- Ausblenden des Elementes
      m_content_items[c].Hide();
      //--- Element ist ein Dropdown-Element
      m_content_items[c].IsDropdown(true);
      //--- Sichere (1) Index  der allgemeinen Inhaltsliste, (2) Index der Baumansicht und (3) Text des Elementes
      m_c_list_index[c]      =c;
      m_c_tree_list_index[c] =m_t_list_index[i];
      m_c_item_text[c]       =m_t_item_text[i];
      //---
      c++;
     }
//--- Sichere die Länge der Liste
   m_content_items_total=::ArraySize(m_content_items);
   return(true);
  }

Dynamische Arrays für eine Baumansicht und eine Liste des Inhaltsbereiches, die wir schon besprochen haben, verwenden wir nun, um alles zu speichern. Da aber nicht alle Elemente dieser Listen sichtbar sind, werden zwei Gruppen dynamischer Arrays, die während den Interaktionen mit der Baumansicht ständig neu formiert, benötigt. Der aktuelle Status der Knoten der lokalen Listen, die geöffnet oder minimiert werden können, werden besprochen. Schauen wir uns ein anderes Beispiel etwas genauer an.

Wir haben zum Beispiel eine Baumansicht mit 11 Elementen:

 Fig. 13. Beispiel einer Baumansicht mit 11 Elementen.

Fig. 13. Beispiel einer Baumansicht mit 11 Elementen.

Da die Elemente A, D und J Elemente des Wurzelverzeichnisses sind, stehen sie nicht in der Gesamtliste des Inhaltsbereiches. Das Bild unten (1) zeigt eine Liste aller Elemente der Baumansicht auf der rechten Seite. Dynamische Arrays, die die Parameter dieser Listen sichern und bereits besprochen wurden, haben ein ‘t’ (kurz für ‘tree’ (Baum)) als Präfix ihres Namens. Auf der linken Seite des Bildes (2) sind die Elemente des Inhaltsbereiches aufgelistet. Dynamische Arrays zur Sicherung der Parameter dieser Liste haben ein ‘c’ (kurz für ‘content’ (Inhalt)) als Präfix ihres Namens.

 Fig. 14. Komplette Liste beider Gruppen.

Fig. 14. Komplette Liste beider Gruppen.

Die lokalen Listen des obigen Beispiels sind Teil der Elemente A, D, E und J. Im Bild unten sind sie blau markiert. 

 Fig. 15. Elemente, die lokale Listen enthalten (blau markiert).

Fig. 15. Elemente, die lokale Listen enthalten (blau markiert).

Jedes Mal, wenn ein Element der Baumansicht einen Inhalt hat, wird eine Inhaltsliste neu formiert. Wenn zum Beispiel A gewählt wird, wird eine Liste der Elemente B und C im Inhaltsbereich geformt (unter 1 im Bild). Wenn D gewählt wurde, wird die Liste der Elemente E und I gebildet (unter 2 im Bild).

 Fig. 16. Beispiel der Listenbildung des Inhaltsbereiches.

Fig. 16. Beispiel der Listenbildung des Inhaltsbereiches.

Die Neuformierung der Liste gezeigter Elemente in einer Baumansicht wird bei der Minimierung oder Öffnung der lokalen Liste der Knoten durchgeführt. Alles ganz einfach und ziemlich offensichtlich. Elemente minimierter Listen werden nicht angezeigt und werden auch nicht in den Array der angezeigten Elemente der Baumansicht eingetragen.

Für alles oben Aufgeführte benötigen Sie einen Array für die Liste der gezeigten Elemente der Baumansicht, mit den Gesamtindices der Baumansicht, und drei Arrays für die Inhaltslisten, mit (1) den Gesamtindices der Inhaltsliste, (2) den Gesamtindices der Baumansicht und (3) einer Beschreibung (angezeigter Text) der Elemente: 

class CTreeView : public CElement
  {
private:
   //--- Arrays der angezeigten Elemente der Baumansicht
   int               m_td_list_index[];
   //--- Arrays der an gezeigten Elemente der Inhaltsliste
   int               m_cd_list_index[];
   int               m_cd_tree_list_index[];
   string            m_cd_item_text[];
  };

Im Folgenden betrachten wir die Methoden für die Bildung und Verwaltung der Listen mit den oben beschriebenen Algorithmen. 

 


Die Methoden zur Verwaltung der der Liste der Elemente

Nachdem alle Parameter der Klasse CTreeView übergeben und das Element erstellt wurde, müssen wir die Liste der angezeigten Elemente bilden und aktualisieren. Um den Code der Hauptmethode zu reduzieren, erstellen wir für diesen abwicklungsbezogegen Zweck zusätzliche "private" Felder und Methoden. Wir brauchen Felder für die Werte der niedrigsten und höchsten Knotenebene und die Anzahl der Elemente des Wurzelverzeichnis der Baumansicht

class CTreeView : public CElement
  {
private:
   //--- (1) Geringste und (2) höchste Knotenebene
   int               m_min_node_level;
   int               m_max_node_level;
   //--- Anzahl der Elemente des Wurzelverzeichnis
   int               m_root_items_total;
   //---
private:
   //--- Bestimme und setze (1) die Knotengrenzen und (2) die Größe des Wurzelverzeichnisses
   void              SetNodeLevelBoundaries(void);
   void              SetRootItemsTotal(void);
  };
//+------------------------------------------------------------------+
//| Bestimme und setze die Knotengrenzen                             |
//+------------------------------------------------------------------+
void CTreeView::SetNodeLevelBoundaries(void)
  {
//--- Bestimme die geringste und höchste Knotenebene
   m_min_node_level =m_t_node_level[::ArrayMinimum(m_t_node_level)];
   m_max_node_level =m_t_node_level[::ArrayMaximum(m_t_node_level)];
  }
//+------------------------------------------------------------------+
//| Bestimme und setze die Größe des Wurzelverzeichnisses            |
//+------------------------------------------------------------------+
void CTreeView::SetRootItemsTotal(void)
  {
//--- Bestimme die Anzahl der Elemente des Wurzelverzeichnisses
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
     {
      //--- Ist es gleich der geringsten Ebene, erhöhen wir den Zähler
      if(m_t_node_level[i]==m_min_node_level)
         m_root_items_total++;
     }
  }

Bisher besprachen wir die Arrays für die Bildung von Listen der angezeigten Elemente. Eine Methode CTreeView::AddDisplayedTreeItem(), die den Array der Baumansicht füllt, wird benötigt. Um ein Element dieser Liste hinzuzufügen, muss der Gesamtindex der Liste dieser Methode übergeben werden. 

class CTreeView : public CElement
  {
private:
   //--- Hinzufügen eines Elementes im Inhaltsbereichs
   void              AddDisplayedTreeItem(const int list_index);
  };
//+------------------------------------------------------------------+
//| Hinzufügen eines Elementes zum Array der angezeigten Elemente    |
//| in der Baumansicht                                               |
//+------------------------------------------------------------------+
void CTreeView::AddDisplayedTreeItem(const int list_index)
  {
//--- Erhöhe die Größe des Arrays um ein Element
   int array_size=::ArraySize(m_td_list_index);
   ::ArrayResize(m_td_list_index,array_size+1);
//--- Sichere die übergebenen Werte
   m_td_list_index[array_size]=list_index;
  }

Nachdem der Array gebildet wird, müssen die Elemente ausgetauscht werden, und die Anzeige der benötigten Elemente muss nach der letzten Änderungen aktualisiert werden. Dafür schreiben wir eine weitere Hilfsmethode CTreeView::RedrawTreeList(). Das Element ist zu Beginn der Methode ausgeblendet. Dann wird zuerst die (1) Y Koordinate berechnet, (2) die Größe der Bildlaufleiste angepasst und (3) die Breite des angezeigten/minimierten Elementes berechnet. Dann wird die Y Koordinate berechnet und die Eigenschaften für jedes Element der Schleife aktualisiert. Nach dem Ende der Schleife, wird das Element wieder sichtbar. 

class CTreeView : public CElement
  {
private:
   //--- Neuzeichnung der Baumansicht
   void              RedrawTreeList(void);
  };
//+------------------------------------------------------------------+
//| Neuzeichnung des Elementes                                       |
//+------------------------------------------------------------------+
void CTreeView::RedrawTreeList(void)
  {
//--- Element ausblenden
   Hide();
//--- Y coordinate of the first item of the tree view
   int y=CElement::Y()+1;
//--- Abfrage der Anzahl der Elemente
   m_items_total=::ArraySize(m_td_list_index);
//--- Anpassen der Bildlaufleiste
   m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
//--- Berechnung der Breite der Baumansicht
   int w=(m_items_total>m_visible_items_total) ? CElement::XSize()-m_scrollv.ScrollWidth() : CElement::XSize()-2;
//--- Setze die neuen Werte
   for(int i=0; i<m_items_total; i++)
     {
      //--- Berechne die Y Koordinaten für jedes Element
      y=(i>0)? y+m_item_y_size-1 : y;
      //--- Übernimm den Gesamtindex des Elementes der Liste
      int li=m_td_list_index[i];
      //--- Aktualisiere die Koordinaten und die Größe
      m_items[li].UpdateY(y);
      m_items[li].UpdateWidth(w);
     }
//--- Zeige das Element
   Show();
  }

Alle oben aufgeführten Felder und Methoden werde von der Methode CTreeView::UpdateTreeViewList() aufgerufen, in der die Baumansicht gebildet und aktualisiert wird (siehe den Quellcode unten). Schauen wir uns die Methode im Detail an. 

Vier lokale dynamische Arrays werden hier für die Kontrolle der Reihenfolge der Elemente beim Bilden einer Liste benötigt. Dazu gehören Arrays für:

  • Allgemeine Indices der Liste des Vorgängerknotens;
  • Lokale Indices der Elemente;
  • Anzahl der Elemente eines Knotens;
  • Anzahl der Verzeichnisse eines Knotens.

Die Anfangsgröße der Arrays ist um zwei größer als die maximale Anzahl der Knoten der Baumansicht. Das ist notwendig, da der Wert des aktuellen Elementes (in der Schleife) für das nächste Element des Arrays gespeichert wird, und die nächste Iteration der Schleife den vorhergehenden Wert überprüft. Die Arrays werden mit -1 initialisiert. Bedenken Sie, dass jedes Mal, wenn die Methode ausgeführt wird, der Array m_td_list_index[] für das Erstellen der Baumansicht mit der Größe Null freigegeben wird. Wir benötige auch einen zusätzlichen Zähler (ii) der Elemente und eine Variable (end_list) für das Erreichen des letzten Elementes des Wurzelverzeichnisses. 

Nach der Deklaration aller lokalen Arrays und Variablen beginnt die Hauptschleife der Methode CTreeView::UpdateTreeViewList(). Sie arbeitet bis:

  • der Knotenzähler (nl) das Maximum überschreitet;
  • das letzte Element im Wurzelverzeichnis erreicht wurde (nach der Prüfung aller übergebenen Elemente);
  • der Nutzer das Programm beendet. 

Dies ist ein Doppelschleife. Die Knoten der Baumansicht werden auf dem ersten Level berechnet. Alle Elemente der lokalen Liste des aktuellen Knotens werden auf dem zweiten Level geprüft. Am Anfang der zweiten Schleife wird der Modus des Dateinavigators geprüft, falls eine Baumansicht dafür verwendet wird. Falls der Modus "Zeige nur Verzeichnisse in der Baumansicht" aktiviert ist, sollte geprüft werden, ob das aktuelle Element ein Verzeichnis ist, um dann dementsprechend fortzufahren, oder nicht. 

Fall die Bedingungen zutreffen werden drei weitere Prüfungen durchgeführt. Die Methode wendet sich in den drei unten aufgelisteten Fällen dem nächsten Element zu:

  • Wenn die Knoten nicht übereinstimmen.
  • Die Reihung des lokalen Index der Elemente nicht stimmt.
  • Wir sind nicht im Wurzelverzeichnis und der Gesamtindex der Liste des Vorgängerknotens entspricht nicht dem Gesicherten.

Nach all den obigen Prüfungen sichern wir den lokalen Index des Elementes dann, wenn durch das nachfolgende Element die Größe der lokalen Liste überschritten wird. 

Denn, wenn das Element eine lokale Liste hat und sie aktuell gezeigt wird, fügen wir ein Element dem Array der gezeigten Elemente in der Baumansicht hinzu. Hier müssen Sie die Werte des aktuellen Elementes im lokalen Arrays sichern. Die einzige Ausnahme ist der lokale Index eines Elementes. Es muss im aktuellen Knoten mit dem Index (nl) gesichert werden, und die Werte aller übrigen Parameter — im nächsten Knoten mit dem Index (n). Zusätzlich wird der Zähler des lokalen Index auf Null zurückgesetzt und die aktuelle (zweite) Schleife beim nächsten Knoten beendet.

Wenn der Wechsel zu dem nächsten Knoten nicht möglich ist, dann gibt es ein Element ohne Liste oder die Liste ist im Moment minimiert. In diesem Fall wird zuerst ein Element dem Array der angezeigten Elemente der Baumansicht hinzugefügt. Dann wird der Zähler des lokalen Index der Elemente erhöht. 

Sind wir jetzt im Wurzelverzeichnis und haben bereits das letzte Element der Liste erreicht, bedeutet das, die Liste wurde erfolgreich gebildet. Das Signal wird gesetzt und die Schleife verlassen. Haben wir hingegen das letzte Element im Wurzelverzeichnis noch nicht erreicht, erhalten wir eine Zahl von Elementen oder von Verzeichnissen, gemäß des gesetzten Modus. Ist es nicht das letzte Element, wechseln wir zum Nächsten. Haben wir das letzte Element erreicht, wechseln wir zum Vorgängerknoten und fahren in der Schleife mit dem davor geprüften Element fort. Wir hören erst mit dem letzten Element des Wurzelverzeichnisses auf.

Nachdem die Baumansicht erstellt wurde, wird am Ende der Methode das Element neu gezeichnet

class CTreeView : public CElement
  {
private:
   //--- Aktualisiere Baumansicht
   void              UpdateTreeViewList(void);
  };
//+------------------------------------------------------------------+
//| Aktualisiere Baumansicht                                         |
//+------------------------------------------------------------------+
void CTreeView::UpdateTreeViewList(void)
  {
//--- Arrays für die Reihung der Elemente:
   int l_prev_node_list_index[]; // common index of the list of theprevious node 
   int l_item_index[];           // local index of the item
   int l_items_total[];          // number of items in the node
   int l_folders_total[];        // number of folders in the node
//--- Setze die Anfangsgröße des Arrays
   int begin_size=m_max_node_level+2;
   ::ArrayResize(l_prev_node_list_index,begin_size);
   ::ArrayResize(l_item_index,begin_size);
   ::ArrayResize(l_items_total,begin_size);
   ::ArrayResize(l_folders_total,begin_size);
//--- Initialisierung der Arrays
   ::ArrayInitialize(l_prev_node_list_index,-1);
   ::ArrayInitialize(l_item_index,-1);
   ::ArrayInitialize(l_items_total,-1);
   ::ArrayInitialize(l_folders_total,-1);
//--- Leeren des Arrays der Elemente der Baumansicht
   ::ArrayFree(m_td_list_index);
//--- Zähler des lokalen Index der Elemente
   int ii=0;
//--- Setzen des Signals für das letzte Element des Wurzelverzeichnisses
   bool end_list=false;
//--- Sammeln der Elemente des Arrays. Diese Schleife arbeitet bis:
//    1: Der Knotenzähler das Maximum überschreitet;
//    2: das letzte Element erreicht wurde (nach der Prüfung aller verschachtelten Elementen);
//    3: der Nutzer beendet das Programm.
   int items_total=::ArraySize(m_items);
   for(int nl=m_min_node_level; nl<=m_max_node_level && !end_list; nl++)
     {
      for(int i=0; i<items_total && !::IsStopped(); i++)
        {
         //--- Im Modus "Zeige nur Verzeichnisse"
         if(m_file_navigator_mode==FN_ONLY_FOLDERS)
           {
            //--- Ist es eine Datei, wechsle zum nächsten Element
            if(!m_t_is_folder[i])
               continue;
           }
         //--- Wenn (1) es kein Knoten oder (2) die Reihung nicht dem lokalen Index entspricht,
         //    wechsle zum nächsten Element
         if(nl!=m_t_node_level[i] || m_t_item_index[i]<=l_item_index[nl])
            continue;
         //--- Wechsle zum nächsten Element, wenn es nicht im Wurzelverzeichnis ist und 
         //    der Gesamtindex des Vorgängerknotens nicht dem gesicherten entspricht
         if(nl>m_min_node_level && m_t_prev_node_list_index[i]!=l_prev_node_list_index[nl])
            continue;
         //--- Sichere den lokalen Index des Elementes, wenn das nächste Element die Größe der lokalen Liste überschreitet
         if(m_t_item_index[i]+1>=l_items_total[nl])
            ii=m_t_item_index[i];
         //--- Wenn die Liste des aktuellen Elementes geöffnet ist
         if(m_t_item_state[i])
           {
            //--- Füge das Element dem Array der angezeigten Elemente hinzu in der Baumansicht
            AddDisplayedTreeItem(i);
            //--- Sichere den aktuellen Wert wechsle zum nächsten Element
            int n=nl+1;
            l_prev_node_list_index[n] =m_t_list_index[i];
            l_item_index[nl]          =m_t_item_index[i];
            l_items_total[n]          =m_t_items_total[i];
            l_folders_total[n]        =m_t_folders_total[i];
            //--- Rücksetzen des lokalen Index der Elemente auf Null
            ii=0;
            //--- Wechsle zum nächsten Knoten
            break;
           }
         //--- Füge Element zum Array der angezeigten Elemente der Baumansicht
         AddDisplayedTreeItem(i);
         //--- erhöhe der Zähler des lokalen Index der Elemente
         ii++;
         //--- Ist das letzte Element des Wurzelverzeichnisses erreicht
         if(nl==m_min_node_level && ii>=m_root_items_total)
           {
            //--- Setze das Signal und beende die aktuelle Schleife
            end_list=true;
            break;
           }
         //--- Ist es noch nicht das letzte Element des Wurzelverzeichnisses
         else if(nl>m_min_node_level)
           {
            //--- Frag nach der Anzahl der Elemente im aktuellen Knoten
            int total=(m_file_navigator_mode==FN_ONLY_FOLDERS)? l_folders_total[nl]: l_items_total[nl];
            //--- Ist es nicht der letzte lokale Indes des Elementes, wechseln wir zum nächsten
            if(ii<total)
               continue;
            //--- Ist es der letzte lokale Index, dann
            //    müssen wir zum Vorgänger wechseln und dort fortfahren
            while(true)
              {
               //--- Rücksetzen der Werte des aktuellen Knotens in den folgenden Listen
               l_prev_node_list_index[nl] =-1;
               l_item_index[nl]           =-1;
               l_items_total[nl]          =-1;
               //--- Reduziere die Anzahl der Knoten, bis sie gleich der lokalen Liste ist
               //    oder solange das Wurzelverzeichnis nicht erreicht wurde
               if(l_item_index[nl-1]+1>=l_items_total[nl-1])
                 {
                  if(nl-1==m_min_node_level)
                     break;
                  //---
                  nl--;
                  continue;
                 }
               //---
               break;
              }
            //--- Fortfahren mit dem Vorgängerknoten
            nl=nl-2;
            //--- Rücksetzen des Zählers der lokalen Liste und fortfahren zum nächsten Knoten
            ii=0;
            break;
           }
        }
     }
//--- Neuzeichnung der Elemente
   RedrawTreeList();
  }

Für das Verschieben der Listen durch die Bildlaufleiste verwenden wir die Methoden CTreeView::ShiftTreeList() und CTreeView::ShiftContentList(). Die wesentliche Logik dieser Methoden ist teilweise identisch, daher beschreiben wir jetzt den Code von nur einem der beiden (für die Baumansicht). 

Zu Beginn der Methode werden alle Elemente der Baumansicht ausgeblendet. Dann wird die Breite der Elemente unter Berücksichtigung der Bildlaufleiste in der aktuellen Liste berechnet. Gibt es eine Bildlaufleiste wird die Position des Schiebers berechnet. Die Koordinaten und Breite werden für jedes sichtbare Element in der Schleife berechnet und aktualisiert. Am Ende der Methode, falls eine Bildlaufleiste zu zeigen ist, muss aktualisiert werden, damit sie oben auf der Liste platziert werden kann. 

class CTreeView : public CElement
  {
private:
   //--- Verschieben der Listen
   void              ShiftTreeList(void);
   void              ShiftContentList(void);
  };
//+------------------------------------------------------------------+
//| Verschieben der Baumansicht durch die Bildlaufleiste             |
//+------------------------------------------------------------------+
void CTreeView::ShiftTreeList(void)
  {
//--- Ausblenden aller Elemente in der Baumansicht
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].Hide();
//--- Wenn eine Bildlaufleiste benötigt wird
   bool is_scroll=m_items_total>m_visible_items_total;
//--- Berechnung der Breite der Listenelemente
   int w=(is_scroll)? m_area.XSize()-m_scrollv.ScrollWidth()-1 : m_area.XSize()-2;
//--- Bestimmung der Position der Bildlaufleiste
   int v=(is_scroll)? m_scrollv.CurrentPos() : 0;
   m_scrollv.CurrentPos(v);
//--- Y Koordinate des ersten Elementes der Baumansicht
   int y=CElement::Y()+1;
//---
   for(int r=0; r<m_visible_items_total; r++)
     {
      //--- Prüfung, um ein zu frühes Verlassen zu verhindern
      if(v>=0 && v<m_items_total)
        {
         //--- Berechne die Y Koordinate
         y=(r>0)? y+m_item_y_size-1 : y;
         //--- Abruf des Gesamtindex der Baumansicht
         int li=m_td_list_index[v];
         //--- Bestimme Koordinaten und Breite
         m_items[li].UpdateX(m_area.X()+1);
         m_items[li].UpdateY(y);
         m_items[li].UpdateWidth(w);
         //--- Zeige das Element
         m_items[li].Show();
         v++;
        }
     }
//--- Neuzeichnen der Bildlaufleiste
   if(is_scroll)
      m_scrollv.Reset();
  }

Eine Methode der Bildung und Aktualisierung der Inhaltsliste ist wesentlich einfacher, da hier eine normale Liste ohne eine hierarchische Ordnung gebildet wird. Hier am Anfang der Methode CTreeView::UpdateContentList(), werden die Arrays zur Bildung der Inhaltslisten geleert. Dann iterieren wir über alle Elemente der Baumansicht in der ersten Schleife und sichern nur die Elemente, deren Parameter zu denen des gewählten Elementes in der Baumansicht passen:

  • Knotenebene
  • Lokaler Index des Elementes
  • Allgemeiner Index der Elemente.

Diese Schleife sichert nur die Beschreibung des Elementes (Anzeigetext) und den Gesamtindex der Baumansicht

In der zweiten Schleife iterieren wir über die Elemente der Inhaltsliste und füllen den Array des Gesamtindex' dieser Liste. Um die notwendigen Elemente zu bestimmen, verwenden wir die aus der ersten Schleife erhaltenen Parameter. Am Ende der Methode wird die Größe der Bildlaufleiste angepasst und eine Liste für die Darstellung der letzten Änderungen wird aktualisiert. 

class CTreeView : public CElement
  {
private:
   //--- Aktualisiere die Inhaltsliste
   void              UpdateContentList(void);
  };
//+------------------------------------------------------------------+
//| Aktualisiere die Inhaltsliste                                    |
//+------------------------------------------------------------------+
void CTreeView::UpdateContentList(void)
  {
//--- Index des gewählten Elementes
   int li=m_selected_item_index;
//--- Leere die Arrays für die Inhaltsliste
   ::ArrayFree(m_cd_item_text);
   ::ArrayFree(m_cd_list_index);
   ::ArrayFree(m_cd_tree_list_index);
//--- Bilde die Inhaltsliste
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
     {
      //--- Wenn (1) die Ebene der Knoten und (2) lokalen Indices der Elemente passen, so wie
      //    (3) der Index des Vorgängerknotens zum Index des gewählten
      if(m_t_node_level[i]==m_t_node_level[li]+1 && 
         m_t_prev_node_item_index[i]==m_t_item_index[li] &&
         m_t_prev_node_list_index[i]==li)
        {
         //--- Erhöhen der Größe der Arrays der angezeigten Elemente der Inhaltsliste
         int size     =::ArraySize(m_cd_list_index);
         int new_size =size+1;
         ::ArrayResize(m_cd_item_text,new_size);
         ::ArrayResize(m_cd_list_index,new_size);
         ::ArrayResize(m_cd_tree_list_index,new_size);
         //--- Sichere den Text des Elementes und den Gesamtindex für die Baumansicht
         m_cd_item_text[size]       =m_t_item_text[i];
         m_cd_tree_list_index[size] =m_t_list_index[i];
        }
     }
//--- Falls später diese Liste nicht leer ist, füllen wir den Array der Gesamtindices der Inhaltsliste
   int cd_items_total=::ArraySize(m_cd_list_index);
   if(cd_items_total>0)
     {
      //--- Zähler der Elemente
      int c=0;
      //--- Iteriere über diese Liste
      int c_items_total=::ArraySize(m_c_list_index);
      for(int i=0; i<c_items_total; i++)
        {
         //--- Wenn die Beschreibung und der Gesamtindex der Baumansicht passen
         if(m_c_item_text[i]==m_cd_item_text[c] && 
            m_c_tree_list_index[i]==m_cd_tree_list_index[c])
           {
            //--- Sichere den Gesamtindex der Inhaltsliste und weiter mit dem Nächsten
            m_cd_list_index[c]=m_c_list_index[i];
            c++;
            //--- Verlassen der Schleife, wenn das Ende der Liste erreicht ist
            if(c>=cd_items_total)
               break;
           }
        }
     }
//--- Korrigiere die Größe der Bildlaufleiste
   m_content_scrollv.ChangeThumbSize(cd_items_total,m_visible_items_total);
//--- Korrigiere die Inhaltsliste der Elemente
   ShiftContentList();
  }

 


Die Verwaltung der Liste der Bereiche

Nun schauen wir ein genauer auf die Veränderungen der Breite der Listen. Was wir benötigen: (1) eine Hauptmethode CTreeView::ResizeListArea(), mit allen Prüfungen und — abhängig von deren Ergebnissen — der Änderung der Listenbreite und (2) vier "private" Hilfsmethoden zur Lösung der unten aufgeführten Aufgaben.

  • Die Methode CTreeView::CheckXResizePointer() — Prüft die Bereitschaft zur Änderung der Listenbreite. Der Code der Methode besteht aus zwei Blöcken, verbunden über dieselben Prüfungen. Wenn der Cursor nicht aktiviert ist, aber die Maus in der Nähe ist, werden die Koordinaten des Pointers aktualisiert und er wird sichtbar. Wenn die linke Maustaste gedrückt wird, wird der Pointer aktiviert. Ist die erste Bedingung der Methode nicht erfüllt, wird der Cursor wieder ausgeblendet.
class CTreeView : public CElement
  {
private:
   //--- Prüfe die Bereitschaft zur Änderung der Breite der Liste
   void              CheckXResizePointer(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Prüfe die Bereitschaft zur Änderung der Breite der Liste         |
//+------------------------------------------------------------------+
void CTreeView::CheckXResizePointer(const int x,const int y)
  {
//--- Ist der Pointer nicht aktiviert, aber die Maus in der Nähe
   if(!m_x_resize.State() && 
      y>m_area.Y() && y<m_area.Y2() && x>m_area.X2()-2 && x<m_area.X2()+3)
     {
      //--- Aktualisiere die Koordinaten des Pointers und zeige ihn
      int l_x=x-m_x_resize.XGap();
      int l_y=y-m_x_resize.YGap();
      m_x_resize.Moving(l_x,l_y);
      m_x_resize.Show();
      //--- Setze das Sichtbarkeitssignal
      m_x_resize.IsVisible(true);
      //--- Wenn die linke Maustaste gedrückt wird
      if(m_mouse_state)
         //--- Aktiviere den Pointer
         m_x_resize.State(true);
     }
   else
     {
      //--- Wenn die linke Maustaste gedrückt wurde
      if(!m_mouse_state)
        {
         //--- Deaktiviere und verberge den Pointer
         m_x_resize.State(false);
         m_x_resize.Hide();
         //--- Entferne das Sichtbarkeitssignal
         m_x_resize.IsVisible(false);
        }
     }
  }
  • Die Methode CTreeView::CheckOutOfArea() ist ein Test für die Verletzung der Beschränkungen. Es macht keinen Sinn, die Breite der Liste zu ändern, um vollständig die Sichtbarkeit von einigen von ihnen beseitigen. Daher setzen wir die Beschränkung auf 80 Pixel. Wir machen es so, dass, wenn der Cursor über den eingestellten Grenzwert der horizontale Begrenzung geht, eine Verschiebung nur vertikal möglich ist, aber innerhalb des Bereiches der verbundenen Listen. 
class CTreeView : public CElement
  {
private:
   //--- Prüfe Einhaltung der Beschränkung
   bool              CheckOutOfArea(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Prüfe Einhaltung der Beschränkung                                |
//+------------------------------------------------------------------+
bool CTreeView::CheckOutOfArea(const int x,const int y)
  {
//--- Beschränkungen
   int area_limit=80;
//--- Überschreiten der horizontalen Beschränkung ...
   if(x<m_area.X()+area_limit || x>m_content_area.X2()-area_limit)
     {
      // ... bewege den Pointer nur mehr vertikal ohne Überschreitung der Grenzen
      if(y>m_area.Y() && y<m_area.Y2())
         m_x_resize.UpdateY(y-m_x_resize.YGap());
      //--- Keine Änderung der Breite der Liste
      return(false);
     }
//--- Ändere die Breite der Liste
   return(true);
  }
  • Die Methoden CTreeView::UpdateTreeListWidth() und CTreeView::UpdateContentListWidth() aktualisieren (1) die Breite der Baumansicht und (2) die Breite der Liste im Inhaltsbereich. Es wird in der Baumansicht nur die rechte Grenze verschoben. Dafür muss dessen Breite geändert werden. Weiters benötigen wir eine Aktualisierung der Koordinaten der Bildlaufleiste. Für eine zu aktualisierende Liste eines Inhaltsbereiches, müssen wir die Breite und die X Koordinate gleichzeitig anpassen, um nur die linken Grenze zu verschieben. 
class CTreeView : public CElement
  {
private:
   //--- Aktualisiere die Breite der Baumansicht
   void              UpdateTreeListWidth(const int x);
   //--- Aktualisiere die Breite der Liste des Inhaltsbereiches
   void              UpdateContentListWidth(const int x);
  };
//+------------------------------------------------------------------+
//| Aktualisiere die Breite der Baumansicht                          |
//+------------------------------------------------------------------+
void CTreeView::UpdateTreeListWidth(const int x)
  {
//--- Berechne und setze die Breite der Baumansicht
   m_area.X_Size(x-m_area.X());
   m_area.XSize(m_area.X_Size());
//--- Berechne und setze die Breite der Elemente in der Baumansicht unter Berücksichtigung der Bildlaufleiste
   int l_w=(m_items_total>m_visible_items_total) ? m_area.XSize()-m_scrollv.ScrollWidth()-4 : m_area.XSize()-1;
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
      m_items[i].UpdateWidth(l_w);
//--- Berechne und setze die Koordinaten der Bildlaufleiste der Baumansicht
   m_scrollv.X(m_area.X2()-m_scrollv.ScrollWidth());
   m_scrollv.XDistance(m_scrollv.X());
  }
//+------------------------------------------------------------------+
//| Aktualisiere die Breite der Liste im Inhaltsbereich              |
//+------------------------------------------------------------------+
void CTreeView::UpdateContentListWidth(const int x)
  {
//--- Berechne und setze die X Koordinate Abstand und Breite des Inhaltsbereiches
   int l_x=m_area.X2()-1;
   m_content_area.X(l_x);
   m_content_area.X_Distance(l_x);
   m_content_area.XGap(l_x-m_wnd.X());
   m_content_area.XSize(CElement::X2()-m_content_area.X());
   m_content_area.X_Size(m_content_area.XSize());
//--- Berechne und setze die X Koordinate und Breite der Elemente der Inhaltsliste
   l_x=m_content_area.X()+1;
   int l_w=(m_content_items_total>m_visible_items_total) ? m_content_area.XSize()-m_content_scrollv.ScrollWidth()-4 : m_content_area.XSize()-2;
   int total=::ArraySize(m_content_items);
   for(int i=0; i<total; i++)
     {
      m_content_items[i].UpdateX(l_x);
      m_content_items[i].UpdateWidth(l_w);
     }
  }

Alle diese Hilfsmethoden werden in der Hauptmethode CTreeView:ResizeListArea() aufgerufen, die am Ende die Ereignisse der Anwendung behandeln. Mehrere Prüfungen müssen zu Beginn der Methode durchgeführt werden. Die Methode wird unter den unten aufgeführten Bedingungen verlassen.

  • Wenn die Änderung der Bereite der Liste deaktiviert ist
  • Wenn der Inhaltsbereich deaktiviert ist
  • Wen der tabellarische Modus aktiviert ist
  • Wenn die Bildlaufleiste aktiv ist (der Schieberegler wird gerade verwendet)

Wurden alle Prüfungen bestanden, wird die Methode CTreeView::CheckXResizePointer() aufgerufen, um die Bereitschaft des Status zur Änderung der Breite der Liste abzufragen. Stell sich später heraus, dass der Pointer deaktiviert ist, sollte das vorher blockierte Fenster entsperrt werden. 

Ist der Pointer aktiviert, müssen wir als erstes prüfen, ob die Beschränkungen verletzt wurden. Diese Methode steht am Ende des Programms. Sind wir im Arbeitsfenster, ist die Form blockiert. Die Kennung der Elemente muss gesichert werden, da nur das blockierte Element entsperrt werden darf. Die Koordinaten des Cursors werden aktualisiert zusammen mit den Koordinaten der Listen und ihrer Breite.  

//+------------------------------------------------------------------+
//| Manage width of lists                                            |
//+------------------------------------------------------------------+
void CTreeView::ResizeListArea(const int x,const int y)
  {
//--- Verlassen, wenn (1) Breite des Inhaltsbereiches nicht änderbar ist oder
//    (2) de Inhaltsbereich deaktiviert ist oder (3) ist im tabellarischen Modus
   if(!m_resize_list_area_mode || m_content_area_width<0 || m_tab_items_mode)
      return;
//--- Verlassen, wenn die Bildlaufleiste aktiv ist
   if(m_scrollv.ScrollState())
      return;
//--- Prüfung der Bereitschaft zur Änderung der Breite der Liste
   CheckXResizePointer(x,y);
//--- Entsperren, wenn der Pointer deaktiviert ist
   if(!m_x_resize.State())
     {
      //--- Wer immer das Fenster blockierte, kann es entsperren
      if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()==CElement::Id())
        {
         m_wnd.IsLocked(false);
         m_wnd.IdActivatedElement(WRONG_VALUE);
         return;
        }
     }
   else
     {
      //--- Prüfe Überschreiten der Beschränkung
      if(!CheckOutOfArea(x,y))
         return;
      //--- Blockiere das Fenster und sichere die Kennung des aktiven Elements
      m_wnd.IsLocked(true);
      m_wnd.IdActivatedElement(CElement::Id());
      //--- Setze die X Koordinate des Objektes in der Mitte des Mauscursors
      m_x_resize.UpdateX(x-m_x_resize.XGap());
      //--- Setze die Y Koordinate nur innerhalb der Beschränkung
      if(y>m_area.Y() && y<m_area.Y2())
         m_x_resize.UpdateY(y-m_x_resize.YGap());
      //--- Aktualisiere die Breite der Baumansicht
      UpdateTreeListWidth(x);
      //--- Aktualisiere die Breite der Inhaltsliste
      UpdateContentListWidth(x);
      //--- Aktualisiere die Koordinaten und die Größe der Listen
      ShiftTreeList();
      ShiftContentList();
      //--- Neuzeichnen des Pointers
      m_x_resize.Reset();
     }
  }

Als Zwischenergebnis ist hier der Codeblock der Ereignisbehandlung CTreeView::OnEvent() für die bewegte Maus CHARTEVENT_MOUSE_MOVE. Viele der Verfahren, die oben diskutiert wurden, werden hier verwendet,. 

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CTreeView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handhabung der Mausbewegung  
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Verlassen, wenn Element is ausgeblendet
      if(!CElement::IsVisible())
         return;
      //--- Koordinaten und der Status der Maustaste
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Prüfung des Fokus' der Liste
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Verschieben der Baumansicht, wenn der Schieberegler der Bildlaufleiste aktiv ist
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state))
        {
         ShiftTreeList();
         return;
        }
      //--- Verschieben der Inhaltsliste durch den Schieberegler der Bildlaufleiste
      if(m_content_scrollv.ScrollBarControl(x,y,m_mouse_state))
        {
         ShiftContentList();
         return;
        }
      //--- Änderung der Breite des Inhaltsbereiches
      ResizeListArea(x,y);
      //--- Verlassen, wenn die Form blockiert ist
      if(m_wnd.IsLocked())
         return;
      //--- Wechsel der Farben, wenn die Maus über dem Objekt ist
      ChangeObjectsColor();
      return;
     }
  }

 

 

Modus tabellarische Objekte

Versuchen wir mal herauszufinden, wie der tabellarische Modus in einer Baumansicht funktioniert. Im Augenblick hat die Bibliothek bereits zwei Klassen für die Erstellung von Tabs: CTabs und CIconTabs. Detailliertere Informationen über ihr Design findet sich im Artikel Grafische Interfaces VII: Das Tab-Control (Kapitel 2). In diesen Klassen werden Tabellen horizontal und vertikal angelegt. In einer Baumansicht können die Elemente nach Kategorien geordnet werden, die sich als Form einer Gruppierung bei vielen Elementen in einer graphischen Darstellung einer Anwendung als nützlich erweisen kann.

Wenn bei der Erstellung einer Baumansicht der Modus Tab-Element gesetzt wurde, muss nach der Erstellung aller Objekte entschieden werden, welche Elemente der Baumansicht Tabs werden. Tabs können nur Elemente sein, die selbst keine lokalen Listen haben. Es sollte beachtet werden, dass Tabs ihre eigene Indizierung haben (siehe das Bild unten). Die spezifische Reihung der Indices sollte beachtet werden, wenn Steuerelemente den Elementen eines Tabs hinzugefügt werden.

Das Bild unten zeigt ein Beispiel für die Reihung im Tab-Modus. Die Knoten A, D, G und H sind keine Tabs, weil sie Listen enthalten.

 

Fig. 17. Indices von Tab-Elementen.

Zur Lösung dieses Problems benötige wir eine Struktur (mit der Deklaration eines Arrays seiner Elemente), das einen dynamischen Array für die Pointer zu den Elementen und ein Feld für den Tab-Index:

class CTreeView : public CElement
  {
private:
   //--- Struktur der Elemente für jedes Tab-Element
   struct TVElements
     {
      CElement         *elements[];
      int               list_index;
     };
   TVElements        m_tab_items[];
  };

Zur Bestimmung welche Knoten einem Tab zugeordnet werden und, um ihren Array zu bilden, verwenden wir die Methode CTreeView::GenerateTabItemsArray(). Wenn der Tab-Modus deaktiviert ist, wird diese Methode sofort verlassen. Dann werden wir über den ganzen Array iterieren und jedes Mal, wenn wir auf ein leeres Element treffen, werden wir den Array der Struktur TVElements um eins erniedrigen und den Gesamtindex des Elements behalten

Dann, wenn der Inhalt des Elements angezeigt wird, wird das erste Element der Liste standardmäßig ausgewählt. Wenn die Anzeige von Elementinhalte deaktiviert ist, wird beim Beenden der Index wieder korrigiert. Der Tab-Modus des Elementes wird in den Eigenschaften aktiviert. 

class CTreeView : public CElement
  {
private:
   //--- Bildet den Array der Elemente
   void              GenerateTabItemsArray(void);
  };
//+------------------------------------------------------------------+
//| Bildet den Array der Elemente                                    |
//+------------------------------------------------------------------+
void CTreeView::GenerateTabItemsArray(void)
  {
//--- Verlassen, wenn Tab-Modus deaktiviert ist
   if(!m_tab_items_mode)
      return;
//--- Hinzufügen nur leerer Elemente zum Array
   int items_total=::ArraySize(m_items);
   for(int i=0; i<items_total; i++)
     {
      //--- Enhält das Element weitere Elemente gehe zum Nächsten
      if(m_t_items_total[i]>0)
         continue;
      //--- Erhöhe die Größe des Arrays der Tab-Elemente um ein Element
      int array_size=::ArraySize(m_tab_items);
      ::ArrayResize(m_tab_items,array_size+1);
      //--- Sichere den Gesamtindex
      m_tab_items[array_size].list_index=i;
     }
//--- Wenn keine Anzeige der Elementinhalte
   if(!m_show_item_content)
     {
      //--- Abfrage der Größe des Arrays der Tab-Elemente
      int tab_items_total=::ArraySize(m_tab_items);
      //--- Korrigieren des Index, wenn außerhalb des Bereichs
      if(m_selected_item_index>=tab_items_total)
         m_selected_item_index=tab_items_total-1;
      //--- Deaktivieren der Auswahl des aktuellen Eintrags in der Liste
      m_items[m_selected_item_index].HighlightItemState(false);
      //--- Der Index des ausgewählten Tabs
      int tab_index=m_tab_items[m_selected_item_index].list_index;
      m_selected_item_index=tab_index;
      //--- Auswahl dieses Artikels
      m_items[tab_index].HighlightItemState(true);
     }
  }

Um ein Steuerelement mit einem Tab in der Baumansicht zu verbinden, verwenden wir die Methode CTreeView::AddToElementsArray(). Diese Methode hat zwei Argumente: (1) Index des Tab-Elementes und (2) das Objekt vom Typ CElement, dessen Pointer im Array der Tab-Elemente gesichert werden sollte. 

class CTreeView : public CElement
  {
public:
   //--- Hinzufügen eines Elements zum Array der Tab-Elemente
   void              AddToElementsArray(const int item_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Hinzufügen eines Elements zum Array der Tab-Elemente                    |
//+------------------------------------------------------------------+
void CTreeView::AddToElementsArray(const int tab_index,CElement &object)
  {
//--- Prüfe die Verletzung der Beschränkung
   int array_size=::ArraySize(m_tab_items);
   if(array_size<1 || tab_index<0 || tab_index>=array_size)
      return;
//--- Hinzufügen des übergebenen Zeigers des Element zum spez. Array der Tabs
   int size=::ArraySize(m_tab_items[tab_index].elements);
   ::ArrayResize(m_tab_items[tab_index].elements,size+1);
   m_tab_items[tab_index].elements[size]=::GetPointer(object);
  }

Die Methode CTreeView::ShowTabElements() wird für die Anzeige der ausgewählten Tab-Elemente verwendet. Die Methode wird verlassen, wenn das Element ausgeblendet oder deaktiviert ist. Dann, in der ersten Schleife, wird der Index des gewählten Tab-Elementes bestimmt. Danach, in der zweiten Schleife, werden nur die zur Anzeige bestimmten Tab-Elemente angezeigt, der Rest wird ausgeblendet. 

class CTreeView : public CElement
  {
public:
   //--- Anzeige der gewählten Tab-Elemente
   void              ShowTabElements(void);
  };
//+------------------------------------------------------------------+
//| Anzeige der gewählten Tab-Elemente                               |
//+------------------------------------------------------------------+
void CTreeView::ShowTabElements(void)
  {
//--- Verlassen, wenn das Element ausgeblendet oder deaktiviert ist
   if(!CElement::IsVisible() || !m_tab_items_mode)
      return;
//--- Index des gewählten Tab-Elementes
   int tab_index=WRONG_VALUE;
//--- Bestimmung des Index des gewählten Tab-Elementes
   int tab_items_total=::ArraySize(m_tab_items);
   for(int i=0; i<tab_items_total; i++)
     {
      if(m_tab_items[i].list_index==m_selected_item_index)
        {
         tab_index=i;
         break;
        }
     }
//--- Anzeige ausschließlich der gewählten Tab-Elemente
   for(int i=0; i<tab_items_total; i++)
     {
      //--- Abfrage der Anzahl der Elemente des Tabs
      int tab_elements_total=::ArraySize(m_tab_items[i].elements);
      //--- Wenn dieser Tab gewählt wurde
      if(i==tab_index)
        {
         //--- Anzeige der Elemente
         for(int j=0; j<tab_elements_total; j++)
            m_tab_items[i].elements[j].Reset();
        }
      else
        {
         //--- Ausblenden der Elemente
         for(int j=0; j<tab_elements_total; j++)
            m_tab_items[i].elements[j].Hide();
        }
     }
  }

 

 

Die Methoden zur Ereignisbehandlung

Nach der Auswahl eines Elementes aus den Listen, wird eine Nachricht erzeugt, dass sich der Pfad zur Baumansicht geändert hat. Danach kann dieses Ereignis im Dateinavigator zur Bestimmung des Pfads der Datei übernommen werden. Um umzusetzen, was wir geplant haben, benötigen wir die Kennung ON_CHANGE_TREE_PATH aus der Datei Defines.mqh (siehe den Quellcode unten):

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CHANGE_TREE_PATH       (23) // Pfad zur Baumansicht wurde geändert

Um den Pfad zum ausgewählten Element zu bestimmen, schreiben wir die Methode CTreeView::CurrentFullPath(), die vom Dateinavigator aufgerufen wird, um den Pfad der Datei abzurufen. Ein Feld und eine Methode für die gewählte Datei aus der Liste der Elemente wird auch benötigt. Die werden nur relevant, wenn die Baumansicht als Teil des Dateinavigators verwendet wird. Betrachten wir die Methode CTreeView::CurrentFullPath() im Detail.

Der Pfad wird im Array path_parts[] gebildet, indem die Einzelteile, beginnend mit dem gewählten Element und dann die Hierarchie der Liste hinauf, Stück für Stück ergänzt werden. Am Anfang kontrollieren wir, ob das gewählte Element eine Datei ist. Ist es ein Verzeichnis, fügen wir es dem Array (Dateinamen werde nicht dem Pfad hinzugefügt) hinzu. 

Dann iterieren wir über die gesamte Baumansicht vom gewählten Element aufwärts. Das ist entsprechen umgesetzt. Ausgelassen werden alle Dateien. Um ein Element dem Array hinzuzufügen müssen, alle Baum-Bedingungen erfüllt sein.

  • Der Index der allgemeinen Liste muss mit dem Index des allgemeinen Liste des Vorgängerknotens übereinstimmen.
  • Der Index des Elements der lokalen Liste muss mit dem Index des Vorgängerknotens übereinstimmen.
  • Ein absteigende Reihung der Knoten muss gegeben sein.

Sind alle drei Bedingungen erfüllt: (1) wird der Elementnamen dem Array hinzugefügt, (2) der aktueller Index der Schleife wird gesichert für eine spätere Prüfung und (3) der Zähler in der Schleife wird zurückgesetzt. Falls wir aber die Ebene Null der Knoten erreichen, wird die Schleife gestoppt

Dann, in einer eigenen Schleife wird eine Zeichenkette gebildet (vollständiger Pfad des gewählten Elementes) inkl. der Trennzeichen "\\". Weiter, wenn das gewählte Element in einer Baumansicht ein Verzeichnis ist, überprüfen wir, ob das gewählte Element Teil des Inhaltsbereiches ist, und, ob es eine Datei ist. Wenn der Modus "zeige Dateien in der Baumansicht" aktiviert ist, dann, wenn die Datei gewählt wurde, wird der Namen sofort nach der Bildung des Pfades gesichert.

Am Ende der Methode wird der Pfad des gewählten Elementes zurückgegeben. Wenn die Datei auch in der aktuellen Kategorie ausgewählt wurde, kann diese mit der "public" Methode CTreeView::SelectedItemFileName() abgefragt werden. 

class CTreeView : public CElement
  {
private:
   //--- Text des gewählten Elementes in der Liste. 
   //    Nur für Dateien falls eine Klasse für eine Dateinavigator verwendet wird.
   //    Wurde keine Datei gewählt, sollte das Feld eine leere Zeichenkette "" sein.
   string            m_selected_item_file_name;
   //---
public:
   //--- Rückgabe des Dateinamens
   string            SelectedItemFileName(void)                   const { return(m_selected_item_file_name);        }
   //--- Rückgabe des vollständigen Pfads des gew. Elementes
   string            CurrentFullPath(void);
  };
//+------------------------------------------------------------------+
//| Rückgabe des vollständigen Pfads                                 |
//+------------------------------------------------------------------+
string CTreeView::CurrentFullPath(void)
  {
//--- Für die Bildung des Verzeichnisses des gew. Elementes
   string path="";
//--- Index des gew. Elementes
   int li=m_selected_item_index;
//--- Array zur Bildung des Verzeichnisses
   string path_parts[];
//--- Abfrage der Beschreibung (Text) des gew. Elementes der Baumansicht,
//    aber nur wenn es ein Verzeichnis ist
   if(m_t_is_folder[li])
     {
      ::ArrayResize(path_parts,1);
      path_parts[0]=m_t_item_text[li];
     }
//--- Iteriere über die ganze Liste
   int total=::ArraySize(m_t_list_index);
   for(int i=0; i<total; i++)
     {
      //--- Nur Verzeichnisse werden behandelt.
      //    Gibt es eine Datei, nächstes Element.
      if(!m_t_is_folder[i])
         continue;
      //--- Wenn der Index zum Index der allgemeinen Liste des Vorgängerknotens passt und
      //    der Index des Elementes einer lokalen Liste zum Index des Vorgängerknotens passt,
      //    dann wird die Reihung beachtet.
      if(m_t_list_index[i]==m_t_prev_node_list_index[li] &&
         m_t_item_index[i]==m_t_prev_node_item_index[li] &&
         m_t_node_level[i]==m_t_node_level[li]-1)
        {
         //--- Erhöhe den Array um ein Element und sichere die Beschreibung des Elementes
         int sz=::ArraySize(path_parts);
         ::ArrayResize(path_parts,sz+1);
         path_parts[sz]=m_t_item_text[i];
         //--- Sichere den Index für die nächste Prüfung
         li=i;
         //--- Verlassen, wenn die Ebene Null erreicht wurde
         if(m_t_node_level[i]==0 || i<=0)
            break;
         //--- Rücksetzen des Zählers in der Schleife
         i=-1;
        }
     }
//--- Bilden der Zeichenkette - den vollständigen Pfad zu dem ausgewählten Element im Baum
   total=::ArraySize(path_parts);
   for(int i=total-1; i>=0; i--)
      ::StringAdd(path,path_parts[i]+"\\");
//--- gew. Element ist ein Verzeichnis in der Baumansicht
   if(m_t_is_folder[m_selected_item_index])
     {
      m_selected_item_file_name="";
      //--- Wenn das Element in der Inhaltsliste gewählt wurde
      if(m_selected_content_item_index>0)
        {
         //--- Sichere den Namen, wenn das gew. Element eine Datei ist
         if(!m_t_is_folder[m_c_tree_list_index[m_selected_content_item_index]])
            m_selected_item_file_name=m_c_item_text[m_selected_content_item_index];
        }
     }
//--- Wenn das gew. Element in einer Baumansicht eine Datei ist
   else
//--- sichere den Namen
      m_selected_item_file_name=m_t_item_text[m_selected_item_index];
//--- und Rückgabe des Pfads
   return(path);
  }

In der aktuellen Version des Elements Baumansicht werden drei Nutzeraktionen verarbeitet.

  • Klick auf die Taste Öffnen/Minimieren der lokalen Liste der Elemente – die Methode CTreeView::OnClickItemArrow(). Falls der Name des graphischen Objektes nicht aus der Baumansicht oder die Kennung des Elementes nicht zur Kennung des Objektes in der Baumansicht passt, wird die Methode verlassen. Dann (1) müssen wir den Status des Elementknopfes abfragen und den Status ins Gegenteil ändern, (2) die Baumansicht mit den letzten Änderungen aktualisieren, (3) die Position des Schiebereglers der Bildlaufleiste berechnen und, (4) falls der der relevante Modus aktiviert ist, nur die Elemente des aktuell gewählten Tab-Elements anzeigen. 
class CTreeView : public CElement
  {
private:
   //--- Handhabung des Klicks auf den Knopf zum Öffnen/Minimieren der Elementliste
   bool              OnClickItemArrow(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Klicken auf den Knopf zum Öffnen/Minimieren der Elementliste     |
//+------------------------------------------------------------------+
bool CTreeView::OnClickItemArrow(const string clicked_object)
  {
//--- Verlassen bei unterschiedlichen Objektnamen
   if(::StringFind(clicked_object,CElement::ProgramName()+"_0_treeitem_arrow_",0)<0)
      return(false);
//--- Abfrage der Kennung des gew. Objektes
   int id=IdFromObjectName(clicked_object);
//--- Verlassen, wenn die Kennung nicht passt
   if(id!=CElement::Id())
      return(false);
//--- Abfrage des Index des Elementes in der allg. Liste
   int list_index=IndexFromObjectName(clicked_object);
//--- Abfrage des Status' des Elementpfeils und setze das Gegenteil
   m_t_item_state[list_index]=!m_t_item_state[list_index];
   ((CChartObjectBmpLabel*)m_items[list_index].Object(1)).State(m_t_item_state[list_index]);
//--- Aktualisiere die Baumansicht
   UpdateTreeViewList();
//--- Berechne die Position des Schiebereglers der Bildlaufleiste
   m_scrollv.CalculateThumbY();
//--- Zeige die Elemente des gew. Tab-Elementes
   ShowTabElements();
   return(true);
  }
  • Klick auf das Element in der Baumansicht - die Methode CTreeView::OnClickItem(). Hier werden ganz am Anfang die gleichen Prüfungen wie vorher gemacht. Unter anderem wird die Methode verlassen, wenn eine Bildlaufleiste aktuell aktiv ist. 

Dann in der Schleife iterieren wir über die sichtbaren Teil der Baumansicht. Wir finden das Element, das gedrückt wurde. Sollte das Element bereist gewählt sein, wird die Methode verlassen. Wenn es nicht gewählt wurde, dann wird die Schleife gestoppt und keine Änderung vorgenommen, wenn der Tab-Modus aktiv ist, und die Anzeige des gew. Inhalts ist deaktiviert, aber das Element gar keine Liste enthält. Sonst wird das gewählte Element aktiviert.

Nach der Schleife, wenn wir bis dahin kommen, wird ein neues Element gewählt. Es bedeutet, dass der Index des ausgewählten Elements in der Liste des Inhaltsbereiches und seine Farbe zurückgesetzt werden müssen. So ist das beispielsweise im Windows Explorer realisiert. Dann wird die Liste im Inhaltsbereich aktualisiert, um die letzten Änderungen anzuzeigen. Ganz am Ende wird die Nachricht mit der Ereigniskennung ON_CHANGE_TREE_PATH erstellt, und die kann von der Ereignisbehandlung einer Anwendung des Nutzers oder Anderem bearbeitet werden.  

class CTreeView : public CElement
  {
private:
   //--- Klick auf ein Element der Baumansicht
   bool              OnClickItem(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Klick auf ein Element der Baumansicht                            |
//+------------------------------------------------------------------+
bool CTreeView::OnClickItem(const string clicked_object)
  {
//--- Verlassen, wenn Bildlaufleiste aktiv ist
   if(m_scrollv.ScrollState() || m_content_scrollv.ScrollState())
      return(false);
//--- Verlassen bei unterschiedlichen Objektnamen
   if(::StringFind(clicked_object,CElement::ProgramName()+"_0_treeitem_area_",0)<0)
      return(false);
//--- Abfrage der Kennung des gew. Objekt
   int id=IdFromObjectName(clicked_object);
//--- Verlassen, wenn Kennung nicht passt
   if(id!=CElement::Id())
      return(false);
//--- Abfrage der aktuellen Position des Schiebereglers der Bildlaufleiste
   int v=m_scrollv.CurrentPos();
//--- Iterieren über die Liste
   for(int r=0; r<m_visible_items_total; r++)
     {
      //--- Prüfung auf Verletzung der Beschränkung
      if(v>=0 && v<m_items_total)
        {
         //--- Abfrage des allg. Index
         int li=m_td_list_index[v];
         //--- Wenn Element in der Liste gewählt wurde
         if(m_items[li].Object(0).Name()==clicked_object)
           {
            //--- Verlassen, wenn Element bereits gewählt wurde
            if(li==m_selected_item_index)
               return(false);
            //--- Wenn der Tab-Modus aktiv ist und Inhaltsanzeige inaktiviert ist,
            //    wird ohne Liste kein Element ausgewählt
            if(m_tab_items_mode && !m_show_item_content)
              {
               //--- Wenn das gew. Element keine Liste hat, Schleife beenden
               if(m_t_items_total[li]>0)
                  break;
              }
            //--- Setze die Farbe auf das vorher gew. Element
            m_items[m_selected_item_index].HighlightItemState(false);
            //--- Sichere dessen Index und ändere dessen Farbe
            m_selected_item_index=li;
            m_items[li].HighlightItemState(true);
            break;
           }
         v++;
        }
     }
//--- Rücksetzen der Farbe des Inhaltsbereiches
   if(m_selected_content_item_index>=0)
      m_content_items[m_selected_content_item_index].HighlightItemState(false);
//--- Rücksetzen des gew. Elementes
   m_selected_content_item_index=WRONG_VALUE;
//--- Erneuern der Inhaltsliste
   UpdateContentList();
//--- Berechne die Position des Schiebereglers der Bildlaufleiste
   m_content_scrollv.CalculateThumbY();
//--- Korrigieren der Inhaltsliste
   ShiftContentList();
//--- Zeige die Elemente des gew. Tab-Elementes
   ShowTabElements();
//--- Sende die Nachricht über das neugew. Verzeichnis in der Baumansicht
   ::EventChartCustom(m_chart_id,ON_CHANGE_TREE_PATH,0,0,"");
   return(true);
  }
  • Klick auf einem Element in der Liste des Inhaltsbereiches – die Methode CTreeView::OnClickContentListItem (). Der Code dieser Methode ist ähnlich der vorigen, wenn auch deutlich einfacher. Es sollte nur erwähnt werden, dass, wenn Sie im Inhaltsbereich auf ein Element klicken, auch ein Ereignis mit der Kennung ON_CHANGE_TREE_PATH erzeugt wird, womit zum Beispiel im Dateinavigator eine Datei ausgewählt werden kann

Die Interaktion der Elemente mit den oben im Artikel erwähnten Methoden führt schließlich dazu, dass beide Listen (wenn der Inhaltsbereich aktiv ist) augenblicklich neu gebildet werden. Die Ereignisbehandlung der Element mit den oben erwähnten Methoden kann im Detail im Folgenden untersucht werden:

//+------------------------------------------------------------------+
//| Ereignisbehandlung                                               |
//+------------------------------------------------------------------+
void CTreeView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handhabung der Mausbewegung  
//...
//--- Handhabung des Klicks der linken Maustaste über einem Objekt
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Verlassen, wenn Inhaltsliste änderbar ist
      if(m_x_resize.IsVisible() || m_x_resize.State())
         return;
      //--- Klick auf dem Elementpfeil
      if(OnClickItemArrow(sparam))
         return;
      //--- Klick auf Element in Baumansicht
      if(OnClickItem(sparam))
         return;
      //--- Klick auf Element im Inhaltsbereich
      if(OnClickContentListItem(sparam))
         return;
      //--- Verschieben der Liste mit dem Schieberegler
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam))
         ShiftTreeList();
      if(m_content_scrollv.OnClickScrollInc(sparam) || m_content_scrollv.OnClickScrollDec(sparam))
         ShiftContentList();
      //---
      return;
     }
  }

Die erste Version der Klasse zum Erstellen eines Elementes der Baumansicht ist fertig. Aber zum richtigen Funktionieren in allen Modi muss sie in die Bibliothek integriert werden. 



Die Integration der Elemente in die Bibliothek

Dateien mit Klassen der Baumansicht müssen mit der Datei WndContainer.mqh verbunden werden (siehe den Quellcode unten). Wir ergänzen zu der Struktur der Arrays der Elemente einen nutzerdefinierten Array für die Baumansicht. Weiters benötigen wir Methoden, um die Anzahl der Baumansichten abzufragen und zur Sicherung der Pointer auf die Elemente, die Teil der Liste der Baumansichten sind. Der Code dieser Methoden kann eingehend in den dem Artikel beigefügten Dateien studiert werden.

#include "TreeItem.mqh"
#include "TreeView.mqh"
//+------------------------------------------------------------------+
//| Klasse zu speichern alle Interface-Objekte                       |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Array der Fenster
   CWindow          *m_windows[];
   //--- Struktur der Fensterelemente
   struct WindowElements
     {
      //--- Liste der Baumansichten
      CTreeView        *m_treeview_lists[];
     };
   //--- Ein Array von Arrays von Elementen für jedes Fenster
   WindowElements    m_wnd[];
   //---
public:
   //--- Anzahl der Listen von Baumansichten
   int               TreeViewListsTotal(const int window_index);
   //---
private:
   //--- Sichern der Pointer auf Elemente der Listen der Baumansichten
   bool              AddTreeViewListsElements(const int window_index,CElement &object);
  };

Einige Ergänzungen müssen bei der aus der Klasse CWndContainer abgeleiteten Klasse CWndEvents gemacht werden, wo de Hauptereignisse bearbeitet werden. Zunächst einmal, wenn eine Baumansicht als Tab-Form zur Gruppierung in einem graphischen Interface verwendet wird, dürfen nur die Elemente des im Augenblick Gewählten gezeigt werden. Daher muss der Code der Methode CWndEvents::OnWindowUnroll() hinzugefügt werden, wie der Code unten zeigt. In diesem besonderen Fall werden die Elemente auf ihre eigenen Arrays verteilt. Statt über das ganze Array aller Elemente zu iterieren, genügt es über die eigenen Arrays zu iterieren, die in der spezifischen Situation relevant sind. 

//+------------------------------------------------------------------+
//| ON_WINDOW_UNROLL Ereignis                                        |
//+------------------------------------------------------------------+
bool CWndEvents::OnWindowUnroll(void)
  {
//--- Gibt es ein "unroll window" Signal
   if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL)
      return(false);
//--- Index des aktiven Fensters
   int awi=m_active_window_index;
//--- Wenn die Kennung des Fensters und die des Unterfensters passen
   if(m_lparam==m_windows[awi].Id() && (int)m_dparam==m_subwin)
     {
      int elements_total=CWndContainer::ElementsTotal(awi);
      for(int e=0; e<elements_total; e++)
        {
         //--- Mach alle Elemente sichtbar unabhängig von der Form und ...
         if(m_wnd[awi].m_elements[e].ClassName()!="CWindow")
           {
            //--- ... den Elementen in einer Dropdown-Liste
            if(!m_wnd[awi].m_elements[e].IsDropdown())
               m_wnd[awi].m_elements[e].Show();
           }
        }
      //--- Gibte es Tabs, zeige nur die gew. Elemente
      int tabs_total=CWndContainer::TabsTotal(awi);
      for(int t=0; t<tabs_total; t++)
         m_wnd[awi].m_tabs[t].ShowTabElements();
      //--- Bei der Baumansicht, zeige nur die Elemente der gew. Tab-Elemente
      int treeview_total=CWndContainer::TreeViewListsTotal(awi);
      for(int tv=0; tv<treeview_total; tv++)
         m_wnd[awi].m_treeview_lists[tv].ShowTabElements();
     }
//--- Aktualisiere den Ort aller Elemente
   MovingWindow();
   m_chart.Redraw();
   return(true);
  }

Die gleiche Schleife sollte der Methode CWndEvents::OnOpenDialogBox() hinzugefügt werden, gleich nach der ähnlichen Schleife über die Elemente des Typs CTabs (tab). Wir besprechen diesen Code nicht hier, um in dem Artikel etwas Platz zu sparen. 

Es ist auch wichtig, für die Baumansicht die "eigenen" Arrays zu leeren. Die folgende Zeile sollte der Methode CWndEvents::Destroy() hinzugefügt werden, damit das auch für die anderen "eigenen" Arrays geschieht: 

::ArrayFree(m_wnd[w].m_treeview_lists);

 

 

Testen der Elemente der Baumansicht

Alles scheint bereit zu sein, um die Baumansicht zu testen. Wir verwenden den EA aus dem vorherigen Artikel und löschen alles, außer dem Hauptmenü und der Zeichenkette des Status'. Dann deklarieren wir eine Instanz von CTreeView, eine Methode zur Erstellung einer Baumansicht und des Abstandes vom Anker an den die Elemente gebunden sind:

class CProgram : public CWndEvents
  {
private:
   //--- Baumansicht
   CTreeView         m_treeview;
   //---
private:
   //--- Baumansicht
#define TREEVIEW1_GAP_X       (2)
#define TREEVIEW1_GAP_Y       (43)
   bool              CreateTreeView(void);
  };

Als Beispiel erzeugen wir ein Baumansicht wie sie bereits in Fig. 12 in diesem Artikel gezeigt wurde (siehe den Quellcode unten). Es gibt 25 Elemente in der gesamten Liste. Die Anzahl sichtbarer Elemente ist nicht größer als 10. Im gleichen Abschnitt des Artikels, gab es eine Klarstellung, dass bei der Erstellung solcher Liste, unabhängige Parameter angegeben werden müssen,. Vor der Bildung der Arrays mit den Parametern für jedes Element, ist es besser sie alle in einem Tabelle-Editor zu zeigen. Diese einfache Visualisierung vereinfacht die Aufgabe und reduziert die Risiken, Fehler zu machen. 

Wir verwenden für jede Gruppe der Elemente Bilder. Das erste Element beinhaltet die Liste. Es soll beispielsweise sichtbar sein nach dem Erstellen des Elementes (Status true), und die Liste der anderen Elemente wird minimiert sein (Status false). Wir aktivieren die Modi (1) markieren der Elemente, wenn die Maus darüber ist, (2) Anzeige des Inhalts des Nachbarbereiches oder (3) Ändern der Breite des Listenbereiches

Nachdem alle Eigenschaften bestimmt sind, werden wir Elemente mit angegebenen Parametern mit der Methode CTreeView::AddItem() hinzufügen. Danach wird eine Baumansicht erstellt und die Pointer auf sie werden im Basiselement gesichert. 

//+------------------------------------------------------------------+
//| Erstelle Baumansicht                                             |
//+------------------------------------------------------------------+
bool CProgram::CreateTreeView(void)
  {
//--- Anzahl der Elemente in der Baumansicht
#define TREEVIEW_ITEMS 25
//--- Sichere den Pointer des Fensters
   m_treeview.WindowPointer(m_window1);
//--- Koordinaten
   int x=m_window1.X()+TREEVIEW1_GAP_X;
   int y=m_window1.Y()+TREEVIEW1_GAP_Y;
//--- Bilde die Arrays der Baumansicht:
//    Bilder der Elemente
#define A "Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp"   // Expert Advisor
#define I "Images\\EasyAndFastGUI\\Icons\\bmp16\\indicator.bmp" // Indicator
#define S "Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp"    // Script
   string path_bmp[TREEVIEW_ITEMS]=
     {A,I,I,I,I,I,I,I,I,S,S,S,S,S,S,S,S,S,S,S,S,S,S,A,A};
//--- Beschreibung der Elemente (Anzeigetext)
   string item_text[TREEVIEW_ITEMS]=
     {"Advisor01","Indicators","01","02","03","04","05","06","07",
      "Scripts","01","02","03","04","05","06","07","08","09","10","11","12","13",
      "Advisor02","Advisor03"};
//--- Index der allgemeinen Liste des Vorgängerknotens
   int prev_node_list_index[TREEVIEW_ITEMS]=
     {-1,0,1,1,1,1,1,1,1,0,9,9,9,9,9,9,9,9,9,9,9,9,9,-1,-1};
//--- Indices der Elemente der lokalen Liste
   int item_index[TREEVIEW_ITEMS]=
     {0,0,0,1,2,3,4,5,6,1,0,1,2,3,4,5,6,7,8,9,10,11,12,1,2};
//--- Nummer der Knotenebene
   int node_level[TREEVIEW_ITEMS]=
     {0,1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0};
//--- Lokaler Index des Vorgängerknotens
   int prev_node_item_index[TREEVIEW_ITEMS]=
     {-1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,-1,-1};
//--- Anzahl der Elemente in der lokalen Liste
   int items_total[TREEVIEW_ITEMS]=
     {2,7,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
//--- Status der Elementliste
   bool item_state[TREEVIEW_ITEMS]=
     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
//--- Eigenschaften vor der Erstellung
   m_treeview.TreeViewAreaWidth(180);
   m_treeview.ContentAreaWidth(0);
   m_treeview.VisibleItemsTotal(10);
   m_treeview.LightsHover(true);
   m_treeview.ShowItemContent(true);
   m_treeview.ResizeListAreaMode(true);
//--- Eigenschaften der Bildlaufleiste
   m_treeview.GetScrollVPointer().AreaBorderColor(clrLightGray);
   m_treeview.GetContentScrollVPointer().AreaBorderColor(clrLightGray);
//--- Elemente hinzufügen
   for(int i=0; i<TREEVIEW_ITEMS; i++)
      m_treeview.AddItem(i,prev_node_list_index[i],item_text[i],path_bmp[i],
                         item_index[i],node_level[i],prev_node_item_index[i],items_total[i],0,item_state[i],true);
//--- Erstelle die Baumansicht
   if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,x,y))
      return(false);
//--- Hinzufügen des Pointers auf das Element in die Datenbank
   CWndContainer::AddToElementsArray(0,m_treeview);
   return(true);
  }

Der Aufruf dieser Methode muss in der Hauptmethode, die die graphische Schnittstelle eines MQL-Programms erstellt, erfolgen. In diesem Fall ist das die Methode CProgram::CreateExpertPanel().

Wir kompilieren das Programm und lassen es als EA laufen. Das nachfolgende Ergebnis ist im Bild zu sehen. Das erste Element ist geöffnet und ausgewählt. Und dessen Inhalt erscheint in der rechten Hälfte.

 Fig. 18. Test der Baumansicht. Nur das erste Element ist geöffnet.

Fig. 18. Test der Baumansicht. Nur das erste Element ist geöffnet.

Zur Demonstration öffnen wir die Listen aller Elemente. Dafür muss die Pfeiltaste des Elementes gedrückt werden. Wir machen es so und wählen das Element mit der Beschreibung "Scripts", um die Skripte im Inhaltsbereich aufzulisten. Das Ergebnis ist in der Abbildung unten gezeigt. Es erscheint, da mehr als 10 sichtbare Elemente existieren, die Bildlaufleiste. Das dritte Element ist im Inhaltsbereich ausgewählt. Wir sehen auch, dass, wenn der Mauszeiger über der Grenze der beiden Bereiche der Listen schwebt, er zu einem zweiseitigen Pfeil wechselt.

Fig. 19. Die Liste aller Elemente. 

Fig. 19. Die Liste aller Elemente.

Wir erstellen zu Testzwecken einen anderen EA, um den Tab-Modus zu sehen. Wir erstellen drei geöffnete Listen nach dem im Bild unten gezeigten Schema:

 

Fig. 20. Schema der Baumansicht.

Wir sichern Elemente wie CCheckBox und CTable für die Listen der Elemente "Advisors" und "Indicators". Die Tab-Elemente der Liste "Scripts" bleibt leer, für Sie zum Üben. Wir nutzen nicht den gesamten Code. Es darf nicht vergessen werden, welches sind die wichtigsten Modi und Eigenschaften, die für diese Optionen verwendet werden: (1) Modus der Tab-Anzeige, (2) Anzeige der Inhaltsliste ist deaktiviert und (3) ein drittes Tab-Element wurde gewählt

//...
   m_treeview.TabItemsMode(true);
   m_treeview.LightsHover(true);
   m_treeview.ShowItemContent(false);
   m_treeview.SelectedItemIndex((m_treeview.SelectedItemIndex()==WRONG_VALUE) ? 3 : m_treeview.SelectedItemIndex());
//--- Eigenschaften der Bildlaufleiste
//...

Wir müssen das Programm kompilieren und auf dem Chart starten. Das Bild unten zeigt das Ergebnis:

 Fig. 21. Test des Tab-Modus.

Fig. 21. Test des Tab-Modus. 

 


Schlussfolgerung

In diesem Artikel (zweites Kapitel, Teil 8 der Artikel-Serie), betrachteten wir eines der kompliziertesten Elemente aus der Bibliothek der grafischen Benutzeroberfläche - die Baumansicht. Der Artikel enthält drei Klassen: 

  • Die Klasse CPointer zum Erstellen eines Nutzerpointers des Mauscursors.
  • Die Klasse CTreeItem zum Erstellen eines Elementes einer Baumansicht.
  • Die Klasse CTreeView zum Erstellen der Baumansicht.

Im nächsten Artikel entwickeln wir dieses Thema weiter und schreiben eine Klasse, die die Erstellung eines Dateinavigators in Ihrer MQL-Anwendung noch leichter macht. 

Unten sind alle Materialien des Teils 7 der Artikel-Serie, die Sie herunterladen können, um sie zu testen. Falls Sie irgendwelche Fragen haben, bezüglich der Verwendung der Materialien in diesen Dateien, finden Sie vielleicht eine Antwort in den detaillierten Beschreibungen über den Entwicklungsprozess in einem der Artikel der Liste unten, oder Sie stellen einfach Ihre Frage im Kommentarteil dieses Artikels.

Liste der Artikel (Kapitel) Teil 8: