Grafische Interfaces I: Testen der Bibliothek in unterschiedlichen Programmen und in dem MetaTrader 4 Terminal (Kapitel 5)

Anatoli Kazharski | 31 Mai, 2016

Inhalt

 

Einleitung

Dieser Artikel ist die Fortsetzung des ersten Teils dieser Serie über grafische Interfaces. 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 des ersten Teils 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 des ersten Teils der Serie über grafische Interfaces, haben wir die Formularklasse mit Methoden für das Verwalten der Form über das Anklicken von Controls ergänzt. Die Tests wurden bisher nur mit einem "Expert Advisor" und nur in dem MetaTrader 5 Terminal durchgeführt. In diesem Teil wollen wir unsere Arbeit in verschiedenen Typen von MQL Programmen, wie zum Beispiel Indikatoren und Skripten, testen. Da unsere Bibliothek darauf ausgelegt ist, plattformübergreifend zu funktionieren, werden wir Sie in allen Metatrader-Plattformen testen, so wie zum Beispiel im MetaTrader 4.

 

Verwendung des Formulars in Indikatoren

Erzeugen Sie ein Verzeichnis für einen neuen Indikator in dem MetaTrader 5 Terminal-Verzeichnis für Indikatoren <data_directory>\MQL5\Indicators). Wie schon in den vorherigen Test mit dem EA, erzeugen Sie in diesem Verzeichnis die Hauptprogramm-Datei und die Program.mqh Datei, welche die CProgram Klasse enthält. Sie brauchen nur die Program.mqh Datei aus dem vorherigen Verzeichnis des EAs in das Verzeichnis des Indikators zu kopieren. In dieser Phase reicht dieser Code aus, um ihn mit einem Indikator zu testen. Fügen Sie in der Hauptdatei des Indikators den nachfolgend gezeigten Programmcode ein.

Die in gelb hervorgehobene Programmzeile bedeutet, dass dieser Indikator in dem Haupt-Chartfenster erscheint. Die Anzahl der Indikator-Speicher kann auf Null gesetzt werden, da wir im Moment nur die Elemente des grafischen Interfaces innerhalb eines Indikators testen wollen.

//+------------------------------------------------------------------+
//|                                                  ChartWindow.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0
//--- Einbeziehen der Trading Panel Klasse
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Benutzerdefinierte Indikator-Initialisierungsfunktion            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   program.OnInitEvent();  
//--- Einrichten des Trading Panels
   if(!program.CreateTradePanel())
     {
      ::Print(__FUNCTION__," > Failed to create graphical interface!");
      return(INIT_FAILED);
     }
//--- Initialisierung erfolgreich
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   program.OnDeinitEvent(reason);
  }
//+------------------------------------------------------------------+
//| Benutzerdefinierte Indikator- Iterationsfunktion                 |
//+------------------------------------------------------------------+
int OnCalculate (const int    rates_total,     // Größe des price[] Arrays
                 const int    prev_calculated, // bearbeitete Balken beim vorherigen Aufruf
                 const int    begin,           // wo wichtige Daten beginnen
                 const double &price[])        // Array für Berechnung
  {
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
   program.OnTimerEvent();
  }
//+------------------------------------------------------------------+
//| ChartEvent Funktion                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   program.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Kompilieren Sie diesen Indikator und laden Sie ihn auf einen Chart. Das Ergebnis sollte so aussehen, wie es in dem nachfolgenden Screenshot gezeigt wird. So wie es auch mit den EA möglich war, kann das Formular minimiert, maximiert und über den Chart bewegt werden. Die Steuerelemente reagieren auf die Bewegungen des Mauszeigers und wenn der Close-Button angeklickt wird (Das Kreuz in der oberen rechten Ecke), dann wird der Indikator von dem Chart entfernt.

Abbildung  1. Test des Formulars als Indikator in dem Haupt-Chartfenster

Abbildung 1. Test des Formulars als Indikator in dem Haupt-Chartfenster

Bitte beachten Sie auch das Formular-Icon in der oberen linken Ecke. Das Programm hat vollkommen automatisch den Typ des Programms identifiziert und das entsprechende Icon gesetzt. Wie Sie ja wissen, können Sie schon in dieser Phase der Entwicklung der CWindow Klasse dieses Icon ändern.

Unser Formular (Form) mit Steuerelementen (Controls) arbeitet als Indikator ohne größere Änderungen an den Programmcode. Aber wenn wir diesen Indikator mit dem selben Formular in einem anderen Fenster als dem Haupt-Chartfenster erzeugen, dann werden nicht alle Eigenschaften so arbeiten wie wir es erwarten. (1) Das Formular wird im Haupt-Chartfenster erscheinen und nicht in dem gewünschten Unterfenster, (2) und das Anklicken des Close-Buttons wird den Indikator nicht von dem Chart entfernen. Warum ist das so? Wir werden uns nun ansehen, wie wir das ändern können.

Eigentlich brauchen wir von dem, was wir bisher getan haben, nichts zu ändern. Alles was wir tun müssen ist, in der CWndEvents Klass eine Methode zu erzeugen, die automatisch die Fensternummer identifiziert in Abhängigkeit von dem Typ und der Nummer des Indikators, welcher sich in dem Unterfenster des Charts befindet. Lassen Sie uns diese Methode DetermineSubwindow() nennen. Am Anfang der Methode muss eine Überprüfung stattfinden, ob dieses Programm ein Indikator ist. Wenn es sich nicht um einen Indikator handelt, dann können wir den Vorgang abbrechen, da nur Indikatoren in den Unterfenstern erscheinen dürfen.

Anschließend fragen wir die Nummer des Indikatorfensters mit der ChartWindowFind() Funktion ab. Falls dieses nicht erfolgreich ist und die Funktion -1 zurückgibt, dann wird eine entsprechende Nachricht in dem Journal ausgegeben und die Methode wird beendet. Wenn die zurückgegebene Nummer größer als 0 ist, dann bedeutet das, dass es sich um ein anderes Fenster als das Haupt-Chartfenster handelt. Es ist noch eine weiterer Überprüfung notwendig. Wir müssen überprüfen, ob sich noch andere Indikatoren in dem Unterfenster befinden. Wenn sich andere Indikatoren in diesen Unterfenster befinden, dann wird unser Indikator vom Chart entfernt und es wird eine entsprechende Nachricht darüber in dem Journal ausgegeben. Dafür müssen wir unter Verwendung der Funktion ChartIndicatorsTotal() die gesamte Anzahl an Indikatoren in dem angegebenen Unterfenster herausfinden. Anschließend fragen wir den Kurznamen des letzten Indikators in der Liste ab und wenn die Anzahl der Indikatoren nicht 1 ist, dann entfernen wir das Programm von dem Chart. Die Abfrage des Kurznamens ist erforderlich, da ein Indikator nur von einem Chart gelöscht werden kann, wenn sein Kurzname angegeben wird.

Fügen Sie die Deklaration und Implementation der DetermineSubwindow() Methode in der CWndEvents Klasse hinzu, wie nachfolgend gezeigt:

class CWndEvents : public CWndContainer
  {
private:
   //--- Identifizierung der Nummer des Unterfensters
   void              DetermineSubwindow(void);
  };
//+-----------------------------------------------------------------+
//| Identifizierung der Nummer des Unterfensters                    |
//+-----------------------------------------------------------------+
void CWndEvents::DetermineSubwindow(void)
  {
//--- Falls es sich nicht um einen Indikator handelt, dann abbrechen
   if(PROGRAM_TYPE!=PROGRAM_INDICATOR)
      return;
//--- Zurücksetzen der letzten Fehlermeldung
   ::ResetLastError();
//--- Identifizierung der Nummer des Indikator-Fensters
   m_subwin=::ChartWindowFind();
//--- Falls die Identifikation der Nummer fehlschlägt, dann Abbruch
   if(m_subwin<0)
     {
      ::Print(__FUNCTION__," > Error when identifying the sub-window number: ",::GetLastError());
      return;
     }
//--- Falls es sich nicht um das Haupt-Chartfenster handelt
   if(m_subwin>0)
     {
      //--- Frage die gesamte Anzahl der Indikatoren in dem angegebenen Unterfenster ab
      int total=::ChartIndicatorsTotal(m_chart_id,m_subwin);
      //--- Abfrage des Kurznamens des letzten Indikators in der Liste
      string indicator_name=::ChartIndicatorName(m_chart_id,m_subwin,total-1);
      //--- Falls das Unterfenster schon einen Indikator besitzt, dann entferne das Programm von dem Chart
      if(total!=1)
        {
         ::Print(__FUNCTION__," > This sub-window already contains an indicator.");
         ::ChartIndicatorDelete(m_chart_id,m_subwin,indicator_name);
         return;
        }
     }
  }

Diese Methode muss in dem Konstruktor der CWndEvents Klasse aufgerufen werden:

//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void)
  {
//--- Identifizierung der Nummer des Unterfensters
   DetermineSubwindow();
  }

Nun haben wir die Bibliothek für Indikatoren vorbereitet, die sich nicht in dem Haupt-Chatfenster befinden. Wir werden nun prüfen ob dieses jetzt funktioniert und korrigieren eventuelle Fehler und Mängel.

Die Bibliothek hat nun drei Hauptmodi für die grafischen Interfaces, welche sich in einem Unterfenster befinden:

  1. Freier Modus (Free mode). In diesem Modus ist die Höhe des Unterfensters nicht fixiert und kann ohne Einschränkungen von dem Anwender verändert werden. Wenn die Form mit den Controls minimiert wird, wird die Höhe des Indikator-Unterfensters nicht verändert und kann weiterhin modifiziert werden.
  2. Fixierter Modus (Fixed mode). Die Höhe des Indikator-Unterfensters kann mit der Höhe des Formulars fixiert werden. Wenn die Form minimiert wird, dann bleibt aber die Höhe des Unterfensters ebenfalls gleich.
  3. Fixierter Modus mit der Möglichkeit das Unterfenster zu minimieren. Das bedeutet, dass wenn der Indikator auf den Chart geladen wird, das Unterfenster in der Größe des Formulars angepasst wird, aber wenn das Unterfenster minimiert wird, erhält es die Höhe des Kopfes unseres Formulars.

Um die oben genannten Modi einfach einstellen zu können, haben wir die RollUpSubwindowMode() Method schon in der CWindow Klasse vorliegen. Wie dieses angewendet wird, wird später gezeigt.

Lassen Sie uns nun drei Indikatoren erzeugen, um alle Modi darstellen zu können. Um das Ganze ein wenig interessanter zu gestalten, wird eine Enumeration (eine drop-down Liste) eine Rolle in den externen Parametern dieser Indikatoren spielen. Diese Drop-down-liste gibt uns eine Auswahlmöglichkeit für drei verschiedene Optionen für das Einrichten der Form:

  1. Auf der linken Seite des Chartfensters;
  2. Auf der rechten Seite des Chartfensters;
  3. Über die gesamte Breite des Charts.

Falls die dritte Option gewählt wird, dann muss die Breite des Formulars angepasst werden, sobald sich die Breite des Charts ändert. Die CWindow Klass besitzt zur Zeit noch keine Methode, die uns dieses erlaubt. Lassen Sie uns eine solche Methode erstellen und wir nennen sie ChangeWindowWidth(). Am Anfang der Methode, wird die aktuelle Breite mit der Breite, die ihr übergeben wurde, verglichen. Falls es einen Unterschied gibt, dann muss die Breite der Form verändert werden und die Koordinaten der Buttons müssen aktualisiert werden.

Deklaration und Implementation der CWindow::ChangeWindowWidth() Methode:

class CWindow : public CElement
  {
public:
   //--- Verändert die Breite des Fensters
   void              ChangeWindowWidth(const int width);
  };
//+--------------------------------------------------------------------+
//| Verändert die Breite des Fensters                                  |
//+--------------------------------------------------------------------+
void CWindow::ChangeWindowWidth(const int width)
  {
//--- Falls sich an der Breite nichts geändert hat, dann abbrechen
   if(width==m_bg.XSize())
      return;
//--- Aktualisierung der Breite für den Hintergrund und des Kopfes
   CElement::XSize(width);
   m_bg.XSize(width);
   m_bg.X_Size(width);
   m_caption_bg.XSize(width);
   m_caption_bg.X_Size(width);
//--- Aktualisierung der Koordinaten und der Abstände von allen Buttons:
//--- Der Button für das Schließen
   int x=CElement::X2()-CLOSE_BUTTON_OFFSET;
   m_button_close.X(x);
   m_button_close.XGap(x-m_x);
   m_button_close.X_Distance(x);
//--- Der Button für die Maximierung
   x=CElement::X2()-ROLL_BUTTON_OFFSET;
   m_button_unroll.X(x);
   m_button_unroll.XGap(x-m_x);
   m_button_unroll.X_Distance(x);
//--- Der Button für die Minimierung
   m_button_rollup.X(x);
   m_button_rollup.XGap(x-m_x);
   m_button_rollup.X_Distance(x);
//--- Der Tooltip Button (falls aktiviert)
   if(m_tooltips_button)
     {
      x=CElement::X2()-TOOLTIP_BUTTON_OFFSET;
      m_button_tooltip.X(x);
      m_button_tooltip.XGap(x-m_x);
      m_button_tooltip.X_Distance(x);
     }
  }

Erzeugen sie nun drei Kopien von dem Indikator, welchen wir zuvor für den Test in dem Hauptfenster des Charts erzeugt haben. Jede Kopie wird für eine der oben genannten Modi verwendet. Geben Sie jedem einen eindeutigen Namen.

Zum Beispiel:

Eine Programmzeile muss in allen diesen Indikatoren verändert werden. Anstelle der Anweisung, dass dieser Indikator in dem Hauptfenster erscheinen soll, muss angegeben werden, dass der Indikator in einem Unterfenster erscheinen soll:

//+------------------------------------------------------------------+
//|                                                   FreeHeight.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property indicator_separate_window
#property indicator_buffers 0
#property indicator_plots   0

Anschließend fügen Sie am Anfang jeder Program.mqh Datei die Enumeration (enum) und die externen Parameter (input) für das Auswählen des Modus ein:

//--- Enumeration für den Fenster-Modus
enum ENUM_WINDOW_MODE
  {
   LEFT  =0,
   RIGHT =1,
   FULL  =2
  };
//--- Externe Parameter
input ENUM_WINDOW_MODE WindowMode=LEFT;

Falls die RIGHT oder die FULL in den externen Settings gewählt worden ist und sich die Größe des Chart Fensters verändert, dann wird das Programm entweder die Koordinaten der Form (in dem RIGHT Modus) oder seine Größe (in dem FULL Modus) veränder, damit die rechte Ecke nicht die Chartgrenzen überschreitet.

Ich denke es macht keinen Sinn, die Form in einem solchen geschlossenen Raum als Indikator im Unterfenster, beweglich zu machen. Bei der aktuellen Implementation, wird keine Justierung vorgenommen, wenn sich die Chartgröße ändert und in den Eigenschaften angegeben worden ist, dass die Form nicht beweglich ist. Daher muss die Justierung bei nicht bewegbaren Formularen ganz individuell in dem internen Eventhandler des Charts der MQL Anwendung vorgenommen werden. Fügen Sie den nachfolgend gezeigten Code dem CProgram::OnEvent() Eventhandler in allen Program.mqh Dateien der erzeugten Indikatoren ein:

//+-----------------------------------------------------------------+
//| Chart Eventhandler                                              |
//+-----------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Falls der Modus für die gesamte Breite des Charts gewählt wurde
      if(WindowMode==FULL)
         m_window.ChangeWindowWidth(m_chart.WidthInPixels()-2);
      //--- Falls der Modus für die rechte Seite gewählt wurde
      else if(WindowMode==RIGHT)
         m_window.X(m_chart.WidthInPixels()-(m_window.XSize()+1));
     }
  }

Wie Sie zur Zeit sehen können, ist der Inhalt von allen Dateien aller Indikatoren, die wir bisher erzeugt haben, absolut identisch. Zudem haben wir die Antwort auf die Frage, wie die CWindow::RollUpSubwindowMode() Method für das Einstellen des Modus des Indikator-Unterfensters, in welchem das Formular mit den Controls sich befindet, angewendet wird, beantwortet.

Standardmäßig werden die Variablen der Klasse CWindow, welche das Programm für die Identifizierung des Indikator-Unterfenster-Modus verwendet, in dem Klassenkonstruktor für die Verwendung des Modus, wenn die Höhe des Indikator-Unterfenster gewechselt werden kann, initialisiert. Daher kann diese Methode übersprungen werden, wenn der Modus FreeHeight gewählt wurde. Nachfolgend der Programmcode für das Einrichten eines Formulars in dem Unterfenster in der CProgram Klasse. Behalten Sie im Hinterkopf, dass die Breite der Form und ihre Koordinaten in Abhängigkeit der gewählten Option in den externen Parametern definiert wird. Die Stelle, wo die CWindow::RollUpSubwindowMode() Methode in den Indikatoren mit unterschiedlichen Modi verwendet wird, ist in Gelb hervorgehoben.

//+-----------------------------------------------------------------+
//| Erzeugung einer Form für Controls                               |
//+-----------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Hinzufügen eines Pointers des Fensters zu dem Fenster-Array
   CWndContainer::AddWindow(m_window);
//--- Größen
   int x_size=(WindowMode!=FULL)? 205 : m_chart.WidthInPixels()-2;
   int y_size=243;
//--- Koordinaten
   int x=(WindowMode!=RIGHT)? 1 : m_chart.WidthInPixels()-(x_size+1);
   int y=1;
//--- Eigenschaften
   m_window.XSize(x_size);
   m_window.YSize(y_size);
// ...
//--- Erzeugen der Form
   if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   return(true);
  }

Der RollUp Indikator wird in dem Modus verwendet, wenn das Indikator-Unterfenster eine festgelegte Höhe hat und sich nicht ändert, wenn das Formular minimiert wird. Das ist der Grund, warum die nachfolgende Programmzeile an der hervogehobenen Stelle dem Programm hinzugefügt werden muss:

m_window.RollUpSubwindowMode(false,true);

Der Wert des ersten Parameters bedeutet, dass das unter Fenster nicht minimiert werden muss, wenn das Formular minimiert ist. Der zweite Parameter bedeutet, dass das Unterfenster eine festgelegte Höhe haben muss, die gleich der Höhe des Formulars ist. Die Parameter in der CWindow::RollUpSubwindowMode() Methode in dem DownFall Indicator müssen wie folgt angegeben werden:

m_window.RollUpSubwindowMode(true,true);

Der Wert des ersten Parameters legt den Modus fest, wenn das Formular minimiert ist und das Unterfenster die gleiche Höhe wie der Kopf des Formulars haben soll.

Kompilieren Sie alle Dateien, in welchen Veränderungen stattgefunden haben und die Indikator Dateien. Testen Sie jeden von ihnen auf dem Chart. Alles sollte, entsprechend der gewählten Modi, korrekt Laufen. Sie bemerken, dass wenn sich die Größe des Chartfensters ändert, die Koordinaten (in dem RIGHT Modus) und die Breite der Form (in dem FULL Modus) nicht geändert werden. Dieses ist keine Überraschung! Jetzt geht der Strom der Events nicht mehr zu dem OnEvent() Chart Eventhandler in der CProgram Klasse. Als wir in die Hauptstruktur der Bibliothek aufgebaut haben, haben wir beachtet, dass der Strom von Chartevents von der CWndEvents Klasse in die ChartEvent() Method zu dem lokalen OnEvent() handler weitergeleitet wird, welcher sich in der CProgram Klasse befindet. Die Letztere ist die Klasse der Anwendungen, die wir entwickeln.

Lassen Sie uns die Event-Weiterleitung Direkt nach dem Durchlaufen aller Control-Eventhandler in der CWndEvents::CheckElementsEvents() Methode platzieren:

//+-----------------------------------------------------------------+
//| Überprüfen der Control-Events                                   |
//+-----------------------------------------------------------------+
void CWndEvents::CheckElementsEvents(void)
  {
   int elements_total=CWndContainer::ElementsTotal(0);
   for(int e=0; e<elements_total; e++)
      m_wnd[0].m_elements[e].OnEvent(m_id,m_lparam,m_dparam,m_sparam);
//--- Weiterleitung des Events zu der Anwendungsdatei
   OnEvent(m_id,m_lparam,m_dparam,m_sparam);
  }

Die Anpassung der Koordinaten des Formulars in dem RIGHT Modus stimmt zur Zeit noch nicht. Der Grund dafür ist, dass das Programm die Koordinaten nicht aktualisiert, falls der Handler der CWindow Klasse mit dem CHARTEVENT_CHART_CHANGE Event aufgerufen wird, weil in der UpdateWindowXY() Methode die Koordinaten nur aktualisiert werden, wenn die Form eine bewegbare Form ist. Wie Sie sich erinnern, haben wir vorher vereinbart, dass die Form mit den Indikatoren festgelegt wird. Nachdem die Iteration über die Controls durchlaufen wurde, ruf das Programm den Chart Eventhandler in der Klasse der CProgram Anwendung, wie es in dem oben dargestellten Programmcode gezeigt wird, und aktualisiert die X-Koordinate entsprechend der aktuellen Breite des Chart Fensters.

Anschließend verlässt das Programm die CheckElementsEvents() Methode in der CWndEvents Klasse und ruft die CWndEvents::ChartEventMouseMove() Methode auf, die sofort verlassen wird, da es sich nicht um ein Mouse-Event handelt. Zur Zeit wird eine Aktualisierung der Position der Form entsprechend ihren aktuellen Koordinaten in den Form-Eigenschaften durch die CWndEvents::ChartEventMouseMove() Methode durchgeführt. Nun wird es Zeit, das CHARTEVENT_CHART_CHANGE Event in der CWndEvents::ChartEvent() Methode zubehandeln. (Hervorgehoben in Gelb).

//+------------------------------------------------------------------+
//| Programm Eventhandling                                           |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Falls das Array leer ist, Abbruch
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Initialisierung der Felder der Eventparameter
   InitChartEventsParams(id,lparam,dparam,sparam);
//--- Überprüfung der Events der Interface-Controls
   CheckElementsEvents();
//--- Event für die Bewegung des Mauszeigers
   ChartEventMouseMove();
//--- Events bei einer Veränderung der Chart-Eigenschaften
   ChartEventChartChange();
  }

Wir fügen der CWndEvents::ChartEventChartChange() Methode noch einen Aufruf der "windows-move"-Funktionen über die aktuellen Koordinaten hinzu und zeichnen den Chart neu:

//+------------------------------------------------------------------+
//| CHARTEVENT CHART CHANGE event                                    |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventChartChange(void)
  {
//--- Events bei einer Veränderung der Chart-Eigenschaften
   if(m_id!=CHARTEVENT_CHART_CHANGE)
      return;
//--- Verschieben des Fensters
   MovingWindow();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
  }

Kompilieren Sie die Dateien der Bibliothek und des Indikators. Versuchen Sie einen Test indem sie sie auf den Chart laden. Jetzt werden die Koordinaten in dem RIGHT Modus korrekt aktualisiert.

Es gibt noch ein weiteres Detail, welches wir bisher nicht beachtet haben. Bei der aktuellen Implementation, wenn einer der erzeugten Indikatoren zusammen mit dem Formular für Steuerelemente auf den Chart geladen werden, dann wird die Nummer des Unterfensters automatisch durch das Programm in der CWndEvents::DetermineSubwindow() Method in dem Klassen-Konstruktor definiert. Falls einer von diesen Indikatoren auf den Chart geladen wird, welcher bereits einen anderen Indikator, der sich ebenfalls in einem Unterfenster befindet, enthält, dann wird alles einwandfrei funktionieren, solange der Indikator sich auf dem Chart befindet. Wenn dieser entfernt wird, dann erhält das Unterfenster unseres Indikators eine neue Nummer in der Liste der Chartfenster. Da die Bibliothek die Fensternummer benötigt, in welcher sie gerade läuft (Zum Beispiel um die relative Koordinaten zu identifizieren), gibt es hier noch einige Probleme. Aus diesem Grund muss die Anzahl der Fenster in dem Chart nachverfolgt werden. Sobald sich die Anzahl verändert, muss dieser Wert in den Feldern der Klassen, wo sie benötigt werden, deklariert werden.

In dem Moment, wo Programme einem Chart hinzugefügt oder entfernt werden, tritt das CHARTEVENT_CHART_CHANGE Event auf. Auf diesem Grund muss eine Überprüfung in der CWndEvents::ChartEventChartChange() Methode stattfinden. Lassen Sie uns für diese Überprüfung eine neue Methode erzeugen und sie CWndEvents::CheckSubwindowNumber() nennen. Die Deklaration und Implementation wird in den nachfolgenden Code gezeigt. Zunächst wird die Übereinstimmung der Nummer des Programmfensters mit der Nummer, welche abgespeichert wurde während das Programm auf den Chart geladen worden ist, überprüft. Wenn sie nicht übereinstimmen, dann muss die aktuelle Nummer, in welcher das Programm gerade läuft, definiert werden und sie muss anschließend in allen Steuerelementen aktualisiert werden.

class CWndEvents : public CWndContainer
  {
private:
   //--- Verifizierung der Control-Events
   void              CheckSubwindowNumber(void);
   //---
  };
//+-----------------------------------------------------------------+
//| Überprüfen und Aktualisieren der Nummer des Programmfensters    |
//+-----------------------------------------------------------------+
void CWndEvents::CheckSubwindowNumber(void)
  {
//--- Falls die Nummern nicht übereinstimmen
   if(m_subwin!=0 && m_subwin!=::ChartWindowFind())
     {
      //--- Identifizierung der Nummer des Unterfensters
      DetermineSubwindow();
      //--- Abspeichern in allen Steuerelementen
      int windows_total=CWndContainer::WindowsTotal();
      for(int w=0; w<windows_total; w++)
        {
         int elements_total=CWndContainer::ElementsTotal(w);
         for(int e=0; e<elements_total; e++)
            m_wnd[w].m_elements[e].SubwindowNumber(m_subwin);
        }
     }
  }

Der Aufruf dieser Methode muss in der CWndEvents::ChartEventChartChange() Methode durchgeführt werden:

//+------------------------------------------------------------------+
//| CHARTEVENT CHART CHANGE event                                    |
//+------------------------------------------------------------------+
void CWndEvents::ChartEventChartChange(void)
  {
//--- Events bei einer Veränderung der Chart-Eigenschaften
   if(m_id!=CHARTEVENT_CHART_CHANGE)
      return;
//--- Überprüfung und Aktualisierung der Nummer des Programmfensters
   CheckSubwindowNumber();
//--- Verschieben des Fensters
   MovingWindow();
//--- Neuzeichnen des Charts
   m_chart.Redraw();
  }

Nun wird alles so funktionieren, wie es auch geplant war. Alle Indikatoren für das Testen der oben genannten Modi, können am Ende des Artikels heruntergeladen werden.

Abbildung  2. Testen des Formulars als Indikator in einem Chart-Unterfenster

Abbildung 2. Testen des Formulars als Indikator in einem Chart-Unterfenster

Es wird bei dem aktuellen Stand der Implementierung empfohlen, nicht mehr als eine Anwendung zu verwenden, die auf die hier entwickelte Bibliothek zurückgreift. Der oben dargestellte Screenshot zeigt mehrere Indikatoren innerhalb eines Charts, was aber nur für eine kompakte Demonstration der programmierten Modi ist. In dem Moment, wo das Scrollen eines Charts aktiviert oder deaktiviert wird, gibt es Konflikte zwischen den Anwendungen, welche diese Bibliothek verwenden. Es gibt einige Ideen für die Eliminierung dieser Konflikte zwischen den Anwendungen. Wenn zukünftige Tests eine brauchbare Lösung für dieses Problem liefern, dann wird es einen weiteren Artikel dieser Serie geben, welcher sich mit diesem Problem auseinandersetzt.

 

Verwendung des Formulars in Skripten

Wir haben bereits die Verwendung dieses Formular als EA und Indikator betrachtet. Kann dieses Formular auch in Skripten verwendet werden? Die Antwort ist ja. Aber in Skripten wird eine solches Formular in seinen Eigenschaften sehr begrenzt sein. Skripte besitzen zum Beispiel keine Eventhandler. Das ist auch der Grund (1) dafür, warum das Formular nicht bewegbar ist, (2) es macht keinen Sinn dieser Form Controls für Aktionen hinzuzufügen und (3) Die grafischen Objekte reagieren nicht auf die Bewegungen des Mauszeigers.

Aber wenn Sie ein Informations-panel erzeugen wollen, welches während der Ausführung des Skriptes einige statistische Daten anzeigt, warum verwenden Sie dann nicht dieses Formular als Basis für diese Aufgabe? Das heißt, alle ihre Anwendungen haben ein einheitliches Design, unabhängig von dem Typ der MQL Anwendung, da sie immer eine Bibliothek verwenden.

Erzeugen Sie ein Verzeichnis für die Hauptdatei des Skriptes und die Program.mqh Datei. Die CProgram Klasse in dieser Datei wird von der CWndEvents Klass abgeleitet, wie wir es zuvor schon bei dem EA und den Indikatoren betrachtet haben. Im Allgemeinen ist alles gleich. Die einzige Ausnahme ist, dass es hier nur einen OnEvent() Handler gibt und das zugehörige Ereignis wird künstlich in der Hauptdatei des Skriptes in einer ewigen Schleife erzeugt. Diese Methode besitzt nur einen Parameter: Die Anzahl der Millisekunden für die Pause, bevor die Methode wieder verlassen wird.

//+-----------------------------------------------------------------+
//| Klasse für das Erzeugen einer Anwendung                         |
//+-----------------------------------------------------------------+
class CProgram : public CWndEvents
  {
protected:
   //--- Window
   CWindow           m_window;
   //---
public:
                     CProgram(void);
                    ~CProgram(void);
   //--- Eventhandler
   virtual void      OnEvent(const int milliseconds);
   //--- Erzeugt das Informations-Panel
   bool              CreateInfoPanel(void);
   //---
protected:
   //--- Erzeugt eine Form
   bool              CreateWindow(const string text);
  };

Um eine Aktivität zu simulieren, ändern wir den Text der Kopfzeile in der CProgram::OnEvent() Methode in die Angabe des Intervalls, welche gleich der Anzahl der übergebenen Millisekunden ist. Lassen Sie uns einen Prozess-Indikator erzeugen. Der nachfolgende Code zeigt, wie wir dieses implementieren:

//+------------------------------------------------------------------+
//| Events                                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int milliseconds)
  {
   static int count =0;  // Counter
   string     str   =""; // Line for the header
//--- Formation in der Kopfzeile für den Fortschritt des Prozesses
   switch(count)
     {
      case 0 : str="SCRIPT PANEL";     break;
      case 1 : str="SCRIPT PANEL .";   break;
      case 2 : str="SCRIPT PANEL ..";  break;
      case 3 : str="SCRIPT PANEL ..."; break;
     }
//--- Aktualisierung der Kopfzeile
   m_window.CaptionText(str);
//--- Neuzeichnen auf dem Chart
   m_chart.Redraw();
//--- Erhöhung des Zählers
   count++;
//--- Wenn größer 3, dann 0
   if(count>3)
      count=0;
//--- Pause
   ::Sleep(milliseconds);
  }

Jetzt brauchen wir nur noch ein Formular zu erzeugen und einen konstanten Aufruf zu der CProgram::OnEvent() Methode in der Hauptdatei des Skriptes in der OnStart() Funktion zu organisieren. Der nachfolgende Code zeigt, dass diese Methode alle 250 Millisekunden aufgerufen wird. Dieser Prozess wird so lange ausgeführt, bis die IsStopped() Funktion den Wert false zurückgibt. Um diesen dauerhaften Loop unterbrechen zu können, muss das Skript manuell von dem Chart entfernt werden. Wenn das Programm durch den User beendet wird, dann gibt die IsStopped() Funktion "true" zurück, was dazu führt, dass der Loop anhält und das Script beendet wird.

//+------------------------------------------------------------------+
//|                                                    InfoPanel.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.0"
//--- Einbeziehen der Trading Panel Klasse
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart(void)
  {
//--- Einrichten des Trading Panels
   if(!program.CreateInfoPanel())
     {
      ::Print(__FUNCTION__," > Failed to create graphical interface!");
      return;
     }
//--- Das ist gut arbeitet, bis es manuell abgebrochen wird
   while(!::IsStopped())
     {
      //--- Generierung eines Events alle 250 Millisekunden
      program.OnEvent(250);
     }
  }
//+------------------------------------------------------------------+

Kompilieren Sie die Dateien und laden Sie das Skript auf den Chart.

Abbildung 3. Test des Formulars in einem Skript.

Abbildung 3. Test des Formulars in einem Skript.

In dem Screenshot können Sie sehen, dass die Bibliothek den Typ des Programms identifiziert und ein Standard-Icon verwendet hat. Dieses Skript kann am Ende dieses Artikels heruntergeladen werden. Dieses wird später in Beispielen mit Elementen angereichert, die zur Informationsgruppe gehören. Dieses Skript, sowie auch der EA und die Indikatoren dieses Artikels, können als Vorlage für die Entwicklung ihrer eigenen Programme verwendet werden.

 

Verwenden der Bibliothek in MetaTrader 4

Die Bibliothek für das Erzeugen von grafischen Interfaces kann in der MetaTrader 4 Handelsplattform verwendet werden. Kopieren Sie alle Dateien der Bibliothek und der Programme, welche wir in diesem Artikel erzeugt haben, von dem Verzeichnis MetaTrader 5 Terminal in die Verzeichnisse von dem MetaTrader 4 Terminal. Die Bibliothek ist fast einsatzbereit.

Das Einzige, was der Bibliothek noch hinzugefügt werden muss, damit keine Kompilierungsfehler auftreten, ist ein zusätzlicher spezieller Parameter, der eine Anweisung an den Compiler ist, einen speziellen strikten Modus für das Prüfen von Fehlern zu verwenden. Dafür fügen Sie bitte die folgende Programmzeile an den Anfang der Header-Dateien der Bibliothek hinzu:

#property strict

Machen Sie dieses in den Dateien WndContainer.mqh, Element.mqh und in den Haupt-Dateien des Programms. Dieses verhindert Fehlermeldungen, wenn die Dateien kompiliert werden.

Wenn alles richtig ist und alle Dateien kompiliert wurden, dann testen Sie die Programme dieses Artikels in dem MetaTrader 4 Terminal. Die sollte nun alles genauso funktionieren wie in dem MetaTrader 5 Terminal.

Abbildung 4. Test der Bibliothek in dem Metatrader 4 Terminal.

Abbildung 4. Test der Bibliothek in dem Metatrader 4 Terminal.

 

Schlussfolgerung

Wir können jetzt beurteilen, wo wir in der Entwicklung der Bibliothek für die Erstellung von grafischen Oberflächen stehen. In der aktuellen Phase der Entwicklung können wir die Struktur der Bibliothek wie folgt darstellen: Wenn Sie diesen Artikel vollständig gelesen haben, dann sollte Ihnen diese Schematik logisch erscheinen und einfach zu verstehen sein.

Abbildung 5. Die Struktur der Bibliothek zu den aktuellen Stand der Entwicklung

Abbildung 5. Die Struktur der Bibliothek zu den aktuellen Stand der Entwicklung

Ich will sie noch einmal daran erinnern, was die Elemente dieser Struktur bedeuten:

Der erste Teil dieser Serie betrachtete im Detail den Prozess der Vorbereitung der Haupt-Struktur der Bibliothek für das Erzeugen von grafischen Interfaces und die Erzeugung des Haupt-Interface-Elementes - das Formular, bzw. das Fenster. Es werden dem Formular noch weitere Steuerelemente (Controls) hinzugefügt Deren Erzeugung wird in den nachfolgenden Artikeln dieser Serie beschrieben. Die Fortführung dieser Serie wird sehr hilfreich sein, da sie im Detail die Erzeugung Ihrer eigenen Controls bespricht und wie Sie diese der Bibliothek hinzufügen. Diese lassen sich dann gut integrieren und sie stehen nicht im Konflikt mit anderen Steuerelementen.

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 Artikel (Kapitel) des ersten Teils: