English Русский Español Deutsch 日本語 Português
preview
从基础到进阶:处理鼠标事件

从基础到进阶:处理鼠标事件

MetaTrader 5 — 示例 |
126 0
CODE X
CODE X

引言

在前一篇文章从基础到进阶:函数指针中,我们探讨了如何在脚本中实现事件机制。文章开篇先讲解了一种特殊数据类型:指针,也就是我们已经接触过的知识点。由于前一篇文章包含非常重要的基础内容 —— 尤其适合打算深入学习图形编程的读者,我强烈建议大家仔细回顾。本文则要聚焦一个相对简单的主题。

本文的核心主题是鼠标交互以及鼠标相关事件。为保证逻辑清晰,我们从第一个知识点开始讲解。


鼠标事件

首先,我们要理解一点:鼠标这东西又蠢、又烦、又折腾人,简直像是从地狱深处爬出来的一样。它的行为看似随机,完全不会考虑代码的运行逻辑,也无视程序员设定的各类约束。尽管MetaTrader 5功能强大且灵活性很高,但在默认情况下,不会为图表窗口内运行的应用程序生成鼠标事件。为什么?根本原因在于MetaTrader 5追求出色的性能与运行效率。而鼠标事件处理机制,会对性能和效率产生负面影响。此外,基于图表的应用程序,通常在鼠标事件处理方面的优化做得很差。

您可能还没有意识到:如果应用程序对鼠标事件的处理逻辑优化不足,会引发多么严重的问题。但此议题我们会在后续文章中深入探讨,而现在先要理解另外一些要点。

读到这里您或许会产生这样的想法:“亲爱的作者,我觉得您对这个平台的鼠标机制理解有误。我们在图表上右键点击时,就能直接调出一系列操作菜单。除此之外,我们还能通过MetaTrader 5的一键交易功能直接下单并管理订单。所以您说鼠标会带来麻烦,只能说明您了解得不够。”

好的,我认可您的说法。亲爱的读者朋友们,你们的观点完全正确。然而,我并没有说鼠标对平台本身是个麻烦。我的意思是鼠标会给附加到图表上的应用程序带来麻烦。特别是当应用程序的事件处理器优化不佳,或者代码结构极其混乱的情况。而且代码体量越大,混乱程度就越高。正因如此,我总是尽量编写精简的代码,优先保证代码简洁。这就是我的考量:避免因为疏忽,写出优化不良的代码。

下一个要点:既然MetaTrader 5默认不会为挂载在图表上的应用生成鼠标事件,我们就必须编写代码,主动告知MetaTrader 5我们需要接收鼠标事件。一旦我们开启了图表的鼠标事件接收,作为开发者,就有义务在应用程序关闭时把鼠标事件禁用掉。

遗憾的是,大多数开发者都会忽略这一步。结果就是:应用退出了,之前开启的鼠标事件却没有关闭,导致MetaTrader 5持续向该图表推送鼠标事件。所以务必保持严谨有序。开启了什么资源,就要记得关闭。不要指望平台会自动帮您处理此问题。

下一条原则:不要启用超出实际需求的资源。由MetaTrader触发的各类鼠标事件,对资源的占用略有差异。如果应用程序用不到某类事件对应的资源,就不要启用它。应当保持一切简单明了,只开启真正需要的资源。

这篇引言的措辞或许略显严苛,但有必要把道理讲清楚,帮助刚入门的开发者打好基础。我见过很多开发者启用了大量不必要的资源,之后又抱怨程序达不到预期效果。

如果排查这类问题就会发现:根源就是多余资源被无谓开启。移除冗余组件之后,应用程序的性能就能恢复正常。这一点不只适用于MetaTrader 5应用程序,更是一条通用的开发准则。

接下来我们要明确的是,鼠标事件只能通过OnChartEvent事件处理器捕获。因此,只有两类程序能够接收并处理鼠标事件:一类是智能交易系统(EA),后文会对此做介绍;另一类是指标。文中虽然以指标为例,但所有内容对EA同样适用,因为所有相关数据都会被送入OnChartEvent处理器。各位读者一定要理解这点,避免产生这样的疑问:“文章只解释了指标中如何操作鼠标,却没有解释EA中该怎么做。”

所有内容都从以下这段代码开始。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09. 
10.     return INIT_SUCCEEDED;
11. };
12. //+------------------------------------------------------------------+
13. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
14. {
15.     return rates_total;
16. };
17. //+------------------------------------------------------------------+
18. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
19. {
20.     switch (id)
21.     {
22.         case CHARTEVENT_MOUSE_MOVE:
23.             break;
24.         case CHARTEVENT_MOUSE_WHEEL:
25.             break;
26.     }
27. };
28. //+------------------------------------------------------------------+
29. void OnDeinit(const int reason)
30. {
31. };
32. //+------------------------------------------------------------------+

代码01

这里,我们给出的就是一个指标的基础框架,该指标会在运行过程中使用鼠标交互。请注意,在OnChartEvent事件处理器中,可以捕获两类鼠标事件。第一个示例在第22行,第二个示例在第24行。无论选用哪一类事件,都不影响代码的编译与运行。不过,按默认设置,MetaTrader 5不会触发这两类事件,原因是这两种事件类型默认都是禁用状态。想要让OnChartEvent处理器接收其中某一类鼠标事件,必须显式启用我们需要的那类事件。

启用和禁用这些事件的操作本身非常简单,实现起来毫不费力。不过两类事件的处理方式略有区别。不知为何,CHARTEVENT_MOUSE_WHEEL(鼠标滚轮事件)的工作机制和CHARTEVENT_MOUSE_MOVE(鼠标移动事件)存在一点差异。但暂时不用纠结这点,很快您就能完全理解,并可以在更多程序里以不同方式使用鼠标交互。关键在于只要编写得当,这个工具非常实用且灵活,在图形化平台中尤其如此。

为了观察鼠标实际是如何工作的,并把一些过程直接显示在图表上,我们将启用其中一类鼠标事件,并直接在图表上输出相关信息。通过这种实操方式,我们就能掌握它的真实工作原理。为此我们会修改代码01,得到以下展示的代码02。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10. 
11.     return INIT_SUCCEEDED;
12. };
13. //+------------------------------------------------------------------+
14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
15. {
16.     return rates_total;
17. };
18. //+------------------------------------------------------------------+
19. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
20. {
21.     string sz = "";
22. 
23.     switch (id)
24.     {
25.         case CHARTEVENT_MOUSE_MOVE:
26.             Comment(sz);
27.             sz += "Mouse position X: " + (string)(short)lparam;
28.             sz += "\nMouse position Y: " + (string)(short)dparam;
29.             Comment(sz);
30.             break;
31.         case CHARTEVENT_MOUSE_WHEEL:
32.             break;
33.     }
34. };
35. //+------------------------------------------------------------------+
36. void OnDeinit(const int reason)
37. {
38.     Comment("");
39.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
40. };
41. //+------------------------------------------------------------------+

代码02

在图表上运行代码02后,我们会看到类似下图展示的效果。

动画01

请留意,当鼠标移动时,左上角会实时更新鼠标位置。然而,部分区域内的位置数值不会发生变化。为什么会这样?原因在于位置坐标不更新的区域,不属于客户区(窗口实际内容区域)。

没错,图表窗口本身是划分成多个区域的。在图形界面编程(如Windows应用开发)中,就需要考虑这类窗口区域概念,里面包含大量细节。不过我会尽量简化讲解,这样一来,您就能明白MQL5编程中真正核心的内容。

以Windows操作系统这类图形界面举例:系统里有一片超大区域叫做桌面,也就是普通用户所说的屏幕区域。屏幕上每一个像素,都对应桌面坐标系中的一个坐标点。这片区域可以先划分为两部分:一部分是任务栏所在区域,另一部分则用于放置应用窗口。不管任务栏有没有被隐藏,只要不是任务栏占用的区域,都算作应用可用区域。

举个例子方便理解,假设屏幕分辨率为1920×1080像素,也就是全高清屏幕。如果任务栏高度占用48像素,那么屏幕可用区域就是1920×1032像素。所以当应用窗口最大化、并且任务栏处于显示状态时,程序窗口只能使用上面这个尺寸,小于完整的全高清分辨率。差异肉眼几乎难以察觉。然而,需要指出的是,如果在这个缩小后的窗口里展示一张1920×1080的图片,显示效果会略有变化,因为缺失的48像素空间需要做适配处理。

程序窗口位于客户区之内,MQL5图表默认采用第四象限坐标系(后面会详细讲解),并且又将窗口本身划分为多个区域。在实践中,通常细分为三个主要区域:第一个是标题栏,第二个是窗口边框,第三个是客户区。

和桌面的逻辑一样:除去客户区之外的所有区域,都会压缩程序可用空间。鼠标坐标只在客户区内才会被上报,坐标原点(0,0)位于客户区左上角,右下角为坐标最大值。这就是标准图表模型(第四象限坐标系)下的行为。

因此,一旦鼠标光标移出MetaTrader 5图表窗口的客户区,该窗口就不再生成鼠标事件。当然,有一种办法可以让光标离开图表客户区后仍能持续接收鼠标事件。不过这属于另一个议题,以后有机会我们再探讨。

这部分内容相对简单易懂。有一个细节:客户区在屏幕上的实际位置并不重要。MetaTrader 5会自动对屏幕坐标做映射,让程序始终基于图表自身坐标系来读取坐标,就好像程序独占整个桌面屏幕一样。

MetaTrader 5的这套自动坐标映射机制,大大减少了开发者的工作量。如果是在MQL5以外的编程环境中,坐标转换工作通常需要开发者手动实现。该机制还能防止程序误捕获本不该响应的鼠标事件。

讲到这里应该清楚了。那鼠标按键呢?我们如何判断某个按键是否被按下?鼠标按键状态通过位掩码来处理。最简单的方式是在代码中使用宏定义。这样便于编写和过滤各类事件,例如同时实现鼠标点击与拖拽操作。

在讲解按键之前,先回顾:在代码02的第9行启用了鼠标事件,不过在第39行关闭了鼠标事件。由于数据的存储类型各不相同,我们需要做数据转换,让输出数值符合预期。所以在第27、28行用到了显式类型转换,原理很简单。


鼠标按键

鼠标按键相关开发是一个容易踩坑的话题,其中的冲突根源也值得一提。有些开发者觉得实现鼠标按键交互非常简单。然而,实际开发会比看起来复杂不少。乍一看,原因有点费解,绝大多数软件(包括操作系统本身)都在用鼠标按键执行各种功能。在MetaTrader 5中,鼠标中键默认调出用于分析的十字光标工具;鼠标右键则会弹出图表操作与设置菜单。

然而,我们自己开发的应用,可能需要占用这些按键,用来实现自定义功能。这就会造成用户预期与MetaTrader 5默认行为之间的冲突,部分场景下操作系统也会卷入这个冲突。对于MetaTrader 5而言,开发者可以临时接管这些鼠标按键,用于自定义逻辑。但是,如果应用从图表移除时没有恢复按键的默认行为,那就是很不规范的做法了。除非程序崩溃导致无法恢复,那种情况下,程序确实无力再做恢复。

一旦程序接管按键后没有释放,这些按键就会被程序占用。这样一来,用户在这个图表上使用MetaTrader 5期间,就无法再用这些按键的原有功能了。如果程序发生异常,占用按键后未能交还控制权,想要恢复按键原有功能,用户只能关闭当前品种图表,再重新打开。只有这样,MetaTrader 5才能重新获得这些鼠标按键的控制权。因此,在设计程序时,必须慎重考虑这一点。否则会给应用的使用者带来极差的体验。

“好的,这个问题我理解了。但具体该怎样实现呢?如果出于某些需求,我需要接管鼠标右键和中键,该怎么做?另外,要把这些按键的控制权交还给MetaTrader 5,哪种方式最合适?”亲爱的读者们,这些都是非常好的问题。刚好借着这个话题,我们来讲另一项很有意思的特性。在之前讲解键盘事件的系列文章中,没有提到我们还可以对键盘实现更高程度的控制。既然我们接下来要接管鼠标按键以及其他交互组件,正好借此演示如何完整地管理各类用户交互元素。实现该功能的代码如下:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10. //+----------------+    
11.     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
12.     ChartSetInteger(0, CHART_CONTEXT_MENU, false);
13.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
14. //+----------------+
15.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false);
16.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, false);
17. //+----------------+
18. 
19.     return INIT_SUCCEEDED;
20. };
21. //+------------------------------------------------------------------+
22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
23. {
24.     return rates_total;
25. };
26. //+------------------------------------------------------------------+
27. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
28. {
29.     string sz = "";
30. 
31.     switch (id)
32.     {
33.         case CHARTEVENT_MOUSE_MOVE:
34.             Comment(sz);
35.             sz += "Mouse position X: " + (string)(short)lparam;
36.             sz += "\nMouse position Y: " + (string)(short)dparam;
37.             Comment(sz);
38.             break;
39.         case CHARTEVENT_MOUSE_WHEEL:
40.             break;
41.     }
42. };
43. //+------------------------------------------------------------------+
44. void OnDeinit(const int reason)
45. {
46.     Comment("");
47.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
48. //+----------------+    
49.     ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
50.     ChartSetInteger(0, CHART_CONTEXT_MENU, true);
51.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
52. //+----------------+
53.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true);
54.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, true);
55. //+----------------+
56. };
57. //+------------------------------------------------------------------+

代码03

运行代码03期间,会屏蔽MetaTrader 5自带的标准交互控件与功能,例如默认的十字光标工具,甚至是按下回车键切换当前图表品种的功能。我们来看具体实现逻辑:首先在第11行,通知MetaTrader 5禁用图表拖拽功能(拖拽图表来查看屏幕外的K线)。第49行代码用于恢复MetaTrader 5的这项原生功能。

在第12行,我们禁用当前图表的右键上下文菜单。该菜单由鼠标右键唤起。第50行代码用来重新启用这个MetaTrader 5原生功能。在第13行,禁用鼠标中键唤起的标准十字光标工具。第51行代码用于恢复该功能。

在第15行,禁用键盘控制图表滚动与缩放的相关操作。第53行代码用于恢复该功能。最后,在第16行禁用回车键唤起交易品种或周期输入框的功能(用户按回车输入品种名称或时间周期)。第54行代码用于恢复该功能。

如果您直接运行代码03,对比代码运行前后图表的状态,就能更容易理解这段代码。附件中附带了完整的源码,我认为没有比这种方式更适合学习这类事件处理机制的方法了。只有动手实验并且观察运行结果,才能学会如何处理各类小问题。之所以这么说,是因为这些功能可以在程序运行的任意时刻启用或关闭。可以在某些场景下开启,另一些场景下关闭,灵活搭配。当然,本文只是做演示,用于教学目的。

现在,我们聚焦范围更广的议题:鼠标按键。同时我们还会加入键盘交互,这样就能更全面地对整套交互系统做测试。实现该功能的代码如下:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10. //+----------------+    
11.     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
12.     ChartSetInteger(0, CHART_CONTEXT_MENU, false);
13.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
14. //+----------------+
15.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false);
16.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, false);
17. //+----------------+
18. 
19.     return INIT_SUCCEEDED;
20. };
21. //+------------------------------------------------------------------+
22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
23. {
24.     return rates_total;
25. };
26. //+------------------------------------------------------------------+
27. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
28. {
29.     string sz = "";
30. 
31.     switch (id)
32.     {
33.         case CHARTEVENT_KEYDOWN:
34.             Comment(sz);
35.             sz += "Key press: " + (string)lparam;
36.             Comment(sz);
37.             break;
38.         case CHARTEVENT_MOUSE_MOVE:
39.             Comment(sz);
40.             sz += "Mouse position X: " + (string)(short)lparam;
41.             sz += "\nMouse position Y: " + (string)(short)dparam;
42.             sz += StringFormat("\nHexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam);
43.             Comment(sz);
44.             break;
45.         case CHARTEVENT_MOUSE_WHEEL:
46.             break;
47.     }
48. };
49. //+------------------------------------------------------------------+
50. void OnDeinit(const int reason)
51. {
52.     Comment("");
53.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
54. //+----------------+    
55.     ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
56.     ChartSetInteger(0, CHART_CONTEXT_MENU, true);
57.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
58. //+----------------+
59.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true);
60.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, true);
61. //+----------------+
62. };
63. //+------------------------------------------------------------------+

代码04

由于代码04和代码03一样,会让很多新手感到棘手,我们先来讲讲如何终止它。您不能用平时移除其他指标的常规方式来操作。以上这段代码会完全接管、屏蔽MetaTrader 5的各类标准交互功能,想要停止它,需要打开下图所示的界面。

图片01

当运行这个具备完全拦截能力的指标时,在MetaTrader 5中选中图片01对应的菜单项,就可以调出程序列表。如果加载的是EA,则需要打开EA列表。之后,我们就能查看当前图表上或平台中已经加载的指标或EA清单,从这里就可以将它们从图表上移除。不通过该途径是无法完成卸载的。关键点是代码彻底屏蔽了用户在该图表中对MetaTrader 5标准交互功能的访问。

现在,我们再来了解代码04实现的功能。

动画02

请务必注意,动画02仅用于演示MQL5如何处理鼠标与键盘的操作。强烈建议实际运行代码04对应的指标,以此理解鼠标按键与键盘组合能够实现哪些效果。有些现象哪怕看起来很简单,只用文字也很难描述清楚。最好亲手实操,搞清楚各类输入组件该如何搭配使用。

然而,在测试按键与键盘的组合交互时您就会发现,代码04的第42行输出的结果并不固定,会出现不一样的值。这点乍看令人费解:这种情况会在按下Shift、Ctrl,或同时按下这两个键时出现。这两个按键有一个很特别的特性:按住按键,但不移动鼠标,此时看到的是第35行代码的输出内容。

然而,如果这时您移动鼠标,MetaTrader 5就会触发事件,转而执行第42行。为什么会这样?原因在于Shift和Ctrl会和鼠标状态绑定在一起,属于位掩码的一部分。理解这一点,您就能实现许多原本很难开发的功能。因此,这点至关重要:如果您希望真正理解MQL5中鼠标和键盘的工作原理,一定要仔细研究代码04的运行输出。

接下来我们还要介绍另一类事件,也就是代码04第45行所对应处理的事件。说实话,鼠标滚轮事件的行为相当怪异,至少在MQL5中是这样。事件触发时返回的数据打包方式,和我们习惯的预期不太一样。或许正因如此,在MQL5的应用程序中,该事件并不常用。

为搞懂其中差异,我们会在事件处理器中补充代码,直观对比CHARTEVENT_MOUSE_MOVE鼠标移动事件与CHARTEVENT_MOUSE_WHEEL鼠标滚轮事件。请相信我,亲自观察运行效果会深受启发。所用的相关代码展示如下。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10.     ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);
11. //+----------------+    
12.     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
13.     ChartSetInteger(0, CHART_CONTEXT_MENU, false);
14.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
15. //+----------------+
16.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false);
17.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, false);
18. //+----------------+
19. 
20.     return INIT_SUCCEEDED;
21. };
22. //+------------------------------------------------------------------+
23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
24. {
25.     return rates_total;
26. };
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     string sz = "";
31. 
32.     switch (id)
33.     {
34.         case CHARTEVENT_KEYDOWN:
35.             Comment(sz);
36.             sz += "Key press: " + (string)lparam;
37.             Comment(sz);
38.             break;
39.         case CHARTEVENT_MOUSE_MOVE:
40.             Comment(sz);
41.             sz += " **** Chart Event Mouse Move ****";
42.             sz += "\nMouse position X: " + (string)(short)lparam;
43.             sz += "\nMouse position Y: " + (string)(short)dparam;
44.             sz += StringFormat("\nHexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam);
45.             Comment(sz);
46.             break;
47.         case CHARTEVENT_MOUSE_WHEEL:
48.             Comment(sz);
49.             sz += " **** Chart Event Mouse Wheel ****";
50.             sz += "\nMouse position X: " + (string)(short)lparam;
51.             sz += "\nMouse position Y: " + (string)(short)(lparam >> 16);
52.             sz += "\nMouse Delta: " + (string) dparam;
53.             sz += StringFormat("\nHexadecimal mask of mouse buttons is: 0x%02X", (uchar)(lparam >> 32));
54.             Comment(sz);
55.             break;
56.     }
57. };
58. //+------------------------------------------------------------------+
59. void OnDeinit(const int reason)
60. {
61.     Comment("");
62.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
63.     ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, false);
64. //+----------------+    
65.     ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
66.     ChartSetInteger(0, CHART_CONTEXT_MENU, true);
67.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
68. //+----------------+
69.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true);
70.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, true);
71. //+----------------+
72. };
73. //+------------------------------------------------------------------+

代码05

亲爱的读者,正文到此。代码05完成了本文想要演示的全部内容。请注意,这里我们没有使用任何复杂的手段去处理鼠标或键盘交互,采用清晰简洁的实现方式,方便您理解背后发生的逻辑。运行代码05,您会看到和以下动图效果相近的现象。

动画03

由此可见,代码04与代码05之间几乎没有差别。但这点细微改动,却让两段代码的用途完全不一样。在代码05的第10行,我们开启事件,用于接收用户的一切鼠标滚轮操作。与之对应,在第63行关闭该类事件的接收。然而,请重点关注事件处理器内部输出的内容,这里存在着一个根本性的差异。

请注意,CHARTEVENT_MOUSE_WHEEL(鼠标滚轮事件)的写法和CHARTEVENT_MOUSE_MOVE(鼠标移动事件)十分相似。但要留意各项数值的来源。两种事件中,鼠标位置和按键状态这些数据,存放的位置并不相同。而这正是二者巨大差异的根源。只有滚动鼠标滚轮产生动作时,我们才会收到CHARTEVENT_MOUSE_WHEEL事件。除此之外,其余所有鼠标相关事件,都会交由CHARTEVENT_MOUSE_MOVE事件处理。这也是应用程序或代码中很少使用CHARTEVENT_MOUSE_WHEEL事件的原因:除非滚轮发生滚动,否则该事件不会带来任何其他鼠标状态更新,比如鼠标移动、鼠标按键按下的信息。

因此,正如之前所述,鼠标可以是开发中的得力帮手。但根据您的开发需求不同,它也可能变成非常棘手的麻烦。


后记

或许至此您应该能体会到,这篇文章只靠研读代码,是不足以明白全部原理的。你必须编译生成可执行程序,并在任意图表上运行测试。只有这样,您才能体会到那些单靠看代码极难掌握的细节,例如结合键盘与鼠标来创建某些元素。

搞清楚这套按键状态组合机制,您就会明白:有些代码不需要启用CHARTEVENT_KEYDOWN键盘按下事件,也一样能够检测Shift和Ctrl按键是否处于按下状态。您还可以搭配不同的鼠标按键,开发更加复杂且高度自定义的工具。

附件文件中包含文中全部的示例代码。请尝试研究每一段代码在图表上的实际输出效果。多尝试移动鼠标、点击鼠标按键、敲击键盘做各类组合测试,吃透这些理论上枯燥,但实操起来十分有趣、也很容易理解的原理。

MQL5文件 描述
代码01 鼠标示例


本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/16000

附加的文件 |
Code_01.mq5 (2.88 KB)
交易中的神经网络:自适应周期分块(LightGTS) 交易中的神经网络:自适应周期分块(LightGTS)
本文将为大家介绍创新的自适应分块技术,这是一种在考虑时序数据内在周期性的前提下,对时间序列做灵活分块的方法。我们还会讲解一种高效编码技术,该技术在处理不同尺度的数据时,可以保留重要语义特征。金融市场数据具备复杂、多尺度的典型特点,上述两类方法为此类数据的精准处理开辟了新路径,能够显著提升预测结果的稳定性与可靠性。
从新手到专家:报表 EA — 搭建工作流程 从新手到专家:报表 EA — 搭建工作流程
经纪商通常会根据预定的时间表定期提供交易账户报表。这些公司通过其 API 技术,可以访问您的账户活动和交易历史,从而为您生成业绩报表。同样,MetaTrader 5 终端会存储您交易活动的详细记录,这些记录可以使用 MQL5 来创建完全定制的报表,并定义个性化的交付方式。
MQL5 中的量子神经网络(第二部分):基于 ALGLIB 马尔可夫矩阵的神经网络反向传播训练 MQL5 中的量子神经网络(第二部分):基于 ALGLIB 马尔可夫矩阵的神经网络反向传播训练
本文介绍了一种创新的量子神经网络架构,用于结合量子力学原理与现代机器学习方法进行算法交易。该系统引入了量子效应(共振、干涉、退相干)、不同时间尺度的多级记忆、结合 ALGLIB 库的马尔可夫链以及自适应参数控制。整个实现过程使用 MQL5 内置的矩阵/向量类型完成,从而降低了在 MetaTrader 5 中实现该系统的门槛。
交易中的神经网络:智能预测流程(Time-MoE) 交易中的神经网络:智能预测流程(Time-MoE)
诚邀大家一起来研究现代Time-MoE框架,该框架已针对时间序列预测任务完成适配。本文将分步实现该架构的核心组件,并同步提供原理讲解与实操示例。该方法使得您不仅能够理解模型的运行机制,还可以将相关原理应用于真实交易场景。