English
preview
Erstellen einer interaktiven grafischen Nutzeroberfläche in MQL5 (Teil 2): Hinzufügen von Steuerelementen und Reaktionsfähigkeit

Erstellen einer interaktiven grafischen Nutzeroberfläche in MQL5 (Teil 2): Hinzufügen von Steuerelementen und Reaktionsfähigkeit

MetaTrader 5Handel | 4 September 2024, 10:42
20 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem letzten Artikel haben wir die Grundlage gelegt, indem wir die grafischen Elemente unserer MetaQuotes Language 5 (MQL5) grafischen Nutzeroberfläche (GUI) zusammengesetzt haben. Wenn Sie sich erinnern, war die Iteration eine statische Ansammlung von GUI-Elementen - eine bloße Momentaufnahme, eingefroren in der Zeit, ohne Reaktionsfähigkeit. Sie war statisch und unnachgiebig. Jetzt wollen wir diesen Schnappschuss auftauen und mit Leben füllen. In dieser mit Spannung erwarteten Fortsetzung heben wir unser Panel auf die nächste Stufe. Schnallen Sie sich an, wir werden unserer Schnittstelle Leben einhauchen:

  • Layout und Reaktionsfähigkeit: Vergessen Sie statische Komponenten! Wir setzen auf relative Positionierung, flexible Layouts und anklickbare, reaktionsfähige und bearbeitbare Komponenten, damit unser Panel auf Nutzerinteraktionen reagiert.
  • Dynamische Aktualisierungen: Echtzeitdaten sind das Herzstück einer jeden Handelsanwendung. Wir werden uns mit dem Abrufen von Live-Kursen beschäftigen und sicherstellen, dass unser Panel die neuesten Marktinformationen widerspiegelt.
  • Komponente Mobilität: Stellen Sie sich vor, Sie könnten Elemente verschieben, die auf die Berührung des Nutzers reagieren. Wir werden untersuchen, wie bestimmte Komponenten beweglich gemacht werden können, um die Nutzerfreundlichkeit zu verbessern.

Die folgenden Themen werden uns zeigen, wie wir ein ansprechendes und interaktives Panel gestalten können:

  1. Illustration der zu automatisierenden Elemente
  2. GUI-Automatisierung in MQL5
  3. Schlussfolgerung


Illustration der zu automatisierenden Elemente

Sieben Komponenten sollen automatisiert werden. Die erste Komponente ist das Schließen des Panels, wenn die Schaltfläche zum Schließen angeklickt wird. Wir beabsichtigen, alle Bedienelemente zu löschen, wenn diese Schaltfläche angeklickt wird. Zweitens, wenn die Schaltflächen für die Positionsverwaltung angeklickt werden, schließen die Schaltflächen ihre jeweiligen Positionen und Aufträge wie angewiesen. Wenn wir zum Beispiel auf die Schaltfläche oder das Etikett „Profit“ klicken, schließen wir alle Positionen, die nur im Gewinn sind. Die dritte Automatisierung wird sich auf die Komponente Handelsvolumen beziehen. Sobald das Unternehmen angeklickt wird, wird eine Dropdown-Liste mit Optionen erstellt, aus der der Nutzer eine Handelsoption auswählen kann.

Die vierte Automatisierung betrifft die Schaltflächen zum Erhöhen oder Verringern der Werte in den Eingabefeldern neben den jeweiligen Handelsschaltflächen, anstatt sie einfach nur einzugeben. Falls der Nutzer die gewünschten Werte direkt eingeben möchte, muss das Bearbeitungsfeld die eingegebenen Werte erfassen, was unseren fünften Automatisierungsschritt darstellt. Der sechste Schritt ist die Erstellung eines Hover-Effekts für die Schaltfläche, auf der die Maus schwebt. Das heißt, wenn sich die Maus im Bereich der schwebenden Schaltfläche befindet, vergrößert sich die Schaltfläche, um anzuzeigen, dass sich die Maus in der Nähe der Schaltfläche befindet, und wenn sich die Maus aus dem Bereich der Schaltfläche entfernt, wird die Schaltfläche auf die Standardfunktionen zurückgesetzt. Schließlich werden die Kursnotierungen bei jedem Tick auf Echtzeitwerte aktualisiert. 

Um das Verständnis dieser Automatisierungsprozesse und -komponenten zu erleichtern, finden Sie im Folgenden eine ausführliche Beschreibung dieser Prozesse und Komponenten mit dem vorherigen Meilenstein.

STUFENDARSTELLUNG

Da wir nun wissen, was wir tun werden, können wir sofort mit der Automatisierung beginnen. Bitte lesen Sie den vorhergehenden Artikel, in dem wir die statische Anordnung der GUI-Elemente erstellt haben, falls Sie ihn noch nicht durchgearbeitet haben, damit Sie mit uns auf dem richtigen Weg sind. Los geht's.


GUI-Automatisierung in MQL5

Wir werden von einfachen zu komplexen Prozessen übergehen, sodass unsere Struktur in chronologischer Reihenfolge angeordnet ist. Daher werden wir die Preise bei jeder Tick- oder Kursänderung aktualisieren. Um dies zu erreichen, benötigen wir OnTick, die integrierte MQL5-Funktion, die normalerweise aufgerufen wird, wenn sich die Kursnotierungen ändern. Die Funktion ist ein ungültiger Datentyp, was bedeutet, dass sie direkt ausgeführt wird und keine Ausgabe zurückgeben muss. Ihre Funktion sollte wie folgt aussehen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

  ...

}
//+------------------------------------------------------------------+

Dies ist die Ereignisbehandlung, die für die Preisaktualisierung zuständig ist und somit das Herzstück unserer Logik. Wir fügen die Steuerlogik zu dieser Funktion wie folgt hinzu:

    // --- Update price quotes ---
    
    // Set the text of the "SELL PRICE" label to the current Bid price
    ObjectSetString(0, LABEL_SELL_PRICE, OBJPROP_TEXT, Bid());

Wir verwenden ObjectSetString, um die Eigenschaft des Objekts, in diesem Fall Text, zu setzen, da wir die Texteingabe der Schaltflächenbeschriftung ändern müssen. Wir geben die Chart-ID des Fensters als 0 für das aktuelle Chart an, wir können aber auch die Funktion „ChartID()“ verwenden, die den Chart-Identifikationsindex für das aktuelle Chart-Fenster liefert. Dann geben wir „LABEL_SELL_PRICE“ als Zielobjektnamen an, um die Beschriftung der Verkaufsschaltfläche zu aktualisieren, und „OBJPROP_TEXT“, um anzugeben, dass es sich bei der zu aktualisierenden Objekteigenschaft um den Textstring-Wert des Objekts handelt. Schließlich geben wir den Textwert an. Der Geldkurs (bid) ist der Wert, den wir aktualisieren müssen, und deshalb tragen wir ihn ein, aber das ist noch nicht alles. Der Eigenschaftstyp, den wir ausfüllen müssen, ist eine Zeichenkette, und unser Angebotspreis hat das Format Double. Wir müssen also den Wert vom Typ Double in einen Wert vom Typ String umwandeln, sonst erhalten wir beim Kompilieren eine Warnung - implizite Umwandlung von „number“ in „string“.

BESCHREIBUNG DER WARNUNG

An dieser Stelle könnten wir den Double-Wert direkt in eine Zeichenkette umwandeln, wie unten gezeigt, aber das wird normalerweise nicht empfohlen, da es mit Bedacht verwendet werden sollte.

    // --- Update price quotes ---
    
    // Set the text of the "SELL PRICE" label to the current Bid price
    ObjectSetString(0, LABEL_SELL_PRICE, OBJPROP_TEXT, (string)Bid());

Typecasting in MQL5 hat, wie die Umwandlung eines numerischen Wertes in eine Zeichenkette, einige Nuancen. Einer der häufigsten ist der Verlust der Präzision. Unser Geldkurs ist beispielsweise ein Fließkommawert, und wenn sein Preis 11,77900 beträgt, werden die letzten beiden Nullen ignoriert und der endgültige Ausgabewert lautet 11,779. Technisch gesehen gibt es keinen logischen Unterschied zwischen den beiden Werten, aber visuell gibt es einen mathematischen Unterschied, da der eine 5 Ziffern und der andere 3 Ziffern hat. Hier ist ein Beispiel dafür, was wir meinen.

TYPISIERUNG DER NUANCE

Wie wir gesehen haben, wird die Warnung durch die Typenumwandlung beseitigt, aber es ist nicht der beste Ansatz, wenn es auf Präzision ankommt. Es wird also eine weitere Funktion benötigt. Wir verwenden die in MQL5 integrierte Funktion DoubleToString für die Umwandlung. Diese Funktion wird verwendet, um einen numerischen Wert mit Fließkomma in einen Textstring zu konvertieren. Sie benötigt zwei Eingabeparameter oder Argumente, die Ziel-Fließkommazahl und das Genauigkeitsformat. In unserem Fall verwenden wir den Geldkurs als Zielwert und _Digits für die Anzahl der Dezimalen, eine Variable, die die Anzahl der Nachkommastellen speichert, die die Preisgenauigkeit des Symbols des aktuellen Charts definiert. Sie können auch die Funktion Digits() verwenden. Dies wäre eine beliebige Zahl im Bereich von 0 bis 8, und wenn sie weggelassen wird, würde sie einen Wert von 8 Ziffern annehmen. Unser Symbol ist zum Beispiel GOLD (XAUUSD), mit 3 Ziffern. Wir hätten also 3 als Ziffernwert, aber zur Automatisierung und um den Code an die Währungspaare anzupassen, verwenden wir die Funktion, um automatisch die Anzahl der Ziffern des jeweiligen Währungspaares abzurufen. Wenn Sie jedoch einen festen Bereich von Dezimalstellen wünschen, verwenden Sie einen statischen Wert. Hier ist der endgültige Code für diese Preiseinstellung. 

    // --- Update price quotes ---
    
    // Set the text of the "SELL PRICE" label to the current Bid price
    ObjectSetString(0, LABEL_SELL_PRICE, OBJPROP_TEXT, DoubleToString(Bid(), _Digits));

Da wir nun die korrekte Konvertierungslogik haben - vielen Dank an die MQL5-Entwickler für die schöne Funktion - erhalten wir die folgenden Ergebnisse.

KORREKTE DARSTELLUNG DES ANGEBOTSPREISES

Für die Festlegung des Briefkurses (ask) der Kauftaste und des Spreads gilt die gleiche Logik. Hier ist der Code dafür. 

    // --- Update price quotes ---
    
    // Set the text of the "SELL PRICE" label to the current Bid price
    ObjectSetString(0, LABEL_SELL_PRICE, OBJPROP_TEXT, DoubleToString(Bid(), _Digits));
    
    // Set the text of the "BUY PRICE" label to the current Ask price
    ObjectSetString(0, LABEL_BUY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), _Digits));
    
    // Set the text of the "SPREAD" button to the current spread value
    ObjectSetString(0, BTN_SPREAD, OBJPROP_TEXT, (string)Spread());

Sie sollten bemerkt haben, dass wir für die Spanne direkt den Wert der Zeichenkette eingeben, obwohl wir diesen Ansatz zuvor kritisiert haben, um die Genauigkeit zu erhalten. In diesem Fall handelt es sich bei der Spread-Funktion um einen ganzzahligen Datentyp, sodass die Genauigkeit nicht die oberste Priorität hat; in jedem Fall haben wir das richtige Format. Sie könnten jedoch auch die Funktion IntegerToString für die Konvertierung verwenden, was denselben Wert ergeben würde.

    // Set the text of the "SPREAD" button to the current spread value
    ObjectSetString(0, BTN_SPREAD, OBJPROP_TEXT, IntegerToString(Spread()));

Die Funktion benötigt drei Argumente, aber nur der Zielwert ist ausreichend, da das Genauigkeitsformat nicht angegeben wird. Jetzt können Sie den Unterschied erfahren. In einem Graphic Interchange Format (GIF) haben wir Folgendes erreicht.

PREISE GIF

Das ist alles, was wir für die Ereignisbehandlung tun müssen, und der vollständige Quellcode, der für die Aktualisierung der Preise verantwortlich ist, lautet wie folgt:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
    // --- Update price quotes ---
    
    // Set the text of the "SELL PRICE" label to the current Bid price
    ObjectSetString(0, LABEL_SELL_PRICE, OBJPROP_TEXT, DoubleToString(Bid(), _Digits));
    
    // Set the text of the "BUY PRICE" label to the current Ask price
    ObjectSetString(0, LABEL_BUY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), _Digits));
    
    // Set the text of the "SPREAD" button to the current spread value
    ObjectSetString(0, BTN_SPREAD, OBJPROP_TEXT, IntegerToString(Spread()));
}
//+------------------------------------------------------------------+

Nun ist die erste Automatisierungskomponente fertig. Das war doch einfach, oder? Wir fahren dann mit den anderen Komponenten unseres GUI-Panels fort. Die Automatisierung der übrigen Elemente erfolgt innerhalb von OnChartEvent, weshalb wir uns dessen Eingabeparameter und Funktionen genauer ansehen wollen.

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{

        ...

}

Der Zweck der Funktion besteht darin, Chart-Änderungen zu verarbeiten, die entweder von einem Nutzer oder einem MQL5-Programm vorgenommen werden. So werden die Interaktionen des Nutzers, wie z. B. das Bewegen der Maus, das Bearbeiten der Schaltflächenfelder und das Anklicken von Beschriftungen und Schaltflächen, von diesem Ereignis-Handler erfasst und verarbeitet. Wir wollen die Argumente aufschlüsseln, um sie besser interpretieren zu können:

  • id: Dieser Parameter steht für die Ereignis-ID und entspricht einem der 11 vordefinierten Ereignistypen. Dazu gehören Ereignisse wie Tastendruck, Mausbewegungen, Objekterstellung, Chart-Änderungen und nutzerdefinierte Ereignisse. Für nutzerdefinierte Ereignisse können wir die IDs von CHARTEVENT_CUSTOM bis CHARTEVENT_CUSTOM_LAST verwenden. Die 11 Ereignistypen sind wie unten dargestellt;

CHART-EREIGNISTYPEN

  • lparam: Ein Ereignisparameter vom Typ long. Sein Wert hängt von dem jeweiligen Ereignis ab, das behandelt wird. Es könnte zum Beispiel einen Tastencode während eines Tastendrucks darstellen.
  • dparam: Ein Ereignisparameter vom Typ double. Ähnlich wie bei lparam variiert sein Wert je nach Ereignistyp. Bei einer Mausbewegung kann sie beispielsweise die Position des Mauszeigers übermitteln.
  • sparam: Ein Ereignisparameter vom Typ string. Auch hier hängt die Bedeutung vom jeweiligen Ereignis ab. Bei der Objekterstellung könnte er beispielsweise den Namen des neu erstellten Objekts enthalten.

Um dies verständlicher darzustellen, erstellen wir innerhalb der Funktion einen Ausdruck, der alle vier Argumente für das Journal enthält.

// Print the 4 function parameters    
Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);

Diese Funktion druckt die ID des Chart-Ereignisses, den Wert des Ereignisses vom langen Typ, den Wert des Ereignisses vom doppelten Typ und den Wert vom Typ String. Schauen wir uns das folgende GIF an, um die Referenzierung zu erleichtern.

Chart EREIGNISSE GIF

Anhand des mitgelieferten GIFs sollte nun alles klar sein. Wir gehen nun zur Erfassung von Chart-Klickereignissen auf den GUI-Panel-Elementen über. Unsere ID wird also „CHARTEVENT_OBJECT_CLICK“ sein. 

   //Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);

   if (id==CHARTEVENT_OBJECT_CLICK){
        
        ...

   }

Zunächst kommentieren wir die vorherige Codezeile aus, da wir unser Journal nicht mit irrelevanten Informationen zukleistern wollen. Die beiden Schrägstriche (//) werden als einzeilige Kommentare bezeichnet und kommentieren einen Code von ihrem Anfang bis zum Ende der Zeile aus, daher der Name „einzeiliger“ Kommentar. Insbesondere Kommentare werden vom Computer während der Ausführung ignoriert. Wir verwenden die if-Anweisung, um zu prüfen, ob ein Objekt angeklickt wurde. Dies wird erreicht, indem die ID des Chart-Ereignisses mit den Enumeration der Objektklicks gleichgesetzt wird. Wenn wir ein Objekt angeklickt haben, können wir die Argumente ausdrucken und sehen, was wir erhalten. Der folgende Code wird verwendet. 

   if (id==CHARTEVENT_OBJECT_CLICK){
      Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam);

      ...

   }

In der Druckfunktion haben wir „LPARAM“ in „LP“ und „DPARAM“ in „DP“ geändert, sodass wir uns nur auf die ID des Chart-Ereignisses und den Namen des angeklickten Objekts konzentrieren können, von wo aus wir die ID des Objekts abrufen und gegebenenfalls Maßnahmen ergreifen können. Im Folgenden wird die Logik veranschaulicht:

OBJEKT KLICKEN GIF

Die erste Automatisierungskomponente wird bei der Zerstörung des GUI-Panels erscheinen, wenn das Ambulanzsymbol angeklickt wird. Aus dem obigen GIF können Sie ersehen, dass der Name des Objekts in der Variablen vom Typ „string-event“ gespeichert wird, sobald ein Objekt angeklickt wird. Anhand dieser Variablen können wir den Namen des angeklickten Objekts ermitteln und prüfen, ob es sich um das gewünschte Objekt handelt, und wenn ja, können wir Maßnahmen ergreifen, in unserem Fall das Panel zerstören. 

      //--- if icon car is clicked, destroy the panel
      if (sparam==ICON_CAR){
         Print("BTN CAR CLICKED. DESTROY PANEL NOW");
         destroyPanel();
         ChartRedraw(0);
      }

Eine weitere if-Anweisung wird verwendet, um die Instanz zu überprüfen, in der das Auto-Symbol angeklickt wurde. Wenn dies der Fall ist, wird die Instanz informiert, dass es angeklickt wurde, und die Zerstörung des Panels kann durchgeführt werden, da es das richtige Symbol für die Aufgabe ist. Danach rufen wir die Funktion „destroyPanel“ auf, deren Ziel es ist, jedes Element unseres Panels zu löschen. Diese Funktion sollte Ihnen bereits bekannt sein, da wir sie in unserem vorherigen Artikel (Teil 1) verwendet haben. Schließlich rufen wir die Funktion ChartRedraw auf. Die Funktion wird verwendet, um ein Neuzeichnen eines bestimmten Charts zu erzwingen. Wenn wir Charteigenschaften oder -objekte (z. B. Indikatoren, Linien oder Formen) programmatisch ändern, werden die Änderungen möglicherweise nicht sofort im Chart angezeigt. Durch den Aufruf dieser Funktion stellen wir sicher, dass das Chart aktualisiert wird und die neuesten Änderungen anzeigt. In einer visuellen Darstellung sind die Ergebnisse wie folgt zu sehen.

PANEEL ZERSTÖRUNG GIF

Sie sehen, die Logik ist ganz einfach. Die gleiche Methode soll bei den anderen Objektklicks angewendet werden. Fahren wir nun mit dem Ereignis fort, wenn die Schaltfläche „Close“ angeklickt wird. Wenn dies geschieht, müssen wir alle offenen Positionen schließen und alle ausstehenden Aufträge löschen. Auf diese Weise wird sichergestellt, dass wir keine Marktaufträge haben. Eine else-if-Anweisung wird benötigt, um die Bedingung zu prüfen, ob die Schaltfläche „Schließen“ angeklickt wurde.

      else if (sparam == BTN_CLOSE) {
          // Button "Close" clicked. Close all orders and positions now.
          Print("BTN CLOSE CLICKED. CLOSE ALL ORDERS & POSITIONS NOW");
      
          // Store the original color of the button
          long originalColor = ObjectGetInteger(0, BTN_CLOSE, OBJPROP_COLOR);
      
          // Change the button color to red (for visual feedback)
          ObjectSetInteger(0, BTN_CLOSE, OBJPROP_COLOR, clrRed);

          ...

      }

Hier wollen wir der Ereignisinstanz eine kleine Änderung hinzufügen. Wenn die Schaltfläche angeklickt wird, soll die Farbe der Schaltfläche geändert werden, um anzuzeigen, dass die Schaltfläche angeklickt wurde, damit der Prozess zum Schließen der Marktaufträge eingeleitet werden kann. Nach dem vollständigen Schließen müssen wir die Farbe der Schaltfläche auf ihre Standardfarbe zurücksetzen. Um die ursprüngliche Farbe der Schaltflächenbeschriftung zu erhalten, deklarieren wir eine Variable vom Typ long mit dem Namen „originalColor“ und speichern darin die Standardfarbe der Schaltfläche. Um die Farbe der Schaltfläche abzurufen, verwenden wir die Funktion ObjectGetInteger und geben die Chart-ID, den Namen der Schaltfläche und die Eigenschaft der Schaltfläche, in unserem Fall die Farbe, ein. Nachdem wir die ursprüngliche Farbe gespeichert haben, können wir nun die Farbe der Schaltflächenbeschriftung verändern, da wir bereits eine Reserve für den ursprünglichen Wert haben. Wir verwenden ObjectSetInteger, um die Farbe des Objekts auf Rot zu setzen. In diesem Zustand leiten wir das Schließen des Auftrags ein.

          // Iterate through all open positions
          for (int i = 0; i <= PositionsTotal(); i++) {
              ulong ticket = PositionGetTicket(i);
              if (ticket > 0) {
                  if (PositionSelectByTicket(ticket)) {
                      // Check if the position symbol matches the current chart symbol
                      if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
                          obj_Trade.PositionClose(ticket); // Close the position
                      }
                  }
              }
          }

Wir verwenden eine for-Schleife, um alle offenen Positionen zu durchlaufen und sie zu schließen. Um alle offenen Positionen zu erhalten, verwenden wir die integrierte MQL5-Funktion PositionsTotal. Diese Funktion gibt die Anzahl der offenen Positionen dieses Handelskontos zurück. Wir erhalten dann das Ticket dieser Position, indem wir den Index der Position in der Funktion PositionGetTicket angeben und ihn in der Variablen vom Datentyp ulong mit dem Namen „ticket“ speichern. Die Funktion gibt das Ticket an der angegebenen Position zurück, im Falle eines Fehlers wird 0 zurückgegeben. Damit wir fortfahren können, müssen wir sicherstellen, dass wir ein Ticket haben. Dies wird erreicht, indem die if-Anweisung verwendet wird, um sicherzustellen, dass der Wert des Tickets größer als 0 ist. Wenn das der Fall ist, bedeutet das, dass wir ein Ticket haben und wir fahren fort, das Ticket auszuwählen, damit wir damit arbeiten können. Wenn wir das Ticket erfolgreich ausgewählt haben, können wir die Informationen über die Position abrufen. Da auf einem bestimmten Handelskonto mehrere Positionen vorhanden sein können, stellen wir sicher, dass wir nur Positionen schließen, die mit diesem bestimmten Währungspaar verbunden sind. Schließlich schließen wir diese Position anhand der Ticketnummer und fahren mit den anderen offenen Positionen fort, falls vorhanden.

Um die Position zu schließen, verwenden wir jedoch „obj_Trade“, gefolgt von einem Punktoperator. Dies wird als Klassenobjekt bezeichnet. Um den Vorgang des Positionsschließens zu vereinfachen, müssen wir eine Klasseninstanz einfügen, die diesen Vorgang unterstützt. Wir binden also eine Handelsinstanz ein, indem wir #include am Anfang des Quellcodes verwenden. Dadurch erhalten wir Zugriff auf die Klasse CTrade, mit der wir ein Handelsobjekt erstellen können. Dies ist von entscheidender Bedeutung, da wir es für die Handelsgeschäfte benötigen.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

Der Präprozessor wird die Zeile #include <Trade/Trade.mqh> durch den Inhalt der Datei Trade.mqh ersetzen. Die spitzen Klammern zeigen an, dass die Datei Trade.mqh aus dem Standardverzeichnis entnommen wird (normalerweise ist es das Terminal-Installationsverzeichnis\MQL5\Include). Das aktuelle Verzeichnis wird bei der Suche nicht berücksichtigt. Die Zeile kann an beliebiger Stelle im Programm platziert werden, aber in der Regel werden alle Einschlüsse am Anfang des Quellcodes platziert, um den Code besser zu strukturieren und die Referenz zu erleichtern. Die Deklaration des Objekts obj_Trade der Klasse CTrade ermöglicht uns dank der MQL5-Entwickler einen einfachen Zugriff auf die in dieser Klasse enthaltenen Methoden.

CTRADE CLASS

Zum Löschen der ausstehenden Aufträge wird die gleiche Iterationslogik verwendet.

          // Iterate through all pending orders
          for (int i = 0; i <= OrdersTotal(); i++) {
              ulong ticket = OrderGetTicket(i);
              if (ticket > 0) {
                  if (OrderSelect(ticket)) {
                      // Check if the order symbol matches the current chart symbol
                      if (OrderGetString(ORDER_SYMBOL) == _Symbol) {
                          obj_Trade.OrderDelete(ticket); // Delete the order
                      }
                  }
              }
          }

Der Hauptunterschied in der Iterationslogik besteht darin, dass wir die Funktion OrdersTotal verwenden, um die Gesamtzahl der Aufträge zu ermitteln, und alles andere, was mit den Aufträgen verknüpft ist. Nachdem alle Positionen geschlossen und die Aufträge gelöscht wurden, müssen wir die Farbe der Schaltflächenbeschriftung auf ihre ursprüngliche Farbe zurücksetzen. 

          // Reset the button color to its original value
          Print("Resetting button to original color");
          ObjectSetInteger(0, BTN_CLOSE, OBJPROP_COLOR, originalColor);
      
          // Force a redrawing of the chart to reflect the changes
          ChartRedraw(0);

Die Funktion „ObjectSetInteger“ wird verwendet, indem die Chart-ID, der Name der Schaltfläche, die Farbeigenschaft und die Originalfarbe übergeben werden. An dieser Stelle kommt uns nun unsere vorherige Variable sehr gelegen. Wir müssen nicht immer die ursprüngliche Farbe eines Objekts eingeben, sondern können sie automatisch speichern und abrufen. Der vollständige Code, der für die Schließung aller offenen Positionen und die Löschung aller offenen Aufträge verantwortlich ist, lautet wie folgt:

      else if (sparam == BTN_CLOSE) {
          // Button "Close" clicked. Close all orders and positions now.
          Print("BTN CLOSE CLICKED. CLOSE ALL ORDERS & POSITIONS NOW");
      
          // Store the original color of the button
          long originalColor = ObjectGetInteger(0, BTN_CLOSE, OBJPROP_COLOR);
      
          // Change the button color to red (for visual feedback)
          ObjectSetInteger(0, BTN_CLOSE, OBJPROP_COLOR, clrRed);
      
          // Iterate through all open positions
          for (int i = 0; i <= PositionsTotal(); i++) {
              ulong ticket = PositionGetTicket(i);
              if (ticket > 0) {
                  if (PositionSelectByTicket(ticket)) {
                      // Check if the position symbol matches the current chart symbol
                      if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
                          obj_Trade.PositionClose(ticket); // Close the position
                      }
                  }
              }
          }
      
          // Iterate through all pending orders
          for (int i = 0; i <= OrdersTotal(); i++) {
              ulong ticket = OrderGetTicket(i);
              if (ticket > 0) {
                  if (OrderSelect(ticket)) {
                      // Check if the order symbol matches the current chart symbol
                      if (OrderGetString(ORDER_SYMBOL) == _Symbol) {
                          obj_Trade.OrderDelete(ticket); // Delete the order
                      }
                  }
              }
          }
      
          // Reset the button color to its original value
          Print("Resetting button to original color");
          ObjectSetInteger(0, BTN_CLOSE, OBJPROP_COLOR, originalColor);
      
          // Force a redrawing of the chart to reflect the changes
          ChartRedraw(0);
      }

Es wird immer empfohlen, nach jeder Hinzufügung von Logik zu einem Bedienfeld den Code zu kompilieren und auszuführen, um sicherzustellen, dass alles wie erwartet funktioniert, bevor man zu einer anderen Steuerlogik übergeht. Das haben wir bis jetzt erreicht.

CLOSE GIF

Jetzt können wir alle Positionen und Aufträge erfolgreich schließen. Wenn Sie auf die Schaltfläche „Close“ klicken, während Positionen geschlossen werden, bleibt die Farbe der Beschriftung der Schaltfläche rot, bis alle Positionen geschlossen sind, und nimmt anschließend wieder ihre ursprüngliche Farbe an. Auch hier können Sie sehen, dass wir die Kaufposition „AUDUSD“ nicht schließen, da der Expert Advisor (EA) derzeit mit dem Gold-Symbol verbunden ist. Jetzt kann dieselbe Logik verwendet werden, um die Reaktionsfähigkeit der anderen Schaltflächenbeschriftungen einzustellen. 

      else if (sparam == BTN_MARKET) {
          // Button "Market" clicked. Close all positions related to the current chart symbol.
          Print(sparam + " CLICKED. CLOSE ALL POSITIONS NOW");
      
          // Iterate through all open positions
          for (int i = 0; i <= PositionsTotal(); i++) {
              ulong ticket = PositionGetTicket(i);
              if (ticket > 0) {
                  if (PositionSelectByTicket(ticket)) {
                      // Check if the position symbol matches the current chart symbol
                      if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
                          obj_Trade.PositionClose(ticket); // Close the position
                      }
                  }
              }
          }
      
          // Force a redrawing of the chart to reflect the changes
          ChartRedraw(0);
      }

Der Unterschied zwischen diesem Code und dem Code der Schaltfläche „Close“ besteht darin, dass wir die Iteration der Auftragsschließung loswerden, da wir nur alle geöffneten Positionen schließen wollen. Um alle Positionen, die im Gewinn sind, zu schließen, wird das folgende Codeschnipsel verwendet.

      else if (sparam == BTN_PROFIT) {
          // Button "Profit" clicked. Close all positions in profit now.
          Print(sparam + " CLICKED. CLOSE ALL POSITIONS IN PROFIT NOW");
      
          // Iterate through all open positions
          for (int i = 0; i <= PositionsTotal(); i++) {
              ulong ticket = PositionGetTicket(i);
              if (ticket > 0) {
                  if (PositionSelectByTicket(ticket)) {
                      // Check if the position symbol matches the current chart symbol
                      if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
                          double profit_or_loss = PositionGetDouble(POSITION_PROFIT);
                          if (profit_or_loss > 0) {
                              obj_Trade.PositionClose(ticket); // Close the position
                          }
                      }
                  }
              }
          }
      
          // Force a redrawing of the chart to reflect the changes
          ChartRedraw(0);
      }

Der Hauptunterschied zwischen diesem Codeschnipsel und dem vorherigen, der alle geöffneten Positionen schließen soll, besteht darin, dass wir eine zusätzliche Logik hinzufügen, um zu prüfen, ob der Gewinn der Position über Null liegt, was bedeutet, dass wir nur die Positionen schließen, die im Gewinn sind. Im Folgenden wird die spezifische Logik erläutert:

                          double profit_or_loss = PositionGetDouble(POSITION_PROFIT);
                          if (profit_or_loss > 0) {
                              obj_Trade.PositionClose(ticket); // Close the position
                          }

Wir definieren eine Variable vom Typ Double mit dem Namen „profit_or_loss“ und speichern darin den aktuellen gleitenden Gewinn oder Verlust der ausgewählten Position. Wenn der Wert größer als 0 ist, schließen wir die Position, da sie bereits im Gewinn ist. Die gleiche Logik wird auf die Verlust-Schaltfläche übertragen, wobei eine Position nur geschlossen wird, wenn sie im Verlust ist. 

      else if (sparam == BTN_LOSS) {
          // Button "Loss" clicked. Close all positions in loss now.
          Print(sparam + " CLICKED. CLOSE ALL POSITIONS IN LOSS NOW");
      
          // Iterate through all open positions
          for (int i = 0; i <= PositionsTotal(); i++) {
              ulong ticket = PositionGetTicket(i);
              if (ticket > 0) {
                  if (PositionSelectByTicket(ticket)) {
                      // Check if the position symbol matches the current chart symbol
                      if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
                          double profit_or_loss = PositionGetDouble(POSITION_PROFIT);
                          if (profit_or_loss < 0) {
                              obj_Trade.PositionClose(ticket); // Close the position
                          }
                      }
                  }
              }
          }
      
          // Force a redrawing of the chart to reflect the changes
          ChartRedraw(0);
      }

Um die ausstehenden Aufträge zu schließen, wenn das Etikett der Schaltfläche „Pend‘n“ angeklickt wird, wird die Iteration der Aufträge verwendet, deren Code wie folgt aussieht.

      else if (sparam == BTN_PENDING) {
          // Button "Pending" clicked. Delete all pending orders related to the current chart symbol.
          Print(sparam + " CLICKED. DELETE ALL PENDING ORDERS NOW");
      
          // Iterate through all pending orders
          for (int i = 0; i <= OrdersTotal(); i++) {
              ulong ticket = OrderGetTicket(i);
              if (ticket > 0) {
                  if (OrderSelect(ticket)) {
                      // Check if the order symbol matches the current chart symbol
                      if (OrderGetString(ORDER_SYMBOL) == _Symbol) {
                          obj_Trade.OrderDelete(ticket); // Delete the order
                      }
                  }
              }
          }
      
          // Force a redrawing of the chart to reflect the changes
          ChartRedraw(0);
      }

Unten sehen Sie die Visualisierung der Meilensteine.

ALL CLOSE BUTTONS GIF

Wie in der Abbildung zu sehen ist, reagieren unsere Schaltflächen in der Kopfzeile nun auf Anklicken. Wir gehen nun dazu über, die Taste für das Handelsvolumen mit Leben zu füllen. Wir möchten, dass, wenn entweder die Schaltfläche oder das Etikett selbst angeklickt wird oder wenn das Dropdown-Symbol angeklickt wird, ein weiteres Unterfenster mit den verschiedenen Optionen erstellt wird, aus denen der Nutzer wählen kann. Die Logik ist wie folgt:

      else if (sparam == BTN_LOTS || sparam == LABEL_LOTS || sparam == ICON_DROP_DN1) {
          // Button "Lots," label "Lots," or dropdown icon clicked. Create a dropdown list.
          Print(sparam + " CLICKED. CREATE A DROPDOWN LIST");
      
          // Enable the button for dropdown functionality
          ObjectSetInteger(0, BTN_LOTS, OBJPROP_STATE, true);
      
          // Create the dropdown list
          createDropDown();
      
          // Redraw the chart to reflect the changes
          ChartRedraw(0);
      }

Sobald die Schaltfläche angeklickt wird, informieren wir die Instanz und setzen den Zustand der Schaltfläche auf true. Dadurch wird die Schaltfläche dunkler, was anzeigt, dass die Schaltfläche angeklickt wurde. Sobald dies geschehen ist, erstellen wir die Dropdown-Liste, indem wir die nutzerdefinierte Funktion „createDropDown“ aufrufen, deren Codeschnipsel bereits im ersten Artikel bereitgestellt wurde. Sobald die Erstellung abgeschlossen ist, muss der Nutzer eine der Optionen auswählen. Wenn also eine Option durch Anklicken ausgewählt wird, müssen wir die Beschriftung der Schaltfläche erfassen und auf die Wahl des Nutzers einstellen sowie die Dropdown-Liste des Optionsfeldes löschen. Dies erreichen wir mit dem folgenden Codeschnipsel.

      else if (sparam == LABEL_OPT1) {
          // Label "Lots" clicked.
          Print("LABEL LOTS CLICKED");
      
          // Get the text from LABEL_OPT1
          string text = ObjectGetString(0, LABEL_OPT1, OBJPROP_TEXT);
      
          // Get the state of the button (enabled or disabled)
          bool btnState = ObjectGetInteger(0, BTN_LOTS, OBJPROP_STATE);
      
          // Set the text of LABEL_LOTS to match LABEL_OPT1
          ObjectSetString(0, LABEL_LOTS, OBJPROP_TEXT, text);
      
          // Destroy the dropdown list
          destroyDropDown();
      
          // If the button was previously enabled, disable it
          if (btnState == true) {
              ObjectSetInteger(0, BTN_LOTS, OBJPROP_STATE, false);
          }
      
          // Redraw the chart
          ChartRedraw(0);
      }

Zunächst wird geprüft, ob die erste Option angeklickt wurde. Ist dies der Fall, wird der Textwert der ausgewählten Option ermittelt und auf den Textwert der Schaltfläche für das Handelsvolumen gesetzt. Wir verwenden die nutzerdefinierte Funktion „destroyDropDown“, um das erstellte Sup-Panel loszuwerden, nachdem wir die Auswahl des Nutzers auf den Zustand der Schaltfläche gesetzt haben, deren Codeschnipsel wie folgt lautet.

//+------------------------------------------------------------------+
//|    Function to destroy dropdown                                  |
//+------------------------------------------------------------------+

void destroyDropDown(){
   ObjectDelete(0,BTN_DROP_DN);
   ObjectDelete(0,LABEL_OPT1);
   ObjectDelete(0,LABEL_OPT2);
   ObjectDelete(0,LABEL_OPT3);
   ObjectDelete(0,ICON_DRAG);
   ChartRedraw(0);
}

Schließlich wird geprüft, ob der Zustand der Schaltfläche zuvor aktiviert war, d. h., ob sie angeklickt wurde, und wenn ja, wird sie deaktiviert, indem die Eigenschaft state auf false gesetzt wird. Die gleiche Logik wird auch bei den Optionen angewandt. Deren Codeschnipsel ist unten aufgeführt:

      else if (sparam==LABEL_OPT2){
         Print("LABEL RISK % CLICKED");
         string text = ObjectGetString(0,LABEL_OPT2,OBJPROP_TEXT);
         bool btnState = ObjectGetInteger(0,BTN_LOTS,OBJPROP_STATE);
         ObjectSetString(0,LABEL_LOTS,OBJPROP_TEXT,text);
         destroyDropDown();
         if (btnState==true){
            ObjectSetInteger(0,BTN_LOTS,OBJPROP_STATE,false);
         }
         ChartRedraw(0);
      }
      else if (sparam==LABEL_OPT3){
         Print("LABEL MONEY CLICKED");
         string text = ObjectGetString(0,LABEL_OPT3,OBJPROP_TEXT);
         bool btnState = ObjectGetInteger(0,BTN_LOTS,OBJPROP_STATE);
         ObjectSetString(0,LABEL_LOTS,OBJPROP_TEXT,text);
         destroyDropDown();
         if (btnState==true){
            ObjectSetInteger(0,BTN_LOTS,OBJPROP_STATE,false);
         }
         ChartRedraw(0);
      }

Wenn die seitlichen Schaltflächen, d. h. die Schaltflächen zum Erhöhen und Verringern, angeklickt werden, müssen wir sie reaktionsfähig machen, indem wir den Wert des jeweiligen Eingabefeldes erhöhen oder verringern. Werfen wir zunächst einen Blick auf die Schaltfläche zur Erhöhung des Handelsvolumens.

      else if (sparam == BTN_P1) {
          // Button "P1" clicked. Increase trading volume.
          Print(sparam + " CLICKED. INCREASE TRADING VOLUME");
      
          // Get the current trading volume from EDIT_LOTS
          double trade_lots = (double)ObjectGetString(0, EDIT_LOTS, OBJPROP_TEXT);
      
          // Increment the trading volume by 0.01
          trade_lots += 0.01;
      
          // Update the value in EDIT_LOTS
          ObjectSetString(0, EDIT_LOTS, OBJPROP_TEXT, DoubleToString(trade_lots, 2));
      
          // Redraw the chart
          ChartRedraw(0);
      }

Wenn die Schaltfläche zum Erhöhen des Handelsvolumens angeklickt wird, informieren wir die Instanz und bereiten uns darauf vor, den Wert des Feldes „Lots“ zu erhöhen, indem wir seinen aktuellen Wert abrufen. Zu dem ermittelten Handelsvolumen addieren wir 0,01 als inkrementellen Schrittwert. Der Operator „+=“ wird zur Vereinfachung des Prozesses verwendet. In der Regel wird der Wert der Losgröße um 0,01 erhöht. Das ist dasselbe, als würde man sagen (trade_lots = trade_lots + 0,01). Das Ergebnis wird dann in das Feld des Loses übertragen. Der double-Wert wird in eine Zeichenkette umgewandelt und mit einer Genauigkeit von 2 Ziffern versehen. Die gleiche Logik gilt für die Schaltfläche zum Verringern, nur dass wir jetzt 0,01 von dem Wert abziehen müssen. 

      else if (sparam == BTN_M1) {
          // Button "M1" clicked. Decrease trading volume.
          Print(sparam + " CLICKED. DECREASE TRADING VOLUME");
      
          // Get the current trading volume from EDIT_LOTS
          double trade_lots = (double)ObjectGetString(0, EDIT_LOTS, OBJPROP_TEXT);
      
          // Decrease the trading volume by 0.01
          trade_lots -= 0.01;
      
          // Update the value in EDIT_LOTS
          ObjectSetString(0, EDIT_LOTS, OBJPROP_TEXT, DoubleToString(trade_lots, 2));
      
          // Redraw the chart
          ChartRedraw(0);
      }

Die gleiche Logik gilt für die anderen ähnlichen Schaltflächen.

      else if (sparam==BTN_P2){
         Print(sparam+" CLICKED. INCREASE STOP LOSS POINTS");
         double sl_points = (double)ObjectGetString(0,EDIT_SL,OBJPROP_TEXT);
         sl_points+=10.0;
         ObjectSetString(0,EDIT_SL,OBJPROP_TEXT,DoubleToString(sl_points,1));
         ChartRedraw(0);
      }
      else if (sparam==BTN_M2){
         Print(sparam+" CLICKED. DECREASE STOP LOSS POINTS");
         double sl_points = (double)ObjectGetString(0,EDIT_SL,OBJPROP_TEXT);
         sl_points-=10.0;
         ObjectSetString(0,EDIT_SL,OBJPROP_TEXT,DoubleToString(sl_points,1));
         ChartRedraw(0);
      }
      
      else if (sparam==BTN_P3){
         Print(sparam+" CLICKED. INCREASE STOP LOSS POINTS");
         double tp_points = (double)ObjectGetString(0,EDIT_TP,OBJPROP_TEXT);
         tp_points+=10.0;
         ObjectSetString(0,EDIT_TP,OBJPROP_TEXT,DoubleToString(tp_points,1));
         ChartRedraw(0);
      }
      else if (sparam==BTN_M3){
         Print(sparam+" CLICKED. DECREASE STOP LOSS POINTS");
         double tp_points = (double)ObjectGetString(0,EDIT_TP,OBJPROP_TEXT);
         tp_points-=10.0;
         ObjectSetString(0,EDIT_TP,OBJPROP_TEXT,DoubleToString(tp_points,1));
         ChartRedraw(0);
      }

Hier legen wir unseren Schrittweite auf 10 Punkte für Stop-Loss und Take-Profit fest. Um festzustellen, ob wir auf dem richtigen Weg sind, stellen wir die Ergebnisse im Folgenden zusammen und visualisieren sie.

DROPDOWN INC DEC BUTTONS

Bis zu diesem Punkt sind die Fortschritte gut. Die anderen verbleibenden Schaltflächen sind die für „Sell“ und „Buy“. Ihre Logik ist ebenfalls recht einfach und folgt der vorherigen Logik. Für die Verkaufstaste gilt die folgende Logik.

      else if (sparam==BTN_SELL){
         Print("BTN SELL CLICKED");
         ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false);
         double trade_lots = (double)ObjectGetString(0,EDIT_LOTS,OBJPROP_TEXT);
         double sell_sl = (double)ObjectGetString(0,EDIT_SL,OBJPROP_TEXT);
         sell_sl = Ask()+sell_sl*_Point;
         sell_sl = NormalizeDouble(sell_sl,_Digits);
         double sell_tp = (double)ObjectGetString(0,EDIT_TP,OBJPROP_TEXT);
         sell_tp = Ask()-sell_tp*_Point;
         sell_tp = NormalizeDouble(sell_tp,_Digits);

         Print("Lots = ",trade_lots,", SL = ",sell_sl,", TP = ",sell_tp);
         obj_Trade.Sell(trade_lots,_Symbol,Bid(),sell_sl,sell_tp);
         ChartRedraw();
      }

Wenn das Klick-Ereignis auf die Schaltfläche „Sell“ zutrifft, informieren wir über die Instanz und setzen den Zustand der Schaltfläche auf „false“, was bedeutet, dass wir die Klickoption aktiviert haben. Um eine Verkaufsposition zu eröffnen, benötigen wir das Handelsvolumen und in Punkten Stop-Loss und Take-Profit. Wir erhalten diese Werte und speichern sie in bestimmten Variablen, um sie leichter abrufen zu können. Um den Stop-Loss zu berechnen, werden die Stop-Loss in Punkten in das Preisformat des kompatiblen Währungspaares umgewandelt, indem sie mit _Point multipliziert werden und der resultierende Wert zum aktuellen Briefkurs addiert wird. Später normalisieren wir den doppelten Ausgabewert auf die Ziffern des Symbols, um Genauigkeit und Präzision zu gewährleisten. Schließlich eröffnen wir eine Verkaufsposition, indem wir die Handelslose, den Geldkurs als Verkaufspreis, den Stop-Loss und den Take-Profit eingeben. Die gleiche Logik gilt für eine Kaufposition, und die Logik ist wie folgt.

      else if (sparam==BTN_BUY){
         Print("BTN BUY CLICKED");
         ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false);
         double trade_lots = (double)ObjectGetString(0,EDIT_LOTS,OBJPROP_TEXT);
         double buy_sl = (double)ObjectGetString(0,EDIT_SL,OBJPROP_TEXT);
         buy_sl = Bid()-buy_sl*_Point;
         buy_sl = NormalizeDouble(buy_sl,_Digits);
         double buy_tp = (double)ObjectGetString(0,EDIT_TP,OBJPROP_TEXT);
         buy_tp = Bid()+buy_tp*_Point;
         buy_tp = NormalizeDouble(buy_tp,_Digits);

         Print("Lots = ",trade_lots,", SL = ",buy_sl,", TP = ",buy_tp);
         obj_Trade.Buy(trade_lots,_Symbol,Ask(),buy_sl,buy_tp);
         ChartRedraw();
      }

Nach dem Test sind die Ergebnisse wie folgt:

BUY SELL GIF

Bis jetzt läuft alles so, wie wir es erwartet haben. Der Nutzer könnte sich dafür entscheiden, die Schaltflächen zum Erhöhen und Verringern nicht zu verwenden, sondern stattdessen direkt auf die Bearbeitungsoptionen in den Feldern der Bearbeitungsschaltflächen zurückzugreifen. Dabei können bei der Bearbeitung unvorhergesehene Fehler passieren, die dazu führen, dass Vorgänge ignoriert werden. Der Nutzer könnte zum Beispiel eine Losgröße von „0,Q7“ eingeben. Technisch gesehen ist dieser Wert nicht ganz eine Zahl, da er den Buchstaben „Q“ enthält. Infolgedessen wird kein Handel unter der Losgröße durchgeführt. So können wir uns vergewissern, dass der Wert immer gültig ist, und wenn dies nicht der Fall ist, eine Instanz des Fehlers auffordern, korrigiert zu werden. Dazu wird eine weitere Chart-Ereigniskennung „CHARTEVENT_OBJECT_ENDEDIT“ verwendet. 

   else if (id==CHARTEVENT_OBJECT_ENDEDIT){
      if (sparam==EDIT_LOTS){
         Print(sparam+" WAS JUST EDITED. CHECK FOR ANY UNFORESEEN ERRORS");
         string user_lots = ObjectGetString(0,EDIT_LOTS,OBJPROP_TEXT);

         ...   

      }
   }

Zunächst wird geprüft, ob die ID des Chart-Ereignisses eine Endbearbeitung eines Bearbeitungsfeldes ist. Wenn ja, prüfen wir, ob es sich bei dem Eingabefeld um die Schaltfläche für das Handelsvolumen handelt, und wenn ja, informieren wir über den Fall und rufen den vom Nutzer eingegebenen Wert zur weiteren Analyse möglicher unvorhergesehener Fehler ab. Die Eingabe wird in einer String-Variablen namens „user_lots“ gespeichert. Für die Analyse müssen wir die Losgröße in Teile aufteilen, wobei unsere Grenze durch das Zeichen Punkt (.) definiert wird, das oft auch als Punkt, Punkt oder Punkt bezeichnet wird.

         string lots_Parts_Array[];
         int splitCounts = StringSplit(user_lots,'.',lots_Parts_Array);//rep '.' = 'a' 
      
         Print("User lots split counts = ",splitCounts);ArrayPrint(lots_Parts_Array,0,"<&> ");

Wir definieren ein dynamisches Speicherarray der aufgeteilten Teile als Variable vom Datentyp String mit dem Namen „lots_Parts_Array“. Dann teilen wir die Nutzereingabe mit Hilfe der Funktion StringSplit auf, die 3 Argumente benötigt. Wir geben den aufzuteilenden Zielwert der Zeichenkette an, in diesem Fall die Eingabe der Losgröße des Nutzers, dann den Punkt als Trennzeichen und schließlich ein Speicherfeld für die resultierenden Teilzeichenfolgen. Die Funktion gibt die Anzahl der Teilzeichenfolgen im Speicherfeld zurück. Wenn das angegebene Trennzeichen in der übergebenen Zeichenkette nicht gefunden wird, wird nur eine Quellzeichenkette in das Array eingefügt. Diese Split-Zahlen werden in der Variable Split-Zahl gespeichert. Schließlich wird das Ergebnis der Split-Zählungen sowie die Array-Werte, d. h. die resultierenden Teilzeichenfolgen, ausgedruckt. Wenn wir die Losgröße auf 0,05 ändern, erhalten wir folgendes Ergebnis:

EDIT LOTS SPLIT

Damit der Eingabewert gültig ist, sollte ein Punkt als Trennzeichen vorhanden sein, was zu zwei getrennten Zählungen führen sollte. Wenn ja, bedeutet dies, dass die Eingabe ein einperiodiges Trennzeichen hat.

         if (splitCounts == 2){

            ...

         }

Wenn die Anzahl der Teilungen gleich 1 ist, bedeutet dies, dass die Eingabe keinen Punkt enthält und daher nicht akzeptiert werden kann. In diesem Fall informieren wir über den Fehler und setzen eine boolesche Variable namens „isInputValid“ auf false.

         else if (splitCounts == 1){
            Print("ERROR: YOUR INPUT MUST CONTAIN DECIMAL POINTS");
            isInputValid = false;
         }

Wenn keine der beiden Bedingungen erfüllt ist, bedeutet dies, dass die Eingabe mehr als ein Perioden-Trennzeichen hat, was falsch ist, und so fahren wir fort, den Fehler zu melden und das Gültigkeits-Flag der Eingabe auf falsch zu setzen.

         else {
            Print("ERROR: YOU CAN NOT HAVE MORE THAN ONE DECIMAL POINT IN INPUT");
            isInputValid = false;
         }

Wenn wir einen ungültigen Wert mit 2-Perioden-Trennzeichen eingeben, ist dies die Ausgabe, die wir für das Journal des Experten erhalten.

2 PERIODS IN INPUT

Um zu prüfen, ob es sich bei den eingegebenen Zeichen um nicht-numerische Zeichen handelt, müssen wir eine Schleife durch jeden der beiden Splits ziehen und jedes Zeichen einzeln bewerten. Eine for-Schleife wird benötigt, um dies mit Leichtigkeit zu erreichen.

            if (StringLen(lots_Parts_Array[0]) > 0){
            
               //
... 

            }

Zunächst stellen wir sicher, dass die erste Zeichenkette bei Index 0 im Speicherfeld nicht leer ist, was der Fall ist, wenn die Länge der Zeichenkette größer als 0 ist. Es wird die Funktion StringLen wird verwendet, um die Anzahl der Symbole in der Zeichenkette zu ermitteln. Wenn die Anzahl der Symbole in der Zeichenkette kleiner oder gleich 0 ist, bedeutet dies, dass die Teilzeichenkette leer ist und der Eingabewert bereits ungültig ist.

            else {
               Print("ERROR: PART 1 (LEFT HAND SIDE) IS EMPTY");
               isInputValid = false;
            }

Zur Veranschaulichung des Fehlers sehen Sie unten, was wir erhalten, wenn wir den linken Teil des Trennzeichens leer lassen.

LINKER TEIL LEER

Um zu prüfen, ob es sich um nicht-numerische Zeichen handelt, verwenden wir eine for-Schleife (siehe unten).

               string split = lots_Parts_Array[0];
               for (int i=0; i<StringLen(split); i++){
                  ushort symbol_code = StringGetCharacter(split,i);
                  string character = StringSubstr(split,i,1);
                  if (!(symbol_code >= 48 && symbol_code <= 57)){
                     Print("ERROR: @ index ",i+1," (",character,") is NOT a numeral. Code = ",symbol_code);
                     isInputValid = false;
                     break;
                  }
               }

Wir definieren eine String-Variable namens „split“, in der wir unsere erste Teilzeichenkette im Speicherarray speichern. Dann wird über alle Zeichen in der Teilzeichenkette iteriert. Für das ausgewählte Zeichen erhalten wir den Zeichencode mit Hilfe der Funktion StringGetCharacter, einer Funktion, die den Wert eines Symbols zurückgibt, das sich an der angegebenen Position einer Zeichenkette befindet, und den Symbolcode in einer vorzeichenlosen Kurzvariablen namens „symbol_code“ speichert. Um das eigentliche Symbolzeichen zu erhalten, verwenden wir die String-Substring-Funktion. Schließlich wird mit einer if-Anweisung geprüft, ob der resultierende Code zu den Zahlencodes gehört, und wenn nicht, bedeutet dies, dass es sich um ein Nicht-Zahlenzeichen handelt. Also informieren wir über den Fehler, setzen das Gültigkeits-Flag der Eingabe auf false und brechen die Schleife vorzeitig ab. Wenn nicht, bedeutet dies, dass alle Zeichen Zahlenwerte sind und die Gültigkeit unserer Eingabe immer noch wahr sein wird, da die Initialisierung auf.

         bool isInputValid = true;

Sie haben vielleicht bemerkt, dass der Zahlenbereich zwischen 48 und 57 als Zahlencodebereich gilt. Nun, wir wollen sehen, warum. Gemäß der ASCII-Tabelle haben diese Zahlensymbole ein dezimales Nummerierungssystem, das mit 48 für das Symbol „0“ beginnt und bis 57 für das Symbol „9“ reicht.

SYMBOL CODES 1

Eine Fortsetzung ist wie folgt.

SYMBOL CODES 2

Die gleiche Logik gilt für den zweiten Teil der geteilten Zeichenfolge, d. h. die Teilzeichenfolge rechts vom Trennzeichen. Der Quellcode lautet wie folgt.

            if (StringLen(lots_Parts_Array[1]) > 0){
               string split = lots_Parts_Array[1];
               for (int i=0; i<StringLen(split); i++){
                  ushort symbol_code = StringGetCharacter(split,i);
                  string character = StringSubstr(split,i,1);
                  if (!(symbol_code >= 48 && symbol_code <= 57)){
                     Print("ERROR: @ index ",i+1," (",character,") is NOT a numeral. Code = ",symbol_code);
                     isInputValid = false;
                     break;
                  }
               }

            }
            else {
               Print("ERROR: PART 2 (RIGHT HAND SIDE) IS EMPTY");
               isInputValid = false;
            }

Zur Vergewisserung, dass wir zwischen einem numerischen und einem nicht-numerischen Symbolzeichen unterscheiden können, wollen wir uns ein Beispiel nehmen. 

NON-NUMERAL INPUT

Sie sehen, dass wir beim Hinzufügen eines Großbuchstabens „A“, dessen Code 65 ist, einen Fehler zurückgeben, der anzeigt, dass die Eingabe ungültig ist. Wir haben in diesem Beispiel „A“ verwendet, da sein Symbolcode in den bereitgestellten Bildern leicht referenziert werden kann. Es könnte auch etwas anderes sein. Nun verwenden wir wieder unser Gültigkeits-Flag der Eingabe, um den gültigen Textwert für das betreffende Eingabefeld festzulegen. 

         if (isInputValid == true){
            Print("SUCCESS: INPUT IS VALID.");
            ObjectSetString(0,EDIT_LOTS,OBJPROP_TEXT,user_lots);
            ObjectSetInteger(0,EDIT_LOTS,OBJPROP_COLOR,clrBlack);
            ObjectSetInteger(0,EDIT_LOTS,OBJPROP_BGCOLOR,clrWhite);
            ChartRedraw(0);
         }

Wenn das Gültigkeits-Flag der Eingabe „true“ ist, melden wir den Erfolg und setzen den Textwert als die ursprüngliche Nutzereingabe, da sie keine Unstimmigkeiten aufweist. Wir setzen die Farbe des Textes wieder auf Schwarz und die Hintergrundfarbe der Schaltfläche auf Weiß. Dies sind in der Regel die ursprünglichen Eigenschaften des Bearbeitungsfeldes. Wenn die Ausgabe falsch ist, bedeutet dies, dass der vom Nutzer eingegebene Wert fehlerhaft war und nicht für Handelsoperationen verwendet werden kann.

         else if (isInputValid == false){
            Print("ERROR: INPUT IS INVALID. ENTER A VALID INPUT!");
            ObjectSetString(0,EDIT_LOTS,OBJPROP_TEXT,"Error");
            ObjectSetInteger(0,EDIT_LOTS,OBJPROP_COLOR,clrWhite);
            ObjectSetInteger(0,EDIT_LOTS,OBJPROP_BGCOLOR,clrRed);
            ChartRedraw(0);
         }

Wir informieren daher über den Fehler und setzen den Textwert auf „Fehler“. Um die ultimative Aufmerksamkeit des Nutzers zu erregen, haben wir die Textfarbe auf weiß und die Hintergrundfarbe auf rot gesetzt, eine auffällige Farbkombination, an der der Nutzer leicht erkennen kann, dass ein Fehler vorliegt. Nach der Kompilierung erhalten wir die folgenden Ergebnisse.

USER INPUT GIF

Bis zu diesem Punkt ist die Automatisierung der meisten Komponenten der Schalttafel abgeschlossen. Die einzigen, die nicht berücksichtigt werden, sind die Bewegung der Dropdown-Liste und der Hover-Effekt der Maus auf einer Schaltfläche. All dies muss berücksichtigt werden, wenn eine Mausbewegung auf dem Chart stattfindet, und daher wird die Ereignis-ID „CHARTEVENT_MOUSE_MOVE“ berücksichtigt. Um die Bewegung der Maus zu verfolgen, müssen wir die Logik zur Erkennung von Mausbewegungen auf dem Chart in der Experteninitialisierungsinstanz aktivieren, was durch die folgende Logik erreicht wird. 

   //--- enable CHART_EVENT_MOUSE_MOVE detection
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);

Beginnen wir mit dem einfachsten, nämlich dem Schwebeeffekt. Wir erhalten das Ereignis, wenn sich die Maus im Chart bewegt, nachdem wir ihre Erkennung aktiviert haben. 

   else if (id==CHARTEVENT_MOUSE_MOVE){

      ...

   }

Um die Position der Maus innerhalb des Charts zu ermitteln, benötigen wir ihre Ordinaten, d. h. ihre Position entlang der x- bzw. y-Achse, sowie ihren Zustand, d. h. ob sie sich bewegt oder stillsteht.

      int mouse_X = (int)lparam;    // mouseX   >>> mouse coordinates
      int mouse_Y = (int)dparam;    // mouseY   >>> mouse coordinates
      int mouse_State = (int)sparam; // Get the mouse state (0 = mouse moving)

Hier deklarieren wir eine Integer-Variable vom Typ „mouse_X“, um den Abstand der Maus entlang der x-Achse zu speichern, bzw. entlang der Datums- und Zeitskala. Auch hier erhalten wir den Double-Parameter und speichern seinen Wert im Parameter „mouse_Y“ und schließlich den String-Parameter in der Variablen „mouse_State“. Am Ende werden sie in Ganzzahlen umgewandelt. Wir benötigen die Anfangskoordinaten des Zielelements und definieren sie daher mit dem folgenden Codeschnipsel.

      //GETTING THE INITIAL DISTANCES AND SIZES OF BUTTON
      
      int XDistance_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_XDISTANCE);
      int YDistance_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_YDISTANCE);
      int XSize_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_XSIZE);
      int YSize_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_YSIZE);
      

Wir ermitteln die jeweiligen Tastenabstände und -größen und speichern sie in den entsprechenden Integer-Variablen. Das Typecasting-Format wird verwendet, um den Wert in Ganzzahlformate umzuwandeln. Um die Mauskoordinaten über die betreffende Schaltfläche zu verfolgen, benötigen wir einige Variablen, die die Logik enthalten.

      static bool prevMouseInside = false;
      bool isMouseInside = false;

Die statische boolesche Variable „prevMouseInside“ wird deklariert, um zu verfolgen, ob die Maus zuvor innerhalb des Schaltflächenbereichs war. Die boolesche Variable „isMouseInside“ speichert den aktuellen Status der Maus über die Schaltfläche, und alle Variablen werden mit dem Wert „false“ initialisiert. Um festzustellen, ob sich die Maus innerhalb des Schaltflächenbereichs befindet, verwenden wir eine bedingte Anweisung.

      if (mouse_X >= XDistance_Hover_Btn && mouse_X <= XDistance_Hover_Btn + XSize_Hover_Btn &&
          mouse_Y >= YDistance_Hover_Btn && mouse_Y <= YDistance_Hover_Btn + YSize_Hover_Btn){
         isMouseInside = true;
      }

Die bedingte Prüfung stellt fest, ob sich der Mauszeiger gerade im Bereich der Schaltfläche befindet. Ist dies der Fall, wird „isMouseInside“ auf „true“ gesetzt, was bedeutet, dass sich die Maus innerhalb des Cursors befindet; andernfalls wird die boolesche Variable auf „false“ gesetzt, wenn die Bedingungen nicht erfüllt sind. Technisch gesehen müssen vier Bedingungen erfüllt sein, damit der Mauszeiger als innerhalb des Schaltflächenbereichs befindlich gilt. Lassen Sie uns zum besseren Verständnis die einzelnen Bedingungen aufschlüsseln.

  • mouse_X >= XDistance_Hover_Btn: Es wird geprüft, ob die X-Koordinate der Maus (mouse_X) größer oder gleich der linken Begrenzung der Schaltfläche (XDistance_Hover_Btn) ist.
  • mouse_X <= XDistance_Hover_Btn + XSize_Hover_Btn: Es wird geprüft, ob die X-Koordinate der Maus kleiner oder gleich der rechten Begrenzung der Schaltfläche ist (Summe aus XDistance_Hover_Btn und Schaltflächenbreite XSize_Hover_Btn).
  • mouse_Y >= YDistance_Hover_Btn: In ähnlicher Weise wird geprüft, ob die Y-Koordinate der Maus (mouse_Y) größer oder gleich der oberen Begrenzung der Schaltfläche (YDistance_Hover_Btn) ist.
  • mouse_Y <= YDistance_Hover_Btn + YSize_Hover_Btn: Es wird geprüft, ob die Y-Koordinate der Maus kleiner oder gleich der unteren Begrenzung der Schaltfläche ist (Summe aus YDistance_Hover_Btn und Höhe der Schaltfläche YSize_Hover_Btn).

Wenn alle Bedingungen erfüllt sind, setzen wir die Variable „isMouseInside“ auf true. Mit dem resultierenden Wert können wir dann überprüfen, ob sich die Maus innerhalb der Schaltfläche befindet. Die folgende Logik ist implementiert. 

      if (isMouseInside != prevMouseInside) {

Hier wird geprüft, ob sich der aktuelle Zustand der Maus (innerhalb oder außerhalb des Tastenbereichs) seit der letzten Prüfung geändert hat. Sie stellt sicher, dass die nachfolgenden Aktionen nur ausgeführt werden, wenn sich die Mausposition relativ zur Schaltfläche ändert. Auch hier müssen wir prüfen, ob die Bedingungen erfüllt wurden.

         // Mouse entered or left the button area
         if (isMouseInside) {
            Print("Mouse entered the Button area. Do your updates!");
            //createRecLabel(BTN_HOVER,25,230,220,35,C'220,220,220',3,C'050,050,255');

            ObjectSetInteger(0, BTN_HOVER, OBJPROP_COLOR, C'050,050,255');
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_BGCOLOR, clrLightBlue);
         }

Wenn die boolesche Variable true ist, bedeutet dies, dass die Maus in den Bereich der Schaltfläche eingetreten ist. Wir informieren über die Instanz durch eine Druckanweisung. Dann ändern wir die Farbe der Schaltflächenbeschriftung und den Hintergrund. Wenn die Variable false ist, bedeutet dies, dass sich der Mauszeiger zuvor im Bereich der Schaltfläche befand und diese gerade verlassen hat. Daher setzen wir die Farben auf die Standardwerte zurück. Nachstehend finden Sie den für diese Logik verantwortlichen Codeschnipsel.

         else if (!isMouseInside) {
            Print("Mouse left Btn proximities. Return default properties.");
            //createRecLabel(BTN_HOVER,25,230,220,35,C'220,220,220',3,C'100,100,100');
            // Reset button properties when mouse leaves the area
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_COLOR, C'100,100,100');
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_BGCOLOR, C'220,220,220');
         }

Nach jeder Änderung der Schaltflächeneigenschaften wird die Funktion „ChartRedraw“ aufgerufen, um die Chart-Anzeige zu aktualisieren und das aktualisierte Erscheinungsbild der Schaltfläche wiederzugeben. Schließlich wird die Variable „prevMouseInside“ aktualisiert, damit sie dem aktuellen Zustand der Maus entspricht („isMouseInside“). Dadurch wird sichergestellt, dass das Programm bei der nächsten Auslösung des Ereignisses den neuen Zustand mit dem vorherigen vergleichen kann.

         ChartRedraw(0);//// Redraw the chart to reflect the changes
         prevMouseInside = isMouseInside;

Der vollständige Code, der für die Erstellung des Hover-Effekts einer Schaltfläche verantwortlich ist, lautet wie folgt:

   else if (id==CHARTEVENT_MOUSE_MOVE){
      int mouse_X = (int)lparam;    // mouseX   >>> mouse coordinates
      int mouse_Y = (int)dparam;    // mouseY   >>> mouse coordinates
      int mouse_State = (int)sparam; // Get the mouse state (0 = mouse moving)
      
      //GETTING THE INITIAL DISTANCES AND SIZES OF BUTTON
      
      int XDistance_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_XDISTANCE);
      int YDistance_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_YDISTANCE);
      int XSize_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_XSIZE);
      int YSize_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_YSIZE);
      
      static bool prevMouseInside = false;
      bool isMouseInside = false;
      
      //Print("Mouse STATE = ",mouse_State); // 0 = mouse moving

      if (mouse_X >= XDistance_Hover_Btn && mouse_X <= XDistance_Hover_Btn + XSize_Hover_Btn &&
          mouse_Y >= YDistance_Hover_Btn && mouse_Y <= YDistance_Hover_Btn + YSize_Hover_Btn){
         isMouseInside = true;
      }
      
      if (isMouseInside != prevMouseInside) {
         // Mouse entered or left the button area
         if (isMouseInside) {
            Print("Mouse entered the Button area. Do your updates!");
            //createRecLabel(BTN_HOVER,25,230,220,35,C'220,220,220',3,C'050,050,255');

            ObjectSetInteger(0, BTN_HOVER, OBJPROP_COLOR, C'050,050,255');
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_BGCOLOR, clrLightBlue);
         }
         else if (!isMouseInside) {
            Print("Mouse left Btn proximities. Return default properties.");
            //createRecLabel(BTN_HOVER,25,230,220,35,C'220,220,220',3,C'100,100,100');
            // Reset button properties when mouse leaves the area
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_COLOR, C'100,100,100');
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_BGCOLOR, C'220,220,220');
         }
         ChartRedraw(0);//// Redraw the chart to reflect the changes
         prevMouseInside = isMouseInside;
      }
   }

Nach dem Kompilieren ergibt sich folgendes Bild:

HOVER EFFECT GIF

Das ist ausgezeichnet. Wir kommen nun zum letzten Teil, der nicht nur die Bewegung des Mauszeigers verfolgt, sondern auch die Bewegung von Objekten oder Komponenten mit ihm. Auch hier deklarieren wir eine statische Integer-Variable, um zu erkennen, wann die Maus angeklickt wird, und eine boolesche Variable, um den Zustand der Mauszeigerbewegung zu speichern. Dies wird mit dem folgenden Codeschnipsel erreicht.

      // CREATE MOVEMENT
      static int prevMouseClickState = false; // false = 0, true = 1;
      static bool movingState = false;

Anschließend müssen wir Variablen initialisieren, die unsere Objektgrößen und -abstände enthalten. 

      // INITIALIZE VARIBALES TO STORE INITIAL SIZES AND DISTANCES OF OBJECTS
      // MLB = MOUSE LEFT BUTTON
      static int mlbDownX = 0; // Stores the X-coordinate of the mouse left button press
      static int mlbDownY = 0; // Stores the Y-coordinate of the mouse left button press
      
      static int mlbDownX_Distance = 0; // Stores the X-distance of an object
      static int mlbDownY_Distance = 0; // Stores the Y-distance of an object
      
      static int mlbDownX_Distance_BTN_DROP_DN = 0; // Stores X-distance for a specific button (BTN_DROP_DN)
      static int mlbDownY_Distance_BTN_DROP_DN = 0; // Stores Y-distance for the same button
      
      static int mlbDownX_Distance_LABEL_OPT1 = 0; // Stores X-distance for a label (LABEL_OPT1)
      static int mlbDownY_Distance_LABEL_OPT1 = 0; // Stores Y-distance for the same label
      
      static int mlbDownX_Distance_LABEL_OPT2 = 0; // Stores X-distance for another label (LABEL_OPT2)
      static int mlbDownY_Distance_LABEL_OPT2 = 0; // Stores Y-distance for the same label
      
      static int mlbDownX_Distance_LABEL_OPT3 = 0; // Stores X-distance for yet another label (LABEL_OPT3)
      static int mlbDownY_Distance_LABEL_OPT3 = 0; // Stores Y-distance for the same label
      
      static int mlbDownX_Distance_ICON_DRAG = 0; // Stores X-distance for an icon (ICON_DRAG)
      static int mlbDownY_Distance_ICON_DRAG = 0; // Stores Y-distance for the same icon

Um die Speichervariablen zu initialisieren, deklarieren wir statische Variablen vom Datentyp und initialisieren sie wie oben mit 0. Sie werden als statisch deklariert, da wir die jeweiligen Größen und Abstände als Referenz speichern müssen, wenn die Plattenkomponenten in Bewegung sind. Auch hier benötigen wir die anfänglichen Elementabstände, und das wird durch den folgenden Codeschnipsel erreicht. 

      //GET THE INITIAL DISTANCES AND SIZES OF BUTTON
      
      int XDistance_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_XDISTANCE);
      int YDistance_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_YDISTANCE);
      //int XSize_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_XSIZE);
      //int YSize_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_YSIZE);
      
      int XDistance_Opt1_Lbl = (int)ObjectGetInteger(0,LABEL_OPT1,OBJPROP_XDISTANCE);
      int YDistance_Opt1_Lbl = (int)ObjectGetInteger(0,LABEL_OPT1,OBJPROP_YDISTANCE);
      
      int XDistance_Opt2_Lbl = (int)ObjectGetInteger(0,LABEL_OPT2,OBJPROP_XDISTANCE);
      int YDistance_Opt2_Lbl = (int)ObjectGetInteger(0,LABEL_OPT2,OBJPROP_YDISTANCE);
      
      int XDistance_Opt3_Lbl = (int)ObjectGetInteger(0,LABEL_OPT3,OBJPROP_XDISTANCE);
      int YDistance_Opt3_Lbl = (int)ObjectGetInteger(0,LABEL_OPT3,OBJPROP_YDISTANCE);

      int XDistance_Drag_Icon = (int)ObjectGetInteger(0,ICON_DRAG,OBJPROP_XDISTANCE);
      int YDistance_Drag_Icon = (int)ObjectGetInteger(0,ICON_DRAG,OBJPROP_YDISTANCE);
      int XSize_Drag_Icon = (int)ObjectGetInteger(0,ICON_DRAG,OBJPROP_XSIZE);
      int YSize_Drag_Icon = (int)ObjectGetInteger(0,ICON_DRAG,OBJPROP_YSIZE);

Hier verwenden wir einfach die Funktion ObjectGetInteger, um die Abstände der Elemente abzurufen, die zusammen mit dem Cursor verschoben werden sollen. Beachten Sie jedoch, dass wir auch die Größe des Symbols erhalten, das in der Panel-Bewegung verwendet werden soll. Der Grund, warum wir die Größen benötigen, genau wie bei der Logik für den Schwebeeffekt, ist, dass wir feststellen können, wann der Mauszeiger innerhalb des Symbolbereichs geklickt wird, damit wir die Bewegung beginnen können. Dann müssen wir die anfänglichen Klickinformationen der Maus erfassen und die Entfernungen der zu verschiebenden Objekte speichern.

if (prevMouseClickState == false && mouse_State == 1) {
    // Check if the left mouse button was clicked and the mouse is in the pressed state

    // Initialize variables to store initial distances and sizes of objects
    mlbDownX = mouse_X; // Store the X-coordinate of the mouse click
    mlbDownY = mouse_Y; // Store the Y-coordinate of the mouse click

    // Store distances for specific objects
    mlbDownX_Distance = XDistance_Drag_Icon; // Distance of the drag icon (X-axis)
    mlbDownY_Distance = YDistance_Drag_Icon; // Distance of the drag icon (Y-axis)

    mlbDownX_Distance_BTN_DROP_DN = XDistance_DropDn_Btn; // Distance of a specific button (BTN_DROP_DN)
    mlbDownY_Distance_BTN_DROP_DN = YDistance_DropDn_Btn;

    mlbDownX_Distance_LABEL_OPT1 = XDistance_Opt1_Lbl; // Distance of a label (LABEL_OPT1)
    mlbDownY_Distance_LABEL_OPT1 = YDistance_Opt1_Lbl;

    mlbDownX_Distance_LABEL_OPT2 = XDistance_Opt2_Lbl; // Distance of another label (LABEL_OPT2)
    mlbDownY_Distance_LABEL_OPT2 = YDistance_Opt2_Lbl;

    mlbDownX_Distance_LABEL_OPT3 = XDistance_Opt3_Lbl; // Distance of yet another label (LABEL_OPT3)
    mlbDownY_Distance_LABEL_OPT3 = YDistance_Opt3_Lbl;

    // Check if the mouse is within the drag icon area
    if (mouse_X >= XDistance_Drag_Icon && mouse_X <= XDistance_Drag_Icon + XSize_Drag_Icon &&
        mouse_Y >= YDistance_Drag_Icon && mouse_Y <= YDistance_Drag_Icon + YSize_Drag_Icon) {
        movingState = true; // Set the moving state to true
    }
}

Wir verwenden eine bedingte Anweisung, um zwei Bedingungen zu prüfen. Erstens: „prevMouseClickState == false“, um sicherzustellen, dass die linke Maustaste zuvor nicht angeklickt wurde, und zweitens: „mouse_State == 1“, um zu prüfen, ob sich die Maus derzeit im gedrückten Zustand befindet (Taste gedrückt). Wenn die beiden Bedingungen erfüllt sind, speichern wir die X- und Y-Koordinaten der Maus sowie die Objektabstände. Abschließend wird geprüft, ob sich die Maus im Bereich des Ziehsymbols befindet. Ist dies der Fall, wird der Bewegungsstatus auf true gesetzt, was bedeutet, dass die Bewegung der Bedienfeldkomponenten beginnen kann. Um dies zu verstehen, sollten wir die vier Bedingungen aufschlüsseln:

  • mouse_X >= XDistance_Drag_Icon: Dabei wird überprüft, ob die X-Koordinate der Maus (mouse_X) größer oder gleich der linken Begrenzung des Ziehsymbolbereichs (XDistance_Drag_Icon) ist.
  • mouse_X <= XDistance_Drag_Icon + XSize_Drag_Icon: Ebenso wird sichergestellt, dass die X-Koordinate kleiner oder gleich der rechten Begrenzung des Ziehsymbolbereichs ist (Summe aus XDistance_Drag_Icon und der Breite des Symbols, XSize_Drag_Icon).
  • mouse_Y >= YDistance_Drag_Icon: Es wird geprüft, ob die Y-Koordinate der Maus (mouse_Y) größer oder gleich der oberen Begrenzung des Ziehsymbolbereichs (YDistance_Drag_Icon) ist.
  • mouse_Y <= YDistance_Drag_Icon + YSize_Drag_Icon: Ebenso wird überprüft, ob die Y-Koordinate kleiner oder gleich der unteren Begrenzung des Ziehsymbolbereichs ist (Summe aus YDistance_Drag_Icon und der Höhe des Symbols, YSize_Drag_Icon).

Wenn alle vier Bedingungen erfüllt sind (d.h. die Maus befindet sich innerhalb des definierten Ziehsymbolbereichs), setzen wir die Variable „movingState“ auf true. Wenn der Bewegungszustand wahr ist, bewegen wir die bezeichneten Objekte bis zu diesem Punkt.

      if (movingState){
         ChartSetInteger(0,CHART_MOUSE_SCROLL,false);
         
         ObjectSetInteger(0,ICON_DRAG,OBJPROP_XDISTANCE,mlbDownX_Distance + mouse_X - mlbDownX);
         ObjectSetInteger(0,ICON_DRAG,OBJPROP_YDISTANCE,mlbDownY_Distance + mouse_Y - mlbDownY);
         
         ...

         ChartRedraw(0);
      }

Hier verwenden wir die Funktion ChartSetInteger, um das Chart-Scroll-Flag zu deaktivieren. Dadurch wird sichergestellt, dass das Chart nicht horizontal gescrollt wird, wenn die Maus bewegt wird. So bewegt sich nur der Mauszeiger zusammen mit den markierten Objekten. Schließlich legen wir die neuen Objektabstände fest, die die aktuellen Mauskoordinaten betreffen, und zeichnen das Chart neu, damit die Änderungen wirksam werden. Kurz und gut, das ist es, was wir haben:

DRAG ICON GIF

Jetzt können Sie sehen, dass wir das Symbol ziehen können. Wir müssen es jedoch auch mit den anderen Komponenten des Panels zusammenziehen. Es gilt also die gleiche Logik. 

         ObjectSetInteger(0,BTN_DROP_DN,OBJPROP_XDISTANCE,mlbDownX_Distance_BTN_DROP_DN + mouse_X - mlbDownX);
         ObjectSetInteger(0,BTN_DROP_DN,OBJPROP_YDISTANCE,mlbDownY_Distance_BTN_DROP_DN + mouse_Y - mlbDownY);
         
         ObjectSetInteger(0,LABEL_OPT1,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT1 + mouse_X - mlbDownX);
         ObjectSetInteger(0,LABEL_OPT1,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT1 + mouse_Y - mlbDownY);
         
         ObjectSetInteger(0,LABEL_OPT2,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT2 + mouse_X - mlbDownX);
         ObjectSetInteger(0,LABEL_OPT2,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT2 + mouse_Y - mlbDownY);

         ObjectSetInteger(0,LABEL_OPT3,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT3 + mouse_X - mlbDownX);
         ObjectSetInteger(0,LABEL_OPT3,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT3 + mouse_Y - mlbDownY);

Durch die Hinzufügung der Logik für das Ziehen anderer Elemente wird sichergestellt, dass sich während der Bewegung des Ziehsymbols auch die anderen Bedienfeldkomponenten bewegen. Nach dem Kompilieren ergibt sich folgendes Bild:

DRAG ICON STICKY GUI

Das war ein Erfolg. Sie können sehen, dass sich alle Komponenten des Bedienfelds mit dem Mauszeiger bewegen. Es gibt jedoch eine kleine Panne, um die wir uns kümmern müssen. Wenn die Maus losgelassen wird, d. h. nicht gedrückt wird, bewegen sich die Komponenten weiter, während sich der Cursor bewegt. Um das Panel aus dem Zustand der Bewegung zu befreien, müssen wir den Zustand auf „false“ setzen, wenn die Maus nicht gedrückt wird. 

      if (mouse_State == 0){
         movingState = false;
         ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
      }

Wenn der Mausstatus Null ist, bedeutet dies, dass die linke Maustaste losgelassen ist und wir den Bewegungsstatus auf „false“ setzen, was bedeutet, dass wir die Komponenten des Panels nicht weiter bewegen müssen. Später aktivieren wir das Bildlaufereignis des Charts, indem wir das Flag auf „true“ setzen. Schließlich setzen wir den vorherigen Mausstatus auf den aktuellen Mausstatus.

      prevMouseClickState = mouse_State;

Der endgültige Quellcode, der für die Automatisierung des Hover-Effekts und die Bewegung des Bedienfelds verantwortlich ist, lautet wie folgt:

   else if (id==CHARTEVENT_MOUSE_MOVE){
      int mouse_X = (int)lparam;    // mouseX   >>> mouse coordinates
      int mouse_Y = (int)dparam;    // mouseY   >>> mouse coordinates
      int mouse_State = (int)sparam; // Get the mouse state (0 = mouse moving)
      
      //GETTING THE INITIAL DISTANCES AND SIZES OF BUTTON
      
      int XDistance_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_XDISTANCE);
      int YDistance_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_YDISTANCE);
      int XSize_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_XSIZE);
      int YSize_Hover_Btn = (int)ObjectGetInteger(0,BTN_HOVER,OBJPROP_YSIZE);
      
      static bool prevMouseInside = false;
      bool isMouseInside = false;
      
      //Print("Mouse STATE = ",mouse_State); // 0 = mouse moving

      if (mouse_X >= XDistance_Hover_Btn && mouse_X <= XDistance_Hover_Btn + XSize_Hover_Btn &&
          mouse_Y >= YDistance_Hover_Btn && mouse_Y <= YDistance_Hover_Btn + YSize_Hover_Btn){
         isMouseInside = true;
      }
      
      if (isMouseInside != prevMouseInside) {
         // Mouse entered or left the button area
         if (isMouseInside) {
            Print("Mouse entered the Button area. Do your updates!");
            //createRecLabel(BTN_HOVER,25,230,220,35,C'220,220,220',3,C'050,050,255');

            ObjectSetInteger(0, BTN_HOVER, OBJPROP_COLOR, C'050,050,255');
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_BGCOLOR, clrLightBlue);
         }
         else if (!isMouseInside) {
            Print("Mouse left Btn proximities. Return default properties.");
            //createRecLabel(BTN_HOVER,25,230,220,35,C'220,220,220',3,C'100,100,100');
            // Reset button properties when mouse leaves the area
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_COLOR, C'100,100,100');
            ObjectSetInteger(0, BTN_HOVER, OBJPROP_BGCOLOR, C'220,220,220');
         }
         ChartRedraw(0);//// Redraw the chart to reflect the changes
         prevMouseInside = isMouseInside;
      }
      
      // CREATE MOVEMENT
      static int prevMouseClickState = false; // false = 0, true = 1;
      static bool movingState = false;
      
      // INITIALIZE VARIBALES TO STORE INITIAL SIZES AND DISTANCES OF OBJECTS
      // MLB = MOUSE LEFT BUTTON
      static int mlbDownX = 0; // Stores the X-coordinate of the mouse left button press
      static int mlbDownY = 0; // Stores the Y-coordinate of the mouse left button press
      
      static int mlbDownX_Distance = 0; // Stores the X-distance of an object
      static int mlbDownY_Distance = 0; // Stores the Y-distance of an object
      
      static int mlbDownX_Distance_BTN_DROP_DN = 0; // Stores X-distance for a specific button (BTN_DROP_DN)
      static int mlbDownY_Distance_BTN_DROP_DN = 0; // Stores Y-distance for the same button
      
      static int mlbDownX_Distance_LABEL_OPT1 = 0; // Stores X-distance for a label (LABEL_OPT1)
      static int mlbDownY_Distance_LABEL_OPT1 = 0; // Stores Y-distance for the same label
      
      static int mlbDownX_Distance_LABEL_OPT2 = 0; // Stores X-distance for another label (LABEL_OPT2)
      static int mlbDownY_Distance_LABEL_OPT2 = 0; // Stores Y-distance for the same label
      
      static int mlbDownX_Distance_LABEL_OPT3 = 0; // Stores X-distance for yet another label (LABEL_OPT3)
      static int mlbDownY_Distance_LABEL_OPT3 = 0; // Stores Y-distance for the same label
      
      static int mlbDownX_Distance_ICON_DRAG = 0; // Stores X-distance for an icon (ICON_DRAG)
      static int mlbDownY_Distance_ICON_DRAG = 0; // Stores Y-distance for the same icon
            
            
      //GET THE INITIAL DISTANCES AND SIZES OF BUTTON
      
      int XDistance_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_XDISTANCE);
      int YDistance_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_YDISTANCE);
      //int XSize_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_XSIZE);
      //int YSize_DropDn_Btn = (int)ObjectGetInteger(0,BTN_DROP_DN,OBJPROP_YSIZE);
      
      int XDistance_Opt1_Lbl = (int)ObjectGetInteger(0,LABEL_OPT1,OBJPROP_XDISTANCE);
      int YDistance_Opt1_Lbl = (int)ObjectGetInteger(0,LABEL_OPT1,OBJPROP_YDISTANCE);
      
      int XDistance_Opt2_Lbl = (int)ObjectGetInteger(0,LABEL_OPT2,OBJPROP_XDISTANCE);
      int YDistance_Opt2_Lbl = (int)ObjectGetInteger(0,LABEL_OPT2,OBJPROP_YDISTANCE);
      
      int XDistance_Opt3_Lbl = (int)ObjectGetInteger(0,LABEL_OPT3,OBJPROP_XDISTANCE);
      int YDistance_Opt3_Lbl = (int)ObjectGetInteger(0,LABEL_OPT3,OBJPROP_YDISTANCE);

      int XDistance_Drag_Icon = (int)ObjectGetInteger(0,ICON_DRAG,OBJPROP_XDISTANCE);
      int YDistance_Drag_Icon = (int)ObjectGetInteger(0,ICON_DRAG,OBJPROP_YDISTANCE);
      int XSize_Drag_Icon = (int)ObjectGetInteger(0,ICON_DRAG,OBJPROP_XSIZE);
      int YSize_Drag_Icon = (int)ObjectGetInteger(0,ICON_DRAG,OBJPROP_YSIZE);
            
      if (prevMouseClickState == false && mouse_State == 1) {
          // Check if the left mouse button was clicked and the mouse is in the pressed state
      
          // Initialize variables to store initial distances and sizes of objects
          mlbDownX = mouse_X; // Store the X-coordinate of the mouse click
          mlbDownY = mouse_Y; // Store the Y-coordinate of the mouse click
      
          // Store distances for specific objects
          mlbDownX_Distance = XDistance_Drag_Icon; // Distance of the drag icon (X-axis)
          mlbDownY_Distance = YDistance_Drag_Icon; // Distance of the drag icon (Y-axis)
      
          mlbDownX_Distance_BTN_DROP_DN = XDistance_DropDn_Btn; // Distance of BTN_DROP_DN
          mlbDownY_Distance_BTN_DROP_DN = YDistance_DropDn_Btn;
      
          mlbDownX_Distance_LABEL_OPT1 = XDistance_Opt1_Lbl; // Distance of LABEL_OPT1
          mlbDownY_Distance_LABEL_OPT1 = YDistance_Opt1_Lbl;
      
          mlbDownX_Distance_LABEL_OPT2 = XDistance_Opt2_Lbl; // Distance of LABEL_OPT2
          mlbDownY_Distance_LABEL_OPT2 = YDistance_Opt2_Lbl;
      
          mlbDownX_Distance_LABEL_OPT3 = XDistance_Opt3_Lbl; // Distance of LABEL_OPT3
          mlbDownY_Distance_LABEL_OPT3 = YDistance_Opt3_Lbl;
      
          // Check if the mouse is within the drag icon area
          if (mouse_X >= XDistance_Drag_Icon && mouse_X <= XDistance_Drag_Icon + XSize_Drag_Icon &&
              mouse_Y >= YDistance_Drag_Icon && mouse_Y <= YDistance_Drag_Icon + YSize_Drag_Icon) {
              movingState = true; // Set the moving state to true
          }
      }
            
      if (movingState){
         ChartSetInteger(0,CHART_MOUSE_SCROLL,false);
         
         ObjectSetInteger(0,ICON_DRAG,OBJPROP_XDISTANCE,mlbDownX_Distance + mouse_X - mlbDownX);
         ObjectSetInteger(0,ICON_DRAG,OBJPROP_YDISTANCE,mlbDownY_Distance + mouse_Y - mlbDownY);
         
         ObjectSetInteger(0,BTN_DROP_DN,OBJPROP_XDISTANCE,mlbDownX_Distance_BTN_DROP_DN + mouse_X - mlbDownX);
         ObjectSetInteger(0,BTN_DROP_DN,OBJPROP_YDISTANCE,mlbDownY_Distance_BTN_DROP_DN + mouse_Y - mlbDownY);
         
         ObjectSetInteger(0,LABEL_OPT1,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT1 + mouse_X - mlbDownX);
         ObjectSetInteger(0,LABEL_OPT1,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT1 + mouse_Y - mlbDownY);
         
         ObjectSetInteger(0,LABEL_OPT2,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT2 + mouse_X - mlbDownX);
         ObjectSetInteger(0,LABEL_OPT2,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT2 + mouse_Y - mlbDownY);

         ObjectSetInteger(0,LABEL_OPT3,OBJPROP_XDISTANCE,mlbDownX_Distance_LABEL_OPT3 + mouse_X - mlbDownX);
         ObjectSetInteger(0,LABEL_OPT3,OBJPROP_YDISTANCE,mlbDownY_Distance_LABEL_OPT3 + mouse_Y - mlbDownY);

         ChartRedraw(0);
      }
      
      if (mouse_State == 0){
         movingState = false;
         ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
      }
      prevMouseClickState = mouse_State;
   }

Kurz gesagt, haben wir genau das erreicht.

FINAL GIF

Das ist großartig. Wir haben gerade unser GUI-Panel mit Leben gefüllt und jetzt ist unser Panel interaktiv und reaktionsfähig. Es verfügt über Hover-Effekte, Schaltflächenklicks, Live-Datenaktualisierungen und reagiert auf Mausbewegungen.


Schlussfolgerung

Abschließend können wir sagen, dass die Integration von dynamischen Funktionen in ein MetaQuotes Language 5 (MQL5) GUI-Panel die Nutzererfahrung erheblich verbessert, indem es interaktiver und funktioneller wird. Durch das Hinzufügen von Hover-Effekten für Schaltflächen wird eine visuell ansprechende Oberfläche geschaffen, die intuitiv auf Nutzeraktionen reagiert. Durch die Echtzeitaktualisierung von Geld- und Briefkursen wird sichergestellt, dass die Händler über die aktuellsten Marktinformationen verfügen, sodass sie schnell fundierte Entscheidungen treffen können. Anklickbare Schaltflächen für die Ausführung von Kauf- und Verkaufsaufträgen sowie eine Funktion zur Schließung von Positionen und Aufträgen rationalisieren die Handelsabläufe und ermöglichen es den Nutzern, umgehend auf Marktveränderungen zu reagieren.

Darüber hinaus bietet die Implementierung von verschiebbaren Unterfenstern und Dropdown-Listen eine zusätzliche Ebene der Anpassung und Flexibilität der Nutzeroberfläche. Die Händler können ihren Arbeitsbereich nach ihren Vorlieben einrichten und so ihre Gesamteffizienz verbessern. Die Dropdown-Listenfunktionalität bietet eine bequeme Möglichkeit, auf verschiedene Optionen zuzugreifen, ohne die Hauptschnittstelle zu überladen, und trägt so zu einer übersichtlicheren und besser organisierten Handelsumgebung bei. Insgesamt verwandeln diese Verbesserungen das MQL5-GUI-Panel in ein robustes und nutzerfreundliches Tool, das den Bedürfnissen moderner Trader gerecht wird und letztlich ihre Handelserfahrung und -effektivität verbessert. Händler können das dargestellte Wissen nutzen, um komplexere und ansprechendere GUI-Panels zu erstellen, die ihre Handelserfahrung verbessern. Wir hoffen, dass Sie den Artikel detailliert, objektiv erklärt und leicht zu verstehen fanden. Prost!


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

Beigefügte Dateien |
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Handelsstrategie kaskadierender Aufträge basierend auf EMA Crossovers für MetaTrader 5 Handelsstrategie kaskadierender Aufträge basierend auf EMA Crossovers für MetaTrader 5
Der Artikel demonstriert einen automatisierten Algorithmus, der auf dem Kreuzen von EMAs für MetaTrader 5 basiert. Detaillierte Informationen zu allen Aspekten der Demonstration eines Expert Advisors in MQL5 und dem Testen in MetaTrader 5 - von der Analyse des Preisbereichsverhaltens bis zum Risikomanagement.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Aufbau des Kerzenmodells Trend-Constraint (Teil 6): Alles in einem integrieren Aufbau des Kerzenmodells Trend-Constraint (Teil 6): Alles in einem integrieren
Eine große Herausforderung ist die Verwaltung mehrerer Chartfenster desselben Paares, in denen das gleiche Programm mit unterschiedlichen Funktionen läuft. Lassen Sie uns besprechen, wie Sie mehrere Integrationen in einem Hauptprogramm zusammenfassen können. Darüber hinaus werden wir Einblicke in die Konfiguration des Programms für den Druck in ein Journal und die Kommentierung der erfolgreichen Signalübertragung auf der Chartschnittstelle geben. Weitere Informationen finden Sie in diesem Artikel, der eine Fortsetzung der Artikelserie ist.