从基础到中级:对象事件(三)
概述
在上一篇文章“从基础到中级:对象事件(二)”中,我们展示了如何实现一种非常有趣、甚至有些奇特的应用程序,其目的是让用户直接在图表上编辑 OBJ_LABEL 对象的文本,而无需打开“对象属性”对话框并在其中编辑显示的文本。
尽管从实用和教育的角度来看,这都很有趣且有用,但我们还可以做一件比上一篇文章中展示的更有趣的事。亲爱的读者,我想你可能已经尝试过,甚至可能想知道,同样的应用是否可以扩展到任何 OBJ_LABEL 对象,而不仅仅是我们程序中创建的那个。因此,我决定向大家展示如何做到这一点。这之所以能成为一个真正有趣的解决方案,正是因为我们稍后将探讨的另一点。
如往常一样,让我们继续讨论本文的第一个主题。因此,现在是时候放下任何可能让你分心的事物,专注于我们接下来要讨论的内容,因为我们将探讨几个非常有趣的点。
精益求精
在上一篇文章中,我们了解到,实现以下动画中展示的效果是可行的。

动画 01
这使得我们能够实现一种机制,直接编辑图表上 OBJ_LABEL 对象所包含的文本。然而,如果我们尝试手动添加一个新的 OBJ_LABEL,我们会发现动画 01 中展示的情况是不可能实现的。为什么呢?原因在于处理事件时使用的过滤器。从某种意义上说,一开始,简单地移除过滤器确实可以解决问题。当然,一开始确实如此。但如果我们尝试这样做,我们会注意到应用程序开始变得有点不稳定。
因此,我们将快速演示如何修改上一篇文章中的源代码,以便使用动画 01 中的机制来编辑任何 OBJ_LABEL 对象。
这可能看起来很复杂,但实际上,只要在更改代码时采取一定的预防措施,一切都会比看起来简单得多。之前文章中已经解释了大部分需要完成的工作,因此我们将只关注真正重要的事项。具体如下:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. switch (id) 17. { 18. case CHARTEVENT_OBJECT_CLICK : 19. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) Swap_ObjLabel_ObjEdit(sparam); 20. break; 21. case CHARTEVENT_OBJECT_ENDEDIT : 22. Swap_ObjLabel_ObjEdit(sparam); 23. break; 24. } 25. ChartRedraw(); 26. }; 27. //+------------------------------------------------------------------+ 28. void Swap_ObjLabel_ObjEdit(const string szName) 29. { 30. struct st_Mem 31. { 32. string font, text; 33. int x, y, w, h, fontSize; 34. color cor; 35. }mem; 36. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_EDIT ? OBJ_LABEL : OBJ_EDIT; 37. 38. mem.font = ObjectGetString(0, szName, OBJPROP_FONT); 39. mem.x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 40. mem.y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 41. mem.w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 42. mem.h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 43. mem.fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 44. mem.cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 45. mem.text = ObjectGetString(0, szName, OBJPROP_TEXT); 46. 47. ObjectDelete(0, szName); 48. ChartRedraw(); 49. ObjectCreate(0, szName, type, 0, 0, 0); 50. 51. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (type != OBJ_EDIT ? true : false)); 52. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, mem.x); 53. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, mem.y); 54. ObjectSetInteger(0, szName, OBJPROP_XSIZE, mem.w); 55. ObjectSetInteger(0, szName, OBJPROP_YSIZE, mem.h); 56. ObjectSetInteger(0, szName, OBJPROP_COLOR, mem.cor); 57. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, mem.fontSize); 58. ObjectSetString(0, szName, OBJPROP_FONT, mem.font); 59. ObjectSetString(0, szName, OBJPROP_TEXT, mem.text); 60. if (type == OBJ_EDIT) 61. { 62. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, clrWhite); 63. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 64. } 65. } 66. //+------------------------------------------------------------------+
代码 01
现在,我想让你看看代码 01,然后告诉我:它有什么难点?如果你一直在关注本系列文章,并且认真学习和实践了文中内容,那么你的答案很可能是:“这里没有什么难的;一切都很简单明了。”亲爱的读者,代码 01 如此简单明了,我认为没有必要解释它是如何工作的。然而,这里有个小问题。没什么复杂的。但在展示如何修复它之前,让我们先看看当代码 01 应用于任何图表时会发生什么。为此,我们将执行一系列简短的步骤,从下面显示的第一个步骤开始。

动画 02
在动画 02 中,我们只将代码 01 添加到了图表中。请注意,没有任何反应,因为我们的目标不是创建任何东西,而是使用已经创建好的东西。紧接着,我们将添加几个 OBJ_LABEL 对象。为此,按下图所示操作即可:

图 01
这张图展示了如何将所需类型的对象添加到图表中。您可以添加任意数量的对象。在本例中,我将只添加两个,因为我们的目标正是展示现有的缺陷。图表随后将如下所示:

图 02
非常好,现在,我们终于要讲到一切开始变得有意义的部分了。一旦应用程序在图表上运行,并且我们有了正确类型的对象,我们将无法再像之前那样进行操作。这是因为,当我们点击其中一个 OBJ_LABEL 对象时,它会转换为 OBJ_EDIT 对象,这样我们就可以直接输入显示的文本。而所有这些操作,都无需打开 OBJ_LABEL 对象的“对象属性”对话框。

动画 03
请注意,正如动画 03 所示,我们确实可以编辑这些对象,正如上一篇文章所承诺和演示的那样。但这并不是全部:现在我们可以对任何 OBJ_LABEL 对象进行编辑。此外,我们还可以做更多的事情,尽管方式略有不同。为此,我们需要打开“对象列表”窗口。这样我们就可以更改某些属性,例如颜色、字体、字号,甚至对象本身的名称。
然而,我们通过这种方式调整的都是一些次级属性。无论如何,OBJ_EDIT 和 OBJ_LABEL 对象将使用同一组属性,因为代码 01 中所示的第 28 行的过程将自行处理这些属性的传输。
那么,本主题开头提到的缺陷是什么呢?亲爱的读者,它并非一个可怕或令人担忧的缺陷。它更多是一个选择和取消选择的问题。在使用代码 01 所示的应用程序时,我们通常只需要图表上出现一个 OBJ_EDIT 对象。这是因为,在某些情况下,图表上有多个 OBJ_EDIT 对象并无太大意义。但由于选择和取消选择过程中的一个缺陷,让我们来看看会发生什么。

动画 04
请注意,最初我们有两个 OBJ_LABEL 对象,然后出现了两个 OBJ_EDIT 对象。这是可以修复的。我并不是说这完全错了,但如果目标只是能够在图表上直接编辑 OBJ_LABEL 文本,那么像动画 04 中展示的这种情况就没有意义了。
修复这类缺陷非常简单。要解决这个问题,我们只需要修改事件处理代码。就我们的情况而言,只需按如下方式修改代码即可:
. . . 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. static string szOldEdit = ""; 17. 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) 22. { 23. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 24. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 25. } 26. break; 27. case CHARTEVENT_OBJECT_ENDEDIT : 28. Swap_ObjLabel_ObjEdit(sparam); 29. szOldEdit = ""; 30. break; 31. } 32. ChartRedraw(); 33. }; 34. //+------------------------------------------------------------------+ . . .
代码 02
请注意,应用此解决方案是多么简单,因为我们只需要修改代码 02 片段中标出的部分。结果见下面的动画 05:

动画 05
“好吧,现在我想我明白了,在重复大家都会因为觉得正确而重复的内容之前,练习并更好地研究材料是多么重要。但关于你刚才如此亲切地演示了如何创建的这个应用程序,我有一个问题。有没有办法在不打开‘对象列表’窗口的情况下控制其他参数?我之所以这么问,是因为显然我们无法改变图表上对象的位置。在对象被放置且应用程序在图表上运行后,移动对象几乎变得不可能。
那么,有没有办法处理这种情况呢?因为这让我很困扰,尽管展示的操作方法看起来很有趣。”
是的,亲爱的读者,这个问题是有解决办法的。这就是为什么你必须要非常理解某些事情。始终要尝试理解概念本身,而不是所使用的特定代码。因为代码可以更改,但概念不会变。
实现你建议的方法有几种,即在图表上运行此处所示的应用程序时,控制对象的位置。最简单的方法之一是更改 OBJ_EDIT 对象的工作方式。
在上一篇文章中,我们提到,要编辑 OBJ_EDIT 对象的内容,其某个属性必须具有特定值。如果该属性被设置为其他值,编辑就会被阻止。“但这对我们来说为什么很重要?我们的想法不是要创建一种在图表上移动对象的方法吗?关于特定属性及其如何影响 OBJ_EDIT 对象的信息,如何帮助我们解决移动对象的问题?我不太明白。您能详细解释一下吗?”
当然,我可以更详细地解释我的意思。如果你还记得,在之前的两篇文章中,我们展示了如何在需要了解发生的事情时,让我们的代码拦截对象事件。由于我们不想承担过多的额外工作,以至于要告诉对象应该如何移动,我们将这项任务交给了 MetaTrader 5。嗯,那部分很容易理解。也许困难的部分,以及理解对象如何工作非常重要的原因在于,当一个 OBJ_EDIT 对象因为被选中而无法编辑时,我们可以将移动对象的责任交给 MetaTrader 5。
对于绝大多数初学者程序员来说,最难理解的部分是,即使在添加了代码 02 中的片段后,我们的代码 01 仍然会将 OBJ_LABEL 对象转换为 OBJ_EDIT 对象。这是为了编辑稍后将显示的文本。但是,如果你再次点击 OBJ_EDIT 对象,会发生什么呢?根据代码中的处理内容,答案是:什么都不会发生。但是,请你想一想:当我们完成移动对象后,MetaTrader 5 将生成一个事件。它会通知我们的应用程序,用户已经释放了对象,并且它现在处于一个新的位置。
因此,如果我们采用一个小策略,就可以迫使 OBJ_EDIT 对象进入一种状态,使 MetaTrader 5 能够管理其移动。这将允许用户在图表上移动该对象。当用户释放该对象时,我们将完成编辑,或者我们可以简单地将 OBJ_EDIT 对象转换回 OBJ_LABEL 对象。亲爱的读者,您必须决定哪种选择最适合您的目标。
这样解释的话,可能会显得有些复杂。但是,当我们把想法变成代码时,事情就变得容易理解多了。但在我们这么做之前,亲爱的读者,我需要给你看一个东西。这将是一个简单的例子,但它会让我们更清楚地理解一个关键细节。
在上一篇文章中,我们提到需要将 OBJ_EDIT 对象的 OBJPROP_SELECTABLE 属性设置为 false,但并未说明原因。尽管在某些情况下,我们可以将 OBJPROP_SELECTABLE 设置为true,并且仍然能够编辑 OBJ_EDIT 对象中的文本,但演示这两种情况无疑会使事情变得更加复杂。要了解实际情况,请查看下面的代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. static string szOldEdit = ""; 17. 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) 22. { 23. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 24. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 25. } 26. break; 27. case CHARTEVENT_OBJECT_ENDEDIT : 28. Swap_ObjLabel_ObjEdit(sparam); 29. szOldEdit = ""; 30. break; 31. } 32. ChartRedraw(); 33. }; 34. //+------------------------------------------------------------------+ 35. void Swap_ObjLabel_ObjEdit(const string szName, bool Selectable = true) 36. { 37. struct st_Mem 38. { 39. string font, text; 40. int x, y, w, h, fontSize; 41. color cor; 42. }mem; 43. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_EDIT ? OBJ_LABEL : OBJ_EDIT; 44. 45. mem.font = ObjectGetString(0, szName, OBJPROP_FONT); 46. mem.x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 47. mem.y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 48. mem.w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 49. mem.h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 50. mem.fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 51. mem.cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 52. mem.text = ObjectGetString(0, szName, OBJPROP_TEXT); 53. 54. ObjectDelete(0, szName); 55. ChartRedraw(); 56. ObjectCreate(0, szName, type, 0, 0, 0); 57. 58. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable); 59. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, mem.x); 60. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, mem.y); 61. ObjectSetInteger(0, szName, OBJPROP_XSIZE, mem.w); 62. ObjectSetInteger(0, szName, OBJPROP_YSIZE, mem.h); 63. ObjectSetInteger(0, szName, OBJPROP_COLOR, mem.cor); 64. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, mem.fontSize); 65. ObjectSetString(0, szName, OBJPROP_FONT, mem.font); 66. ObjectSetString(0, szName, OBJPROP_TEXT, mem.text); 67. if (type == OBJ_EDIT) 68. { 69. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, clrWhite); 70. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 71. } 72. } 73. //+------------------------------------------------------------------+
代码 03
代码 03 与代码 02 类似,我们只看到了其中的一部分,但这次它几乎完整地显示了出来。这可以从第 35 行看到。第二个参数中指定的值仅在一个地方使用,即在第 58 行。现在请注意,这可能会让你感到有些困惑,具体取决于你在代码中实现的配置类型。目前,代码 03 的行为方式如下:

动画 06
“等一下,等等。这段代码的运行方式与代码 02 不同。这怎么可能?仅仅改变一个属性,代码就真的会这样变化吗?这太奇怪了。但是,编辑操作不能与对象选择同时进行吗?”在某些情况下,亲爱的读者,答案是肯定的。然而,由于有些问题需要深入探讨特定属性的工作原理,解释起来相当困难,因此我更倾向于使用另一种方法。这样更容易解释和演示,同时也不会让你感到困惑。
所以我想你已经注意到了以下情况:如果为 OBJ_EDIT 对象启用了 OBJPROP_SELECTABLE 属性,那么我们应该认为无法直接在图表上编辑文本。但是,移动该对象是完全可行的。
现在,考虑到这些事实,我们可以修改代码 03 以获得预期的行为。具体修改方法如下所示。
. . . 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. { 22. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE); 23. if (type == OBJ_EDIT) ObjectSetInteger(0, sparam, OBJPROP_SELECTABLE, false); 24. else if (type == OBJ_LABEL) 25. { 26. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 27. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 28. ObjectSetInteger(0, sparam, OBJPROP_SELECTED, true); 29. } 30. } 31. break; . . .
代码 04
在这种情况下,在代码 04 中,我们将仅关注代码 03 中真正需要更改的部分。换句话说,我们只看到需要修改的片段。现在,当我们在代码 03 中使用这个片段时,或者如果你更喜欢的话,在代码 02 中使用,结果将如下:

动画 07
“得了吧,这里正在实现的是一个多么疯狂且荒谬的解决方案啊。你一定是疯了。直接在图表上移动和编辑 OBJ_LABEL 对象,想出这样的解决办法根本不可能。”这真是个疯狂的想法。
但抛开疯狂的一面不谈,我刚才展示的内容难道不有趣吗?毕竟,我们只费了一点劲,就设法创建了一种直接在图表上编辑 OBJ_LABEL 对象的方法。当然,这个方案并非完美无缺。这是因为你需要使用该应用程序一段时间,才能习惯它的工作方式。这是一系列必须在特定时间执行的操作,如动画 07 所示。因此,第 24 行通过了检查,从而我们将 OBJ_LABEL 对象转换为 OBJ_EDIT 对象。但是,由于我们希望能够移动它,所以在第 28 行我们启用了对象选择功能。这样我们就可以在图表上移动 OBJ_EDIT 对象。
一旦我们再次点击同一对象,第 23 行的检查就会禁用选择功能。因此,在下一次点击时,MetaTrader 5 将不会选择 OBJ_EDIT 对象,而是选择文本,从而允许对其进行更改。这样做出来的效果已经不错了,但还可以做得更好。然而,由于我们的目标不是开发一个应用程序,而是展示我们能做什么以及如何去做,所以我对这里呈现的结果已经感到满意,可以转向下一个主题了。
直接在图表上管理尺寸
我可以毫不谦虚地说,到目前为止我们所看到的一切都很有趣,很有意思。但与我们接下来要看到的相比,这些都显得黯然失色。我们开始吧。应该说,根据你的创造力以及你想象所有事物如何组合的能力,会有更多或更少的选择。这仅仅是因为我们已经了解了 MetaTrader 5 和我们的应用程序是如何相互作用的,这使我们能够获得某种类型的结果,并为应用程序的工作方式创建一个特定的模型。
在我看来,MetaTrader 5 不仅用于交易,还能用于创建其他东西,这非常有趣。例如,可以将其用作图形编辑器、文本编辑器,甚至可以搭建一个用于简单游戏(如拼图或老式街机风格的经典游戏)的小平台。
许多人可能认为这样的事情是不可能的,因为我们显然没有实现它们的必要机制。然而,与许多人认为并自认为所了解的 MetaTrader 5 的功能相反,我们实际上有潜力在平台上实现这种功能。关键在于要有想象力,并具备必要的知识。更重要的是,我们必须有正确的概念,并知道如何利用它们来为我们服务。如果能够实现这一点,您将能够在 MetaTrader 5 中创造出许多有趣的东西,而不再局限于查看枯燥无味的价格图表。也许您甚至会想出一种稍微有趣一点的价格图表绘制方式,比如使用 3D 元素等。
但目前,你可能还没有准备好去想象这一点。换句话说,要做到这样的事情,现阶段我们仍然只是处于所有这些工作的基础阶段。是的,与你可能想象的相反,我们仍然处于 MQL5 编程所代表的基础水平;我们甚至可以说还处于初级水平。看看我们在这个基础水平上已经取得了多少成果。
那么,现在我们将来看一些其他的东西,这些事情做起来相当容易,不过如果你没有阅读过前面的文章,可能会觉得有点难理解。由于我们会用到一些技巧,所以可能会显得有些复杂。但是,我并不想将你们的想象力限制在我们能力的初级水平。如果我们能做某件事,那就去做吧,尽管要真正理解发生了什么以及使用了哪个概念,可能需要一些时间。
本主题的目标是开发一种使用鼠标调整图表上对象大小的方法。但我们尝试在不直接使用鼠标事件的情况下实现这一点。这似乎相当困难,不是吗?我们的想法是继续使用 MetaTrader 5 来管理某些事件,同时以一种能产生特定结果的方式来控制它们。
这看起来确实是一项非常艰巨的任务。这似乎是某个从精神病院逃出来的疯子才会想出来的,他痴迷于把所有人都变得和他一样疯狂。但你会发现,这其实很简单 — 你只需要坚持下去。或许,它甚至比这更简单。然而,由于这个主题相当广泛,需要多篇文章才能进行恰当的阐释和理解,我们将从以下事实开始:与代码其余部分不同,在这里,我们将允许你或任何其他用户直接调整对象大小。
不过在第一阶段,这还不适用于所有对象类型。它可能是由您的应用程序创建的,也可能是由用户添加到图表中的。这都没关系;无论对象是程序创建的还是用户手动添加的,我们都会以同样的方式处理它们。虽然存在一些限制,但我们会在实现代码时对其进行检查。首先,让我们来看一下下面的代码:
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. int OnInit() 09. { 10. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 11. 12. return INIT_SUCCEEDED; 13. }; 14. //+------------------------------------------------------------------+ 15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 16. { 17. return rates_total; 18. }; 19. //+------------------------------------------------------------------+ 20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 21. { 22. switch (id) 23. { 24. case CHARTEVENT_KEYDOWN : 25. break; 26. case CHARTEVENT_OBJECT_CLICK : 27. break; 28. case CHARTEVENT_OBJECT_DRAG : 29. break; 30. } 31. ChartRedraw(); 32. }; 33. //+------------------------------------------------------------------+ 34. void OnDeinit(const int reason) 35. { 36. ObjectsDeleteAll(0, def_Prefix); 37. ChartRedraw(); 38. }; 39. //+------------------------------------------------------------------+
代码 05
代码 05 是我们的基础模板。请注意,代码 05 指定了必须处理的三个事件。这是因为我们并不打算在此处构建一些所谓 “fantabulous”(该词为 “fantastic”(奇妙的) 与 “fabulous” (极好的)的合成词)的应用程序。在这里,我们将做一些简单易懂的事情:直接在图表上更改任何对象的大小,并可能更改其位置。对于位置更改,我还在考虑是否值得实现。
好吧,这里由你做主。那我们接下来要做什么呢?”亲爱的读者,下一步是使用 CHARTEVENT_OBJECT_CLICK 事件来获取我们将要处理的对象的数据。原则上,我们只会处理这些信息,即对象名称,因为对象名称才是我们真正需要的信息,其他数据都可以据此获取。所以现在我们将重点关注 OnChartEvent 过程,这样我们就不必再次展示全部代码了。因此,下面可以看到第一个更改:
. . . 19. //+------------------------------------------------------------------+ 20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 21. { 22. static string szNameObj = NULL; 23. 24. switch (id) 25. { 26. case CHARTEVENT_KEYDOWN : 27. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) szNameObj = NULL; 28. break; 29. case CHARTEVENT_OBJECT_CLICK : 30. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 31. if (szNameObj != NULL); 32. if (szNameObj != sparam); else szNameObj = NULL; 33. break; 34. case CHARTEVENT_OBJECT_DRAG : 35. break; 36. } 37. ChartRedraw(); 38. }; 39. //+------------------------------------------------------------------+ . . .
代码 06
“我们仍然不知道接下来要做什么。你能分享一小段代码吗?我开始有点担心了。”好吧,亲爱的读者们,这是你们要求的(笑声)。但在分享之前,先来看看代码06中的这段代码所蕴含的逻辑。理解这段逻辑对于预测接下来的步骤非常重要。因此,下一步将在下面的代码中展示:
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. //+------------------------------------------------------------------+
代码 07
“哇!别紧张,没必要走到那一步。”好吧,这是你自找的,这就是结果。虽然看起来很复杂,但这段代码本身非常简单且极其基础。要理解它,只需冷静地观察一切是如何完成的。总之,我将简要解释一下这里正在做的事情,因为这是我们接下来要做的事情的第一部分。
在第 08 行,我们创建了一个结构,其目的是围绕我们选择的对象构建一个框架。亲爱的读者,现在请注意。我们将围绕其创建轮廓的对象必须具有笛卡尔坐标。此处暂不考虑与价格刻度相关的坐标。由于 MetaTrader 5 中此类对象较少,且其中可能只有两个对象值得关注 — OBJ_TEXT 和 OBJ_BITMAP 对象 — 因此我目前认为没有使用它们的必要。然而,其他对象对我们来说也很重要。
请注意,在 OnChartEvent 事件处理函数中,我们引用了结构体内部的几个过程。但该结构本身包含一个内部私有过程。正是这个过程负责创建围绕选定对象的线条。
在这种情况下,我们使用了一种比较鲜艳的颜色。这是在第 28 行定义的。如果你愿意,可以用另一种看起来更合适的颜色替换它。但是,除非你在进行实验以了解其工作原理,否则请避免更改代码的其他部分。非常好,在第 38 行,我们记录所选对象是前景对象还是背景对象。这对我们来说很重要,因为在任何情况下,我们都会将对象发送到背景并取消选择,从而表明它未被选中。
但我们为什么要这么做呢?这样做的目的是为了防止该对象干扰我们接下来要做的事情。请记住,选中一个对象后,我们可以利用它的某个特定点在图表窗口中移动它。这是由 MetaTrader 5 直接完成的。然而,在这种情况下,我们不需要这种行为,因为我们将自己控制和管理它。首先,你需要完全理解这段代码的工作原理。但在结束这篇文章之前,让我们先来看看代码 07 的功能。这将在下面的动画中展示。但首先,请看一下图表上存在的对象。试着关注对象名称,因为这对于理解事件处理程序如何处理每个对象非常重要。这可以在图 03 中看到:

图 03
现在让我们继续观看演示动画。

动画 08
最后的想法
在本文中,我们展示了如何将 OBJ_LABEL 对象与 OBJ_EDIT 对象结合使用,以便直接在图表上编辑文本。此外,我们还了解了如何添加逻辑,使对象能够被移动,即使这在最初是不可能的。这是因为,当我们从 OBJ_LABEL 对象切换到 OBJ_EDIT 对象,然后再切换回来时,在实现对象移动方面陷入了死胡同。
我们还看到了下一篇文章中将要详细研究的初步内容。但是,由于我希望你们能很好地理解这里所做的工作,因为以后会用得上,我决定提前简要地展示一些内容,在我看来,这些内容会为你们提供关于使用 MQL5 控制 MetaTrader 5 的各种思路。
以上就是全部内容。我们下一篇文章见。祝你好运,继续学习,期待不久后与你相见!
| MQ5 文件 | 描述 |
|---|---|
| 代码 01 | 对象事件的演示 |
| 代码 02 | 对象事件的演示 |
| 代码 03 | 对象事件的演示 |
| 代码 04 | 对象事件的演示 |
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/16075
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
交易中的神经网络:不依赖特定数据的时间序列泛化(核心模型模块)
新手在交易中的10个基本错误
利用深度强化学习优化Ilan智能交易系统