Grafische Interfaces VIII: Die Baumansicht (Kapitel 2)
Inhalt
- Einführung
- Element der Baumansicht
- Entwicklung der Klasse CTreeItem zur Erstellung der Baumansicht
- Die Klasse CPointer für die Verwendung der Maus
- Entwicklung der Klasse CTreeView zur Erstellung der Baumansicht
- Die Parameter der Liste der Elemente
- Die Methoden zur Verwaltung der der Liste der Elemente
- Die Verwaltung der Liste der Bereiche
- Modus tabellarische Objekte
- Die Methoden zur Ereignisbehandlung
- Die Integration der Elemente in die Bibliothek
- Testen der Elemente der Baumansicht
- Schlussfolgerung
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.
- Hintergrund
- Signal der Anwesenheit der lokalen Listeneinträge. Pfeile und Plus-/Minus-Piktogramme zeigen den Status (offen/minimiert) der Liste.
- Element-Namen. Es könnte zum Beispiel erforderlich sein, um visuell ein Objekt einer bestimmten Kategorie zuzuordnen.
- 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.
- Hintergrund der Baumansicht
- Liste der Elemente der Baumansicht
- Vertikale Bildlaufleiste der Baumansicht
- Hintergrund der Inhaltsliste
- Liste der Elemente der Inhaltsliste
- Horizontale Bildlaufleiste der Inhaltsliste
- 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.
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.
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).
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.
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.
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.
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.
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:
- Graphische Interfaces VIII: Der Kalender (Kapitel 1)
- Grafische Interfaces VIII: Die Baumansicht (Kapitel 2)
- Grafische Interfaces VIII: Der Dateinavigator (Kapitel 3)
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2539
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.