从基础到中级:对象事件(一)
概述
在上一篇文章“从基础到中级:对象(四)”中,我们展示了如何修改MetaTrader 5中已有的对象以创建另一个对象。这样做是出于一个颇有意思的目的,而这个目的本身也很耐人寻味。亲爱的读者,在那篇文章中,我也给你出了一个小挑战,但我不打算给出答案。原因在于,我想鼓励你练习并研究文章材料,以便你在学习和实践新想法的过程中,能够学会独立解决必然会遇到的问题。
尽管这个任务非常简单,而且这个问题本身并不影响把这段代码用作学习材料,但我认为,如果你能自己解决它,探索并分析每种情况,直到找到最佳解决方案,你会更加为自己感到骄傲,并对所学内容更有信心。因此,在本文中,我们将探讨另一个话题,从某种意义上说,它与图形对象的使用相关,尽管它与迄今为止所展示的内容有所不同。
你可能已经猜到,我们正在对这一话题给予相当的关注,甚至可能超过了很多人认为真正必要的程度。在我们深入探讨 MQL5 的功能之前,我们需要先了解几件事。所有这些都有助于实现一个更宏大的目标:创建专为特定目的设计的更复杂的应用程序,这些应用程序不同于我们在任何其他程序中发现的任何东西,至少在 MetaTrader 5 这个语境下,确实如此。
好的,让我们继续讨论一个新的话题。这样我们就可以开始讨论这篇文章的主要内容了。
对象事件
至此,我们已经展示并解释了如何处理用户在与 MetaTrader 5 交互时产生的事件,以及如何在图表层面处理这些事件。例如,当我们按下某个键或使用鼠标执行某些操作时。在图表上放置一个对象就是这样的操作之一:虽然它主要是通过鼠标执行的,但它也会产生与之相关的其他事件。
这些事件本质上会导致 MetaTrader 5 触发其他事件,而这些事件在这种情况下与对象有关。因此,它们被以不同的方式处理,至少这是预期的处理方式。MetaTrader 5 专门生成这些事件来帮助程序员,从而帮助应用程序本身处理图表上对象所执行的任何操作。
这些事件可以通过鼠标点击、移动、创建、删除,甚至修改对象的某个属性来触发。然而,并非所有事件都会被触发,因为其中一些事件必须被启用。原因很简单:在 MetaTrader 5 中,这些事件默认是禁用的。这与鼠标事件的工作原理非常相似。应用程序可能对接收此类事件的通知不感兴趣,因此 MetaTrader 5 会直接忽略这些事件。这样一来,我们的环境运行速度相对较快,出现性能问题或平台本身稳定性问题的可能性也较低。
但有时这类事件是必要的。在这种情况下,我们需要了解如何处理它们,因为可能有必要进行修正或调整,而这恰恰是因为用户对图形对象进行了更改。
亲爱的读者,手动分析图形对象的变化并不是您想要做的事情:这不仅会消耗大量 CPU 时间,还会使代码变得异常复杂。但正是因为 MetaTrader 5 为我们提供了图表变化的信息,我们才能大大减少监控图表上每个单独对象所需的工作量。这使我们能够准确了解用户何时、何地以及为何更改一个仅用于提供信息的对象。
此时,你可能还没有意识到,MetaTrader 5 生成的事件对于向我们的应用程序通知对象更改的重要性。此外,你可能还没有充分意识到,如果恶意用户开始更改某些对象的特定属性,他们可以多么严重地干扰或破坏你的应用程序的运行。这就是为什么我们需要讨论对象事件的原因。这无疑是一个可以帮助你避免问题,甚至理解应用程序在其整个生命周期中可能发生的某些故障原因的主题。
通过更新应用程序,你还可以降低故障风险。然而,与此同时,你可能会忽略一些真正需要分析的重要问题。
由于 MetaTrader 5 对某个对象执行操作后产生的特定事件,在不同的应用程序中往往会有不同的解释,因此我们不会尝试为此创建一个固定的实现模型或协议。亲爱的读者,我希望你试着理解我将要用到的概念。我还想让你明白,有时需要监控每一个操作,而有时我们可以完全忽略涉及某个对象的某些类型的活动,无论这个对象是我们的应用程序放置在图表上的,还是用户手动添加到同一图表上的。凡事都有个时机。所以,首先尝试理解概念本身,而不是代码本身,因为代码可能会发生根本性的变化。
所以,在我们开始之前,需要明确一点。通常,当我们处理更具体的任务,即实现某个特定目标时,MetaTrader 5 处理对象的标准方式可能并不最合适。在这种情况下,除了 MetaTrader 5 默认使用的规则外,我们还需要制定额外的规则。
为了确保我们应用程序中设定的规则得到遵守,我们使用了 MetaTrader 5 提供的内置机制,该机制能让我们了解特定对象发生了什么。这样,我们就不必时刻监控用户可能对某个对象进行的操作。如果对象发生了任何变化 — 无论是用户向图表中添加新对象、修改对象,还是删除对象 — MetaTrader 5 都会通知我们,这样我们就能采取必要的行动。
理解这一点极其重要,甚至可能比理解代码本身更为重要。总之,有些事件需要启用,而有些则不需要。了解何时启用或甚至禁用这些事件,是你必须通过实践来掌握的技能。不管怎样,我们总得有个起点。所以,让我们尽可能简单、循序渐进地来处理这个问题。我们将从下面显示的代码开始:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_Prefix "Demo" 005. //+------------------------------------------------------------------+ 006. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 007. //+------------------------------------------------------------------+ 008. #include <Tutorial\File 01.mqh> 009. //+------------------------------------------------------------------+ 010. input double user01 = 1.5; //Stop-Target Relationship 011. input bool user02 = true; //Extend lines to the right 012. //+------------------------------------------------------------------+ 013. st_Cross gl_Cross; 014. //+------------------------------------------------------------------+ 015. int OnInit() 016. { 017. gl_Cross.Init(); 018. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 019. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 020. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 021. 022. return INIT_SUCCEEDED; 023. }; 024. //+------------------------------------------------------------------+ 025. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 026. { 027. return rates_total; 028. }; 029. //+------------------------------------------------------------------+ 030. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 031. { 032. #define macro_CLEAN_EVENT { \ 033. isPaint = ""; \ 034. gl_Cross.Hide(); \ 035. bMouseL = false; \ 036. ChartSetInteger(0, CHART_MOUSE_SCROLL, true); \ 037. } 038. 039. st_TimePrice tp; 040. static string isPaint = ""; 041. static bool bMouseL = false; 042. 043. switch (id) 044. { 045. case CHARTEVENT_KEYDOWN: 046. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) macro_CLEAN_EVENT; 047. break; 048. case CHARTEVENT_MOUSE_MOVE: 049. tp = gl_Cross.Move((ushort)lparam, (ushort)dparam); 050. if (((uchar)sparam & MOUSE_LEFT) != 0) 051. { 052. if (isPaint != "") 053. { 054. bMouseL = true; 055. if (!ObjectGetInteger(0, isPaint, OBJPROP_TIME)) ObjectMove(0, isPaint, 0, tp.Time, tp.Price); 056. ObjectMove(0, isPaint, 1, tp.Time, tp.Price); 057. } 058. }else if (bMouseL) macro_CLEAN_EVENT; 059. if ((((uchar)sparam & MOUSE_MIDDLE) != 0) && (isPaint == "")) 060. { 061. ObjectCreate(0, isPaint = macro_NameObject, OBJ_FIBO, 0, 0, 0); 062. ChartSetInteger(0, CHART_MOUSE_SCROLL, false); 063. Modifier_OBJ_FIBO(isPaint); 064. gl_Cross.Show(); 065. } 066. break; 067. case CHARTEVENT_MOUSE_WHEEL: 068. break; 069. case CHARTEVENT_OBJECT_CLICK: 070. Comment(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d Y: %03d Object: %s", (ushort)lparam, (ushort)dparam, sparam)); 071. break; 072. case CHARTEVENT_OBJECT_DRAG : 073. Comment(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam)); 074. break; 075. } 076. ChartRedraw(); 077. 078. #undef macro_CLEAN_EVENT 079. }; 080. //+------------------------------------------------------------------+ 081. void OnDeinit(const int reason) 082. { 083. Comment(""); 084. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 085. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 086. 087. gl_Cross.Hide(); 088. 089. if (reason == REASON_REMOVE) 090. ObjectsDeleteAll(0, def_Prefix); 091. }; 092. //+------------------------------------------------------------------+ 093. void Modifier_OBJ_FIBO(const string szNameObj) 094. { 095. #define macro_Mod_OBJ_FIBO(txt, pos, cor, width, style) { \ 096. ObjectSetDouble(0, szNameObj, OBJPROP_LEVELVALUE, levels, pos); \ 097. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELCOLOR, levels, cor); \ 098. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELWIDTH, levels, width); \ 099. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELSTYLE, levels, style); \ 100. ObjectSetString(0, szNameObj, OBJPROP_LEVELTEXT, levels, txt); \ 101. levels++; \ 102. } 103. 104. int levels = 0; 105. 106. ObjectSetInteger(0, szNameObj, OBJPROP_SELECTABLE, false); 107. ObjectSetInteger(0, szNameObj, OBJPROP_COLOR, clrNONE); 108. ObjectSetInteger(0, szNameObj, OBJPROP_RAY_RIGHT, user02); 109. 110. macro_Mod_OBJ_FIBO("Stop", 0, clrRed, 2, STYLE_SOLID); 111. macro_Mod_OBJ_FIBO("Enter", 1, clrBlue, 2, STYLE_DASH); 112. macro_Mod_OBJ_FIBO("Partial", 1 + (user01 / 2), clrYellowGreen, 1, STYLE_DASHDOTDOT); 113. macro_Mod_OBJ_FIBO("Take", 1 + user01, clrGreen, 2, STYLE_SOLID); 114. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELS, levels); 115. 116. #undef macro_Mod_OBJ_FIBO 117. } 118. //+------------------------------------------------------------------+
代码 01
以下是我们上一篇文章中研究、解释和提供的代码。嗯,事实上,这并不完全正确,因为在这里我们向源代码中添加了两个新元素。我故意这样做,是在熟悉材料的基础上进行,这样你就能明白,复杂的应用程序不是一蹴而就的。它是逐步、一步步构建起来的。尽管我们在这里并不专注于创建任何特定的应用程序。我们的目标只是为了演示和解释与 MQL5 编程相关的方面,以便控制 MetaTrader 5 平台本身。
经过编译并添加到图表中后,代码 01 将展示一些值得观察的有趣行为。现在,它将为我们提供有用的信息。请记住,这些数据最初是由 MetaTrader 5 根据图表上执行的操作生成的。以下动画展示了在任何图表上使用该应用程序时可以看到的效果:

动画 01
请注意动画 01 的左上角。您会看到一些消息,其目的是通知我们图表上的某个对象何时发生了事件。请注意,对于 CHARTEVENT_OBJECT_CLICK 事件,我们不仅会被告知对象的名称和鼠标位置,还会被告知发生的事件类型。此类事件具有广泛的潜在用途。事实上,仅仅观察这两个事件就带来了许多可能性。
但由于我们正在处理的是非常简单直接的内容,我认为没有必要详细解释代码 01 中的具体操作。只需冷静地研究它,并运用从之前文章中学到的知识,就能准确理解应用程序中的具体操作。但是请记住,该应用程序甚至可以观察它自己没有创建的对象。
亲爱的读者,这正是我希望你明白的。我们正在分析 MetaTrader 5 的标准行为,以了解它如何与对象交互,以及当发生任何变化时,它将如何确切地通知我们。
虽然我们可以继续向代码 01 添加新元素,但我们不会这样做。因为那最终会模糊我们的主要目标,而我们的目标正是教你并传授我一些编程经验和知识,以便让平台真正为你服务,而不是成为你的阻碍。那么,让我们来创建一个新型应用程序吧。它将专门用于描述要实现的功能,当然,前提是你了解如何使用 MQL5 来控制 MetaTrader 5。
要做到这一点,你需要掌握当前主题所涵盖材料的精髓。在我看来,这部分是其他所有内容的基础,如果不理解这部分,就很难理解我们接下来要做什么。
避免重复对象
真正让我感到困扰的事情之一就是名声不佳的 CTRL+C 和 CTRL+V。但在 MetaTrader 5 中,这并不起作用,至少不是以大多数人所期望的方式起作用。这是因为,在选定一个对象并打开其可用操作菜单时,我们可以看到,默认情况下,MetaTrader 5 在将这些操作应用于对象时并不支持 Ctrl+C 和 Ctrl+V。对许多人来说,这可能看起来完全正常且自然,但对其他人来说,可能会显得有些奇怪和令人困惑。您可以在下图看到这一点。

图 01
是的,这看起来可能有点奇怪。但是,在 MetaTrader 5 中,CTRL+C 和 CTRL+V 的使用方式有所不同。说实话,我认为很少有用户知道如何在 MetaTrader 5 中使用 CTRL+C 和 CTRL+V 来复制图形对象。
要理解这是如何实现的,需要解释其他相关内容。但在此处,这样做会使文章变得非常枯燥,而且在我看来,并不是特别有趣。事实上,大多数用户对当前平台的满意度相当高。那为什么要改呢?不是吗?
不过,对于我稍后要解释的内容,我绝对有必要向你们展示如何在 MetaTrader 5 中的任何对象上使用 Ctrl+C 和 Ctrl+V,因为我认为这是你们不知道如何操作的一个功能。对于那些偶然看到我的文章并开始研究它们的人来说,它也成了一种额外的收获。因此,在下面的动画中,我们可以看到如何对任何对象执行 CTRL+C 和 CTRL+V 操作。

动画 02
请注意,这非常简单、容易,甚至很有趣。然而,很少有人知道,要复制图表上的任何对象,只需按住 CTRL 键并拖动该对象即可。这样做,我们就能创建出对象的精确副本。尽管实际上这并不是真正的精确复制,稍后将对此进行解释。谢天谢地,它不是精确复制,否则,你可能很难做到一些你甚至还没怀疑过的事情。
因此,当我们这样做时,如动画 02 所示,我们创建了图表上已存在的对象的副本。但有时这并非我们想要的。或者,更准确地说,对于某些类型的对象,我们不希望在图表上出现副本。这是因为这些对象可能包含的信息如果被复制,可能会误导操作者,甚至普通用户。
当这些对象能够从我们的应用程序接收信息时,这一点尤为重要。在这种情况下,我们可能会在两个看起来完全相同的对象中看到相互矛盾的信息。为了更清楚地说明这一点,也为了让你能够正确理解我所说的内容,我们来做一个小测试。我们将使用下面显示的代码进行测试:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. string gl_NameObj; 09. //+------------------------------------------------------------------+ 10. int OnInit() 11. { 12. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 13. 14. ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0); 15. ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true); 16. ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50); 17. ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50); 18. ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150); 19. ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue); 20. ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20); 21. ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console"); 22. 23. return INIT_SUCCEEDED; 24. }; 25. //+------------------------------------------------------------------+ 26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 27. { 28. return rates_total; 29. }; 30. //+------------------------------------------------------------------+ 31. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 32. { 33. switch (id) 34. { 35. case CHARTEVENT_MOUSE_MOVE: 36. ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 37. break; 38. } 39. ChartRedraw(); 40. }; 41. //+------------------------------------------------------------------+ 42. void OnDeinit(const int reason) 43. { 44. ObjectsDeleteAll(0, def_Prefix); 45. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 46. ChartRedraw(); 47. }; 48. //+------------------------------------------------------------------+
代码 02
代码 02 将有助于说明我在谈论复制对象时所指的含义。由于它非常简单,而且您很容易理解其中的一切,亲爱的读者,如果您一直在关注我的文章,我将不再解释它的功能。相反,我们将仅展示当将前文所述的思想应用于实践时所能看到的结果。该结果可在下方动画 03 中看到:

动画 03
在这里,我们遇到了一个与事件处理速度相关的问题。由于我们处理的是鼠标事件,处理速度非常快,最终我们开始注意到发生了一些奇怪的事情。但是,想想那些可能每五分钟甚至每小时发生一次的事件。在这种情况下,就很难察觉了。所以现在你可能开始明白我的意思了。由于你可能不知道如何在图表上复制一个对象,像我们在动画 03 中看到的情况几乎不会发生。
但是,既然我们已经知道在 MetaTrader 5 中创建对象副本是多么容易,这种情况就可能经常发生。在某些情况下,这可能会导致严重的问题,特别是当复制的对象位于目标对象前面时。这种情况发生的频率远超人们的想象,因此会造成更大的混乱。
然而,我们可以防止对象被复制,或者至少降低因此而引发冲突的可能性。这是因为某些类型的对象在特定配置下可能会遮挡其他对象。幸运的是,此处创建的对象完全无害,仅用于教育目的。
亲爱的读者,现在你需要明白一件事。如图所示,当一个对象被复制时,MetaTrader 5 会为这个新对象创建一个名称。可以说,这个名称遵循一个标准化规则,并且分为三部分。第一部分指的是我们在图表中使用的时间周期。第二部分指明了所用对象的类型。第三部分是一个随机生成的数字。我们忽略这第三部分,因为它的目的是避免同类对象之间的冲突。
好吧,但这告诉我们什么呢?我们又该如何利用这些知识来为自己谋利呢?现在,最有趣的部分来了,亲爱的读者。在观察如何创建副本时,基于某些可以使用代码 01 检查的特性,我们可以确定是否创建了对象的副本。尽管这并非一门精确的科学,因为我们在不脱离纯 MQL5 的情况下,如何做到这一点以及能做到什么程度都存在一些复杂因素。
这些复杂问题中的一个很快就会变得清晰。但首先,我们可以做一些更有趣的事情:实现一个功能,其目的是分析 MetaTrader 5 本身在响应用户与对象交互时生成的事件。因此,代码 02 将成为我们下面看到的代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. enum eBtnMouse { 09. MOUSE_LEFT = 0x01, 10. MOUSE_RIGHT = 0x02, 11. MOUSE_KEY_SHIFT = 0x04, 12. MOUSE_KEY_CTRL = 0x08, 13. MOUSE_MIDDLE = 0x10, 14. MOUSE_EXTRA_1 = 0x20, 15. MOUSE_EXTRA_2 = 0x40 16. }; 17. //+------------------------------------------------------------------+ 18. string gl_NameObj; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 23. 24. ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0); 25. ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true); 26. ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50); 27. ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50); 28. ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150); 29. ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue); 30. ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20); 31. ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console"); 32. 33. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true); 34. 35. return INIT_SUCCEEDED; 36. }; 37. //+------------------------------------------------------------------+ 38. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 39. { 40. return rates_total; 41. }; 42. //+------------------------------------------------------------------+ 43. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 44. { 45. static bool check = true; 46. 47. switch (id) 48. { 49. case CHARTEVENT_MOUSE_MOVE: 50. ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 51. if (((uchar)sparam & (MOUSE_LEFT | MOUSE_KEY_CTRL)) == (MOUSE_LEFT | MOUSE_KEY_CTRL)) 52. { 53. if (check) Print(StringFormat("CHARTEVENT_MOUSE_MOVE\n%s", StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam))); 54. check = false; 55. }else 56. check = true; 57. break; 58. case CHARTEVENT_OBJECT_CREATE: 59. Print(StringFormat("CHARTEVENT_OBJECT_CREATE\nObject: %s", sparam)); 60. break; 61. case CHARTEVENT_OBJECT_CLICK: 62. Print(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d Y: %03d Object: %s", (ushort)lparam, (ushort)dparam, sparam)); 63. break; 64. case CHARTEVENT_OBJECT_DRAG : 65. Print(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam)); 66. break; 67. } 68. ChartRedraw(); 69. }; 70. //+------------------------------------------------------------------+ 71. void OnDeinit(const int reason) 72. { 73. Comment(""); 74. ObjectsDeleteAll(0, def_Prefix); 75. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 76. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false); 77. ChartRedraw(); 78. }; 79. //+------------------------------------------------------------------+
代码 03
看到这段代码,你可能会想:“拜托,这段代码太复杂了。我们真的需要这么复杂才能避免图表上的对象重复吗?”其实,在代码 03 中,我们还没有尝试去防止对象被复制。而且,这段代码并不像看起来那么复杂。之所以看起来复杂,可能是因为你偶然看到了这篇文章,而没有足够了解之前出版物中已经解释过的背景知识。没错,代码 03 看起来确实是个不小的挑战。
然而,出于对那些关注并研究我文章的人的尊重,我在此将仅重点介绍新内容。至于其他内容,我相信,只要初学者们学习和实践已解释的内容,他们也能理解。
这里我们感兴趣的第一处新内容是第 33 行。亲爱的读者,请注意。在 MetaTrader 5 中,对象创建事件默认是禁用的。然而,如果我们不希望应用程序创建的对象被检测到并触发创建事件,那么我们必须保持此事件禁用状态,直到该对象被创建之后,就像在第 33 行启用它之前所做的那样。由于我们仅在创建 OBJ_LABEL 对象之后才启用此事件,因此该对象不会触发创建事件,而该事件反过来会被 OnChartEvent 过程捕获。
现在,在启用此事件后,图表上的任何对象都会触发一个创建事件。第 58 行会捕获这个事件,然后向终端打印一条消息。最后,当执行第 76 行时,我们将禁用此创建事件,从而阻止 MetaTrader 5 在图表上继续触发此事件。
其余代码都比较容易理解。接下来,我们可以继续讨论在实践中应用代码 03 的部分。让我们从以下动画中看到的内容开始:

动画 04
在这个动画中,我们可以看到图表上存在哪些对象。我们还注意到,代码没有打印任何消息,这表明事件与之前解释的预期相符。
代码 03 运行后,我们可以打开包含对象列表的窗口,以检查图表中存在哪些元素。打开列表后,我们将看到如下所示的图像。

图 02
亲爱的读者,现在请注意一件事。防止对象被复制主要有两种方法。第一种方法是跟踪并检查你在图 02 中看到的对象列表。这可以通过代码实现。然而,在我看来,这会导致不必要的资源消耗,因为每次第 58 行检测到创建事件时,我们都必须分析该对象列表发生了什么变化。这会使代码不必要地复杂化,并增加 CPU 使用率。
另一种方法是对第49行捕获到的鼠标事件进行分析,尤其是那些会导致第 51 行过滤器评估为 true 的事件。然而,这种机制并非 100% 完美,因为在某些情况下,同一个过滤器可能会触发误判(false positive)。
要了解导致误判的原因,请参阅我的另一篇文章“从零开始开发交易 EA(第 23 部分):新订单系统(六)”。在这里,我们使用这种组合,它激活第 51 行,用于复制对象,以便将待处理的订单发送到交易服务器。然而,只要我们耐心、细心并多加注意,就能解决这个误判问题。我们下次再讨论这个问题,因为它涉及一些目前不适合演示的操作。这里的重点是完全不同的:理解如何防止创建重复的对象。
现在,让我们更详细地了解一下代码 03 将如何展示图表上的内容。这可以通过以下动画来观察:

动画 05
在这种情况下,只生成了 CHARTEVENT_OBJECT_CLICK 和 CHARTEVENT_OBJECT_DRAG 事件,这证实了所有操作都按预期进行。现在,我们将对图表上的按钮进行一些稍微不同的操作。这可以通过以下动画来观察:

动画 06
请注意这里一个细节。在创建副本的那一刻,即生成复本时,MetaTrader 5 会触发两个事件。下图可以更清晰地展示这一点。

图 03
在图 03 中,我们可以清楚地看到对象被复制或生成副本的瞬间。现在请注意!请注意,原对象保持静止状态。从下图 02 和 04 的对比可以看出,是副本发生了移动。

图 04
为什么了解这一点很重要?原因在于,根据我们的任务需求,我们可能需要删除副本或原始对象。就我个人而言,我觉得删除副本更简单,因为在捕捉到创建事件时,副本的名称就已经被提供给我们了。然而,我们完全也可以先删除原始对象,然后重命名副本,这样一切就都保持不变了。或许本意是想移动对象,结果却创建了一个副本。然而,我认为这需要花费太多精力。所以,我们将继续走最简单的路。
由于计划对代码 03 进行的更改将移除之前已实现和演示的功能,我们将采取另一种方法。我们将创建一个新文件。这样,代码 03 就会包含在文章附件中,这样您就可以安心地研究它,而无需手动输入。修改后的新代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. enum eBtnMouse { 09. MOUSE_LEFT = 0x01, 10. MOUSE_RIGHT = 0x02, 11. MOUSE_KEY_SHIFT = 0x04, 12. MOUSE_KEY_CTRL = 0x08, 13. MOUSE_MIDDLE = 0x10, 14. MOUSE_EXTRA_1 = 0x20, 15. MOUSE_EXTRA_2 = 0x40 16. }; 17. //+------------------------------------------------------------------+ 18. string gl_NameObj; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 23. 24. ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0); 25. ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true); 26. ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50); 27. ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50); 28. ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150); 29. ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue); 30. ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20); 31. ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console"); 32. 33. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true); 34. 35. return INIT_SUCCEEDED; 36. }; 37. //+------------------------------------------------------------------+ 38. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 39. { 40. return rates_total; 41. }; 42. //+------------------------------------------------------------------+ 43. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 44. { 45. static bool check = true; 46. static string szObjName = ""; 47. 48. switch (id) 49. { 50. case CHARTEVENT_MOUSE_MOVE: 51. ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 52. if (((uchar)sparam & (MOUSE_LEFT | MOUSE_KEY_CTRL)) == (MOUSE_LEFT | MOUSE_KEY_CTRL)) 53. { 54. if (check) 55. if (ObjectDelete(0, szObjName)) 56. Comment("Removing copy object: " + szObjName); 57. check = false; 58. }else 59. check = true; 60. break; 61. case CHARTEVENT_OBJECT_CREATE: 62. szObjName = sparam; 63. break; 64. case CHARTEVENT_OBJECT_CLICK: 65. Comment(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d Y: %03d Object: %s", (ushort)lparam, (ushort)dparam, sparam)); 66. break; 67. case CHARTEVENT_OBJECT_DRAG : 68. Comment(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam)); 69. break; 70. } 71. ChartRedraw(); 72. }; 73. //+------------------------------------------------------------------+ 74. void OnDeinit(const int reason) 75. { 76. Comment(""); 77. ObjectsDeleteAll(0, def_Prefix); 78. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 79. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false); 80. ChartRedraw(); 81. }; 82. //+------------------------------------------------------------------+
代码 04
运行代码 04 时,我们将得到类似下方的结果:

动画 07
请注意,我在这里添加了一个新对象,没有任何问题。随后,我们立即尝试使用本文中描述的机制创建该对象的副本。请注意图表左上角显示的内容。换句话说,我们刚刚找到了一种防止对象被复制的方法,尽管也可以采取其他措施来达到同样的效果。
最后的思考
尽管本文仅专注于处理对象事件,但我们仍需探讨与对象相关的其他三个事件。我们将在下一篇文章中对此进行讨论。对于那些刚开始学习如何使用 MQL5 控制 MetaTrader 5 的人来说,我不想把事情弄得太复杂。
所以,亲爱的读者,请尝试学习并实践本文所涵盖的内容。这些知识定会帮助你理解 MetaTrader 5 的诸多工作原理,而你需要很好地掌握这些知识,才能说:“是的,我的编程能力足以驾驭 MetaTrader 5 的各项功能。”
所以,请使用附件中的代码进行练习,我们下一篇文章见。
| MQ5 文件 | 描述 |
|---|---|
| 代码 01 | 对象事件的演示 |
| 代码 02 | 对象事件的演示 |
| 代码 03 | 对象事件的演示 |
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/16035
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MQL5自动化交易策略(第二十六部分):构建针形K线均价加仓的多持仓交易系统
MQL5 自动化交易策略(第二十五部分):基于最小二乘法拟合的趋势线交易 EA 与动态信号生成
新手在交易中的10个基本错误