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

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

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

Введение

В предыдущей статье, От начального до среднего уровня: События в объектах (III), мы показали первую часть чего-то ещё большего, что мы рассмотрим в данной статье и где завершим начатое в предыдущей публикации.

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

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


Улучшение интерактивности

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

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

Анимация 01

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

В моих статьях до настоящего момента мы показывали, что можем взаимодействовать с MetaTrader 5 для достижения определенных результатов. Однако большая часть работы была возложена на MetaTrader 5, и мы выполнили лишь часть её в процессе реализации кода. Однако здесь у нас есть небольшая проблема. Ее можно резюмировать следующим образом: MetaTrader 5 может выполнить большую часть работы, необходимой нашему приложению для управления размерами выделенного объекта, но, и это ключевой момент, мы не сможем этого сделать без дополнительной работы. Я говорю это, потому что при использовании исходного кода, который полностью приведен ниже, МЫ НЕ ПОЛУЧИМ НА 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_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. //+------------------------------------------------------------------+

Код 01

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

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

Возможно, изображение 01 вам ни о чем не говорит. Но я подчеркиваю один момент в ней именно для того, чтобы обратить ваше внимание на одну деталь. Контурная линия НЕ ИМЕЕТ ЭЛЕМЕНТОВ ВЗАИМОДЕЙСТВИЯ, то есть у этой контурной линии нет элементов взаимодействия, то есть ни MetaTrader 5, ни пользователь не понимают, как именно её можно перемещать. А теперь внимание: В коде 01 строка 84 служит именно для того, чтобы запретить пользователю выделять любой объект, имя которого имеет префикс, указанный в строке 04 этого же кода. И что это значит? Хорошо, это объясняет, почему мы не можем выделять объект OBJ_BUTTON, так как данный объект имеет в своем имени упомянутый префикс. Вы можете увидеть это на изображении 02 ниже.

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

Прошу заметить, что мы выделили зелёным цветом именно тот префикс, который не позволяет нам выделять данный объект. Хотя на первый взгляд это не кажется столь существенным, этой простой детали уже достаточно, чтобы проверка в строке 84 не позволила нам его выделять. Хорошо, но нам и не нужно выделять зеленые линии, которые используются в качестве контура. Мы можем сделать лучше, если выделяем сам объект типа OBJ_RECTANGLE_LABEL и предоставим MetaTrader 5 сделать всё остальное за нас. Да, это отчасти правда. Для этого потребуется внести простое изменение в код 01. Его можно увидеть в приведенном ниже фрагменте кода:

                   .
                   .
                   .
034. //+----------------+
035.     public  :
036.         void CreateBoxResize(const string szArg, const uchar adjust = 2)
037.         {
038.             isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK);
039.             ObjectSetInteger(0, szArg, OBJPROP_BACK, true);
040.             ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false);
041.             x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust;
042.             y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust;
043.             w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust;
044.             h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust;
045.             for (uchar c = 0; c < 4; c++)
046.             {
047.                 CreateEdge(c);
048.                 ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3);
049.                 ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true);
050.                 ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true);
051.             }
052.         }
053. //+----------------+
                   .
                   .
                   .

Код 02

То, что этот фрагмент кода 02 создаст в коде 01, показано на анимации 02 ниже:

Анимация 02

Хорошо, в анимации 02 мы кое-что видим. Давайте спокойно рассмотрим его. Посмотрите на изображение 03:

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

Эта точка, которую мы выделили на изображении 03, является именно той ключевой точкой, которую мы можем использовать для перетаскивания объекта в окне. Для этого мы используем MetaTrader 5, вообще ни о чем не беспокоясь. Однако, если присмотреться, вы заметите, что у нас видны только 3 точки. Где находится четвёртая? Хорошо, вот наша первая проблема. Четвертая точка находится рядом с другой точкой, именно в этом месте, которое выделили на изображении 03. Но если бы это была единственная реальная проблема, ничего страшного бы не было. Тут мало что можно было бы сделать. Тем не менее, есть проблема чуть более раздражающая, чем эта. Она показана в анимации ниже:

Анимация 03

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

Ладно, на этом этапе многие бы сдались и начали говорить: "То, что вы хотите сделать, невозможно". Неужели это правда, уважаемый читатель? Хорошо, подумайте об этом немного. Мы знаем, как MetaTrader 5 передает нашему приложению информацию о событиях, происходящих с объектами. Мы также увидели, как определенные решения, принятые в процессе реализации, могут повлиять на тип результата, которого мы сможем достичь. Итак, я спрашиваю:

Неужели то, что я хочу сделать, действительно невозможно?

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

Не стоит слишком привязываться к коду. Всегда старайтесь понять задействованные концепции и то, как они использовались.

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

Хорошо, давайте остановимся и немного подумаем об этом. Мы знаем, что НЕ МОЖЕМ ВЫДЕЛЯТЬ ни один из объектов, имена которых имеют префикс, определенный в коде. Это происходит именно из-за строки 84 из кода 01. Но теперь мы знаем, что нам не нужно выделять данные объекты. В процессе создания объекта мы можем указать MetaTrader 5, что он уже будет выделен. Это связано со строками 49 и 50, которые видны во фрагменте кода 02 и представляют собой именно ту модификацию, которую мы реализовали в коде 01.

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

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

Хорошо, тогда мы создадим новый код, отталкиваясь от кода 01 с модификацией, показанной во фрагменте кода 02. Его можно увидеть ниже:

                   .
                   .
                   .
013.         string  szObjects[6];
014. //+----------------+
015.         void CreateEdge(const char what)
016.         {
017.             ushort p1 = x, p2 = y, p3 = w, p4 = h;
018. 
019.             switch (what)
020.             {
021.                 case 0: p4 = 1; break;
022.                 case 1: p3 = 1; break;
023.                 case 2: p2 = y + h; p4 = 1; break;
024.                 case 3: p1 = x + w; p3 = 1; break;
025.                 case 5: p1 += p3; p2 += p4;
026.                 case 4: p3 = p4 = 1; break;
027.             }
                   .
                   .
                   .
035.         }
036. //+----------------+
037.     public  :
038.         void CreateBoxResize(const string szArg, const uchar adjust = 2)
039.         {
                   .
                   .
                   .
047.             for (uchar c = 0; c < szObjects.Size(); c++)
048.             {
049.                 CreateEdge(c);
050.                 if (c > 3)
051.                 {
052.                     ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3);
053.                     ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true);
054.                     ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true);
055.                 }
056.             }
057.         }
058. //+----------------+
                   .
                   .
                   .

Код 03

Теперь посмотрите на следующее, уважаемый читатель: в этом фрагменте кода 03 есть одно простое изменение. Однако оно весьма значимо. Прошу заметить, что в строке 13 мы изменили количество элементов в массиве. В операторе switch в строке 19 у нас также есть ещё два интересных момента, которые можно увидеть в строках 25 и 26. А теперь давайте сделаем небольшую паузу, чтобы мы могли объяснить ещё кое-что очень важное.

В статье От начального до среднего уровня: Оператор SWITCH было изображение, на котором упоминалось наличие каких-то красных линий. Это изображение можно увидеть ещё раз ниже:

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

Дело здесь заключается именно в тех красных линиях, которые видны на изображении 04. В этом операторе switch в строке 19 как раз и используются эти красные линии. Это важно, поскольку позволяет выполнению “провалиться” в следующий case. "Надо же, а я-то думал, что всё уже просто не может запутаться ещё сильнее! А вы мне говорите, что выполнение может перейти в другой блок кода? И это делается намеренно? Мне кажется, программирование гораздо сложнее, чем я себе представлял. Сейчас я думаю о том, чтобы бросить всё и заняться чем-нибудь другим".

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

Посмотрите на последовательность операторов case. Вы заметите, что они идут почти по порядку, пока вдруг этот порядок не меняется: с третьего на пятый, а затем на четвертый. "Как странно. Я не обратил на это внимания. Теперь, когда вы об этом упомянули, это действительно кажется странным. Но зачем вы это делаете?" Причина в том, что я сделал это намеренно. Почему? Именно для того, чтобы объяснить вам ту красную линию, которую можно увидеть на изображении 04.

Итак, обратите внимание: хотя это и кажется чем-то простым, это обычно серьезно запутывает многих хороших программистов, а не только новичков. Обратите внимание, что в каждой строке между 21 и 26 во фрагменте из кода 03 стоит оператор break в конце строки. Во всех строках, кроме одной строки 25. Почему? Причина заключается в том, что когда совпадет case в строке 25, эта строка будет выполнена. Но именно здесь возникает вопрос о переходе к следующему case, Я НЕ ХОЧУ, ЧТОБЫ ВЫПОЛНЕНИЕ НА ЭТОМ ЗАКОНЧИЛОСЬ. Я хочу, чтобы процедура, присутствующая в следующем case, была выполнена без предварительной оценки этого случая.

"Господи, помилуй! Я ничего не понял из того, что вы сказали. Но подождите минутку, дайте мне спокойно обдумать ваши слова. Насколько я понимаю, вы хотите, чтобы в момент проверки строки 25 и выполнения процедуры обработки операторов case, процедура обработки операторов case из строки 26 также выполнялась. Вы это имели в виду?" Да, и такое возможно, уважаемый читатель. "Но кое-чего я не понял. Как будет выполнен код в case, расположенном в строке 26, без проверки этого case? Это не имеет смысла.

Именно в этот момент происходит переход к следующему case". Будьте внимательны далее, уважаемый читатель. Поскольку оператор case всегда заканчивается оператором break, как показано на изображении 04, если у нас НЕТ оператора BREAK, выполнение продолжится в ветке следующего case, БЕЗ ПРОВЕРКИ СЛЕДУЮЩЕГО CASE. И вот тут вступает в игру красная линия.

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

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

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

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

Анимация 04

"Ммм, интересно. В каком-то смысле, как мне кажется, сейчас у нас есть относительно многообещающие перспективы. Итак, каким будет наш следующий шаг?" Что ж, уважаемый читатель, не всегда всё так просто. Вопрос здесь не в том, каким будет следующий шаг. Главное, и это то, что мне от вас нужно, — чтобы вы понимали, что именно мы здесь делаем. Этот тип манипуляций, который мы показываем, требует определенной осторожности и создания тестов, чтобы гарантировать, что всё не выйдет из-под контроля. Без этого мы рискуем совершить ошибку, которая может превратить наш день в нечто очень неприятное.

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

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

                   .
                   .
                   .
079. //+------------------------------------------------------------------+
080. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
081. {
082.     static string szNameObj = NULL;
083. 
084.     switch (id)
085.     {
086.         case CHARTEVENT_KEYDOWN         :
087.             if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)))
088.             {
089.                 gl_RZ.RemoveBoxResize(szNameObj);
090.                 szNameObj = NULL;
091.             }
092.             break;
093.         case CHARTEVENT_OBJECT_CLICK    :
094.             Print((ushort)lparam, " x ", (ushort)dparam, " ->> ", sparam);
095.             if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break;
096.             if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj);
097.             if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL;
098.             break;
099.         case CHARTEVENT_OBJECT_DRAG     :
100.             Print("CHARTEVENT_OBJECT_DRAG ->> ", sparam);
101.             break;
102.     }
103.     ChartRedraw();
104. };
105. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 04

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

Обратите внимание, что в строках 94 и 100 мы указываем на вывод какого-то сообщения в терминал. Итак, давайте посмотрим на результат первого, скажем так, обычного выполнения кода. Его можно увидеть в анимации ниже:

Анимация 05

Теперь давайте понаблюдаем за поведением, которого мы хотим избежать. Это показано в анимации 06 ниже:

Анимация 06

В анимациях 05 и 06 мы видим стандартное поведение MetaTrader 5 в действии. Однако с поведением, показанным в анимации 05, мы можем смириться, а с поведением, показанным в анимации 06, — нет. В случае с анимацией 06 пользователь включает и выключает выделение объекта, который он не должен иметь возможности выделять. Мы должны иметь возможность только перемещать объект. Однако у нас не должно быть возможности включать или отключать состояние выделения объекта.

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

                   .
                   .
                   .
007. //+------------------------------------------------------------------+
008. struct st_BoxResize
009. {
                   .
                   .
                   .
036. //+----------------+
037.     public  :
                   .
                   .
                   .
065. //+----------------+
066.         void Block(const string szArg)
067.         {
068.             for (uchar c = 0; c < szObjects.Size(); c++)
069.                 if (szObjects[c] == szArg)
070.                     ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true);
071.         }
072. //+----------------+
073. }gl_RZ;
074. //+------------------------------------------------------------------+
                   .
                   .
                   .
086. //+------------------------------------------------------------------+
087. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
088. {
089.     static string szNameObj = NULL;
090. 
091.     switch (id)
092.     {
093.         case CHARTEVENT_KEYDOWN         :
094.             if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)))
095.             {
096.                 gl_RZ.RemoveBoxResize(szNameObj);
097.                 szNameObj = NULL;
098.             }
099.             break;
100.         case CHARTEVENT_OBJECT_CLICK    :
101.             Print((ushort)lparam, " x ", (ushort)dparam, " ->> ", sparam);
102.             if (StringFind(sparam, def_Prefix) != INVALID_HANDLE)
103.             {
104.                 gl_RZ.Block(sparam);
105.                 break;
106.             }
107.             if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj);
108.             if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL;
109.             break;
110.         case CHARTEVENT_OBJECT_DRAG     :
111.             Print("CHARTEVENT_OBJECT_DRAG ->> ", sparam);
112.             break;
113.     }
114.     ChartRedraw();
115. };
116. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 05

Обратите внимание, как легко реализовать решение первой проблемы. Единственное, что нужно сделать, это внедрить новую процедуру в структуру, что можно увидеть в строке 66. Далее необходимо добавить вызов этой новой процедуры, что и делается в строке 104. И какой результат? Вы можете убедиться сами, посмотрев анимацию ниже:

Анимация 07

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

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

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

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. enum E_Elment   {
009.                     TOP,
010.                     LEFT,
011.                     BOTTOM,
012.                     RIGHT,
013.                     TL,
014.                     BR
015.                 };
016. //+------------------------------------------------------------------+
017. struct st_BoxResize
018. {
019.     private :
020.         ushort  x, y, w, h;
021.         bool    isBack;
022.         string  szObjects[6];
023. //+----------------+
024.         void SetPosition(const E_Elment what)
025.         {
026.             ushort p1 = x, p2 = y, p3 = w, p4 = h;
027. 
028.             switch (what)
029.             {
030.                 case TOP: p4 = 1; break;
031.                 case LEFT: p3 = 1; break;
032.                 case BOTTOM: p2 = y + h; p4 = 1; break;
033.                 case RIGHT: p1 = x + w; p3 = 1; break;
034.                 case BR: p1 += p3; p2 += p4;
035.                 case TL: p3 = p4 = 1; break;
036.             }
037.             ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1);
038.             ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2);
039.             ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3);
040.             ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4);
041.         }
042. //+----------------+
043.         void CreateEdge(const E_Elment what)
044.         {
045.             szObjects[what] = macro_NameObject;
046.             ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0);
047.             ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime);
048.             SetPosition(what);
049.         }
050. //+----------------+
051.     public  :
                   .
                   .
                   .
072. //+----------------+
073.         void UpdateBoxSize(const string szArg)
074.         {
075.             for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++)
076.                 if (szObjects[c] == szArg)
077.                 {
078.                     x = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_XDISTANCE);
079.                     y = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_YDISTANCE);
080.                     w = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_XDISTANCE) - x;
081.                     h = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_YDISTANCE) - y;
082.                     for (E_Elment i = 0; i < (E_Elment)4; i++)
083.                         SetPosition(i);
084.                 }
085.         }
086. //+----------------+
                   .
                   .
                   .
100. //+----------------+
101. }gl_RZ;
102. //+------------------------------------------------------------------+
                   .
                   .
                   .
114. //+------------------------------------------------------------------+
115. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
116. {
117.     static string szNameObj = NULL;
118. 
119.     switch (id)
120.     {
                   .
                   .
                   .
137.         case CHARTEVENT_OBJECT_DRAG     :
138.             if (StringFind(sparam, def_Prefix) != INVALID_HANDLE)
139.                 gl_RZ.UpdateBoxSize(sparam);
140.             break;
141.     }
142.     ChartRedraw();
143. };
144. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 06

Я знаю, что код 06 довольно фрагментирован, но мы так сделали намеренно, потому что здесь хотим подчеркнуть только те части, которые действительно нуждаются в реализации. Обратите внимание, что для упрощения ряда моментов мы создали перечисление в строке 08. Это, в свою очередь, облегчит реализацию двух последних шагов. Мы также разделили на два блока часть, посвященную созданию объектов, формирующих ограничивающий прямоугольник. Один из них — процедура SetPosition, которую можно увидеть в строке 24. Другой этап — это непосредственно создание сторон ограничивающего прямоугольника. В данном случае эта процедура находится в строке 43.

Здесь важно именно появление процедуры UpdateBoxSize, которая находится в строке 73. Данная процедура, по сути, перестроит ограничивающий прямоугольник в новом положении, когда событие CHARTEVENT_OBJECT_DRAG вызовет функцию, показанную в строке 139.

Как бы то ни было, в итоге цель, которую мы хотим достичь, можно увидеть ниже.

Анимация 08

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

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

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. enum E_Elment   {
009.                     TOP,
010.                     LEFT,
011.                     BOTTOM,
012.                     RIGHT,
013.                     TL,
014.                     BR
015.                 };
016. //+------------------------------------------------------------------+
017. struct st_BoxResize
018. {
019.     private :
020.         ushort  x, y, w, h;
021.         bool    isBack;
022.         string  szObjects[6];
023. //+----------------+
024.         void SetPosition(const E_Elment what)
025.         {
026.             ushort p1 = x, p2 = y, p3 = w, p4 = h;
027. 
028.             switch (what)
029.             {
030.                 case TOP: p4 = 1; break;
031.                 case LEFT: p3 = 1; break;
032.                 case BOTTOM: p2 = y + h; p4 = 1; break;
033.                 case RIGHT: p1 = x + w; p3 = 1; break;
034.                 case BR: p1 += p3; p2 += p4;
035.                 case TL: p3 = p4 = 1; break;
036.             }
037.             ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1);
038.             ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2);
039.             ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3);
040.             ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4);
041.         }
042. //+----------------+
043.         void CreateEdge(const E_Elment what)
044.         {
045.             szObjects[what] = macro_NameObject;
046.             ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0);
047.             ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime);
048.             SetPosition(what);
049.         }
050. //+----------------+
051.     public  :
052.         void CreateBoxResize(const string szArg, const uchar adjust = 2)
053.         {
054.             isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK);
055.             ObjectSetInteger(0, szArg, OBJPROP_BACK, true);
056.             ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false);
057.             x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust;
058.             y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust;
059.             w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust;
060.             h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust;
061.             for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++)
062.             {
063.                 CreateEdge(c);
064.                 if ((c == TL) || (c == BR))
065.                 {
066.                     ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3);
067.                     ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true);
068.                     ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true);
069.                 }
070.             }
071.         }
072. //+----------------+
073.         void UpdateBoxSize(const string szArg)
074.         {
075.             for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++)
076.                 if (szObjects[c] == szArg)
077.                 {
078.                     x = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_XDISTANCE);
079.                     y = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_YDISTANCE);
080.                     w = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_XDISTANCE) - x;
081.                     h = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_YDISTANCE) - y;
082.                     for (E_Elment i = 0; i < (E_Elment)4; i++)
083.                         SetPosition(i);
084.                 }
085.         }
086. //+----------------+
087.         void RemoveBoxResize(const string szArg, const bool update)
088.         {
089.             ObjectSetInteger(0, szArg, OBJPROP_BACK, isBack);
090.             ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false);
091.             if (update)
092.             {
093.                 ObjectSetInteger(0, szArg, OBJPROP_XDISTANCE, x);
094.                 ObjectSetInteger(0, szArg, OBJPROP_YDISTANCE, y);
095.                 ObjectSetInteger(0, szArg, OBJPROP_XSIZE, w);
096.                 ObjectSetInteger(0, szArg, OBJPROP_YSIZE, h);
097.             }
098.             ObjectsDeleteAll(0, def_Prefix);
099.         }
100. //+----------------+
101.         void Block(const string szArg)
102.         {
103.             for (uchar c = 0; c < szObjects.Size(); c++)
104.                 if (szObjects[c] == szArg)
105.                     ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true);
106.         }
107. //+----------------+
108. }gl_RZ;
109. //+------------------------------------------------------------------+
110. int OnInit()
111. {
112.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
113. 
114.     return INIT_SUCCEEDED;
115. };
116. //+------------------------------------------------------------------+
117. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
118. {
119.     return rates_total;
120. };
121. //+------------------------------------------------------------------+
122. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
123. {
124.     static string szNameObj = NULL;
125. 
126.     switch (id)
127.     {
128.         case CHARTEVENT_KEYDOWN         :
129.             if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)))
130.             {
131.                 gl_RZ.RemoveBoxResize(szNameObj, false);
132.                 szNameObj = NULL;
133.             }
134.             break;
135.         case CHARTEVENT_OBJECT_CLICK    :
136.             if (StringFind(sparam, def_Prefix) != INVALID_HANDLE)
137.             {
138.                 gl_RZ.Block(sparam);
139.                 break;
140.             }
141.             if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj, true);
142.             if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL;
143.             break;
144.         case CHARTEVENT_OBJECT_DRAG     :
145.             if (StringFind(sparam, def_Prefix) != INVALID_HANDLE)
146.                 gl_RZ.UpdateBoxSize(sparam);
147.             break;
148.     }
149.     ChartRedraw();
150. };
151. //+------------------------------------------------------------------+
152. void OnDeinit(const int reason)
153. {
154.     ObjectsDeleteAll(0, def_Prefix);
155.     ChartRedraw();
156. };
157. //+------------------------------------------------------------------+

Код 07

В данном случае я не буду объяснять, что происходит в коде 07. Я хочу, чтобы вы попытались изучить его и таким образом понять, как этот код, на первый взгляд бессмысленный и многим воспринимаемый как плод воображения какого-то гениального безумца, умудряется делать то, что показано в анимациях ниже. Во-первых, стандартное изменение размеров:

Анимация 09

Теперь — комбинированное изменение размеров с другим, которое впоследствии будет отменено.

Анимация 10


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

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

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

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

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

Возможно, в следующей статье мы покажем другие способы дальнейшего улучшения рассмотренного здесь кода, и которые будут доступны в приложенном файле. Всё будет зависеть от того, добавит ли представленный материал что-то к тому, что было рассмотрено здесь, или нет. Как бы то ни было, не ждите, что я покажу вам, как улучшить положение дел. Вы должны найти решение самостоятельно, и если у вас не получается реализовать всё с самого начала, постарайтесь изучить то, что уже рассмотрели до этого момента. Для усвоения этого материала требуется время и практика.

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

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

Прикрепленные файлы |
Anexo.zip (2.94 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Торговые инструменты MQL5 (Часть 28): Полигональная заливка кривой-бабочки в MQL5 Торговые инструменты MQL5 (Часть 28): Полигональная заливка кривой-бабочки в MQL5
Мы расширяем возможности холста (canvas) для отображения кривой-бабочки в MetaTrader 5, добавляя многослойную заливку крыльев, жилки крыла, точки текстуры чешуек и изображение всего тела (брюшко, грудь, голова, глаза, усики). В этой статье реализованы полигональные заливки с вертикальными и радиальными градиентами, а также залитые круги и эллипсы, все с использованием сглаживания методом суперсэмплинга. Вы также получите многоразовые вспомогательные функции MQL5 и порядок рендеринга, который преобразует простую кривую в настраиваемую, детализированную иллюстрацию на графике.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Почему MetaTrader 5 подходит для AI-торговли: MQL5 + Python + ONNX + AI Assistant как экосистема алготрейдинга Почему MetaTrader 5 подходит для AI-торговли: MQL5 + Python + ONNX + AI Assistant как экосистема алготрейдинга
MetaTrader 5 подходит для AI-торговли, потому что объединяет рыночные данные, MQL5-разработку, Python-исследования, ONNX-модели, Strategy Tester, VPS и экосистему MQL5.community в одном рабочем процессе. Статья показывает практический путь от AI-подсказки на графике к структурированному сигналу, работе с кодом через AI Assistant в MetaEditor, модели качества, созданию советнику, тестированию и контролируемому запуску торговой системы.