English Русский Deutsch 日本語
preview
在 MQL5 中创建交易管理面板(第九部分):代码组织(三):通信模块

在 MQL5 中创建交易管理面板(第九部分):代码组织(三):通信模块

MetaTrader 5示例 |
204 0
Clemence Benjamin
Clemence Benjamin

内容


引言

今天,我们的目标是在上一篇文章的基础上,继续扩展我们的新管理面板。在那篇文章中,我们引入了模块化,作为更广泛的代码组织的一个关键方面。我们介绍了 AdminHomeDialog 类,它负责创建管理主界面。这个主面板作为访问各种功能的中心枢纽,由访问控制按钮组成,这些按钮通向三个主要组件面板:

  • 交易管理面板 
  • 通信面板
  • 分析面板

这些并非系统能力的极限,因为在我们继续完善和扩展现有基础功能的同时,新功能仍然可以继续扩展。在本文中,我们特别关注将通信面板作为一个模块,并从其先前管理面板的版本出发,对其进行进一步增强。

本次讨论的关键要点:

  • 理解MQL5中的类
  • 开发头文件
  • 从内置类继承
  • 利用 ListView 头文件
  • 应用颜色以获得更好的 UI 设计
回顾上一篇文章,模块化是指将应用程序分解成独立的、不同的单元(模块),每个模块都在自己的文件中定义。这些模块可以单独开发和维护。在本例中,主程序是一个 .mq5 文件 (New_Admin_Panel.mq5),而对话框则是在 .mqh 头文件(AdminHomeDialog.mqhCommunicationsDialog.mqh)中定义的类。该结构涉及主程序创建 AdminHomeDialog 的一个实例,而后者又可以创建 CommunicationsDialog 和任何潜在的未来对话框的实例。请看下图。

模块化流程图。

新管理面板的模块化流程

1. 主程序 (New_Admin_Panel.mq5)

  • 这是应用程序的入口点。
  • 它初始化系统并创建 AdminHomeDialog 的一个实例,该实例作为主要的用户界面。

2. AdminHomeDialog

  • 充当用户交互的中心枢纽。
  • 响应事件(如按钮点击),以创建其他对话框(如 CommunicationsDialog)的实例。
  • 被设计为可扩展的,允许它根据需要生成额外的对话框(例如,FutureDialog)。

3. CommunicationsDialog

  • 一个专门的对话框,负责特定功能,例如通过 Telegram API 发送消息。
  • AdminHomeDialog 在用户操作(例如,点击“发送消息”按钮)触发时动态创建。

4. 事件驱动交互

  • 系统使用事件驱动的方法,这在 MQL5 应用程序中很常见。例如,在 AdminHomeDialog 中点击按钮会触发 CommunicationsDialog 的创建和显示,然后该对话框执行其任务(例如,与 Telegram 交互)。

5. 模块化设计

  • 每个组件都有明确的角色:主程序负责初始化,AdminHomeDialog 管理界面,而 CommunicationsDialog 处理通信任务。这种模块化使得扩展或修改变得容易。

通过以上对我们新程序的介绍和概述,我们现在可以深入研究开发创新性模块 CommunicationsDialog 的细节。在第一部分中,这只是一个基本的通信界面,但现在我们通过整合新功能扩展了其功能。有了这些基础知识,我们可以更好地图形化我们的程序。在接下来的小节中,我们将探索使我们的自定义类成为可能的基础模块。


开发 CommunicationsDialog 类

为了增强理解,我在下面包含了一张图片,它展示了从基类到我们的自定义类的层次结构流程。这种方法是促进代码可重用性的强大方式。在整个项目中,有许多面板组件,每个组件都有不同的用途。这种独特性也使得每个代码组件都能适应于集成到其他项目中。

在 MQL5 中,Dialog 类通常指的是 (Dialog.mqh) 包含文件中的 CAppDialogCDialog,因为它们是标准库中的基础对话框类。

CommunicationsDialogCAdminHomeDialog 都继承自 CAppDialog,创建了一个结构化的层次结构,其中多个对话框类共享一个用于对话框功能的公共基类。这个结构将在下面的层次结构流程图中表示。

从 Dialog 构建自定义类

基类与自定义类的关系

首先,从您的桌面打开 MetaEditor 5,或通过按 F4 从终端启动它。

在导航器窗口中,在 Include 文件夹中找到 Dialog.mqh 并打开它以供参考。 

按照下图进行操作。

定位 Dialog.mqh

在 MetaEditor 5 中定位 Dialog 类

接下来,创建一个新文件来开发新类。 

通常,我们的程序由头文件设置、布局和颜色定义、类声明和方法实现等部分组成。我们将从我们文件的基础部分开始。(CommunicationsDialog.mqh)、它的所有者是谁(MetaQuotes Ltd.,尽管您可以将其更改为您的名字),以及它来自哪里(MQL5 社区)。这些注释就像您代码的标题页,帮助他人识别其用途。

接下来,我们使用 #ifndef#define 来创建一个名为 COMMUNICATIONS_DIALOG_MQH 的“保护宏”。这可以防止文件在项目中被多次包含,否则可能导致错误。可以把它想象成一个锁,上面写着:“如果我已经被打开过,就不要再打开我了。”

#include 行引入了我们从 MQL5 库中需要的工具。Dialog.mqh 为我们提供了基础的对话框类(CAppDialog),而 Button.mqh 和 Edit.mqh 提供了按钮和文本框类。Label.mqh 和 ListView.mqh 添加了标签和用于快速消息的列表。最后,Telegram.mqh 是一个自定义文件(假定其存在),用于处理 Telegram 消息传递。这些就像从工具箱中借用工具来构建我们的对话框。以下是本节的代码片段。

第 1 节:文件头和包含文件

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

第 2 节:布局和颜色定义

在构建对话框之前,我们需要规划其大小和外观。#define 语句就像用尺寸和颜色来设置一张蓝图。在布局方面,COMMS_PANEL_WIDTH(300 像素)和 COMMS_PANEL_HEIGHT(350 像素)决定了我们的对话框在屏幕上的大小。边距(COMMS_MARGIN_LEFT、COMMS_MARGIN_TOP、COMMS_MARGIN_RIGHT)在边缘周围提供内边距,而 COMMS_GAP_VERTICAL 则在项目之间垂直添加空间。每个控件都有自己的尺寸:输入框很高(COMMS_INPUT_HEIGHT),按钮是小矩形(COMMS_BUTTON_WIDTH 和 HEIGHT),而列表视图和标签也有它们各自的尺寸。

颜色使对话框美观且易于阅读。我们使用十六进制数字(例如,用 0x808080 表示深灰色),因为这是计算机在 MQL5 中理解颜色的方式。CLR_PANEL_BG 设置对话框的主背景色,CLR_CLIENT_BG 为控件所在的区域着色,而 CLR_CAPTION_BG 和 CLR_CAPTION_TEXT 则用于设置标题栏的样式。边框使用 CLR_BORDER_BG 和 CLR_BORDER,而像输入框(CLR_INPUT_BG、CLR_INPUT_TEXT)和按钮(CLR_SEND_BG、CLR_CLEAR_BG)这样的控件也有它们自己的颜色。每个 define 语句边上的注释解释了它们的作用,方便以后进行调整。

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

第 3 节:类声明

现在,我们来定义对话框的核心:CCommunicationDialog 类。您可以将一个类想象成创建对话框对象的“配方”。我们有 : public CAppDialog 是因为我们的对话框是基于 CAppDialog 构建的,这是 MQL5 中一个现成的对话框类,为我们提供了像标题栏和边框这样的基本对话框功能。

private 部分列出了我们将在对话框内部使用的“配料”。m_inputBox 是一个供用户输入消息的文本字段,m_sendButtonm_clearButton 是用于发送或清除消息的按钮,m_quickMsgLabel 是一个文本标签,而 m_quickMessageList 是一个预设消息的列表。我们还将 m_chatIdm_botToken 存储为字符串,用于 Telegram 通信,并将 m_quickMessages 存储为一个数组,用于存放八个快速消息选项。

在 public 部分,我们列出了任何人都可以用来与我们的对话框交互的函数。构造函数(CCommunicationDialog)使用聊天 ID 和机器人令牌来设置对话框,而析构函数 (~CCommunicationDialog) 在我们完成使用后进行清理。Create 方法在图表上构建对话框,OnEvent 处理点击和操作,Toggle 用于显示或隐藏它。

回到 private 部分,我们有用于创建每个控件的辅助函数(如 CreateInputBox 等)和事件处理函数(如 OnClickSend、OnClickClear),用于决定按钮被点击时发生什么。这些是私有的,因为它们是对话框工作原理的内部细节。

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

第 4 节:构造函数和析构函数

构造函数就像在玩新玩具之前对其进行设置。当有人创建一个 CCommunicationDialog 时,他们必须提供一个 chatId 和 botToken(用于 Telegram 的字符串)。m_chatId(chatId) 和 m_botToken(botToken) 这部分会将这些值复制到我们的私有变量中,以便对话框知道将消息发送到哪里。在花括号内,我们用八个用户可以挑选的便捷短语来填充 m_quickMessages 数组。这在对话框“诞生”时发生,所以它已经准备就绪。

析构函数是“清理团队”。它在对话框被删除时运行(例如,当您关闭程序时)。目前,它是空的,因为 CAppDialog 为我们处理了大部分清理工作,我们也没有任何额外的东西需要整理。它在这里作为一个占位符,以防我们以后添加特殊的清理任务,比如释放内存。

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

第 5 节:Create 方法

Create 方法是我们将对话框构建到屏幕上的地方,就像用零件组装一个玩具。它接收诸如 chart(出现位置)、name(唯一 ID)、subwin(哪个图表窗口)以及用于位置和大小的坐标(x1, y1, x2, y2)等输入。我们首先调用 CAppDialog::Create 来设置基本的对话框结构。如果失败,我们返回 false,表示出了问题。

接下来,Caption("Communications Panel") 设置对话框顶部的标题。然后,我们给它上色!ObjectSetInteger 通过直接与对话框的各个部分(如“Back”代表背景,“Client”代表控件区域)对话来更改颜色。我们使用我们定义的颜色常量(例如 CLR_PANEL_BG)来让它看起来更美观。if(!m_panel_flag) 检查仅在对话框不是特殊类型时才添加边框(由 m_panel_flag 控制,这是来自 CAppDialog 的一个变量)。

之后,我们调用辅助函数来创建每个控件(输入框、按钮等)。如果任何一个创建失败,我们就会停止并返回 false。最后,我们使用 m_inputBox.Text 将第一条快速消息放入输入框,并调用 ChartRedraw() 来在图表上显示所有内容。返回 true 表示成功。

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

第 6 节:控件创建方法

这些方法就像是在组装我们对话框的各个部件。每一个方法都会创建一个控件(输入框、按钮、标签、列表)并将其放置在对话框上。如果成功,它们都返回 true;如果失败,则返回 false,这样我们就可以在出现问题时停止操作。

对于 CreateInputBox,我们使用布局定义(例如 COMMS_MARGIN_LEFT)来计算位置。输入框很宽(使用 ClientAreaWidth()),并且很高(COMMS_INPUT_HEIGHT 的三倍)。我们使用图表 ID、一个唯一的名称(m_name + "_InputBox")和坐标来调用 m_inputBox.Create。Add 方法将其放置在对话框上,而 ObjectSetInteger 则设置其颜色。

CreateClearButton 和 CreateSendButton 在输入框下方创建按钮。我们使用 COMMS_GAP_VERTICAL 将它们垂直堆叠,并使用 clear_button_x2 将“发送”按钮放在“清除”按钮旁边。每个按钮都有一个名称、文本(“清除”或“发送”)以及来自我们定义的颜色。

CreateQuickMsgLabel 在按钮下方添加一个标签,使用了更多的间距计算。它只是文本,所以只需要一个文本颜色。CreateQuickMessageList 在更低的位置创建一个列表视图,并用我们构造函数中的快速消息来填充它。每个控件都使用相同的模式:创建、添加、着色,并通过 Print 消息检查错误以帮助调试。

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

第 7 节:切换和事件处理

Toggle 是一个用于显示或隐藏对话框的简单开关。IsVisible() 检查它是否在屏幕上。如果是,Hide() 使其消失;如果不是,Show() 将其带回。ChartRedraw() 更新图表,以便您能立即看到变化。这就像为对话框拨动电灯开关。

OnEvent 是监听用户操作(如点击)的“大脑”。它接收一个 id(发生了什么)、lparam(详细信息)、dparam(更多详细信息)和 sparam(哪个对象)。如果 id 是 CHARTEVENT_OBJECT_CLICK,则表示有东西被点击了。我们检查 sparam(被点击对象的名称)是否与 m_sendButton.Name() 或 m_clearButton.Name() 匹配,然后调用 OnClickSend 或 OnClickClear。返回 true 表示我们已经处理了它。

如果 id 是 ON_CHANGE 且 sparam 是列表的名称,则表示用户选择了一条快速消息。lparam 告诉我们选择了哪一个(作为一个数字),然后我们使用 m_inputBox.Text 将该消息放入输入框。如果没有任何匹配项,我们让 CAppDialog::OnEvent 来处理它,将事件向上传递。

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

第 8 节:按钮事件处理程序

当“发送”按钮被点击时,OnClickSend 运行。它从 m_inputBox.Text() 获取文本,并检查其是否不为空("")。如果有消息,它会尝试使用 SendMessageToTelegram(一个来自 Telegram.mqh 的函数)和我们的 m_chatId、m_botToken 来发送它。如果成功,我们打印一条成功消息;如果失败,则打印一条失败消息。如果输入框为空,我们只打印一条提示。这是对话框的主要工作——发送消息!

OnClickClear 更简单。当“清除”被点击时,它使用 m_inputBox.Text("") 将输入框文本设置为空,重绘图表以显示更改,并打印一条确认信息。这就像重置一个表单。

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

在文件末尾,添加 #endif 来关闭头文件保护宏:

#endif // COMMUNICATIONS_DIALOG_MQH

这与开头的 #ifndef 相匹配,将所有内容整齐地包裹起来。


CommunicationsDialog 与其他头文件及主程序的集成

CommunicationsDialogAdminHomeDialog 内部被处理。在本节中,我将解释它的工作原理。我不会涵盖 AdminHomeDialog 的完整代码,因为我们在上一篇文章中已经详细讨论过它。

步骤 1

要将 CommunicationsDialog 连接到 AdminHomeDialog,我们需要将其引入我们的文件。#include <CommunicationsDialog.mqh> 这一行就像在两者之间打开一扇门。它告诉 MQL5 加载 CommunicationsDialog.mqh 文件,使其 CCommunicationDialog 类可供我们使用。将此行放在 (AdminHomeDialog.mqh) 的顶部附近,在其他包含文件(如 Dialog.mqh 和 Button.mqh)之后。没有这一行,AdminHomeDialog 就不会知道通信面板的存在,因此这是链接它们的第一步。

#include <CommunicationsDialog.mqh>  // 使用增强的通信对话框

步骤 2

在 CAdminHomeDialog 类内部,我们需要一种方法来持有我们的通信面板。我们在 private 部分添加 CCommunicationDialog *m_commPanel,使用一个指针(* 表示它是一个我们稍后将创建的对象的引用)。这就像为需要时才打开的玩具预留一个位置。我们还添加 m_chatId 和 m_botToken 作为字符串变量来存储 Telegram 的详细信息,这些信息将传递给 CCommunicationDialog。这些是私有的,因为只有这个类需要管理它们,为集成做好了准备。

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

步骤 3

构造函数在 CAdminHomeDialog 创建时进行设置。我们更新它以接收 chatId 和 botToken 作为输入,并使用 : m_chatId(chatId), m_botToken(botToken) 部分将它们复制到 m_chatId 和 m_botToken 中。我们还使用 : m_commPanel(NULL) 将 m_commPanel 设置为 NULL。这意味着我们不会立即创建通信面板——我们会等到用户请求时再创建。这就像保持一个盒子关闭,直到你准备好玩里面的东西。

析构函数在对话框完成时进行清理。我们检查 if(m_commPanel) 来看我们是否已经创建了一个通信面板。如果是,delete m_commPanel 会释放其内存(就像扔掉一个用过的玩具),而 m_commPanel = NULL 确保我们不会意外地再次尝试使用它。这使我们的程序保持整洁,并在连接到 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;
   }
}

步骤 4

这就是神奇发生的地方——当“通信”按钮被点击时,OnClickCommunications 会启动通信面板。首先,我们检查 if(m_commPanel == NULL) 来看它是否尚未创建。如果是 NULL,我们使用 new CCommunicationDialog(m_chatId, m_botToken) 来创建一个新的,并传入我们存储的 Telegram 详细信息。这就像打开那个玩具箱并用正确的指令进行设置。

如果 new 失败(可能是计算机内存空间不足),m_commPanel 保持为 NULL,我们打印一个错误并停止。否则,我们调用 m_commPanel.Create 在图表上构建它。我们使用 m_chart_id(我们所在的图表)、“CommPanel” 作为名称、m_subwin(哪个窗口),以及坐标(左上角 20, 435,大小为宽 300 高 350)。如果 Create 失败,我们打印错误,删除它,并将 m_commPanel 重置为 NULL。

如果它已经创建或刚刚创建成功,m_commPanel.Toggle() 会将其打开或关闭——如果隐藏则显示,如果显示则隐藏。这种“延迟创建”意味着我们只在用户点击时才构建它,从而在需要之前节省资源。

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

步骤 5

OnEvent 监听用户操作(如点击),并通过将事件传递给它来连接CommunicationsDialog。当 id 是 CHARTEVENT_OBJECT_CLICK 时,我们检查 if sparam(被点击对象的名称)是否与 m_commButton.Name() 匹配。如果匹配,我们打印一条消息并调用 OnClickCommunications 来打开面板,返回 true 表示我们已经处理了它。

关键的集成部分是 else if(m_commPanel != NULL && m_commPanel.IsVisible()) 块。如果通信面板存在 (!= NULL) 并且在屏幕上 (IsVisible()),我们将事件(id、lparam 等)发送给 m_commPanel.OnEvent。这让 CommunicationsDialog 可以处理自己的点击,比如“发送”或“清除”。我们返回 m_commPanel.OnEvent 返回的任何值,从而链接两个对话框的事件系统。如果没有匹配项,CAppDialog::OnEvent 接管。这种团队合作让两个对话框都能平滑地响应用户。

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


测试结果 

在成功编译通信模块并将其集成到主程序中后,我们在终端图表上启动了程序,没有出现问题。所有面板都对点击做出响应,这一点已由专家日志中的条目证实。在 AdminHomeDialog 中点击“通信面板”按钮会启动 CommunicationsDialog 的创建,但初始逻辑会隐藏它,需要再次点击才能使其可见。当 CommunicationsDialog 在图表上可见时,再次点击该按钮会将其隐藏,从而有效地切换其开关状态。

然而,我注意到列表视图存在一个重大挑战:用于选择要发送的快速消息的点击和滚动事件没有按预期工作,我们需要找出问题所在。我相信这是一个我们可以修复的小问题。目前,核心概念是功能性的和可见的,我们的下一步是完善它并添加更多功能。

测试新的通信面板

测试通信模块


结论

CommunicationsDialog 模块的开发,已集成到 AdminHomeDialog.mqh 中,这为我们的新管理面板 MQL5 交易应用程序中的 Telegram 消息传递,产生了一个功能性和模块化的系统。我们已经实现了一个响应式的管理界面,它成功地按需启动和切换通信面板,这一点由专家日志中显示的点击事件(如 “Clicked object: m_SendButton”)所证明。延迟创建和切换方法优化了资源使用,而事件驱动的设计确保了可扩展性,证明了核心概念在图表上是可见和可操作的。这种模块化结构,以 CAdminHomeDialog 作为枢纽,CCommunicationDialog 作为专用工具,为未来的扩展奠定了坚实的基础。

然而,仍然存在一些小挑战,例如列表视图的点击和滚动事件不适用于快速消息选择,以及通信面板的按钮事件需要进一步完善。尽管如此,该系统的优势——效率、响应性和清晰的集成——超过了这些不足。通过对事件传播进行有针对性的修复,并计划完善它和添加更多功能,我们完全有能力将这个原型转变为一个为交易者准备的、经过打磨的、功能丰富的工具。好消息!Telegram.mqh 文件现在已在代码库中提供。

文件附件表

文件名 说明
CommunicationsDialog.mqh 定义了一个用于发送 Telegram 消息的对话框,具有文本输入和快速消息选项列表。
AdminHomeDialog.mqh 用于创建管理主对话框。它包含所有坐标声明。
New_Admin_Panel.mqh 最新的管理面板融入了模块化的概念。
Telegram.mqh 用于通过 Telegram 传输消息和通知。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17044

附加的文件 |
AdminHomeDialog.mqh (16.81 KB)
Telegram.mqh (1.35 KB)
MQL5自动化交易策略(第十四部分):基于MACD-RSI统计方法的交易分层策略 MQL5自动化交易策略(第十四部分):基于MACD-RSI统计方法的交易分层策略
本文将介绍一种结合MACD和RSI指标与统计方法的交易分层策略,通过MQL5实现动态自动化交易。我们将探讨这种级联式策略的架构设计,通过关键代码段详解其实现方式,并指导读者如何进行回测以优化策略表现。最后,我们将总结该策略的潜力,并为自动化交易的进一步优化奠定基础。
从基础到中级:模板和类型名称(四) 从基础到中级:模板和类型名称(四)
在本文中,我们将非常仔细地研究如何解决上一篇文章末尾提出的问题。尝试创建这种类型的模板,以便能够创建数据联合的模板。
将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(四) —— 测试交易策略 将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(四) —— 测试交易策略
随着当今人工智能的快速发展,语言模型(LLMs)是人工智能的重要组成部分,因此我们应该考虑如何将强大的 LLMs 整合到我们的算法交易中。对于大多数人来说,很难根据他们的需求微调这些强大的模型,在本地部署它们,然后将它们应用于算法交易。本系列文章将采取循序渐进的方法来实现这一目标。
MQL5 交易工具包(第 7 部分):使用最近取消的挂单函数扩展历史管理 EX5 库 MQL5 交易工具包(第 7 部分):使用最近取消的挂单函数扩展历史管理 EX5 库
了解如何完成历史管理 EX5 库中最终模块的创建,重点关注负责处理最近取消的挂单的函数。这将为您提供使用 MQL5 有效检索和存储与已取消挂单相关的关键详细信息的工具。