在 MQL5 中创建交易管理面板(第九部分):代码组织(三):通信模块
内容
引言
今天,我们的目标是在上一篇文章的基础上,继续扩展我们的新管理面板。在那篇文章中,我们引入了模块化,作为更广泛的代码组织的一个关键方面。我们介绍了 AdminHomeDialog 类,它负责创建管理主界面。这个主面板作为访问各种功能的中心枢纽,由访问控制按钮组成,这些按钮通向三个主要组件面板:
- 交易管理面板
- 通信面板
- 分析面板
这些并非系统能力的极限,因为在我们继续完善和扩展现有基础功能的同时,新功能仍然可以继续扩展。在本文中,我们特别关注将通信面板作为一个模块,并从其先前管理面板的版本出发,对其进行进一步增强。
本次讨论的关键要点:
- 理解MQL5中的类
- 开发头文件
- 从内置类继承
- 利用 ListView 头文件
- 应用颜色以获得更好的 UI 设计

新管理面板的模块化流程
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) 包含文件中的 CAppDialog 或 CDialog,因为它们是标准库中的基础对话框类。
CommunicationsDialog 和 CAdminHomeDialog 都继承自 CAppDialog,创建了一个结构化的层次结构,其中多个对话框类共享一个用于对话框功能的公共基类。这个结构将在下面的层次结构流程图中表示。

基类与自定义类的关系
首先,从您的桌面打开 MetaEditor 5,或通过按 F4 从终端启动它。
在导航器窗口中,在 Include 文件夹中找到 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_sendButton 和 m_clearButton 是用于发送或清除消息的按钮,m_quickMsgLabel 是一个文本标签,而 m_quickMessageList 是一个预设消息的列表。我们还将 m_chatId 和 m_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 与其他头文件及主程序的集成
CommunicationsDialog 在 AdminHomeDialog 内部被处理。在本节中,我将解释它的工作原理。我不会涵盖 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
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MQL5自动化交易策略(第十四部分):基于MACD-RSI统计方法的交易分层策略
将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(四) —— 测试交易策略
MQL5 交易工具包(第 7 部分):使用最近取消的挂单函数扩展历史管理 EX5 库