От начального до среднего уровня: События в объектах (III)
Введение
В предыдущей статье От начального до среднего уровня: События в объектах (II) мы показали, как можно реализовать очень интересный и даже довольно любопытный тип приложения, целью которого будет дать возможность редактировать текст объекта OBJ_LABEL прямо на графике, без необходимости открытия окна свойств объектов и редактирования отображаемого текста там.
Хотя это было очень интересно и хорошо с практической и образовательной точки зрения, мы можем сделать нечто ещё более интересное, чем то, что было показано в предыдущей статье. Думаю, вы, уважаемый читатель, наверняка протестировали это и даже задумались, можно ли расширить то самое приложение, чтобы охватить любой объект типа OBJ_LABEL, а не только тот, который мы создавали в нашей программе, поэтому я решил показать, как это можно сделать. Это может стать действительно интересным решением как раз из-за другого вопроса, который мы рассмотрим позже.
Как обычно, перейдем к первой теме этой статьи. Итак, пора отвлечься от посторонних вещей и сосредоточиться на том, что рассмотрим в этой статье, ведь здесь мы разберем несколько очень интересных моментов.
Улучшаем то, что уже было хорошо
Итак, в предыдущей статье мы увидели, что можно сделать то, что показано на следующей анимации.

Анимация 01
Таким образом, можно реализовать механизм для непосредственного редактирования текста, присутствующего в объекте типа OBJ_LABEL на графике. Однако, если мы попытаемся добавить новый OBJ_LABEL вручную, мы увидим, что показанное в анимации 01, невозможно. Но почему? Причина кроется в фильтрах, используемых при обработке событий. В некотором смысле, на первых порах простое удаление фильтров действительно решило бы проблему. Конечно, сначала так и будет. Потому что, если мы попробуем это сделать, мы заметим, что приложение немного сходит с ума.
По этой причине мы быстро покажем, как следует изменить исходный код из предыдущей статьи, чтобы использовать механизм из анимации 01 для редактирования любого объекта типа OBJ_LABEL.
Это может показаться сложным, но на самом деле всё гораздо проще, чем кажется. Разумеется, при условии соблюдения определенных мер предосторожности при изменении кода. Большая часть того, что необходимо сделать, уже объяснялась в предыдущих статьях, поэтому мы сосредоточимся только на том, что действительно важно. Это показано ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. switch (id) 17. { 18. case CHARTEVENT_OBJECT_CLICK : 19. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) Swap_ObjLabel_ObjEdit(sparam); 20. break; 21. case CHARTEVENT_OBJECT_ENDEDIT : 22. Swap_ObjLabel_ObjEdit(sparam); 23. break; 24. } 25. ChartRedraw(); 26. }; 27. //+------------------------------------------------------------------+ 28. void Swap_ObjLabel_ObjEdit(const string szName) 29. { 30. struct st_Mem 31. { 32. string font, text; 33. int x, y, w, h, fontSize; 34. color cor; 35. }mem; 36. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_EDIT ? OBJ_LABEL : OBJ_EDIT; 37. 38. mem.font = ObjectGetString(0, szName, OBJPROP_FONT); 39. mem.x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 40. mem.y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 41. mem.w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 42. mem.h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 43. mem.fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 44. mem.cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 45. mem.text = ObjectGetString(0, szName, OBJPROP_TEXT); 46. 47. ObjectDelete(0, szName); 48. ChartRedraw(); 49. ObjectCreate(0, szName, type, 0, 0, 0); 50. 51. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (type != OBJ_EDIT ? true : false)); 52. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, mem.x); 53. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, mem.y); 54. ObjectSetInteger(0, szName, OBJPROP_XSIZE, mem.w); 55. ObjectSetInteger(0, szName, OBJPROP_YSIZE, mem.h); 56. ObjectSetInteger(0, szName, OBJPROP_COLOR, mem.cor); 57. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, mem.fontSize); 58. ObjectSetString(0, szName, OBJPROP_FONT, mem.font); 59. ObjectSetString(0, szName, OBJPROP_TEXT, mem.text); 60. if (type == OBJ_EDIT) 61. { 62. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, clrWhite); 63. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 64. } 65. } 66. //+------------------------------------------------------------------+
Код 01
Теперь я хочу, чтобы вы посмотрели на код 01 и сказали мне: что в нём сложного? Итак, если вы следили за этой серией статей, а также изучали и применяли материалы на практике, ваш ответ, вероятно, будет таким: "Здесь нет ничего сложного, всё очень просто и понятно". Уважаемый читатель, этот код 01 настолько прост и понятен, что я не вижу необходимости объяснять, как он работает. Однако здесь есть небольшая проблема. Ничего сложного. Но прежде чем показать, как это исправить, давайте посмотрим, что происходит, когда код 01 применяется на любом графике. Для этого мы выполним небольшую последовательность шагов, начиная с первого, который представлен ниже.

Анимация 02
В анимации 02 мы добавили к графику только код 01. Обратите внимание, что ничего не произошло, так как цель не в том, чтобы что-то создать, а в том, чтобы работать с уже созданным. Сразу после этого мы добавим несколько объектов типа OBJ_LABEL. Для этого мы используем то, что показано далее:

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

Изображение 02
Отлично, теперь начинается часть, в которой всё обретает смысл. Как только приложение будет запущено на графике и у нас появится какой-либо объект правильного типа, мы больше не сможем делать всё так, как раньше. Это происходит потому, что при клике на один из объектов типа OBJ_LABEL он преобразуется в объект типа OBJ_EDIT, что позволит нам напрямую вводить отображаемый текст. И всё это без необходимости открывать окно свойств объекта типа OBJ_LABEL.

Анимация 03
Обратите внимание, что, как видно из анимации 03, мы действительно можем редактировать эти объекты, как и было обещано и продемонстрировано в предыдущей статье. Но это ещё не всё: теперь мы можем сделать это с любым объектом типа OBJ_LABEL. Кроме того, мы можем делать гораздо больше, хотя и несколько иным способом. Для этого необходимо открыть окно списка объектов. Таким образом, можно изменить некоторые свойства, такие как цвет, шрифт, размер шрифта, а также имя самого объекта.
Однако эти свойства, которыми мы будем манипулировать таким образом, являются дополнительными свойствами. В любом случае, объекты OBJ_EDIT и OBJ_LABEL будут использовать один и тот же набор свойств. Поскольку процедура в строке 28, которую видно в коде 01, сама позаботится о передаче этих свойств.
Итак, в чём же заключается тот недостаток, о котором упоминали в начале темы? Что ж, уважаемый читатель, это не ужасный и не тревожный недостаток. Это скорее проблема выбора и снятия выделения. При использовании приложения, показанного в коде 01, нам часто потребуется, чтобы на графике присутствовал только один объект типа OBJ_EDIT. Это связано с тем, что в некоторых ситуациях присутствие нескольких объектов типа OBJ_EDIT на графике не имеет особого смысла. Но из-за ошибки в процессе выбора и снятия выделения, посмотрите, что происходит.

Анимация 04
Обратите внимание, что изначально у нас было два объекта типа OBJ_LABEL, а затем появилось два объекта типа OBJ_EDIT. Это можно исправить. Я не говорю, что это совершенно неправильно, но ситуация, подобная показанной в анимации 04, не имеет смысла, если цель состоит просто в возможности редактировать текст OBJ_LABEL непосредственно на графике.
Исправить ошибку такого типа очень просто. Для решения данной проблемы нам нужно всего лишь изменить код обработки событий. В нашем конкретном случае будет достаточно изменить код, как показано ниже:
. . . 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. static string szOldEdit = ""; 17. 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) 22. { 23. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 24. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 25. } 26. break; 27. case CHARTEVENT_OBJECT_ENDEDIT : 28. Swap_ObjLabel_ObjEdit(sparam); 29. szOldEdit = ""; 30. break; 31. } 32. ChartRedraw(); 33. }; 34. //+------------------------------------------------------------------+ . . .
Код 02
Прошу заметить, насколько просто применить данное решение, поскольку нужно изменить только те точки, которые показаны во фрагменте кода 02. Результат можно увидеть в анимации 05 ниже:

Анимация 05
"Хорошо, теперь я, кажется, понимаю, насколько важно практиковаться и лучше изучать материал, прежде чем повторять что-то, что все будут повторять просто потому, что это кажется правильным. Но у меня есть вопрос по поводу этого приложения, создание которого вы так любезно продемонстрировали. Разве нет способа управлять другими параметрами без необходимости открывать окно со списком объектов? Спрашиваю потому, что, по всей видимости, у нас нет способа изменить положение, в котором будет отображаться объект. После размещения объекта и запуска приложения на графике, переместить объект становится практически невозможно.
Итак, есть ли какой-нибудь способ разрешить подобную ситуацию? Потому что это меня довольно сильно смущает, хотя показанный способ манипуляции мне кажется очень интересным".
Да, уважаемый читатель, есть способы решить эту проблему. Вот почему важно, чтобы вы очень хорошо понимали некоторые вещи. Всегда старайтесь понять саму концепцию, а не конкретный использованный код. Это объясняется тем, что код можно изменять, а концепцию — нет.
Существует несколько способов сделать то, что вы предлагаете, то есть управлять положением объекта, когда показанное здесь приложение запущено на графике. Один из самых простых способов — изменить принцип работы объекта OBJ_EDIT.
В предыдущей статье мы упоминали, что для редактирования содержимого объекта типа OBJ_EDIT необходимо, чтобы одно из его свойств имело определенное значение. Если это же свойство содержит другое значение, редактирование блокируется. "Но почему это важно для нас? Разве идея заключалась не в том, чтобы создать способ перемещения объекта на графике? Как эта информация об определенном свойстве и о том, как оно влияет на объект типа OBJ_EDIT, может помочь нам решить вопрос с перемещением? Я этого не понимаю. Не могли бы вы объяснить подробнее?"
Конечно, я могу подробнее объяснить, к чему я клоню. Если вы помните, в двух последних статьях мы показывали, как наш код может перехватывать события объектов, когда нам нужно знать, что происходит. Поскольку мы не хотим брать на себя слишком большой объем дополнительной работы, вплоть до того, чтобы указывать объектам, как они должны перемещаться, мы поручаем эту задачу MetaTrader 5. Что ж, эту часть понять легко. Возможно, сложная часть (отсюда и необходимость очень хорошо понимать, как работают объекты) заключается в том, что когда объект типа OBJ_EDIT не может редактироваться, потому что он выделен, мы можем переложить на MetaTrader 5 ответственность за перемещение объекта.
Самая же трудная для понимания часть для подавляющего большинства начинающих программистов, состоит в том, что наш код 01 даже после добавления фрагмента из кода 02 всё равно будет преобразовывать объект типа OBJ_LABEL в объект типа OBJ_EDIT. Это делается для редактирования текста, который впоследствии будет отображаться. Однако что произойдет, если вы снова кликнете по объекту типа OBJ_EDIT? Исходя из того, с чем мы имеем дело в коде, ответ следующий: НИЧЕГО. Но подумайте на мгновение: когда мы закончим перемещать объект, MetaTrader 5 сгенерирует событие. Оно уведомит наше приложение о том, что пользователь отпустил объект и теперь тот находится в новом положении.
Таким образом, если мы применим небольшую стратегию, то сможем принудительно перевести объект типа OBJ_EDIT в состояние, которое позволит MetaTrader 5 управлять его перемещением. Таким образом, пользователь сможет перемещать объект на графике. Когда пользователь отпустит объект, мы завершим редактирование, или же мы можем просто преобразовать объект типа OBJ_EDIT в объект типа OBJ_LABEL. Вам, уважаемый читатель, предстоит решить, какой из вариантов лучше всего подходит для ваших целей.
Возможно, при таком объяснении это может показаться несколько сложным. Однако, когда мы превращаем идеи в код, понимать вещи становится гораздо проще. Но прежде чем мы это сделаем, нужно кое-что показать вам, уважаемый читатель. Это будет простой пример, но оно сделает гораздо более понятной одну важнейшую для нас деталь.
Когда мы говорили в предыдущей статье, что нам необходимо установить свойство OBJPROP_SELECTABLE объекта типа OBJ_EDIT в значение false, мы так и не показали почему. Хотя в некоторых случаях мы можем установить OBJPROP_SELECTABLE в значение true и при этом всё равно иметь возможность редактировать текст в объекте типа OBJ_EDIT. Но демонстрация того или иного случая, когда мы можем это сделать, без сомнения, делает вещи гораздо более сложными. Чтобы увидеть, что было сказано на практике, посмотрите на приведенный ниже код.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. static string szOldEdit = ""; 17. 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) 22. { 23. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 24. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 25. } 26. break; 27. case CHARTEVENT_OBJECT_ENDEDIT : 28. Swap_ObjLabel_ObjEdit(sparam); 29. szOldEdit = ""; 30. break; 31. } 32. ChartRedraw(); 33. }; 34. //+------------------------------------------------------------------+ 35. void Swap_ObjLabel_ObjEdit(const string szName, bool Selectable = true) 36. { 37. struct st_Mem 38. { 39. string font, text; 40. int x, y, w, h, fontSize; 41. color cor; 42. }mem; 43. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_EDIT ? OBJ_LABEL : OBJ_EDIT; 44. 45. mem.font = ObjectGetString(0, szName, OBJPROP_FONT); 46. mem.x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 47. mem.y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 48. mem.w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 49. mem.h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 50. mem.fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 51. mem.cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 52. mem.text = ObjectGetString(0, szName, OBJPROP_TEXT); 53. 54. ObjectDelete(0, szName); 55. ChartRedraw(); 56. ObjectCreate(0, szName, type, 0, 0, 0); 57. 58. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable); 59. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, mem.x); 60. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, mem.y); 61. ObjectSetInteger(0, szName, OBJPROP_XSIZE, mem.w); 62. ObjectSetInteger(0, szName, OBJPROP_YSIZE, mem.h); 63. ObjectSetInteger(0, szName, OBJPROP_COLOR, mem.cor); 64. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, mem.fontSize); 65. ObjectSetString(0, szName, OBJPROP_FONT, mem.font); 66. ObjectSetString(0, szName, OBJPROP_TEXT, mem.text); 67. if (type == OBJ_EDIT) 68. { 69. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, clrWhite); 70. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 71. } 72. } 73. //+------------------------------------------------------------------+
Код 03
Код 03 аналогичен коду 02, где мы видели лишь фрагмент набора, но на этот раз он здесь приведён почти целиком. Это можно увидеть в строке 35. И данное значение, указанное во втором аргументе, используется только в одном месте, то есть, в строке 58. А теперь будьте внимательны, потому что это может стать немного запутанным в зависимости от типа конфигурации, которую вы реализуете в своем коде. В текущем состоянии код 03 будет вести себя следующим образом:

Анимация 06
"Секунду, подождите немного. Этот код работает не так, как код 02. Как это возможно? Неужели простое изменение одного свойства изменило код таким образом? Это очень странно. Но разве нельзя разрешить редактирование одновременно с возможностью выбора объекта?" В некоторых случаях — да, уважаемый читатель. Однако, поскольку некоторые вопросы довольно сложно объяснить из-за необходимости вдаваться в подробности работы определенных свойств, я предпочитаю использовать другой подход. Потому что так проще объяснить и продемонстрировать это, не запутав вас.
Итак, я думаю, вы заметили следующее: если свойство OBJPROP_SELECTABLE включено для объекта типа OBJ_EDIT, следует предположить, что мы не можем редактировать текст непосредственно на графике. Однако переместить объект будет вполне возможно.
Итак, теперь, учитывая эти факты, мы можем изменить код 03, чтобы получить ожидаемое поведение. Это делается путем модификации кода, как показано ниже.
. . . 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. { 22. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE); 23. if (type == OBJ_EDIT) ObjectSetInteger(0, sparam, OBJPROP_SELECTABLE, false); 24. else if (type == OBJ_LABEL) 25. { 26. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 27. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 28. ObjectSetInteger(0, sparam, OBJPROP_SELECTED, true); 29. } 30. } 31. break; . . .
Код 04
В данном случае, в коде 04, мы сосредоточимся только на той части, которую действительно необходимо изменить в коде 03. Иными словами, мы видим только тот фрагмент, который нуждался в модификации. Теперь, когда мы используем этот фрагмент в коде 03 (или, если хотите, в коде 02), результат будет таким:

Анимация 07
"Да ладно, какое же это безумное и абсурдное решение, которое здесь внедряют. Ты, должно быть, ненормальный. Невозможно было придумать такой способ решения проблемы перемещения и редактирования объекта типа OBJ_LABEL непосредственно на графике". Это безумие.
Но если оставить в стороне эту безумную сторону дела, разве то, что я только что вам показал, не забавно? Потому что, по сути, приложив совсем немного усилий, удалось создать способ прямого редактирования объекта типа OBJ_LABEL на графике. Конечно, это решение не является на 100% идеальным. Это связано с тем, что нужно некоторое время попользоваться приложением, чтобы привыкнуть к тому, как оно работает. Поскольку это последовательность операций, которые должны быть выполнены в определенное время, как показано в анимации 07. Следовательно, строка 24 пройдет проверку, и таким образом мы преобразуем объект типа OBJ_LABEL в объект типа OBJ_EDIT. Но поскольку мы хотим иметь возможность перемещать его, в строке 28 мы активируем выделение объекта. Это позволит нам перемещать объект типа OBJ_EDIT на графике.
Как только мы снова щелкнем по тому же объекту, проверка в строке 23 отключит возможность выбора. Таким образом, при повторном щелчке MetaTrader 5 будет выделять не объект типа OBJ_EDIT, а текст, позволяя его изменять. Получилось хорошо, но можно сделать ещё лучше. Однако, поскольку цель состоит НЕ в создании приложения, а в том, чтобы показать, что мы можем сделать и как это сделать, я уже доволен представленным результатом и могу перейти к следующей теме.
Управление размерами непосредственно на графике
Могу без ложной скромности сказать, что всё, что мы увидели до этого момента, было очень весело и интересно. Но ничто не сравнится с тем, что мы увидим сейчас. Давайте приступим. Нужно сказать, что в зависимости от вашей креативности и способности представить, как всё может сочетаться, будет больше или меньше вариантов. Просто потому, что мы уже поняли, как MetaTrader 5 и наше приложение взаимодействуют друг с другом, что позволяет нам получать определенный тип результатов и создавать определенную модель работы приложения.
На мой взгляд, очень интересно использовать MetaTrader 5 не только для рыночных операций, но и для создания других вещей. Например, графический редактор, текстовый редактор или даже небольшая платформа для простых игр, таких как головоломки или классические игры, в стиле старых игровых автоматов.
Многие, возможно, считают, что подобные вещи сделать невозможно, так как у нас, судя по всему, нет необходимых для этого механизмов. Однако, вопреки тому, что многие думают и считают, что знают о возможностях MetaTrader 5, у нас действительно есть потенциал для реализации подобного функционала на этой платформе. Главное, чтобы у нас было воображение и необходимые знания. Но главное, чтобы мы обладали правильными концепциями и знали, как использовать их в своих интересах. Если это произойдёт, вы сможете создавать множество интересных вещей для работы в MetaTrader 5, а не ограничиваться просмотром скучных и неинтересных ценовых графиков. Возможно, вы даже сможете придумать немного более интересный способ построения ценового графика, используя, помимо прочего, 3D-элементы.
Но в данный момент вы, скорее всего, ещё не готовы это себе представить. То есть для того, чтобы сделать что-то подобное, к текущему моменту мы находимся лишь в самой базовой точке всего этого. И да, вопреки тому, что можно себе представлять, мы всё еще находимся на базовом уровне того, что представляет собой программирование на MQL5, даже можно говорить, что на уровне для начинающих. И посмотрите, как много нам уже удалось сделать всего лишь на этом базовом уровне.
Ну что ж, теперь мы рассмотрим кое-что другое, что сделать довольно легко. Хотя это может быть немного сложно для понимания, если вы не следили за статьями. Поскольку мы будем использовать некоторые приёмы, это может показаться несколько сложным. Однако я не хочу ограничивать ваше воображение базовым уровнем наших возможностей. Если мы можем что-то сделать, давайте это сделаем. Хотя мне требуется некоторое время, чтобы по-настоящему понять, что происходит и какая концепция используется.
Цель данной темы — разработать способ изменения размеров объекта на графике с помощью мыши. Но мы постараемся сделать это без прямого использования событий мыши. Кажется, это довольно сложно, не правда ли? Но идея будет заключаться в том, чтобы продолжать использовать помощь MetaTrader 5 для управления определенными событиями, а мы будем контролировать их таким образом, чтобы они приводили к определенному результату.
Это действительно кажется очень сложной задачей. Что-то, что пришло бы в голову только какому-нибудь сумасшедшему, сбежавшему из санатория и одержимому идеей сделать всех остальных одинаковыми. Но вы увидите, что это очень просто — достаточно просто идти вперед. Возможно, даже проще. Однако, поскольку эта тема довольно обширна и потребует более чем одной статьи для должного объяснения и понимания, мы начнем со следующего факта: в отличие от того, что происходило в остальном коде, здесь мы уже позволим вам или любому другому пользователю манипулировать размерами любого типа объекта.
Хотя на первом этапе это будет применимо не к каждому типу. Возможно, это было создано вашим приложением или добавлено пользователем на график. Это не имеет значения, мы будем работать со всеми одинаково. Существуют некоторые ограничения, но мы рассмотрим их по мере реализации кода. Итак, для начала рассмотрим следующий код:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 11. 12. return INIT_SUCCEEDED; 13. }; 14. //+------------------------------------------------------------------+ 15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 16. { 17. return rates_total; 18. }; 19. //+------------------------------------------------------------------+ 20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 21. { 22. switch (id) 23. { 24. case CHARTEVENT_KEYDOWN : 25. break; 26. case CHARTEVENT_OBJECT_CLICK : 27. break; 28. case CHARTEVENT_OBJECT_DRAG : 29. break; 30. } 31. ChartRedraw(); 32. }; 33. //+------------------------------------------------------------------+ 34. void OnDeinit(const int reason) 35. { 36. ObjectsDeleteAll(0, def_Prefix); 37. ChartRedraw(); 38. }; 39. //+------------------------------------------------------------------+
Код 05
Код 05 — это наш базовый шаблон. Прошу заметить, что в коде 05 указаны три события, которые необходимо обработать. Это связано с тем, что мы не собираемся создавать фантабулозное приложение (это было бы слиянием слов "фантастическое" и "фабулозное", то есть невероятное и баснословное). Здесь мы просто сделаем что-то простое и понятное: изменим размеры и, возможно, положение любого объекта. И это непосредственно на графике. Что касается положения, я всё ещё обдумываю, стоит ли это реализовывать.
"Хорошо, вы здесь главный. Итак, что же мы предпримем дальше? Итак, уважаемый читатель, следующий шаг — использовать событие CHARTEVENT_OBJECT_CLICK для получения данных об объекте, с которым мы собираемся работать. В принципе, мы будем работать только с этой информацией, а именно с названием объекта. Потому что это единственная информация, которая действительно важна для нас. И потому что всё остальное мы можем из этого получить. Итак, теперь мы сосредоточимся на процедуре OnChartEvent, чтобы не пришлось снова показывать весь код. Таким образом, первые изменения можно увидеть ниже:
. . . 19. //+------------------------------------------------------------------+ 20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 21. { 22. static string szNameObj = NULL; 23. 24. switch (id) 25. { 26. case CHARTEVENT_KEYDOWN : 27. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) szNameObj = NULL; 28. break; 29. case CHARTEVENT_OBJECT_CLICK : 30. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 31. if (szNameObj != NULL); 32. if (szNameObj != sparam); else szNameObj = NULL; 33. break; 34. case CHARTEVENT_OBJECT_DRAG : 35. break; 36. } 37. ChartRedraw(); 38. }; 39. //+------------------------------------------------------------------+ . . .
Код 06
"Мы до сих пор не знаем, что сделаем. Не могли бы вы поделиться небольшим фрагментом кода? Я начинаю немного волноваться". Что ж, уважаемые читатели. Вы сами этого хотели. (СМЕХ). Но прежде чем это сделать, взгляните на логику, заложенную во фрагменте кода из пункта 06. Понимание данной логики очень важно для того, чтобы предвидеть дальнейшие действия. Таким образом, следующий шаг показан в приведенном ниже коде:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. struct st_BoxResize 09. { 10. private : 11. ushort x, y, w, h; 12. bool isBack; 13. string szObjects[4]; 14. //+----------------+ 15. void CreateEdge(const char what) 16. { 17. ushort p1 = x, p2 = y, p3 = w, p4 = h; 18. 19. switch (what) 20. { 21. case 0: p4 = 1; break; 22. case 1: p3 = 1; break; 23. case 2: p2 = y + h; p4 = 1; break; 24. case 3: p1 = x + w; p3 = 1; break; 25. } 26. szObjects[what] = macro_NameObject; 27. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 28. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 29. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 30. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 31. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 32. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 33. } 34. //+----------------+ 35. public : 36. void CreateBoxResize(const string szArg, const uchar adjust = 2) 37. { 38. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 39. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 40. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 41. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 42. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 43. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 44. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 45. for (uchar c = 0; c < 4; c++) 46. CreateEdge(c); 47. } 48. //+----------------+ 49. void RemoveBoxResize(const string szArg) 50. { 51. ObjectSetInteger(0, szArg, OBJPROP_BACK, isBack); 52. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 53. ObjectsDeleteAll(0, def_Prefix); 54. } 55. //+----------------+ 56. }gl_RZ; 57. //+------------------------------------------------------------------+ 58. int OnInit() 59. { 60. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 61. 62. return INIT_SUCCEEDED; 63. }; 64. //+------------------------------------------------------------------+ 65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 66. { 67. return rates_total; 68. }; 69. //+------------------------------------------------------------------+ 70. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 71. { 72. static string szNameObj = NULL; 73. 74. switch (id) 75. { 76. case CHARTEVENT_KEYDOWN : 77. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 78. { 79. gl_RZ.RemoveBoxResize(szNameObj); 80. szNameObj = NULL; 81. } 82. break; 83. case CHARTEVENT_OBJECT_CLICK : 84. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 85. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 86. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 87. break; 88. case CHARTEVENT_OBJECT_DRAG : 89. break; 90. } 91. ChartRedraw(); 92. }; 93. //+------------------------------------------------------------------+ 94. void OnDeinit(const int reason) 95. { 96. ObjectsDeleteAll(0, def_Prefix); 97. ChartRedraw(); 98. }; 99. //+------------------------------------------------------------------+
Код 07
Ух, ты! Не торопитесь, не нужно заходить так далеко". Что ж, вы просили, вот результат. Но хотя это и кажется очень сложным, сам по себе этот код очень прост и чрезвычайно базовый. Чтобы это понять, достаточно спокойно понаблюдать за тем, как всё делается. В любом случае, я кратко объясню, что здесь делается. Поскольку это первая часть того, что мы сделаем позже.
В строке 08 мы создаём структуру, цель которой — сформировать рамку вокруг выбранного нами объекта. А теперь будьте внимательны, уважаемый читатель. Объект, вокруг которого мы создадим этот контур, должен иметь декартовы координаты. Координаты ценовой шкалы здесь пока не рассматриваются. Поскольку в MetaTrader 5 реализовано мало объектов этого типа, и из них интерес могут представлять только два — это объекты типа OBJ_TEXT и OBJ_BITMAP, я пока не вижу причин работать с ними. Однако остальные тоже важны для нас.
Прошу заметить, что в обработчике события OnChartEvent мы ссылаемся на некоторые процедуры внутри структуры. Но сама структура содержит внутреннюю и приватную процедуру. Именно она отвечает за создание линий, которые будут окружать выделенный объект.
В данном случае мы используем довольно яркий цвет. Это определено в строке 28. При желании, можно заменить его на другой, который покажется вам более подходящим. Однако избегайте изменения других частей кода, если только вы не экспериментируете, чтобы понять, как он работает. Отлично, в строке 38 мы фиксируем то, что выбранный объект находится на переднем плане или является объектом на заднем плане. Это важно для нас, так как в любом случае мы отправим объект на задний план и снимем с него выделение, чтобы тем самым показать, что он не выбран.
Но зачем мы это делаем? Причина заключается в том, чтобы объект не мешал тому, что мы будем делать дальше. Помните, когда объект выбран, мы можем использовать одну из его конкретных точек для перемещения в графическом окне. Это делается непосредственно с помощью MetaTrader 5. Однако в данном случае нам не нужно такое поведение, поскольку мы возьмем управление в свои руки и будем контролировать это самостоятельно. Во-первых, нужно, чтобы вы полностью поняли, как работает этот код. Но перед завершением этой статьи, давайте посмотрим, на что способен код 07. Что ж, это показано на следующей анимации. Но сначала взгляните на объекты, присутствующие на графике. Постарайтесь сосредоточиться на названиях объектов, поскольку это важно для понимания того, как обработчик событий будет обрабатывать каждый из них. Это видно на изображении 03:

Изображение 03
Теперь перейдём к демонстрационной анимации.

Анимация 08
Заключительные идеи
В данной статье мы продемонстрировали, как можно работать с объектами типа OBJ_LABEL совместно с объектами типа OBJ_EDIT, чтобы иметь возможность редактировать текст непосредственно на графике. Кроме того, мы увидели, как можно добавить логику, позволяющую перемещать объект, даже если изначально это было невозможно. Потому что, когда мы переключались с объекта типа OBJ_LABEL на объект типа OBJ_EDIT и наоборот, мы заходили в тупик в плане реализации перемещения объекта.
Мы также увидели начало того, что будет более подробно изучено в следующей статье. Но, поскольку я хочу, чтобы вы постарались очень хорошо понять то, что было сделано здесь, так как это понадобится позже, я решил кратко забежать вперед и показать то, что, на мой взгляд, натолкнет вас на различные идеи об управлении MetaTrader 5 с помощью MQL5.
На этом пока всё, увидимся в следующей статье. Удачи вам в дальнейшем обучении и до скорой встречи!
| Файл MQ5 | Описание |
|---|---|
| Code 01 | Демонстрация событий в объектах |
| Code 02 | Демонстрация событий в объектах |
| Code 03 | Демонстрация событий в объектах |
| Code 04 | Демонстрация событий в объектах |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/16075
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Торговые инструменты MQL5 (Часть 26): Интеграция частотного биннинга, энтропии и критерия хи-квадрат в визуальный анализатор
Моделирование рынка: Первые шаги на SQL в MQL5 (II)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования