English Русский 中文 Deutsch 日本語
preview
Creación de un Panel de administración de operaciones en MQL5 (Parte IX): Organización del código (III): Módulo de comunicación

Creación de un Panel de administración de operaciones en MQL5 (Parte IX): Organización del código (III): Módulo de comunicación

MetaTrader 5Ejemplos |
20 0
Clemence Benjamin
Clemence Benjamin

Contenido


Introducción

Hoy, nuestro objetivo es ampliar nuestro nuevo panel de administración desde donde lo dejamos en el artículo anterior, donde introdujimos la modularización como un aspecto clave para una organización más amplia del código. Hemos introducido la clase AdminHomeDialog, responsable de crear la interfaz Admin Home. Este panel de inicio funciona como centro para acceder a varias funciones y consta de botones de control de acceso que conducen a tres paneles de componentes principales:

  • Panel de gestión comercial 
  • Panel de comunicaciones
  • Panel de análisis

Estos no son los límites finales del sistema, ya que nuevas características siguen siendo posibles a medida que continuamos refinando y ampliando la base existente. En esta discusión, nos centramos específicamente en el Panel de Comunicaciones como módulo, mejorándolo aún más respecto de su versión anterior dentro del monolítico Panel de Administración.

Principales conclusiones de este debate:

  • Comprensión de las clases en MQL5
  • Desarrollo de archivos de encabezado
  • Herencia de clases integradas
  • Utilizando el archivo de encabezado ListView
  • Aplicación de colores para un mejor diseño de la interfaz de usuario
Resumiendo el artículo anterior, la modularización se refiere a dividir una aplicación en unidades distintas e independientes (módulos), cada una definida en su propio archivo. Estos módulos se pueden desarrollar y mantener por separado. En este caso, el programa principal es un archivo .mq5 (New_Admin_Panel.mq5), mientras que los diálogos son clases definidas en archivos de encabezado .mqh (AdminHomeDialog.mqh y CommunicationsDialog.mqh). La estructura implica que el programa principal cree una instancia de AdminHomeDialog, que, a su vez, puede crear instancias de CommunicationsDialog y cualquier posible diálogo futuro. Vea el diagrama a continuación.

Flujo modular (New_Admin_Panel).

Flujo modular (New_Admin_Panel).

1. Programa principal (New_Admin_Panel.mq5)

  • Este es el punto de entrada de la aplicación.
  • Inicializa el sistema y crea una instancia de AdminHomeDialog, que sirve como interfaz de usuario principal.

2. AdminHomeDialog

  • Actúa como centro neurálgico para la interacción con los usuarios.
  • Responde a eventos, como clics de botones, para crear instancias de otros diálogos como CommunicationsDialog.
  • Diseñado para ser extensible, lo que le permite generar cuadros de diálogo adicionales (por ejemplo, FutureDialog) según sea necesario.

3. CommunicationsDialog

  • Un diálogo especializado responsable de una funcionalidad específica, como enviar mensajes a través de la API de Telegram.
  • Creado dinámicamente por AdminHomeDialog cuando se activa por una acción del usuario (por ejemplo, al hacer clic en el botón «Send Message»).

4. Interacciones impulsadas por eventos

  • El sistema utiliza un enfoque basado en eventos, común en aplicaciones MQL5. Por ejemplo, al hacer clic en un botón en AdminHomeDialog se activa la creación y visualización de CommunicationsDialog, que a continuación realiza su tarea (por ejemplo, interactuar con Telegram).

5. Diseño modular

  • Cada componente tiene una función distinta: el programa principal se inicializa, AdminHomeDialog gestiona la interfaz y CommunicationsDialog se encarga de las tareas de comunicación. Esta modularidad permite una fácil expansión o modificación.

Con la introducción y descripción general de nuestro nuevo programa anteriores, ahora podemos profundizar en los detalles del desarrollo de nuestro módulo pionero, CommunicationsDialog. En la parte I esta era solo una interfaz de comunicación básica, pero ahora hemos ampliado su funcionalidad incorporando nuevas características. Con este conocimiento fundamental, podemos visualizar mejor nuestra dirección. En las siguientes subsecciones, exploraremos los componentes esenciales que hacen posibles nuestras clases personalizadas.


Desarrollo de la clase CommunicationsDialog

Para mejorar la comprensión, he incluido una imagen a continuación que ilustra la jerarquía del flujo desde la clase base a nuestras clases personalizadas. Este enfoque es una forma poderosa de promover la reutilización del código. A lo largo del proyecto, hay muchos componentes de panel, cada uno de los cuales cumple una función distinta. Esta singularidad hace que cada componente del código sea adaptable también para su integración en otros proyectos.

En MQL5, la clase Dialog suele hacer referencia a CAppDialog o CDialog del archivo de inclusión (Dialog.mqh), ya que estas sirven como clases de diálogo fundamentales en la biblioteca estándar.

Tanto CommunicationsDialog como CAdminHomeDialog heredan de CAppDialog, creando una jerarquía estructurada en la que varias clases de diálogo comparten una base común para la funcionalidad del diálogo. Esta estructura se representará en el diagrama de flujo de jerarquía a continuación.

Creación de clases personalizadas desde Dialog.mqh

Relación entre la clase base y las clases personalizadas.

Para empezar, abra MetaEditor 5 desde su escritorio o ejecútelo desde el terminal pulsando F4.

En el Navegador, localice Dialog.mqh en la carpeta Includes y ábralo para consultarlo. 

Siga la imagen a continuación para obtener orientación.

Localización de Dialog.mqh

Cómo localizar la clase Dialog en MetaEditor 5.

A continuación, cree un nuevo archivo para desarrollar la nueva clase. 

Generalmente, nuestro programa se compone de secciones como configuración de encabezado, definiciones de diseño y color, declaración de clases e implementaciones de métodos. Comenzaremos con lo básico de nuestro archivo. Los comentarios del encabezado en la parte superior nos indican qué es este archivo (CommunicationsDialog.mqh), quién es su propietario (MetaQuotes Ltd., aunque puede cambiarlo por su nombre) y de dónde procede (MQL5 community). Estos comentarios son como una portada para tu código, que ayuda a otros a identificar su propósito.

A continuación, utilizamos #ifndef y #define para crear una «protección» denominada COMMUNICATIONS_DIALOG_MQH. Esto evita que el archivo se incluya varias veces en un proyecto, lo que podría provocar errores. Piensa en ello como una cerradura que dice: «Si ya me han abierto, no me vuelvas a abrir».

Las líneas #include incorporan las herramientas que necesitamos de la biblioteca MQL5. El archivo (Dialog.mqh) nos proporciona la clase de diálogo base (CAppDialog), mientras que (Button.mqh) y (Edit.mqh) proporcionan clases de botones y cuadros de texto. (Label.mqh) y (ListView.mqh) añaden una etiqueta y una lista para mensajes rápidos. Por último, (Telegram.mqh) es un archivo personalizado (que se supone que existe) que gestiona los mensajes de Telegram. Son como herramientas que tomamos prestadas de una caja de herramientas para construir nuestro diálogo. A continuación se muestra el fragmento de código para esta sección.

Sección 1: Encabezado de archivo e inclusiones

//+------------------------------------------------------------------+
//|                                         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"

Sección 2: Diseño y definiciones de color

Antes de construir el diálogo, necesitamos planificar su tamaño y apariencia. Las declaraciones #define son como configurar un plano con medidas y colores. Para el diseño, COMMS_PANEL_WIDTH (300 píxeles) y COMMS_PANEL_HEIGHT (350 píxeles) deciden qué tan grande será nuestro diálogo en la pantalla. Los márgenes (COMMS_MARGIN_LEFT, COMMS_MARGIN_TOP, COMMS_MARGIN_RIGHT) proporcionan relleno alrededor de los bordes, mientras que COMMS_GAP_VERTICAL agrega espacio entre los elementos verticalmente. Cada control tiene su propio tamaño: el cuadro de entrada tiene una altura determinada (COMMS_INPUT_HEIGHT), los botones se presentan como pequeños rectángulos (COMMS_BUTTON_WIDTH y HEIGHT), y la vista de lista y la etiqueta tienen sus propias dimensiones.

Los colores hacen que el diálogo sea bonito y legible. Utilizamos números hexadecimales (como 0x808080 para el gris oscuro) porque así es como las computadoras entienden los colores en MQL5. CLR_PANEL_BG establece el fondo principal del cuadro de diálogo, CLR_CLIENT_BG colorea el área donde se encuentran los controles, y CLR_CAPTION_BG y CLR_CAPTION_TEXT dan estilo a la barra de título. Los bordes toman CLR_BORDER_BG y CLR_BORDER, mientras que los controles como el cuadro de entrada (CLR_INPUT_BG, CLR_INPUT_TEXT) y los botones (CLR_SEND_BG, CLR_CLEAR_BG) toman sus propios colores. Los comentarios junto a cada definición explican lo que hacen, lo que facilita su modificación posterior.

// **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)

Sección 3: Declaración de clase

Ahora estamos definiendo el núcleo de nuestro diálogo: la clase CCommunicationDialog. Piensa en una clase como una receta para crear un objeto de diálogo. Decimos: "public CAppDialog" porque nuestro cuadro de diálogo se basa en CAppDialog, una clase de cuadro de diálogo ya preparada de MQL5 que nos proporciona funciones básicas de cuadro de diálogo, como una barra de título y bordes.

La sección privada enumera los ingredientes que utilizaremos dentro del diálogo. El m_inputBox es un campo de texto donde los usuarios escriben mensajes, m_sendButton y m_clearButton son botones para enviar o borrar el mensaje, m_quickMsgLabel es una etiqueta de texto y m_quickMessageList es una lista de mensajes preestablecidos. También almacenamos m_chatId y m_botToken como cadenas para Telegram, y m_quickMessages como una matriz para contener ocho opciones de mensajes rápidos.

En la sección pública, enumeramos las funciones que cualquiera puede utilizar para interactuar con nuestro diálogo. El constructor (CCommunicationDialog) configura el diálogo con un ID de chat y un token de bot, y el destructor (~CCommunicationDialog) lo limpia cuando hemos terminado. Create crea el cuadro de diálogo en el gráfico, OnEvent gestiona los clics y las acciones, y Toggle lo muestra u oculta.

En privado, tenemos funciones auxiliares para crear cada control (CreateInputBox, etc.) y controladores de eventos (OnClickSend, OnClickClear) para decidir qué sucede cuando se pulsan los botones. Son privados porque son detalles internos sobre cómo funciona el diálogo.

//+------------------------------------------------------------------+
//| 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
};

Sección 4: Constructor y destructor

El constructor es como montar un juguete nuevo antes de jugar con él. Cuando alguien crea un CCommunicationDialog, debe asignarle un chatId y un botToken (cadenas para Telegram). Las partes m_chatId(chatId) y m_botToken(botToken) copian estos datos en nuestras variables privadas para que el diálogo sepa dónde enviar los mensajes. Dentro de las llaves, rellenamos la matriz m_quickMessages con ocho frases útiles entre las que los usuarios pueden elegir. Esto ocurre cuando se inicia el diálogo, por lo que está listo para funcionar.

El destructor es el equipo de limpieza. Se ejecuta cuando se elimina el cuadro de diálogo (como cuando cierra el programa). Actualmente, está vacío porque CAppDialog maneja la mayor parte de la limpieza por nosotros y no tenemos nada adicional que ordenar. Está aquí como marcador de posición en caso de que agreguemos una limpieza especial más adelante, como liberar memoria.

//+------------------------------------------------------------------+
//| 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()
{
}

Sección 5: El método Create

El método Create es donde construimos el cuadro de diálogo en la pantalla, como si montáramos un juguete a partir de piezas. Requiere entradas como gráfico (donde aparece), nombre (un identificador único), subventana (qué ventana del gráfico) y coordenadas (x1, y1, x2, y2) para la posición y el tamaño. Primero llamamos a CAppDialog::Create para configurar la estructura básica del cuadro de diálogo. Si eso falla, devolvemos false para indicar que algo salió mal.

A continuación, Caption("Communications Panel") establece el título en la parte superior del cuadro de diálogo. ¡Y luego le damos color! ObjectSetInteger cambia los colores comunicándose directamente con las partes del cuadro de diálogo (como «Back» para el fondo y «Client» para el área de control). Utilizamos nuestras definiciones de color (por ejemplo, CLR_PANEL_BG) para que tenga un aspecto agradable. La comprobación if(!m_panel_flag) añade bordes solo si el cuadro de diálogo no es de un tipo especial (controlado por m_panel_flag, una variable de CAppDialog).

Después de eso, llamamos a funciones auxiliares para crear cada control (cuadro de entrada, botones, etc.). Si alguno falla, nos detenemos y devolvemos false. Por último, colocamos el primer mensaje rápido en el cuadro de entrada con m_inputBox.Text y llamamos a ChartRedraw() para mostrar todo en el gráfico. Devolver true significa éxito.

//+------------------------------------------------------------------+
//| 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);
}

Sección 6: Métodos de creación de controles

Estos métodos son como ensamblar las piezas de nuestro diálogo. Cada uno crea un control (cuadro de entrada, botones, etiqueta, lista) y lo coloca en el cuadro de diálogo. Todas devuelven verdadero si tienen éxito o falso si algo falla, por lo que podemos detenernos si hay algún problema.

Para CreateInputBox, calculamos posiciones utilizando nuestras definiciones de diseño (por ejemplo, COMMS_MARGIN_LEFT). El cuadro de entrada es ancho (usando ClientAreaWidth()) y alto (tres veces COMMS_INPUT_HEIGHT). Llamamos m_inputBox.Create con el ID del gráfico, un nombre único (m_name + "_InputBox") y coordenadas. Agregar lo coloca en el cuadro de diálogo y ObjectSetInteger establece sus colores.

CreateClearButton y CreateSendButton crean botones debajo del cuadro de entrada. Los apilamos verticalmente con COMMS_GAP_VERTICAL y posicionamos Send junto a Clear usando clear_button_x2. Cada uno recibe un nombre, un texto («Clear» o «Send») y colores de nuestras definiciones.

CreateQuickMsgLabel añade una etiqueta debajo de los botones, utilizando más cálculos de espaciado. Es solo texto, por lo que solo necesita un color de texto. CreateQuickMessageList crea una vista de lista aún más baja, rellenándola con nuestros mensajes rápidos desde el constructor. Cada control utiliza el mismo patrón: crear, añadir, colorear y comprobar si hay errores con mensajes de impresión para ayudar a depurar.

//+------------------------------------------------------------------+
//| 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);
}

Sección 7: Conmutación y gestión de eventos

Toggle es un sencillo interruptor para mostrar u ocultar el cuadro de diálogo. IsVisible() comprueba si está en la pantalla. Si es así, Hide() lo hace desaparecer; si no, Show() lo vuelve a mostrar. ChartRedraw() actualiza el gráfico para que puedas ver el cambio inmediatamente. Es como encender un interruptor para el diálogo.

OnEvent es el cerebro que escucha las acciones del usuario, como los clics. Obtiene un id (lo que sucedió), lparam (detalles), dparam (más detalles) y sparam (qué objeto). Si id es CHARTEVENT_OBJECT_CLICK, significa que se ha hecho clic en algo. Comprobamos si sparam (el nombre del objeto en el que se ha hecho clic) coincide con m_sendButton.Name() o m_clearButton.Name(), y luego llamamos a OnClickSend o OnClickClear. Devolver «true» indica que lo hemos gestionado.

Si id es ON_CHANGE y sparam es el nombre de la lista, el usuario ha seleccionado un mensaje rápido. lparam nos indica cuál (como un número), y colocamos ese mensaje en el cuadro de entrada con m_inputBox.Text. Si no hay ninguna coincidencia, dejamos que CAppDialog::OnEvent se encargue de ello, pasando el evento a la cadena superior.

//+------------------------------------------------------------------+
//| 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);
}

Sección 8: Controladores de eventos de botones

OnClickSend se ejecuta cuando se hace clic en el botón "Send". Toma el texto de m_inputBox.Text() y verifica si no está vacío (""). Si hay un mensaje, intenta enviarlo usando SendMessageToTelegram (una función de Telegram.mqh) con nuestro m_chatId y m_botToken. Si funciona, imprimimos un mensaje de éxito; si no, uno de fallo. Si la casilla está vacía, simplemente imprimimos una nota. ¡Ésta es la función principal del diálogo: enviar mensajes!

OnClickClear es más sencillo. Cuando se hace clic en "Clear", el texto del cuadro de entrada se establece en nada ("") con m_inputBox.Text(""), se vuelve a dibujar el gráfico para mostrar el cambio e imprime una confirmación. Es como reiniciar un formulario.

//+------------------------------------------------------------------+
//| 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.");
}

Al final del archivo, agregue #endif para cerrar la protección del encabezado:

#endif // COMMUNICATIONS_DIALOG_MQH

Esto coincide con el #ifndef del principio, resolviendo todo perfectamente.


Integración de CommunicationsDialog con otros archivos de encabezado y el programa principal

CommunicationsDialog se gestiona dentro de AdminHomeDialog. En esta sección explicaré cómo funciona. No voy a cubrir el código completo de AdminHomeDialog, ya que lo hemos analizado en profundidad en el artículo anterior.

Paso 1

Para conectar CommunicationsDialog a AdminHomeDialog, necesitamos incorporarlo a nuestro archivo. La línea #include (CommunicationsDialog.mqh) es como abrir una puerta entre ambos. Le indica a MQL5 que cargue el archivo CommunicationsDialog.mqh, lo que pone a nuestra disposición la clase CCommunicationDialog. Coloca esto cerca de la parte superior de (AdminHomeDialog.mqh), después de otras inclusiones como (Dialog.mqh) y (Button.mqh). Sin esto, AdminHomeDialog no sabría nada del panel de comunicación, por lo que es el primer paso para vincularlos.

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

Paso 2

Dentro de la clase CAdminHomeDialog, necesitamos una forma de mantener nuestro panel de comunicación. Añadimos CCommunicationDialog *m_commPanel en la sección privada, utilizando un puntero (el * significa que es una referencia a un objeto que crearemos más adelante). Es como reservar un lugar para un juguete que desempacaremos cuando lo necesitemos. También añadimos m_chatId y m_botToken como variables de cadena para almacenar los detalles de Telegram, que pasaremos a CCommunicationDialog. Son privadas porque solo esta clase necesita gestionarlas, preparando el terreno para la integración.

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
};

Paso 3

El constructor configura CAdminHomeDialog cuando se crea. Lo actualizamos para que tome chatId y botToken como entradas, que copiamos en m_chatId y m_botToken utilizando la parte : m_chatId(chatId), m_botToken(botToken). También establecemos m_commPanel en NULL (nada todavía) con: m_commPanel(NULL). Esto significa que no crearemos el panel de comunicación de inmediato, sino que esperaremos hasta que el usuario lo solicite. Es como mantener una caja cerrada hasta que estés listo para jugar con lo que hay dentro.

El destructor limpia cuando finaliza el diálogo. Comprobamos if(m_commPanel) para ver si hemos creado un panel de comunicación. Si es así, eliminar m_commPanel libera su memoria (como tirar un juguete usado), y m_commPanel = NULL garantiza que no intentemos volver a utilizarlo por error. Esto mantiene nuestro programa ordenado y evita fallos al conectarse a 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;
   }
}

Paso 4

Aquí es donde ocurre la magia: OnClickCommunications abre el panel de comunicación cuando se hace clic en el botón «Communications». Primero, comprobamos if(m_commPanel == NULL) para ver si aún no se ha creado. Si es NULL, utilizamos new CCommunicationDialog(m_chatId, m_botToken) para crear uno nuevo, pasando nuestros datos almacenados de Telegram. Es como abrir esa caja de juguetes y montarla siguiendo las instrucciones correctas.

Si falla (quizás porque la computadora se quedó sin espacio), m_commPanel permanece NULL, imprimimos un error y nos detenemos. De lo contrario, llamamos a m_commPanel.Create para crearlo en el gráfico. Utilizamos m_chart_id (el gráfico en el que estamos), «CommPanel» como nombre, m_subwin (qué ventana) y coordenadas (20, 435 para la esquina superior izquierda, 300 de ancho y 350 de alto para el tamaño). Si Create falla, imprimimos un error, lo eliminamos y restablecemos m_commPanel a NULL.

Si ya está creado o se acaba de crear correctamente, m_commPanel.Toggle() lo activa o desactiva, mostrándolo si está oculto y ocultándolo si está visible. Esta «creación diferida» significa que solo lo construimos cuando el usuario hace clic, ahorrando recursos hasta que se necesitan.

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(); 
}

Paso 5

OnEvent escucha las acciones del usuario, como los clics, y conecta CommunicationsDialog pasando eventos a este. Cuando id es CHARTEVENT_OBJECT_CLICK, comprobamos si sparam (el nombre del objeto en el que se ha hecho clic) coincide con m_commButton.Name(). Si es así, imprimimos un mensaje y llamamos a OnClickCommunications para abrir el panel, devolviendo true para indicar que lo hemos gestionado.

La parte clave de la integración es el bloque else if(m_commPanel != NULL && m_commPanel.IsVisible()). Si el panel de comunicación existe (!= NULL) y está en pantalla (IsVisible()), enviamos el evento (id, lparam, etc.) a m_commPanel.OnEvent. Esto permite que CommunicationsDialog gestione sus propios clics, como «Send» o «Clear». Devolvemos lo que devuelva m_commPanel.OnEvent, vinculando los sistemas de eventos de los dos cuadros de diálogo. Si no hay coincidencia, CAppDialog::OnEvent toma el control. Este trabajo en equipo permite que ambos diálogos respondan al usuario con fluidez.

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);
}


Pruebas y resultados 

Tras compilar con éxito el módulo de comunicaciones e integrarlo para que funcionara con el programa principal, lanzamos el programa en el gráfico del terminal sin problemas. Todos los paneles responden a los clics, como lo confirman las entradas en el registro de Expertos. Al hacer clic en el botón «Panel de comunicaciones» en AdminHomeDialog se inicia la creación de CommunicationsDialog, pero la lógica inicial lo oculta, por lo que es necesario hacer clic por segunda vez para que se muestre. Cuando el cuadro de diálogo CommunicationsDialog está visible en el gráfico, al hacer clic de nuevo en el botón se oculta, activando y desactivando así su visualización.

Sin embargo, he detectado un problema importante en la vista de lista: los eventos de clic y desplazamiento para seleccionar mensajes rápidos que enviar no funcionan como deberían, y tenemos que identificar dónde está el fallo. Creo que se trata de un problema menor que podemos solucionar. Por ahora, el concepto básico es funcional y visible, y nuestros próximos pasos consisten en perfeccionarlo y añadir más funciones.

Probando el nuevo panel de comunicaciones.

Probando el módulo de comunicaciones.


Conclusión

El desarrollo del módulo CommunicationsDialog, integrado en AdminHomeDialog.mqh, ha dado como resultado un sistema funcional y modular para la transmisión de mensajes vía Telegram dentro de nuestro nuevo panel de administración. Hemos logrado una interfaz de administración receptiva que inicia y alterna con éxito el panel de comunicación bajo demanda, como lo demuestra el registro "Expertos" que muestra eventos de clic como «Objeto clicado: m_SendButton». El enfoque de creación y alternancia diferida optimiza el uso de los recursos, mientras que el diseño basado en eventos garantiza la escalabilidad, lo que demuestra que el concepto central es visible y operativo en el gráfico. Esta estructura modular, con CAdminHomeDialog como centro y CCommunicationDialog como herramienta específica, sienta unas bases sólidas para futuras ampliaciones.

Sin embargo, siguen existiendo algunos pequeños problemas, como que los eventos de clic y desplazamiento de la vista de lista no funcionan para la selección rápida de mensajes y que los eventos de los botones del panel de comunicaciones necesitan perfeccionarse. A pesar de ello, las ventajas del sistema (eficiencia, capacidad de respuesta y una integración clara) compensan con creces las deficiencias. Con correcciones específicas para la propagación de eventos y planes para perfeccionarla y añadir más funciones, estamos en una buena posición para transformar este prototipo en una herramienta pulida y rica en funciones para los operadores. ¡Buenas noticias! El archivo Telegram.mqh ya está disponible en CodeBase.

Tabla de archivos adjuntos

Nombre del archivo Descripción
CommunicationsDialog.mqh Define un cuadro de diálogo para enviar mensajes a Telegram, con un campo de entrada de texto y una lista de opciones de mensajes rápidos.
AdminHomeDialog.mqh Para la creación del cuadro de diálogo del administrador. Contiene todas las declaraciones de coordenadas.
New_Admin_Panel.mqh El último panel de administración incorpora el concepto de modularidad.
Telegram.mqh Para transmitir mensajes y notificaciones a través de Telegram.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17044

Archivos adjuntos |
AdminHomeDialog.mqh (16.81 KB)
Telegram.mqh (1.35 KB)
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Aprendizaje automático y Data Science (Parte 34): Descomposición de series temporales, desglosando el mercado bursátil hasta su núcleo Aprendizaje automático y Data Science (Parte 34): Descomposición de series temporales, desglosando el mercado bursátil hasta su núcleo
En un mundo repleto de datos ruidosos e impredecibles, identificar patrones significativos puede resultar complicado. En este artículo, exploraremos la descomposición estacional, una potente técnica analítica que ayuda a separar los datos en sus componentes clave: tendencia, patrones estacionales y ruido. Al desglosar los datos de esta manera, podemos descubrir información oculta y trabajar con datos más claros y fáciles de interpretar.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
El componente View para tablas en el paradigma MQL5 MVC: Contenedores El componente View para tablas en el paradigma MQL5 MVC: Contenedores
En este artículo, hablaremos sobre cómo crear un control «Contenedor» que permita desplazarse por su contenido. Dentro del proceso, se mejorarán las clases ya implementadas de controles de la biblioteca gráfica.