English Русский 中文 Español 日本語 Português
preview
Verbessern Sie Ihre Handelscharts durch interaktiven GUI's in MQL5 (Teil II): Ein bewegliches GUI (II)

Verbessern Sie Ihre Handelscharts durch interaktiven GUI's in MQL5 (Teil II): Ein bewegliches GUI (II)

MetaTrader 5Handelssysteme | 22 August 2023, 09:32
339 0
Kailash Bai Mina
Kailash Bai Mina

Einführung

Willkommen zum zweiten Teil dieser Serie. Im ersten Teil haben wir besprochen, wie man ein einfaches, bewegliches Dashboard erstellt. Dieser zweite Teil zielt darauf ab, dasselbe Ziel zu erreichen, allerdings auf eine effizientere Art und Weise, die für vollwertige EA/Indikator-Anwendungen geeignet ist.

Wenn wir beispielsweise zwei bewegliche Dashboards auf dem Bildschirm haben wollen, müssen wir den vorhandenen Code duplizieren und sechs zusätzliche globale Variablen mit jeweils unterschiedlichen Namen erstellen. Wenn wir nun entscheiden, dass wir drei bewegliche Dashboards benötigen, würde die Komplexität des Codes erheblich zunehmen und viel schwieriger zu verwalten sein. Es ist klar, dass wir einen strafferen Ansatz brauchen.

Glücklicherweise können wir auf .mqh-Dateien zurückgreifen, um diesen Prozess zu vereinfachen.

Das werden wir in diesem Artikel behandeln:

  1. Das Konzept der Klassen verstehen
  2. Erstellen derselben Instrumentenpanels mit der .mqh-Datei
  3. Einrichten von zwei Dashboards auf demselben Diagramm mit einer .mqh-Datei


Das Konzept der Klassen verstehen

Bevor wir uns näher damit befassen, ist es wichtig, das Konzept der Klassen zu verstehen. Klassen können unglaublich fortschrittlich und komplex werden, wenn wir uns weiter in sie vertiefen, aber vorerst werden wir nur die Grundlagen behandeln. Das Verständnis und die effektive Nutzung dieser grundlegenden Konzepte sind wichtige Schritte, bevor man zu komplizierteren Details übergeht.

Was genau sind also Klassen?

Einfach ausgedrückt, ist eine Klasse ein komplexer Datentyp, ähnlich wie int, string und andere, aber mit etwas mehr Komplexität.

Es gibt zahlreiche Möglichkeiten, Klassen zu definieren, aber im Grunde kann man sie als Code-Cluster betrachten. Welche Art von Code, werden Sie sich fragen? Sie bestehen in der Regel aus einer Sammlung von Funktionen, die oft als Methoden bezeichnet werden, und Variablen. Manch einer mag behaupten, dass diese Definition etwas vage oder leicht ungenau ist. Es ist jedoch wichtig, daran zu denken, dass wir hier nicht für eine Prüfung pauken, wie wir es vielleicht in der Schule getan haben. Unser Hauptziel ist es, die Leistungsfähigkeit von Klassen zu nutzen, um die Programmierung überschaubarer und effizienter zu machen, und dafür ist eine starre Definition nicht entscheidend.

Im Grunde genommen sind Klassen eine Sammlung von Funktionen und Variablen, die wir zu unserem Vorteil nutzen können.

Dieses Verständnis führt natürlich zu vier grundlegenden Fragen:

  1. Wo erstellen wir sie?
  2. Wie deklarieren wir sie?
  3. Wie schreibt wir sie?
  4. Wie verwenden wir sie?

Übrigens, wenn Sie sich fragen, warum wir überhaupt Klassen brauchen, ist die Antwort ganz einfach. Klassen vereinfachen den Kodierungsprozess und machen die Codeverwaltung zu einem Kinderspiel.

  1. Wo erstellen wir sie?

    Die Wahl des Dateityps für die Erstellung von Klassen — sei es .mq5 oder .mqh — ist flexibel. In der Regel entscheiden wir uns jedoch für separate .mqh-Dateien.

    Der Unterschied zwischen der Erstellung von Klassen in .mq5 und .mqh ist bemerkenswert. Wenn Sie Ihre Klassen in einer .mqh-Datei entwickeln, müssen Sie sie in .mq5 importieren. Dies liegt daran, dass die Erstellung eines EA/Indikators ausschließlich für .mq5-Dateien möglich ist. Wenn Sie die Klasse jedoch direkt in einer .mq5-Datei einrichten, ist kein Importvorgang erforderlich.

    Wir bevorzugen im Allgemeinen separate .mqh-Dateien, da sie die Verwaltbarkeit des Codes verbessern. Der Importvorgang ist unkompliziert — er erfordert lediglich eine einzige Codezeile. Für diese Diskussion werden wir eine separate .mqh-Datei verwenden.


  2. Wie melden wir sie an?

    Die Deklaration einer Klasse ist sehr einfach. Nachfolgend ein Beispiel für die Deklaration einer einfachen, leeren Klasse:

    class YourClassName
      {
      };
    
    Im obigen Codeschnipsel ist „YourClassName“ ein Platzhalter. Ersetzen Sie „YourClassName“ durch den tatsächlichen Namen, den Sie Ihrer Klasse zuweisen möchten.



  3. Wie schreibt wir sie?

    Um dies zu verstehen, sollten wir zunächst über Variablen sprechen, bevor wir uns den Funktionen zuwenden.

    Angenommen, Sie möchten zwei Variablen deklarieren: eine vom Typ 'int' und eine vom Typ 'bool'. Sie können dies auf folgende Weise tun:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class YourClassName
      {
       int var1;
       bool var2;
      };
    //+------------------------------------------------------------------+
    
    

    Beachten Sie bitte, dass Sie diesen Variablen nicht direkt in der Klassendeklaration Werte zuweisen können. Der folgende Code zum Beispiel führt zu einem Fehler:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class YourClassName
      {
       int var1 = "int var";
       bool var2 = true;
      };
    //+------------------------------------------------------------------+
    
    
    Die Fehlermeldung, die Sie erhalten, lautet:
    '=' - unzulässige Verwendung einer Zuweisung
    '=' - unzulässige Verwendung einer Zuweisung

    In Szenarien, in denen unsere Logik von anfänglichen Variablenwerten abhängt, können wir einen sogenannten Konstruktor verwenden. Darüber hinaus gibt es auch einen so genannten Destruktor, der im Wesentlichen das Gegenstück zum Konstruktor darstellt.

    Konstruktoren und Destruktoren sind spezielle Funktionen, die immer mit einer Klasse verbunden sind, unabhängig davon, ob wir sie explizit deklarieren oder nicht. Wenn wir sie nicht deklarieren, werden sie implizit als leere Funktionen betrachtet. Der Konstruktor wird ausgeführt, wenn eine Instanz der Klasse deklariert wird, und der Destruktor wird ausgeführt, wenn eine Instanz der Klasse den Geltungsbereich verlässt. Es ist wichtig zu beachten, dass es in MQL5 keine Möglichkeit gibt, eine Instanz der Klasse explizit zu löschen.

    So verhält sich beispielsweise die Funktion OnInit() in unserem Code wie ein Konstruktor und die Funktion OnDeinit() wie ein Destruktor. Die Klasse ist hier der Einfachheit halber im Hintergrund versteckt. Dieses Muster ist in vielen Sprachen üblich, auch in Java, das immer eine Standardklasse enthält.

    Was eine „Instanz“ ist, werden wir im nächsten Schritt: „Wie verwenden wir sie?“ kurz erläutern.

    Verstehen Sie jetzt, dass wir Konstruktoren verwenden können, um unseren Variablen Anfangswerte zuzuweisen. Der Destruktor ist für diese Diskussion nicht relevant, aber wir werden ihn sicherlich in den späteren Teilen dieser Serie behandeln.

    Bitte beachten Sie, dass es sehr empfehlenswert ist, einen Konstruktor zu verwenden.

    Manche Programmierer ignorieren zwar die Warnungen des Compilers und gehen davon aus, dass Variablen implizit auf Standardwerte gesetzt werden, wenn sie nicht explizit definiert sind, aber dieser Ansatz ist weder ganz richtig noch ganz falsch.

    Die Annahme rührt daher, dass in vielen Programmiersprachen (auch in MQL4) Variablen ohne explizite Definition standardmäßig einen Wert annehmen, ohne dass es zu Inkonsistenzen im Code kommt. In MQL5 kann es jedoch zu Inkonsistenzen in Ihrem Code kommen, wenn Sie Ihre Variablen nicht explizit definieren.

    Im Folgenden sind die Standardwerte für die am häufigsten verwendeten Datentypen aufgeführt:

    Typ Zu deklarierender Code Angenommener Standardwert
     int  int test; 0
     double  double test; 0.0
     bool  bool test; false
     string  string test; NULL
     datetime  datetime test;   1970.01.01 00:00:00

    Anmerkung: Der angenommene Standardwert ist der Wert, den Sie sehen, wenn Sie die nicht initialisierte Variable ausdrucken. Wenn Sie dasselbe mit einer if-Anweisung überprüfen, verhalten sich einige wie erwartet, andere jedoch nicht, was zu Problemen führen kann.

    Unser Testskript sieht wie folgt aus:

    void OnStart()
      {
       type test;
       if(test == presumedDefaultValue)
         {
          Alert("Yes, This is the default value of the test variable.");
         }
       else
         {
          Alert("No, This is NOT the default value of the test variable.");
         }
      }
    

    Ersetzen Sie „type“ durch den Variablentyp und „presumedDefaultValue“ durch den Wert, den Sie als Standardwert erwarten.

    Hier sehen Sie, dass für bool und string alles perfekt funktioniert und die Meldung „Ja, dies ist der Standardwert der Testvariablen“ ausgegeben wird. Bei int, double und datetime ist dies jedoch nicht so einfach möglich. Wir erhalten die Warnmeldung, die besagt: „Nein, dies ist NICHT der Standardwert der Testvariablen“. Dieses unerwartete Ergebnis kann zu logischen Problemen führen.

    Nachdem wir nun die Bedeutung des Konstruktors verstanden haben, wollen wir uns ansehen, wie man einen Konstruktor erstellt:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class name
      {
    private:
       
    public:
       int               var1;
       bool              var2;
                         name();
                        ~name();
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name()
      {
       var1 = 0;
       var2 = true;
      }
    //+------------------------------------------------------------------+
    
    

    Hier haben wir public und private Felder in die Klasse aufgenommen, wobei private derzeit leer ist. Es ist sehr wichtig, den Konstruktor mit dem Modifikator public zu deklarieren, um ihn in anderen Dateien zu verwenden. Um dies zu erklären, müssen wir über Zugrissmodifiokatoren (auch bekannt als Access Specifiers oder Access Modifiers) sprechen.

    Zugriffsmodifikatoren definieren, wie der Compiler auf Variablen, Mitglieder von Strukturen oder Klassen zugreifen kann.

    Zugriffsmodifikatoren Beschreibung
     public  Ermöglicht den uneingeschränkten Zugriff auf die Variable oder die Klassenmethode
     private  Erlaubt den Zugriff von Methoden dieser Klasse sowie von Methoden der abgeleiteten Klassen. Ein anderer Zugang ist nicht möglich;
     protected  Erlaubt den Zugriff auf Variablen und Klassenmethoden nur von Methoden der gleichen Klasse.
     virtual  Gilt nur für Klassenmethoden (aber nicht für Methoden von Strukturen) und teilt dem Compiler mit, dass diese Methode in die Tabelle der virtuellen Funktionen der Klasse aufgenommen werden soll. 

    In diesem Artikel werden wir nur die Begriffe „public“ und „private“ verstehen, die anderen werden nicht verwendet, aber wir werden sie sicherlich in späteren Teilen der Serie behandeln.

    Wir werden uns in diesem Artikel auf public und private Modifikatoren konzentrieren und andere für spätere Teile der Serie auslassen. Öffentlich bedeutet im Wesentlichen, dass die als public definierten Variablen und Funktionen überall verwendet/verändert werden können (Variablen), auch in anderen .mq5- oder .mqh-Dateien. Andererseits erlaubt private nur den Zugriff innerhalb der in der aktuellen Klasse definierten Funktion.

    Wenn Sie sich fragen, warum man sie überhaupt braucht, gibt es viele Gründe wie Datenschutz, Abstraktion, Wartbarkeit, Wiederverwendbarkeit. 

    Wir haben unseren Konstruktorcode wie folgt definiert:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name()
      {
       var1 = 0;
       var2 = true;
      }
    //+------------------------------------------------------------------+
    

    Constructor/Destructor hat keinen Rückgabetyp.

    Beachten Sie, dass Konstruktoren/Destruktoren keinen Rückgabetyp haben. Wir weisen hier einfach uninitialisierten Variablen einen Wert zu, weil es manchmal notwendig ist, die bool-Variable (d.h. var2 in diesem Fall) auf true als Anfangswert zu setzen.

    Es gibt eine alternative Möglichkeit, dies zu tun:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name() : var1(0), var2(true)
      {
      }
    //+------------------------------------------------------------------+
    

    Dieser Konstruktor hat keinen Körper und initialisiert einfach die Werte der nicht initialisierten Variablen auf den gewünschten Wert. Sie können zwar beide Methoden anwenden, aber im Allgemeinen ist es besser, die zweite Methode zu verwenden. Wenn Sie zum Beispiel const verwenden, wenn Sie eine Variable deklarieren, um sie unveränderbar zu machen, funktioniert die erste Methode, einer nicht initialisierten Variablen einen Wert zuzuweisen, nicht, aber die zweite schon. Das liegt daran, dass wir in der ersten Methode einen Wert zuweisen, während wir in der zweiten Methode einen Wert initialisieren.

    Oder Sie können dies alternativ auch in die Mitgliederliste schreiben:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class name
      {
    private:
       
    public:
       int               var1;
       bool              var2;
                         name() : var1(0), var2(true) {}
                        ~name();
      };
    //+------------------------------------------------------------------+
    



    Nachdem wir uns nun mit der Deklaration von Variablen, Konstruktoren und Destruktoren beschäftigt haben, ist es einfach zu verstehen, wie man eine Funktion erstellt.

    Wenn Sie eine Funktion mit dem Namen Funktionsname() erstellen möchten, die eine Eingabe als String-Variable annimmt und einfach die Variable ausgibt, würde sie etwa so aussehen: 

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class className
      {
    public:
       void              functionName(string printThis)
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void className::functionName(string printThis)
      {
       Print(printThis);
      }
    //+------------------------------------------------------------------+
    


    Wir deklarieren die Funktion in der Mitgliederliste in der Klasse mit dem Namen „className“ mit dem Modifikator „public“, sodass wir diese Funktion überall verwenden können. Dann schreiben wir den Hauptteil der Funktion.

    Beachten Sie, dass die Deklaration der Funktion in der Klasse, d.h.

    functionName(string printThis)

    beim Schreiben des Funktionskörpers genau übereinstimmen muss.

    Damit ist unsere grundlegende Einführung in das Schreiben von Klassen in MQL5 abgeschlossen.


  4. Wie verwenden wir sie?


    Um dies besser zu verstehen, sehen wir uns unsere Ordnerstruktur an:

    • Test Projekt
      • mainFile.mq5
      • includeFile.mqh

    Sehen wir uns zunächst unseren vollständigen Klassencode an, den wir in includeFile.mqh geschrieben haben:

    In diesem Beispiel haben wir eine Klasse namens „className“ deklariert, die einen Konstruktor, einen Destruktor, drei Variablen (eine private und zwei public) und eine öffentliche Funktion enthält.

    • Konstruktor: Wir initialisieren die Variablen var0, var1 und var2 mit 10, 0 bzw. true.
    • Destruktor: Derzeit ist er leer und macht daher nichts.
    • var0: Dies ist eine private Integer-Variable, die mit 10 initialisiert und in der Funktion (functionName) verwendet wird.
    • var1: Dies ist eine public Integer-Variable, die mit 0 initialisiert ist und auch in der Funktion (Funktionsname) verwendet wird.
    • functionName: Diese ungültige Funktion mit dem Namen „functionName“ akzeptiert die Ganzzahl „printThisNumber“ und gibt die Summe von printThisNumber, var0 und var1 aus.

    Als Nächstes wollen wir unsere mainFile.mq5 untersuchen:

    #include "includeFile.mqh"
    className classInstance;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       classInstance.functionName(5)//This line will print (5+10+0) = 15
       classInstance.var1 = 50;//This will change the value of var1 to 50
       classInstance.functionName(5)//Now, this line will print (5+10+50) = 65
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    

    Wir binden zunächst die Datei includeFile.mqh in unsere mainFile.mq5 ein. Dann erstellen wir eine Instanz der Klasse mit:

    className classInstance;

    Diese Instanz ermöglicht es uns, Variablen oder Funktionen der Klasse zu ändern und zu verwenden. Dieser Vorgang wird als Instanziierung bezeichnet, aber wir werden uns hier nicht mit den genauen Definitionen befassen.
    Sie können so viele Instanzen erstellen, wie Sie möchten, und sie sind alle unabhängig voneinander.

    Wir haben "" anstelle von <> für die Suche nach der .mqh-Datei verwendet, weil <> nach der .mqh-Datei im Ordner 'include' sucht, während "" nach der .mqh-Datei im aktuellen Verzeichnis sucht.

    Diese Anleitung vermittelt ein solides Verständnis für die Verwendung der Klasse.


    Erstellen derselben Instrumentenpanels mit der .mqh-Datei

    Machen wir uns auf die Reise, um ein ähnliches Dashboard von Grund auf neu zu erstellen, aber dieses Mal mit einer .mqh-Datei. Wo nötig, werden wir Teile unseres früheren Codes übernehmen. Um unsere Codedateien effektiv zu organisieren, erstellen wir einen neuen Ordner mit dem bezeichnenden Namen „Movable Dashboard MQL5“.

    Als Nächstes erzeugen wir zwei neue Dateien: die Datei „Movable_Dashboard_MQL5.mq5“, die als primäre .mq5-Datei dienen wird, und die Datei „GUI_Movable.mqh“, die den Code enthält, um das Dashboard beweglich zu machen. Die korrekte Benennung dieser Dateien ist entscheidend für die einfache Verwaltung mehrerer Dateien.

    Zunächst erstellen wir ein 200x200 großes weißes Dashboard mit Hilfe der Methode Object Create in unserer Haupt-.mq5-Datei (Movable_Dashboard_MQL5.mq5) innerhalb der OnInit():

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //---
       //Set the name of the rectangle as "TestRectangle"
       string name = "TestRectangle";
       //Create a Rectangle Label Object at (time1, price1)=(0,0)
       ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
       //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
       //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
       //Set XSize to 200px i.e. Width of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
       //Set YSize to 200px i.e. Height of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
       ChartRedraw();
    //---
       return(INIT_SUCCEEDED);
      }
    

    Ergebnis: 

    Abbildung 1: Grundlegendes Bild des Dashboards

    Abbildung 1. Grundlegendes Dashboard-Bild


    Sie fragen sich vielleicht, warum wir das Dashboard in unserer Haupt-.mq5-Datei (Movable_Dashboard_MQL5.mq5) und nicht in der .mqh-Datei (GUI_Movable.mqh) erstellen. Diese Entscheidung dient vor allem der Vereinfachung und kann von Ihren spezifischen Zielen abhängen, die wir im nächsten Abschnitt behandeln.

    Wenden wir uns nun der .mqh-Datei (GUI_Movable.mqh) zu, die derzeit wie folgt aussieht:

    //+------------------------------------------------------------------+
    //| Class GUI_Movable                                                |
    //+------------------------------------------------------------------+
    class GUI_Movable
      {
       
      };
    //+------------------------------------------------------------------+
    

    Hier haben wir lediglich eine Klasse deklariert, ohne explizit einen Konstruktor und einen Destruktor zu definieren.

    Was ist also unser Ziel? Wir wollen diesen Code so anpassen, dass er in unsere Hauptdatei implementiert werden kann, wodurch unser Dashboard beweglich wird.

    Wie können wir das erreichen? Der einzige Code, der benötigt wird, um das Dashboard in unserer vorherigen .mq5-Datei (Movable_Dashboard_MQL5.mq5) beweglich zu machen, ist:

    //Declare some global variable that will be used in the OnChartEvent() function
    int previousMouseState = 0;
    int mlbDownX = 0;
    int mlbDownY = 0;
    int mlbDownXDistance = 0;
    int mlbDownYDistance = 0;
    bool movingState = false;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
    //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
          
          string name = "TestRectangle";
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+
    
    

    Wir werden nun folgendermaßen vorgehen: 

    1. Code für die Klasse GUI_Movable schreiben.
    2. Erstellen einer Instanz der Klasse in der Haupt-.mq5-Datei.
    3. Dieser Instanz einen Namen geben.
    4. Verwenden der Methoden der Klasse GUI_Movable, um das Dashboard beweglich zu machen

    Diese Schritte mögen anfangs abschreckend wirken, aber mit etwas Übung wird der Prozess intuitiv.

    1. Schreiben wir Code für die Klasse GUI_Movable:

      Wir müssen die Bestandteile unserer Klasse planen. Hier ist eine Aufschlüsselung:

      1. Wir müssen sechs Variablen mit dem Modifikator „private“ deklarieren (previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance, movingState). Zu diesen Variablen gehören fünf Ganzzahlen und ein Boolescher Wert.
      2. Wir müssen eine siebte public Variable deklarieren, die den Namen des Dashboards speichern wird. Wir müssen diese Variable public machen, weil wir sie von unserer Haupt-.mq5-Datei aus ändern müssen.
      3. Wir müssen einen Weg finden, die OnChartEvent-Funktion in der .mqh-Datei zu nutzen, da sich alle unsere deklarierten Variablen dort befinden, und wir diese Variablen innerhalb der OnChartEvent-Funktion benötigen.


      1. Beginnen wir mit der Deklaration unserer sechs privaten Variablen in der Klasse, von denen fünf ganze Zahlen und eine boolesche Variable sind. Wir werden einen Konstruktor verwenden, um diese Werte zu initialisieren:

        //+------------------------------------------------------------------+
        //| Class GUI_Movable                                                |
        //+------------------------------------------------------------------+
        class GUI_Movable
          {
        private:
           int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
           bool              movingState;
        public:
                             GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false) {};
          };
        //+------------------------------------------------------------------+
        

        Jetzt brauchen wir 0 für alle int und false für bool als Startwerte, also haben wir den Konstruktor verwendet, um sie zu initialisieren.

      2. Als Nächstes deklarieren wir eine public Variable, um den Namen des Dashboards zu speichern. Diese Variable muss von unserer Haupt-.mq5-Datei aus zugänglich sein.

        public: 
           string Name;
        

        Der ursprüngliche Wert wird natürlich NULL sein, aber aus formalen Gründen werden wir ihn mit NULL initialisieren und unseren Konstruktor ändern (eine Formalität, weil String keine Inkonsistenzen verursacht)

        GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
        
        
      3. Dieser Schritt mag etwas kompliziert erscheinen, ist aber ganz einfach, wenn man ihn verstanden hat.

        Wir erstellen eine public Funktion namens OnEvent, die die folgenden Eingaben akzeptiert: id, lparam, dparam und sparam. Da OnChartEvent() nichts zurückgibt (void), machen wir auch OnEvent() void.

        Die Funktion OnEvent erledigt alles, wofür OnChartEvent() gedacht ist, allerdings in der .mqh-Datei. Wir werden OnEvent() in der eigentlichen Funktion OnChartEvent() in der Hauptdatei verwenden.

        Um Fehler zu vermeiden, die durch die Deklaration von OnChartEvent() sowohl in der .mqh- als auch in der main-Datei verursacht werden, haben wir diese separate Funktion namens OnEvent() erstellt. So deklarieren wir es:

        public:
           string            Name;
           void              OnEvent(int id, long lparam, double dparam, string sparam);
        

        Schreiben wir nun den Funktionscode. Es wird alles tun, wofür das ursprüngliche OnChartEvent() gedacht war:

        //+------------------------------------------------------------------+
        //|                                                                  |
        //+------------------------------------------------------------------+
        void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
          {
           
          }
        //+------------------------------------------------------------------+
        

        Wir haben diese Funktion in den globalen Bereich gestellt. Jetzt können wir einfach den gleichen Code hier einfügen, und er wird Zugriff auf die in der Klasse deklarierten Variablen haben.

        Die vollständige Funktion sieht wie folgt aus:

        //+------------------------------------------------------------------+
        //|                                                                  |
        //+------------------------------------------------------------------+
        void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
          {
           //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
           if(id == CHARTEVENT_MOUSE_MOVE)
             {
              //define X, Y, XDistance, YDistance, XSize, YSize
              int X = (int)lparam;
              int Y = (int)dparam;
              int MouseState = (int)sparam;
        
              string name = Name;
              int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
              int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
              int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
              int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
        
              if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
                {
                 mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
                 mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
                 mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
                 mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
        
                 if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
                   {
                    movingState = true; //If yes the set movingState to True
                   }
        
                }
        
              if(movingState)//if movingState is true, Update the Dashboard position
                {
                 ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
                 ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
                 ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
                 ChartRedraw(0); //Redraw Chart
                }
        
              if(MouseState == 0)//Check if MLB is not pressed
                {
                 movingState = false;//set movingState again to false
                 ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
                }
        
              previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
             }
          }
        //+------------------------------------------------------------------+
        
        

        Das Einzige, was wir ändern, ist: 

        string name = "TestRectangle";

        zu:

        string name = Name;

        Denn natürlich müssen wir die Variable Name verwenden, die wir in unserer Haupt-.mq5-Datei gesetzt haben.


    2. Erstellen einer Instanz der Klasse in der Haupt-.mq5-Datei: 

      Das kann ganz einfach auf folgende Weise geschehen:

      #include "GUI_Movable.mqh"
      GUI_Movable Dashboard;
      

      Hier haben wir die .mqh-Datei eingebunden und uns für "" anstelle von <> entschieden, um den Speicherort der Datei anzugeben. <> sucht die .mqh-Datei im Include-Ordner, während "" die .mqh-Datei im aktuellen Verzeichnis sucht, das in diesem Fall ein Ordner namens „Movable Dashboard MQL5“ ist. Anschließend deklarieren wir eine Instanz der Klasse GUI_Movable und geben ihr einen passenden Namen, nämlich „Dashboard“. Dieser Name ermöglicht es uns, den Code zu verwenden, den wir in der .mqh-Datei geschrieben haben.

    3. Geben wir dieser Instanz einen Namen:

      Dies kann mühelos in der Funktion OnInit() ausgeführt werden. So sollte unser OnInit() aussehen:

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //---
         //Set the name of the rectangle as "TestRectangle"
         string name = "TestRectangle";
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Dashboard.Name = name;
         
      //---
         return(INIT_SUCCEEDED);
        }
      

      Am Ende verwenden wir:
      //Give dashboard's name to the class instance
      Dashboard.Name = name;
      

      um die Variable „Name“ in der Dashboard-Instanz der Klasse GUI_Movable zuzuweisen. Dies wird später in OnEvent() innerhalb der Instanz verwendet werden. Denken Sie unbedingt daran, die Eigenschaft CHART_EVENT_MOUSE_MOVE auf true zu setzen. Dies ermöglicht die Erkennung von Mausereignissen. Wir werden diesen Schritt später im Konstruktor wiederholen, aber für den Moment halten wir die Dinge unkompliziert

    4. Verwenden wir die Methoden der Klasse GUI_Movable, um das Dashboard beweglich zu machen: 

      Trotz des etwas komplizierten Namens ist dieser Schritt ganz einfach.

      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
        {
         Dashboard.OnEvent(id, lparam, dparam, sparam);
        }
      

      In dieser Phase platzieren wir die Funktion OnEvent() innerhalb von OnChartEvent(), um die Funktionalität von OnChartEvent() in der .mqh-Datei zu nutzen.

    Abschließend finden Sie hier unseren vollständigen Code:

    Struktur des Ordners:

    • Movable Dashboard MQL5
      • Movable_Dashboard_MQL5.mq5
      • GUI_Movable.mqh

    1. Movable_Dashboard_MQL5.mq5
      #include "GUI_Movable.mqh"
      GUI_Movable Dashboard;
      
      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
         //---
         //Set the name of the rectangle as "TestRectangle"
         string name = "TestRectangle";
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Dashboard.Name = name;
         
      //---
         return(INIT_SUCCEEDED);
        }
      
      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
        {
         Dashboard.OnEvent(id, lparam, dparam, sparam);
        }
      //+------------------------------------------------------------------+
      
      
    2. GUI_Movable.mqh
      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
      public:
      		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         string            Name;
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };
      //+------------------------------------------------------------------+
      
      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
        {
         //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
         if(id == CHARTEVENT_MOUSE_MOVE)
           {
            //define X, Y, XDistance, YDistance, XSize, YSize
            int X = (int)lparam;
            int Y = (int)dparam;
            int MouseState = (int)sparam;
      
            string name = Name;
            int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
            int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
            int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
            int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
      
            if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
              {
               mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
               mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
               mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
               mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
      
               if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
                 {
                  movingState = true; //If yes the set movingState to True
                 }
      
              }
      
            if(movingState)//if movingState is true, Update the Dashboard position
              {
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
               ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
               ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
               ChartRedraw(0); //Redraw Chart
              }
      
            if(MouseState == 0)//Check if MLB is not pressed
              {
               movingState = false;//set movingState again to false
               ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
              }
      
            previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
           }
        }
      //+------------------------------------------------------------------+
      
      

    Kompilieren Sie zuerst die .mqh-Datei und dann die .mq5-Datei. Dadurch wird eine .ex5-Datei erstellt, die an das Chart angehängt werden kann.

    Mit diesen Schritten haben wir das, was wir in Teil 1 erreicht haben, mit effizienterem Code wiederholt. Beachten Sie den signifikanten Unterschied in der Menge des Codes, der in der Haupt-.mq5-Datei zwischen Teil 1 und Teil 2 verwendet wird. Und das Beste daran ist, dass es von nun an nur noch besser werden kann.


    Ergebnis:

    Abbildung 2. Einfaches bewegliches Dashboard

    Abbildung 2. Einfaches bewegliches Dashboard



    Einrichten von zwei Dashboards auf demselben Diagramm mit einer .mqh-Datei

    Anstatt das Dashboard mit ObjectCreate in unserer .mq5-Hauptdatei zu erstellen, werden wir dies nun in unserer .mqh-Datei tun. Sie werden sehen, wie einfach die Dinge danach werden.

    Gehen wir nun auf die Änderungen ein, die wir an unserer .mqh-Datei vornehmen werden:

    1. Wir müssen den Modifikator der String-Variablen „Name“ von public auf private ändern. Der „Name“ ist in unserer Hauptdatei nicht erforderlich - wir brauchen ihn nur in der .mqh-Datei. Wir machen es also private. Dies kann wie folgt geschehen:

      Von:
      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
      public:
      	             GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         string            Name;
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };
      

      Zu: 

      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
         string            Name;
      public:
      		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };
      

      Einfach durch den anderen Standort von

      string            Name;
      hat sich der Variablenmodifikator von public auf private geändert.

    2. Als Nächstes fügen wir eine public Methode namens CreateDashboard() hinzu. Diese Methode nimmt die folgenden Eingaben entgegen: name (string), xDis (int), yDis (int), xSize (int), ySize (int).

      Wir fügen dies zunächst in die Liste der Klassenmitglieder ein:

      public:
         void              OnEvent(int id, long lparam, double dparam, string sparam);
         void              CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);
      

      Definieren wir nun diese Funktion im globalen Bereich, indem wir den Code aus unserer Hauptdatei wie folgt kopieren:

      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) {
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Name = name;
         //Redraw Chart
         ChartRedraw();
      }
      //+------------------------------------------------------------------+
      
      


    Anschließend müssen wir unsere .mq5-Datei ändern:

    #include "GUI_Movable.mqh"
    GUI_Movable Dashboard1;
    GUI_Movable Dashboard2;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       Dashboard1.CreateDashboard("Dashboard1", 100, 100, 200, 200);
       Dashboard2.CreateDashboard("Dashboard2", 100, 350, 200, 200);
    //---
       return(INIT_SUCCEEDED);
      }
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       Dashboard1.OnEvent(id, lparam, dparam, sparam);
       Dashboard2.OnEvent(id, lparam, dparam, sparam);
      }
    //+------------------------------------------------------------------+
    
    

    Um es für Sie auf den Punkt zu bringen:

    Wir beginnen mit dem Einbinden der Datei „GUI_Movable.mqh“, die die Definition der Klasse GUI_Movable enthält. Diese Klasse enthält Methoden zum Erstellen und Bearbeiten von Ereignissen im Zusammenhang mit einem beweglichen Dashboard.

    Als Nächstes deklarieren wir zwei Instanzen der Klasse GUI_Movable, Dashboard1 und Dashboard2. Diese Instanzen stellen die beiden Dashboards dar, die wir in unserem Programm erstellen und steuern werden.

    In der Funktion OnInit(), die automatisch beim Start des Expert Advisors aufgerufen wird, erstellen wir die beiden Dashboards, indem wir die Methode CreateDashboard() für unsere beiden Instanzen aufrufen. Wir übergeben den Namen des Dashboards sowie seine Position und Größe (in Pixeln) als Parameter an diese Methode. Die Funktion gibt dann INIT_SUCCEEDED zurück, um anzuzeigen, dass die Initialisierung erfolgreich war.

    Schließlich gibt es noch die Funktion OnChartEvent(), die immer dann ausgelöst wird, wenn ein Ereignis im Chart auftritt (z. B. ein Mausklick oder eine Bewegung). In dieser Funktion rufen wir die Methode OnEvent() auf unseren beiden Dashboard-Instanzen auf und übergeben alle empfangenen Parameter. Dadurch kann jedes Dashboard das Ereignis unabhängig behandeln, entsprechend der Logik, die in der Methode OnEvent() der Klasse GUI_Movable definiert ist.

    Wie Sie sehen können, ist dieser Ansatz einfach und sauber, während die gleiche Funktionalität beibehalten wird. Dadurch ist der Code in vollwertigen EAs/Indikatoren gut verwendbar.

    Vollständiger Code der .mqh-Datei:

    //+------------------------------------------------------------------+
    //| Class GUI_Movable                                                |
    //+------------------------------------------------------------------+
    class GUI_Movable
      {
    private:
       int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
       bool              movingState;
       string            Name;
    public:
    		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
       void              OnEvent(int id, long lparam, double dparam, string sparam);
       void              CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
    
          string name = Name;
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) {
       //Create a Rectangle Label Object at (time1, price1)=(0,0)
       ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
       //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
       //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
       //Set XSize to 200px i.e. Width of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
       //Set YSize to 200px i.e. Height of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
       //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
       ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
       //Give dashboard's name to the class instance
       Name = name;
       //Redraw Chart
       ChartRedraw();
    }
    //+------------------------------------------------------------------+
    
    

    Ergebnis: 

    Abbildung 3. Zwei bewegliche Dashboards im gleichen Diagramm

    Abbildung 3. Zwei bewegliche Dashboards auf demselben Chart


    Wenn Sie sich über die Hierarchie dieser Dashboards wundern, so steht das zu einem früheren Zeitpunkt erstellte Dashboard an einer niedrigeren Position als das später erstellte Dashboard.


    Schlussfolgerung

    Für diejenigen, die ihr bereits vorhandenes Dashboard beweglich machen wollen, ist das Verfahren recht einfach. Unter Bezugnahme auf den Abschnitt mit dem Titel „Erstellen derselben Instrumentenpanels mit der .mqh-Datei“, werden Sie feststellen, dass Sie jedes Dashboard mit nur wenigen Zeilen Code in Ihrem bestehenden EA/Indikator in ein bewegliches Dashboard umwandeln können. Dazu muss lediglich die Datei GUI_Movable.mqh eingebunden und eine Instanz der Klasse erstellt werden, der der Name des Dashboards zugewiesen wird. Mit diesen einfachen Schritten wird Ihr Dashboard interaktiv und kann leicht mit der Maus verschoben werden.


    Mit dem Abschluss dieses zweiten Teils haben wir erfolgreich gelernt, wie wir die Interaktivität eines Dashboards verbessern können, indem wir es beweglich machen. Dies kann auf jeden bereits existierenden EA/Indikator angewandt werden, oder wenn ein neuer von Grund auf erstellt wird.

    Obwohl dieser Artikel sehr langatmig war und das Konzept der Klassen schwierig zu erklären und zu verstehen sein kann, glaube ich, dass dieses Wissen für Ihre künftige Programmierarbeit von großem Nutzen sein wird.

    Ich hoffe wirklich, dass Sie diesen Artikel hilfreich fanden, und sei es auch nur im Kleinen. Ich freue mich darauf, Sie im nächsten Teil der Serie wiederzusehen.

    Fröhliches Kodieren, fröhliches Handeln!


    Übersetzt aus dem Englischen von MetaQuotes Ltd.
    Originalartikel: https://www.mql5.com/en/articles/12880

    Beigefügte Dateien |
    MQL5.zip (5.01 KB)
    Entwicklung eines MQTT-Clients für MetaTrader 5: ein TDD-Ansatz Entwicklung eines MQTT-Clients für MetaTrader 5: ein TDD-Ansatz
    Dieser Artikel berichtet über die ersten Versuche bei der Entwicklung eines nativen MQTT-Clients für MQL5. MQTT ist ein Client-Server-Publish/Subscribe-Messaging-Transportprotokoll. Es ist leichtgewichtig, offen, einfach und so konzipiert, dass sie leicht zu implementieren ist. Diese Eigenschaften machen es ideal für den Einsatz in vielen Situationen.
    Entwicklung eines Wiedergabesystems — Marktsimulation (Teil 04): Anpassung der Einstellungen (II) Entwicklung eines Wiedergabesystems — Marktsimulation (Teil 04): Anpassung der Einstellungen (II)
    Lassen Sie uns mit der Entwicklung des Systems und der Kontrollen fortfahren. Ohne die Möglichkeit, den Dienst zu kontrollieren, ist es schwierig, Fortschritte zu machen und das System zu verbessern.
    ONNX-Modelle in Klassen packen ONNX-Modelle in Klassen packen
    Die objektorientierte Programmierung ermöglicht die Erstellung eines kompakteren Codes, der leicht zu lesen und zu ändern ist. Hier sehen wir uns das Beispiel für drei ONNX-Modelle an.
    Mean Reversion, eine einfache Handelsstrategie Mean Reversion, eine einfache Handelsstrategie
    Mean Reversion ist eine Form des entgegengesetzten Handels, bei der der Händler erwartet, dass der Kurs zu einer Art Gleichgewicht zurückkehrt, das im Allgemeinen durch einen Mittelwert oder eine andere Statistik der zentralen Tendenz gemessen wird.