从基础到中级:对象事件(四)
引言
在上一篇文章“从基础到进阶:对象事件(三)”中,我们展示了更大主题的第一部分,本文将继续探讨并完成这一主题。
亲爱的读者,由于这些文章的目的是向您展示如何控制 MetaTrader 5 以实现任何应用程序,我希望您能认真学习和实践这里所展示的内容。关键在于,这些文章中讨论的任何代码都不应被视为已完成的应用程序,也不应在没有适当谨慎和常识的情况下使用。
许多人可能会认为我在这里讨论的内容有点荒谬,因为很多人不相信应用程序竟然能实现我在这里展示的功能。但编程之美不在于做显而易见的事,而在于发现鲜有人能察觉的新可能性。正因如此,我决定创作这个系列,其目的正是为了证明,只要你理解了某些概念,几乎任何事情都是可以实现的。所以,现在是时候让前一篇文章中展示的所有内容真正开始变得有意义了。
提升交互性
我们在上一篇文章中看到的,至少从附件中的当前代码来看,还远未形成一个逻辑上完整的解决方案。但是,它被保留在那里作为练习,以展示如何用最少的代码创建这种交互。
前一篇文章的附件文件尚未完全成熟。尽管我们已经展示了一些可以实现的东西,而且是以一种非常简单的方式,但它们仍然无法让我们以想要的方式与对象进行交互。简而言之,这段代码只能创建下面动画 01 中所示的内容。

动画 01
虽然这看起来可能有些傻气且不是特别有用,但这个动画向我们展示了这段代码中已经具备的潜力,因为它允许我们在选定对象周围创建一个轮廓。然而,我们的目标不仅仅是创建这样的轮廓,还要改变选定对象的大小。问题就出在这里。这正是出现一些困难的地方,尤其是对于那些没有练习和研究过前几篇文章中所示内容的人来说。
在我迄今为止的文章中,我们已经展示了如何与 MetaTrader 5 交互以实现特定结果。然而,大部分工作都交给了 MetaTrader 5,我们在实现代码时只执行了其中的一部分。但在此处,我们遇到了一个小问题。这个问题可以概括如下:MetaTrader 5 可以完成我们的应用程序处理选定对象大小调整所需的大部分工作,但 — 这是关键点 — 我们无法在不进行额外工作的情况下实现这一点。我这么说是因为,当使用下面完整展示的源代码时,我们将无法获得 100% 正确的交互效果,执行结果如动画 01 所示。
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. struct st_BoxResize 09. { 10. private : 11. ushort x, y, w, h; 12. bool isBack; 13. string szObjects[4]; 14. //+----------------+ 15. void CreateEdge(const char what) 16. { 17. ushort p1 = x, p2 = y, p3 = w, p4 = h; 18. 19. switch (what) 20. { 21. case 0: p4 = 1; break; 22. case 1: p3 = 1; break; 23. case 2: p2 = y + h; p4 = 1; break; 24. case 3: p1 = x + w; p3 = 1; break; 25. } 26. szObjects[what] = macro_NameObject; 27. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 28. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 29. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 30. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 31. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 32. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 33. } 34. //+----------------+ 35. public : 36. void CreateBoxResize(const string szArg, const uchar adjust = 2) 37. { 38. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 39. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 40. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 41. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 42. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 43. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 44. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 45. for (uchar c = 0; c < 4; c++) 46. CreateEdge(c); 47. } 48. //+----------------+ 49. void RemoveBoxResize(const string szArg) 50. { 51. ObjectSetInteger(0, szArg, OBJPROP_BACK, isBack); 52. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 53. ObjectsDeleteAll(0, def_Prefix); 54. } 55. //+----------------+ 56. }gl_RZ; 57. //+------------------------------------------------------------------+ 58. int OnInit() 59. { 60. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 61. 62. return INIT_SUCCEEDED; 63. }; 64. //+------------------------------------------------------------------+ 65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 66. { 67. return rates_total; 68. }; 69. //+------------------------------------------------------------------+ 70. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 71. { 72. static string szNameObj = NULL; 73. 74. switch (id) 75. { 76. case CHARTEVENT_KEYDOWN : 77. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 78. { 79. gl_RZ.RemoveBoxResize(szNameObj); 80. szNameObj = NULL; 81. } 82. break; 83. case CHARTEVENT_OBJECT_CLICK : 84. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 85. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 86. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 87. break; 88. case CHARTEVENT_OBJECT_DRAG : 89. break; 90. } 91. ChartRedraw(); 92. }; 93. //+------------------------------------------------------------------+ 94. void OnDeinit(const int reason) 95. { 96. ObjectsDeleteAll(0, def_Prefix); 97. ChartRedraw(); 98. }; 99. //+------------------------------------------------------------------+
代码 01
“但是,你为什么说这种交互不会 100% 正确呢?这对我来说毫无道理,因为这个机制显然是有效的。”没错,亲爱的读者,这个机制确实有效。但是,这里有一个小问题,如下图所示。

图 01
图 01 本身可能无法传达任何信息。但我在其中特别强调了一点,以引起你对一个细节的注意。轮廓没有交互元素,因此无论是 MetaTrader 5 还是用户,都没有明确的交互点来移动它。现在请注意:在代码 01 中,第 84 行正是为了防止用户选择任何名称中包含该代码第 04 行所指定前缀的对象。这是什么意思呢?好吧,这就解释了为什么我们无法选择 OBJ_BUTTON 对象,因为这个对象的名称中就包含这个前缀。你可以在下面的图 02 中看到这一点。

图 02
请注意,我们已用绿色高亮标出了阻止我们选择此对象的前缀。虽然乍一看这似乎并不那么重要,但这个简单的细节已经足以让第 84 行的检查阻止我们选择它。好吧,但我们不需要选择用作轮廓的绿线。如果我们选择 OBJ_RECTANGLE_LABEL 对象本身,并让 MetaTrader 5 为我们完成其余操作,效果会更好。是的,这在一定程度上是正确的。这需要对代码 01 进行简单的修改。您可以在下面的代码片段中看到这一点:
. . . 034. //+----------------+ 035. public : 036. void CreateBoxResize(const string szArg, const uchar adjust = 2) 037. { 038. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 039. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 040. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 041. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 042. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 043. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 044. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 045. for (uchar c = 0; c < 4; c++) 046. { 047. CreateEdge(c); 048. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 049. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 050. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 051. } 052. } 053. //+----------------+ . . .
代码 02
下面的动画 02 展示了代码 02 的这一片段在代码 01 中会产生什么效果:

动画 02
好的,在动画 02 中我们已经能看到一些效果了。下面我们来仔细看看。请看图 03:

图 03
图 03 中高亮显示的点,正是我们可用于拖动对象的锚点。为此,我们使用 MetaTrader 5,无需担心其他任何事情。然而,如果你仔细观察,你会发现只有三个点可见。第四个点在哪里呢?嗯,这是我们的第一个问题。第四个点紧挨着另一个点,恰好位于图 03 中突出显示的位置。如果这只是唯一真正的问题,那也没什么大不了的。对此我们几乎无能为力。然而,还有一个比这更烦人的问题。它在下面的动画中显示:

动画 03
“现在我什么都不明白了。如果动画 03 中的内容是 MetaTrader 5 正常运行的一部分,那我们为什么还要担心它呢?””亲爱的读者,我想你可能还没有完全理解我们的意图。让我稍微澄清一下情况。我们想要实现的效果与动画 03 中展示的类似,但不同的是,我们不希望绿线之间失去连接。换句话说,当锚点移动时,与之相连的任何线条也必须相应移动,以界定新的区域,从而展示所选对象的新尺寸。那才是我们的目标,而不是动画 03 中看到的内容。你现在明白问题所在了吗?
好吧,此时此刻,很多人可能会放弃,并开始说:“你想做的事是不可能的。”亲爱的读者,这是真的吗?那么,请你想一想。我们知道 MetaTrader 5 是如何将关于对象发生的事件的信息传递给我们的应用程序的。我们也已经了解到在实现过程中做出的某些决定是如何影响我们所能获得的结果类型的。所以我想问:
我想要做的事情真的不可能吗?
答案是否定的。然而,由于知识和经验的有限,大多数人最终会认为这是不可能的,因为正如动画 03 所示,事情并不像预期的那样发展。但我们必须再次强调以下几点:
不要对代码过于执着。始终尝试理解所涉及的概念以及它们的使用方式。
这正是程序员所做的。他们面对一个问题,运用自己的编程知识,寻找并开发出解决方案。每个开发人员创建解决方案的方式肯定各不相同。然而,真正重要的是最终结果。因此,不同的应用程序可能具有或多或少的交互性,学习如何使用它们也可能或多或少简单。但无论如何,真正重要和我们感兴趣的还是最终结果。
好的,让我们停下来思考一下这个问题。我们知道,我们不能选择名称带有代码中定义的前缀的任何对象。这种情况的发生正是由于代码 01 中的第 84 行。但现在我们知道我们不需要选择这些对象。在创建对象时,我们可以告诉 MetaTrader 5 该对象已被选中。这与代码 02 片段中的第 49 行和第 50 行有关,它们正是我们在代码 01 中实现的修改。
正因为如此,我们设法让MetaTrader 5赋予用户移动以前无法移动的对象的能力,因为我们无法选择它们。亲爱的读者,我不知道你是否理解了我试图向你展示的内容。如果不理解,请尝试重新阅读之前文章中已经解释过的材料。我想要向你们展示的,正是我们将要用来实现目标的理念。我指的是一种完全交互式的方式来调整图表上对象的大小。
好的,让我们这样做:由于许多人可能不知道如何将代码 02 的片段合并到代码 01 中,以获得动画03中所示的效果,我们将创建新的代码,以便附件能让我们访问完整的代码 02。这样,你就可以安心地研究它,并学习如何将一段代码整合到现有的代码中。我们会多次这样做。知道如何做到这一点将帮助你学习和练习原本不可能做到的事情。
那么,接下来我们将基于代码 01 创建新的代码,并按照代码 02 片段中所示进行修改。您可以在下面看到:
. . . 013. string szObjects[6]; 014. //+----------------+ 015. void CreateEdge(const char what) 016. { 017. ushort p1 = x, p2 = y, p3 = w, p4 = h; 018. 019. switch (what) 020. { 021. case 0: p4 = 1; break; 022. case 1: p3 = 1; break; 023. case 2: p2 = y + h; p4 = 1; break; 024. case 3: p1 = x + w; p3 = 1; break; 025. case 5: p1 += p3; p2 += p4; 026. case 4: p3 = p4 = 1; break; 027. } . . . 035. } 036. //+----------------+ 037. public : 038. void CreateBoxResize(const string szArg, const uchar adjust = 2) 039. { . . . 047. for (uchar c = 0; c < szObjects.Size(); c++) 048. { 049. CreateEdge(c); 050. if (c > 3) 051. { 052. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 053. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 054. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 055. } 056. } 057. } 058. //+----------------+ . . .
代码 03
亲爱的读者,现在请看以下内容:在代码 03 的这个片段中,有一个简单的改动。然而,这个改动意义重大。请注意,我们在第 13 行更改了数组中的元素数量。在第 19 行的 switch 语句中,还有两个更有趣的点,可以在第 25 行和第 26 行看到。现在让我们暂停一下,以便解释另一个非常重要的事情。
在文章“从基础到中级:switch 操作符”中,有一张图中标出了几条红线。您可以在下方再次查看那张图片:

图 04
这里的重点正是图 04 中可见的那些红线。第 19 行的这个 switch 语句正好使用了那些红线。这很重要,因为它允许执行流程在没有 break 的情况下继续落入下一个 case。“哇,我还以为事情已经够复杂了!现在你却告诉我执行流程可以跳转到另一个代码块?而且这是有意为之?在我看来,编程比我想象的要难得多。现在我在考虑放弃一切,去做别的事情。”
亲爱的读者,请冷静,没有理由放弃。就在事情即将变得更加有趣的时候,你真的打算放弃吗?不,让我们一起来理解我所说的这一步究竟包含哪些内容。
看看这一系列的 case 语句。你会发现它们几乎都是按顺序排列的,但到了第三到第五个,顺序突然变了,然后又变回了第四个。“真奇怪。我没注意到这一点。你这么一说,确实挺奇怪的。但你为什么要这么做呢?”原因是我故意这么做的。为什么呢?正是为了解释图 04 中可以看到的红线。
所以请注意:尽管这看起来很简单,但它通常会让许多优秀的程序员(不仅仅是初学者)感到非常困惑。请注意,在代码 03 的片段中,21 到 26 行之间的每一行末尾都有一个 break 语句。除了第 25 行之外,其他每一行都有。为什么呢?原因是当第 25 行的条件匹配时,该行将被执行。但这正是问题所在:如何跳到下一个条件?我不想在这里结束执行。我希望在下一个条件中出现的程序能够在不预先评估该条件的情况下执行。
“天哪!你刚才说的话我完全不明白。不过,等一下,让我冷静地思考一下你的话。据我理解,当第 25 行被检查并执行 case 处理过程时,你也希望继续执行第 26 行对应的 case 分支中的处理代码。你是这个意思吗?”是的,亲爱的读者,这是可能的。“但有件事我不明白。如果不检查第 26 行的 case,如何执行该行代码?这说不通啊。
这时,就会出现后续的 case。”亲爱的读者,现在请密切关注。如图 04 所示,由于 case 语句总是以 break 语句结尾,如果我们没有 BREAK 语句,执行将继续到下一个 case 的分支中,而不检查下一个 case。这就是红线发挥作用的地方。
请注意,在创建编号为 04 的对象时,我们将它的尺寸设置为 1。在创建编号为 05 的对象时,我们也将其尺寸设置为 1。然而,在编号为 05 的对象中,我们改变了其初始位置,使其位于右下角,而编号为 04 的对象则位于左上角。所有这些都是通过利用向下执行到下一个 case 语句的功能来实现的,这可以在第 19 行的 switch 语句中看到。
然而,请注意以下几点:在第 50 行,我们检查正在创建的对象。在绘制选定对象的边界正方形后,我们继续以与代码 02 片段相同的方式创建最后两个对象。不过,这次的目的和结果都不同,如下图所示。

图 05
请注意,在这种情况下,我们有两个点,而这正是我们所需要的。然而,我们还没有完成。关键在于,尽管这些点已经创建好了,但值得看看当我们与它们交互时会发生什么。这可以在下面的动画中看到:

动画 04
“嗯,有意思。从某种意义上说,我觉得我们现在的前景相当乐观。那么,我们下一步该怎么做呢?”亲爱的读者,事情并不总是那么简单。这里的问题不是下一步该怎么做。最主要的是 — 这也是我需要你理解的 — 你要确切地明白我们在这里做什么。我们展示的这种操作需要一定的谨慎,并需要制定测试来确保一切不会失控。否则,我们就有可能犯错,给我们带来很糟糕的后果。
所以我再说一遍:不要对代码太过执着。试着理解相关的概念,并创建一个对你来说清晰的解决方案。只有这样,你才能解决应用程序生命周期中可能出现的问题。
在我不断重复的这一警告之后,我们可以继续讨论下一个要点。在这种情况下,我们需要修改事件处理代码,以及稍后讨论的其他内容。但首先,让我们来看一下事件处理代码。您可以在下面看到:
. . . 079. //+------------------------------------------------------------------+ 080. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 081. { 082. static string szNameObj = NULL; 083. 084. switch (id) 085. { 086. case CHARTEVENT_KEYDOWN : 087. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 088. { 089. gl_RZ.RemoveBoxResize(szNameObj); 090. szNameObj = NULL; 091. } 092. break; 093. case CHARTEVENT_OBJECT_CLICK : 094. Print((ushort)lparam, " x ", (ushort)dparam, " ->> ", sparam); 095. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 096. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 097. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 098. break; 099. case CHARTEVENT_OBJECT_DRAG : 100. Print("CHARTEVENT_OBJECT_DRAG ->> ", sparam); 101. break; 102. } 103. ChartRedraw(); 104. }; 105. //+------------------------------------------------------------------+ . . .
代码 04
在代码 04 所示的片段中,我们做了一点简单的修改,以向您展示一些在我看来很重要的内容,尽管这些内容在之前的文章中已经介绍过。然而,在您真正理解之前,不断强化某些概念总是有益的。
请注意,在第 94 行和第 100 行,我们指示应在终端打印出某些消息。那么,让我们来看一下这段代码首次(可以说是)正常执行的结果。结果可以在下面的动画中看到:

动画 05
现在让我们观察一下我们想要避免的行为。这在下面的动画 06 中有所展示:

动画 06
在动画 05 和 06 中,我们可以看到 MetaTrader 5 的标准行为。但是,我们可以接受动画 05 中显示的行为,但不能接受动画 06 中显示的行为。在动画 06 中,用户对他们本不应选择的对象进行了选择和取消选择的操作。我们只应能够移动该对象,而不应能够启用或禁用该对象的选定状态。
那么,让我们一步一步地解决这个问题。第一步是防止用户在简单点击对象时,对象的选定状态发生改变。这可以通过以下代码实现:
. . . 007. //+------------------------------------------------------------------+ 008. struct st_BoxResize 009. { . . . 036. //+----------------+ 037. public : . . . 065. //+----------------+ 066. void Block(const string szArg) 067. { 068. for (uchar c = 0; c < szObjects.Size(); c++) 069. if (szObjects[c] == szArg) 070. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 071. } 072. //+----------------+ 073. }gl_RZ; 074. //+------------------------------------------------------------------+ . . . 086. //+------------------------------------------------------------------+ 087. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 088. { 089. static string szNameObj = NULL; 090. 091. switch (id) 092. { 093. case CHARTEVENT_KEYDOWN : 094. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 095. { 096. gl_RZ.RemoveBoxResize(szNameObj); 097. szNameObj = NULL; 098. } 099. break; 100. case CHARTEVENT_OBJECT_CLICK : 101. Print((ushort)lparam, " x ", (ushort)dparam, " ->> ", sparam); 102. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 103. { 104. gl_RZ.Block(sparam); 105. break; 106. } 107. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 108. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 109. break; 110. case CHARTEVENT_OBJECT_DRAG : 111. Print("CHARTEVENT_OBJECT_DRAG ->> ", sparam); 112. break; 113. } 114. ChartRedraw(); 115. }; 116. //+------------------------------------------------------------------+ . . .
代码 05
注意第一个问题的解决方案实施起来是多么容易。我们只需要向结构中添加一个新过程,如第 66 行所示。然后,我们需要在第 104 行添加对该新过程的调用。结果如何呢?你可以通过观看下面的动画来亲眼见证:

动画 07
现在让我们继续讨论我们需要解决的问题的第二部分。但首先,我想提醒大家,我们在此展示的解决方案仅用于教育目的。这并非我们唯一可实施的方案。然而,正因这些文章的目的,我们将展示其中最简单的解决方案。
然而,这并不妨碍你实现一个更合适的解决方案。甚至可以这样设计:鼠标每次移动以改变大小,都会立即反映在我们试图以交互方式缩放的对象尺寸上。也许我以后还会教你如何实现这一点。原则上,在您未来可能计划实施的各种情况下,这也会非常具有教育意义,甚至很有趣。
无论如何,我们还是采用最简单的解决方案。基本上,它包括两个阶段,第一个阶段可以从以下片段中看到。
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. enum E_Elment { 009. TOP, 010. LEFT, 011. BOTTOM, 012. RIGHT, 013. TL, 014. BR 015. }; 016. //+------------------------------------------------------------------+ 017. struct st_BoxResize 018. { 019. private : 020. ushort x, y, w, h; 021. bool isBack; 022. string szObjects[6]; 023. //+----------------+ 024. void SetPosition(const E_Elment what) 025. { 026. ushort p1 = x, p2 = y, p3 = w, p4 = h; 027. 028. switch (what) 029. { 030. case TOP: p4 = 1; break; 031. case LEFT: p3 = 1; break; 032. case BOTTOM: p2 = y + h; p4 = 1; break; 033. case RIGHT: p1 = x + w; p3 = 1; break; 034. case BR: p1 += p3; p2 += p4; 035. case TL: p3 = p4 = 1; break; 036. } 037. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 038. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 039. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 040. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 041. } 042. //+----------------+ 043. void CreateEdge(const E_Elment what) 044. { 045. szObjects[what] = macro_NameObject; 046. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 047. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 048. SetPosition(what); 049. } 050. //+----------------+ 051. public : . . . 072. //+----------------+ 073. void UpdateBoxSize(const string szArg) 074. { 075. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 076. if (szObjects[c] == szArg) 077. { 078. x = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_XDISTANCE); 079. y = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_YDISTANCE); 080. w = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_XDISTANCE) - x; 081. h = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_YDISTANCE) - y; 082. for (E_Elment i = 0; i < (E_Elment)4; i++) 083. SetPosition(i); 084. } 085. } 086. //+----------------+ . . . 100. //+----------------+ 101. }gl_RZ; 102. //+------------------------------------------------------------------+ . . . 114. //+------------------------------------------------------------------+ 115. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 116. { 117. static string szNameObj = NULL; 118. 119. switch (id) 120. { . . . 137. case CHARTEVENT_OBJECT_DRAG : 138. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 139. gl_RZ.UpdateBoxSize(sparam); 140. break; 141. } 142. ChartRedraw(); 143. }; 144. //+------------------------------------------------------------------+ . . .
代码 06
我知道代码 06 相当零散,但我们故意这样做,因为我们只想突出显示实际需要实现的部分。请注意,为了简化一些内容,我们在第 08 行创建了一个枚举。这反过来会使最后两个步骤的实现变得更加容易。我们还把与创建构成边界矩形的对象相关的部分拆分为两个模块。其中之一是 SetPosition 过程,可以在第 24 行看到。另一个阶段是直接创建边界矩形的边。在这种情况下,该过程在第 43 行。
这里真正重要的是第 73 行新增的 UpdateBoxSize 过程。当 CHARTEVENT_OBJECT_DRAG 事件调用第 139 行所示的函数时,该过程会根据新的位置重新更新边界矩形。
无论如何,我们想要实现的目标最终可以在下面看到。

动画 08
这段代码片段,如动画 08 所示,是关键所在。在此,动画 08 中展示的结果获取方式是可以改变的。然而,重要的是它要完全按照动画 08 所示的方式工作。除了这一目标外,动画 08 还包含了一些内容,我们可以根据每个特定程序员认为必要或可取的程度进行更改。我们可以认为,如动画 08 所示,在这个阶段调整大小的过程已经完成,或者我们可以采用不同的方式来完成 — 这就是我们将要遵循的路径。原因很简单:要让教学方式尽可能接近我认为的理想状态。
因此,下一步可以在下面的代码中看到。然而,在这种情况下,我将保留完整的代码,以便您能够欣赏到使用非常简单和基础的编程水平所能创造出的所有美感,同时正确应用迄今为止所展示的各种概念和知识。请尽情欣赏这个最终效果。亲爱的读者,经过漫长的等待和对图表上交互式调整对象大小问题解决方案的诸多假设,这是你应得的。
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. enum E_Elment { 009. TOP, 010. LEFT, 011. BOTTOM, 012. RIGHT, 013. TL, 014. BR 015. }; 016. //+------------------------------------------------------------------+ 017. struct st_BoxResize 018. { 019. private : 020. ushort x, y, w, h; 021. bool isBack; 022. string szObjects[6]; 023. //+----------------+ 024. void SetPosition(const E_Elment what) 025. { 026. ushort p1 = x, p2 = y, p3 = w, p4 = h; 027. 028. switch (what) 029. { 030. case TOP: p4 = 1; break; 031. case LEFT: p3 = 1; break; 032. case BOTTOM: p2 = y + h; p4 = 1; break; 033. case RIGHT: p1 = x + w; p3 = 1; break; 034. case BR: p1 += p3; p2 += p4; 035. case TL: p3 = p4 = 1; break; 036. } 037. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 038. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 039. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 040. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 041. } 042. //+----------------+ 043. void CreateEdge(const E_Elment what) 044. { 045. szObjects[what] = macro_NameObject; 046. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 047. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 048. SetPosition(what); 049. } 050. //+----------------+ 051. public : 052. void CreateBoxResize(const string szArg, const uchar adjust = 2) 053. { 054. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 055. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 056. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 057. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 058. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 059. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 060. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 061. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 062. { 063. CreateEdge(c); 064. if ((c == TL) || (c == BR)) 065. { 066. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 067. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 068. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 069. } 070. } 071. } 072. //+----------------+ 073. void UpdateBoxSize(const string szArg) 074. { 075. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 076. if (szObjects[c] == szArg) 077. { 078. x = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_XDISTANCE); 079. y = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_YDISTANCE); 080. w = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_XDISTANCE) - x; 081. h = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_YDISTANCE) - y; 082. for (E_Elment i = 0; i < (E_Elment)4; i++) 083. SetPosition(i); 084. } 085. } 086. //+----------------+ 087. void RemoveBoxResize(const string szArg, const bool update) 088. { 089. ObjectSetInteger(0, szArg, OBJPROP_BACK, isBack); 090. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 091. if (update) 092. { 093. ObjectSetInteger(0, szArg, OBJPROP_XDISTANCE, x); 094. ObjectSetInteger(0, szArg, OBJPROP_YDISTANCE, y); 095. ObjectSetInteger(0, szArg, OBJPROP_XSIZE, w); 096. ObjectSetInteger(0, szArg, OBJPROP_YSIZE, h); 097. } 098. ObjectsDeleteAll(0, def_Prefix); 099. } 100. //+----------------+ 101. void Block(const string szArg) 102. { 103. for (uchar c = 0; c < szObjects.Size(); c++) 104. if (szObjects[c] == szArg) 105. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 106. } 107. //+----------------+ 108. }gl_RZ; 109. //+------------------------------------------------------------------+ 110. int OnInit() 111. { 112. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 113. 114. return INIT_SUCCEEDED; 115. }; 116. //+------------------------------------------------------------------+ 117. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 118. { 119. return rates_total; 120. }; 121. //+------------------------------------------------------------------+ 122. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 123. { 124. static string szNameObj = NULL; 125. 126. switch (id) 127. { 128. case CHARTEVENT_KEYDOWN : 129. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 130. { 131. gl_RZ.RemoveBoxResize(szNameObj, false); 132. szNameObj = NULL; 133. } 134. break; 135. case CHARTEVENT_OBJECT_CLICK : 136. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 137. { 138. gl_RZ.Block(sparam); 139. break; 140. } 141. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj, true); 142. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 143. break; 144. case CHARTEVENT_OBJECT_DRAG : 145. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 146. gl_RZ.UpdateBoxSize(sparam); 147. break; 148. } 149. ChartRedraw(); 150. }; 151. //+------------------------------------------------------------------+ 152. void OnDeinit(const int reason) 153. { 154. ObjectsDeleteAll(0, def_Prefix); 155. ChartRedraw(); 156. }; 157. //+------------------------------------------------------------------+
代码 07
在此情况下,我不会解释代码 07 中的内容。我希望你尝试研究它,从而理解这段乍一看似乎毫无意义、被许多人认为是某个疯狂天才想象力的产物的代码,是如何实现下面动画中展示的功能的。首先,是标准的大小调整:

动画 09
现在,面则是一次组合式调整,其中另一项调整随后会被取消。

动画 10
最后的思考
编程并非枯燥、困难或令人疲惫不堪。事实上,它令人愉快、兴奋且有趣。关键在于:你是否明白自己想要做什么?你是否能够理解基本概念,并将其应用于一组逻辑指令中,告诉机器做什么、何时做以及为什么做?还是你只是坐在那里敲打一堆毫无意义的东西,希望机器能从中理解你的意图?
许多人认为只有优秀且有才华的专业人士才能从事编程工作。我不否认这可能确实如此,但亲爱的读者,没有什么能阻止你想出解决问题的方法,将其记录在纸上,分析应使用哪些概念,然后将其实现为代码。
天赋属于少数人,但努力、决心、学习和自律则属于那些真正明确自己目标并寻找实现途径的人。
想想看。利用手头的资料进行学习和练习。不要把昨天能做的事拖到明天。无论如何,开始并致力于让自己快乐的事情永远不会太晚。
也许在下一篇文章中,我们会展示其他方法来进一步改进这里讨论的代码,这些方法将在附加文件中提供。一切将取决于所呈现的材料是否对本文所涵盖的内容有所补充。总之,别指望我会教你如何改进。如果你无法从头开始实现所有内容,那么就试着研究一下到目前为止已经涵盖的内容。掌握这些内容需要时间和练习。
| MQ5 文件 | 描述 |
|---|---|
| 代码 01 | 对象事件演示 |
| 代码 02 | 对象事件演示 |
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/16094
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
交易中的神经网络:用于精确分类的高效特征提取(构建对象)
新手在交易中的10个基本错误
交易中的神经网络:面向准确分类的有效特征提取(Mantis)