English Русский 中文 Español 日本語 Português
Grafische Interfaces II: Die Trennlinien und Context-Menüelemente (Kapitel 2)

Grafische Interfaces II: Die Trennlinien und Context-Menüelemente (Kapitel 2)

MetaTrader 5Beispiele | 29 Juni 2016, 08:40
656 0
Anatoli Kazharski
Anatoli Kazharski

Inhalt  

 

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. Die Warnung des Compilers, dass es eine Variable mit gleichem Namen schon gibt.

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.

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

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.

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.

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:

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2202

Beigefügte Dateien |
Grafische Interfaces II: Einrichtung des Eventhandlers für die Bibliothek (Kapitel 3) Grafische Interfaces II: Einrichtung des Eventhandlers für die Bibliothek (Kapitel 3)
Der vorherige Artikel beinhaltet die Implementation der Klassen für das Erzeugen der Bestandteile des Hauptmenüs. Nun ist es an der Zeit, dass wir uns die Eventhandler in den Basisklassen und in den Klassen für die Controls näher betrachten. Wir werden unsere Aufmerksamkeit auch auf das Verwalten des Status des Charts, in Abhängigkeit der Position des Mauszeigers, richten.
Universeller Expert Advisor: Ein benutzerdefiniertes Trailing Stop (Part 6) Universeller Expert Advisor: Ein benutzerdefiniertes Trailing Stop (Part 6)
Der sechste Teil des Artikels über den universellen Expert Advisor beschreibt die Verwendung eines Trailingstops. Dieser Artikel führt sie durch die Erstellung eines Trailingstop-Moduls, welches einheitliche Regeln verwendet, sowie durch den Vorgang, wie dieses Modul der Trading Engine hinzugefügt wird, damit es automatisch Positionen verwaltet.
Grafische Interfaces II: Das Hauptmenü Element (Kapitel 4) Grafische Interfaces II: Das Hauptmenü Element (Kapitel 4)
Dieses ist das letzte Kapitel des zweiten Teils der Serie über die grafischen Interfaces. Hier betrachten wir die Erzeugung des Hauptmenüs. Hier demonstrieren wir die Entwicklung dieses Controls und das Einrichten der Eventhandler innerhalb der Bibliotheks-Klasse, damit später die Anwendung korrekt auf die Aktionen des Users reagiert. Wir besprechen hier zudem, wie man Kontextmenüs dem Hauptmenü hinzufügt. Zudem werden wir noch besprechen, wie man inaktive Elemente blockiert.
Grafische Interfaces II: Das Menu-Item-Element (Kapitel 1) Grafische Interfaces II: Das Menu-Item-Element (Kapitel 1)
In dem zweiten Teil dieser Serie, werden wir im Detail auf die Entwicklung von Interface Elementen, wie einen Hauptmenü und Kontextmenüs eingehen. Wir werden auch das Zeichnen und das Entwickeln einer speziellen Klasse für diese Elemente berücksichtigen. Wir werden ausführlich den Fragen der Verwaltung von Events und benutzerdefinierten Events nachgehen.