
Grafische Interfaces II: Die Trennlinien und Context-Menüelemente (Kapitel 2)
Inhalt
- Einleitung
- Entwicklung einer Klasse für die Erzeugung einer Trennlinie
- Test über das Hinzufügen einer Trennlinie
- Entwicklung einer Klasse für die Erzeugung eines Kontextmenüs
- Test das Hinzufügen eines Kontextmenüs
- Die Weiterentwicklung der Klasse für das Abspeichern von Pointern aller Elemente.
- Schlussfolgerung
Einleitung
Der erste Artikel Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1) betrachtet im Detail den Sinn und Zweck der Bibliothek. Eine vollständige Liste der Links zu den Artikeln finden Sie am Ende von jedem Kapitel. Zudem finden Sie dort eine Möglichkeit das Projekt, entsprechend dem aktuellen Entwicklungszustand, herunterzuladen. Die Dateien müssen in den gleichen Verzeichnissen untergebracht werden, so, wie Sie auch in dem Archiv abgelegt sind.
In dem vorherigen Kapitel haben wir eine Klasse für die Erzeugung eines Menüpunktes geschrieben. Diese wird sowohl als unabhängiges Control und als ein Teil eines Kontextmenüs und eines Hauptmenüs verwendet. In diesem Artikel beschreiben wie die Erzeugung einer Trennlinie, die nicht nur als eigenständiges Element genutzt werden kann, sondern auch als Teil von vielen anderen Elementen. Anschließend haben wir alles, was für die Entwicklung einer Kontextmenü-Klasse notwendig ist, die ebenfalls in diesem Artikel gesprochen wird. Zudem werden wir alle notwendigen Ergänzungen dieser Klasse hinzufügen, die für das Abspeichern von Pointern aller Elemente des grafischen Interfaces dieser Anwendung benötigt werden.
Entwicklung einer Klasse für die Erzeugung einer Trennlinie
In Kontextmenüs, sehen wir neben verschiedenen Typen von Menüpunkten, auch ein weiteres Interface-Element, eine Trennlinie. Dieses Element kann nicht nur in Kontextmenüs verwendet werden. Zum Beispiel besitzt die Statusbar des MetaTrader Trading-Terminals und des MetaEditor Code-Editors ebenfalls vertikale Trennlinien. Dieses ist der Grund, warum wir hierfür eine eigenständige Klasse entwickeln. Somit kann Sie in jedem anderen Control oder auch als separates Element eines grafischen Interfaces verwendet werden.
Um einen Eindruck des Umfangs dieses Vorhabens gewinnen zu können: Eine Trennlinie muss aus mindestens zwei Teilen bestehen. Falls eine Linie heller als ihr Hintergrund ist und eine andere Linie dunkler, dann erzeugt dieses den Eindruck einer Nut auf der Oberfläche. Es gibt zwei Wege, wie eine Trennlinie erzeugt werden kann: (1) Über zwei einfache Objekte des Typs CRectLabel, welche bereits in der Objects.mqh Datei existieren, oder (2) über die Erzeugung des Objektes vom Typ OBJ_BITMAP_LABEL welches dann als Grundlage für weitere Zeichnungen dient. Lassen Sie uns die zweite Option verwenden. Die Standardbibliothek schlägt die CCanvas Klasse für das Zeichnen vor. Diese Klasse besitzt bereits alle notwendigen Methoden für das Zeichnen von einfachen geometrischen Objekten, was uns die Implementation unseres Vorhabens erheblich erleichtern wird.
Die CCanvas Klasse muss so eingefügt werden, dass sie auf die gleiche Art und Weise verwendet werden kann, wie auch andere primitive Objekte, welche bereits in der Objects.mqh Datei existieren. Dieses können wir ganz einfach dadurch erreichen, indem wir die CCanvas Klasse von der CChartObjectBmpLabel Klasse ableiten. Wir müssen lediglich kleinere Änderungen an den Programmcode der CCanvas Klasse vornehmen, damit es später beim Kompilieren des Programms nicht zu Fehlermeldungen kommt. Dies liegt daran, dass es in beiden Klassen, der CCanvas Klasse und der CChartObject Klasse, die als Basis für unsere CChartObjectBmpLabel Klasse dient, ein Feld (variable) mit dem Namen m_chart_id gibt. Aus diesem Grunde wird der Compiler später eine Warnung ausgeben, dass dieser Variablen-Name bereits existiert:
Abbildung 1. Warnung des Kompilers
In der Praxis stellt eine solche Warnung keinen kritischen Fehler da und eine Kompilierung würde auch in diesem Falle stattfinden. Aber es wird empfohlen, solche Situationen zu vermeiden, da sie immer ein Potenzial für unvorhergesehenes Verhalten von Programmen darstellen. Lassen Sie uns dieses als Regel betrachten und sie in Zukunft befolgen. Außerdem müssen die Änderungen in der Canvas.mqh Datei auf jeden Fall vorgenommen werden, weil die CCanvas Klasse von der CChartObjectBmpLabel Klasse abgeleitet wird. Auf diese Weise können wir diese Warnung für immer loswerden. Wir entfernen einfach die Variable m_chart_id aus der CCanvas Klasse. Wenn wir Veränderungen in den Klassen der Standardbibliothek vornehmen, müssen wir uns darüber bewusst sein, dass es bei dem nächsten Terminal Update dazu führen kann, dass die Standardbibliothek ebenso aktualisiert wird und somit unsere Veränderungen verloren gehen. Da wir unser Ziel aber nicht ohne die Veränderungen in der CCanvas Klasse erreichen können, erzeugen wir eine Kopie dieser Datei und platzieren Sie in dem Verzeichnis, in welchem sich auch die Dateien unserer Bibliothek befinden.
Erzeugen Sie ein Canvas Verzeichnis in dem #Include Verzeichnis. Erzeugen Sie eine Kopie von der Datei, die die СCanvas Klasse enthält und nennen Sie sie in CustomCanvas.mqh um Und verändern Sie den Namen der Klasse in CCustomCanvas. Beziehen Sie die ChartObjectsBmpControls.mqh Datei der Standardbibliothek mit in der CustomCanvas.mqh Datei mit ein und machen Sie die CCustomCanvas Klasse zu einer abgeleiteten Klasse von der CChartObjectBmpLabel Klasse. Anschließend entfernen Sie die m_chart_id Variable Aus dem Klassen-Körper der CCustomCanvas Klasse und bilden sie einen Konstruktor.
//+------------------------------------------------------------------+ //| CustomCanvas.mqh | //| Copyright 2009-2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include <Files\FileBin.mqh> #include <Controls\Rect.mqh> #include <ChartObjects\ChartObjectsBmpControls.mqh> //... //+------------------------------------------------------------------+ //| Class CCustomCanvas | //| Verwendung: Klasse für das Arbeiten mit dynamischen Ressourcen | //+------------------------------------------------------------------+ class CCustomCanvas : public CChartObjectBmpLabel { //...
Jetzt beziehen Sie die CustomCanvas.mqh Datei mit in der Objects.mqh Datei mit ein:
//+------------------------------------------------------------------+ //| Objects.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Enums.mqh" #include "Defines.mqh" #include "..\Canvas\CustomCanvas.mqh" #include <ChartObjects\ChartObjectsBmpControls.mqh> #include <ChartObjects\ChartObjectsTxtControls.mqh>
Anschließend lassen Sie uns die CRectCanvas Klasse erzeugen, welche eine abgeleitete Klasse von der CCustomCanvas Kasse sein muss. Die CRectCanvas Klasse gleicht den anderen Klassen, welche sich in der Objects.mqh Datei befinden. Deren Inhalt haben wir uns in dem vorherigen Artikel angesehen. Nun kann sie für die Zeichnung aller anderen Interface-Elemente verwendet werden, und sie wird somit nun zu einem Teil der Bibliothek, die wir hier entwickeln.
Nun ist alles bereit, damit wir mit der Entwicklung der CSeparateLine Klasse beginnen können welche für die Erzeugung einer Trennlinie gedacht ist. Erzeugen Sie die SeparateLine.mqh Datei in dem Controls Verzeichnis. Beziehen Sie die Element.mqh und die Window.mqh Dateien mit ein. Anschließend gehen sie nach den folgenden Schritten vor:
1) Erzeugen Sie die CSeparateLine Klasse;
2) In dieser Klasse, deklarieren Sie einen Pointer zu dem Formular, zu welchen das Element hinzugefügt werden soll und erzeugen Sie eine Methode für das Abspeichern dieses Pointers;
3) Erzeugen sie eine Instanz der CRectCanvas Klasse;
4) Deklarieren und verwalten der Standard visuellen Methoden für alle Elemente, welche für die Verwaltung dieses Elementes verwendet werden können.
//+------------------------------------------------------------------+ //| SeparateLine.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| Klasse für die Erzeugung einer Trennlinie | //+------------------------------------------------------------------+ class CSeparateLine : public CElement { private: //--- Ein Pointer zu der Form zu welchem das Element hinzugefügt worden ist CWindow *m_wnd; //--- Objekt für die Erzeugung einer Trennlinie CRectCanvas m_canvas; //--- public: CSeparateLine(void); ~CSeparateLine(void); //--- Speichert den Pointer des zugewiesenen Formulars void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Chart Eventhandler virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Bewegen des Elementes virtual void Moving(const int x,const int y); //--- (1) Anzeigen, (2) verstecken, (3) zurücksetzen, (4) löschen virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); }; //+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CSeparateLine::CSeparateLine(void) { //--- Abspeichern des namens der Elementklasse in der Basisklasse CElement::ClassName(CLASS_NAME); } //+------------------------------------------------------------------+ //| Destruktor | //+------------------------------------------------------------------+ CSeparateLine::~CSeparateLine(void) { } //+------------------------------------------------------------------+
Um das Erscheinungsbild der Trennlinie festlegen zu können, werden wir drei Methoden erzeugen. Diese Methoden werden für die folgenden Einstellungen verwendet:
- Den Typ der Trennlinie: (1) horizontal, (2) vertikal.
- Die Farbe des dunklen Teils.
- Die Farbe des hellen Teils.
Die Enumeration, welche wir für die Spezifizierung des Typs verwenden, muss in derEnums.mqh Datei hizugefügt werden:
//+------------------------------------------------------------------+ //| Enumeration Für die Typen der Trennlinie | //+------------------------------------------------------------------+ enum ENUM_TYPE_SEP_LINE { H_SEP_LINE =0, V_SEP_LINE =1 };
Nun können wir die zugehörigen Variablen und Methoden zu der CSeparateLine Klasse hinzufügen und mit der Initialisierung der Standardwerte in den Konstruktor fortfahren:
class CSeparateLine : public CElement { private: //--- Eigenschaften ENUM_TYPE_SEP_LINE m_type_sep_line; color m_dark_color; color m_light_color; //--- public: //--- (1) Line type, (2) line colors void TypeSepLine(const ENUM_TYPE_SEP_LINE type) { m_type_sep_line=type; } void DarkColor(const color clr) { m_dark_color=clr; } void LightColor(const color clr) { m_light_color=clr; } //--- }; //+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CSeparateLine::CSeparateLine(void) : m_type_sep_line(H_SEP_LINE), m_dark_color(clrBlack), m_light_color(clrDimGray) { }
Wir müssen lediglich Methoden für die Erzeugung der Elemente und die Methoden, in welchen die Trennlinie gezeichnet wird hinzufügen. Die folgenden Parameter müssen den public Methoden für die Erzeugung des Elementes CreateSeparateLine() übergeben werden. Diese Methoden werden dann in der Anwendung des Benutzers aufgerufen:
- Chart Identifizierer;
- Die Nummer des Chart-Fensters;
- Die Nummer des Indexes der Linie. Dieses ist notwendig, falls es mehrere Trennlinien gibt, die innerhalb einer Schleife erzeugt werden. Das gilt sowohl für Kontextmenüs, wie auch für jedes andere Interface-element. In so einem Falle wäre ein einziger Elementbezeichner für die Erzeugung eines eindeutigen Namens für dieses grafische Objekt nicht ausreichend;
- Koordinaten;
- Größe
class CSeparateLine : public CElement { public: //--- Erzeugen einer Trennlinie bool CreateSeparateLine(const long chart_id,const int subwin,const int index, const int x,const int y,const int x_size,const int y_size); //--- private: //--- Erzeugt die Grundlage (Canvas) für das Zeichnen einer Trennlinie bool CreateSepLine(void); //--- Zeichnen einer Trennlinie void DrawSeparateLine(void); //--- };
Der Code der CreateSeparateLine() Unterscheidet sich im Prinzip nicht von dem Programm ähnlicher Methoden (Wie zum Beispiel in der CMenuItem Klasss), was der Grund dafür ist dass wir uns als nächstes den Programmcode der CreateSepLine() Methode ansehen.
Wie bei allen anderen Methoden dieses Typs, wird der Name des grafischen Objektes zu Beginn festgelegt. Anschließend wird das grafische Objekt (canvas) Erzeugt, auf welchem wir zeichnen wollen. Wir wollen darauf hinweisen, dass für die Erzeugung des Objektes vom Typ OBJ_BITMAP_LABEL die CreateBitmapLabel() Methode verwendet wird, welche zu der CCustomCanvas Klasse gehört. In dieser Klasse wird das Hinzufügen eines Objektes zu dem Chart, wenn ein neues Objekt erzeugt wird, nicht wie in derCChartObjectBmpLabel Klasse unterstützt, wo die CChartObject::Attach() Methode der Basisklasse nach der Erzeugung des Objektes verwendet wird. Wir müssen uns selbst darum kümmern. Da die CCustomCanvas Klasse von der CChartObjectBmpLabel Klasse abgeleitet wurde, können wir auf die CChartObject::Attach() Methode der Basisklasse zugreifen. Falls ein Objekt nicht zu dem Chart hinzugefügt worden ist, ist es nicht möglich dieses zu verwalten.
Nachdem ein Objekt erzeugt wurde, es zu dem Chart hinzugefügt wurde und seine Eigenschaften festgelegt wurden, können wir eine Trennlinie auf unsere benutzerdefinierte Grundlage (canvas) mit Hilfe derDrawSeparateLine() Methode zeichnen. Dieses wird in dem nachfolgenden Programmcode gezeigt: Anschließen wird der Object-Pointer in dem Array der CElement Basisklasse abgespeichert.
//+------------------------------------------------------------------+ //| Erzeugt die Grundlage (canvas) für das Zeichnen einer Trennlinie | //+------------------------------------------------------------------+ bool CSeparateLine::CreateSepLine(void) { //--- Den Objektnamen bilden string name=CElement::ProgramName()+"_separate_line_"+(string)CElement::Index()+"__"+(string)CElement::Id(); //--- Erzeugen eines Objektes if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE)) return(false); //--- Zum Chart hinzufügen if(!m_canvas.Attach(m_chart_id,name,m_subwin,1)) return(false); //--- Eigenschaften m_canvas.Background(false); //--- Abstände von Eckpunkt m_canvas.XGap(m_x-m_wnd.X()); m_canvas.YGap(m_y-m_wnd.Y()); //--- Zeichnen der Trennlinie DrawSeparateLine(); //--- Hinzufügen zu dem Array CElement::AddToArray(m_canvas); return(true); }
Der Programmcode der Methode DrawSeparateLine() ist recht einfach. Zunächst müssen wir die Größe des Canvas ermitteln. Dann säubern/löschen wir das Canvas unter Verwendung der CCustomCanvas::Erase() Methode. Anschließend fährt das Programm in Abhängigkeit, ob eine vertikale oder eine horizontale Linie gezeichnet werden soll, mit dem entsprechenden Programmblock fort. Wir werden hier beispielhaft die Erzeugung einer horizontalen Linie zeigen. Zunächst werden die Koordinaten für zwei Punkte der Linie definiert und dann wird die erste Linie in dem oberen linken Teil des Canvas gezeichnet. Dann werden die Koordinaten für die zweite Linie in dem unteren Teil des Canvas definiert. Falls das Canvas nur zwei Pixel groß ist, dann liegen die beiden Linien sehr dicht beieinander. Man kann einen Abstand zwischen den zwei Linien darüber definieren, dass man die Höhe des Canvas größer als 2 Pixel macht. Um die Veränderungen sichtbar machen zu können, muss das Canvas am Ende der Methode über die Methode CCustomCanvas::Update() neu gezeichnet werden.
//+------------------------------------------------------------------+ //| Zeichnet eine Trennlinie | //+------------------------------------------------------------------+ void CSeparateLine::DrawSeparateLine(void) { //--- Die Koordinaten für die Linien int x1=0,x2=0,y1=0,y2=0; //--- Die Größe des Canvas int x_size =m_canvas.X_Size()-1; int y_size =m_canvas.Y_Size()-1; //--- Clear Canvas m_canvas.Erase(::ColorToARGB(clrNONE,0)); //--- Falls es sich um eine horizontale Linie handelt if(m_type_sep_line==H_SEP_LINE) { //--- Die dunklere Linie darüber x1=0; y1=0; x2=x_size; y2=0; //--- m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_dark_color)); //--- Die hellere Linie darunter x1=0; x2=x_size; y1=y_size; y2=y_size; //--- m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_light_color)); } //--- Falls die Linie vertikal ist else { //--- Die dunkle Linie auf der linken Seite x1=0; x2=0; y1=0; y2=y_size; //--- m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_dark_color)); //--- Die hellere Linie auf der rechten Seite x1=x_size; y1=0; x2=x_size; y2=y_size; //--- m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_light_color)); } //--- Neuzeichnen des Canvas m_canvas.Update(); }
Test das Hinzufügen einer Trennlinie
Nun können wir einen ersten Test durchführen. In dem vorherigen Artikel haben wir einen Menüpunkt zu einem Formular hinzugefügt. Wir werden nun eine Trennlinie auf die gleiche Art und Weise als ein separates Interface Element hinzufügen.
Ich will sie hiermit nochmal kurz über den Prozess des Hinzufügens eines Elementes zu dem Formular informieren.
- Falls ich das Element noch nicht in der Basis befindet, muss die Datei in der WndContainer.mqh Datei mit einbezogen werden.
- Da die benutzerdefinierte Klasse unserer Anwendung (In unserem Fall ist es die CProgram) von der CWndContainer -> CWndEvents abgeleitet werden muss, wird die Instanz für die Erzeugung der Elementen-Klasse und ihre Methoden verfügbar.
- Anschließend rufen Sie die Methode für die Erzeugung des Elementes in der Methode auf, wo die grafischen Interfaces des Programms erzeugt werden.
Falls wir alles richtig gemacht haben, dann sollte nach dem Kompilieren und Starten des Programms das Ergebnis wie folgt aussehen:
Abbildung 2. Test der Trennlinie.
Die Entwicklung der CSeparateLine Klasse Ist hiermit abgeschlossen und nun sind wir bereit für die Implementierung der Klasse für die Erzeugung eines Kontextmenüs.
Entwicklung einer Klasse für die Erzeugung eines Kontextmenüs
Zuvor haben wir bei der Entwicklung der Bibliothek drei Interface-Elemente erfolgt: (1) Ein Formular mit Controls (CWindow), (2) Das Menüpunkt-Control (CMenuItem) und (3) die Trennlinie (CSeparateLine). Jedes dieser Elemente gehört zu den einfachen Typen von Elementen, da sie nur aus primitiven Objekten zusammengestellt wurden. Ein Kontextmenü wird als eine komplexe Klasse (compound) bezeichnet. Sie wird nicht nur auf einfachen Objekten zusammengestellt, sondern auch noch aus anderen Elementen. Die Basisklasse für diese Elemente ist CElement.
Erzeugen Sie die ContextMenu.mqh Datei in dem Controls Verzeichnis in dem Verzeichnis unserer Bibliothek. In dieser Datei beziehen Sie die Dateien, die für das Erzeugen des Kontextmenüs notwendig sind, mit ein:
//+------------------------------------------------------------------+ //| ContextMenu.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "MenuItem.mqh" #include "SeparateLine.mqh"
Anschließend erzeugen Sie die CContextMenu Klasse mit den standard-virtuellen Methoden für alle Bibliothekselemente, den Pointer des Formulars und die Methode um diesen abzuspeichern. Dann brauchen wir ein Objekt vom Typ OBJ_RECTANGLE_LABEL als Hintergrund. Für die Erzeugung dieses Objektes verwenden wir die CRectLabel Klasse aus der Object.mqh Datei. Die CMenuItem Klasse für die Menüpunkte wurde in dem vorherigen Artikel erzeugt. Da ein Kontext-Menü normalerweise aus mehr als nur einem CMenuItem besteht und die Anzahl dieser Klassen zu Beginn nicht bekannt ist, verwenden wir ein dynamisches Array für die Instanzen von dieser Klasse. Das gleiche gilt für die Trennlinien (CSeparateLine) Eines Kontextmenüs.
//+------------------------------------------------------------------+ //| Klasse für das Erzeugen eines Kontextmenüs | //+------------------------------------------------------------------+ class CContextMenu : public CElement { private: //--- Ein Pointer zu der Form zu welchem das Element hinzugefügt worden ist CWindow *m_wnd; //--- Objekte für die Erzeugung eines Menüelements CRectLabel m_area; CMenuItem m_items[]; CSeparateLine m_sep_line[]; //--- public: CContextMenu(void); ~CContextMenu(void); //--- Speichert den Pointer des zugewiesenen Formulars void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- Chart Eventhandler virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Timer virtual void OnEventTimer(void); //--- Bewegen des Elementes virtual void Moving(const int x,const int y); //--- (1) Anzeigen, (2) verstecken, (3) zurücksetzen, (4) löschen virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Setting, (2) resetting of priorities for left clicking virtual void SetZorders(void); virtual void ResetZorders(void); }; //+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CContextMenu::CContextMenu(void) { //--- Abspeichern des namens der Elementklasse in der Basisklasse CElement::ClassName(CLASS_NAME); //--- Das Kontextmenü ist ein Dropdown Element CElement::m_is_dropdown=true; } //+------------------------------------------------------------------+ //| Destruktor | //+------------------------------------------------------------------+ CContextMenu::~CContextMenu(void) { } //+------------------------------------------------------------------+
Um das Erscheinungsbild des Kontextmenüs festlegen zu können, sind entsprechende Variablen und Methoden notwendig:
class CContextMenu : public CElement { private: //--- Die Eigenschaften des Hintergrundes int m_area_zorder; color m_area_color; color m_area_border_color; color m_area_color_hover; color m_area_color_array[]; //--- Die Eigenschaften von dem Menüpunkt (Menu items) int m_item_y_size; color m_item_back_color; color m_item_border_color; color m_item_back_color_hover; color m_item_back_color_hover_off; color m_label_color; color m_label_color_hover; string m_right_arrow_file_on; string m_right_arrow_file_off; //--- Die Eigenschaften der Trennlinie color m_sepline_dark_color; color m_sepline_light_color; //--- public: //--- Die Anzahl der Menüpunkte int ItemsTotal(void) const { return(::ArraySize(m_items)); } //--- Die Methoden für das Festlegen des Erscheinungsbildes des Kontextmenüs: // Die Farbe des Hintergrundes void MenuBackColor(const color clr) { m_area_color=clr; } void MenuBorderColor(const color clr) { m_area_border_color=clr; } //--- (1) Höhe, (2) Hintergrundfarbe und (3) Farbe des Rahmens void ItemYSize(const int y_size) { m_item_y_size=y_size; } void ItemBackColor(const color clr) { m_item_back_color=clr; } void ItemBorderColor(const color clr) { m_item_border_color=clr; } //--- Die Hintergrundfarbe des (1) verfügbaren und des(2) gesperrten Menüpunktes, wenn sich der Mauszeiger darüber befindet. void ItemBackColorHover(const color clr) { m_item_back_color_hover=clr; } void ItemBackColorHoverOff(const color clr) { m_item_back_color_hover_off=clr; } //--- (1) Standard und (2) Fokussierte Textfarbe void LabelColor(const color clr) { m_label_color=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } //--- Definieren eines Icons um darauf hinzuweisen dass der Menüpunkt ein Kontextmenü besitzt void RightArrowFileOn(const string file_path) { m_right_arrow_file_on=file_path; } void RightArrowFileOff(const string file_path) { m_right_arrow_file_off=file_path; } //--- (1) Dunkle und(2) helle Farbe der Trennlinien void SeparateLineDarkColor(const color clr) { m_sepline_dark_color=clr; } void SeparateLineLightColor(const color clr) { m_sepline_light_color=clr; } //--- };
Der Menüpunkt und sein Kontextmenü müssen miteinander verbunden werden, ansonsten ist es nicht möglich diese Elemente korrekt zu verwalten. Um genau zu sein, muss das Kontextmenü und seine Elemente einen Zugriff auf den Menüpunkt haben, zu welchem sie hinzugefügt wurden, und dieses ist der vorherige Knotenpunkt. Das bedeutet, dass sowohl die CContextMenu Klasse, als auch die CMenuItem Klasse einen Pointer des CMenuItem Typs benötigen, sowie Methoden diesen Pointer abzuspeichern und abfragen zu können. Dieser Pointer wird auch für die Überprüfung der korrekten Reihenfolge der Erzeugung der graphischen Interfaces eines Programms verwendet. Dieses werden wir später zeigen, wenn wir die Methoden für die Erzeugung eines Kontextmenüs besprechen.
Hinzufügen des Pointers und der Methoden für das Abspeichern und der Abfrage in der CContextMenu Klasse:
class CContextMenu : public CElement { private: //--- Pointer zu dem vorherigen Knotenpunkt CMenuItem *m_prev_node; //--- public: //--- Abrufen und speichern des Pointers des vorherigen Knotenpunkte CMenuItem *PrevNodePointer(void) const { return(m_prev_node); } void PrevNodePointer(CMenuItem &object) { m_prev_node=::GetPointer(object); } //--- };
Das gleiche muss der CMenuItem Klasse hinzugefügt werden:
class CMenuItem : public CElement { private: //--- Pointer zu dem vorherigen Knotenpunkt CMenuItem *m_prev_node; //--- public: //--- Abrufen und speichern des Pointers des vorherigen Knotenpunkte CMenuItem *PrevNodePointer(void) const { return(m_prev_node); } void PrevNodePointer(CMenuItem &object) { m_prev_node=::GetPointer(object); } //--- };
Das graphische Interface wird in der benutzerdefinierten Klasse der Anwendung (CProgram) eingebaut. Wenn wir das Kontextmenü erzeugen, benötigen wir eine Methode, die die Anzahl der Elemente in dem Kontextmenü spezifiziert und einige Parameter, die nicht für alle Elemente gleich sind. Lassen Sie uns die CContextMenu::AddItem() Methode schreiben, welche folgende Parameter akzeptiert: (1) Den Text des Menüpunktes, (2) Einen Pfad zu dem Icon für das Label von dem verfügbaren Element, (3) Ein Pfad zu dem Icon für das gesperrte Element und (4) Den Typ des Menüpunktes. Es werden zudem Arrays für das Abspeichern dieser Werte benötigt. The size of these arrays will be increased by one element each time when the CContextMenu::AddItem() method is called.
class CContextMenu : public CElement { private: //--- Arrays Für die Eigenschaften des Menüpunktes: // (1) Text, (2) label of the available item, (3) label of the blocked item string m_label_text[]; string m_path_bmp_on[]; string m_path_bmp_off[]; //--- public: //--- Fügt einen Menüpunkt mit spezifizierten Eigenschaften vor der Erzeugung des Kontextmenüs hinzu void AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type); //--- }; //+------------------------------------------------------------------+ //| Fügt einen Menüpunkt hinzu | //+------------------------------------------------------------------+ void CContextMenu::AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type) { //--- Die Größe des Arrays um einen erhöhen int array_size=::ArraySize(m_items); ::ArrayResize(m_items,array_size+1); ::ArrayResize(m_label_text,array_size+1); ::ArrayResize(m_path_bmp_on,array_size+1); ::ArrayResize(m_path_bmp_off,array_size+1); //--- Abspeichern der übergebenen Werte m_label_text[array_size] =text; m_path_bmp_on[array_size] =path_bmp_on; m_path_bmp_off[array_size] =path_bmp_off; //--- Festlegen des Typs des Menüpunktes m_items[array_size].TypeMenuItem(type); }
Um eine Trennlinie einem Kontextmenü hinzufügen zu können, benötigen wir ein Array um die Indexnummer des Menüpunktes, nach welchem diese Trennlinie gezeichnet werden soll, abspeichern zu können. Die Indexnummer des Menüpunktes wird der CContextMenu::AddSeparateLine() Methode übergeben. Der nachfolgende Programmcode zeigt dieses:
class CContextMenu : public CElement { private: //--- Ein Array mit Indexnummern von Menüpunkten, nach welchen eine Trennlinie gesetzt werden soll int m_sep_line_index[]; //--- public: //--- Fügt eine Trennlinie nach den angegebenen Elementen und vor der Erzeugung des Kontextmenüs hinzu void AddSeparateLine(const int item_index); //--- }; //+------------------------------------------------------------------+ //| Süd eine Trennlinie hinzu | //+------------------------------------------------------------------+ void CContextMenu::AddSeparateLine(const int item_index) { //--- Die Größe des Arrays um einen erhöhen int array_size=::ArraySize(m_sep_line); ::ArrayResize(m_sep_line,array_size+1); ::ArrayResize(m_sep_line_index,array_size+1); //--- Abspeichern der Index-nummer m_sep_line_index[array_size]=item_index; }
Sie benötigen nur noch Methoden, die uns über die Indexnummer erlauben, folgende Informationen zu erhalten: (1) Den Pointer zu dem Menüpunkt, (2) Beschreibung (Angezeigter Text) und (3) den Typ. In jeder Methode wird zunächst eine Überprüfung, ob die Array-Größe überschritten wird, durchgeführt und anschließend wird der Index angepasst. Falls der übergebene Index größer als das Array ist, dann wird das letzte Element aufgerufen und wenn der Index kleiner als Null ist, dann wird das erste Element aufgerufen.
class CContextMenu : public CElement { public: //--- Gibt den Pointer des Elementes von dem Kontext-Menü zurück CMenuItem *ItemPointerByIndex(const int index); //--- Gibt die Beschreibung zurück (Angezeigter Text) string DescriptionByIndex(const int index); //--- Gibt den Typ des Menüpunktes zurück ENUM_TYPE_MENU_ITEM TypeMenuItemByIndex(const int index); //--- }; //+------------------------------------------------------------------+ //| Gibt den Pointer des Menüpunktes über den Index zurück | //+------------------------------------------------------------------+ CMenuItem *CContextMenu::ItemPointerByIndex(const int index) { int array_size=::ArraySize(m_items); //--- Falls es kein Element in dem Kontextmenü gibt, dann benachrichtigen if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has at least one item!"); } //--- Korrekturen, falls die Größe überschritten wird int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index; //--- Rückgabe des Pointers return(::GetPointer(m_items[i])); } //+------------------------------------------------------------------+ //| Gib den Namen des Elementes über den Index zurück | //+------------------------------------------------------------------+ string CContextMenu::DescriptionByIndex(const int index) { int array_size=::ArraySize(m_items); //--- Falls es kein Element in dem Kontextmenü gibt, dann benachrichtigen if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has at least one item!"); } //--- Korrekturen, falls die Größe überschritten wird int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index; //--- Gib die Beschreibung des Elementes zurück return(m_items[i].LabelText()); } //+------------------------------------------------------------------+ //| Gib den Typ des Elementes über den Index zurück | //+------------------------------------------------------------------+ ENUM_TYPE_MENU_ITEM CContextMenu::TypeMenuItemByIndex(const int index) { int array_size=::ArraySize(m_items); //--- Falls es kein Element in dem Kontextmenü gibt, dann benachrichtigen if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has at least one item!"); } //--- Korrekturen, falls die Größe überschritten wird int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index; //--- Gibt den Typ des Elementes zurück return(m_items[i].TypeMenuItem()); }
Ein Kontextmenü könnte auch verschiedene Gruppen von Radio Elementen beinhalten. Um Verwirrungen bei der Identifizierung, welches Elementen angeklickt worden ist, zu vermeiden, benötigt jedes Radio Element seinen eigenen Gruppen-Bezeichner und den Index in Relation zu der Liste der Gruppe. Das nachfolgende Bild zeigt, dass neben den allgemeinen Indizes und der Kennung des Kontextmenü-Elementes, ein Radio Element seine eigenen Unterscheidungsmerkmale besitzt.
Abbildung 3. Schematische Darstellung der Identifikatoren und Indizes der verschiedenen Gruppen in einem Kontextmenü.
Bei der Bildung eines Kontextmenüs, muss zunächst der Typ des Menüpunktes identifiziert werden, bevor das Menü zu dem Chart hinzugefügt wird. Falls es sich um einen Radio-Element handelt, muss die Gruppe, zu welchem es gehört, spezifiziert werden. Mit anderen Worten, wir benötigen eine Methode, die es uns erlaubt, den Bezeichner von jedem Radio Element bestimmen zu können. Die Standard Bezeichner der Radio-Elemente ist gleich Null. Falls wir diese so lassen, dann besitzt ein Kontext-Menü nur eine Gruppe von Radio Elementen, egal wie viele wir hier hinzufügen. Es wird es Situationen geben, wo ist notwendig ist, den Bezeichner eines Radio Elementes und welches dieser Elemente aktuell hervorgehoben ist, herausfinden zu können. Es muss zudem eine Möglichkeit geben die Radio Elemente zu schalten.
Dann benötigen wir noch Methoden für das Arbeiten mit Checkboxen. Verlauf und herauszufinden, in welchem Status sich eine Checkbox befindet und ihren Status zu ändern. Die Deklarationen implementation dieser Methoden wird nachfolgend dargestellt
class CContextMenu : public CElement { public: //--- (1) Abfragen und (2) setzen des Status der Checkbox bool CheckBoxStateByIndex(const int index); void CheckBoxStateByIndex(const int index,const bool state); //--- (1) Liefert und (2) Setzt die ID des Radio Elementes über den Index int RadioItemIdByIndex(const int index); void RadioItemIdByIndex(const int item_index,const int radio_id); //--- (1) Liefert das selektierte Radio Element, (2) Schaltet das Radio Element int SelectedRadioItem(const int radio_id); void SelectedRadioItem(const int radio_index,const int radio_id); //--- }; //+-------------------------------------------------------------------+ //| Liefert den Status der Checkbox über den Index | //+-------------------------------------------------------------------+ bool CContextMenu::CheckBoxStateByIndex(const int index) { int array_size=::ArraySize(m_items); //--- Falls es kein Element in dem Kontextmenü gibt, dann benachrichtigen if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has at least one item!"); } //--- Korrekturen, falls die Größe überschritten wird int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index; //--- Liefert den Status des Elementes return(m_items[i].CheckBoxState()); } //+------------------------------------------------------------------+ //| Setzt den Status der Checkbox über den Index | //+------------------------------------------------------------------+ void CContextMenu::CheckBoxStateByIndex(const int index,const bool state) { //--- Überprüfung auf Überschreitung der Grenzen int size=::ArraySize(m_items); if(size<1 || index<0 || index>=size) return; //--- Setzen des Status m_items[index].CheckBoxState(state); } //+------------------------------------------------------------------+ //| Liefert das Radio Element über den Index | //+------------------------------------------------------------------+ int CContextMenu::RadioItemIdByIndex(const int index) { int array_size=::ArraySize(m_items); //--- Falls es kein Element in dem Kontextmenü gibt, dann benachrichtigen if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if the context menu has at least one item!"); } //--- Korrekturen, falls die Größe überschritten wird int i=(index>=array_size)? array_size-1 :(index<0)? 0 : index; //--- Liefert den Bezeichner return(m_items[i].RadioButtonID()); } //+------------------------------------------------------------------+ //| Setz die ID des Radio Elementes über den Index | //+------------------------------------------------------------------+ void CContextMenu::RadioItemIdByIndex(const int index,const int id) { //--- Überprüfung auf Überschreitung der Grenzen int array_size=::ArraySize(m_items); if(array_size<1 || index<0 || index>=array_size) return; //--- Setzt den Bezeichner m_items[index].RadioButtonID(id); } //+------------------------------------------------------------------+ //| Liefert den Index des Radio Elementes über die ID | //+------------------------------------------------------------------+ int CContextMenu::SelectedRadioItem(const int radio_id) { //--- Zähler für das Radio Element int count_radio_id=0; //--- Durchlaufen der Liste der Kontextmenü-Elemente int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Zum Nächsten wechseln, falls dieses kein Radio Element ist if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON) continue; //--- Wenn die Bezeichner übereinstimmen if(m_items[i].RadioButtonID()==radio_id) { //--- Falls es sich um ein aktiviertes Radio Element handelt, dann Loop verlassen if(m_items[i].RadioButtonState()) break; //--- Erhöhen des Zählers für die Radio Elemente count_radio_id++; } } //---Index zurückgeben return(count_radio_id); } //+------------------------------------------------------------------+ //| Schaltet das Radio Element über den Index und die ID | //+------------------------------------------------------------------+ void CContextMenu::SelectedRadioItem(const int radio_index,const int radio_id) { //--- Zähler für das Radio Element int count_radio_id=0; //--- Durchlaufen der Liste der Kontextmenü-Elemente int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Zum Nächsten wechseln, falls dieses kein Radio Element ist if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON) continue; //--- Wenn die Bezeichner übereinstimmen if(m_items[i].RadioButtonID()==radio_id) { //--- Schaltet das Radio Element if(count_radio_id==radio_index) m_items[i].RadioButtonState(true); else m_items[i].RadioButtonState(false); //--- Erhöhen des Zählers für die Radio Elemente count_radio_id++; } } }
Nun ist alles dafür vorbereitet, um die Methoden für das Hinzufügen eines Kontextmenüs zu dem Chart zu erzeugen. Das Hinzufügen durchläuft drei Stufen:
- Erzeugen des Hintergrundes
- Erzeugen der Menüpunkt
- Erzeugen der Trennlinien.
Jede Stufe benötigt eine private Methode. Später werden wir in einer gemeinsamen public Methode aufgerufen. Wir deklarieren wie in dem Körper der Klasse:
class CContextMenu : public CElement { public: //--- Methoden für das Erzeugen eines Kontextmenüs bool CreateContextMenu(const long chart_id,const int window,const int x=0,const int y=0); //--- private: bool CreateArea(void); bool CreateItems(void); bool CreateSeparateLine(const int line_number,const int x,const int y); //--- };
Die Höhe des Hintergrundes hängt von der Anzahl der Elemente und der Trennlinien ab. Das ist der Grund, warum es keine Einstellungsmöglichkeit dieses Wertes in der Klasse der Anwendung gibt, da dieser Wert in der CContextMenu::CreateArea() Methode, die für das Festlegen des Hintergrundes entwickelt wurde, ersetzt wird. Die Höhe für die Trennlinien ist 9 pixels und somit muss für die Berechnung der Gesamthöhe die Anzahl der Linien mit diesen Wert multipliziert werden..
//+------------------------------------------------------------------+ //| Erzeugt die gemeinsame Fläche des Kontextmenüs | //+------------------------------------------------------------------+ bool CContextMenu::CreateArea(void) { //--- Den Objektnamen bilden string name=CElement::ProgramName()+"_contextmenu_bg_"+(string)CElement::Id(); //--- Berechnung der Höhe des Kontextmenüs, in Abhängigkeit von der Anzahl der Menüelemente und der Trennlinien int items_total =ItemsTotal(); int sep_y_size =::ArraySize(m_sep_line)*9; m_y_size =(m_item_y_size*items_total+2)+sep_y_size-(items_total-1); //--- Festlegen des Hintergrundes if(!m_area.Create(m_chart_id,name,m_subwin,m_x,m_y,m_x_size,m_y_size)) return(false); //--- Festlegen der Eigenschaften m_area.BackColor(m_area_color); m_area.Color(m_area_border_color); m_area.BorderType(BORDER_FLAT); m_area.Corner(m_corner); m_area.Selectable(false); m_area.Z_Order(m_area_zorder); m_area.Tooltip("\n"); //--- Abstände von Eckpunkt m_area.XGap(m_x-m_wnd.X()); m_area.YGap(m_y-m_wnd.Y()); //--- Размеры фона m_area.XSize(m_x_size); m_area.YSize(m_y_size); //--- Set the object pointer CElement::AddToArray(m_area); return(true); }
Die Trennlinien werden über die Methode CContextMenu::CreateSeparateLine() gesetzt. Die Anzahl der Linien und die Koordinaten werden dieser Methode als Parameter übergeben:
//+------------------------------------------------------------------+ //| Erzeugt eine Trennlinie | //+------------------------------------------------------------------+ bool CContextMenu::CreateSeparateLine(const int line_number,const int x,const int y) { //--- Abspeichern des Pointers der Form m_sep_line[line_number].WindowPointer(m_wnd); //--- Festlegen der Eigenschaften m_sep_line[line_number].TypeSepLine(H_SEP_LINE); m_sep_line[line_number].DarkColor(m_sepline_dark_color); m_sep_line[line_number].LightColor(m_sepline_light_color); //--- Erzeugen einer Trennlinie if(!m_sep_line[line_number].CreateSeparateLine(m_chart_id,m_subwin,line_number,x,y,m_x_size-10,2)) return(false); //--- Set the object pointer CElement::AddToArray(m_sep_line[line_number].Object(0)); return(true); }
Die CContextMenu::CreateSeparateLine() Methode wird in der CContextMenu::CreateItems() Methode aufgerufen. Die Koordinaten und die Reihenfolge für das Festlegen dieser Elemente wird in einer Schleife definiert. Zuvor haben wir schon das m_sep_line_index[] Array betrachtet. Wenn ein Kontextmenü gebildet wird, werden die Indexnummern der Menüpunkte, nach welchen eine Trennlinie erfolgen soll, in diesem Array abgespeichert. Der Vergleich der Anzahl der aktuellen Durchläufe des Loops mit der Indexnummer des Menüpunktes in dem m_sep_line_index[] Array identifiziert, an welcher Stelle eine Trennlinie gesetzt werden muss.
Es ist zudem notwendig, einen Pointer zu den vorherigen Knotenpunkt zu speichern, bevor jedes Element in dem Kontextmenü festgelegt wird. Nachfolkgend wird der Programmcode der CContextMenu::CreateItems() Methode mit detaillierten Kommentaren gezeigt.
//+-----------------------------------------------------------------+ //| Erzeugen einer Liste von Menü-Items | //+-----------------------------------------------------------------+ bool CContextMenu::CreateItems(void) { int s =0; // For identification of the location of separation lines int x =m_x+1; // X coordinate int y =m_y+1; // Y coordinate. Wird in einer Schleife für jeden Menüpunkt berechnet. //--- Anzahl von Abstandslinien int sep_lines_total=::ArraySize(m_sep_line_index); //--- int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Berechnung der Y Koordinate y=(i>0)? y+m_item_y_size-1 : y; //--- Abspeichern des Pointers der Form m_items[i].WindowPointer(m_wnd); //--- Hinzufügen des Pointers zu den vorherigen Knotenpunkt m_items[i].PrevNodePointer(m_prev_node); //--- Festlegen der Eigenschaften m_items[i].XSize(m_x_size-2); m_items[i].YSize(m_item_y_size); m_items[i].IconFileOn(m_path_bmp_on[i]); m_items[i].IconFileOff(m_path_bmp_off[i]); m_items[i].AreaBackColor(m_area_color); m_items[i].AreaBackColorOff(m_item_back_color_hover_off); m_items[i].AreaBorderColor(m_area_color); m_items[i].LabelColor(m_label_color); m_items[i].LabelColorHover(m_label_color_hover); m_items[i].RightArrowFileOn(m_right_arrow_file_on); m_items[i].RightArrowFileOff(m_right_arrow_file_off); m_items[i].IsDropdown(m_is_dropdown); //--- Ränder von den Seiten des Panels m_items[i].XGap(x-m_wnd.X()); m_items[i].YGap(y-m_wnd.Y()); //--- Erzeugung eines Menü-Items if(!m_items[i].CreateMenuItem(m_chart_id,m_subwin,i,m_label_text[i],x,y)) return(false); //--- Mit dem nächsten Fortschreiten, falls alle Trennlinien gesetzt sind if(s>=sep_lines_total) continue; //--- Wenn alle Indizes übereinstimmen, kann eine Trennlinie nach diesem Element gesetzt werden if(i==m_sep_line_index[s]) { //--- Koordinaten int l_x=x+5; y=y+m_item_y_size+2; //--- Festlegen einer Trennlinie if(!CreateSeparateLine(s,l_x,y)) return(false); //--- Die Justierung der y-Koordinate für das nachfolgende Element y=y-m_item_y_size+7; //--- Erhöhen des Zählers für die Trennlinien s++; } } return(true); }
Jetzt muss noch die CContextMenu::CreateContextMenu() Methode für die externe Verwendung implementiert werden. In dieser Stufe, lassen Sie uns eine Option betrachten, wo ein Kontextmenü zu einem externen Menü oder einen unabhängigen Menüelement hinzugefügt werden muss. Das bedeutet, dass bevor wir ein Kontextmenü erzeugen, ein Pointer zu dem vorherigen Knotenpunkt übergeben werden muss, so wie wir es vorher auch schon erwähnt haben. Neben der Überprüfung der Präsenz eines Pointers zu dem Formular, muss auch noch eine Überprüfung der Präsenz eines Pointers zu dem vorherigen Knotenpunkt durchgeführt werden. Für den Anwender der Bibliothek bedeutet dass, einen zusätzlichen Referenzpunkt zu besitzen, um mögliche Fehler bei der Bildung eines grafischen Interfaces zu eliminieren.
Ein Kontextmenü ist normalerweise nach seiner Erzeugung versteckt, da ist natürlich darauf ausgelegt ist über einen Mausklick oder über einen Klick auf den Arbeitsbereich zu öffnen. Die Hide() Methode ist dafür ausgelegt, Objekte in jedem Element zu verstecken. Es gibt eine ähnliche Methode in der CContextMenuKlasse. Zunächst werden die Objekte des Kontextmenüs - der Hintergrund und die Trennlinien von ihr versteckt. Anschließend werden alle Menüpunkte in einer Schleife versteckt.. Zur selben Zeit werden die eigenen CMenuItem::Hide() Methoden für die Menüpunkte aufgerufen. Die Trennlinien können auf eine ähnliche Art und Weise versteckt werden, da dieses Element seine eigeneCSeparateLine::Hide() Methode besitzt. Aber da dieses Element das einzige Element ist, welches nur aus einem grafischen Objekt besteht und nicht für die Interaktion mit dem Anwender entwickelt wurde, wurde es dem gemeinsamen Array von Kontextmenü-Objekten hinzugefügt und wird in einer zugehörigen Schleife versteckt.
//+-----------------------------------------------------------------+ //| Versteckt das Kontextmenü | //+-----------------------------------------------------------------+ void CContextMenu::Hide(void) { //--- Abbrechen, falls das Element versteckt ist if(!CElement::m_is_visible) return; //--- Verstecken der Objekte des Kontextmenüs for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_NO_PERIODS); //--- Verstecken der Menüpunkte int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) m_items[i].Hide(); //--- Den Fokus löschen CElement::MouseFocus(false); //--- Den Status eines versteckten Controls zuweisen CElement::m_is_visible=false; }
Alle Methoden für das Verwalten eines Kontextmenüs sind in einer ähnlichen Art und Weise strukturiert und wir werden sie hier nicht weiter besprechen. Sie finden den dazugehörigen Programmcode in den an diesem Artikel angehängten Dateien. Wir besprechen hier nur den Programmcode der CContextMenu::Delete() Methode für das Entfernen des Elementes. Hier werden neben dem Entfernen aller grafischen Objekte auch alle Arrays, die für die Bildung des Kontextmenüs benutzt wurden, geleert. Wenn wir dieses nicht machen würden, dann würde jedes Mal, wenn ein Symbol- oder Timeframe-Wechsel stattfindet, die Liste der Menüelemente vergrößert werden. Während der Testphase können Sie hiermit experimentieren, indem Sie die entsprechenden Linien auskommentieren.
//+----------------------------------------------------------------+ //| Entfernung | //+----------------------------------------------------------------+ void CContextMenu::Delete(void) { //--- Entfernen der Objekte m_area.Delete(); int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) m_items[i].Delete(); //--- Entfernen der Trennlinien int sep_total=::ArraySize(m_sep_line); for(int i=0; i<sep_total; i++) m_sep_line[i].Delete(); //--- Leeren der Arrays ::ArrayFree(m_items); ::ArrayFree(m_sep_line); ::ArrayFree(m_sep_line_index); ::ArrayFree(m_label_text); ::ArrayFree(m_path_bmp_on); ::ArrayFree(m_path_bmp_off); //--- Leeren des Arrays der Objekte CElement::FreeObjectsArray(); }
Wenn wir nun auf die Methode für das Erzeugen des Kontextmenüs zurückkommen, müssen wir beachten, dass die Koordinaten immer in Relation zu den vorherigen Knotenpunkt gesetzt werden. Lassen Sie uns die Möglichkeit schaffen, dass der Anwender benutzerdefinierte Koordinaten festlegen kann, sobald Sie dieses notwendig wird. Die Standard-Koordinaten werden in der CContextMenu::CreateContextMenu() Methode für die Erzeugung eines Kontextmenüs auf Null gesetzt. Die Koordinaten werden automatisch von dem vorherigen Knotenpunkt aus berechnet, es sei denn, das mindestens eine Koordinate angegeben wurde. Wenn beide Koordinaten angegeben werden, dann wird die automatische Berechnung abgebrochen.
Für Kontextmenüs, welche über andere Kontextmenüs geöffnet werden, werden die Koordinaten automatisch von der rechten Seite des Menüelementes aus berechnet, zu welchem diese angefügt wurden. Bei den Kontextmenüs, die zu einem Menüelement des Hauptmenüs hinzugefügt wurden, werden die Koordinaten über den unteren Teil des Menüelementes berechnet. Für die Verwaltung einen solchen Systems, benötigen wir eine weitere Variable und Methode für die CContextMenu Klasse. Lassen Sie uns noch eine neue Enumeration zu der Enums.mqh Datei hinzufügen:
//+-----------------------------------------------------------------+ //| Enumeration für die Anfügeseite eines Menüs | //+-----------------------------------------------------------------+ enum ENUM_FIX_CONTEXT_MENU { FIX_RIGHT =0, FIX_BOTTOM =1 };
Der Klasse für das Kontextmenü muss noch eine entsprechende Variable und Methode für das Setzen der Koordinaten-Berechnungsmethode hinzugefügt werden. Standardmäßig werden die Koordinaten Von der rechten Seite eines Elementes aus berechnet.
class CContextMenu : public CElement { private: //--- Anfügeseite des Contextmenüs ENUM_FIX_CONTEXT_MENU m_fix_side; //--- public: //--- Festlegen des Kontextmenü Anfüge-Modus void FixSide(const ENUM_FIX_CONTEXT_MENU side) { m_fix_side=side; } }; //+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CContextMenu::CContextMenu(void) : m_fix_side(FIX_RIGHT) { }
Nachfolgend ist der Programmcode für die CContextMenu::CreateContextMenu() Methode. Es kann nur ein Element erzeugt werden, wenn es auch einen Pointer gibt. Die Eigenschaften dieses Knotenpunktes sind nur nach der zuvor besprochenen entsprechenden Überprüfung verfügbar. Dieses gibt uns dann die Möglichkeit die Berechnung der relativen Koordinaten automatisch durchzuführen. Das Verstecken eines Kontextmenüs Muss in den Programmcode nach der Erzeugung erfolgen.
//+-----------------------------------------------------------------+ //| Erzeugt ein Kontextmenü | //+-----------------------------------------------------------------+ bool CContextMenu::CreateContextMenu(const long chart_id,const int subwin,const int x=0,const int y=0) { //--- Verlassen, wenn es keinen Pointer zu einer Form gibt if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a context menu it has to be passed " "a window object using the WindowPointer(CWindow &object)."); return(false); } //--- Verlassen, falls es keinen Pointer zu einem vorherigen Knotenpunkt gibt if(::CheckPointer(m_prev_node)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a context menu it has to be passed " "the pointer to the previous node using the CContextMenu::PrevNodePointer(CMenuItem &object) method."); return(false); } //--- Initialisierung der Variablen m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =subwin; //--- Falls die Koordinaten nicht angegeben wurden if(x==0 || y==0) { m_x =(m_fix_side==FIX_RIGHT)? m_prev_node.X2()-3 : m_prev_node.X()+1; m_y =(m_fix_side==FIX_RIGHT)? m_prev_node.Y()-1 : m_prev_node.Y2()-1; } //--- Wenn Koordinaten angegeben wurden else { m_x =x; m_y =y; } //--- Abstände von Eckpunkt CElement::XGap(m_x-m_wnd.X()); CElement::YGap(m_y-m_wnd.Y()); //--- Erzeugen des Kontextmenüs if(!CreateArea()) return(false); if(!CreateItems()) return(false); //--- Verstecken des Elementes Hide(); return(true); }
Eine Überprüfung über die Anwesenheit eines vorherigen Knotenpunkt-Pointers mit einer Bedingung muss der CMenuItem Klasse der CreateMenuItem() Methode hinzugefügt werden. Das Nichtvorhandensein eines Pointers bedeutet, dass wir ein unabhängiges Menüelement vorliegen haben. Das bedeutet, dass dieses Element kein Teil eines Kontextmenüs ist. Solche Elemente können entweder Elemente von einem simplen Typ sein (MI_SIMPLE) Oder Elemente, die selbst ein Kontextmenü (MI_HAS_CONTEXT_MENU) besitzen. Dieses mag im Moment vielleicht schwer zu verstehen sein, aber es wird deutlicher, wenn wir uns die Beispiele am Ende des Artikels ansehen.
Platzieren Sie diesen Programmcode in die CMenuItem::CreateMenuItem() Methode nach der Überprüfung der Anwesenheit eines Pointers, wie nachfolgend gezeigt.
//--- Falls es keinen Pointer zu einem vorherigen Knotenpunkt gibt, dann // haben wir einen unabhängigen Menüpunkt vorliegen, welcher kein Teil eines Kontextmenüs ist if(::CheckPointer(m_prev_node)==POINTER_INVALID) { //--- Abbrechen, falls der gesetzte Typ nicht übereinstimmt if(m_type_menu_item!=MI_SIMPLE && m_type_menu_item!=MI_HAS_CONTEXT_MENU) { ::Print(__FUNCTION__," > The type of the independent menu item can be only MI_SIMPLE or MI_HAS_CONTEXT_MENU,", "that is only with a context menu.\n", __FUNCTION__," > The type of the menu item can be set using the CMenuItem::TypeMenuItem() method"); return(false); } }
Test das Hinzufügen eines Kontextmenüs
Das Hinzufügen eines Kontextmenüs zum Chart kann nun getestet werden. Beziehen Sie die ContextMenu.mqh Datei mit in der CContextMenu Klasse für die Menüs mit ein.
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Window.mqh" #include "MenuItem.mqh" #include "ContextMenu.mqh" #include "SeparateLine.mqh"
Erzeugen sie eine Instanz der CContextMenu Klasse in der benutzerdefinierten Klasse der CProgram Anwendung und deklarieren Sie Methoden für die Erzeugung eines Kontextmenüs. Die Ränder von einem Eckpunkt des Formulars müssen hier nicht angegeben werden, da sie in Relation zu dem Menüelement, zu welchem sie hinzugefügt werden, berechnet werden.
class CProgram : public CWndEvents { private: //--- Menüelement und Kontextmenü CMenuItem m_menu_item1; CContextMenu m_mi1_contextmenu1; //--- private: #define MENU_ITEM1_GAP_X (6) #define MENU_ITEM1_GAP_Y (25) bool CreateMenuItem1(const string item_text); bool CreateMI1ContextMenu1(void); };
Lassen Sie uns nun ein Kontextmenü bilden. Es besitzt fünf Elemente: Drei Einfache(MI_SIMPLE) Und zwei vom Typ Checkbox (MI_CHECKBOX). Beziehen Sie die Ressourcen für die Icons der Labels für einfache Elemente außerhalb der Methode mit ein. Das Icon wird für ein verfügbares Element farbig dargestellt und für ein nicht verfügbares / gesperrtes Element ohne Farbe. Sie können diese Elemente am Ende des Artikels herunterladen. Anschließend speichern Sie die Pointer des Formulars und des vorherigen Knotenpunktes am Anfang der Methode in dem Kontextmenü ab. Wenn wir dieses nicht durchführen, dann wird das grafische Interface nicht erzeugt und das Programm wird von dem Chart entfernt. Anschließend folgen Sie den Pfeilen mit (1) der Beschreibung (angezeigter Text), mit den Icons von (2) den verfügbaren und (3) den gesperrten Status (4) und Element-Typen. Anschließend müssen wir die gemeinsamen Eigenschaften aller Elemente angeben und diese der Klasse des Kontextmenüs über eine Schleife unter Verwendung der CContextMenu::AddItem() Methode hinzufügen . Wir sind eine Trennlinie nach dem zweiten Element ein (Index 1). Nachdem wir alle diese Schritte vollzogen haben, können wir das Kontext-Menü dem Chart hinzufügen. Am Ende der Methode fügen wir den Pointer der Basis hinzu. Nachfolgend ist der Code dieser Methode aufgelistet:
//+-----------------------------------------------------------------+ //| Erzeugt ein Kontextmenü | //+-----------------------------------------------------------------+ #resource "\\Images\\Controls\\coins.bmp" #resource "\\Images\\Controls\\coins_colorless.bmp" #resource "\\Images\\Controls\\line_chart.bmp" #resource "\\Images\\Controls\\line_chart_colorless.bmp" #resource "\\Images\\Controls\\safe.bmp" #resource "\\Images\\Controls\\safe_colorless.bmp" //--- bool CProgram::CreateMI1ContextMenu1(void) { //--- Fünf Elemente in dem Kontextmenü #define CONTEXTMENU_ITEMS1 5 //--- Abspeichern des Fenster-Pointers m_mi1_contextmenu1.WindowPointer(m_window); //--- Den Pointer des vorherigen Knotenpunktes speichern m_mi1_contextmenu1.PrevNodePointer(m_menu_item1); //--- Array Mit den Namen der Elemente string items_text[CONTEXTMENU_ITEMS1]= { "ContextMenu 1 Item 1", "ContextMenu 1 Item 2", "ContextMenu 1 Item 3", "ContextMenu 1 Item 4", "ContextMenu 1 Item 5" }; //--- Label Array Für die verfügbaren Modi string items_bmp_on[CONTEXTMENU_ITEMS1]= { "Images\\Controls\\coins.bmp", "Images\\Controls\\line_chart.bmp", "Images\\Controls\\safe.bmp", "","" }; //--- Label Array Für den blockierten Modus string items_bmp_off[CONTEXTMENU_ITEMS1]= { "Images\\Controls\\coins_colorless.bmp", "Images\\Controls\\line_chart_colorless.bmp", "Images\\Controls\\safe_colorless.bmp", "","" }; //--- Array mit Elementtypen ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS1]= { MI_SIMPLE, MI_SIMPLE, MI_SIMPLE, MI_CHECKBOX, MI_CHECKBOX }; //--- Festlegen der Eigenschaften bevor er erzeugt wird m_mi1_contextmenu1.XSize(160); m_mi1_contextmenu1.ItemYSize(24); m_mi1_contextmenu1.AreaBackColor(C'240,240,240'); m_mi1_contextmenu1.AreaBorderColor(clrSilver); m_mi1_contextmenu1.ItemBackColorHover(C'240,240,240'); m_mi1_contextmenu1.ItemBackColorHoverOff(clrLightGray); m_mi1_contextmenu1.ItemBorderColor(C'240,240,240'); m_mi1_contextmenu1.LabelColor(clrBlack); m_mi1_contextmenu1.LabelColorHover(clrWhite); m_mi1_contextmenu1.RightArrowFileOff("Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp"); m_mi1_contextmenu1.SeparateLineDarkColor(C'160,160,160'); m_mi1_contextmenu1.SeparateLineLightColor(clrWhite); //--- Hinzufügen von Menüpunkten zu dem Kontextmenü for(int i=0; i<CONTEXTMENU_ITEMS1; i++) m_mi1_contextmenu1.AddItem(items_text[i],items_bmp_on[i],items_bmp_off[i],items_type[i]); //--- Trennlinie nach dem zweiten Element m_mi1_contextmenu1.AddSeparateLine(1); //--- Erzeugen des Kontextmenüs if(!m_mi1_contextmenu1.CreateContextMenu(m_chart_id,m_subwin)) return(false); //--- Hinzufügen des Pointers von dem Control zu der Basisklasse CWndContainer::AddToElementsArray(0,m_mi1_contextmenu1); return(true); }
Jetzt fügen wir einen Aufruf der Methode für das Erzeugen eines Kontextmenüs der Hauptmethode für das Erzeugen des grafischen Interfaces hinzu. Obwohl das Kontextmenü bei der Einrichtung versteckt ist, wird es in diesem Test angezeigt. Der nachfolgende Code zeigt, welche Zeilen in der CProgram::CreateTradePanel() Methode hinzugefügt werden müssen.
//+-----------------------------------------------------------------+ //| Erzeugt ein Trading-Panel | //+-----------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- Erzeugung einer Form für Controls if(!CreateWindow("EXPERT PANEL")) return(false); //---Erzeugung der Controls: // Menüelement if(!CreateMenuItem1("Menu item")) return(false); if(!CreateMI1ContextMenu1()) return(false); //--- Trennlinie if(!CreateSepLine()) return(false); //--- Anzeigen des Kontextmenüs m_mi1_contextmenu1.Show(); //--- Neuzeichnen auf dem Chart m_chart.Redraw(); return(true); }
Kompilieren Sie die Projektdateien und laden Sie das Programm auf einem Chart. Das Ergebnis sollte so aussehen, wie es in dem nachfolgenden Screenshot gezeigt wird.
Abbildung 4. Test eines Kontextmenüs.
Zum augenblicklichen Zeitpunkt verändern die Element ihre Farbe nicht, wenn der Mauszeiger über sie bewegt wird. Diese Funktionalität kann in der CContextMenu Klasse des Kontextmenüs erzeugt werden oder Sie können eine fertige Klasse aus der CMenuItem Klasse verwenden. Nachdem das Kontextmenü zu dem Chart hinzugefügt wurde, wird sein Pointer zur Basis hinzugefügt. Aber die Pointer zu jedem Menü-Element bleiben für die Verwendung in der CWndEvents Klass für die Eventverarbeitung zum aktuellen Zeitpunkt nicht zugänglich. Für jedes komplexe (compound) Control, Welches aus mehreren Elementen besteht, erzeugen wir in der CWndContainer Klasse eine Methode, über welche die Pointer zu diesen Elementen abgerufen werden können. Dafür implementieren wir die ItemPointerByIndex() Methode in der CContextMenu Klasse, mit welcher wir den Pointer für ein Menüelement über seinen Index erhalten können.
Die Weiterentwicklung der Klasse für das Abspeichern von Pointern aller Elemente.
Lassen Sie uns die AddContextMenuElements() Methode in der CWndContainer Klasse für das Arbeiten mit dem Kontextmenü-Element implementieren. Der Formular-Index und das Element-Objekt müssen als Parameter übergeben werden. Am Anfang der Methode wird eine Überprüfung vorgenommen, ob es sich bei dem übergebenen Element auch um ein Kontext-Menü handelt. Falls es zutrifft, dann wird ein Pointer zu dem Kontextmenü (CContextMenu) Benötigt, um einen Zugriff auf seine Methoden zu bekommen. Wie können wir dieses realisieren, falls das Objekt, welches übergeben worden ist, zu der Basisklasse gehört (CElement)? Hierfür ist es ausreichend einen Pointer vom Typ CContextMenu zu deklarieren Und leasen Pointer dem übergebenen Objekt zuzuweisen. In den nachfolgenden Programmcode würde dieses in Gelb hervorgehoben. Über diesen Weg haben wir einen Zugriff auf die Elemente des Kontextmenüs. Anschließend werden sie in einer Schleife dem gemeinsamen Array von Elementen des Formulars hinzugefügt. Am Ende jeden Durchlaufs werden die Menüelemente der CWndContainer::AddToObjectsArray() Methode für das Speichern der Pointer zu den Objekten in dem Objekt-Array vom Typ CChartObject übergeben.
//+-----------------------------------------------------------------+ //| Klasse für das Abspeichern der Interface-Objekte | //+-----------------------------------------------------------------+ class CWndContainer { protected: //--- Speichert die Pointer der Kontextmenü-Elemente in der Basis bool AddContextMenuElements(const int window_index,CElement &object); }; //+-----------------------------------------------------------------+ //| Speichert die Pointer der Kontextmenü-Elemente in der Basis | //+-----------------------------------------------------------------+ bool CWndContainer::AddContextMenuElements(const int window_index,CElement &object) { //--- Verlassen, falls es sich nicht um ein Kontext-Menü handelt if(object.ClassName()!="CContextMenu") return(false); //--- Abfrage der Kontextmenü Pointer CContextMenu *cm=::GetPointer(object); //--- Abspeichern der Pointer zu den Objekten in der Basis int items_total=cm.ItemsTotal(); for(int i=0; i<items_total; i++) { //--- Vergrößern des Elementen Arrays int size=::ArraySize(m_wnd[window_index].m_elements); ::ArrayResize(m_wnd[window_index].m_elements,size+1); //--- Abfrage der Menü-Elemente Pointer CMenuItem *mi=cm.ItemPointerByIndex(i); //--- Abspeichern des pointers in dem Array m_wnd[window_index].m_elements[size]=mi; //--- Hinzufügen aller Pointer von allen Objekten eines Menüelementes zu dem gemeinsamen Array AddToObjectsArray(window_index,mi); } //--- return(true); }
Dieses wird in der CWndContainer::AddToElementsArray() Methode, direkt nach dem Erhöhen des Zählers für die Elemente, aufgerufen. Um in diesen Artikel ein wenig Platz zu sparen, zeigen wir hier nur die verkürzte Version des Programmcodes: Sie finden die vollständigen Versionen in den Dateien die diesen Artikel beigefügt sind.
//+-----------------------------------------------------------------+ //| Fügt einen Pointer zu dem Array der Elemente hinzu | //+-----------------------------------------------------------------+ void CWndContainer::AddToElementsArray(const int window_index,CElement &object) { //--- Falls die Basis keine Formulare für Controls enthält //--- Falls es eine Anfrage für eine nicht existierende Form gibt //--- Hinzufügen zu dem gemeinsamen Array von Elementen //--- Hinzufügen von Element-Objekten zu dem gemeinsamen Array von Objekten //--- Abspeichern der ID von dem letzten Element in allen Forms //--- Erhöhen des Zählers für die Elemente //--- Abspeichern der Pointer zu den Kontextmenü Objekten in der Basis if(AddContextMenuElements(window_index,object)) return; }
Der Code der CWndContainer::AddToElementsArray() Methode wird auf die gleiche Weise ergänzt, wie wir es auch mit ähnlichen Methoden für komplexe (compound) Elemente gemacht haben.
Wenn wir den Programmcode kompilieren und das Programm auf den Chart Laden, dann verändern die Elemente des Kontextmenüs ihre Farbe, sobald sich die Maus über diesen Elementen befindet.
Abbildung 5. Test der Kontextmenü-Elemente.
Die Entwicklung der Klasse für die Erzeugung eines Kontextmenüs ist abgeschlossen. Der nächste Schritt ist die Einrichtung der Eventhandler und die Eventhandler der Menüelemente. Dieses werden wir in dem nächsten Artikel besprechen.
Schlussfolgerung
Unsere Bibliothek beinhaltet bereits drei Klassen für die Erzeugung solcher Elemente, wie:
- Ein Menüpunkt;
- Eine Trennlinie;
- Ein Kontextmenü.
In dem nächsten Artikel werden wir Eventhandler für die Bibliothek in der Hauptklasse einrichten und in den Klassen für die Controls, die wir zuvor erzeugt haben.
Die unten aufgelisteten Archive enthalten die Dateien der Bibliothek zu dem aktuellen Stand der Entwicklung, sowie Bilder und Dateien der besprochenen Programme (Der EA, die Indicatoren und das Skript). Sie können für Tests in dem MetaTrader 4 und MetaTrader 5 Terminal heruntergeladen werden. Wenn Sie fragen zur Verwendung dieses Materials haben, dann können Sie zunächst auf die detaillierte Beschreibung in dem Artikel zu dieser Bibliothek zurückgreifen oder Sie stellen Ihre Frage(n) in den Kommentaren zu diesem Artikel.
Liste der Artikeln (Kapitel) des zweiten Teils:
- Grafische Interfaces II:Das Menu-Item-Element(Kapitel 1)
- Grafische Interfaces II: Die Trennlinien und Context-Menüelemente (Kapitel 2)
- Grafische Interfaces II: Einrichtung des Eventhandlers für die Bibliothek (Kapitel 3)
- Grafische Interfaces II: Das Hauptmenü Element (Kapitel 4)
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2202





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