Español Português
preview
От начального до среднего уровня: События в объектах (I)

От начального до среднего уровня: События в объектах (I)

MetaTrader 5Примеры |
42 0
CODE X
CODE X

Введение

В предыдущей статье От базового до среднего уровня: Объекты (IV) было показано, как можно модифицировать объект, присутствующий в MetaTrader 5, для создания другого объекта. И делалось это с целью, которая может оказаться весьма интересной и сама по себе вызывает любопытство. В той статье вам, мой дорогой читатель, также было предложено небольшое задание, решение которого я показывать не буду. Это связано с тем, что я хочу побудить вас практиковаться и изучать материалы статей, чтобы научиться самостоятельно решать проблемы, которые обязательно будут возникать по мере обучения и внедрения новых идей.

Хотя решить эту задачу очень просто, и сама проблема не мешает использовать код в учебных целях, я думаю, что вы будете гораздо больше гордиться собой и чувствовать себя увереннее в том, что изучаете, если сможете решить её самостоятельно, исследуя и анализируя каждую ситуацию до тех пор, пока не найдёте наилучшее возможное решение. Поэтому в данной статье мы рассмотрим ещё одну тему, которая в некотором смысле связана с работой с графическими объектами, хотя и несколько отличается от того, что было показано до настоящего момента.

Возможно, вы уже догадываетесь, что мы уделим этому вопросу большое внимание. Возможно, даже больше, чем многие считают действительно необходимым. Прежде чем мы сможем углубиться в возможности MQL5, необходимо понять несколько вещей. Всё это делается ради более масштабной цели: создания более сложных приложений со специфическим назначением, аналогов которым мы не найдём ни в одной другой программе, по крайней мере, если говорить о MetaTrader 5.

Хорошо, давайте перейдем к новой теме. Таким образом мы начнем говорить о главном в данной статье.


События в объектах

До настоящего момента мы показывали и объясняли, как работать с событиями, которые пользователь генерирует при взаимодействии с MetaTrader 5, и как мы можем работать с событиями, которые пользователь генерирует при взаимодействии с MetaTrader 5, связывая эти события с графиком. Например, когда мы нажимаем клавишу или даже когда используем мышь для выполнения каких-либо действий. Как, например, размещение какого-либо объекта на графике — действие, которое, хоть и выполняется в основном с помощью мыши, также порождает другие связанные с ним события.

И эти события, по сути, приводят к тому, что MetaTrader 5 инициирует другие события, которые в данном случае связаны с объектами. И по этой причине они обрабатываются по-другому. Ну, по крайней мере, так ожидается. MetaTrader 5 генерирует эти события именно для того, чтобы помочь программисту и, как следствие, самому приложению обрабатывать любые действия, совершенные с объектом на графике.

Эти события могут быть вызваны щелчком мыши, перемещением, созданием, удалением или даже изменением чего-либо на объекте. Однако не все события будут запущены, так как некоторые из них необходимо включить. Причина очень проста - по умолчанию они будут отключены в MetaTrader 5. Это работает примерно так же, как и с событиями мыши. Это связано с тем, что приложение может быть не заинтересовано в получении уведомлений о таких событиях. MetaTrader 5 просто игнорирует срабатывание данных событий. Таким образом, мы получаем относительно более быструю среду с меньшей вероятностью возникновения проблем с производительностью или даже со стабильностью самой платформы.

Но бывают случаи, когда подобные события становятся необходимыми. В таком случае нам необходимо понять, как их обрабатывать, потому что может возникнуть необходимость внести некоторые исправления или корректировки именно из-за изменений, внесенных пользователем в графический объект.

Ручной анализ изменений графического объекта, помимо того, что потребляет много процессорного времени и делает код абсурдно сложным, — это не то, чем вы, уважаемый читатель, захотите заниматься. Но именно потому, что MetaTrader 5 предоставляет нам информацию об изменениях на графике, мы можем значительно сократить работу по отслеживанию каждого отдельного объекта, размещенного на нем. Это позволяет нам точно знать, когда, где и почему пользователь изменил объект, единственная цель которого — информирование.

В данный момент вы, возможно, не представляете, насколько важны подобные события, которые генерирует MetaTrader 5, чтобы уведомить наше приложение об изменении объекта. Более того, возможно, вы даже не до конца осознаете, насколько сильно злонамеренный пользователь может повредить работе вашего приложения, если начнет изменять определенные свойства некоторых объектов. Именно поэтому нам нужно говорить о событиях в объектах. Это, несомненно, та тема, которая поможет вам избежать проблем или даже понять причины определенных сбоев, которые могут возникнуть в вашем приложении на протяжении всего срока его службы.

Обновляя свое приложение, вы также можете снизить риск возникновения сбоев. Но, в то же время, вы можете упускать из виду вопросы, которые действительно важно проанализировать.

Поскольку зачастую определенные события, генерируемые платформой MetaTrader 5 в результате действий с объектом, могут интерпретироваться по-разному в зависимости от конкретного приложения, мы не будем пытаться создать фиксированную модель или протокол для реализации. Я хочу, чтобы вы, уважаемый читатель, попытались понять концепцию, которая будет использована. Но также я хочу, чтобы вы постарались понять: бывают моменты, когда необходимо отслеживать каждое действие, и моменты, когда мы можем полностью игнорировать определенные виды активности с объектом — будь то объект, размещенный на графике нашим приложением, или объект, который пользователь добавил на этот же график самостоятельно. Для всего есть своё время. Поэтому постарайтесь сначала понять саму концепцию, а не код как таковой, поскольку он может кардинально меняться.

Итак, прежде чем мы начнем, необходимо кое-что прояснить. Как правило, стандартная обработка объекта платформой MetaTrader 5 может оказаться не самой подходящей, если речь идет о более специфической задаче, где мы реализуем что-то для достижения очень конкретной цели. Поэтому в подобных ситуациях нам необходимо установить и другие правила, помимо тех, которые MetaTrader 5 уже использует по умолчанию.

И для того, чтобы правила, которые мы установим в нашем приложении, могли соблюдаться, мы используем встроенный механизм MetaTrader 5, позволяющий узнать, что произошло с конкретным объектом. Таким образом, мы устраняем необходимость постоянно отслеживать, что пользователь может сделать с тем или иным объектом. Если с объектом что-то происходит — будь то добавление пользователем нового объекта на график, его изменение или даже удаление, — MetaTrader 5 отправит нам уведомление, благодаря чему мы сможем принять необходимые меры.

Понимание этого чрезвычайно важно. Возможно, даже важнее, чем понимание самого кода. В любом случае, некоторые события необходимо будет включить, а другие — нет. А умение определять подходящее время для включения или даже отключения данных событий — это то, чему нужно научиться на практике. В любом случае, с чего-то нужно начать. Поэтому давайте разберем это как можно проще и последовательнее. Начнём с кода, который мы видим ниже:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. #include <Tutorial\File 01.mqh>
009. //+------------------------------------------------------------------+
010. input double    user01 = 1.5;                   //Stop-Target Relationship
011. input bool      user02 = true;                  //Extend lines to the right
012. //+------------------------------------------------------------------+
013. st_Cross gl_Cross;
014. //+------------------------------------------------------------------+
015. int OnInit()
016. {
017.     gl_Cross.Init();
018.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
019.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
020.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
021. 
022.     return INIT_SUCCEEDED;
023. };
024. //+------------------------------------------------------------------+
025. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
026. {
027.     return rates_total;
028. };
029. //+------------------------------------------------------------------+
030. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
031. {
032. #define macro_CLEAN_EVENT   {                               \
033.             isPaint = "";                                   \
034.             gl_Cross.Hide();                                \
035.             bMouseL = false;                                \
036.             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);   \
037.                             }
038. 
039.     st_TimePrice    tp;
040.     static string   isPaint = "";
041.     static bool     bMouseL = false;
042. 
043.     switch (id)
044.     {
045.         case CHARTEVENT_KEYDOWN:
046.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) macro_CLEAN_EVENT;
047.             break;
048.         case CHARTEVENT_MOUSE_MOVE:
049.             tp = gl_Cross.Move((ushort)lparam, (ushort)dparam);
050.             if (((uchar)sparam & MOUSE_LEFT) != 0)
051.             {
052.                 if (isPaint != "")
053.                 {
054.                     bMouseL = true;
055.                     if (!ObjectGetInteger(0, isPaint, OBJPROP_TIME)) ObjectMove(0, isPaint, 0, tp.Time, tp.Price);
056.                     ObjectMove(0, isPaint, 1, tp.Time, tp.Price);
057.                 }
058.             }else if (bMouseL) macro_CLEAN_EVENT;
059.             if ((((uchar)sparam & MOUSE_MIDDLE) != 0) && (isPaint == ""))
060.             {
061.                 ObjectCreate(0, isPaint = macro_NameObject, OBJ_FIBO, 0, 0, 0);
062.                 ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
063.                 Modifier_OBJ_FIBO(isPaint);
064.                 gl_Cross.Show();
065.             }
066.             break;
067.         case CHARTEVENT_MOUSE_WHEEL:
068.             break;
069.         case CHARTEVENT_OBJECT_CLICK:
070.             Comment(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d    Y: %03d    Object: %s", (ushort)lparam, (ushort)dparam, sparam));
071.             break;
072.         case CHARTEVENT_OBJECT_DRAG :
073.             Comment(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam));
074.             break;
075.     }
076.     ChartRedraw();
077. 
078. #undef macro_CLEAN_EVENT
079. };
080. //+------------------------------------------------------------------+
081. void OnDeinit(const int reason)
082. {
083.     Comment("");
084.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
085.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
086. 
087.     gl_Cross.Hide();
088. 
089.     if (reason == REASON_REMOVE)
090.         ObjectsDeleteAll(0, def_Prefix);
091. };
092. //+------------------------------------------------------------------+
093. void Modifier_OBJ_FIBO(const string szNameObj)
094. {
095. #define macro_Mod_OBJ_FIBO(txt, pos, cor, width, style) {                       \
096.             ObjectSetDouble(0, szNameObj, OBJPROP_LEVELVALUE, levels, pos);     \
097.             ObjectSetInteger(0, szNameObj, OBJPROP_LEVELCOLOR, levels, cor);    \
098.             ObjectSetInteger(0, szNameObj, OBJPROP_LEVELWIDTH, levels, width);  \
099.             ObjectSetInteger(0, szNameObj, OBJPROP_LEVELSTYLE, levels, style);  \
100.             ObjectSetString(0, szNameObj, OBJPROP_LEVELTEXT, levels, txt);      \
101.             levels++;                                                           \
102.                                                         }
103. 
104.     int levels = 0;
105. 
106.     ObjectSetInteger(0, szNameObj, OBJPROP_SELECTABLE, false);
107.     ObjectSetInteger(0, szNameObj, OBJPROP_COLOR, clrNONE);
108.     ObjectSetInteger(0, szNameObj, OBJPROP_RAY_RIGHT, user02);
109. 
110.     macro_Mod_OBJ_FIBO("Stop", 0, clrRed, 2, STYLE_SOLID);
111.     macro_Mod_OBJ_FIBO("Enter", 1, clrBlue, 2, STYLE_DASH);
112.     macro_Mod_OBJ_FIBO("Partial", 1 + (user01 / 2), clrYellowGreen, 1, STYLE_DASHDOTDOT);
113.     macro_Mod_OBJ_FIBO("Take", 1 + user01, clrGreen, 2, STYLE_SOLID);
114.     ObjectSetInteger(0, szNameObj, OBJPROP_LEVELS, levels);
115. 
116. #undef macro_Mod_OBJ_FIBO
117. }
118. //+------------------------------------------------------------------+

Код 01

Здесь представлен код, который мы рассматривали, объясняли и предоставляли в предыдущей статье. Ну, на самом деле это не совсем так, поскольку здесь мы вносим два новых дополнения в исходный код. Я делаю это намеренно, опираясь на уже знакомый материал, именно для того, чтобы вы заметили: очень сложное приложение не появляется в один миг. Оно строится постепенно, шаг за шагом. Хотя здесь мы и не сосредоточены на создании какого-то конкретного приложения. Наша цель — лишь продемонстрировать и объяснить аспекты, связанные с программированием на MQL5 для управления самой платформой MetaTrader 5.

Итак, после компиляции и добавления на график, код 01 будет демонстрировать довольно интересное поведение, за которым стоит понаблюдать. Теперь это предоставит нам полезную информацию. Стоит помнить, что эти данные изначально генерируются платформой MetaTrader 5 на основе действий, совершенных на графике. В следующей анимации можно увидеть, чего ожидать при использовании приложения на любом графике:


Анимация 01

Обратите внимание на верхний левый угол анимации 01. Вы увидите сообщения, цель которых — информировать нас о том, когда произошло какое-либо событие с объектами, находящимися на графике. Прошу заметить, что в случае события CHARTEVENT_OBJECT_CLICK нам сообщается не только имя объекта и положение мыши, но и тип произошедшего события. Подобные события открывают самые разные возможности применения. На самом деле, просто наблюдая за этими двумя событиями, открывается множество возможностей.

Но поскольку мы имеем дело с чем-то очень простым и очевидным, не вижу необходимости подробно объяснять, что происходит в коде 01. Вам достаточно спокойно изучить его и применить знания, полученные из предыдущих статей, чтобы понять, что именно происходит в приложении. Однако следует помнить, что приложение может наблюдать даже за объектами, не созданными им.

Именно это я хочу, чтобы вы поняли, уважаемый читатель. Мы анализируем стандартное поведение платформы MetaTrader 5, чтобы понять, как она взаимодействует с объектами и как именно будет отправлять нам уведомления в случае каких-либо изменений.

Что ж, хотя мы могли бы продолжить добавлять новые элементы в код 01, мы не станем этого делать. Поскольку это в итоге скрыло бы нашу главную цель, которая заключается как раз в том, чтобы обучить вас и передать немного моего опыта и знаний о программировании, чтобы вы смогли заставить платформу работать на вас, а не против вас. Итак, давайте создадим новый тип приложения. Оно будет посвящено описанию реализуемого функционала, при условии, конечно, что вы понимаете, как использовать MQL5 для управления MetaTrader 5.

И для этого необходимо, чтобы вы усвоили суть материала, изложенного в текущей теме. Поскольку без понимания этой части, которая, на мой взгляд, является основой для всего остального, будет довольно сложно разобраться в том, что мы будем делать далее.


Избегание дубликатов

Один из моментов, который мне действительно докучает, — это пресловутый CTRL+C и CTRL+V. Но в MetaTrader 5 это не применимо, по крайней мере, не так, как большинство людей ожидают. Так происходит, потому что при выборе объекта и открытии меню действий с ним мы заметим, что по умолчанию в MetaTrader 5 ОТСУТСТВУЕТ комбинация клавиш CTRL+C и CTRL+V для применения этих действий к объектам. То, что для многих может быть совершенно нормальным и естественным, у других вызывает некоторое недоумение и замешательство. Вы можете увидеть это на следующем изображении.


Изображение 01

Да, это может показаться немного странным. Однако в MetaTrader 5 сочетание клавиш CTRL+C и CTRL+V доступно не таким образом. И, честно говоря, я думаю, что очень немногие пользователи знают, как использовать CTRL+C CTRL+V в MetaTrader 5 для дублирования графического объекта.

Чтобы понять, как это делается, потребуется объяснить другие вещи. Но в данном случае это сделало бы статью очень скучной и, кроме того, на мой взгляд, не вызвало бы особого интереса. Дело в том, что большинство пользователей вполне устраивает платформа в ее текущем виде. Так зачем это менять? Не так ли?

Но для того, что я хочу объяснить позже, действительно стоит рассказать вам как можно выполнить комбинацию клавиш CTRL+C и CTRL+V для любого объекта здесь, в MetaTrader 5, потому что я думаю, что вы этого не знаете. Кроме того, это становится своего рода дополнительным материалом для тех, кто случайно наткнется на эти мои статьи и начнет их изучать. Итак, на следующей анимации мы можем видеть, как выполнить CTRL+C CTRL+V для любого объекта.


Анимация 02

Прошу заметить, что это очень просто, легко и даже весело. Однако мало кто знает, что для создания копии любого объекта на графике достаточно удерживать клавишу CTRL в момент перетаскивания объекта. Таким образом, выполняя эту операцию, мы создаём точную копию объекта. Хотя на самом деле это не совсем точная копия, как будет объяснено чуть позже. И слава Богу, что это не так, ведь иначе было бы трудно сделать кое-что ещё, о чем вы, возможно, пока даже не догадываетесь.

Итак, когда мы это делаем, как показано на анимации 02, мы создаем дубликат объекта, который уже существовал на графике. Но порой это совсем не то, чего мы хотим. Или, точнее говоря, существуют определенные типы объектов, дубликаты которых на графике НАМ НЕ НУЖНЫ. Это связано с тем, что данные объекты могут содержать информацию, дублирование которой может ввести в заблуждение оператора или даже пользователя.

Тем более когда эти объекты могут получать информацию от нашего приложения. В таком случае мы можем в итоге увидеть противоречивую информацию в двух внешне одинаковых объектах. А чтобы это стало понятнее и вы могли правильно понять, о чем я говорю, давайте проведём небольшой тест. Сделаем его с помощью кода, который мы увидим ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
07. //+------------------------------------------------------------------+
08. string gl_NameObj;
09. //+------------------------------------------------------------------+
10. int OnInit()
11. {
12.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
13. 
14.     ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0);
15.     ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true);
16.     ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50);
17.     ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50);
18.     ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150);
19.     ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue);
20.     ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20);
21.     ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console");
22. 
23.     return INIT_SUCCEEDED;
24. };
25. //+------------------------------------------------------------------+
26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
27. {
28.     return rates_total;
29. };
30. //+------------------------------------------------------------------+
31. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
32. {
33.     switch (id)
34.     {
35.         case CHARTEVENT_MOUSE_MOVE:
36.             ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
37.             break;
38.     }
39.     ChartRedraw();
40. };
41. //+------------------------------------------------------------------+
42. void OnDeinit(const int reason)
43. {
44.     ObjectsDeleteAll(0, def_Prefix);
45.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
46.     ChartRedraw();
47. };
48. //+------------------------------------------------------------------+

Код 02

Код 02 поможет проиллюстрировать, к чему я веду, говоря о дублировании объектов. Поскольку это очень просто и вы легко сможете во всем разобраться, мой дорогой и уважаемый читатель, всякий раз, когда вы изучаете материал моих статей, я не буду объяснять, что он делает. Вместо этого мы лишь покажем тип результата, который мы можем увидеть, когда изложенное в предыдущих абзацах применяется на практике. Результат можно увидеть в анимации 03 ниже:


Анимация 03

Здесь возникает вопрос, связанный со скоростью обработки событий. Поскольку мы имеем дело с событиями мыши, скорость обработки довольно высока, и в итоге мы начинаем замечать, что происходит нечто странное. Но подумайте о событиях, которые могут происходить каждые пять минут или даже каждый час. В таком случае заметить это было бы сложно. Так что теперь вы, возможно, начинаете понимать, к чему я клоню. Поскольку вы, вероятно, не знали, как дублировать объект на графике, подобная ситуация, которую мы видим в анимации 03, вряд ли когда-либо возникнет.

Но теперь, когда мы знаем, как легко создать дубликат объекта в MetaTrader 5, подобные ситуации могут возникать довольно часто. И это в какой-то момент может привести к серьезным проблемам, тем более, если дублированный объект находится перед интересующим объектом. Это случается гораздо чаще, чем можно себе представить, и, следовательно, порождает ещё больше путаницы.

Однако мы можем предотвратить дублирование объекта, или, по крайней мере, уменьшить вероятность возникновения конфликтов из-за этого. Это связано с тем, что некоторые типы объектов могут скрывать другие объекты в зависимости от их настройки. Но здесь, к счастью, созданный объект совершенно безвреден и предназначен исключительно для образовательных целей.

Теперь вам нужно понять одну вещь, уважаемый читатель. При дублировании объекта, как показано здесь, MetaTrader 5 создаст имя для этого нового объекта. Это название, скажем так, следует правилу стандартизации и разделено на три части. Первая из них относится к временному интервалу, который мы используем на графике. Вторая часть будет посвящена указанию типа используемого объекта. А третья часть — это случайно сгенерированное число. Мы игнорируем эту третью часть, так как ее цель — избежать коллизий между объектами одного типа.

Хорошо, но что это нам говорит? И как мы можем использовать эти знания в своих интересах? Итак, теперь начнётся самая интересная часть, уважаемый читатель. На основе определенных характеристик, которые можно проверить при использовании кода 01 для просмотра процесса создания дубликата, мы можем определить, был ли создан дубликат объекта или нет. Хотя это и не точная наука, поскольку существуют сложности в отношении того, как мы можем это делать и насколько далеко мы можем зайти, не отказываясь от чистого MQL5.

Одно из этих осложнений вскоре прояснится. Но прежде мы можем сделать кое-что более интересное, а именно реализовать функционал, целью которого будет анализ самих событий, генерируемых MetaTrader 5 в ответ на взаимодействие пользователя с объектом. Таким образом, код 02 превратится в код, который мы увидим ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
07. //+------------------------------------------------------------------+
08. enum eBtnMouse  {
09.         MOUSE_LEFT      = 0x01, 
10.         MOUSE_RIGHT     = 0x02,
11.         MOUSE_KEY_SHIFT = 0x04,
12.         MOUSE_KEY_CTRL  = 0x08,
13.         MOUSE_MIDDLE    = 0x10,
14.         MOUSE_EXTRA_1   = 0x20,
15.         MOUSE_EXTRA_2   = 0x40
16.                 };
17. //+------------------------------------------------------------------+
18. string gl_NameObj;
19. //+------------------------------------------------------------------+
20. int OnInit()
21. {
22.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
23. 
24.     ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0);
25.     ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true);
26.     ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50);
27.     ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50);
28.     ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150);
29.     ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue);
30.     ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20);
31.     ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console");
32. 
33.     ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
34. 
35.     return INIT_SUCCEEDED;
36. };
37. //+------------------------------------------------------------------+
38. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
39. {
40.     return rates_total;
41. };
42. //+------------------------------------------------------------------+
43. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
44. {
45.     static bool check = true;
46. 
47.     switch (id)
48.     {
49.         case CHARTEVENT_MOUSE_MOVE:
50.             ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
51.             if (((uchar)sparam & (MOUSE_LEFT | MOUSE_KEY_CTRL)) == (MOUSE_LEFT | MOUSE_KEY_CTRL)) 
52.             {
53.                 if (check) Print(StringFormat("CHARTEVENT_MOUSE_MOVE\n%s", StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)));
54.                 check = false;
55.             }else
56.                 check = true;
57.             break;
58.         case CHARTEVENT_OBJECT_CREATE:
59.             Print(StringFormat("CHARTEVENT_OBJECT_CREATE\nObject: %s", sparam));
60.             break;
61.         case CHARTEVENT_OBJECT_CLICK:
62.             Print(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d    Y: %03d    Object: %s", (ushort)lparam, (ushort)dparam, sparam));
63.             break;
64.         case CHARTEVENT_OBJECT_DRAG :
65.             Print(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam));
66.             break;
67.     }
68.     ChartRedraw();
69. };
70. //+------------------------------------------------------------------+
71. void OnDeinit(const int reason)
72. {
73.     Comment("");
74.     ObjectsDeleteAll(0, def_Prefix);
75.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
76.     ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false);
77.     ChartRedraw();
78. };
79. //+------------------------------------------------------------------+

Код 03

Глядя на этот код, вы, возможно, подумаете: "Да ладно, этот код очень сложный. Действительно ли нам нужно столько сложностей, чтобы избежать дублирования объектов на графике?" Хорошо, в коде 03 мы пока не пытаемся предотвратить дублирование объектов. Нет, этот код не так сложен, как кажется. Возможно, вам так кажется, потому что вы попали на эту статью случайно, не имея достаточной базы из того, что уже было объяснено в предыдущих публикациях. Да, код 03, похоже, представляет собой серьезную проблему.

Тем не менее, из уважения к тем, кто следит за моими статьями, и изучает их, здесь я сосредоточусь только на новом. Всё остальное, я думаю, поймут даже новички, если будут изучать и практиковать объясненное.

Хорошо, первая новая и интересующая нас здесь часть — это строка 33. Обратите внимание на нее, уважаемый читатель. В MetaTrader 5 событие создания объекта по умолчанию отключено. Однако, если мы не хотим, чтобы создаваемый нами в приложении объект был обнаружен и запускал событие создания, нам следует отключить это событие, которое мы включаем в строке 33. Поскольку мы включаем это событие только ПОСЛЕ создания объекта OBJ_LABEL, данный объект не вызовет событие создания, которое, в свою очередь, будет перехвачено процедурой OnChartEvent.

Теперь, после активации этого события, любой объект, размещенный на графике, будет запускать событие создания. Эти данные будут перехвачены строкой 58, которая затем выведет сообщение на терминал. В итоге, когда выполнится строка 76, мы выключаем это событие создания, предотвращая тем самым повторный вызов данного события со стороны MetaTrader 5 на графике.

Хорошо, остальная часть кода довольно проста для понимания. Затем мы можем перейти к той части, где будем применять код 03 на практике. Начнем с того, что можно увидеть на следующей анимации:


Анимация 04

В этой анимации мы можем увидеть, какие объекты присутствуют на графике. Мы также можем заметить, что код не вывел никаких сообщений, а это свидетельствует о том, что события соответствуют ожиданиям, как и объяснялось ранее.

Как только код 03 будет запущен, мы сможем открыть окно со списком объектов, чтобы проверить, какие элементы присутствуют на графике. Открыв список, мы увидим изображение, показанное ниже.


Изображение 02

А теперь обратите внимание на одну вещь, уважаемый читатель. Существует два основных способа предотвратить дублирование объектов. Первый — поддерживать и анализировать список объектов, который вы видите на изображении 02. Это можно сделать с помощью кода. Однако, на мой взгляд, это приводит к избыточному потреблению ресурсов, потому, что каждый раз, когда строка 58 фиксирует событие создания, нам приходится анализировать, что произошло с этим списком объектов. А это излишне усложняет код и увеличивает затраты ресурсов процессора.

Другой способ заключается в анализе событий мыши, перехваченных в строке 49, и прежде всего тех, которые приводят к истинному значению фильтра в строке 51. Однако этот механизм не является идеальным на 100%, потому что бывают ситуации, когда этот же фильтр может вызвать ложное срабатывание.

Чтобы понять, что может вызывать ложноположительные результаты, ознакомьтесь с другой моей статьей Разработка торгового советника с нуля (Часть 23): Новая система ордеров (VI) . Там мы используем эту комбинацию, которая активирует строку 51 и применяется для дублирования объекта, именно для того, чтобы иметь возможность отправлять отложенные ордера на торговый сервер. Однако при некотором терпении, аккуратности и внимательности мы можем обойти эту проблему ложного срабатывания. Однако мы рассмотрим это в другой раз, поскольку это предполагает проведение некоторых манипуляций, которые неуместно демонстрировать прямо сейчас. Здесь акцент делается на совершенно другом, а именно на понимании того, как предотвратить создание дубликата объекта.

Давайте теперь подробнее рассмотрим, как код 03 покажет нам, что происходит на графике. Это можно увидеть в следующей анимации:


Анимация 05

В данном случае были сгенерированы только события CHARTEVENT_OBJECT_CLICK и CHARTEVENT_OBJECT_DRAG, что подтверждает: всё произошло в соответствии с ожиданиями. Теперь мы сделаем кое-что немного другое с кнопкой, присутствующей на графике. Это можно увидеть в следующей анимации:


Анимация 06

Обратите внимание на одну деталь здесь. В точный момент создания копии, то есть при генерации дубликата, MetaTrader 5 вызывает два события. Это лучше видно на следующем изображении.


Изображение 03

На этом изображении 03 мы можем четко видеть момент копирования объекта или генерации дубликата. Теперь внимание! Прошу заметить, что исходный объект оставался неподвижным. Перемещена была именно копия, как можно видеть при сравнении изображения 02 с изображением 04, которое представлено ниже.


Изображение 04

Почему важно это знать? Причина заключается в том, что в зависимости от нашей задачи, может потребоваться удалить либо копию, либо исходный объект. Лично мне проще удалить копию, поскольку ее имя предоставляется нам при перехвате события создания. Однако ничто не мешает нам удалить оригинал, а затем переименовать копию, чтобы всё осталось как прежде. Возможно, целью было переместить объект и в итоге создать его копию. Тем не менее, я думаю, это требует слишком много работы. Поэтому мы продолжим идти по самому простому пути.

Поскольку планируемые изменения в коде 03 приведут к удалению ранее реализованного и продемонстрированного функционала, мы пойдем другим путем. Итак, создадим новый файл. Таким образом, код 03 будет в приложении к статье, чтобы вы могли спокойно изучить его, не переписывая вручную. Таким образом, новый код с необходимыми изменениями представлен ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
07. //+------------------------------------------------------------------+
08. enum eBtnMouse  {
09.         MOUSE_LEFT      = 0x01, 
10.         MOUSE_RIGHT     = 0x02,
11.         MOUSE_KEY_SHIFT = 0x04,
12.         MOUSE_KEY_CTRL  = 0x08,
13.         MOUSE_MIDDLE    = 0x10,
14.         MOUSE_EXTRA_1   = 0x20,
15.         MOUSE_EXTRA_2   = 0x40
16.                 };
17. //+------------------------------------------------------------------+
18. string gl_NameObj;
19. //+------------------------------------------------------------------+
20. int OnInit()
21. {
22.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
23. 
24.     ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0);
25.     ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true);
26.     ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50);
27.     ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50);
28.     ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150);
29.     ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue);
30.     ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20);
31.     ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console");
32. 
33.     ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
34. 
35.     return INIT_SUCCEEDED;
36. };
37. //+------------------------------------------------------------------+
38. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
39. {
40.     return rates_total;
41. };
42. //+------------------------------------------------------------------+
43. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
44. {
45.     static bool check = true;
46.     static string szObjName = "";
47. 
48.     switch (id)
49.     {
50.         case CHARTEVENT_MOUSE_MOVE:
51.             ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
52.             if (((uchar)sparam & (MOUSE_LEFT | MOUSE_KEY_CTRL)) == (MOUSE_LEFT | MOUSE_KEY_CTRL)) 
53.             {
54.                 if (check)
55.                     if (ObjectDelete(0, szObjName))
56.                         Comment("Removing copy object: " + szObjName);
57.                 check = false;
58.             }else
59.                 check = true;
60.             break;
61.         case CHARTEVENT_OBJECT_CREATE:
62.             szObjName = sparam;
63.             break;
64.         case CHARTEVENT_OBJECT_CLICK:
65.             Comment(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d    Y: %03d    Object: %s", (ushort)lparam, (ushort)dparam, sparam));
66.             break;
67.         case CHARTEVENT_OBJECT_DRAG :
68.             Comment(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam));
69.             break;
70.     }
71.     ChartRedraw();
72. };
73. //+------------------------------------------------------------------+
74. void OnDeinit(const int reason)
75. {
76.     Comment("");
77.     ObjectsDeleteAll(0, def_Prefix);
78.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
79.     ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false);
80.     ChartRedraw();
81. };
82. //+------------------------------------------------------------------+

Код 04

При запуске этого кода 04 мы получим результат, очень похожий на следующий:


Анимация 07

Обратите внимание, что я без проблем добавил сюда новый объект. Сразу после этого мы попытались создать копию данного объекта, используя механизм, описанный в этой статье. И обратите внимание на то, что будет выводиться в левом верхнем углу графика. То есть мы только что нашли способ предотвратить дублирование объектов, хотя для достижения того же результата можно было бы предпринять и другие действия.


Заключительные идеи

Хотя эта статья была сосредоточена исключительно на обработке событий объектов, нам всё ещё нужно рассмотреть три других события, которые могут происходить с объектом. Но об этом мы поговорим в следующей статье. Я не хочу слишком усложнять задачу для тех, кто только начинает изучать управление MetaTrader 5 с помощью MQL5.

Итак, уважаемый читатель, постарайтесь изучить и применить на практике то, что было рассмотрено в этой статье. Эти знания, безусловно, помогут вам понять многие аспекты работы MetaTrader 5, и надо будет их очень хорошо усвоить, прежде чем вы сможете сказать: "Да, я умею программировать достаточно, чтобы управлять возможностями MetaTrader 5".

Так что практикуйтесь с кодами из приложения, и до встречи в следующей статье.

Файл MQ5 Описание
Code 01 Демонстрация событий в объектах
Code 02 Демонстрация событий в объектах
Code 03 Демонстрация событий в объектах 

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/16035

Прикрепленные файлы |
Anexo.zip (3.8 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Разрабатываем пользовательский индикатор рыночных настроений Разрабатываем пользовательский индикатор рыночных настроений
В этой статье мы разрабатываем пользовательский индикатор рыночных настроений, который классифицирует рыночные условия как бычьи, медвежьи, в режиме risk-on, в режиме risk-off или нейтральные. Благодаря анализу нескольких таймфреймов индикатор дает трейдерам более ясное представление об общей направленности рынка и краткосрочных подтверждениях.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Советник для размещения сделок на основе риска с графическим интерфейсом на графике (Часть 1): Проектирование пользовательского интерфейса Советник для размещения сделок на основе риска с графическим интерфейсом на графике (Часть 1): Проектирование пользовательского интерфейса
Узнайте, как создать аккуратную и профессиональную панель управления на графике в MQL5 для советника, размещающего сделки на основе риска. В этом пошаговом руководстве объясняется, как спроектировать функциональный графический интерфейс, позволяющий трейдерам вводить параметры сделки, рассчитывать размер лота и готовиться к автоматическому размещению ордеров.