在 MQL5 中创建交易管理面板(第九部分):代码组织(二):模块化
内容:
引言
本文标志着在创建可维护的管理面板EA方面取得了突破。上一篇文章中介绍的代码组织极大地改进了我们的主代码,而今天,我们通过将关键组件模块化到外部文件中,更进了一步。这种方法确保了未来的更新可以专注于改进单个组件,而不会干扰代码的其他部分。
一个能说明其好处的明确例子是,当我需要优化“通信面板”时。在一个庞大、单一的代码库中滚动查找相关部分,实在是令人不知所措。通过将代码分解为结构化的模块,我们简化了导航,使开发和维护效率大大提高。
我们的灵感来自于那些结构良好、体现了代码组织最佳实践的项目。今天,我们将通过为定义我们程序的基本功能引入自定义类来实现模块化。下面,我们以表格形式列出了我们计划开发的完整潜在模块列表。
| 模块文件 | 说明 |
|---|---|
| AdminHomeDialog.mqh | 声明交易管理面板的中心部分,提供对程序内其他工具的访问。 |
| Aunthentication.mqh | 该模块管理用户身份验证,包括密码验证和双因素认证。 |
| ThemeManager.mqh | 负责管理管理面板的外观和样式。 |
| Telegram.mqh | 包含与 Telegram 交互的函数和类,通常用于发送消息、通知。 |
| CommunicationsDialog.mqh | 这将负责处理管理面板内与通信功能相关的用户界面(UI)和交互。 |
| AnalyticsDialog.mqh | 用于在对话框面板中显示和管理分析数据,例如交易统计、性能指标或可视化图表。 |
| TradeManagementDialog.mqh | 这将处理与交易任务相关的用户界面创建,用户可以在其中高效地执行和管理交易。 |
成功创建这些文件后,就可以将它们包含到主代码中了。
#include <Telegram.mqh> #include <Authentication.mqh> #include <AdminHomeDialog.mqh> #include <AnalyticsDialog.mqh> #include <TradeManagementDialog.mqh> #include <CommunicationDialog.mqh>
面板组件的所有声明都将放置在包含文件中,而主代码将主要包含定义。由于定义通常比声明更小,这种方法使主程序保持整洁和不杂乱,从而提高了可读性和可维护性。
我相信您已经能够预见到,随着这些创新,我们的项目正在如何演进。在下一节中,我们将详细解释模块化,然后在本项目中实施它。

主代码与头文件之间的关系
概览
通过上面引言的简要概述,我们现在将更详细地探讨模块化,然后再深入开发和实现我们的代码组件。每个模块都将得到彻底的解释,并分解每一行代码的功能。
最后,我们将所有模块集成到一个新的交易管理面板主代码库中,本质上是以增强的结构和效率从头开始重建它。
在本文结束时,我们将开发、集成并测试以下文件:
- AdminHomeDialog.mqh
- Authentication.mqh
- Telegram.mqh
模块化
在 MQL5 编程中,模块化指的是将程序分解为更小、独立且可复用的部分的实践,主要通过使用类、函数和包含文件来实现。这种方法允许开发者将特定功能封装到模块或类中,例如创建 UI 组件或交易逻辑,这些模块或类可以根据需要在应用程序的不同部分甚至多个应用程序中包含或实例化。通过这样做,代码变得更易于管理、更容易维护,并且更不容易出错,因为对一个模块的更改不一定会影响其他模块,从而促进了代码复用,提高了可读性,并方便了在 MetaTrader 5 环境中的协作开发。
在此背景下,我们已经在上面的引言中概述了我们新程序的子组件。此外,还有其他资源可供进一步阅读此主题,并且我在不同的文章中遇到了应用模块化的各种方法。
在接下来的步骤中,我将引导您详细了解每个模块的开发,确保对其实现和集成有清晰的理解。
代码实现
现在是时候应用我们的 MQL5 知识来开发交易管理面板 EA 的关键组件了。好消息是,这些文件被设计为可以轻松地适应并集成到您自己的项目中。
MQL5 中头文件的主要结构:
MQL5 中的头文件,通常带有.mqh 扩展名,是用于定义类、常量、枚举和函数原型的地方,这些可以被包含在其他 MQL5 脚本或EA中。以下是基于其他内置头文件代码的典型结构:
- 文件元数据:包括版权信息、链接和版本控制。
- 包含语句:列出当前头文件所依赖的其他头文件或库。
- 定义/常量:定义在类内部或代码其他部分使用的宏或常量,以确保值的一致性。
- 类声明:声明类及其继承关系(如果有的话)、私有成员、公共方法和受保护方法。
- 事件映射:使用宏来定义事件如何映射到成员函数,用于事件驱动编程。
- 方法实现:虽然并非所有头文件都严格要求,但在本例中,包含了方法实现,这对于封装性不是关键的小型类来说很常见。出于 MQL5 中的性能考虑,包含实现可以减少函数调用的开销。
- 构造函数和析构函数:这些是类定义的一部分,指定了类的对象如何被创建和销毁。
基于上述框架,这是一个示例代码模板:
// Meta Data here on top. #include // Include other necessary libraries or headers #include //+------------------------------------------------------------------+ //| Defines | //+------------------------------------------------------------------+ #define CONSTANT_NAME1 (value1) // Constants or macro definitions #define CONSTANT_NAME2 (value2) //+------------------------------------------------------------------+ //| Class CClass | //| Usage: Description of class purpose | //+------------------------------------------------------------------+ class CClass : public CParentClass // Inherits from another class if needed { private: // Private member variables CSomeControl m_control; // Example control member public: CClass(void); // Constructor ~CClass(void); // Destructor virtual bool Create(/* parameters */); // Virtual method for polymorphism virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); protected: // Protected methods or members bool CreateSomeControl(void); void SomeEventHandler(void); }; //+------------------------------------------------------------------+ //| Event Handling | //+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CClass) ON_EVENT(SOME_EVENT,m_control,SomeEventHandler) EVENT_MAP_END(CParentClass) // Constructor implementation CClass::CClass(void) { } // Destructor implementation CClass::~CClass(void) { } // Method implementations if included in the header bool CClass::Create(/* parameters */) { // Implementation of create method } // Event handler examples void CClass::SomeEventHandler(void) { // Handle the event } //+------------------------------------------------------------------+
要在 MetaEditor 中创建头文件,请按Ctrl + N或通过菜单手动导航来打开一个新文件。在弹出的窗口中,选择 Include (*.mqh) 并开始编辑。默认情况下,生成的模板包含一些注释作为指导说明。
参考下图:
在 MetaEditor 中创建一个新的头文件
这是默认的头文件模板,其中包含一些注释说明作为指导。
//+------------------------------------------------------------------+ //| Telegram.mqh | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" //+------------------------------------------------------------------+ //| defines | //+------------------------------------------------------------------+ // #define MacrosHello "Hello, world!" // #define MacrosYear 2010 //+------------------------------------------------------------------+ //| DLL imports | //+------------------------------------------------------------------+ // #import "user32.dll" // int SendMessageA(int hWnd,int Msg,int wParam,int lParam); // #import "my_expert.dll" // int ExpertRecalculate(int wParam,int lParam); // #import //+------------------------------------------------------------------+ //| EX5 imports | //+------------------------------------------------------------------+ // #import "stdlib.ex5" // string ErrorDescription(int error_code); // #import //+------------------------------------------------------------------+
管理主页头文件
在本节中,我们将开发 CAdminHomeDialog 类,它作为我们 MQL5 程序中管理面板的主界面。它集成了用于对话框和按钮控件的基本头文件,同时使用预定义的常量来保持一致的面板尺寸和间距。
//+------------------------------------------------------------------+ //| AdminHomeDialog.mqh | //+------------------------------------------------------------------+ #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> //+------------------------------------------------------------------+ //| Defines | //+------------------------------------------------------------------+ #define ADMIN_PANEL_WIDTH (335) #define ADMIN_PANEL_HEIGHT (350) #define INDENT_LEFT (11) #define INDENT_TOP (11) #define INDENT_RIGHT (11) #define INDENT_BOTTOM (11) #define CONTROLS_GAP_X (5) #define CONTROLS_GAP_Y (5) #define BUTTON_WIDTH (250) #define BUTTON_HEIGHT (40)
CAdminHomeDialog 类继承自 CAppDialog,并包含四个关键按钮:m_tradeMgmtButton、m_commButton、m_analyticsButton 和 m_showAllButton——它们提供到管理面板不同部分的无缝导航。该类结构保持精简,拥有最小的构造函数和析构函数,而 Create 方法确保所有按钮都被正确初始化,以提供流畅的用户体验。
//+------------------------------------------------------------------+ //| CAdminHomeDialog class | //+------------------------------------------------------------------+ class CAdminHomeDialog : public CAppDialog { private: CButton m_tradeMgmtButton; CButton m_commButton; CButton m_analyticsButton; CButton m_showAllButton; public: CAdminHomeDialog(void) {} ~CAdminHomeDialog(void) {} 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); }; //+------------------------------------------------------------------+ //| Create | //+------------------------------------------------------------------+ bool CAdminHomeDialog::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) { if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2)) return false; if(!CreateTradeMgmtButton()) return false; if(!CreateCommButton()) return false; if(!CreateAnalyticsButton()) return false; if(!CreateShowAllButton()) return false; return true; }
用户交互在 OnEvent 方法中处理,按钮点击会触发调试消息并调用它们各自的事件处理器:OnClickTradeManagement、OnClickCommunications、OnClickAnalytics 和 OnClickShowAll。这些处理器目前只记录交互,但随着我们增强功能,它们将被扩展。
//+------------------------------------------------------------------+ //| Event Handling (Enhanced Debugging) | //+------------------------------------------------------------------+ bool CAdminHomeDialog::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK) { Print("Clicked object: ", sparam); // Debug which object was clicked if(sparam == m_tradeMgmtButton.Name()) { Print("Trade Management button detected"); OnClickTradeManagement(); return true; } else if(sparam == m_commButton.Name()) { Print("Communications button detected"); OnClickCommunications(); return true; } else if(sparam == m_analyticsButton.Name()) { Print("Analytics button detected"); OnClickAnalytics(); return true; } else if(sparam == m_showAllButton.Name()) { Print("Show All button detected"); OnClickShowAll(); return true; } } return CAppDialog::OnEvent(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+ //| Button Click Handlers | //+------------------------------------------------------------------+ void CAdminHomeDialog::OnClickTradeManagement() { Print("Trade Management Panel clicked"); } void CAdminHomeDialog::OnClickCommunications() { Print("Communications Panel clicked"); } void CAdminHomeDialog::OnClickAnalytics() { Print("Analytics Panel clicked"); } void CAdminHomeDialog::OnClickShowAll() { Print("Show All clicked"); }
按钮创建方法——CreateTradeMgmtButton、CreateCommButton、CreateAnalyticsButton 和 CreateShowAllButton——动态生成具有唯一标识符、精确定位和清晰标签的按钮。“Show All”按钮甚至包含一个表情符号以增强 UI。随着我们继续开发,将引入更多的改进和优化,以提升性能和可用性。
//+------------------------------------------------------------------+ //| Control Creation Methods | //+------------------------------------------------------------------+ bool CAdminHomeDialog::CreateTradeMgmtButton() { int x = INDENT_LEFT; int y = INDENT_TOP; return m_tradeMgmtButton.Create(m_chart_id, m_name+"_TradeBtn", m_subwin, x, y, x+BUTTON_WIDTH, y+BUTTON_HEIGHT) && m_tradeMgmtButton.Text("Trade Management Panel") && Add(m_tradeMgmtButton); } bool CAdminHomeDialog::CreateCommButton() { int x = INDENT_LEFT; int y = INDENT_TOP + BUTTON_HEIGHT + CONTROLS_GAP_Y; return m_commButton.Create(m_chart_id, m_name+"_CommBtn", m_subwin, x, y, x+BUTTON_WIDTH, y+BUTTON_HEIGHT) && m_commButton.Text("Communications Panel") && Add(m_commButton); } bool CAdminHomeDialog::CreateAnalyticsButton() { int x = INDENT_LEFT; int y = INDENT_TOP + (BUTTON_HEIGHT + CONTROLS_GAP_Y) * 2; return m_analyticsButton.Create(m_chart_id, m_name+"_AnalyticsBtn", m_subwin, x, y, x+BUTTON_WIDTH, y+BUTTON_HEIGHT) && m_analyticsButton.Text("Analytics Panel") && Add(m_analyticsButton); } bool CAdminHomeDialog::CreateShowAllButton() { int x = INDENT_LEFT; int y = INDENT_TOP + (BUTTON_HEIGHT + CONTROLS_GAP_Y) * 3; return m_showAllButton.Create(m_chart_id, m_name+"_ShowAllBtn", m_subwin, x, y, x+BUTTON_WIDTH, y+BUTTON_HEIGHT) && m_showAllButton.Text("Show All 💥") && Add(m_showAllButton); }
在主程序中实现 AdminHomeDialog.mqh:
1. 通过 #include“AdminHomeDialog.mqh” 引入
#include "AdminHomeDialog.mqh"
包含 AdminHomeDialog.mqh 使得 CAdminHomeDialog 类在主脚本中可用。没有这个包含语句,编译器将无法识别CAdminHomeDialog,从而导致错误。这种模块化的方法使主脚本保持整洁,同时将对话框的实现保留在单独的文件中,以实现更好的组织和可维护性。
2. 声明为 CAdminHomeDialog ExtDialog;
CAdminHomeDialog ExtDialog;
将 ExtDialog 声明为 CAdminHomeDialog 的一个实例,使得脚本可以在整个程序中引用和控制管理主页面板。这个对象处理面板的创建、可见性和事件管理,使其在不同函数中都可访问。
3. 在 CreateHiddenPanels() 内部使用 ExtDialog 创建
bool CreateHiddenPanels() { bool success = ExtDialog.Create(0, "Admin Home", 0, MAIN_DIALOG_X, MAIN_DIALOG_Y, MAIN_DIALOG_X + MAIN_DIALOG_WIDTH, MAIN_DIALOG_Y + MAIN_DIALOG_HEIGHT); if(success) { ExtDialog.Hide(); ChartRedraw(); } return success; }
Create()方法使用特定的尺寸初始化面板,并将其正确定位在图表上。将其放在CreateHiddenPanels()内部可确保面板仅在初始化期间创建一次,从而使设置过程井然有序,并防止不必要的重新初始化。
4. 在 OnChartEvent() 中根据身份验证状态显示或隐藏
if(authManager.IsAuthenticated()) { if(!ExtDialog.IsVisible()) { ExtDialog.Show(); ChartRedraw(); } ExtDialog.ChartEvent(id, lparam, dparam, sparam); } else { if(ExtDialog.IsVisible()) { ExtDialog.Hide(); } }
管理主页面板应仅在成功身份验证后才可访问。检查 authManager.IsAuthenticated() 可确保未经授权的用户无法与面板交互。如果身份验证有效,则显示面板;否则,它保持隐藏状态,从而增强了安全性和访问控制。
5. 在脚本被移除时,在 OnDeinit() 中销毁
void OnDeinit(const int reason) { ExtDialog.Destroy(reason); }
当EA从图表上移除时,调用 ExtDialog.Destroy() 可确保为面板分配的资源被彻底清理。这可以防止潜在的内存泄漏或孤立的图形对象,这些对象可能会干扰脚本的后续实例。
Telegram 头文件
要创建 Telegram 头文件,由于其简单性和直接的操作,Telegram 函数被直接复制到头文件源代码中。然而,对于其他需要更结构化设置的文件,这种方法可能会有所不同,这些设置涉及类、方法、构造函数和析构函数,如前所述。因此,这是我们创建列表中最简单的头文件。通过将函数模块化,我们减少了主代码的长度,并且该函数可以在需要它的其他项目中轻松复用。
//+------------------------------------------------------------------+ //| Telegram.mqh | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" //+------------------------------------------------------------------+ //| Telegram.mqh - Telegram Communication Include File | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Send the message to Telegram | //+------------------------------------------------------------------+ bool SendMessageToTelegram(string message, string chatId, string botToken) { string url = "https://api.telegram.org/bot" + botToken + "/sendMessage"; string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}"; char postData[]; ArrayResize(postData, StringToCharArray(jsonMessage, postData) - 1); int timeout = 5000; char result[]; string responseHeaders; int responseCode = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, postData, result, responseHeaders); if (responseCode == 200) { Print("Message sent successfully: ", message); return true; } else { Print("Failed to send message. HTTP code: ", responseCode, " Error code: ", GetLastError()); Print("Response: ", CharArrayToString(result)); return false; } } //+------------------------------------------------------------------+
在主代码中实现 Telegram 头文件
这只需两个简单的步骤即可完成:
1. 按如下方式将文件包含到主代码中。
#include<Telegram.mqh> 以上代码仅在文件存储于 MQL5/Include 目录时才有效。否则,必须按如下方式声明子文件夹名称:
#include <FolderName\Telegram.mqh> // Replace FolderName with actual location name
2. 最后,我们需要在主代码需要时调用该函数,如下所示:
SendMessageToTelegram("Your verification code: " + ActiveTwoFactorAuthCode,
TwoFactorAuthChatId, TwoFactorAuthBotToken) 开发身份验证头文件
从之前的开发中,您已经看到了安全提示逻辑的演变,它一直被用于管理面板的每个版本中。这个逻辑也可以适用于其他与面板相关的项目,尤其是在为其功能开发机密模块时。在此阶段,我们正在开发 Authentication.mqh,它整合了以前管理面板中使用的所有安全逻辑。我将在下面分享代码,然后解释其工作原理。
//+------------------------------------------------------------------+ //| authentication.mqh | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property version "1.0" #property strict // Authentication Dialog Coordinates #define AUTH_DIALOG_X 100 #define AUTH_DIALOG_Y 100 #define AUTH_DIALOG_WIDTH 300 #define AUTH_DIALOG_HEIGHT 200 #define PASS_INPUT_X 20 #define PASS_INPUT_Y 50 #define PASS_INPUT_WIDTH 260 // Wider input field #define PASS_INPUT_HEIGHT 30 #define PASS_LABEL_X 20 #define PASS_LABEL_Y 20 #define PASS_LABEL_WIDTH 200 #define PASS_LABEL_HEIGHT 20 #define FEEDBACK_LABEL_X 20 #define FEEDBACK_LABEL_Y 100 #define FEEDBACK_LABEL_WIDTH 260 #define FEEDBACK_LABEL_HEIGHT 40 // Button spacing adjustments #define LOGIN_BTN_X 20 #define LOGIN_BTN_Y 130 #define LOGIN_BTN_WIDTH 120 #define LOGIN_BTN_HEIGHT 30 #define CANCEL_BTN_X 160 // Added 20px spacing from login button #define CANCEL_BTN_Y 130 #define CANCEL_BTN_WIDTH 120 #define CANCEL_BTN_HEIGHT 30 // Two-Factor Authentication Dialog Coordinates #define TWOFA_DIALOG_X 100 #define TWOFA_DIALOG_Y 100 #define TWOFA_DIALOG_WIDTH 300 #define TWOFA_DIALOG_HEIGHT 200 #define TWOFA_INPUT_X 20 #define TWOFA_INPUT_Y 50 #define TWOFA_INPUT_WIDTH 180 #define TWOFA_INPUT_HEIGHT 30 #define TWOFA_LABEL_X 20 #define TWOFA_LABEL_Y 20 #define TWOFA_LABEL_WIDTH 260 #define TWOFA_LABEL_HEIGHT 20 #define TWOFA_FEEDBACK_X 20 #define TWOFA_FEEDBACK_Y 100 #define TWOFA_FEEDBACK_WIDTH 260 #define TWOFA_FEEDBACK_HEIGHT 40 #define TWOFA_VERIFY_BTN_X 60 #define TWOFA_VERIFY_BTN_Y 130 #define TWOFA_VERIFY_WIDTH 120 #define TWOFA_VERIFY_HEIGHT 30 #define TWOFA_CANCEL_BTN_X 140 #define TWOFA_CANCEL_BTN_Y 130 #define TWOFA_CANCEL_WIDTH 60 #define TWOFA_CANCEL_HEIGHT 30 #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> #include <Telegram.mqh> class CAuthenticationManager { private: CDialog m_authDialog; CDialog m_2faDialog; CEdit m_passwordInput; CEdit m_2faCodeInput; CLabel m_passwordLabel; CLabel m_feedbackLabel; CLabel m_2faLabel; CLabel m_2faFeedback; CButton m_loginButton; CButton m_closeAuthButton; CButton m_2faLoginButton; CButton m_close2faButton; string m_password; string m_2faChatId; string m_2faBotToken; int m_failedAttempts; bool m_isAuthenticated; string m_active2faCode; public: CAuthenticationManager(string password, string twoFactorChatId, string twoFactorBotToken) : m_password(password), m_2faChatId(twoFactorChatId), m_2faBotToken(twoFactorBotToken), m_failedAttempts(0), m_isAuthenticated(false), m_active2faCode("") { } ~CAuthenticationManager() { m_authDialog.Destroy(); m_2faDialog.Destroy(); } bool Initialize() { if(!CreateAuthDialog() || !Create2FADialog()) { Print("Authentication initialization failed"); return false; } m_2faDialog.Hide(); // Ensure 2FA dialog starts hidden return true; } bool IsAuthenticated() const { return m_isAuthenticated; } void HandleEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK) { if(sparam == "LoginButton") HandleLoginAttempt(); else if(sparam == "2FALoginButton") Handle2FAAttempt(); else if(sparam == "CloseAuthButton") m_authDialog.Hide(); else if(sparam == "Close2FAButton") m_2faDialog.Hide(); } } private: bool CreateAuthDialog() { if(!m_authDialog.Create(0, "Authentication", 0, AUTH_DIALOG_X, AUTH_DIALOG_Y, AUTH_DIALOG_X + AUTH_DIALOG_WIDTH, AUTH_DIALOG_Y + AUTH_DIALOG_HEIGHT)) return false; if(!m_passwordInput.Create(0, "PasswordInput", 0, PASS_INPUT_X, PASS_INPUT_Y, PASS_INPUT_X + PASS_INPUT_WIDTH, PASS_INPUT_Y + PASS_INPUT_HEIGHT) || !m_passwordLabel.Create(0, "PasswordLabel", 0, PASS_LABEL_X, PASS_LABEL_Y, PASS_LABEL_X + PASS_LABEL_WIDTH, PASS_LABEL_Y + PASS_LABEL_HEIGHT) || !m_feedbackLabel.Create(0, "AuthFeedback", 0, FEEDBACK_LABEL_X, FEEDBACK_LABEL_Y, FEEDBACK_LABEL_X + FEEDBACK_LABEL_WIDTH, FEEDBACK_LABEL_Y + FEEDBACK_LABEL_HEIGHT) || !m_loginButton.Create(0, "LoginButton", 0, LOGIN_BTN_X, LOGIN_BTN_Y, LOGIN_BTN_X + LOGIN_BTN_WIDTH, LOGIN_BTN_Y + LOGIN_BTN_HEIGHT) || !m_closeAuthButton.Create(0, "CloseAuthButton", 0, CANCEL_BTN_X, CANCEL_BTN_Y, CANCEL_BTN_X + CANCEL_BTN_WIDTH, CANCEL_BTN_Y + CANCEL_BTN_HEIGHT)) return false; m_passwordLabel.Text("Enter Password:"); m_feedbackLabel.Text(""); m_feedbackLabel.Color(clrRed); m_loginButton.Text("Login"); m_closeAuthButton.Text("Cancel"); m_authDialog.Add(m_passwordInput); m_authDialog.Add(m_passwordLabel); m_authDialog.Add(m_feedbackLabel); m_authDialog.Add(m_loginButton); m_authDialog.Add(m_closeAuthButton); m_authDialog.Show(); return true; } bool Create2FADialog() { if(!m_2faDialog.Create(0, "2FA Verification", 0, TWOFA_DIALOG_X, TWOFA_DIALOG_Y, TWOFA_DIALOG_X + TWOFA_DIALOG_WIDTH, TWOFA_DIALOG_Y + TWOFA_DIALOG_HEIGHT)) return false; if(!m_2faCodeInput.Create(0, "2FAInput", 0, TWOFA_INPUT_X, TWOFA_INPUT_Y, TWOFA_INPUT_X + TWOFA_INPUT_WIDTH, TWOFA_INPUT_Y + TWOFA_INPUT_HEIGHT) || !m_2faLabel.Create(0, "2FALabel", 0, TWOFA_LABEL_X, TWOFA_LABEL_Y, TWOFA_LABEL_X + TWOFA_LABEL_WIDTH, TWOFA_LABEL_Y + TWOFA_LABEL_HEIGHT) || !m_2faFeedback.Create(0, "2FAFeedback", 0, TWOFA_FEEDBACK_X, TWOFA_FEEDBACK_Y, TWOFA_FEEDBACK_X + TWOFA_FEEDBACK_WIDTH, TWOFA_FEEDBACK_Y + TWOFA_FEEDBACK_HEIGHT) || !m_2faLoginButton.Create(0, "2FALoginButton", 0, TWOFA_VERIFY_BTN_X, TWOFA_VERIFY_BTN_Y, TWOFA_VERIFY_BTN_X + TWOFA_VERIFY_WIDTH, TWOFA_VERIFY_BTN_Y + TWOFA_VERIFY_HEIGHT) || !m_close2faButton.Create(0, "Close2FAButton", 0, TWOFA_CANCEL_BTN_X, TWOFA_CANCEL_BTN_Y, TWOFA_CANCEL_BTN_X + TWOFA_CANCEL_WIDTH, TWOFA_CANCEL_BTN_Y + TWOFA_CANCEL_HEIGHT)) return false; m_2faLabel.Text("Enter verification code:"); m_2faFeedback.Text(""); m_2faFeedback.Color(clrRed); m_2faLoginButton.Text("Verify"); m_close2faButton.Text("Cancel"); m_2faDialog.Add(m_2faCodeInput); m_2faDialog.Add(m_2faLabel); m_2faDialog.Add(m_2faFeedback); m_2faDialog.Add(m_2faLoginButton); m_2faDialog.Add(m_close2faButton); return true; } void HandleLoginAttempt() { if(m_passwordInput.Text() == m_password) { m_isAuthenticated = true; m_authDialog.Hide(); m_2faDialog.Hide(); // Ensure both dialogs are hidden } else { if(++m_failedAttempts >= 3) { Generate2FACode(); m_authDialog.Hide(); m_2faDialog.Show(); } else { m_feedbackLabel.Text(StringFormat("Invalid password (%d attempts left)", 3 - m_failedAttempts)); } } } void Handle2FAAttempt() { if(m_2faCodeInput.Text() == m_active2faCode) { m_isAuthenticated = true; m_2faDialog.Hide(); m_authDialog.Hide(); // Hide both dialogs on success } else { m_2faFeedback.Text("Invalid code - please try again"); m_2faCodeInput.Text(""); } } void Generate2FACode() { m_active2faCode = StringFormat("%06d", MathRand() % 1000000); SendMessageToTelegram("Your verification code: " + m_active2faCode, m_2faChatId, m_2faBotToken); } }; //+------------------------------------------------------------------+
所提供模块中的 CAuthenticationManager 类处理一个基于 MQL5 应用程序的多步骤用户身份验证流程。它通过一个基于对话框的界面来管理密码身份验证和双因素身份验证(2FA)。身份验证流程始于用户在对话框中输入密码。如果密码正确,则立即授予访问权限。但是,如果密码验证失败,系统会跟踪失败的尝试次数,在三次错误输入后,会触发第二个对话框进行 2FA 验证。2FA 流程包括生成一个六位数的验证码,该验证码通过 Telegram 发送给用户,用户必须输入正确的代码才能继续。
该模块为对话框元素使用预定义的坐标,以确保布局的一致性,并且它包含反馈机制,以告知用户错误或成功验证的信息。该类还集成了 Telegram.mqh 库来处理 2FA 代码的发送。它的设计考虑了可扩展性,允许轻松修改密码和 2FA 设置,并确保在身份验证成功时隐藏两个对话框。这种设计在保持强大安全功能的同时,简化了用户体验。
新的交易管理面板
在此阶段,我们正在集成所有先前开发的模块,以构建一个结构更合理、效率更高的管理面板。这个改进版本增强了组织性和模块化,使其组件可以在终端内的其他应用程序中轻松共享和复用。
新的管理面板包括用于图形界面的 AdminHomeDialog.mqh 和用于身份验证管理的 Authentication.mqh。该EA为 Telegram 聊天 ID 和用于 2FA 验证的机器人令牌定义了输入参数。在初始化期间 (OnInit),它会尝试初始化身份验证并创建一个隐藏面板 (CreateHiddenPanels),如果任一过程不成功,则初始化失败。
OnChartEvent 函数处理图表事件,在根据身份验证状态显示或隐藏 AdminHomeDialog 面板之前,会先处理身份验证。如果已通过身份验证,它会确保面板可见,并将事件转发给面板。否则,它会隐藏面板。反初始化函数 (OnDeinit) 确保对话框被正确销毁。这种设计确保了对管理面板主页的安全访问,要求在授予其功能控制权之前必须通过身份验证。
以下是新程序的完整代码:
//+------------------------------------------------------------------+ //| New Admin Panel.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property version "1.00" // Panel coordinate defines #define MAIN_DIALOG_X 30 #define MAIN_DIALOG_Y 80 #define MAIN_DIALOG_WIDTH 335 #define MAIN_DIALOG_HEIGHT 350 #include "AdminHomeDialog.mqh" #include <Authentication.mqh> // Input parameters for authentication input string TwoFactorChatID = "YOUR_CHAT_ID"; input string TwoFactorBotToken = "YOUR_BOT_TOKEN"; string AuthPassword = "2024"; CAdminHomeDialog ExtDialog; CAuthenticationManager authManager(AuthPassword, TwoFactorChatID, TwoFactorBotToken); //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(!authManager.Initialize() || !CreateHiddenPanels()) { Print("Initialization failed"); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ExtDialog.Destroy(reason); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { authManager.HandleEvent(id, lparam, dparam, sparam); if(authManager.IsAuthenticated()) { if(!ExtDialog.IsVisible()) { ExtDialog.Show(); ChartRedraw(); } // Handle dialog events only when authenticated ExtDialog.ChartEvent(id, lparam, dparam, sparam); } else { if(ExtDialog.IsVisible()) { ExtDialog.Hide(); } } } //+------------------------------------------------------------------+ //| Create hidden panels | //+------------------------------------------------------------------+ bool CreateHiddenPanels() { bool success = ExtDialog.Create(0, "Admin Home", 0, MAIN_DIALOG_X, MAIN_DIALOG_Y, MAIN_DIALOG_X + MAIN_DIALOG_WIDTH, MAIN_DIALOG_Y + MAIN_DIALOG_HEIGHT); if(success) { ExtDialog.Hide(); ChartRedraw(); } return success; }
测试
在此,我们展示了在开发各个组件并将其逻辑地集成到新的管理面板之后的测试结果。我们成功地编译了程序并在图表上运行,如下所示。

在 EURUSD 图表上测试新的管理面板
结论
我相信您能体会到我们是如何简化管理面板的组织结构的。通过继承 CAppDialog,我们显著提高了应用程序的响应性,首次使我们能够自由地在图表上拖动面板。此外,从基类继承的最小化按钮使我们能够最小化应用程序,在保持其后台运行的同时,提供无遮挡的图表视图。面板可以在需要时随时最大化,确保了无缝的可用性。
我们对可读性、可扩展性和模块化的关注,驱使我们分别开发了每个组件。这种方法增强了代码的可复用性,使我们能够通过简单地包含相关文件和调用必要方法,将这些模块集成到其他程序中。展望未来,我们计划完成剩余的组件,这些组件将使新的管理面板更加强大。这些组件包括 CommunicationsDialog.mqh、TradeManagementDialog.mqh 和 AnalyticsDialog.mqh,每一个都被设计为可在不同应用程序中复用。
作为起点,您可以尝试在自己的程序中实现 Telegram.mqh,看看它集成起来是多么容易。我在下面附上了所有必要的文件——享受测试和进一步开发它们的乐趣吧!
| 文件 | 说明 |
|---|---|
| New Admin Panel .mqh | 最新的管理面板融入了模块化的概念。 |
| Telegram.mqh | 用于通过 Telegram 传输消息和通知。 |
| Authentication.mqh | 此文件包含所有安全声明和完整的逻辑。 |
| AdminHomeDialog.mqh | 用于创建管理主对话框。它包含所有坐标声明。 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16562
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
价格行为分析工具包开发(第十三部分):RSI 哨兵工具
在MQL5中构建带自定义画布图形的凯特纳通道(Keltner Channel)指标
分析交易所价格的二进制代码(第二部分):转换为 BIP39 并编写 GPT 模型