От начального к среднему уровню: Объекты и подокна (III)
Введение
В предыдущей статье «От начального к среднему уровню: Объекты и подокна (II)» мы рассмотрели, как можно самым простым и практичным способом обработать ситуацию, когда какой-либо объект, размещённый нашим кодом на графике, был удалён — намеренно или по ошибке оператора либо пользователя. Однако такой подход в конечном итоге может вызвать недовольство у некоторых пользователей. По их мнению, удаление объекта с графика не должно приводить к удалению всей программы с графика.
Объекты и подокна (III)
Хотя теоретически цель здесь заключается в том, чтобы сохранить целостность подокна и размещённых в нём объектов, то, что мы увидим, выходит далеко за рамки этой темы. Этот подход можно использовать даже в том случае, если вы не используете подокна и просто хотите, чтобы тот или иной объект оставался на графике, пока приложение всё ещё активно. По этой причине мы начнём с чего-то более простого, а затем расширим эту концепцию до случая, когда нам понадобятся подокна.
Итак, до предыдущей статьи мы разрабатывали концепцию, призванную помочь вам создавать довольно сложные приложения с минимальными усилиями. Это связано с тем, что в MetaTrader 5 индикаторы являются основой целого ряда элементов, которые в конечном итоге приводят к тому, что многие знают как советники. Мы пока не начали говорить о советниках именно потому, что без индикаторов такие программы мало на что пригодны. Однако, если у нас есть прочная и хорошо выстроенная основа и мы понимаем, что можем, а чего не можем делать в рамках возможностей, предоставляемых индикаторами, создание и разработка советника становится одной из самых простых задач.
Однако, если мы заранее не создали эту основу, всё, что связано с советниками, станет запутанным и будет иметь очень мало смысла. Хотя вам может показаться, что всё это — пустая трата времени, понимание того, как действовать в различных ситуациях, гораздо важнее, чем писать код без чёткого планирования. В конце концов, вы окажетесь в ситуации, которую не сможете разрешить, именно потому, что вам не хватило терпения или необходимого понимания, чтобы спланировать то, что вы собираетесь реализовать.
Итак, исходя из того, что мы уже рассмотрели, мы получаем следующую ситуацию: нам удалось создать индикатор, который можно использовать как в подокне, так и вне его, просто интегрировав его со вторым индикатором. Таким образом, первый индикатор, который раньше был основным, становится вспомогательным. Поэтому нам не нужно выполнять всю работу сразу. Мы можем разбить это на небольшие этапы, более простые и удобные в работе, а уже потом объединить всё в один более крупный и сложный блок.
Исходя из этого принципа, можно предположить следующее: приведённый ниже код можно проанализировать, адаптировать под наши потребности, а затем интегрировать в гораздо более крупную систему.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\C_MiniChart.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_height 150 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. C_MiniChart *gl_MiniChart; 13. //+------------------------------------------------------------------+ 14. int OnInit(void) 15. { 16. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 17. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true); 18. gl_MiniChart = new C_MiniChart("Mini Chart"); 19. 20. return INIT_SUCCEEDED; 21. }; 22. //+------------------------------------------------------------------+ 23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 24. { 25. return rates_total; 26. }; 27. //+------------------------------------------------------------------+ 28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 29. { 30. switch(id) 31. { 32. case CHARTEVENT_OBJECT_DELETE: 33. if(gl_MiniChart == sparam) 34. { 35. ChartIndicatorDelete(0, ChartWindowFind(0, def_ShortName), def_ShortName); 36. OnDeinit(REASON_INITFAILED); 37. } 38. break; 39. } 40. }; 41. //+------------------------------------------------------------------+ 42. void OnDeinit(const int reason) 43. { 44. delete gl_MiniChart; 45. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false); 46. }; 47. //+------------------------------------------------------------------+ 48. //+------------------------------------------------------------------+ 49.
Код 01
Если вы запустите этот код 01 и удалите созданный им объект, то заметите, что сам код также будет удалён с графика. Об этом уже говорилось и объяснялось в предыдущей статье. Поэтому я не вижу необходимости подробнее останавливаться на работе этого кода 01. Однако здесь мы подойдём к этой же ситуации иначе. То есть вместо того, чтобы просто удалять с графика сам индикатор или любую другую программу, размещённую на графике, мы сделаем так, чтобы удалённый объект снова появился, тем самым полностью изменив подход к решению проблемы.
Для начала я хочу, чтобы вы поняли следующее: чем больше вы хотите, чтобы всё вернулось в состояние, в котором оно находилось до того, как всё было разобрано, тем больше усилий потребуется, чтобы гарантировать, что все необходимые данные действительно будут доступны, когда потребуется воссоздать то, что было уничтожено. В некоторых случаях мы сможем использовать простые структуры данных, а в других придётся прибегнуть к небольшой базе данных. К счастью, в обоих случаях MQL5 предоставляет все ресурсы и механизмы, необходимые для выполнения соответствующего анализа и внесения нужных исправлений. Вопрос лишь в одном: сколько усилий вы готовы приложить, чтобы решить эту проблему?
Хорошо, если ставить вопрос так, нам придётся с чего-то начать. И начнём с того, что потребует наименьших усилий. Когда я говорю о наименьших усилиях, я имею в виду, что любые изменения или настройки, внесённые пользователем или оператором, будут полностью утрачены. Это заставит пользователя заново внести все эти изменения или выполнить все эти настройки.
Это решение, хотя и может показаться несколько радикальным, всё же лучше, чем альтернатива, которую мы рассматривали в предыдущей статье или которая показана в этом коде 01. Итак, чтобы этот код 01 не удалялся с графика при удалении созданного им объекта, нам нужно внести в него совсем небольшое изменение, хотя оно полностью изменит поведение кода. Это изменение показано ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\C_MiniChart.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_height 150 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. C_MiniChart *gl_MiniChart; 13. //+------------------------------------------------------------------+ 14. void Initilize(void) 15. { 16. gl_MiniChart = new C_MiniChart("Mini Chart"); 17. ChartRedraw(); 18. } 19. //+------------------------------------------------------------------+ 20. int OnInit(void) 21. { 22. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 23. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true); 24. Initilize(); 25. 26. return INIT_SUCCEEDED; 27. }; 28. //+------------------------------------------------------------------+ 29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 30. { 31. return rates_total; 32. }; 33. //+------------------------------------------------------------------+ 34. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 35. { 36. switch(id) 37. { 38. case CHARTEVENT_OBJECT_DELETE: 39. if(gl_MiniChart == sparam) 40. Initilize(); 41. break; 42. } 43. }; 44. //+------------------------------------------------------------------+ 45. void OnDeinit(const int reason) 46. { 47. delete gl_MiniChart; 48. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false); 49. }; 50. //+------------------------------------------------------------------+ 51. //+------------------------------------------------------------------+ 52.
Код 02
Этот код 02 решает часть нашей проблемы. Если вы следили за статьями этой серии, не думаю, что у вас возникнут трудности с пониманием того, что мы здесь делаем. Достаточно посмотреть на этот код 02 и проанализировать его, чтобы сразу понять, какова его цель. Однако, хотя этот код 02 и работает, в нём есть несколько небольших недочётов. Чтобы это понять, сначала мне нужно, чтобы вы посмотрели, как проводится большая часть тестов. Большинство начинающих программистов просто делают то, что показано в следующей анимации.

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

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

Изображение 01
Но как такое возможно? Откуда взялась эта ошибка? Я не вижу ничего неправильного в коде 02, поскольку, по сути, мы лишь немного изменили код 01. Я не понимаю, почему появилась эта ошибка. Итак, уважаемый читатель, эта ошибка демонстрирует одну из причин, по которой MQL5 НЕ ПОДДЕРЖИВАЕТ ОПЕРАЦИИ С УКАЗАТЕЛЯМИ. Указатели — замечательный инструмент, но, если не понимать их как следует, они превращаются в камень в ботинке. В предыдущих фрагментах кода у нас есть указатель, объявленный в двенадцатой строке. Однако он инициализируется только при использовании оператора new. А теперь обратите внимание: если, и только если, инициализация происходила бы лишь один раз, то есть в момент выполнения обработчика OnInit, сообщение, выделенное на изображении 01, НЕ ПОЯВИЛОСЬ БЫ.
Однако в коде 02 указатель может инициализироваться более одного раза. "Ах! Как? Я всегда думал, что одна переменная не может быть связана с двумя разными областями памяти". И действительно, так и не может быть. Проблема в том, что при выполнении строки двадцать четыре этого кода 02 происходит первая инициализация указателя. Но если пользователь удалит объект с графика, произойдёт повторная инициализация того же указателя. Это происходит из-за строки сорок. Именно из-за этой второй инициализации связанная с ним область памяти фактически не будет освобождена при выполнении строки 47. По этой причине появляется сообщение, выделенное на изображении 01, которое указывает на то, что здесь что-то не так.
Но как мы можем избежать подобных проблем? По сути, есть два решения. Первое — не использовать указатели в вашем коде. Однако это существенно ограничивает возможности и даже может сделать ваш код менее понятным по мере его роста. Второе решение состоит в том, чтобы перед инициализацией указателя гарантировать, что он свободен от любых предыдущих ссылок. На мой взгляд, это лучшее решение. Поэтому мы применим его, чтобы устранить проблему, на которую указывает сообщение, выделенное на изображении 01. Однако в этом коде 02 есть и вторая проблема. Эта вторая проблема в некотором смысле менее вредна для системы. Но в зависимости от того, что именно реализуется, она может доставить нам немало головной боли. И эта проблема находится внутри процедуры OnDeinit. А теперь остановитесь и немного подумайте. В строке сорок семь этого кода 02 мы удаляем объект с графика. Верно? В этом нет ничего плохого.
Однако обратите внимание, что, удаляя этот объект, мы по-прежнему просим MetaTrader 5 уведомлять нас, когда с графика удаляется какой-либо объект. По этой причине в очередь событий MetaTrader 5 добавит событие, указывающее на то, что объект был удалён с графика. Следовательно, если выполнение кода займёт немного больше времени, будет выполнена сороковая строка, из-за чего объект, уничтоженный потому, что выполнение кода завершалось, будет создан заново. Во многих случаях этого может и не произойти, но в других такое вполне возможно. Поэтому нам нужно исправить это, прежде чем поведение начнёт выходить из-под нашего контроля. После этого анализа мы можем изменить код 02 и получить версию, показанную ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\C_MiniChart.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_height 150 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. C_MiniChart *gl_MiniChart; 13. //+------------------------------------------------------------------+ 14. void Initilize(void) 15. { 16. delete gl_MiniChart; 17. gl_MiniChart = new C_MiniChart("Mini Chart"); 18. ChartRedraw(); 19. } 20. //+------------------------------------------------------------------+ 21. int OnInit(void) 22. { 23. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 24. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true); 25. Initilize(); 26. 27. return INIT_SUCCEEDED; 28. }; 29. //+------------------------------------------------------------------+ 30. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 31. { 32. return rates_total; 33. }; 34. //+------------------------------------------------------------------+ 35. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 36. { 37. switch(id) 38. { 39. case CHARTEVENT_OBJECT_DELETE: 40. if(gl_MiniChart == sparam) 41. Initilize(); 42. break; 43. } 44. }; 45. //+------------------------------------------------------------------+ 46. void OnDeinit(const int reason) 47. { 48. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false); 49. delete gl_MiniChart; 50. }; 51. //+------------------------------------------------------------------+
Код 03
Эти простые изменения, внесённые в код 03, позволят избежать упомянутых выше проблем. Теперь давайте посмотрим, как код отреагирует на удаление объекта с графика. Это можно увидеть в следующей анимации.

Анимация 03
Просто идеально. Всё сработало отлично, без каких-либо видимых сбоев. Теперь обратите внимание: даже после того, как вы переместили объект по экрану, он вернулся в исходное положение. Такова цена выбора решения, требующего минимальных усилий, как я упоминал в начале статьи. При этом мы решили игнорировать любые настройки или изменения, которые могли быть внесены, тем самым вынуждая пользователя заново делать всё с самого начала. Что ж, это невысокая цена, поскольку индикатор продолжает оставаться активным на графике, в отличие от того, что мы делали раньше, когда просто удаляли индикатор с графика.
В любом случае, мы можем немного улучшить это и сделать работу пользователя или оператора немного комфортнее. Однако, решая сделать это, мы должны учитывать один важный момент: чем точнее мы захотим воссоздать состояние объекта, чтобы пользователь не заметил разницы между состоянием до и после его удаления с графика, тем больше усилий для этого потребуется. Поэтому мы лишь немного увеличим усилия, поскольку цель здесь исключительно учебная. Что ж, теперь пришло время принять некоторые решения.
Поскольку я хочу, чтобы всё было достаточно просто, и в то же время дать вам представление о том, как выполнить более полное воссоздание, мы поступим следующим образом: сохраним положение и размеры графического объекта. И хотя мы собираемся сделать только это, вы заметите, что усилия, необходимые для правильной реализации такого поведения, будут гораздо больше, чем всё то, что мы видели до сих пор. Для начала нам нужно будет изменить исходный код класса. Исходный вариант показан ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "defines.mqh" 05. //+------------------------------------------------------------------+ 06. class C_MiniChart 07. { 08. private : 09. //+----------------+ 10. string m_szNameObject; 11. int m_Sub; 12. //+----------------+ 13. void SetInteger(ENUM_OBJECT_PROPERTY_INTEGER prop, int value) { ObjectSetInteger(0, m_szNameObject, prop, value); } 14. //+----------------+ 15. int WhatSubWin(void) 16. { 17. for(int c0 = 0; c0 < (int)(ChartGetInteger(0, CHART_WINDOWS_TOTAL)); c0++) 18. for(int c1 = (ChartIndicatorsTotal(0, c0) - 1); c1 >= 0; c1--) 19. if(ChartIndicatorName(0, c0, c1) == def_ShortName) 20. return c0; 21. return INVALID_HANDLE; 22. } 23. //+----------------+ 24. public : 25. //+----------------+ 26. C_MiniChart(string szName) 27. { 28. if((m_Sub = WhatSubWin()) == INVALID_HANDLE) 29. return; 30. ObjectCreate(0, m_szNameObject = def_ShortName + "#" + szName + "#" + (string)ObjectsTotal(0, -1, -1), OBJ_CHART, m_Sub, 0, 0); 31. ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol); 32. SetInteger(OBJPROP_XDISTANCE, 10); 33. SetInteger(OBJPROP_YDISTANCE, 0); 34. SetInteger(OBJPROP_XSIZE, 300); 35. SetInteger(OBJPROP_YSIZE, indicator_height); 36. SetInteger(OBJPROP_PERIOD, PERIOD_M15); 37. SetInteger(OBJPROP_DATE_SCALE, false); 38. SetInteger(OBJPROP_PRICE_SCALE, false); 39. SetInteger(OBJPROP_SELECTABLE, true); 40. SetInteger(OBJPROP_SELECTED, false); 41. } 42. //+----------------+ 43. ~C_MiniChart() 44. { 45. ObjectDelete(0, m_szNameObject); 46. ChartRedraw(); 47. } 48. //+----------------+ 49. bool operator==(const string szArg) { return (szArg == m_szNameObject); } 50. //+----------------+ 51. }; 52. //+------------------------------------------------------------------+
Код 04
Хорошо, поскольку я хочу, чтобы вы могли поэкспериментировать с этим без необходимости набирать этот код вручную, в приложенном файле изменённая версия будет иметь другое имя. Это сделано исключительно для того, чтобы облегчить её идентификацию и дать вам возможность также проводить тесты в вашей локальной среде. Хорошо, давайте начнём с создания механизма для хранения положения объекта. Позже вы поймёте, почему этот механизм следует рассматривать отдельно от остального кода. Таким образом, изменив код предыдущего класса, мы получим то, что показано ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "defines.mqh" 05. //+------------------------------------------------------------------+ 06. class C_MiniChart 07. { 08. private : 09. //+----------------+ 10. string m_szNameObject; 11. int m_Sub; 12. //+----------------+ 13. struct st_Memory 14. { 15. short x, y, w, h; 16. bool selectable; 17. } m_Memory; 18. //+----------------+ 19. void SetInteger(ENUM_OBJECT_PROPERTY_INTEGER prop, int value) { ObjectSetInteger(0, m_szNameObject, prop, value); } 20. //+----------------+ 21. int WhatSubWin(void) 22. { 23. for(int c0 = 0; c0 < (short)(ChartGetInteger(0, CHART_WINDOWS_TOTAL)); c0++) 24. for(int c1 = (ChartIndicatorsTotal(0, c0) - 1); c1 >= 0; c1--) 25. if(ChartIndicatorName(0, c0, c1) == def_ShortName) 26. return c0; 27. return INVALID_HANDLE; 28. } 29. //+----------------+ 30. public : 31. //+----------------+ 32. C_MiniChart(string szName) 33. { 34. if((m_Sub = WhatSubWin()) == INVALID_HANDLE) 35. return; 36. m_Memory.x = 10; 37. m_Memory.y = 0; 38. m_Memory.w = 300; 39. m_Memory.h = indicator_height; 40. m_Memory.selectable = true; 41. m_szNameObject = def_ShortName + "#" + szName + "#" + (string)ObjectsTotal(0, -1, -1); 42. RestoreMemory(); 43. ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol); 44. SetInteger(OBJPROP_PERIOD, PERIOD_M15); 45. SetInteger(OBJPROP_DATE_SCALE, false); 46. SetInteger(OBJPROP_PRICE_SCALE, false); 47. SetInteger(OBJPROP_SELECTED, false); 48. } 49. //+----------------+ 50. ~C_MiniChart() 51. { 52. ObjectDelete(0, m_szNameObject); 53. ChartRedraw(); 54. } 55. //+----------------+ 56. void RestoreMemory(void) 57. { 58. if(ObjectFind(0, m_szNameObject) < 0) 59. ObjectCreate(0, m_szNameObject, OBJ_CHART, m_Sub, 0, 0); 60. SetInteger(OBJPROP_XDISTANCE, m_Memory.x); 61. SetInteger(OBJPROP_YDISTANCE, m_Memory.y); 62. SetInteger(OBJPROP_XSIZE, m_Memory.w); 63. SetInteger(OBJPROP_YSIZE, m_Memory.h); 64. SetInteger(OBJPROP_SELECTABLE, m_Memory.selectable); 65. } 66. //+----------------+ 67. void StoreMemory(void) 68. { 69. m_Memory.x = (short) ObjectGetInteger(0, m_szNameObject, OBJPROP_XDISTANCE); 70. m_Memory.y = (short) ObjectGetInteger(0, m_szNameObject, OBJPROP_YDISTANCE); 71. m_Memory.w = (short) ObjectGetInteger(0, m_szNameObject, OBJPROP_XSIZE); 72. m_Memory.h = (short) ObjectGetInteger(0, m_szNameObject, OBJPROP_YSIZE); 73. m_Memory.selectable = (bool) ObjectGetInteger(0, m_szNameObject, OBJPROP_SELECTABLE); 74. } 75. //+----------------+ 76. bool operator==(const string szArg) { return (szArg == m_szNameObject); } 77. //+----------------+ 78. }; 79. //+------------------------------------------------------------------+
Код 05
Итак, теперь у нас в руках действительно необычный код. Это связано с тем, что код 05 работает так же, как и код 04. Однако он содержит небольшой блок внутренней памяти, предназначенный для локального использования. И это позволяет коду 05 восстанавливать настройки, которые могли быть утрачены. Это произойдёт, если пользователь удалит объект с графика, пока индикатор остаётся активным. Если возникает именно такая ситуация, индикатор сможет использовать данные, сохранённые во внутренней памяти объекта, чтобы воссоздать его с ранее сохранёнными свойствами.
Итак, если вас заинтересовало то, что я только что сказал, обратите внимание на то, как работает этот механизм. Потому что, хотя он и очень прост, при его использовании в каком-либо коде требуется определённая осторожность.
Как я уже упоминал, для этих данных нам нужен отдельный участок памяти. Он создаётся в строке тринадцать этого кода 05. Но, возможно, вы задаётесь вопросом: хорошо, эта память на самом деле не является отдельной. Она находится внутри класса, который создаёт объект. Не вызовет ли это каких-нибудь проблем в дальнейшем? Нет, если вы понимаете, как на самом деле всё работает при выполнении кода. Если вы просто думаете, что уже понимаете, что делаете, то, скорее всего, допустите какую-нибудь ошибку. Но если вы действительно поймёте то, что будет объяснено далее, всё будет работать отлично.
После создания этой структуры памяти нам нужно будет инициализировать её между строками тридцать пять и тридцать девять. Обратите внимание, что эти строки находятся внутри конструктора класса. Сделать это именно в этот момент очень важно. Точно так же важно, чтобы имя объекта было задано в этом конструкторе. Теперь обратите внимание, что МЫ НЕ СОЗДАЁМ ОБЪЕКТ В КОНСТРУКТОРЕ. Это связано с тем, что, если бы мы создали его здесь, нам было бы трудно понять, когда и где объект был создан на самом деле.
Таким образом, после инициализации структуры памяти мы можем вызвать процедуру, которая создаст объект или, точнее, снова поместит его на график. За это отвечает процедура, реализованная в строке пятьдесят пять. Эта процедура делает в точности то же самое, что и конструктор в коде 04, за единственным исключением: в строке пятьдесят семь мы проверяем, находится ли объект, который мы хотим снова поместить на график, в списке объектов или нет. Но зачем выполнять эту проверку? Дело в том, что в зависимости от обстоятельств могли произойти какие-то изменения, и место удалённого объекта мог занять другой объект. Пока не беспокойтесь об этом, так как это будет темой другой статьи. Итак, давайте продолжим разбираться в том, что происходит.
После восстановления данных во время выполнения конструктора класса объект уже будет готов к работе и будет иметь все ранее заданные настройки. Но прежде чем перейти к основному коду, давайте рассмотрим ещё одну процедуру, добавленную в класс. Она находится в строке шестьдесят пять. Назначение этой процедуры очень простое. Она будет получать данные, которые мы хотим восстановить, и сохранять их в структуре памяти объекта, чтобы сохранить конфигурацию объекта хотя бы временно. Это самый простой и базовый код, который можно реализовать для восстановления объекта, удалённого пользователем. Теперь давайте посмотрим, как пришлось изменить основной код, поскольку он не совсем такой же, как в коде 03. Тем не менее, он очень похож, как вы можете видеть ниже, где приведён полный код.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\C_MiniChart_2.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_height 150 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. C_MiniChart *gl_MiniChart; 13. //+------------------------------------------------------------------+ 14. void Initilize(void) 15. { 16. if(gl_MiniChart == NULL) 17. gl_MiniChart = new C_MiniChart("Mini Chart"); 18. else(*gl_MiniChart).RestoreMemory(); 19. ChartRedraw(); 20. } 21. //+------------------------------------------------------------------+ 22. int OnInit(void) 23. { 24. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 25. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true); 26. gl_MiniChart = NULL; 27. Initilize(); 28. 29. return INIT_SUCCEEDED; 30. }; 31. //+------------------------------------------------------------------+ 32. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 33. { 34. return rates_total; 35. }; 36. //+------------------------------------------------------------------+ 37. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 38. { 39. switch(id) 40. { 41. case CHARTEVENT_OBJECT_DELETE: 42. if(gl_MiniChart == sparam) 43. Initilize(); 44. break; 45. case CHARTEVENT_OBJECT_DRAG: 46. if(gl_MiniChart == sparam) 47. (*gl_MiniChart).StoreMemory(); 48. break; 49. } 50. }; 51. //+------------------------------------------------------------------+ 52. void OnDeinit(const int reason) 53. { 54. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false); 55. delete gl_MiniChart; 56. }; 57. //+------------------------------------------------------------------+
Код 06
Этот код 06 действительно очень изящен, хотя в нём не хватает одной детали. Однако, прежде чем исправить эту деталь, я хочу, чтобы вы посмотрели на этот код и полюбовались им, ведь он способен вернуть кое-что из мира мёртвых. На самом деле объект возвращается несколько деформированным, но это связано с тем, что мы сохраняем не все свойства объекта, а только некоторые. Поэтому, чтобы понять код 06, сначала нужно разобраться в предыдущих кодах, включая то, что объяснялось в других статьях этой же серии.
В частности, в статье "От начального к среднему уровню: события в объектах (IV)", поскольку в ней я объясняю, как работать с простыми событиями в таких же простых объектах. Конечно, возможно, вам также понадобится обратиться и к предыдущим статьям. Но это решение я оставляю на ваше усмотрение.
Важная деталь: указателям НЕЛЬЗЯ напрямую присваивать произвольные значения, как если бы это были адреса памяти. Они всегда должны указывать на какую-либо область памяти.
Однако, несмотря на эту особенность, указателю можно присвоить значение NULL, что позволяет определить его как нулевой указатель. Эта ситуация чрезвычайно важна для нас. Это связано с тем, что, в отличие от того, как мы поступали ранее в процедуре `Initialize`, теперь мы не будем удалять объект, на который указывает указатель, при повторном вызове процедуры, если процедура будет вызвана во второй раз, а это будет происходить всякий раз, когда объект удаляется с графика. И снова: чтобы понять, как работает этот код 06, необходимо разобраться в предыдущих кодах.
Таким образом, в строке 16 мы выполняем проверку, цель которой — определить, является указатель действительным или нет. Поскольку при первом вызове он считается недействительным, условие выполнится, память будет выделена с помощью оператора new, и указатель начнет ссылаться на эту область памяти. А теперь переходим к части, которая многим кажется непонятной. Что происходит, когда строка 41 снова вызывает процедуру из строки 14? Итак, в этом случае объект уже будет удалён с графика. Однако объект или блок памяти, на который указывает указатель, ещё не будет освобождён. Этот блок памяти будет освобождён только при выполнении оператора delete, а это происходит только в строке 52.
Теперь обратите внимание: поскольку строка 51 отключает уведомление о событии, связанном с удалением объекта с графика, MetaTrader 5 перестанет генерировать событие, которое привело бы к повторному выполнению строки 41. Очень важно выполнять эти действия именно в таком порядке. В противном случае при повторном выполнении проверки в строке 16 мы получим недействительный указатель, и будет создан новый. Однако, поскольку этого не произойдёт при нормальном выполнении кода, при выполнении сорок первой строки проверка в шестнадцатой строке приведёт к тому, что выполнение продолжится с семнадцатой строки. И эта строка приведёт к восстановлению данных объекта. Однако, поскольку объект больше не находится на графике, класс принудительно создаст новый объект с тем же именем, как видно из 57-й строки кода 05.

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

Анимация 05
Однако, и это важно: если после изменения свойств вы переместите объект, изменения сохранятся, как показано в следующей анимации.

Анимация 06
Хм, интересно. Но разве нет способа изменить свойства и сохранить новые значения, не перемещая объект по графику? Да, мой друг, такой способ есть. Но прежде чем показать его, давайте посмотрим еще одну анимацию, в которой показано, что происходит при удалении приложения, которое поддерживает присутствие объекта на графике. Эта анимация показана ниже.

Анимация 07
Обратите внимание, что ни в одной из анимаций не было никаких признаков проблем в коде. Это отличный знак. Итак, давайте посмотрим, как решить проблему, показанную в анимации 05, где после изменения свойств они были потеряны именно потому, что не были сохранены в памяти. Чтобы решить эту проблему, нам нужно всего лишь добавить одну строку кода. Ее цель — перехватить именно то событие, которое MetaTrader 5 генерирует, чтобы уведомить нас об изменении какого-либо свойства объекта. Поскольку код остается практически тем же, мы можем сосредоточиться только на том фрагменте, который действительно нужно изменить. Он показан ниже.
. . . 35. //+------------------------------------------------------------------+ 36. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 37. { 38. switch(id) 39. { 40. case CHARTEVENT_OBJECT_DELETE: 41. if(gl_MiniChart == sparam) 42. Initilize(); 43. break; 44. case CHARTEVENT_OBJECT_CHANGE: 45. case CHARTEVENT_OBJECT_DRAG: 46. if(gl_MiniChart == sparam) 47. (*gl_MiniChart).StoreMemory(); 48. break; 49. } 50. }; 51. //+------------------------------------------------------------------+ . . .
Фрагмент 01
И это всё, уважаемый читатель. Единственное, что нам нужно сделать, чтобы исправить то, что показано в анимации 05, — это добавить строку case CHARTEVENT_OBJECT_CHANGE: (в приведённом листинге это строка 44). Таким образом, когда MetaTrader 5 сгенерирует уведомление о том, что какое-либо свойство объекта было изменено, наш код сможет перехватить это уведомление и обеспечить обновление данных, хранящихся в памяти. Таким образом, даже если после изменения свойств объект будет удалён без перемещения, наше приложение сможет воссоздать его с сохранёнными свойствами. Помните, что для восстановления объекта, идентичного тому, который существовал ранее, нам придётся сохранить гораздо больше свойств. Но, поскольку здесь я хочу лишь показать, как мы могли бы это сделать, я доволен полученным результатом.
Заключительные замечания
Хотя мы и не перенесли индикатор, представленный в этой статье, в подокно путём его интеграции с каким-либо другим индикатором, как мы делали в предыдущей статье, то, что мы здесь рассмотрели и объяснили, можно считать ключевым моментом для понимания того, как мы решим вторую часть другой задачи: обеспечить, чтобы индикатор, находящийся в подокне и интегрированный с другим индикатором, не удалялся только потому, что с графика был удалён какой-либо из объектов, которые он создаёт и поддерживает.
Хотя при соотнесении того, что мы здесь рассмотрели, с тем, что было объяснено в предыдущей статье, может показаться довольно простым, но существует, так сказать, небольшая хитрость, позволяющая избежать поведения, отличного от ожидаемого. Одно дело — удалить объект с графика и снова разместить его на нём. А совсем другое — восстановить индикатор целиком, если по какой-либо причине он был удалён с графика. Обе ситуации требуют слегка разных подходов.
| Файл MQ5 | Описание |
|---|---|
| Indicators\Code 01 | Базовая демонстрация |
| Indicators\Code 02 | Базовая демонстрация |
| Indicators\Code 03 | Базовая демонстрация |
| Indicators\Code 04 | Базовая демонстрация |
| Indicators\Code 05 | Базовая демонстрация |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/17398
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Перекрестный рекуррентный количественный анализ (CRQA) в MQL5: Разработка полноценной библиотеки анализа
От начального к среднему уровню: Объекты и подокна (II)
Нейросети в трейдинге: Диффузионная генерация торговых планов (Вычислительная основа)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования