English Русский Español Deutsch 日本語 Português
preview
从基础到中级:对象事件(三)

从基础到中级:对象事件(三)

MetaTrader 5示例 |
33 0
CODE X
CODE X

概述

在上一篇文章“从基础到中级:对象事件(二)”中,我们展示了如何实现一种非常有趣、甚至有些奇特的应用程序,其目的是让用户直接在图表上编辑 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

附加的文件 |
Anexo.zip (4.19 KB)
交易中的神经网络:不依赖特定数据的时间序列泛化(核心模型模块) 交易中的神经网络:不依赖特定数据的时间序列泛化(核心模型模块)
我们继续介绍Mamba4Cast框架。今天,我们将深入探讨该方案的实现。Mamba4Cast的设计目标并非对每一条全新时间序列都进行冗长的预热,而是支持直接部署上线。依托零样本预测理念,该模型无需额外训练、也无需调优超参数,就可以在真实业务数据上输出高质量预测结果。
开发多币种 EA(第 28 部分):添加平仓管理器 开发多币种 EA(第 28 部分):添加平仓管理器
在并行运行多个策略时,您可能希望定期关闭所有持仓,并重新启动策略。现有代码仅允许通过手动操作来实现这一行为。让我们尝试将这一部分自动化。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
利用深度强化学习优化Ilan智能交易系统 利用深度强化学习优化Ilan智能交易系统
本研究对经典Ilan网格交易策略进行改进,通过在MQL5中集成Q-learning算法,构建出适用于MetaTrader 5平台的自适应版本。本文展示如何定义状态特征、将连续状态空间映射为Q-table可处理的离散值、使用ε-贪婪算法选择操作,以及针对平均持仓收益与平仓事件设计奖励机制。您将实现Q-table的存储与加载功能、通过参数调优优化学习效率,并在策略测试器中对EURUSD(欧元兑美元)/AUDUSD(澳元兑美元)进行测试,以评估稳定性和回撤风险。