English 日本語
preview
Erstellen eines Handelsadministrator-Panels in MQL5 (Teil IX): Code Organisation (III): Kommunikationsmodul

Erstellen eines Handelsadministrator-Panels in MQL5 (Teil IX): Code Organisation (III): Kommunikationsmodul

MetaTrader 5Beispiele |
107 0
Clemence Benjamin
Clemence Benjamin

Inhalt


Einführung

Heute wollen wir unser New Admin Panel dort weiter ausbauen, wo wir im letzten Artikel aufgehört haben, in dem wir die Modularisierung als Schlüsselaspekt einer umfassenderen Codeorganisation vorgestellt haben. Wir haben die Klasse AdminHomeDialog eingeführt, die für die Erstellung der Admin-Home-Oberfläche zuständig ist. Dieses Home-Panel dient als zentraler Knotenpunkt für den Zugriff auf verschiedene Funktionen und besteht aus Zugriffskontrolltasten, die zu drei Hauptkomponenten-Panels führen:

  • Trade Management Panel 
  • Communications Panel
  • Analytics Panel

Dies sind nicht die endgültigen Grenzen des Systems, da neue Funktionen möglich sind, wenn wir die bestehende Grundlage weiter verfeinern und erweitern. In dieser Diskussion konzentrieren wir uns speziell auf das Kommunikations-Panel als Modul, das im Vergleich zu seiner vorherigen Version innerhalb des monolithischen Admin-Panels weiter verbessert wurde.

Die wichtigsten Erkenntnisse aus dieser Diskussion:

  • Verstehen von Klassen in MQL5
  • Entwicklung von Header-Dateien
  • Vererbung der integrierten Klassen
  • Verwendung der ListView-Header-Datei
  • Anwendung von Farben für ein besseres UI-Design
Um den vorherigen Artikel zu rekapitulieren: Modularisierung bedeutet, eine Anwendung in verschiedene, unabhängige Einheiten (Module) zu unterteilen, die jeweils in einer eigenen Datei definiert sind. Diese Module können separat entwickelt und gepflegt werden. In diesem Fall ist das Hauptprogramm eine .mq5-Datei (New_Admin_Panel.mq5), während die Dialoge Klassen sind, die in .mqh-Header-Dateien definiert sind (AdminHomeDialog.mqh und CommunicationsDialog.mqh). Die Struktur sieht vor, dass das Hauptprogramm eine Instanz von AdminHomeDialog erstellt, die wiederum Instanzen von CommunicationsDialog und allen möglichen zukünftigen Dialogen erstellen kann. Siehe das nachstehende Chart.

Modular (New_Admin_Panel) Ablauf.

Neuer modularer Ablauf des Admin-Panels

1. Hauptprogramm (New_Admin_Panel.mq5)

  • Dies ist der Einstiegspunkt in die Anwendung.
  • Es initialisiert das System und erstellt eine Instanz von AdminHomeDialog, die als primäre Nutzeroberfläche dient.

2. AdminHomeDialog

  • Dient als zentraler Knotenpunkt für die Nutzerinteraktion.
  • Reagiert auf Ereignisse, wie z. B. das Klicken auf eine Schaltfläche, um Instanzen anderer Dialoge wie CommunicationsDialog zu erstellen.
  • Es ist so konzipiert, dass es erweiterbar ist und bei Bedarf zusätzliche Dialoge (z. B. FutureDialog) erzeugen kann.

3. CommunicationsDialog

  • Ein spezialisiertes Dialogfeld, das für bestimmte Funktionen zuständig ist, z. B. das Senden von Nachrichten über die Telegram-API.
  • Wird dynamisch von AdminHomeDialog erstellt, wenn es durch eine Nutzeraktion ausgelöst wird (z. B. Anklicken einer Schaltfläche „Nachricht senden“).

4. Ereignisgesteuerte Interaktionen

  • Das System verwendet einen ereignisgesteuerten Ansatz, wie er in MQL5-Anwendungen üblich ist. So löst beispielsweise ein Schaltflächenklick im AdminHomeDialog die Erstellung und Anzeige des CommunicationsDialogs aus, der dann seine Aufgabe ausführt (z. B. die Interaktion mit Telegram).

5. Modularer Aufbau

  • Jede Komponente hat eine bestimmte Aufgabe: Das Hauptprogramm initialisiert, AdminHomeDialog verwaltet die Schnittstelle, und CommunicationsDialog erledigt Kommunikationsaufgaben. Diese Modularität ermöglicht eine einfache Erweiterung oder Änderung.

Mit der obigen Einführung und dem Überblick über unser neues Programm können wir nun in die Details der Entwicklung unseres bahnbrechenden Moduls, CommunicationsDialog, eintauchen. In Teil (I) war dies nur eine einfache Kommunikationsschnittstelle, aber jetzt haben wir ihre Funktionalität durch die Einbeziehung neuer Merkmale erweitert. Mit diesem grundlegenden Wissen können wir uns besser ein Bild von unserer Richtung machen. In den nächsten Unterabschnitten werden wir uns mit den wesentlichen Bausteinen beschäftigen, die unsere nutzerdefinierten Klassen möglich machen.


Entwicklung der Klasse CommunicationsDialog

Zum besseren Verständnis habe ich unten ein Bild eingefügt, das die Hierarchie des Flusses von der Basisklasse zu unseren nutzerdefinierten Klassen veranschaulicht. Dieser Ansatz ist ein wirksames Mittel zur Förderung der Wiederverwendbarkeit von Code. Das gesamte Projekt besteht aus zahlreichen Schalttafelkomponenten, die jeweils einem bestimmten Zweck dienen. Diese Einzigartigkeit macht jede Codekomponente auch für die Integration in andere Projekte anpassbar.

In MQL5 verweist die Klasse Dialog normalerweise auf CAppDialog oder CDialog aus der Include-Datei (Dialog.mqh), da diese als grundlegende Dialogklassen in der Standardbibliothek dienen.

Sowohl CommunicationsDialog als auch CAdminHomeDialog erben von CAppDialog, wodurch eine strukturierte Hierarchie entsteht, in der mehrere Dialogklassen eine gemeinsame Basis für die Dialogfunktionalität nutzen. Diese Struktur wird in dem nachstehenden Hierarchie-Flussdiagramm dargestellt.

Erstellen von nutzerdefinierten Klassen aus dem Dialog

Beziehung zwischen der Basisklasse und den nutzerdefinierten Klassen

Um loszulegen, öffnen Sie MetaEditor 5 von Ihrem Desktop aus oder starten Sie es aus dem Terminal, indem Sie F4 drücken.

Suchen Sie im Navigator die Datei Dialog.mqh im Ordner Includes und öffnen Sie sie als Referenz. 

Folgen Sie der nachstehenden Abbildung zur Orientierung.

Auffinden von Dialogs.mqh

Auffinden der Klasse Dialog in MetaEditor 5

Erstellen Sie anschließend eine neue Datei für die Entwicklung der neuen Klasse. 

Im Allgemeinen besteht unser Programm aus Abschnitten wie Kopfzeile, Layout und Farbdefinitionen, Klassendeklaration und Methodenimplementierungen. Wir beginnen mit den Grundlagen unserer Datei. Die Header-Kommentare am Anfang sagen uns, um welche Datei es sich handelt (CommunicationsDialog.mqh), wem sie gehört (MetaQuotes Ltd., Sie können dies jedoch in Ihren Namen ändern) und woher sie stammt (MQL5-Community). Diese Kommentare sind wie ein Titelblatt für Ihren Code, das anderen hilft, seinen Zweck zu erkennen.

Als Nächstes verwenden wir #ifndef und #define, um eine „Wächter“ namens COMMUNICATIONS_DIALOG_MQH zu erstellen. Dadurch wird verhindert, dass die Datei mehrfach in ein Projekt aufgenommen wird, was zu Fehlern führen könnte. Stellen Sie es sich als ein Schloss vor, das sagt: „Wenn ich bereits geöffnet wurde, öffnen Sie mich nicht noch einmal“.

Die #include-Zeilen bringen die benötigten Werkzeuge aus der MQL5-Bibliothek ein. Mit (Dialog.mqh) erhalten wir die Basis-Dialogklasse (CAppDialog), während (Button.mqh) und (Edit.mqh) Schaltflächen- und Textfeldklassen bereitstellen. (Label.mqh) und (ListView.mqh) fügen ein Label und eine Liste für Schnellmeldungen hinzu. Schließlich ist (Telegram.mqh) eine nutzerdefinierte Datei (von der angenommen wird, dass sie existiert), die Telegram-Nachrichten verarbeitet. Sie sind wie Werkzeuge aus einem Werkzeugkasten, die wir uns für unseren Dialog ausleihen. Nachstehend finden Sie den Codeausschnitt für diesen Abschnitt.

Abschnitt 1: Dateikopf und Includes

//+------------------------------------------------------------------+
//|                                         CommunicationsDialog.mqh |
//|                             Copyright 2000-2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#ifndef COMMUNICATIONS_DIALOG_MQH
#define COMMUNICATIONS_DIALOG_MQH

#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Label.mqh>
#include <Controls\ListView.mqh>
#include "Telegram.mqh"

Abschnitt 2: Layout und Farbdefinitionen

Bevor wir den Dialog aufbauen, müssen wir seine Größe und sein Aussehen planen. Die #define-Anweisungen sind wie eine Blaupause mit Abmessungen und Farben. Für das Layout bestimmen COMMS_PANEL_WIDTH (300 Pixel) und COMMS_PANEL_HEIGHT (350 Pixel), wie groß unser Dialog auf dem Bildschirm sein wird. Ränder (COMMS_MARGIN_LEFT, COMMS_MARGIN_TOP, COMMS_MARGIN_RIGHT) sorgen für eine Auffüllung der Ränder, während COMMS_GAP_VERTICAL den vertikalen Abstand zwischen den Elementen erhöht. Jedes Steuerelement hat seine eigene Größe: das Eingabefeld ist hoch (COMMS_INPUT_HEIGHT), die Schaltflächen sind kleine Rechtecke (COMMS_BUTTON_WIDTH und HEIGHT), und die Listenansicht und die Beschriftung haben ihre eigenen Abmessungen.

Farben machen den Dialog schön und lesbar. Wir verwenden hexadezimale Zahlen (z. B. 0x808080 für Dunkelgrau), weil Computer in MQL5 Farben so verstehen. CLR_PANEL_BG legt den Haupthintergrund des Dialogs fest, CLR_CLIENT_BG färbt den Bereich, in dem die Steuerelemente sitzen, und CLR_CAPTION_BG und CLR_CAPTION_TEXT gestalten die Titelleiste. Umrandungen erhalten CLR_BORDER_BG und CLR_BORDER, während Steuerelemente wie das Eingabefeld (CLR_INPUT_BG, CLR_INPUT_TEXT) und Schaltflächen (CLR_SEND_BG, CLR_CLEAR_BG) ihre eigenen Farben erhalten. Die Kommentare neben den einzelnen Definitionen erklären, was sie bewirken, sodass sie später leicht angepasst werden können.

// **Layout Defines**
#define COMMS_PANEL_WIDTH       300
#define COMMS_PANEL_HEIGHT      350
#define COMMS_MARGIN_LEFT       10
#define COMMS_MARGIN_TOP        10
#define COMMS_MARGIN_RIGHT      10
#define COMMS_GAP_VERTICAL      10
#define COMMS_INPUT_HEIGHT      30
#define COMMS_BUTTON_WIDTH      80
#define COMMS_BUTTON_HEIGHT     30
#define COMMS_LISTVIEW_WIDTH    280
#define COMMS_LISTVIEW_HEIGHT   80
#define COMMS_LABEL_HEIGHT      20

// **Color Defines (Hexadecimal Values)**
#define CLR_PANEL_BG      0x808080  // Dark Gray (Dialog background)
#define CLR_CLIENT_BG     0xD3D3D3  // Light Gray (Client area background)
#define CLR_CAPTION_BG    0x404040  // Darker Gray (Caption background)
#define CLR_CAPTION_TEXT  0xFFFFFF  // White (Caption text)
#define CLR_BORDER_BG     0xFFFFFF  // White (Border background)
#define CLR_BORDER        0xA9A9A9  // Gray (Border color)
#define CLR_INPUT_BG      0xFFFFFF  // White (Input box background)
#define CLR_INPUT_TEXT    0x000000  // Black (Input box text)
#define CLR_SEND_BG       0x00FF00  // Lime Green (Send button background)
#define CLR_CLEAR_BG      0xF08080  // Light Coral (Clear button background)
#define CLR_BUTTON_TEXT   0x000000  // Black (Button text)
#define CLR_LABEL_TEXT    0xFFFFFF  // White (Label text)
#define CLR_LIST_BG       0xFFFFFF  // White (List view background)
#define CLR_LIST_TEXT     0x000000  // Black (List view text)

Abschnitt 3: Erklärung zur Klasse

Jetzt definieren wir das Herzstück unseres Dialogs: die Klasse CCommunicationDialog. Betrachten Sie eine Klasse als ein Rezept für die Erstellung eines Dialogobjekts. Wir sagen: public CAppDialog, weil unser Dialog auf CAppDialog aufbaut, einer vorgefertigten Dialogklasse aus MQL5, die uns grundlegende Dialogfunktionen wie eine Titelleiste und Rahmen bietet.

Der private Bereich listet die Zutaten auf, die wir im Dialog verwenden werden. m_inputBox ist ein Textfeld, in das der Nutzer Nachrichten einträgt, m_sendButton und m_clearButton sind Schaltflächen zum Senden bzw. Löschen der Nachricht, m_quickMsgLabel ist eine Text und m_quickMessageList ist eine Liste mit voreingestellten Nachrichten. Wir speichern auch m_chatId und m_botToken als Zeichenkette für Telegram und m_quickMessages als Array, um acht Optionen für Schnellnachrichten zu speichern.

Im öffentlichen Bereich führen wir Funktionen auf, die jeder verwenden kann, um mit unserem Dialog zu interagieren. Der Konstruktor (CCommunicationDialog) richtet den Dialog mit einer Chat-ID und einem Bot-Token ein, und der Destruktor (~CCommunicationDialog) räumt auf, wenn wir fertig sind. Create erstellt das Dialogfeld auf dem Chart, OnEvent behandelt Klicks und Aktionen, und Toggle zeigt oder verbirgt es.

Zurück im privaten Bereich haben wir Hilfsfunktionen, um jedes Steuerelement zu erstellen (CreateInputBox, etc.) und Event-Handler (OnClickSend, OnClickClear), um zu entscheiden, was passiert, wenn Schaltflächen angeklickt werden. Diese sind privat, weil es sich um interne Details der Funktionsweise des Dialogs handelt.

//+------------------------------------------------------------------+
//| Class CCommunicationDialog                                       |
//| Purpose: A dialog for sending Telegram messages with controls    |
//+------------------------------------------------------------------+
class CCommunicationDialog : public CAppDialog
{
private:
   CEdit         m_inputBox;           // Field to edit/send message
   CButton       m_sendButton;         // Send message button
   CButton       m_clearButton;        // Clear edit box button
   CLabel        m_quickMsgLabel;      // Label for "QuickMessages"
   CListView     m_quickMessageList;   // ListView for quick messages
   string        m_chatId;             // Telegram chat ID
   string        m_botToken;           // Telegram bot token
   string        m_quickMessages[8];   // Array of quick messages

public:
   CCommunicationDialog(const string chatId, const string botToken);
   ~CCommunicationDialog();
   
   virtual bool Create(const long chart, const string name, const int subwin,
                       const int x1, const int y1, const int x2, const int y2);
   virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   void Toggle();                      // Toggle dialog visibility

private:
   //--- Create dependent controls
   bool CreateInputBox(void);
   bool CreateClearButton(void);
   bool CreateSendButton(void);
   bool CreateQuickMsgLabel(void);
   bool CreateQuickMessageList(void);

   //--- Handlers of dependent controls events
   void OnClickSend(void);      // Handler for Send button
   void OnClickClear(void);     // Handler for Clear button
};

Abschnitt 4: Konstruktor und Destruktor

Der Konstruktor ist so, als ob man ein neues Spielzeug aufbaut, bevor man damit spielt. Wenn jemand einen CCommunicationDialog erstellt, muss er ihm eine chatId und einen botToken (Zeichenkette für Telegram) geben. Die Teile m_chatId(chatId) und m_botToken(botToken) kopieren diese in unsere privaten Variablen, damit der Dialog weiß, wohin er Nachrichten senden soll. Innerhalb der geschweiften Klammern füllen wir das Array m_quickMessages mit acht praktischen Phrasen, aus denen die Nutzer auswählen können. Dies geschieht bei der Geburt des Dialogs, sodass er sofort einsatzbereit ist.

Der Destruktor ist die Putzkolonne. Er wird ausgeführt, wenn das Dialogfeld gelöscht wird (z. B. wenn Sie das Programm schließen). Zurzeit ist sie leer, weil CAppDialog die meisten Aufräumarbeiten für uns erledigt und wir nichts zusätzlich putzen müssen. Es ist hier als Platzhalter für den Fall, dass wir später spezielle Aufräumarbeiten, wie das Freigeben von Speicher, hinzufügen.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCommunicationDialog::CCommunicationDialog(const string chatId, const string botToken)
   : m_chatId(chatId), m_botToken(botToken)
{
   // Initialize quick messages
   m_quickMessages[0] = "Updates";
   m_quickMessages[1] = "Close all";
   m_quickMessages[2] = "In deep profits";
   m_quickMessages[3] = "Hold position";
   m_quickMessages[4] = "Swing Entry";
   m_quickMessages[5] = "Scalp Entry";
   m_quickMessages[6] = "Book profit";
   m_quickMessages[7] = "Invalid Signal";
}

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CCommunicationDialog::~CCommunicationDialog()
{
}

Abschnitt 5: Methode erstellen

Mit der Methode „Erstellen“ bauen wir den Dialog auf dem Bildschirm auf, so wie man ein Spielzeug aus Teilen zusammensetzt. Sie benötigt Eingaben wie Chart (wo es erscheint), Name (eine eindeutige ID), Subwin (welches Chartfenster) und Koordinaten (x1, y1, x2, y2) für Position und Größe. Wir rufen zunächst CAppDialog::Create auf, um die grundlegende Dialogstruktur einzurichten. Wenn das fehlschlägt, geben wir false zurück, um zu sagen, dass etwas schief gelaufen ist.

Mit Caption("Communications Panel") wird der Titel am oberen Rand des Dialogfelds festgelegt. Dann färben wir ihn ein! ObjectSetInteger ändert die Farben, indem es die Teile des Dialogs direkt anspricht (z. B. „Back“ für den Hintergrund, „Client“ für den Kontrollbereich). Wir verwenden unsere Farbdefinitionen (z. B. CLR_PANEL_BG), damit es schön aussieht. Die Prüfung if(!m_panel_flag) fügt einen Rahmen nur hinzu, wenn der Dialog kein spezieller Typ ist (gesteuert durch m_panel_flag, eine Variable von CAppDialog).

Danach rufen wir Hilfsfunktionen auf, um jedes Steuerelement (Eingabefeld, Schaltflächen usw.) zu erstellen. Wenn einer davon fehlschlägt, brechen wir ab und geben false zurück. Schließlich geben wir die erste Kurznachricht mit m_inputBox.Text in das Eingabefeld ein und rufen ChartRedraw() auf, um alles im Chart anzuzeigen. Die Rückgabe von true bedeutet Erfolg.

//+------------------------------------------------------------------+
//| Create Method                                                    |
//| Initializes the dialog and its controls with full color styling  |
//+------------------------------------------------------------------+
bool CCommunicationDialog::Create(const long chart, const string name, const int subwin,
                                  const int x1, const int y1, const int x2, const int y2)
{
   // Create the base dialog
   if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2))
      return(false);
   
   Caption("Communications Panel"); // Set the title
   
   // Set dialog background color
   ObjectSetInteger(m_chart_id, m_name + "Back", OBJPROP_BGCOLOR, CLR_PANEL_BG);
   
   // Set client area background color
   ObjectSetInteger(m_chart_id, m_name + "Client", OBJPROP_BGCOLOR, CLR_CLIENT_BG);
   
   // Set caption colors
   ObjectSetInteger(m_chart_id, m_name + "Caption", OBJPROP_BGCOLOR, CLR_CAPTION_BG);
   ObjectSetInteger(m_chart_id, m_name + "Caption", OBJPROP_COLOR, CLR_CAPTION_TEXT);
   
   // Set border colors (if border exists, i.e., m_panel_flag is false)
   if(!m_panel_flag)
   {
      ObjectSetInteger(m_chart_id, m_name + "Border", OBJPROP_BGCOLOR, CLR_BORDER_BG);
      ObjectSetInteger(m_chart_id, m_name + "Border", OBJPROP_BORDER_COLOR, CLR_BORDER);
   }
   
   // Create all controls
   if(!CreateInputBox())
      return(false);
   if(!CreateClearButton())
      return(false);
   if(!CreateSendButton())
      return(false);
   if(!CreateQuickMsgLabel())
      return(false);
   if(!CreateQuickMessageList())
      return(false);
   
   // Set initial text in input box
   m_inputBox.Text(m_quickMessages[0]);
   ChartRedraw();
   return(true);
}

Abschnitt 6: Methoden zur Erstellung von Kontrollen

Diese Methoden sind wie das Zusammensetzen der Teile unseres Dialogs. Jeder erstellt ein Steuerelement (Eingabefeld, Schaltflächen, Etikett, Liste) und platziert es auf dem Dialogfeld. Sie geben alle true zurück, wenn sie erfolgreich sind, oder false, wenn etwas fehlschlägt, sodass wir anhalten können, wenn es ein Problem gibt.

Für CreateInputBox berechnen wir die Positionen anhand unserer Layout-Definitionen (z. B. COMMS_MARGIN_LEFT). Das Eingabefeld ist breit (mit ClientAreaWidth()) und hoch (dreimal COMMS_INPUT_HEIGHT). Wir rufen m_inputBox.Create mit der Chart-ID, einem eindeutigen Namen (m_name + „_InputBox“) und den Koordinaten auf. Add fügt es in das Dialogfeld ein, und ObjectSetInteger legt seine Farben fest.

CreateClearButton und CreateSendButton erzeugen Schaltflächen unterhalb des Eingabefeldes. Wir stapeln sie vertikal mit COMMS_GAP_VERTICAL und positionieren Send neben Clear mit clear_button_x2. Jedes erhält einen Namen, einen Text („Clear“ oder „Send“) und Farben aus unseren Definitionen.

CreateQuickMsgLabel fügt eine Beschriftung unter den Schaltflächen hinzu, wobei mehr Abstände berechnet werden. Da es sich nur um Text handelt, braucht er nur eine Textfarbe. CreateQuickMessageList macht eine Listenansicht noch niedriger und füllt sie mit unseren Kurznachrichten aus dem Konstruktor. Jedes Steuerelement verwendet dasselbe Muster: Erstellen, Hinzufügen, Einfärben und Prüfen auf Fehler mit Druckmeldungen zur Unterstützung der Fehlersuche.

//+------------------------------------------------------------------+
//| CreateInputBox                                                   |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateInputBox(void)
{
   int x1 = COMMS_MARGIN_LEFT;
   int y1 = COMMS_MARGIN_TOP;
   int x2 = ClientAreaWidth() - COMMS_MARGIN_RIGHT;
   int y2 = y1 + 3 * COMMS_INPUT_HEIGHT;

   if(!m_inputBox.Create(m_chart_id, m_name + "_InputBox", m_subwin, x1, y1, x2, y2))
   {
      Print("Failed to create InputBox");
      return(false);
   }
   if(!Add(m_inputBox))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_InputBox", OBJPROP_BGCOLOR, CLR_INPUT_BG);
   ObjectSetInteger(m_chart_id, m_name + "_InputBox", OBJPROP_COLOR, CLR_INPUT_TEXT);
   return(true);
}

//+------------------------------------------------------------------+
//| CreateClearButton                                                |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateClearButton(void)
{
   int button_y1 = COMMS_MARGIN_TOP + 3 * COMMS_INPUT_HEIGHT + COMMS_GAP_VERTICAL;
   int x1 = COMMS_MARGIN_LEFT;
   int x2 = x1 + COMMS_BUTTON_WIDTH;
   int y2 = button_y1 + COMMS_BUTTON_HEIGHT;

   if(!m_clearButton.Create(m_chart_id, m_name + "_ClearButton", m_subwin, x1, button_y1, x2, y2))
   {
      Print("Failed to create ClearButton");
      return(false);
   }
   m_clearButton.Text("Clear");
   if(!Add(m_clearButton))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_ClearButton", OBJPROP_BGCOLOR, CLR_CLEAR_BG);
   ObjectSetInteger(m_chart_id, m_name + "_ClearButton", OBJPROP_COLOR, CLR_BUTTON_TEXT);
   return(true);
}

//+------------------------------------------------------------------+
//| CreateSendButton                                                 |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateSendButton(void)
{
   int button_y1 = COMMS_MARGIN_TOP + 3 * COMMS_INPUT_HEIGHT + COMMS_GAP_VERTICAL;
   int clear_button_x2 = COMMS_MARGIN_LEFT + COMMS_BUTTON_WIDTH;
   int x1 = clear_button_x2 + COMMS_GAP_VERTICAL;
   int x2 = x1 + COMMS_BUTTON_WIDTH;
   int y2 = button_y1 + COMMS_BUTTON_HEIGHT;

   if(!m_sendButton.Create(m_chart_id, m_name + "_SendButton", m_subwin, x1, button_y1, x2, y2))
   {
      Print("Failed to create SendButton");
      return(false);
   }
   m_sendButton.Text("Send");
   if(!Add(m_sendButton))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_SendButton", OBJPROP_BGCOLOR, CLR_SEND_BG);
   ObjectSetInteger(m_chart_id, m_name + "_SendButton", OBJPROP_COLOR, CLR_BUTTON_TEXT);
   return(true);
}

//+------------------------------------------------------------------+
//| CreateQuickMsgLabel                                              |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateQuickMsgLabel(void)
{
   int label_y1 = COMMS_MARGIN_TOP + 3 * COMMS_INPUT_HEIGHT + COMMS_GAP_VERTICAL +
                  COMMS_BUTTON_HEIGHT + COMMS_GAP_VERTICAL;
   int x1 = COMMS_MARGIN_LEFT;
   int x2 = x1 + COMMS_LISTVIEW_WIDTH;
   int y2 = label_y1 + COMMS_LABEL_HEIGHT;

   if(!m_quickMsgLabel.Create(m_chart_id, m_name + "_QuickMsgLabel", m_subwin, x1, label_y1, x2, y2))
   {
      Print("Failed to create QuickMessages Label");
      return(false);
   }
   m_quickMsgLabel.Text("QuickMessages");
   if(!Add(m_quickMsgLabel))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_QuickMsgLabel", OBJPROP_COLOR, CLR_LABEL_TEXT);
   return(true);
}

//+------------------------------------------------------------------+
//| CreateQuickMessageList                                           |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateQuickMessageList(void)
{
   int list_y1 = COMMS_MARGIN_TOP + 3 * COMMS_INPUT_HEIGHT + COMMS_GAP_VERTICAL +
                 COMMS_BUTTON_HEIGHT + COMMS_GAP_VERTICAL +
                 COMMS_LABEL_HEIGHT + COMMS_GAP_VERTICAL;
   int x1 = COMMS_MARGIN_LEFT;
   int x2 = x1 + COMMS_LISTVIEW_WIDTH;
   int y2 = list_y1 + COMMS_LISTVIEW_HEIGHT;

   if(!m_quickMessageList.Create(m_chart_id, m_name + "_QuickMsgList", m_subwin, x1, list_y1, x2, y2))
   {
      Print("Failed to create ListView");
      return(false);
   }
   if(!Add(m_quickMessageList))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_QuickMsgList", OBJPROP_BGCOLOR, CLR_LIST_BG);
   ObjectSetInteger(m_chart_id, m_name + "_QuickMsgList", OBJPROP_COLOR, CLR_LIST_TEXT);
   
   for(int i = 0; i < ArraySize(m_quickMessages); i++)
   {
      if(!m_quickMessageList.AddItem("Message: " + m_quickMessages[i]))
         return(false);
   }
   
   return(true);
}

Abschnitt 7: Toggle und Ereignisbehandlung

Toggle ist ein einfacher Schalter zum Ein- und Ausblenden des Dialogs. IsVisible() prüft, ob es auf dem Bildschirm ist. Wenn ja, lässt Hide() sie verschwinden; wenn nicht, bringt Show() sie zurück. ChartRedraw() aktualisiert das Chart, damit Sie die Änderung sofort sehen. Es ist, als würde man einen Lichtschalter für den Dialog umlegen.

OnEvent ist das Gehirn, das auf Nutzeraktionen, wie Klicks, hört. Es erhält eine id (was passiert ist), lparam (Details), dparam (weitere Details) und sparam (welches Objekt). Wenn id CHARTEVENT_OBJECT_CLICK lautet, bedeutet dies, dass etwas angeklickt wurde. Wir prüfen, ob sparam (der Name des angeklickten Objekts) mit m_sendButton.Name() oder m_clearButton.Name() übereinstimmt, und rufen dann OnClickSend oder OnClickClear auf. Die Rückgabe von true sagt, dass wir es geschafft haben.

Wenn id ON_CHANGE und sparam der Name der Liste ist, hat der Nutzer eine Kurznachricht ausgewählt. lparam sagt uns, welche (als Zahl), und wir setzen diese Nachricht mit m_inputBox.Text in das Eingabefeld. Wenn nichts passt, überlassen wir es CAppDialog::OnEvent, das Ereignis in der Kette weiterzuleiten.

//+------------------------------------------------------------------+
//| Toggle                                                           |
//+------------------------------------------------------------------+
void CCommunicationDialog::Toggle()
{
   if(IsVisible())
      Hide();
   else
      Show();
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| OnEvent                                                          |
//+------------------------------------------------------------------+
bool CCommunicationDialog::OnEvent(const int id, const long &lparam,
                                   const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == m_sendButton.Name())
      {
         OnClickSend();
         return true;
      }
      else if(sparam == m_clearButton.Name())
      {
         OnClickClear();
         return true;
      }
   }
   else if(id == ON_CHANGE && sparam == m_quickMessageList.Name())
   {
      int selectedIndex = (int)lparam;
      if(selectedIndex >= 0 && selectedIndex < ArraySize(m_quickMessages))
      {
         m_inputBox.Text(m_quickMessages[selectedIndex]);
      }
      return true;
   }
   return CAppDialog::OnEvent(id, lparam, dparam, sparam);
}

Abschnitt 8: Ereignisbehandlung der Schaltflächen

OnClickSend wird ausgeführt, wenn die Schaltfläche „Send“ angeklickt wird. Es wird der Text aus m_inputBox.Text() entnommen und überprüft, ob er nicht leer ist (""). Wenn es eine Nachricht gibt, versucht es, diese mit SendMessageToTelegram (eine Funktion aus Telegram.mqh) mit unserer m_chatId und m_botToken zu senden. Wenn es funktioniert, drucken wir eine Erfolgsmeldung, wenn nicht, eine Fehlermeldung. Wenn das Feld leer ist, drucken wir einfach eine Notiz. Dies ist die Hauptaufgabe des Dialogs - das Versenden von Nachrichten!

OnClickClear ist einfacher. Wenn Sie auf „Clear“ klicken, wird der Text des Eingabefeldes mit m_inputBox.Text("") auf Null ("") gesetzt, das Chart neu gezeichnet, um die Änderung anzuzeigen, und eine Bestätigung ausgedruckt. Das ist so, als würde man ein Formular zurücksetzen.

//+------------------------------------------------------------------+
//| OnClickSend                                                      |
//+------------------------------------------------------------------+
void CCommunicationDialog::OnClickSend()
{
   string message = m_inputBox.Text();
   if(message != "")
   {
      if(SendMessageToTelegram(message, m_chatId, m_botToken))
         Print("Message sent to Telegram: ", message);
      else
         Print("Failed to send message to Telegram");
   }
   else
   {
      Print("No message to send - input box is empty");
   }
}

//+------------------------------------------------------------------+
//| OnClickClear                                                     |
//+------------------------------------------------------------------+
void CCommunicationDialog::OnClickClear()
{
   m_inputBox.Text("");  // Clear the input box
   ChartRedraw();
   Print("Input box cleared.");
}

Fügen Sie am Ende der Datei #endif ein, um den Header Guard zu schließen:

#endif // COMMUNICATIONS_DIALOG_MQH

Dies entspricht dem #ifndef am Anfang und schließt alles sauber ab.


Integration von CommunicationsDialog mit anderen Header-Dateien und dem Hauptprogramm

Der CommunicationsDialog wird innerhalb des AdminHomeDialogs behandelt. In diesem Abschnitt werde ich erklären, wie das funktioniert. Ich werde nicht den gesamten Code von AdminHomeDialog behandeln, da wir ihn im vorherigen Artikel ausführlich besprochen haben.

Schritt 1

Um den CommunicationsDialog mit dem AdminHomeDialog zu verbinden, müssen wir ihn in unsere Datei aufnehmen. Die Zeile #include (CommunicationsDialog.mqh) ist wie das Öffnen einer Tür zwischen den beiden. Sie weist MQL5 an, die Datei CommunicationsDialog.mqh zu laden und ihre Klasse CCommunicationDialog für uns verfügbar zu machen. Platzieren Sie dies am Anfang von (AdminHomeDialog.mqh), nach anderen Includes wie (Dialog.mqh) und (Button.mqh). Ohne dies wüsste AdminHomeDialog nichts über das Kommunikationspanel, es ist also der erste Schritt zur Verknüpfung der beiden.

#include <CommunicationsDialog.mqh>  // Use the enhanced Communications dialog

Schritt 2

Innerhalb der Klasse CAdminHomeDialog benötigen wir eine Möglichkeit, unser Kommunikationspanel zu speichern. Wir fügen CCommunicationDialog *m_commPanel in den privaten Abschnitt ein, wobei wir einen Zeiger verwenden (das * bedeutet, dass es sich um einen Verweis auf ein Objekt handelt, das wir später erstellen werden). Das ist so, als würden wir einen Platz für ein Spielzeug reservieren, das wir bei Bedarf auspacken. Wir fügen auch m_chatId und m_botToken als String-Variablen hinzu, um Telegram-Details zu speichern, die wir an CCommunicationDialog übergeben werden. Diese sind privat, da nur diese Klasse sie verwalten muss, um die Voraussetzungen für die Integration zu schaffen.

class CAdminHomeDialog : public CAppDialog
{
private:
   CCommunicationDialog *m_commPanel; // Pointer to the Communications panel
   string              m_chatId;      // Telegram Chat ID
   string              m_botToken;    // Telegram Bot Token
   
///.................Space for other members e.g. buttons
};

Schritt 3

Der Konstruktor richtet CAdminHomeDialog ein, wenn er erstellt wird. Wir aktualisieren es, um chatId und botToken als Eingaben zu nehmen, die wir in m_chatId und m_botToken kopieren, indem wir den : m_chatId(chatId), m_botToken(botToken) verwenden. Wir setzen auch m_commPanel auf NULL (noch nichts) mit : m_commPanel(NULL). Das bedeutet, dass wir das Kommunikationspanel nicht sofort einrichten, sondern warten, bis der Nutzer es anfordert. Es ist, als würde man eine Schachtel geschlossen halten, bis man bereit ist, mit dem zu spielen, was darin ist.

Der Destruktor räumt auf, wenn der Dialog beendet ist. Wir prüfen if(m_commPanel), um festzustellen, ob wir ein Kommunikationspanel erstellt haben. Wenn dies der Fall ist, wird durch das Löschen von m_commPanel der Speicher freigegeben (wie beim Wegwerfen eines gebrauchten Spielzeugs), und m_commPanel = NULL stellt sicher, dass wir nicht versehentlich versuchen, es erneut zu verwenden. Dadurch bleibt unser Programm übersichtlich und verhindert Abstürze beim Verbinden mit CommunicationsDialog.

CAdminHomeDialog::CAdminHomeDialog(string chatId, string botToken)
   : m_commPanel(NULL), m_chatId(chatId), m_botToken(botToken)
{
}

CAdminHomeDialog::~CAdminHomeDialog(void)
{
   if(m_commPanel)
   {
      delete m_commPanel;
      m_commPanel = NULL;
   }
}

Schritt 4

Hier passiert der Clou: OnClickCommunications startet das Kommunikationspanel, wenn die Schaltfläche „Communications“ angeklickt wird. Zunächst wird geprüft, ob (m_commPanel == NULL), um festzustellen, ob es noch nicht erstellt wurde. Wenn er NULL ist, verwenden wir new CCommunicationDialog(m_chatId, m_botToken), um einen neuen Dialog zu erstellen und unsere gespeicherten Telegram-Details zu übergeben. Das ist so, als würde man die Spielzeugkiste öffnen und sie mit der richtigen Anleitung aufstellen.

Wenn new fehlschlägt (vielleicht ging dem Computer der Speicherplatz aus), bleibt m_commPanel NULL, und wir geben eine Fehlermeldung aus und halten an. Andernfalls rufen wir m_commPanel.Create auf, um es im Chart zu erstellen. Wir verwenden m_chart_id (das Chart, in dem wir uns befinden), „CommPanel“ als Namen, m_subwin (welches Fenster) und Koordinaten (20, 435 für die linke obere Ecke, 300 breit und 350 hoch für die Größe). Wenn Create fehlschlägt, drucken wir eine Fehlermeldung, löschen es und setzen m_commPanel auf NULL zurück.

Wenn es bereits erstellt ist oder gerade erfolgreich erstellt wurde, schaltet m_commPanel.Toggle() es ein oder aus - zeigt es an, wenn es versteckt ist, und versteckt es, wenn es angezeigt wird. Diese „träge Erstellung“ bedeutet, dass wir sie erst erstellen, wenn der Nutzer klickt, und so Ressourcen sparen, bis sie benötigt werden.

void CAdminHomeDialog::OnClickCommunications()
{
   if(m_commPanel == NULL)
   {
      m_commPanel = new CCommunicationDialog(m_chatId, m_botToken); // Pass chatId and botToken
      if(m_commPanel == NULL)
      {
         Print("Error: Failed to allocate Communications panel");
         return;
      }
      if(!m_commPanel.Create(m_chart_id, "CommPanel", m_subwin, 20, 435, 20 + 300, 435 + 350))
      {
         Print("Error: Failed to create Communications panel");
         delete m_commPanel;
         m_commPanel = NULL;
         return;
      }
   }
   m_commPanel.Toggle(); 
}

Schritt 5

OnEvent wartet auf Nutzeraktionen wie Klicks und verbindet CommunicationsDialog, indem es Ereignisse an es weitergibt. Wenn id == CHARTEVENT_OBJECT_CLICK ist, prüfen wir, ob sparam (der Name des angeklickten Objekts) mit m_commButton.Name() übereinstimmt. Wenn dies der Fall ist, drucken wir eine Meldung und rufen OnClickCommunications auf, um das Panel zu öffnen.

Der wichtigste Teil der Integration ist der else-Block if(m_commPanel != NULL && m_commPanel.IsVisible()). Wenn das Kommunikationspanel existiert (!= NULL) und auf dem Bildschirm zu sehen ist (IsVisible()), senden wir das Ereignis (id, lparam, etc.) an m_commPanel.OnEvent. Dadurch kann CommunicationsDialog seine eigenen Klicks, wie „Send“ oder „Clear“, verarbeiten. Wir geben zurück, was m_commPanel.OnEvent zurückgibt, und verknüpfen so die Ereignissysteme der beiden Dialoge. Wenn es keine Übereinstimmung gibt, übernimmt CAppDialog::OnEvent die Aufgabe. Durch dieses Zusammenspiel können beide Dialoge reibungslos auf den Nutzer reagieren.

bool CAdminHomeDialog::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      Print("Clicked object: ", sparam);
      if(sparam == m_commButton.Name())
      {
         Print("Communications button detected");
         OnClickCommunications();
         return true;
      }
      // ... other button checks ...
   }
   // Forward remaining events to CommPanel if visible
   else if(m_commPanel != NULL && m_commPanel.IsVisible())
   {
      return m_commPanel.OnEvent(id, lparam, dparam, sparam);
   }
   return CAppDialog::OnEvent(id, lparam, dparam, sparam);
}


Tests und Ergebnisse 

Nachdem wir das Kommunikationsmodul erfolgreich kompiliert und in das Hauptprogramm integriert hatten, starteten wir das Programm auf der Terminalkarte ohne Probleme. Alle Panels reagieren auf Klicks, was durch die Einträge im Expertenprotokoll bestätigt wird. Wenn Sie in AdminHomeDialog auf die Schaltfläche „Kommunikationspanel“ klicken, wird der Kommunikationsdialog erstellt, aber die anfängliche Logik blendet ihn aus, sodass ein zweiter Klick erforderlich ist, um ihn sichtbar zu machen. Wenn der Kommunikationsdialog im Chart sichtbar ist, wird er durch erneutes Klicken auf die Schaltfläche ausgeblendet, d. h. er wird ein- und ausgeschaltet.

Bei der Listenansicht ist mir jedoch ein großes Problem aufgefallen: Die Klick- und Bildlaufereignisse für die Auswahl der zu versendenden Kurznachrichten funktionieren nicht wie erwartet, und wir müssen herausfinden, wo wir einen Fehler gemacht haben. Ich glaube, das ist ein kleines Problem, das wir beheben können. Im Moment ist das Kernkonzept funktional und sichtbar, und unsere nächsten Schritte bestehen darin, es zu verfeinern und weitere Funktionen hinzuzufügen.

Testen des neuen Kommunikationspanels

Testen des Kommunikationsmoduls


Schlussfolgerung

Die Entwicklung des Moduls CommunicationsDialog, das in AdminHomeDialog.mqh integriert ist, hat zu einem funktionalen und modularen System für Telegram-Nachrichten innerhalb unserer neuen Admin Panel, MQL5 Handelsanwendung, geführt. Wir haben eine reaktionsschnelle Verwaltungsoberfläche geschaffen, die das Kommunikationspanel bei Bedarf erfolgreich startet und umschaltet, wie das Expertenprotokoll mit Klickereignissen wie „Clicked object: m_SendButton“ zeigt. Der Ansatz der verzögerten Erstellung und des Umschaltens optimiert die Ressourcennutzung, während das ereignisgesteuerte Design die Skalierbarkeit sicherstellt und beweist, dass das Kernkonzept auf dem Chart sichtbar und einsatzfähig ist. Dieser modulare Aufbau mit CAdminHomeDialog als Drehscheibe und CCommunicationDialog als spezialisiertem Werkzeug bildet eine solide Grundlage für zukünftige Erweiterungen.

Allerdings gibt es noch kleinere Probleme, z. B. funktionieren die Klick- und Bildlaufereignisse der Listenansicht nicht für die Schnellauswahl von Nachrichten, und die Schaltflächenereignisse des Kommunikationspanels müssen weiter verfeinert werden.  Trotzdem überwiegen die Stärken die Mängel des Systems - Effizienz, Reaktionsschnelligkeit und eine klare Integration. Mit gezielten Korrekturen an der Ereignisausbreitung und Plänen zur Verfeinerung und Hinzufügung weiterer Funktionen sind wir gut positioniert, um diesen Prototyp in ein ausgefeiltes, funktionsreiches Tool für Händler zu verwandeln. Gute Nachrichten! Die Datei Telegram.mqh ist jetzt in der Codebase verfügbar.

Tabelle der Dateianhänge

Dateiname Beschreibung
CommunicationsDialog.mqh Definiert einen Dialog zum Versenden von Telegram-Nachrichten, der eine Texteingabe und eine Liste von Schnellnachrichtenoptionen enthält.
AdminHomeDialog.mqh Für die Erstellung des Admin-Home-Dialogs. Sie enthält alle Koordinatenangaben.
New_Admin_Panel.mqh Das neueste Admin Panel beinhaltet das Konzept der Modularität.
Telegram.mqh Zur Übermittlung von Nachrichten und Benachrichtigungen über Telegram.

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

Beigefügte Dateien |
AdminHomeDialog.mqh (16.81 KB)
Telegram.mqh (1.35 KB)
Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 16): Einführung in die Quarters Theory (II) - Intrusion Detector EA Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 16): Einführung in die Quarters Theory (II) - Intrusion Detector EA
In unserem letzten Artikel haben wir ein einfaches Skript namens „Quarters Drawer“ vorgestellt. Auf dieser Grundlage gehen wir nun den nächsten Schritt und erstellen einen Monitor Expert Advisor (EA), der diese Quarter verfolgt und einen Überblick über mögliche Marktreaktionen auf diesen Niveaus bietet. Begleiten Sie uns in diesem Artikel bei der Entwicklung eines Tools zur Zonenerkennung.
Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 15): Einführung in die Quarters-Theorie (I) - Quarters Drawer Script Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 15): Einführung in die Quarters-Theorie (I) - Quarters Drawer Script
Unterstützungs- und Widerstandspunkte sind kritische Niveaus, die potenzielle Trendumkehr und -fortsetzungen signalisieren. Obwohl es schwierig sein kann, diese Niveaus zu identifizieren, sind Sie, wenn Sie sie einmal gefunden haben, gut vorbereitet, um sich auf dem Markt zurechtzufinden. Als weitere Hilfe können Sie das in diesem Artikel vorgestellte Tool „Quarters Drawer“ verwenden, mit dem Sie sowohl primäre als auch sekundäre Unterstützungs- und Widerstandsniveaus identifizieren können.
Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 6): Stop-Out-Prävention Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 6): Stop-Out-Prävention
Schließen Sie sich unserer heutigen Diskussion an, wenn wir nach einem algorithmischen Verfahren suchen, mit dem wir die Gesamtzahl der Ausstiege aus Gewinngeschäften minimieren können. Das Problem, mit dem wir konfrontiert waren, ist sehr schwierig, und die meisten Lösungen, die in den Diskussionen in der Gemeinschaft genannt wurden, haben keine festen Regeln. Unser algorithmischer Ansatz zur Lösung des Problems erhöhte die Rentabilität unserer Handelsgeschäft und reduzierte den durchschnittlichen Verlust pro Handelsgeschäft. Es müssen jedoch noch weitere Fortschritte gemacht werden, um alle Handelsgeschäfte, die ausgestoppt werden, vollständig herauszufiltern, aber unsere Lösung ist ein guter erster Schritt, den jeder ausprobieren kann.
Automatisieren von Handelsstrategien in MQL5 (Teil 8): Aufbau eines Expert Advisors mit harmonischen Schmetterlingsmustern Automatisieren von Handelsstrategien in MQL5 (Teil 8): Aufbau eines Expert Advisors mit harmonischen Schmetterlingsmustern
In diesem Artikel bauen wir einen MQL5 Expert Advisor, um harmonische Schmetterlingsmuster zu erkennen. Wir identifizieren Umkehrpunkte und validieren Fibonacci-Levels, um das Muster zu bestätigen. Wir visualisieren dann das Muster auf dem Chart und führen automatisch Handelsgeschäfte aus, wenn es bestätigt wird.