От начального до среднего уровня: События в объектах (II)
Введение
В предыдущей статье От начального до среднего уровня: События в объектах (I) была объяснена и продемонстрирована первая базовая часть, в которой основное внимание уделялось событиям, происходящим в объектах. Однако нам ещё необходимо обсудить три других события, которые могут быть инициированы при взаимодействии объекта с пользователем. Из этих трёх событий одно должно быть явно активировано, чтобы MetaTrader 5 действительно начал его генерировать. В противном случае, даже если наш код располагает ресурсами для обработки этого события, он никогда не получит уведомление о его возникновении.
Понимать каждый из этих видов событий очень важно перед их совместным использованием, поэтому мы поступим следующим образом: создадим небольшие темы исключительно для объяснения того, как и когда MetaTrader 5 генерирует каждый из этих трех последних типов событий. Как только вы их поймете, мы сможем посмотреть, как использовать их вместе для реализации какого-нибудь интересного функционала. Итак, начнём.
Событие CHARTEVENT_OBJECT_DELETE
Это событие, как и CHARTEVENT_OBJECT_CREATE и другие события, связанные с мышью, должно включаться и выключаться нашим приложением. Это сделано для того, чтобы MetaTrader 5 знал, когда запущенному на графике приложению необходимо получать уведомление об удалении какого-либо присутствующего на графике объекта.
Это событие определенно имеет превентивную цель. Это важно потому, что, если мы хотим поддерживать в приложении определённую конфигурацию объектов, такое событие позволяет не допустить удаления пользователем чувствительного или критически важного объекта с графика. Будь то по ошибке при удалении объектов с графика или по любой другой причине.
В принципе, это событие очень простое. Однако необходимо принять некоторые меры предосторожности при программировании или реализации кода. Причина этих мер предосторожности заключается в том, что в некоторых случаях мы можем столкнуться с проблемами, связанными с цветовой схемой. Это в итоге сильно затрудняет правильное отображение объекта, когда приложению необходимо воссоздать его автоматически.
Но то, о чем я говорю, ни в коем случае не будет здесь предметом нашего беспокойства. Данный материал не ориентирован на объяснение того, как реализовать универсальное решение, способное охватить все возможные случаи. Кроме того, каждый случай индивидуален.
Не существует полностью интегрированного и универсального способа воссоздания объекта, который был неправомерно удален с графика. Вы, как программист, можете придумать множество способов это гарантировать. Лично я предлагаю использовать небольшую структуру, в которой будут храниться свойства объектов, создаваемых нами, или, скорее, нашим приложением. Таким образом, когда приложению потребуется воссоздать объект, оно сделает это с использованием соответствующей цветовой схемы или позиционирования. Помимо, конечно, других вопросов, таких как шрифт, размер шрифта, размер объекта; думаю, это понятно.
После этого краткого введения, думаю, вы уже готовы увидеть, каким будет первый код в этой статье. Код приведен ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. 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. //+------------------------------------------------------------------+
Код 01
Цель кода 01 очень проста: показать, что происходит, когда мы удаляем объект с графика. Поскольку нам нужно включать и выключать уведомление, чтобы MetaTrader 5 сообщал нам, был ли удален объект или нет, этот код значительно упрощает понимание того, как всё устроено.
Пожалуй, самая сложная часть это не включение уведомления, поскольку это сделать очень просто, и для этого достаточно использовать строку 13. Как правило, мы включаем уведомление в самом начале выполнения приложения, чтобы не пропустить события удаления, которые могут произойти. Однако трудность заключается как раз в том, чтобы знать, когда именно отключать это уведомление. MetaTrader 5 работает определенным образом, и, в зависимости от того, в какой момент отключить уведомление, можно получить довольно странные, но в большинстве случаев совершенно безобидные результаты.
Чтобы понять это, попробуйте изменить расположение содержимого, которое находится в строке 42. Именно оно отвечает за отключение уведомлений о событиях при удалении объекта с графика. Поскольку исходный код будет в приложении, вы можете попробовать поменять местами содержимое строки 42 и строки 43. Это приведет к тому, что результат удаления индикатора с графика станет довольно интересным. Таким образом, мы увидим, что мы не всегда можем выполнять эти действия в каком-либо порядке. И это поможет вам понять различные вопросы, которые в противном случае было бы сложно объяснить.
В любом случае, когда код 01 будет запущен, мы сможем выполнять в основном два типа действий. Первый пример можно увидеть в следующей анимации:

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

Анимация 02
Как показано в анимации 01, здесь, в анимации 02, ясно видно, что в тот самый момент, когда мы удаляем объект, он создается заново. Тем не менее, несмотря на это, окно отображает это не сразу. Нужно снова открыть окно, чтобы он снова отобразился в списке присутствующих на графике объектов.
Итак, это событие служит для уведомления о том, что объект был удалён с графика. Теперь нам нужно рассмотреть ещё два варианта. Однако здесь возникают довольно интересные вопросы. А чтобы лучше объяснить, давайте начнём новую тему.
Событие CHARTEVENT_OBJECT_CHANGE
Событие, которое мы сейчас рассмотрим, довольно интересное, если мы понимаем, как оно работает. Это связано с тем, что зависимости от ситуации можно реализовать с его помощью очень интересные вещи, поскольку оно генерируется каждый раз, когда у объекта изменяется какое-либо из его свойств.
В предыдущей теме мы упоминали, что не существует способа создать на 100% универсальный механизм для восстановления удаленного объекта. И это правда. Однако, в зависимости от способа реализации кода, мы можем найти подходящий способ воссоздать практически любой тип объекта в универсальном виде. Судя по тому, что было показано до настоящего момента, это довольно сложная задача. Поэтому сохраняйте спокойствие, потому что, хотя мы можем создавать хорошие приложения с помощью относительно простых механизмов, в будущем мы можем найти лучшие способы. Знания накапливаются, как и опыт. Поэтому всегда полезно учиться и практиковаться. Без спешки, но стремясь к постоянному обучению.
Чтобы показать, насколько это может быть интересно и почему важно понимать, что именно реализуется, мы возьмем код 01, который рассматривали в предыдущей теме, и изменим его, чтобы увидеть в действии событие из этой темы. Это делается в приведенном ниже коде:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. struct st_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("Ocorreu um evento: CHARTEVENT_OBJECT_CHANGE no objeto ["+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. //+------------------------------------------------------------------+
Код 02
При выполнении кода 02 мы увидим две вещи. Первая из них показана в следующей анимации.

Анимация 03
Обратите внимание, что в тот самый момент, когда мы изменяем какое-либо свойство объекта, генерируется событие, которое будет перехвачено в строке 80. Поскольку мы не фильтруем источник события, любое изменение в любом объекте приведет к тому, что строка 81 отобразится в левом верхнем углу графика, как можно видеть на анимации 03. Но самое интересное начинается, когда происходит второе событие, как это видно в анимации 04:

Анимация 04
"Странно. Почему объект не сохранил свое свойство? Мы вызываем процедуру, указанную в строке 35. В случае с кодом 01 я понимаю причину, но в данном случае я предположил, что если объект будет удален из графика, приложение воссоздаст его с теми же атрибутами. Вызов функции SetDefault для сброса значений, измененных в анимации 03, не производился. На мой взгляд, это довольно странно".
Уважаемый читатель, многие начинающие программисты часто попадают в одну и ту же ловушку: они пишут код, но не понимают некоторых внутренних аспектов самого кода. Обычно это происходит именно потому, что они копируют код из какого-то другого места. Но это также может произойти, когда мы тестируем новую реализацию, в которой всё кажется в полном порядке, однако при проверке в итоге замечаем, что некоторые детали всё ещё не реализованы. В любом случае, это не ошибка, даже наоборот. Это хороший способ попрактиковаться и протестировать новые решения, которые могут оказаться интересными в будущем.
Отлично, но тогда в чём проблема? Дело в том, что при использовании MetaTrader 5 для обновления какого-либо свойства объекта, вы не обновляете свойства, с которыми он был создан. Мы обновляем только локальные свойства соответствующего объекта. Таким образом, когда он удаляется, и строка 80 из кода 02 воссоздает его, это будет сделано точно с использованием параметров или свойств, определенных и все ещё сохраненных в глобальной переменной.
Таким образом, объект воссоздается в точности так, как он был настроен в коде. Для внесения изменений нам необходимо обновить объект внутри приложения, добавив к нему соответствующие изменения. И для этого нам нужно использовать перехват события, выполненный как раз в строке 82. Поэтому мы не фильтруем событие в коде 02. Я хочу, чтобы вы поняли, как приложение получает уведомление об изменении свойства одного из объектов, присутствующих на графике.
Поскольку MetaTrader сообщает нам только имя объекта, а не то, какие именно свойства были изменены, нам необходимо зафиксировать их все. По крайней мере, те, которые нас действительно интересуют. Хотя многие объекты обладают схожими свойствами, некоторые свойства присущи исключительно одному конкретному объекту и отсутствуют у других. Вот почему мы и сказали, что создание на 100% универсального решения — очень сложная задача.
Хорошо, учитывая это, мы можем изменить код 02, чтобы сохранить некоторые свойства в неизменном виде. По крайней мере, во время выполнения кода. Если приложение будет удалено из графика, оно потеряет свои определяемые пользователем свойства и вернется к тому, что мы реализовали в коде. Существует несколько способов решения данной проблемы. Но об этом мы поговорим в другой раз.
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. //+------------------------------------------------------------------+
Код 03
Код 03 показывает, что нужно сделать. Результат выполнения можно увидеть в следующей анимации:

Анимация 05
Прошу заметить, что теперь объект воссоздан в соответствии со свойствами, которые пользователь определил несколько минут назад. Даже если объект будет удален с графика, из-за его важности он будет воссоздан. Однако, в отличие от того, что происходило ранее, теперь он будет воссоздан не со свойствами, определенными в приложении, а со свойствами, заданными пользователем и сохраненными благодаря вызову, выполненному в строке 92.
Но обратите внимание, что на этот раз мы используем фильтрацию. Это важно в силу самой природы того, что мы делаем. Рассмотрим следующий сценарий: У вас есть два объекта на графике, и вы решаете изменить свойства одного из них. До этого момента всё хорошо. Однако, когда удаляем объект, который использует наше приложение, он будет воссоздан незамедлительно. Тем не менее, если бы фильтрация, которая находится в строке 92, не применялась, изменение объекта, отличного от того, который контролируется приложением, привело бы к тому, что свойства этого другого объекта применились бы к объекту, воссоздаваемому приложением, что вызвало бы огромную путаницу во всем, что делалось бы на графике с объектами. Вот почему фильтрация строки 92 так важна для нас.
Попробуйте протестировать этот же код 03 без использования фильтрации в строке 92 и понаблюдайте за результатами. Иногда такой подход может быть интересным. Поэтому понимание и наблюдение за тем, что происходит, может быть полезно вам в какой-то момент, уважаемый читатель.
Обучение происходит не только тогда, когда мы правильно пишем код.
Это также происходит, когда код работает не так, как ожидается.
В данном случае понимание того, как было найдено решение, так же важно, как и само решение.
Отлично, теперь у нас достаточно элементов, чтобы рассмотреть следующий и последний тип события, которое может произойти с объектом. В данном случае, поскольку речь идет об уникальном объекте и о событии, связанном конкретно с ним, мы проведем небольшой эксперимент, который, на мой взгляд, будет очень увлекательным и весьма поучительным. Мы сделаем то, чего MetaTrader 5 по умолчанию не делает. Но мы можем создать приложение, способное это сделать. Это будет очень интересно, учитывая характер реализации.
Итак, перейдём к новой теме, чтобы всё было должным образом разделено.
Событие CHARTEVENT_OBJECT_ENDEDIT
Один момент, который, на мой взгляд, довольно любопытен, заключается в том, что многие пользователи понятия не имеют, насколько сильно мы можем манипулировать MetaTrader 5 для выполнения определенных типов задач. Часто речь идёт о вещах, которые, как люди упорно утверждают, невозможно или нереально сделать.
Что ж, по сути, есть один вопрос, который, с одной стороны, довольно необычен. С другой стороны, делать это так, как делается, не имеет особого смысла. Но главное – терпение. Дело в том, что вопреки распространенному мнению, MQL5 не является языком, ориентированным на создание приложений для MetaTrader 5. Хотя в общих чертах это служит именно для этого. По сути, MQL5 — это язык, ориентированный на то, чтобы дать нам определенный контроль над тем, как должен работать MetaTrader 5 или как мы хотим, чтобы он работал. Конечно, при соблюдении определённых правил. Я говорю это, потому что без понимания этой простой и понятной детали будет сложно понять то, что мы сделаем в этой теме.
Для подавляющего большинства пользователей, особенно для новичков, то, что будет сделано здесь, покажется чем-то из другого мира. Возможно, какой-то код, созданный искусственным интеллектом или пришельцами. Впрочем, нет ничего подобного. То, что мы собираемся сделать, вполне возможно при наличии совсем небольших знаний, но с хорошей порцией воображения и креативности. Помимо, конечно, хорошей порции наблюдательности.
Не знаю, замечали ли вы уже одну вещь: объект OBJ_LABEL очень похож на OBJ_EDIT. В общих чертах наибольшее, и, пожалуй, единственное различие между обоими объектами заключается в том, что OBJ_EDIT позволяет нам редактировать текст прямо на графике, в то время как объект OBJ_LABEL этого не позволяет. Но так ли это на самом деле?
Что ж, уважаемый читатель, правда заключается в том, что, хотя они очень похожи, объект OBJ_EDIT не обладает теми же характеристиками, что и объект OBJ_LABEL, и наоборот. Но это если смотреть на вещи с точки зрения пользователя. Однако с точки зрения программирования всё обстоит не совсем так. В данном случае оба объекта в основном одинаковы и даже могут работать совместно, если только подумать, как это сделать.
То, что мы сделаем здесь, заключается в следующем: мы создадим способ сделать так, чтобы объект OBJ_LABEL можно было редактировать прямо на графике без необходимости открывать окно его свойств для ввода текста. Кажется невозможным? Давайте тогда посмотрим, как это можно сделать. Помните, что цель здесь образовательная, поэтому не стоит слишком зацикливаться на том, как будет реализован код. Постарайтесь понять, почему то, что вы увидите далее, работает. Это самое важное.
Для начала нам нужен исходный код. Поскольку мы не хотим создавать слишком многое с нуля, чтобы не терять много времени на объяснение наших действий, мы создадим вариацию кода, который рассматривали в предыдущей теме. Его можно увидеть ниже:
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. //+------------------------------------------------------------------+
Код 04
Мы добавили сюда несколько вещей. Однако следует отметить следующее: принцип работы кода 04 аналогичен принципу работы кода 03. Но обратите внимание, что в строке 40, когда мы вызываем процедуру для создания объекта, нам необходимо указать, какой тип объекта следует создать. В данном случае мы будем работать с OBJ_EDIT и OBJ_LABEL. При внесении этих изменений мы начинаем готовить почву для другой возможности, суть которой вы скоро поймете — а именно, для манипулирования объектом OBJ_LABEL с целью редактирования текста прямо на графике.
Теперь обратите внимание на следующий пункт, который встречается в этом коде 04. Прошу заметить, что в строке 49, где мы фактически создаём объект, мы определяем свойство OBJPROP_SELECTABLE. Это свойство очень важно, и мы должны правильно его определить. В объекте OBJ_LABEL нам необходимо установить это свойство в значение true, если хотим иметь возможность выбрать объект позже, как мы это делали до этого момента. Однако, если определить это же свойство как true в объекте типа OBJ_EDIT, мы не сможем редактировать текст непосредственно на графике.
Как было сказано ранее, каждый объект имеет свои особенности, и в зависимости от типа действия, которое мы хотим выполнить, нам необходимо, чтобы его свойства были определены конкретным образом. Итак, будем кратки, когда у нас есть объект OBJ_EDIT со свойством OBJPROP_SELECTABLE в состоянии true, мы не сможем редактировать текст Но мы сможем перемещать объект по экрану. Если этому же свойству присвоено значение false, мы можем редактировать текст, но не можем перемещать объект по экрану.
Хорошо, теперь прошу заметить, что в строке 90 мы указываем тип объекта, который хотим создать, а именно, объект типа OBJ_LABEL. Однако в строке 108 мы больше не используем вызов Create, а вызываем функцию, которая воссоздаст объект. Так происходит, потому что мы уже знаем, какой тип нам нужно будет создать. Но приложение этого не делает, потому что, если использовать данный метод для запроса типа объекта, мы почти всегда получим неправильный тип. Такое происходит из-за того, что MetaTrader 5 уже уничтожил этот объект.
Когда к нам поступает событие CHARTEVENT_OBJECT_DELETE, оно не запрашивает разрешение на уничтожение объекта. MetaTrader 5 к этому моменту уже удалил объект. Следовательно, если мы не используем объект нулевого типа, то есть OBJ_VLINE, мы никогда не узнаем, какой тип объекта был уничтожен, если спросим об этом MetaTrader 5. Поскольку он не сможет сообщить об этом, так как объект больше не существует.
Пока что, на мой взгляд, всё очень просто и понятно. Теперь подходим к самой интересной части: Мы возьмем код 04 и изменим его так, чтобы несколько событий работали совместно для получения желаемого результата.
Поскольку изложение всего сразу может показаться довольно сложным, мы сделаем это в два простых шага. Первый пример показан в коде, который можно увидеть ниже.
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. //+------------------------------------------------------------------+
Код 05
Здесь, в коде 05, происходит нечто очень странное. Можно увидеть это в следующей анимации:

Анимация 06
Внимательно посмотрите на анимацию 06, уважаемый читатель. Обратите внимание, что в строке 92 мы запрашиваем создание объекта типа OBJ_LABEL. По этой причине в строке 44 будет показан текст, указывающий на тип используемого нами объекта. Пока всё идет как обычно. Однако, как только мы щелкнем по этому объекту типа OBJ_LABEL, он будет изменен на объект типа OBJ_EDIT. Это делается именно с помощью кода обработки перехваченного события в строке 106. Прошу заметить, что мы сосредоточимся на объекте, который будем создавать изначально.
Однако из-за строки 109 текст, находящийся в объекте OBJ_LABEL, будет перенесен в объект OBJ_EDIT в строке 116. Это важно для нас, потому что без этой передачи в объект OBJ_EDIT был бы помещен другой текст. А это не то, чего мы хотим. То, чего мы действительно хотим, — это иметь возможность изменять текст объекта OBJ_LABEL прямо на графике.
Теперь обратите внимание: в рамках события CHARTEVENT_OBJECT_CLICK мы удалим объект типа OBJ_LABEL и создадим объект типа OBJ_EDIT. Но поскольку мы включили уведомление для события CHARTEVENT_OBJECT_DELETE в строке 89, нам необходимо его деактивировать. Это делается в строке 112. Сразу после этого мы включаем ещё одно событие, цель которого — сообщить MetaTrader 5 о том, что мы хотим получать уведомления о создании объекта. Это приведет к тому, что событие создания объекта, выполняемое приложением, будет перехвачено в строке 119. В этом обработчике мы отключим уведомление о создании и снова включим уведомление об удалении.
Благодаря этому обработчик в строке 126 снова сможет работать, хотя и не идеально. Это связано с тем, что если мы уничтожим объект OBJ_EDIT, он будет создан заново, но его текст больше не будет соответствовать исходному тексту, существовавшему до уничтожения объекта.
Мы оставим это небольшое исправление в качестве упражнения для вашей практики, чтобы вы могли найти способ решить эту незначительную проблему. Сделать это очень просто. Однако помните, что потребуется обработать некоторые данные, чтобы восстановить текст, существовавший на момент создания объекта OBJ_EDIT. А вот восстановление текста, существовавшего между созданием OBJ_EDIT и его удалением, потребует значительно больше работы. Вам также потребуется отслеживать события клавиатуры и мыши, чтобы поддерживать актуальность текста в памяти и иметь возможность его воспроизводить.
Лично я считаю эту вторую часть совершенно ненужной и довольно раздражающей. Поскольку, если пользователь удалил текст перед сохранением в объекте OBJ_LABEL, пусть тогда сам вспоминает, какой текст был в объекте OBJ_EDIT. (СМЕХ).
Что ж, теперь наступает финальная часть, которая заключается как раз в использовании события из этой темы. Для этого мы внесем последнее изменение в код. Его можно увидеть ниже.
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. //+------------------------------------------------------------------+
Код 06
В коде 06 уже реализовано несколько функций, включая исправление этой небольшой проблемы. Но мы намеренно изменили часть кода, чтобы скрыть, как мы решили проблему. Это сделано для того, чтобы менее опытные пользователи не могли сразу увидеть, как было получено решение.
Поскольку код 06 так же прост, как и остальные, и любой, кто изучал и практиковался, сможет решить его без труда, я думаю, мне не нужно много объяснять. Скажу лишь следующее: когда объект типа OBJ_EDIT завершит редактирование, что обычно происходит при нажатии клавиши ENTER, MetaTrader 5 сгенерирует событие типа CHARTEVENT_OBJECT_ENDEDIT. Это событие будет перехвачено в строке 136, и это приведет к тому, что объект, ранее имевший тип OBJ_EDIT, снова станет объектом типа OBJ_LABEL.
Результат возможного выполнения показан ниже:

Анимация 07
Но не позволяйте анимации 07 ввести вас в заблуждение, уважаемый читатель. Постарайтесь изучить и понять, что здесь на самом деле происходит. То, что вы видите в этой анимации 07, — лишь верхушка огромного айсберга. Это происходит потому, что здесь мы показали лишь самые основные аспекты вопроса. Мы можем пойти гораздо дальше того, что было показано здесь.
Заключительные идеи
Возможно, многим эта статья показалась несколько скучной, поскольку мы постоянно показывали полный код и давали лишь краткие пояснения о том, как он работает. Но лично я считаю излишним повторять то, что уже объяснялось в других статьях этой же серии. Конечно, мы сделали нечто, что может многих удивить и даже вызвать недоверие. Это нормально, это часть процесса обучения.
Но прежде чем мы закончим эту статью и перейдем к следующей, я хотел бы дать вам ещё один совет, уважаемый читатель. В последней теме вы увидели, что мы можем делать много интересных вещей. Если присмотреться, то вы заметите, что мы изменили размеры объекта OBJ_LABEL, чтобы можно было разместить немного более длинный текст. Мой совет: постарайтесь понять, как именно создается текст в объекте типа OBJ_EDIT. Это необходимо для того, чтобы вы могли изменять размеры объекта OBJ_LABEL при отображении текста на графике.
Кроме того, попробуйте придумать способ перемещения объекта типа OBJ_LABEL на графике. Это связано с тем, что он всегда будет оставаться зафиксированным на том месте, где был создан. Обратите внимание, что это происходит именно потому, что в момент его выбора, из-за события CHARTEVENT_OBJECT_CLICK, он трансформируется в объект типа OBJ_EDIT. Это не позволяет нам изменить его местоположение.
Кроме того, мы можем сделать кое-что ещё, но, скорее всего, мы рассмотрим это в следующей статье. Если я, случайно, замечу, что это малоинтересно или не внесет большого вклада для включения в статью, я скажу, о чем идет речь. Но в любом случае теперь вы знаете об MQL5 и MetaTrader 5 немного больше, чем знали в начале этой статьи.
Поэтому постарайтесь изучить и попрактиковаться с кодами, представленными в приложении, и до встречи в следующей статье.
| Файл MQ5 | Описание |
|---|---|
| Code 01 | Демонстрация событий в объектах |
| Code 02 | Демонстрация событий в объектах |
| Code 03 | Демонстрация событий в объектах |
| Code 04 | Демонстрация событий в объектах |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/16050
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Осваиваем графики Kagi в MQL5 (Часть I): Создание движка графика Kagi
Автоматизация торговых стратегий с помощью MQL5 (Часть 47): Торговая система Nick Rypock Trailing Reverse (NRTR) с поддержкой хеджирования
Советник для размещения ордеров на основе риска с графическим интерфейсом на графике (Часть 2): Добавление интерактивности и логики
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования