
基于 .Net 框架和 C# 开发图形界面(第二部分):另外的图形元素
目录
- 简介
- 安排新元素测试
- 消息框(MessageBox)
- 选项卡控件(TabControl)
- 复选框(CheckBox)
- 单选按钮(Radio Button)
- 组合框(Combo Box)
- 数字选择(NumericUpDown, 数字列表窗口)
- 日期时间选择器(DataTimePicker, 日期选择窗口)
- ElementHide 和 ElementEnable - 隐藏及禁用指定元素
- AddItem - 添加子元素
- Exception - 接收异常的事件
- 可用图形元素和事件的汇总表格
- 结论
简介
从 2018 年10月开始, MetaTrader 5 支持了与 .Net 框架库的整合。这组库实际上远不止是执行特定任务范围的框架或专用系统,例如绘制图形窗口或实现网络交互,.NET框架实际上拥有一切。它允许开发网站(Net Core,MVC),创建具有统一专业界面(Windows窗体)的系统应用程序,构建复杂的分布式系统,在节点之间进行数据交换,以及使用数据库(实体框架)。此外,.NET框架是一个由程序员和公司组成的庞大社区,拥有数千个不同的开源项目。如果交互是正确组织的,那么所有这些都可以在今天的MQL中使用。
在本文中,我们将继续开发在第一部分中创建的 GuiController 的功能。此功能旨在与基于Windows窗体技术的 .NET框架的图形功能交互。目前,在MQL图形功能上提供了大量信息,有许多不同的库通过MQL或多或少地做相同的事情。因此,我不希望读者将此材料视为“使用表单的另一个库”。事实上,本材料只是描述与.NET框架交互的一系列文章的一部分,并逐渐揭示了该软件平台的无限特性。Windows窗体只是这个平台中的一个构建块,尽管它非常方便和全面,就像.NET技术的任何一部分一样。Windows窗体图形子系统是探索此框架的一个很好的起点,经过适当的研究,它可以应用于与 .NET 框架的其他交互。此外,它还允许创建相当高效且最重要的是易于实现的交易面板、EA配置窗口、高级图形指标、机器人控制系统以及用户与交易平台之间交互的其他相关内容。
然而,为了实现所有这些令人兴奋的特性,有必要显著改进MQL程序和C#库之间的交互模块。您可能还记得,在第一节中,GuiController 模块只能与几个 WinForms 元素交互,例如按钮(Button)、文本标签(Label)、用于输入文本的文本字段(TextBox)和垂直滚动条。尽管支持很少,我们还是成功地创建了一个完整且功能相当强大的图形面板:
图 1. 文章第一部分创建的交易面板
尽管取得了令人印象深刻的成绩,但我们不会就此止步,继续改进我们的控制器。在本文中,我们将为它提供额外的图形元素,允许用户创建大多数类型的表单。
安排新元素测试
为了引入对新元素的支持,需要组织一种“测试台”。这使我们能够微调新元素的使用,并消除引入新功能时出现的潜在错误。我们的“测试台”由控制器、带有必要图形元素集的表单和用于处理这些元素的EA组成。所有窗体都将位于单个 DemoForm.exe 中。在EA中,我们将开发一个自定义参数,指定从DemoForm.exe下载的图形表单:
图 2. 选择下载的带有必要元素的自定义表单
测试EA本身非常简单,实际上,它由两部分组成:下载函数(标准的OnInit初始化函数)和图形事件处理程序函数(OnTimer函数中的事件循环)。您可能还记得,GuiController中的工作是通过调用静态方法来执行的。只有四种主要方法:
- ShowForm - 从指定构建中载入一个表单;
- HideForm - 隐藏表单;
- GetEvent - 从表单中取得一个事件;
- SendEvent - 向表单发送一个事件.
在OnInit函数中,我们将根据所选元素下载必要的窗口。函数原型如下:
int OnInit() { switch(ElementType) { case WINFORM_TAB: GuiController::ShowForm("DemoForm.exe", "tab_form"); break; case WINFORM_BUTTON: GuiController::ShowForm("DemoForm.exe", "button_form"); break; ... } ... }
在 OnTimer 函数中,我们将处理来自以下表单的事件:
//+------------------------------------------------------------------+ //| 计时器函数 | //+------------------------------------------------------------------+ void OnTimer() { //-- 根据计时器取得新的事件 for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); ... if(id == TabIndexChange) printf("Selecet new tab. Index: " + (string)lparam + " Name: " + sparam); else if(id == ComboBoxChange) printf("组合框 '" + el_name + "' 已改变 " + sparam); ... } }
这样,对于每个图形元素,我们将创建一个简短的工作示例,说明与它的交互。我们还将详细描述它支持的事件。
消息框(MessageBox)
从第二个版本开始,控制器支持消息框,这是一个标准的用户信息元素。它还向用户提供多个选项,并以所选选项的形式接收响应。
要启动消息窗口的演示,请在启动EA时选择 Windows 窗体元素类型参数中的“按钮和消息框(Buttons and MessageBox)”选项。启动EA后,会出现一个表单,提示您选择以下选项之一:
图 3. 调用消息框的示例窗体
这个窗体以及随后的所有窗体都是示范形式,因此不具备交易逻辑。但是,按下任何按钮后,EA会发送一条警告消息,请求确认所选操作。例如,单击“卖出(SELL)”时将显示以下消息窗口:
图 4. 交易EA要求确认开立新的空头头寸
用户单击其中一个按钮后,单击事件将被记住并记录在GuiController事件缓冲区中。EA以指定的频率调查事件缓冲区,并在发现新事件进入缓冲区后立即开始处理它。这样,EA需要接收“点击按钮(Button clicking)”事件,并通过发送 “MessageBox”对到来的事件做出反应。
for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; //-- 取得一个新的事件 GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); //-- 定义它的类型 - button clicking if(id == ClickOnElement) { //-- 在终端控制台上显示点击按钮的名称 printf("您按下了 '" + sparam + "' 按钮"); string msg; //-- 根据点击按钮的类型,构建 MessageBox 消息 if(el_name != "btnCancelAll") msg = "您是否想要开立一个新的 " + sparam + " 仓位?"; else msg = "您是否确定关闭所有仓位?"; //-- 使用 MessageBox 显示命令发送正在进行的事件 GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OKCancel, msg); } ... }
让我们分析发送事件的签名:
GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OKCancel, msg);
这意味着EA要求显示消息框,其中“msg”变量中的主文本显示“确定”和“取消”按钮(OKCancel)。在本文的第一部分中,我们提到SendEvent方法的第一个参数包含发送事件接收器的图形元素的名称。但是,此字段与MessageBox的工作方式不同。消息框没有绑定到特定的图形窗口或元素(尽管Windows Froms允许这样的绑定)。因此,GuiController创建新的消息窗口,它不需要目标地址。通常,在显示消息后,应该阻止与之相关的窗口。事实上,如果在显示消息时,可以重复单击“买入”或“卖出”按钮而忽略出现的消息框,则会很奇怪。因此, 在GuiController中,此事件的第一个参数的名称代表应被阻止的元素,直到用户单击其中一个MessageBox按钮。任意图形元素的阻塞函数是可选的。它是使用整数lparam变量指定的:0-不阻挡窗口,1-如果存在就阻挡。但是,使用常量而不是零和一更方便。为此,使用 BlockingControl枚举在 GuiController中定义两个常量:
- LockControl;
- NotLockControl
第一个选项在用户按下按钮之前阻止窗口,第二个选项不允许用户访问图形窗口。
除了文本,消息窗口可以包含各种按钮的组合,通过单击这些按钮,用户同意某种选择。按钮集是使用System.Windows.Forms.MessageBoxButtons系统枚举定义的。枚举元素对MQL用户不可用,因为它们是在外部生成中定义的。为了简化处理 GuiController的MQL程序员的工作,我们实现了新的枚举——具有相同参数的System.Windows.Forms.MessageBoxButtons的克隆。枚举是在 IController.cs 中定义的:
// // 概述: // 定义那些按钮在 System.Windows.Forms.MessageBox 中显示的常数. public enum MessageBoxButtons { // // 概述: // 消息框包含 OK (确定)按钮。 OK = 0, // // 概述: // 消息框包含 OK (确定)和 Cancel (取消)按钮。 OKCancel = 1, // // 概述: // 消息框中包含 Abort(退出), Retry(重试)和 Ignore (忽略)按钮。 AbortRetryIgnore = 2, // // 概述: // 消息框中包含 Yes(是), No(否), 和 Cancel (取消)按钮。 YesNoCancel = 3, // // 概述: // 消息框中包含 Yes (是)和 No (否)按钮。 YesNo = 4, // // 概述: // 消息框中包含 Retry (重试)和 Cancel (取消)按钮。 RetryCancel = 5 }
这些枚举的常量可以直接在MQL编辑器中使用,例如通过 IntelliSens,使得配置MessageBox非常方便。例如,如果我们在 SendEvent中将OKCancel替换为YesNoCancel,对话框窗口将接收另一组按钮:
GuiController::SendEvent("ButtonForm", MessageBox, LockControl, YesNoCancel, msg);
图 5. 标准的三按钮组合 - Yes/No/Cancel
除了按钮集,GuiController还支持配置消息图标以及窗口标题文本。由于 SendEvent 方法具有固定数量的参数,因此通过它传递所有设置是很有问题的,因此找到了另一种解决方案。消息文本行可以使用“|”符号分为多个部分。在这种情况下,每个部分负责一个特定的附加参数。节数可能从一个(无分隔符)到三个(两个分隔符)不等。让我们探讨几个例子,假设您希望显示一条不带图标或附加标题的简单消息,在这种情况下,消息发送格式如下:
GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "This is a simple message");
图 6. 窗口名称中没有图标和附加文本的简单消息
可以使用附加部分中的特殊常量将图标添加到消息中。假设我们想要显示一条带有警告图标的消息。为此,请将消息文本格式替换为以下格式:
GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "Warning|Your action can be dangerous");
图 7. 带有警告图标的消息
图标不仅可以用关键字设置,还可以用假名图标设置。如果我们输入“?”而不是警告,效果如下。除了警告,我们还可以设置信息、问题和错误图标。下面是图标的关键字和假名表:
图标 | 关键字 | 假名 |
---|---|---|
![]() |
Warning | ! |
![]() |
Error | !!! |
![]() |
Info | i |
![]() |
Question | ? |
除了图标,我们还可以设置消息窗口的名称。为此,请用“|”分隔文本并输入窗口名称。以下是包含错误消息的窗口的完整定义示例:
GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, "!!!|操作取消|严重错误");
图 8. 带有错误图标的命名消息窗口
控制器具有智能线路分析功能。如果我们输入“!!!!|操作已取消”,将显示带有适当消息的严重错误图标。如果在行中指示两个部分“操作被取消|严重错误”,则不显示图标,但窗口名称更改为严重错误。
选项卡控件(TabControl)
选项卡是将元素分组的方便工具:
图 9. 含有两个选项卡的面板
选项卡控件元素支持单个TabIndexChange事件,它通知用户已移动到另一个选项卡。测试EA以表单上的代码跟踪表更改为特征。让我们看看代码片段:
for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); if(id == TabIndexChange) printf("选择了新页面. Index: " + (string)lparam + " Name: " + sparam); }
TabIndexChange 事件传递两个参数: lparam 和 sparam. 第一个包含用户选择选项卡的索引。第二个包含所选选项卡的名称。例如,如果用户选择第一个选项卡,则EA会键入以下消息:
选择新选项卡. 索引: 0 名称: tabPage1
选项卡是非常有用的图形元素,并不总是需要跟踪选项卡。WindowsForm要求单个窗体的所有元素具有唯一的名称。因此,位于不同选项卡中的同一类型的两个元素是唯一的,并且根据 WindForms,应以不同的方式命名。另一方面,通常需要跟踪直接控件的按下情况,这样一个元素所在的标签并不总是重要的。然而,有时有必要跟踪这些事件,因此,GuiController提供了足够用于此元素的必要交互接口。
复选框(CheckBox)
复选框是任何图形界面的关键元素之一。尽管它很简单,但是它被用于各种各样的界面,从旧版本的Windows开始,到Web和移动应用程序结束。它允许任何选项的直观指示。此外,还可以显示由于某种原因不可用的选项,允许用户直观地选择彼此不矛盾的选项:
图 10. 使用复选框组合选择选项
复选框有三种状态:选中(Checked)、未选中(Unchecked)和部分选中(Indeterminate)。Windows Forms 使用 System.Windows.Forms.CheckState 结构来描述这些状态:
namespace System.Windows.Forms { // // 概述: // 指定控件的状态,如复选框,可以选中、取消选中, // 或设置为不确定状态。 public enum CheckState { // // 概述: // 控件没有被选中 Unchecked = 0, // // 概述: // 控件被选中 Checked = 1, // // 概述: // 控件不确定状态,不确定状态的控件通常有阴影 // 外观 Indeterminate = 2 } }
每次用户单击此复选框时,GuiController都会通过lparam变量使用 CheckBoxChange 事件将其状态传递给MQL EA。它的数值对应了枚举中的选项之一: 0 — Unchecked, 1 — Checked, 2 — Indeterminate.
在演示示例中,EA跟踪“启用EURUSD交易”和“启用GBPUSD交易”复选框的选择。一旦其中一个选项可用,它就会解锁其“允许获利”和“允许止损”子选项。相反,如果用户从其中一个主选项中删除标志,则其子选项将立即锁定。这要归功于两个事件:ElementEnable和CheckBoxChange。下面的代码介绍了EA处理复选框的算法:
void OnTimer() { //-- 根据计时器取得新的事件 for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); if(id == CheckBoxChange) ChangeEnableStopAndProfit(el_name, id, lparam, dparam, sparam); } } //+------------------------------------------------------------------+ //| 修改启用止损和获利 | //+------------------------------------------------------------------+ void ChangeEnableStopAndProfit(string el_name, int id, long lparam, double dparam, string sparam) { int id_enable = ElementEnable; if(el_name == "EURUSDEnable") { GuiController::SendEvent("EURUSDProfit", id_enable, lparam, dparam, sparam); GuiController::SendEvent("EURUSDStop", id_enable, lparam, dparam, sparam); } else if(el_name == "GBPUSDEnable") { GuiController::SendEvent("GBPUSDProfit", id_enable, lparam, dparam, sparam); GuiController::SendEvent("GBPUSDStop", id_enable, lparam, dparam, sparam); } }
一旦通知EA用户选中了其中一个复选框,它就会将当前的 ElementEnable事件“true”发送给GuiController。相反,如果用户取消选中该框,则发送ElementEnable等于'false'。由于EA和表单在不同事件的帮助下进行了交互,因此创建了一种交互效果:表单开始根据用户选择更改子元素的可访问性,尽管控制逻辑本身直接位于EA中。
单选按钮(Radio Button)
单选按钮是一个简单的图形元素,允许用户从预定义的点中选择必要的点:
图 11. 单选按钮
当用户更改他们的选择时,EA会收到两次更改事件:来自未选中按钮和来自选中按钮。使用相同的RadioButtonChange标识符跟踪这两个事件。Here is an example of its use:
for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); else if(id == RadioButtonChange) { if(lparam == true) printf("您选择了 " + sparam); else printf("您取消选择了 " + sparam); } }
lparam参数包含一个标志,通知按钮发生了什么:它是被选中(plaram=true)还是未选中(lparam=false)。单击按钮时,EA在终端中显示类似的消息:
你已经取消选择了EA 您已经选择了指标 您已经取消选择了指标 您已经选择了脚本 ...
组合框(Combo Box)
组合框是最常见的元素之一。除了复选框之外,它还应用于Web开发和现代移动应用程序。它也是Windows中最常用的元素之一:
图 12. 组合框和可用菜单项
组合框用于两种主要模式。第一个允许用户输入除现有值之外的新值:
图 13. 选择能够输入新符号的符号
第二种模式只为用户提供预先定义的菜单项,而不能选择自定义菜单项(见图11)。还有第三种模式隐藏菜单项,但很少使用,所以我们不会停留在它上面。
所有组合框显示模式都是使用DropDownStyle属性设置的。此属性通常在开发图形界面时设置一次,因此guiController没有允许您更改组合框类型的事件。但是,控制器允许跟踪组合框中元素的选择,以及输入新值。因此,ComboBox支持两个事件:它自己的ComboBoxChange和TextChange。我们的演示表单由两个组合框元素组成。第一个选项提供选择平台(MetaTrader 4/MetaTrader 5),第二个选项选择交易品种。第二个元素在默认情况下被阻止。然而,一旦用户选择了一个平台,他们就可以选择一个交易符号。下面是实现该功能的代码:
for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); if(id == ComboBoxChange) { if(el_name == "comboBox1") //-- 用户选择平台后立即取消阻止交易品种列表: GuiController::SendEvent("comboBox2", ElementEnable, 1, 0.0, ""); printf("组合框 '" + el_name + "' 已改变 " + sparam); } }
如果我们开始选择组合框元素,演示 EA 将开始在终端中显示用户选择的参数:
在 MetaTrader 5 中组合框 “comboBox1" 已经改变 在 GBPUSD 上组合框 'comboBox2' 已经改变 在 USDJPY 上组合框 'comboBox2' 已经改变 ...
数字选择(NumericUpDown, 数字列表窗口)
数字列表窗口通常用于分析系统,包括交易面板。这就是为什么这个元素是第一个包含在 GuiController中的元素。数字列表窗口允许设置控制输入类型的特定值,只能输入数字。可以使用特殊的小滚动按钮更改步长,也可以显示数字的小数位:
图 14. 数字列表窗口
GuiController 对这种元素类型支持四种事件:
- NumericChange 接收或发送包含窗口新数值的事件;
- NumericFormatChange 发送一个事件,指定数字的位数容量(在lparam变量中)及其更改步长(在dparam变量中);
- NumericMaxChange 发送指定最大可能值的事件;
- NumericMinChange 发送指定最小可能值的事件。
NumericUpDown 通过单一的 NumericChange 事件与用户交互。当用户更改此窗口中的数值时,EA将通过事件接收适当的通知。这是唯一可能的用户交互。但是,EA可以配置窗口设置最重要的参数:小数位数、更改步长以及最大和最小可接受值。所有这些参数都取决于EA逻辑及其使用的数据类型,因此,不可能在表单设置中直接定义它们。它们应该在程序启动期间定义。
测试EA包括一个小示例,说明如何使用NumericUpDown。下面提供了从图13上传表单的代码。
//+------------------------------------------------------------------+ //| EA 交易初始化函数 | //+------------------------------------------------------------------+ int OnInit() { if(ElementType != WINFORM_HIDE) EventSetMillisecondTimer(100); else EventSetMillisecondTimer(1000); switch(ElementType) { ... case WINFORM_NUMERIC: { GuiController::ShowForm(assembly, "NumericForm"); double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); double price_step = NormalizeDouble(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE), Digits()); long digits = (long)Digits(); GuiController::SendEvent("NumericForm", TextChange, 0, 0.0, "NumericForm (" + Symbol() + ")"); NumericSet("StopLoss", Digits(), ask, (double)LONG_MAX, 0.0, price_step); NumericSet("TakeProfit", Digits(), bid, (double)LONG_MAX, 0.0, price_step); break; } ... } return(INIT_SUCCEEDED); }
从代码中可以看出,在上传过程中,EA接收到当前交易品种上的数据:其询问和投标价格、小数位数和价格步长。然后,使用特殊的辅助 NumericSet 函数为 NumericUpDown表单元素设置这些参数。让我们看一下它的代码:
//+------------------------------------------------------------------+ //| 设置 NumericUpDown 参数 | //| name - NumericUpDown 元素的名称 | //| digits - 交易品种的小数位数 | //| init - 初始化双精度值 | //| max - 最大值 | //| min - 最小值 | //| step - 变化步长 | //+------------------------------------------------------------------+ void NumericSet(string name, long digits, double init, double max, double min, double step) { int id_foramt_change = NumericFormatChange; int id_change = NumericChange; int id_max = NumericMaxChange; int id_min = NumericMinChange; long lparam = 0; double dparam = 0.0; string sparam = ""; GuiController::SendEvent(name, id_max, lparam, max, sparam); GuiController::SendEvent(name, id_min, lparam, min, sparam); GuiController::SendEvent(name, id_change, lparam, init, sparam); GuiController::SendEvent(name, id_foramt_change, digits, step, sparam); }
代码可以自适应地工作,根据它所运行的交易品种,我们将看到不同的价格格式:
图 15. 每个交易品种的单独价格格式
日期时间选择器(DataTimePicker, 日期选择窗口)
此元素在概念上与 NumericUpDown类似,唯一的区别是它允许用户安全地选择日期,而不是数字:
图 16. 在 DateTimePicker元素中选择精确时间
与 NumericUpDown 元素相比,与 DateTimePicker 的交互更加简单,这是因为,与数字格式不同,数字格式取决于EA当前的交易环境,日期格式或多或少具有通用性。它可以在开发表单时设置,然后保持完整。因而 DataTimePicker 支持唯一的 DateTimePickerChange 事件,通过 lparam 参数传递和接收准确的日期。这里是使用元素的一个例子:
for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); if(id == DateTimePickerChange) printf("用户设置了新 datetime 值: " + TimeToString((datetime)lparam)); }
如果我们运行演示 EA 并把 DateTimePicker 作为演示元素,窗口类似于图15中出现的窗口,如果我们开始以各种方式更改日期和时间,则EA将响应这些事件,并在日志中显示新的日期和时间值:
User set a new datetime value: 2019.05.16 14:21 User set a new datetime value: 2019.05.16 10:21 User set a new datetime value: 2021.05.16 10:21
然而,与元素的交互比看起来要复杂一些,MQL 和 C# 有不同的时间格式,MQL采用简化的POSIX时间格式,分辨率为1秒,最早的可能日期为1970.01.01,而C#采用更高级的时间格式,分辨率为100纳秒。因此,为了与不同的系统交互,我们需要开发将一种时间格式转换为另一种时间格式的时间转换器。在 GuiController 中就使用了这样的转换器,它设计为 MtConverter 公共静态类:
/// <summary> /// MetaTrader - C# 系统转换器 /// </summary> public static class MtConverter { /// <summary> /// 把 C# 日期时间格式改为 MQL (POSIX) 日期时间格式 /// </summary> /// <param name="date_time"></param> /// <returns></returns> public static long ToMqlDateTime(DateTime date_time) { DateTime tiks_1970 = new DateTime(1970, 01, 01); if (date_time < tiks_1970) return 0; TimeSpan time_delta = date_time - tiks_1970; return (long)Math.Floor(time_delta.TotalSeconds); } /// <summary> /// 把 MQL (Posix) 时间格式转换为 C# 时间格式 /// </summary> /// <param name="mql_time">MQL 日期时间单位</param> /// <returns></returns> public static DateTime ToSharpDateTime(long mql_time) { DateTime tiks_1970 = new DateTime(1970, 01, 01); if (mql_time <= 0 || mql_time > int.MaxValue) return tiks_1970; TimeSpan time_delta = new TimeSpan(0, 0, (int)mql_time); DateTime sharp_time = tiks_1970 + time_delta; return sharp_time; } }
目前,它只包含两个方法:ToMqlDateTime 将 DateTime转换为 MQL。第二个方法将MQL时间值转换为C# DateTime结构,结果正好相反。由于datetime(mql)和 datetime(C#)类型彼此不兼容,因此转换是通过常规的“long”类型执行的,这对于所有系统都是相同的。因此,在接收到 DateTimePikerChange 事件时,我们需要显式地将lparam转换为datetime以接收正确的时间值:
//-- 显式将 long 值转换为datetime。它是完全安全的 printf("User set new datetime value: " + TimeToString((datetime)lparam));
我们不仅可以获得新的值,而且可以用类似的方式自己设定它们。例如,我们可以使用以下命令设置当前终端时间:
GuiController::SendEvent("DateTimePicker", DateTimePickerChange, ((long)TimeCurrent()), 0.0, "");
ElementHide 和 ElementEnable - 隐藏及禁用指定元素
有一些通用事件允许您控制任何 WinForms 元素,其中就有 ElementHide 和 ElementEnable 事件。如需隐藏元素,按如下方法使用 ElementHide :
GuiController::SendEvent("HideGroup", ElementHide, true, 0.0, "");
其中 HideGroup 是需要隐藏的元素名称,如需显示元素,就做对应的调用:
GuiController::SendEvent("HideGroup", ElementHide, false, 0.0, "");
请注意所使用元素的名称,WindowsForm中的单个元素可能包含内部元素,这被称为元素嵌套(elements nesting)。这样的安排允许在组级别管理所有元素。在示例中,文本框与嵌套在其中的“label”标题一起使用。在给定的频率下,框中的所有元素都将消失,然后再次出现:
图 17. 从EA中隐藏任意图形元素
此外,可以使每个元素不可用,虽然它不会消失,但使用它变得不可使用。这是一个有用的选项,允许设计更复杂和直观的界面。我们已经在标志描述中提到使用此事件。可以通过发送以下事件使元素不可用:
GuiController::SendEvent("element", ElementEnable, false, 0.0, "");
只需将“false”标志替换为“true”,即可再次激活元素:
GuiController::SendEvent("element", ElementEnable, true, 0.0, "");
AddItem — 添加子元素
某些元素可能包含其他元素,在程序启动之前,这些子元素的内容通常是未知的。假设我们需要显示一个交易品种列表,这样用户就可以选择他们需要的交易品种。出于这些目的,最合理的做法是使用组合框:
图 18. 预先设置的交易品种列表
但是,在编译图形表单的阶段不能提前输入交易品种,因为可用交易品种的列表可能因代理而异。因此,此类型的内容应动态形成。使用的命令是 AddItem 。最简单的方法是列出 MarketWatch 中所有可用的交易品种,并将其作为菜单项添加:
//+------------------------------------------------------------------+ //| EA 交易初始化函数 | //+------------------------------------------------------------------+ int OnInit() { EventSetMillisecondTimer(100); GuiController::ShowForm(assembly, "SendOrderForm"); for(int i = 0; i < SymbolsTotal(true); i++) { GuiController::SendEvent("SymbolComboBox", AddItem, 0, 0, SymbolName(i, true)); } return(INIT_SUCCEEDED); }
如您所见,这个命令非常容易使用。尽管该命令是通用的,但并非所有元素都允许向其添加子元素,目前,GuiController仅支持组合框的此事件,然而,这足以构建高效的图形界面。此列表将来可能会扩展。
Exception — 接收异常的事件
我们不可能总是写没有错误的程序,此外,在一些情况下,可能在无法预知的场景下程序执行时会出现错误。在这些情况下,能够获得所发生的数据的反馈是必要的。Exception 事件就是为了这种交互而提供的,它是由 GuiController 本身生成,再发送给到 MQL EA 中的。GuiController 在两种情况下会创建错误消息:
- 在系统异常拦截的情况下;
- 如果从EA发送的事件不支持为其选择的元素,或者事件本身具有无效值。
让我们逐个分析这些选项。为了演示第一个选项,让我们返回演示代码,即显示 NumericUpDown元素的选项。在这个启动选项中,调用一个特殊的 NumericSet函数。它会设置 NumericUpDown 元素的工作范围:
void NumericSet(string name, long digits, double init, double max, double min, double step) { int id_foramt_change = NumericFormatChange; int id_change = NumericChange; int id_max = NumericMaxChange; int id_min = NumericMinChange; long lparam = 0; double dparam = 0.0; string sparam = ""; // GuiController::SendEvent(name, id_max, lparam, max, sparam); GuiController::SendEvent(name, id_min, lparam, min, sparam); GuiController::SendEvent(name, id_change, lparam, init, sparam); GuiController::SendEvent(name, id_foramt_change, digits, step, sparam); }
此函数已被轻微更改,因此未设置元素的最大值(其中的相应字符串被注释掉),如果在黄金图表上编译后运行此表单,它会突然停止显示当前价格:
图 19. 价格设置表单中的零值
发生了什么?异常系统是用来回答这个问题的。每个异常都可以通过 GuiController::GetEvent获得,就像其他消息一样:
//-- 根据计时器取得新的事件 for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); ... if(id == Exception) printf("Unexpected exception: " + sparam); }
消息输出已经说了很多:
意外异常:“1291,32”值对于“Value”是不可接受的,"Value”应在“Minimum”到“Maximum”的范围内。 参数名称: Value 意外异常: '1291,06' 对于 'Value' 是不可接受的,"Value”应在“Minimum”到“Maximum”的范围内。 参数名称: Value
实际情况是 NumericUpDown 默认的工作范围是从 0 到 100,因此,设置黄金的当前价格(每金衡盎司1292美元)会导致错误。要避免这种情况,请使用NumericMaxChange事件通过程序扩展可接受值的范围。这就是 NumericSet 函数中注释掉的代码所做的。
当发送错误事件时,调用异常的第二个选项是可能出现的,例如,可以将事件发送到不存在的目标:
GuiController::SendEvent("empty", ElementEnable, 0, 0.0, "");
回应将如下:
意外异常: SendEvent:未找到名为“empty”的元素
我们还可以尝试发送不支持该项目的事件。例如,让我们尝试将以下文本添加到“止损水平”输入字段(NumericUpDown元素类型):
GuiController::SendEvent("StopLoss", AddItem, 0, 0.0, "New Text");
答案将非常简洁:
意外异常:元素“StopLos”不支持“Add Item”事件
异常系统为创建复杂的图形应用程序提供了宝贵的帮助,这种应用程序中的错误是不可避免的,开发速度和方便程度取决于程序员识别它们的速度。
可用图形元素和事件的汇总表格
将支持的图形元素系统化,与 GuiController协同工作是合理的。为此,我们创建一个表,其中包含有关这些元素的摘要信息以及在GuiController中使用它们的方法。“Sample usage(示例用法)”列包含一个简短的示例代码,说明如何从MQL程序中使用此元素。所讨论的代码应被视为使用元素的总体模式的一部分。例如,第一个示例的代码(MessageBox):
string msg = "!!!|操作被取消|严重错误"; GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, msg);
应当在下面的环境中考虑:
void OnTimer { //... //-- 根据计时器取得新的事件 for(static int i = 0; i < GuiController::EventsTotal(); i++) { int id; string el_name; long lparam; double dparam; string sparam; GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); if(id == MessageBox) { string msg = "!!!|操作被取消|严重错误"; GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, msg); } } }
该模式在其他示例中也以类似的方式使用。
图形元素 | 元素或事件的名称 | 主要事件 IDs | 示例用法 |
---|---|---|---|
![]() |
消息框(MessageBox) | 消息框(MessageBox) |
string msg = "!!!|操作被取消|严重错误"; GuiController::SendEvent("ButtonForm", MessageBox, LockControl, OK, msg); |
![]() |
页面(Tabs) | TabIndexChange |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("选择了新页面. 索引: " + (string)lparam + " Name: " + sparam); |
![]() |
复选框(CheckBox) | CheckBoxChange |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("选择了" + sparam + " " + lparam); |
![]() |
单选按钮(RadioButton) | RadioButtonChange |
|
![]() |
组合框(ComboBox) | ComboBoxChange |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("组合框 '" + el_name + "' 已改变 " + sparam); |
![]() |
数字调节(NumericUpDown) | NumericChange NumericFormatChange NumericMaxChange NumericMinChange |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("数值 '" + el_name + "' 已改变, 新值: " + DoubleToString(dparam, 4)); |
![]() |
日期时间选择器(DateTimePicker) | DateTimePickerChange |
|
|
Vertical Scroll | ScrollChange |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("垂直滚动部分有了新数值: " + (string)lparam); |
|
文本框(TextBox) | TextChange |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("输入了新数值: " + sparam); |
|
按钮(Button) | ClickOnElement |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("按钮 " + sparam + " 被按下"); |
![]() |
标签(Label) | TextChange |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("标签有了新的文字: " + sparam); |
除了图形元素之外,GuiController还支持使用它们的通用事件。让我们以表格的形式来安排这些事件:
事件 | 描述 | 示例用法 |
---|---|---|
ElementHide | 隐藏/恢复一个元素 |
GuiController::SendEvent("HideGroup", ElementHide, true, 0.0, ""); |
ElementEnable | 启用/禁用一个元素 |
GuiController::SendEvent("HideGroup", ElementEnable, true, 0.0, ""); |
AddItem | 把新的子元素加到所选元素中 |
GuiController::ShowForm(assembly, "SendOrderForm"); for(int i = 0; i < SymbolsTotal(true); i++) GuiController::SendEvent("SymbolComboBox", AddItem, 0, 0, SymbolName(i, true)); |
异常 | 取得在调用 CLR 时的异常 |
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam); printf("Unexpected exception: " + sparam); |
结论
我们分析了Windows窗体的主要图形元素以及与之交互的示例。这些元素很少,但它们代表了任何图形应用程序的主干。尽管它们不包括表格(交易中另一个非常重要的元素),但是您仍然可以使用它们来创建功能性的图形应用程序。
最新的 GuiController 版本附在下面,此外,还可以从GitHub存储库系统复制此版本。可以在此处找到库版本: https://github.com/PublicMqlProjects/MtGuiController.git. 您还可以复制示范窗体项目。它位于 https://github.com/PublicMqlProjects/GuiControllerElementsDemo.git. 阅读 文章的第一部分,了解如何通过版本控制系统获得最新的库版本。所附文件包含所有项目的完整源代码。其中有三个:EA调用带有元素的窗体,DemoForm.exe 构建中的一组表单,编译的 Guicontroller(source\mql5\libraries\guicontroller.dll)以及一个源代码(source\sharp\guicontroller)。另外,请注意,演示 EA 需要到已启动表单的绝对路径。在您的PC上,它将与 “assembly” EA参数中指定的不同,因此将其替换为实际路径。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/6549
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.




ClickOnElement 中的错误;
'ClickOnElement' - 未声明标识符 GuiMtController.mq5 57 16
请教下面这段代码报错怎样解决?谢谢!!!!!!!
GuiController::SendEvent("ButtonForm", MtGuiController.MessageBox,LockControl, YesNoCancel, msg);
,感谢您提供的全面信息。在新的 .NET Core 中,这也可以作为 WPF类库 使用吗?
以下是链接:
到 repo 的链接: https://github.com/5nail000/MtGui_ProfitCalculator/tree/master/mql5
mql5-script 代码:https://github.com/5nail000/MtGui_ProfitCalculator/raw/refs/heads/master/mql5/CalculateHistoryProfit.mq5
MtGuiController.dll : https://github.com/5nail000/MtGui_ProfitCalculator/raw/refs/heads/master/mql5/MtGuiController.dll
我对这个库进行了一些修改,面板不是作为一个单独的库实现的,而是集成到了基础库中。在 repo 中还提供了库和窗体的代码...