Русский Español Português
preview
From Basic to Intermediate: Object Events (II)

From Basic to Intermediate: Object Events (II)

MetaTrader 5Examples |
82 0
CODE X
CODE X

Introduction

In the previous article From Basic to Intermediate: Object Events (I) the first basic part was explained and demonstrated, focusing mainly on events that occur in objects. However, we still need to discuss three other events that can be triggered when an object interacts with the user. Of these three events, one must be explicitly enabled for MetaTrader 5 to actually start generating it. Otherwise, even if our code is prepared to handle this event, it will never receive a notification that it has occurred.

It is very important to understand each of these event types before using them together, so we will proceed as follows: we will create short sections solely to explain how and when MetaTrader 5 generates each of these last three types of events. Once you understand them, we can look at how to use them together to implement some interesting functionality. So, let us begin.


The CHARTEVENT_OBJECT_DELETE event

This event, like CHARTEVENT_OBJECT_CREATE and other mouse-related events, must be enabled and disabled by our application. This is done so that MetaTrader 5 knows when an application running on the chart needs to receive a notification that an object present on the chart has been deleted.

This event definitely has a preventive purpose. This is important because, if we want to maintain a certain object configuration in the application, this event lets us prevent the user from deleting a sensitive or critical object from the chart, whether by mistake while deleting objects from the chart or for any other reason.

In principle, this event is very simple. However, some precautions must be taken when programming or implementing the code. The reason for these precautions is that, in some cases, we may encounter issues related to the color scheme. This ultimately makes it very difficult to display the object correctly when the application needs to recreate it automatically.

But what I am talking about will in no way be our concern here. This material is not aimed at explaining how to implement a universal solution capable of covering every possible case. Besides, each case is different.

There is no fully integrated and universal way to recreate an object that was improperly deleted from the chart. As the programmer, you can come up with many ways to ensure this. Personally, I suggest using a small structure that stores the properties of the objects created by us, or rather, by our application. This way, when the application needs to recreate an object, it will do so using the appropriate color scheme or positioning. In addition, of course, to other matters such as the font, font size, and object size; I think the idea is clear.

After this brief introduction, I think you are ready to see what the first code in this article will look like. The code is shown below:

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_szObjectName;
09. //+------------------------------------------------------------------+
10. int OnInit()
11. {
12.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
13.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
14. 
15.     gl_szObjectName = macro_NameObject;
16.     Proc_Object();
17. 
18.     return INIT_SUCCEEDED;
19. };
20. //+------------------------------------------------------------------+
21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
22. {
23.     return rates_total;
24. };
25. //+------------------------------------------------------------------+
26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
27. {
28.     switch (id)
29.     {
30.         case CHARTEVENT_MOUSE_MOVE:
31.             ObjectSetString(0, gl_szObjectName, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
32.             break;        
33.         case CHARTEVENT_OBJECT_DELETE:
34.             if (sparam == gl_szObjectName) Proc_Object();
35.             break;
36.     }
37.     ChartRedraw();
38. };
39. //+------------------------------------------------------------------+
40. void OnDeinit(const int reason)
41. {
42.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
43.     ObjectsDeleteAll(0, def_Prefix);
44.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
45.     ChartRedraw();
46. };
47. //+------------------------------------------------------------------+
48. void Proc_Object(void)
49. {
50.     ObjectCreate(0, gl_szObjectName, OBJ_LABEL, 0, 0, 0);
51.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_SELECTABLE, true);
52.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_XDISTANCE, 50);
53.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_YDISTANCE, 50);
54.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_XSIZE, 150);
55.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_COLOR, clrMediumBlue);
56.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_FONTSIZE, 20);
57.     ObjectSetString(0, gl_szObjectName, OBJPROP_FONT, "Lucida Console");
58. }
59. //+------------------------------------------------------------------+

Code 01

The purpose of Code 01 is very simple: to show what happens when we delete an object from the chart. Since we need to enable and disable the notification so that MetaTrader 5 tells us whether an object has been deleted or not, this code makes it much easier to understand how everything works.

Perhaps the most difficult part is not enabling the notification, since that is very easy to do and only requires line 13. As a rule, we enable the notification at the very beginning of application execution so that we do not miss any deletion events that may occur. The real difficulty is knowing exactly when to disable this notification. MetaTrader 5 works in a certain way, and depending on the moment at which the notification is disabled, we can get rather strange, although in most cases completely harmless, results.

To understand this, try changing the position of the content found on line 42. It is responsible for disabling event notifications when an object is deleted from the chart. Since the source code will be in the application, you can try swapping the contents of line 42 and line 43. This will make the result of removing the indicator from the chart quite interesting. This way, we will see that we cannot always perform these actions in just any order. It will also help you understand various issues that would otherwise be difficult to explain.

In any case, when Code 01 is running, we will basically be able to perform two types of actions. The first example can be seen in the following animation:

Animation 01

In this case, we select the object and delete it using the DELETE key. Please note that the object is recreated immediately after the DELETE key is pressed. However, because this is tied to a mouse event for updating the information, the information will start displaying properly only after a mouse movement or mouse event occurs. The second situation is an attempt to delete the object using the object list window. This can be seen in the following animation:

Animation 02

As shown in Animation 01, here in Animation 02 it is clear that the very moment we delete the object, it is recreated. Even so, the window does not show this immediately. The window has to be opened again for the object to appear once more in the list of objects present on the chart.

So, this event serves to notify us that an object has been deleted from the chart. Now we need to look at two more cases. However, some rather interesting issues arise here. To explain this better, let us start a new section.


The CHARTEVENT_OBJECT_CHANGE event

The event we are going to examine now is quite interesting if we understand how it works. This is because, depending on the situation, it can be used to implement some very interesting features, since it is generated every time one of an object’s properties changes.

In the previous section, we mentioned that there is no way to create a 100% universal mechanism for restoring a deleted object. And that is true. However, depending on how the code is implemented, we can find a suitable way to recreate almost any type of object in a more generic way. Judging by what has been shown so far, this is a rather complex task. So stay calm, because although we can create good applications using relatively simple mechanisms, we may find better ways in the future. Knowledge accumulates, as does experience. Therefore, it is always useful to learn and practice. Without rushing, but striving for continuous learning.

To show how interesting this can be and why it is important to understand exactly what is being implemented, we will take Code 01, which we examined in the previous section, and modify it to see the event from this section in action. This is done in the code shown below:

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_Obj
09. {
10. //+----------------+
11.     private:
12.         string  szName,
13.                 font;
14.         bool    Selectable;
15.         int     x,
16.                 y,
17.                 w,
18.                 fontSize;
19.         color   cor;
20. //+----------------+
21.     public:
22. //+----------------+
23.         void SetDefault(const string arg)
24.         {
25.             szName      = arg;
26.             font        = "Lucida Console";
27.             Selectable  = true;
28.             x           = 50;
29.             y           = 50;
30.             w           = 150;
31.             cor         = clrMediumBlue;
32.             fontSize    = 20;
33.         }
34. //+----------------+
35.         void Create(void)
36.         {
37.             ObjectCreate(0, szName, OBJ_LABEL, 0, 0, 0);
38.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable);
39.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
40.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
41.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
42.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
43.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
44.             ObjectSetString(0, szName, OBJPROP_FONT, font);
45.         }
46. //+----------------+
47.         string GetName(void)
48.         {
49.             return szName;
50.         }
51. //+----------------+
52. }gl_Obj;
53. //+------------------------------------------------------------------+
54. int OnInit()
55. {
56.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
57.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
58. 
59.     gl_Obj.SetDefault(macro_NameObject);
60.     gl_Obj.Create();
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.     switch (id)
73.     {
74.         case CHARTEVENT_MOUSE_MOVE:
75.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
76.             break;        
77.         case CHARTEVENT_OBJECT_DELETE:
78.             if (sparam == gl_Obj.GetName()) gl_Obj.Create();
79.             break;
80.         case CHARTEVENT_OBJECT_CHANGE:
81.             Comment("An event occurred: CHARTEVENT_OBJECT_CHANGE in object ["+sparam+"]");
82.             break;
83.     }
84.     ChartRedraw();
85. };
86. //+------------------------------------------------------------------+
87. void OnDeinit(const int reason)
88. {
89.     Comment("");
90.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
91.     ObjectsDeleteAll(0, def_Prefix);
92.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
93.     ChartRedraw();
94. };
95. //+------------------------------------------------------------------+

Code 02

When running Code 02, we will see two things. The first is shown in the following animation.

Animation 03

Note that the very moment we change any property of the object, an event is generated and will be intercepted on line 80. Since we do not filter the source of the event, any change in any object will cause line 81 to be displayed in the upper-left corner of the chart, as can be seen in Animation 03. But the most interesting part begins when the second event occurs, as shown in Animation 04:

Animation 04

"Strange. Why did the object not keep its property? We call the procedure shown on line 35. In the case of Code 01, I understand the reason, but in this case I assumed that if the object were deleted from the chart, the application would recreate it with the same attributes. The SetDefault function was not called to reset the values changed in Animation 03. In my opinion, this is quite strange."

Dear reader, many novice programmers often fall into the same trap: they write code but do not understand some internal aspects of the code itself. This usually happens precisely because they copy code from somewhere else. But it can also happen when we test a new implementation in which everything seems to be perfectly fine, only to discover during testing that some details have still not been implemented. In any case, this is not a mistake; quite the opposite. It is a good way to practice and test new solutions that may prove interesting in the future.

Great, but what is the problem then? The point is that when you use MetaTrader 5 to update some property of an object, you are not updating the properties with which it was created. We update only the local properties of the corresponding object. Therefore, when it is deleted and line 80 of Code 02 recreates it, this will be done exactly using the parameters or properties defined and still stored in the global variable.

Thus, the object is recreated exactly as it was configured in the code. To apply changes, we need to update the object inside the application by adding the corresponding changes to it. And for this, we need to use the event interception performed precisely on line 82. That is why we do not filter the event in Code 02. I want you to understand how the application receives a notification that a property of one of the objects present on the chart has changed.

Since MetaTrader tells us only the name of the object, and not which exact properties were changed, we need to capture all of them. At least the ones that really interest us. Although many objects have similar properties, some properties belong exclusively to one particular object and are not present in others. That is why we said that creating a 100% universal solution is a very complex task.

With that in mind, we can modify Code 02 to keep some properties unchanged. At least while the code is running. If the application is removed from the chart, it will lose its user-defined properties and return to what we implemented in the code. There are several ways to solve this problem. But we will talk about that another time.

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. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font;
014.         bool    Selectable;
015.         int     x,
016.                 y,
017.                 w,
018.                 fontSize;
019.         color   cor;
020. //+----------------+
021.     public:
022. //+----------------+
023.         void SetDefault(const string arg)
024.         {
025.             szName      = arg;
026.             font        = "Lucida Console";
027.             Selectable  = true;
028.             x           = 50;
029.             y           = 50;
030.             w           = 150;
031.             cor         = clrMediumBlue;
032.             fontSize    = 20;
033.         }
034. //+----------------+
035.         void Create(void)
036.         {
037.             ObjectCreate(0, szName, OBJ_LABEL, 0, 0, 0);
038.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable);
039.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
040.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
041.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
042.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
043.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
044.             ObjectSetString(0, szName, OBJPROP_FONT, font);
045.         }
046. //+----------------+
047.         string GetName(void)
048.         {
049.             return szName;
050.         }
051. //+----------------+
052.         void Update(void)
053.         {
054.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
055.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
056.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
057.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
058.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
059.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
060.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
061.         }
062. //+----------------+
063. }gl_Obj;
064. //+------------------------------------------------------------------+
065. int OnInit()
066. {
067.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
068.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
069. 
070.     gl_Obj.SetDefault(macro_NameObject);
071.     gl_Obj.Create();
072. 
073.     return INIT_SUCCEEDED;
074. };
075. //+------------------------------------------------------------------+
076. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
077. {
078.     return rates_total;
079. };
080. //+------------------------------------------------------------------+
081. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
082. {
083.     switch (id)
084.     {
085.         case CHARTEVENT_MOUSE_MOVE:
086.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
087.             break;        
088.         case CHARTEVENT_OBJECT_DELETE:
089.             if (sparam == gl_Obj.GetName()) gl_Obj.Create();
090.             break;
091.         case CHARTEVENT_OBJECT_CHANGE:
092.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
093.             break;
094.     }
095.     ChartRedraw();
096. };
097. //+------------------------------------------------------------------+
098. void OnDeinit(const int reason)
099. {
100.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
101.     ObjectsDeleteAll(0, def_Prefix);
102.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
103.     ChartRedraw();
104. };
105. //+------------------------------------------------------------------+

Code 03

Code 03 shows what needs to be done. The result of its execution can be seen in the following animation:

Animation 05

Please note that the object is now recreated according to the properties that the user defined a few minutes earlier. Even if the object is deleted from the chart, it will be recreated because of its importance. However, unlike what happened before, it will now be recreated not with the properties defined in the application, but with the properties set by the user and saved thanks to the call made on line 92.

But note that this time we are using filtering. This is important because of the very nature of what we are doing. Consider the following scenario: you have two objects on the chart, and you decide to change the properties of one of them. Up to this point, everything is fine. However, when we delete the object controlled by our application, it will be recreated immediately. Nevertheless, if the filtering on line 92 were not applied, changing an object different from the one controlled by the application would cause the properties of that other object to be applied to the object recreated by the application, which would create enormous confusion in everything being done on the chart with objects. That is why the filtering on line 92 is so important to us.

Try testing this same Code 03 without using the filtering on line 92 and observe the results. Sometimes this approach can be interesting. Therefore, understanding and observing what happens may be useful to you at some point, dear reader.

Learning does not happen only when we write code correctly.

It also happens when the code does not work as expected.

In this case, understanding how the solution was found is just as important as the solution itself.

Great, now we have enough elements to examine the next and final type of event that can occur with an object. In this case, since we are dealing with a unique object and with an event specifically related to it, we will conduct a small experiment that, in my opinion, will be very exciting and quite instructive. We will do something that MetaTrader 5 does not do by default. But we can create an application capable of doing it. This will be very interesting, given the nature of the implementation.

So, let us move on to a new section so that everything is properly separated.


The CHARTEVENT_OBJECT_ENDEDIT event

One point that I find quite curious is that many users have no idea how much we can manipulate MetaTrader 5 to perform certain types of tasks. Often these are things that people stubbornly insist are impossible or unrealistic to do.

Well, essentially, there is one issue that, on the one hand, is quite unusual. On the other hand, doing it the way it is done does not make much sense. But the main thing is patience. The point is that, contrary to popular belief, MQL5 is not a language focused on creating applications for MetaTrader 5. Although, in general terms, it does serve exactly that purpose. Essentially, MQL5 is a language aimed at giving us a certain degree of control over how MetaTrader 5 should work, or how we want it to work. Of course, while following certain rules. I say this because, without understanding this simple and clear detail, it will be difficult to understand what we will do in this section.

For the vast majority of users, especially beginners, what will be done here will seem like out of this world. Perhaps some code created by artificial intelligence or aliens. However, there is nothing of the sort. What we are going to do is entirely possible with very little knowledge, but with a good dose of imagination and creativity. In addition, of course, to a good dose of observation.

I do not know whether you have already noticed one thing: the OBJ_LABEL object is very similar to OBJ_EDIT. In general terms, the main, and perhaps the only, difference between the two objects is that OBJ_EDIT allows us to edit text directly on the chart, while the OBJ_LABEL object does not. But is that really so?

Well, dear reader, the truth is that although they are very similar, the OBJ_EDIT object does not have the same characteristics as the OBJ_LABEL object, and vice versa. But this is if we look at things from the user’s point of view. From a programming point of view, however, things are not quite like that. In this case, both objects are basically the same and can even work together, if we just think about how to do it.

What we will do here is the following: we will create a way to make the OBJ_LABEL object editable directly on the chart without having to open its properties window to enter text. Seems impossible? Then let us see how this can be done. Remember that the purpose here is educational, so do not focus too much on how the code will be implemented. Try to understand why what you will see next works. That is the most important thing.

To begin with, we need the source code. Since we do not want to create too much from scratch and waste a lot of time explaining our actions, we will create a variation of the code examined in the previous section. It can be seen below:

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. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font;
014.         bool    Selectable;
015.         int     x,
016.                 y,
017.                 w,
018.                 h,
019.                 fontSize;
020.         color   cor,
021.                 backColor;
022.         ENUM_OBJECT actual;
023. //+----------------+
024.     public:
025. //+----------------+
026.         void SetDefault(const string arg)
027.         {
028.             szName      = arg;
029.             font        = "Lucida Console";
030.             Selectable  = true;
031.             x           = 50;
032.             y           = 50;
033.             w           = 150;
034.             h           = 27;
035.             cor         = clrMediumBlue;
036.             backColor   = clrWhite;
037.             fontSize    = 20;
038.         }
039. //+----------------+
040.         void Create(const ENUM_OBJECT type)
041.         {
042.             actual = type;
043.             Recreates();
044.         }
045. //+----------------+
046.         void Recreates(void)
047.         {
048.             ObjectCreate(0, szName, actual, 0, 0, 0);
049.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
050.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
051.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
052.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
053.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
054.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
055.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
056.             ObjectSetString(0, szName, OBJPROP_FONT, font);
057.             if (actual == OBJ_EDIT)
058.             {
059.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
060.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
061.             }
062.         }
063. //+----------------+
064.         string GetName(void)
065.         {
066.             return szName;
067.         }
068. //+----------------+
069.         void Update(void)
070.         {
071.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
072.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
073.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
074.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
075.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
076.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
077.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
078.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
079.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
080.         }
081. //+----------------+
082. }gl_Obj;
083. //+------------------------------------------------------------------+
084. int OnInit()
085. {
086.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
087.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
088. 
089.     gl_Obj.SetDefault(macro_NameObject);
090.     gl_Obj.Create(OBJ_LABEL);
091. 
092.     return INIT_SUCCEEDED;
093. };
094. //+------------------------------------------------------------------+
095. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
096. {
097.     return rates_total;
098. };
099. //+------------------------------------------------------------------+
100. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
101. {
102.     switch (id)
103.     {
104.         case CHARTEVENT_MOUSE_MOVE      :
105.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
106.             break;        
107.         case CHARTEVENT_OBJECT_DELETE   :
108.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
109.             break;
110.         case CHARTEVENT_OBJECT_CHANGE   :
111.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
112.             break;
113.     }
114.     ChartRedraw();
115. };
116. //+------------------------------------------------------------------+
117. void OnDeinit(const int reason)
118. {
119.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
120.     ObjectsDeleteAll(0, def_Prefix);
121.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
122.     ChartRedraw();
123. };
124. //+------------------------------------------------------------------+

Code 04

We have added a few things here. However, the following should be noted: Code 04 works in much the same way as Code 03. But note that on line 40, when we call the procedure to create the object, we need to specify which type of object should be created. In this case, we will work with OBJ_EDIT and OBJ_LABEL. By making these changes, we begin laying the groundwork for another possibility, the essence of which you will soon understand: manipulating an OBJ_LABEL object in order to edit text directly on the chart.

Now pay attention to the next point found in this Code 04. Please note that on line 49, where we actually create the object, we define the OBJPROP_SELECTABLE property. This property is very important, and we must define it correctly. In an OBJ_LABEL object, we need to set this property to true if we want to be able to select the object later, as we have done so far. However, if we define this same property as true in an OBJ_EDIT object, we will not be able to edit the text directly on the chart.

As stated earlier, each object has its own peculiarities, and depending on the type of action we want to perform, we need its properties to be defined in a specific way. So, to be brief, when we have an OBJ_EDIT object with the OBJPROP_SELECTABLE property set to true, we cannot edit the text, but we can move the object around the screen. If this same property is set to false, we can edit the text, but we cannot move the object around the screen.

Now, please note that on line 90 we indicate the type of object we want to create, namely an OBJ_LABEL object. However, on line 108 we no longer use the Create call; instead, we call a function that will recreate the object. This happens because we already know which type we will need to create. But the application does not, because if we use this method to request the object type, we will almost always get the wrong type. This happens because MetaTrader 5 has already destroyed that object.

When we receive the CHARTEVENT_OBJECT_DELETE event, it does not ask permission to destroy the object. By that moment, MetaTrader 5 has already deleted the object. Consequently, unless we use an object of the zero type, that is, OBJ_VLINE, we will never know what type of object was destroyed if we ask MetaTrader 5 about it. It will not be able to tell us, because the object no longer exists.

So far, in my opinion, everything is very simple and clear. Now we come to the most interesting part: we will take Code 04 and modify it so that several events work together to obtain the desired result.

Since presenting everything at once may seem rather complex, we will do this in two simple steps. The first example is shown in the code below.

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. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font,
014.                 text;
015.         bool    Selectable;
016.         int     x,
017.                 y,
018.                 w,
019.                 h,
020.                 fontSize;
021.         color   cor,
022.                 backColor;
023.         ENUM_OBJECT actual;
024. //+----------------+
025.     public:
026. //+----------------+
027.         void SetDefault(const string arg)
028.         {
029.             szName      = arg;
030.             font        = "Lucida Console";
031.             Selectable  = true;
032.             x           = 50;
033.             y           = 50;
034.             w           = 250;
035.             h           = 27;
036.             cor         = clrMediumBlue;
037.             backColor   = clrWhite;
038.             fontSize    = 20;
039.         }
040. //+----------------+
041.         void Create(const ENUM_OBJECT type)
042.         {
043.             actual  = type;
044.             text    = EnumToString(type);
045.             Recreates();
046.         }
047. //+----------------+
048.         void Recreates(void)
049.         {
050.             ObjectCreate(0, szName, actual, 0, 0, 0);
051.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
052.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
053.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
054.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
055.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
056.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
057.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
058.             ObjectSetString(0, szName, OBJPROP_FONT, font);
059.             ObjectSetString(0, szName, OBJPROP_TEXT, text);
060.             if (actual == OBJ_EDIT)
061.             {
062.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
063.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
064.             }
065.         }
066. //+----------------+
067.         string GetName(void)
068.         {
069.             return szName;
070.         }
071. //+----------------+
072.         void Update(void)
073.         {
074.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
075.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
076.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
077.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
078.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
079.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
080.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
081.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
082.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
083.         }
084. //+----------------+
085. }gl_Obj;
086. //+------------------------------------------------------------------+
087. int OnInit()
088. {
089.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
090. 
091.     gl_Obj.SetDefault(macro_NameObject);
092.     gl_Obj.Create(OBJ_LABEL);
093. 
094.     return INIT_SUCCEEDED;
095. };
096. //+------------------------------------------------------------------+
097. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
098. {
099.     return rates_total;
100. };
101. //+------------------------------------------------------------------+
102. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
103. {
104.     switch (id)
105.     {
106.         case CHARTEVENT_OBJECT_CLICK    :
107.             if (sparam == gl_Obj.GetName())
108.             {
109.                 string sz0 = ObjectGetString(0, sparam, OBJPROP_TEXT);
110.                 if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_EDIT)
111.                     break;
112.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
113.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
114.                 ObjectDelete(0, sparam);
115.                 gl_Obj.Create(OBJ_EDIT);
116.                 ObjectSetString(0, sparam, OBJPROP_TEXT, sz0);
117.             }
118.             break;
119.         case CHARTEVENT_OBJECT_CREATE   :
120.             if (sparam == gl_Obj.GetName())
121.             {
122.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false);
123.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
124.             }
125.             break;
126.         case CHARTEVENT_OBJECT_DELETE   :
127.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
128.             break;
129.         case CHARTEVENT_OBJECT_CHANGE   :
130.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
131.             break;
132.     }
133.     ChartRedraw();
134. };
135. //+------------------------------------------------------------------+
136. void OnDeinit(const int reason)
137. {
138.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
139.     ObjectsDeleteAll(0, def_Prefix);
140.     ChartRedraw();
141. };
142. //+------------------------------------------------------------------+

Code 05

Here, in Code 05, something very strange happens. You can see this in the following animation:

Animation 06

Look carefully at Animation 06, dear reader. Note that on line 92 we request the creation of an OBJ_LABEL object. For this reason, line 44 sets the text to indicate the type of object being used. So far, everything proceeds as usual. However, as soon as we click this OBJ_LABEL object, it will be changed into an OBJ_EDIT object. This is done precisely by the code that handles the intercepted event on line 106. Please note that we will focus on the object that we initially create.

However, because of line 109, the text contained in the OBJ_LABEL object will be transferred to the OBJ_EDIT object on line 116. This is important to us because, without this transfer, different text would be placed in the OBJ_EDIT object. And that is not what we want. What we really want is to be able to change the text of the OBJ_LABEL object directly on the chart.

Now note the following: as part of the CHARTEVENT_OBJECT_CLICK event, we will delete the OBJ_LABEL object and create an OBJ_EDIT object. But since we enabled notification for the CHARTEVENT_OBJECT_DELETE event on line 89, we need to deactivate it. This is done on line 112. Immediately after that, we enable another event whose purpose is to tell MetaTrader 5 that we want to receive notifications about object creation. This will cause the object creation event performed by the application to be intercepted on line 119. In this handler, we will disable the creation notification and enable the deletion notification again.

Thanks to this, the handler on line 126 will be able to work again, although not perfectly. This is because if we delete the OBJ_EDIT object, it will be recreated, but its text will no longer match the original text that existed before the object was destroyed.

We will leave this small fix as an exercise for your practice, so that you can find a way to solve this minor problem. It is very easy to do. However, remember that some data will need to be processed in order to restore the text that existed at the moment the OBJ_EDIT object was created. Restoring the text that existed between the creation of OBJ_EDIT and its deletion, however, will require much more work. You will also need to handle keyboard and mouse events to keep the text up to date in memory and be able to reproduce it.

Personally, I consider this second part completely unnecessary and rather annoying. Since, if the user deleted the text before saving it in the OBJ_LABEL object, then let the user remember what text was in the OBJ_EDIT object. (LAUGHTER).

Well, now comes the final part, which consists precisely of using the event from this section. To do this, we will make one last change to the code. It can be seen below.

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. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font,
014.                 text;
015.         bool    Selectable;
016.         int     x,
017.                 y,
018.                 w,
019.                 h,
020.                 fontSize;
021.         color   cor,
022.                 backColor;
023.         ENUM_OBJECT actual;
024. //+----------------+
025.     public:
026. //+----------------+
027.         void SetDefault(const string arg)
028.         {
029.             szName      = arg;
030.             font        = "Lucida Console";
031.             Selectable  = true;
032.             x           = 50;
033.             y           = 50;
034.             w           = 250;
035.             h           = 27;
036.             cor         = clrMediumBlue;
037.             backColor   = clrWhite;
038.             fontSize    = 20;
039.         }
040. //+----------------+
041.         void Create(const ENUM_OBJECT type)
042.         {
043.             actual  = type;
044.             text    = EnumToString(type);
045.             Recreates();
046.         }
047. //+----------------+
048.         void Recreates(void)
049.         {
050.             ObjectCreate(0, szName, actual, 0, 0, 0);
051.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
052.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
053.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
054.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
055.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
056.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
057.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
058.             ObjectSetString(0, szName, OBJPROP_FONT, font);
059.             ObjectSetString(0, szName, OBJPROP_TEXT, text);
060.             if (actual == OBJ_EDIT)
061.             {
062.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
063.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
064.             }
065.         }
066. //+----------------+
067.         string GetName(void)
068.         {
069.             return szName;
070.         }
071. //+----------------+
072.         void Update(void)
073.         {
074.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
075.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
076.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
077.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
078.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
079.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
080.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
081.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
082.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
083.             text        = ObjectGetString(0, szName, OBJPROP_TEXT);
084.         }
085. //+----------------+
086. }gl_Obj;
087. //+------------------------------------------------------------------+
088. int OnInit()
089. {
090.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
091. 
092.     gl_Obj.SetDefault(macro_NameObject);
093.     gl_Obj.Create(OBJ_LABEL);
094. 
095.     return INIT_SUCCEEDED;
096. };
097. //+------------------------------------------------------------------+
098. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
099. {
100.     return rates_total;
101. };
102. //+------------------------------------------------------------------+
103. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
104. {
105. //+----------------+
106.     #define macro_SWAP(A)   {                                           \
107.                 string sz0 = ObjectGetString(0, sparam, OBJPROP_TEXT);  \
108.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);   \
109.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);    \
110.                 ObjectDelete(0, sparam);                                \
111.                 gl_Obj.Create(A);                                       \
112.                 ObjectSetString(0, sparam, OBJPROP_TEXT, sz0);          \
113.                 gl_Obj.Update();                                        \
114.                             }
115. //+----------------+
116.     switch (id)
117.     {
118.         case CHARTEVENT_OBJECT_CLICK    :
119.             if (sparam == gl_Obj.GetName())
120.                 if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) != OBJ_EDIT)
121.                     macro_SWAP(OBJ_EDIT);
122.             break;
123.         case CHARTEVENT_OBJECT_CREATE   :
124.             if (sparam == gl_Obj.GetName())
125.             {
126.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false);
127.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
128.             }
129.             break;
130.         case CHARTEVENT_OBJECT_DELETE   :
131.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
132.             break;
133.         case CHARTEVENT_OBJECT_CHANGE   :
134.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
135.             break;
136.         case CHARTEVENT_OBJECT_ENDEDIT  :
137.             if (sparam == gl_Obj.GetName()) macro_SWAP(OBJ_LABEL)
138.             break;
139.     }
140.     ChartRedraw();
141. //+----------------+
142.     #undef macro_SWAP
143. //+----------------+
144. };
145. //+------------------------------------------------------------------+
146. void OnDeinit(const int reason)
147. {
148.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
149.     ObjectsDeleteAll(0, def_Prefix);
150.     ChartRedraw();
151. };
152. //+------------------------------------------------------------------+

Code 06

Code 06 already implements several functions, including the fix for this small problem. But we deliberately changed part of the code to hide how we solved the problem. This was done so that less experienced users could not immediately see how the solution was obtained.

Since Code 06 is as simple as the others, and anyone who has studied and practiced will be able to solve it without difficulty, I do not think I need to explain much. I will say only this: when an OBJ_EDIT object finishes editing, which usually happens when the ENTER key is pressed, MetaTrader 5 generates a CHARTEVENT_OBJECT_ENDEDIT event. This event will be intercepted on line 136, and this will cause the object that previously had the OBJ_EDIT type to become an OBJ_LABEL object again.

A possible result is shown below:

Animation 07

But do not let Animation 07 mislead you, dear reader. Try to study and understand what is really happening here. What you see in Animation 07 is only the tip of a huge iceberg. This is because here we have shown only the most basic aspects of the matter. We can go much further than what has been shown here.


Final thoughts

Many may have found this article somewhat boring, since we constantly showed the full code and gave only brief explanations of how it works. But personally, I consider it unnecessary to repeat what has already been explained in other articles in this same series. Of course, we did something that may surprise many people and even make them skeptical. That is normal; it is part of the learning process.

But before we finish this article and move on to the next one, I would like to give you one more piece of advice, dear reader. In the last section, you saw that we can do many interesting things. If you look closely, you will notice that we changed the dimensions of the OBJ_LABEL object so that a slightly longer text could be placed. My advice is: try to understand exactly how text is created in an OBJ_EDIT object. This is necessary so that you can change the dimensions of the OBJ_LABEL object when displaying text on the chart.

Also, try to come up with a way to move the OBJ_LABEL object on the chart. This is because it will always remain fixed in the place where it was created. Note that this happens precisely because, at the moment it is selected, due to the CHARTEVENT_OBJECT_CLICK event, it is transformed into an OBJ_EDIT object. This prevents us from changing its position.

In addition, there is something else we can do, but most likely we will examine it in the next article. If, by chance, I notice that it is not very interesting or would not contribute much to the article, I will say what it is about. In any case, you now know a little more about MQL5 and MetaTrader 5 than you did at the beginning of this article.

So try to study and practice with the code presented in the attachment, and I will see you in the next article.

MQ5 file Description
Code 01 Demonstration of object events
Code 02 Demonstration of object events
Code 03  Demonstration of object events
Code 04  Demonstration of object events

Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/16050

Attached files |
Anexo.zip (4.5 KB)
Market Simulation: Getting Started with SQL in MQL5 (V) Market Simulation: Getting Started with SQL in MQL5 (V)
In the previous article, I showed how to proceed in order to add a query mechanism. This was needed so that, inside MQL5 code, you could fully use SQL and retrieve results when executing a SQL SELECT ... FROM query. But there is still one last function we need to implement. This is the DatabaseReadBind function. Since understanding it properly requires a slightly more detailed explanation, it was decided to cover it not in the previous article, but in today's article. So, since the topic will be fairly extensive, let us proceed directly to the next section.
Community of Scientists Optimization (CoSO): Practice Community of Scientists Optimization (CoSO): Practice
We resume the topic of optimization by the scientific community. CoSO should not be viewed as a ready-made solution, but as a promising research platform. With proper development, CoSO can find its niche in tasks where adaptability and resilience to change are important, and computation time is not critical.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Market Microstructure in MQL5 (Part 6): Order Flow Market Microstructure in MQL5 (Part 6): Order Flow
This article adds six order-flow functions and a new OrderFlowAnalysis struct to MicroStructureFoundation.mqh: VPINOHLC, signed flow imbalance, trade intensity versus a 20-session baseline, a late-minus-early smart-money index, flow momentum, and a wrapper that outputs a confidence weight. Flow confidence is gated by noise and jump intensity from Parts 5 and 4. Calibrated on 602 NQ M1 NY sessions, it provides ready-to-use intraday flow signals with documented thresholds.