Скачать MetaTrader 5

MQL5 для начинающих: Антивандальная защита графических объектов

13 ноября 2015, 09:46
Dina Paches
6
3 621

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

ВикипедиЯ

Содержание:


1. Введение

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

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

Пример панели управления до изменения свойств ее объектов "вручную" и после

Рис. 1. Пример внешнего вида панели управления до изменения свойств ее объектов вручную и после

Описанные в статье варианты конструирования в коде ответных действий на вмешательство "со стороны" могут оказаться не лишними для таких случаев, когда, например, в сторонней программе, запущенной на графике и не предназначенной непосредственно для его очистки, может применяться функция для удаления объектов (ObjectsDeleteAll() или созданная самостоятельно), производящая по заданным в ней параметрам:

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

Статья может быть полезна и тем, кто только начинает знакомиться с обработкой событий в функции OnChartEvent().

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

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

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


2. Одна из первичных защит объектов в MQL5/MQL4

Без такой защиты доступ к свойствам объектов создаваемой программы был бы более открыт.

Имеется в виду следующее. Для первичной защиты объектов от удаления и изменения через свойства этих объектов их имени, описания, цвета и т.д. имеется и может явно применяться свойство OBJPROP_HIDDEN. Оно устанавливает или снимает запрет на показ имени графического объекта в списке объектов из меню терминала: Графики -> Объекты -> Список объектов. По умолчанию запрет устанавливается для объектов, отображающих события календаря, историю торговли, а также для объектов, созданных из MQL5-программ.

Явная установка запрета, а не по умолчанию, схематично может выглядеть в коде так:

ObjectSetInteger(chart_id,name,OBJPROP_HIDDEN,true);

где:

  • ObjectSetInteger — это функция, задающая значение соответствующего свойства объекта;
  • chart_id это идентификатор графика, где находится объект (ноль означает текущий график);
  • name это имя объекта, по отношению к которому применяется функция;
  • true, в сочетании с OBJPROP_HIDDEN, означает скрытие показа объекта в списке объектов (false отменяет скрытие показа).

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

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

Список объектов на графике до нажатия кнопки "Все"

Рис. 2. Список объектов на графике до нажатия кнопки "Все"

Список объектов на чарте после нажатия кнопки "Все"

Рис. 3. Список объектов на графике после нажатия кнопки "Все"

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

Кроме того, одним из многих достоинств торговых терминалов MetaTrader 5 и MetaTrader 4 можно по праву назвать легкость самостоятельного написания автоматических помощников к этим терминалам и применения многочисленных помощников, созданных другими. А существуют ли люди, никогда ни в чем не ошибающиеся? Да и уровень подготовки при написании программ может быть разный. Как и разными могут быть внутренние этические границы. Язык программирования, как и люди, может со временем меняться, совершенствуясь.

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

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

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


3. Методика создания приводимых здесь вариантов

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

Чтобы не быть слишком академичной в изложении и для избежания "менторского" тона, расскажу о приводимой здесь методике примерно так, как сама к ней пришла. Коды, упоминаемые ниже, прикреплены в конце статьи. Для их работы нужно сохранить у себя в папке Include терминала включаемый файл ObjectCreateAndSet, находящийся в Code Base. Он содержит функции по созданию объектов и изменению их свойств с необходимыми проверками. Этот файл не требуется для работы прилагаемого индикатора test_types_of_chart_events.


3.1. Часть размышлений и мероприятия перед принятием решений о практической реализации в коде

На данный момент, согласно Документации, с помощью функции OnChartEvent() можно получать и обрабатывать для каких-либо своих целей/задач информацию о девяти разновидностях событий, не считая событий в целом по изменениям на графике и различных пользовательских. Есть среди этих девяти стандартных видов событий и те, что оповещают об удалении, изменении и создании графических объектов.

В первую очередь я задалась вопросами, как реализовать в коде:

  • "самовосстановление" объектов при их удалении или изменении кем-либо вручную или программно;
  • "самоуход" программы с графика при таких действиях со стороны.

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

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

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

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

if(id==CHARTEVENT_OBJECT_DELETE)
        {
         string deletedChartObject=sparam;
         //---
         for(int i=QUANT_OBJ-1;i>=0;i--)
           {
            if(StringCompare(deletedChartObject,nameObj[i],true)==0)
              {
            /*здесь какой-либо код с действиями, если имя объекта,
            поступившее по событию, полностью совпало с именем
            объекта программы*/
               return;
              }
           }
        }

Кроме того, вариант "ревизий" (по событиям или периодических) сам по себе не решает проблему переименования объекта кем-либо или чем-либо со стороны, в результате чего программа может потерять над ним контроль, оставляя после себя на графике "бесхозные" объекты.

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

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

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

else  if(id==CHARTEVENT_OBJECT_DELETE)
     {
     if(flagAntivandal==0)
        {
            string deletedChartObject=sparam;
            //---
            for(int i=QUANT_OBJ-1;i>=0;i--)
              {
               //--- действия при полном совпадении имени:
               if(StringCompare(deletedChartObject,nameObj[i],true)==0)
                 {
                  //--- вывод на печать во вкладку "Эксперты" терминала:
                  TEST_PRINT(deletedChartObject);
                  //--- отключаем "сигнализацию" на время совершения "своих" операций программы
                  flagAntivandal=-1;
                  //--- удаляем оставшиеся объекты:
                  ObjectsDeleteAll(0,prefixObj,0,-1);
                  //--- пересоздаем панель
                  PanelCreate();
                 //--- обновляем график 
                  ChartRedraw();
                 //--- включаем флаг обратно на "охрану" объектов:
                  flagAntivandal=0;
                  return;
                 }
              }
         return;
        }
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE)

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

Если у вас на компьютере мало свободного места, то не рекомендую испытывать вышеприведенный код на практике. Если много, то можете убедиться самостоятельно, заменив им полностью обработку событий удаления и изменения в прилагаемом тестовом коде test_count_click_2.

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

Кроме того, в тот момент оставалось неизвестным решение в коде для случаев переименования объекта. Не хватало «якоря», который мог бы помочь в таких ситуациях.

Поэтому, я задумчиво освежила память информацией о типах событий графика из Документации и заглянула в описание работы функции ObjectSetString(), где написано, что при переименовании объекта одновременно формируются два события:

  • удаление объекта со старым именем,
  • создание графического объекта с новым именем.

Перейдя затем в раздел Свойства объектов, посчитала, что таким якорем вполне может послужить время создания объектов, для определения которого есть свойство OBJPROP_CREATETIME.

Как можно убедиться, например, с помощью прилагаемого скрипта test_get_objprop_createtime, время создания объекта равно локальному времени компьютера, на котором запущен терминал:

Время создания объекта равно локальному времени компьютера на момент создания объекта

Рис. 4. Время создания объекта равно локальному времени компьютера на момент создания объекта

Названный тестовый скрипт создает на графике кнопку, определяет время ее создания и выводит его на печать через Print() в журнал вкладки Эксперты. Далее он определяет и выводит на печать в ту же вкладку терминала различные виды времени: локальное время компьютера, расчетное текущее время торгового сервера, последнее известное время сервера по времени прихода последней котировки, время по GMT. Затем "замирает" на 10 секунд, после чего удаляет созданную им кнопку с графика, завершая на этом свою работу.

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


План мероприятий состоял из следующего:

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

  • при изменении свойств какого-либо графического объекта вручную через диалог свойств и другой программой;
  • при удалении объекта через «Список объектов» вручную и другой программой;
  • при переименовании объекта вручную через диалог его свойств и другой программой.

Попутно необходимо отображать время создания объектов и записывать полученные результаты в виде таблиц для удобства восприятия.

Соответственно, для реализации первого пункта плана потребовалось:

3.1.2. Программный помощник-индикатор, рассказывающий в качестве стороннего наблюдателя о происходящих на графике событиях.

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

Подробно на этом помощнике останавливаться не буду, его полный код прикреплен в файле под именем test_types_of_chart_events. Однако поскольку в последующем он может быть полезным и вам при работе или знакомстве с событиями, то упомяну здесь о следующем:

  • Этот индикатор предназначен для работы с девятью видами стандартных событий.
  • Он выводит название события и поступившие с ним значения: id, lparam, dparam, sparam (имя объекта при событиях с графическими объектами).
  • В нем предусмотрено отключение/включение оповещений о некоторых событиях, поэтому проводите испытания с ним на отдельном графике, где во время запуска этого тестового кода не работают программы для трейдинга. В противном случае вы можете отключить оповещения, необходимые для корректной работы этих программ.

    Внешние настраиваемые свойства тестового индикатора-наблюдателя

Рис. 5. Внешние настраиваемые свойства тестового индикатора-наблюдателя

По умолчанию в индикаторе отключены оповещения о событиях, не относящихся напрямую к действиям с объектами. Предусмотрено отключение/включение показа оповещений о пяти из девяти видов стандартных событий, с которыми он работает.

  • Кроме того, этот тестовый код выводит информацию на вкладке торгового терминала Эксперты с помощью функции Print(). Поэтому, чтобы не забивать лог-файлы терминала огромным количеством поступающих данных, через этот индикатор отключаются и не отображаются события CHARTEVENT_MOUSE_MOVE о перемещении мыши и нажатии ее кнопок. Включение отображения информации об этом виде события в коде тестового индикатора не предусмотрено.

3.1.3. Другими помощниками для проведения экспериментов стали:

  • скрипт, создающий на графике объекты Кнопка и Поле ввода;
  • скрипт, изменяющий свойства объектов по заданному в нем имени и удаляющий измененные им объекты после паузы.


3.2. Итоги проведенных экспериментов

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

Действие с объектом id lparam dparam sparam Наименование события, оповещающего о действиях, произведенных "вручную" Время создания объекта ** Наименование события, оповещающего о действиях, произведенных своей и сторонней программой
Время создания объекта **
Создание нового объекта (не за счет переименования) 7
0 0 имя объекта CHARTEVENT_OBJECT_CREATE локальное время компьютера, на котором запущен терминал CHARTEVENT_OBJECT_CREATE локальное время компьютера, на котором запущен терминал
Изменение имени объекта (при этом происходит удаление объекта с прежним именем и одновременное создание нового)
6

7

8
0

0

0
0

0

0
имя объекта,
имя объекта,
имя объекта
CHARTEVENT_OBJECT_DELETE

CHARTEVENT_OBJECT_CREATE

CHARTEVENT_OBJECT_CHANGE
у нового объекта равно времени создания объекта с прежним именем отсутствуют оповещения
у нового объекта равно времени создания объекта с прежним именем
Изменение фона объекта 8 0 0 имя объекта СHARTEVENT_OBJECT_CHANGE не меняется отсутствуют оповещения не меняется
Изменение координаты нахождения объекта по оси X 8 0 0 имя объекта СHARTEVENT_OBJECT_CHANGE не меняется отсутствуют оповещения не меняется
Удаление объекта 6 0 0 имя объекта CHARTEVENT_OBJECT_DELETE *** CHARTEVENT_OBJECT_DELETE ***

 

Таблица 1. Часть из того, что может "видеть" своя и сторонняя программа при оповещениях о событиях создания, изменения и удаления объектов на графике *


Примечания:

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

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

*** Если при оповещении о событии удаления объекта попробовать определить время его создания с помощью OBJPROP_CREATETIME, то, поскольку объект уже удален, выйдет сообщение об ошибке 4203, которая означает применение ошибочного идентификатора свойства графического объекта.

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

Однако, чтобы не ограничивать пользователей стандартным набором видов оповещений о событиях, разработчики предусмотрели для нас возможность конструирования в коде своих собственных оповещений при каких-либо индивидуальных конкретных событиях. Для этого используется функция EventChartCustom(), которая генерирует оповещения о заданных пользовательских событиях для своего или всех графиков терминала.

По итогам экспериментов "созрели" и вполне универсальные решения.

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

Основаны эти решения:

  • на расстановке в коде флагов, в том числе, в зависимости от решаемых задач, адаптивных;
  • на работе специально созданных функций, которые будут приведены далее в составе схем вариантов конструирования.

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

  • Переменные для хранения флагов, объявленные на глобальном для программы уровне видимости переменных, вне всяких функций, то бишь до блока функции OnInit() (не путать с глобальными переменными терминала).
  • Создание для "охраняемых" объектов общего, не короткого префикса в их именах.
  • Включение в свойствах графика отправки оповещений об удалении объектов (CHART_EVENT_OBJECT_DELETE), если это свойство отключено.
  • Установку периодичности срабатывания таймера, и, соответственно, включение в код функции OnTimer(), где будут производиться через заданный интервал времени проверки включения оповещений об удалении объектов. Остановка таймера выполняется в функции OnDeinit() с помощью EventKillTimer().
  • Наличие в коде, в теле функции OnChartEvent(), блоков обработки оповещений о событиях CHARTEVENT_OBJECT_CHANGE и CHARTEVENT_OBJECT_DELETE с прописанными в них действиями при несанкционированных вмешательствах.

При варианте реализации очистки графика от переименованных объектов, бывших ранее объектами программы, необходимо предусмотреть:

  • Определение и сохранение времени создания "охраняемых" объектов в массив данных, если этих объектов много, или в обычные переменные при малом количестве охраняемых объектов.
  • Специфические функции для удаления объектов, включая удаление объектов по времени их создания.

Кроме того, важен порядок реализации "охранных" действий в коде.

Если в коде конструировать только "самоуход" программы с графика при несанкционированных изменениях/удалении объектов, то, в зависимости от поставленных задач, может быть достаточно одной переменной для флага. Со значениями, например:
  • -1: Отключение "сигнализации", перед тем как в коде предполагается произвести действия со своими объектами. И для уменьшения лишних обработок указание в начале блоков кода обработки событий удаления и изменения return при этом значении флага. То есть, по сути, это означает игнорировать поступающие оповещения о событиях удаления и изменения объектов, выходя из блоков обработки оповещений о таких событиях при этом значении флага:
if(flagAntivandal==-1){return;}
  • 0: Включение "сигнализации" охраны объектов (всех или части из них). Устанавливается при постановке "на сигнализацию" после совершения "своих" действий с объектами. Параллельно в качестве "охранных" мер в блоках кода обработки событий удаления и изменения объектов прописывается следующий порядок проведения операций:
    • если при значении флага 0 поступило оповещение о событии удаления или изменения объекта, то сначала проверяется, что событие произошло именно с одним из "охраняемых" объектов;
    • если событие произошло с одним из "охраняемых" объектов, то перед принятием мер отключить флаг "сигнализации", сменив его значение на равное -1, в том числе, чтобы не зацикливать работу кода при удалении программы с графика;
    • и только после этих предварительных мероприятий применяется функция по удалению программы с графика, составленная с помощью:
      • ChartIndicatorDelete() для индикатора. Предварительно следует задать короткое имя индикатора в OnInit():
IndicatorSetString(INDICATOR_SHORTNAME,short_name);
  • ExpertRemovе() для советника. Если эта функция вам пока еще не знакома, то обратите внимание на примечание к ее описанию в Документации и приведенный там пример.

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

Теперь о сказанном на примерах.


4. Схемы реализации вариантов при несанкционированном изменении или удалении объектов программы

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

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

Как выглядит панель прилагаемого тестового кода в зависимости от языка торгового терминала

Рис. 6. Панель прилагаемого тестового кода в зависимости от языка торгового терминала

Этот тестовый код прост и на его основе легче будет отследить приведенные ниже схемы работы двух следующих разновидностей действий:

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

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


4.1. "Самоуход" программы с графика с удалением измененных объектов

Полный вариант рабочего тестового кода прикреплен под названием test_count_click_1.

4.1.1. Создание файла с этой схемой в коде:

Файл test_count_click_0 сохранен под именем test_count_click_1. Это можно сделать, открыв в MetaEditor прилагаемый файл с номером 0 в названии, а затем через верхнее меню MetaEditor: Файл -> Сохранить как...

4.1.2. Переменные, объявленные вне всяких функций, на уровне видимости всем частям программы, то есть до блока функции OnInit():

Произведена замена имени индикатора в #define для последующей внутренней подстановки:

#define NAME_INDICATOR             "count_click_1: "

Добавлена переменная для флага-семафора:

int flagAntivandal;
Добавлен массив для хранения времени создания объектов, размер которого равен количеству "охраняемых" объектов:
datetime timeCreateObj[QUANT_OBJ];

Добавлен текстовый массив для хранения двух предупредительных сообщений при "самоуходе" индикатора с графика. Одно при удачном удалении с графика, второе — если возникла ошибка при этой операции. Массив объявлен на уровне видимости всеми частями программы, поскольку предусмотрен вывод этих сообщений на двух языках (английском и русском) в зависимости от языка терминала.

string textDelInd[2];

Кроме того, сообщения, хранимые в этом массиве, могут пригодиться, если предусматривать уход индикатора с графика по причине получения в OnInit() кода причины деинициализации REASON_INITFAILED.

4.1.3. В функции LanguageTerminal(), ответственной за вывод текстов сообщений в зависимости от языка торгового терминала:

Добавляются тексты самих сообщений при удачном и неудачном "самоуходе" программы с графика.

void LanguageTerminal()
  {
   string language_t=NULL;
   language_t=TerminalInfoString(TERMINAL_LANGUAGE);
//---
   if(language_t=="Russian")
     {
      textObj[3]="Да: ";
      textObj[4]="Нет: ";
      textObj[5]="Всего: ";
      //---
      StringConcatenate(textDelInd[0],"Не удалось удалиться индикатору: \"",
                        prefixObj,"\". Код ошибки: ");
      StringConcatenate(textDelInd[1],"Удалился с графика индикатор: \"",
                        prefixObj,"\".");
     }
   else
     {
      textObj[3]="Yes: ";
      textObj[4]="No: ";
      textObj[5]="All: ";
      //---
      StringConcatenate(textDelInd[0],"Failed to delete indicator: \"",
                        prefixObj,"\". Error code: ");
      StringConcatenate(textDelInd[1],
                        "Withdrew from chart indicator: \"",
                        prefixObj,"\".");
     }
//---
   return;
  }

4.1.4. В функции PanelCreate(), создающей объекты панели управления:

После создания объектов определяется время их создания и сохраняется в массиве. В простом варианте это может выглядеть примерно так:

void PanelCreate(const long chart_ID=0,const int sub_window=0)
  {
   for(int i=NUMBER_ALL;i>=0;i--)
     {
      //--- поля для отображения количества щелчков:
      if(ObjectFind(chart_ID,nameObj[i])<0)
        {
         EditCreate(chart_ID,nameObj[i],sub_window,X_DISTANCE+WIDTH,
                    Y_DISTANCE+(HEIGHT)*(i),WIDTH,HEIGHT,
                    textObj[i],"Arial",FONT_SIZE,"\n",ALIGN_RIGHT,true,
                    CORNER_PANEL,clrText[i],CLR_PANEL,CLR_BORDER);
         //--- определяем и записываем время создания объекта:
         CreateTimeGet(chart_ID,nameObj[i],timeCreateObj[i]);
        }
     }
//---
   int correct=NUMBER_ALL+1;
//---
   for(int i=QUANT_OBJ-1;i>=correct;i--)
     {
      //--- поля с сопроводительными надписями:
      if(ObjectFind(chart_ID,nameObj[i])<0)
        {
         EditCreate(chart_ID,nameObj[i],sub_window,X_DISTANCE+(WIDTH*2),
                    Y_DISTANCE+(HEIGHT)*(i-correct),WIDTH,HEIGHT,
                    textObj[i],"Arial",FONT_SIZE,"\n",ALIGN_LEFT,true,
                    CORNER_PANEL,clrText[i-correct],CLR_PANEL,CLR_BORDER);
         //--- определяем и записываем время создания объекта:
         CreateTimeGet(chart_ID,nameObj[i],timeCreateObj[i]);
        }
     }
   return;
  }

4.1.5. Вариант функции для определения и записи в переменную времени создания объекта:

bool CreateTimeGet(const long chart_ID,
                   const string &name,
                   datetime &value
                   )
  {
   datetime res=0;
   if(!ObjectGetInteger(chart_ID,name,OBJPROP_CREATETIME,0,res))
     {
      Print(LINE_NUMBER,__FUNCTION__,", Error = ",
            GetLastError(),", name: ",name);
      return(false);
     }
   value=res;
   return(true);
  }

На случай, если программа находилась на графике при аварийном перезапуске терминала, например, в результате зависания, добавлена функция ревизии времени создания объектов:

bool RevisionCreateTime(int quant,datetime &time_create[])
  {
   datetime t=0;
   for(int i=quant-1;i>=0;i--)
     {
      t=time_create[i];
      if(t==0)
        {
         Print(LINE_NUMBER,__FUNCTION__,", Error create time: ",
               TimeToString(t,TIME_DATE|TIME_SECONDS));
         return(false);
        }
     }
//---
   return(true);
  }

Вызов этой функции поставлен в OnInit() после PanelCreate().

Предполагаю, что если после аварийного принудительного закрытия терминала и его последующего перезапуска эта функция вернет false, то перед этим во вкладке терминала "Эксперты" может быть сообщение от функции CreateTimeGet() об ошибке 4102, означающей, что график не отвечает.

4.1.6. В блоке функции OnInit():

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

flagAntivandal=-1;

4.1.6.2. Задается короткое имя индикатора, если оно не задано. Оно потребуется при принудительном удалении программы с графика.

В приведенном примере тестового кода короткое имя индикатора ранее уже было задано и равнялось префиксу объектов, состоящему из имени индикатора, символа и периода графика, на котором он будет установлен:

//--- создаем префикс для имен объектов кода и короткое имя индикатора:
   StringConcatenate(prefixObj,NAME_INDICATOR,Symbol(),"_",EnumToString(Period()));
//--- задаем короткое имя индикатора:
   IndicatorSetString(INDICATOR_SHORTNAME,prefixObj);

4.1.6.3. После функции по созданию панели управления и ревизии массива с временем создания объектов включаем "охранную" сигнализацию:

//--- создаем панель:
   PanelCreate();
/*ревизия массива со временем создания объектов и завершение работы
программы, если что-то не так:*/
   if(!RevisionCreateTime(QUANT_OBJ,timeCreateObj))
     {return(REASON_INITFAILED);}
/*устанавливаем флаг в положение реагирования
на события удаления и изменения объектов:*/
   flagAntivandal=0;

4.1.6.4. Включаем на графике оповещение о событиях удаления графических объектов, если оно отключено в свойствах:

//--- включение оповещения о событиях удаления графических объектов
 bool res=true;
 ChartEventObjectDeleteGet(res);
 if(res!=true)
 {ChartEventObjectDeleteSet(true);}

Примечание: ChartEventObjectDeleteGet() и ChartEventObjectDeleteSet() — это готовые функции со страницы примеров работы с графиком в Документации.

4.1.6.5. Перед выходом из OnInit() задаем в секундах периодичность срабатывания таймера для проверок, если во время работы этой программы будет отключено в свойствах графика оповещение о событиях удаления объектов:

EventSetTimer(5);

4.1.7. В блоке функции OnDeinit():

Прописываем отключение "сигнализации":

flagAntivandal=-1;

Прописываем остановку генерации событий от таймера:

EventKillTimer();

Удаление индикатора, если от OnInit() поступит код причины деинициализации REASON_INITFAILED:

//--- удаление индикатора, если от OnInit()поступило REASON_INITFAILED.
   if(reason==REASON_INITFAILED)
     {
      ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
     }

4.1.8. В блоке функции OnTimer():

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

4.1.9. Конструирование действий в функции OnChartEvent():

4.1.9.1. Если при событиях изменения и удаления объектов не предусмотрена обработка, кроме как для "охранных" мер, то можно объединить обработку этих событий одним блоком.

4.1.9.2. Затем первым делом — проверка флага на то, включена ли "сигнализация" при поступлении оповещения о событии изменения/удаления объекта. Если значение флага равно -1 (отключена "сигнализация"), то выход из блока обработки событий.

4.1.9.3. При значении флага, равном 0 ("сигнализация" включена), можно сначала поставить проверку на совпадение имени объекта, поступившего в оповещении, с префиксом объектов программы. Для этого используется функция StringFind(), чтобы не перебирать весь массив имен объектов программы при каждом оповещении об изменениях и удалении любых объектов на графике. Префикс в данном случае задан в OnInit() не короткий, и за счет этого получается неплохой фильтр. Формирование префикса приведено в п.4.1.6.2.

Если нет совпадения по префиксу, происходит выход из блока обработки событий.

4.1.9.4. Если есть совпадение по префиксу, то только тогда выполняется проверка поступившего оповещения о событии на полное совпадение по имени с "охраняемыми" объектами программы. При полном совпадении имени значение охранного флага меняется на -1 (отключение), чтобы не создавать лишние циклы при удалении программы с графика.

4.1.9.5. Далее, на случай наличия объекта, ставшего "бесхозным" из-за переименования, выполняется поиск объектов с тем же временем создания, что и у того, по которому было оповещение, с последующим удалением, если найдено.

4.1.9.6. И только после этого — удаление программы с графика.

Описанные действия, сконструированные в коде:

else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagAntivandal==-1)//игнорируем событие, если "сигнализация отключена"
        {return;}
      else if(flagAntivandal==0)//если "сигнализация" включена
        {
         //--- смотрим совпадение по префиксу:
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)//если есть совпадение по префиксу
           {
            string chartObject=sparam;
            findPrefixObject=-1;
            //---
            for(int i=QUANT_OBJ-1;i>=0;i--)
              {
               //--- действия при полном совпадении имени:
               if(StringCompare(chartObject,nameObj[i],true)==0)
                 {
                  //--- отключаем сигнализацию, чтобы не зацикливать операции удаления:
                  flagAntivandal=-1;
/*удаляем объекты, у которых время создания равно времени создания удаленного или
 измененного объекта:*/
                  ObDelCreateTimeExcept0(timeCreateObj[i],0,0,OBJ_EDIT);
                  //--- удаляем индикатор с графика:
                  ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
                  //---
                  ChartRedraw();
                  return;
                 }
              }//for(int i=QUANT_OBJ-1;i>=0;i--)
            return;
           }//if(findPrefixObject==0)
         return;
        }//if(flagAntivandal==0)
      return;
     }//if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)  

4.1.10. Функция для удаления объектов по времени их создания ObDelCreateTimeExcept0():

Схема функции для удаления объектов по времени их создания может выглядеть следующим образом:

void ObDelCreateTimeExcept0(datetime &create_time,// время создания
                            long chart_ID=0,// идентификатор графика
                            int sub_window=-1,// номер подокна
                            ENUM_OBJECT type=-1)// -1 = все типы объектов
  {
/*выход из функции, если время объекта = 0(1970.01.01 00:00:00),
 чтобы не удалять с графика те объекты, созданные вручную или 
 программами, у которых время создания могло обнулиться после
 перезапуска терминала:*/
   if(create_time==0){return;}
   int quant_obj=0;
   quant_obj=ObjectsTotal(chart_ID,sub_window,type);
//---
   if(quant_obj>0)
     {
      datetime create_time_x=0;
      string obj_name=NULL;
      //---
      for(int i=quant_obj-1;i>=0;i--)
        {
         obj_name=ObjectName(chart_ID,i,sub_window,type);
         create_time_x=(datetime)ObjectGetInteger(chart_ID,obj_name,
                        OBJPROP_CREATETIME);
         //---
         if(create_time_x==create_time)
           {ObDelete(chart_ID,obj_name);}
        }
     }
   return;
  }
//+------------------------------------------------------------------+

Внутри нее есть фильтр на то, что если у "охраняемого" объекта дата создания равна 0 (это означает: 1970.01.01 00:00:00), то следует прекратить по нему обработку. Это необходимо для того, чтобы не удалять с графика те объекты, созданные вручную или программами, у которых время создания обнулилось после перезапуска терминала.

4.1.11. Функция ObDelete() в коде выше:

Она взята из библиотеки ObjectCreateAndSet, и в ней предусмотрен вывод ошибок при удалении объектов:

bool ObDelete(long chart_ID,string name)
  {
   if(ObjectFind(chart_ID,name)>-1)
     {
      ResetLastError();
      if(!ObjectDelete(chart_ID,name))
        {
         Print(LINE_NUMBER,__FUNCTION__,", Error Code = ",
               GetLastError(),", name: ",name);
         return(false);
        }
     }
   return(true);
  }

4.1.12. Функция для удаления индикатора:

//+------------------------------------------------------------------+
//| Удаление индикатора с графика                                    |
//+------------------------------------------------------------------+
void ChIndicatorDelete(const string short_name,//короткое имя индикатора
                       const string &text_0,//текст при ошибке при удалении индикатора
                       const string &text_1,//текст при удачном удалении
                       const long  chart_id=0,// идентификатор графика
                       const int   sub_window=-1// номер подокна
                       )
  {
   bool res=ChartIndicatorDelete(chart_id,sub_window,short_name);
//---
   if(!res){Print(LINE_NUMBER,text_0,GetLastError());}
   else Print(LINE_NUMBER,text_1);
  }
//+------------------------------------------------------------------+

где LINE_NUMBER — это заданный в #define общий текст начала сообщений, содержащий номер строки кода:

#define LINE_NUMBER    "Line: ",__LINE__,", "

Теперь перейдем к следующей схеме.


4.2. "Самовосстановление" объектов программы

Конструирование самовосстановления объектов потребует более серьезного внимания при расстановке и смене значений флагов, чтобы не допустить зацикливания работы программы.

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

4.2.1. Создание файла с этой схемой в коде:

Файл test_count_click_1 сохранен под именем test_count_click_2.

4.2.2. Переменные, объявленные вне всяких функций, на уровне видимости всем частям программы, то есть до блока функции OnInit():

Произведена замена имени индикатора в #define для последующей внутренней подстановки:

#define NAME_INDICATOR             "count_click_2: "

Добавлена еще одна переменная, уже для вспомогательного адаптивного флага, помимо ранее описанного основного простого:

int flagAntivandal, flagResetEventDelete;

Создана переменная и для текста сообщения перед проведением восстановительных работ:

string textRestoring;

4.2.3. В функции LanguageTerminal(), ответственной за вывод текстов сообщений в зависимости от языка торгового терминала:

Добавлено сообщение, выводимое перед восстановительными работами при обнаружении несанкционированного удаления или изменения объектов панели:

void LanguageTerminal()
  {
   string language_t=NULL;
   language_t=TerminalInfoString(TERMINAL_LANGUAGE);
//---
   if(language_t=="Russian")
     {
      textObj[3]="Да: ";
      textObj[4]="Нет: ";
      textObj[5]="Всего: ";
      //---
      textRestoring="Уведомление о страховом случае: объект ";
      //---
      StringConcatenate(textDelInd[0],"Не удалось удалиться индикатору: \"",
                        prefixObj,"\". Код ошибки: ");
      StringConcatenate(textDelInd[1],"Удалился с графика индикатор: \"",
                        prefixObj,"\".");
     }
   else
     {
      textObj[3]="Yes: ";
      textObj[4]="No: ";
      textObj[5]="All: ";
      //---
      textRestoring="Notification of the insured event: object ";
      //---
      StringConcatenate(textDelInd[0],"Failed to delete indicator: \"",
                        prefixObj,"\". Error code: ");
      StringConcatenate(textDelInd[1],
                        "Withdrew from chart indicator: \"",
                        prefixObj,"\".");
     }
//---
   return;
  }

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

4.2.4. В блоке функции OnInit():

До функции создания объектов инициализируем оба флага значением -1 (полное отключение сигнализации):

flagAntivandal = flagResetEventDelete = -1;

После блока создания объектов панели и ревизии массива времени создания объектов — инициализация флагов значением 0 (постановка на "сигнализацию"):

//--- создаем панель:
   PanelCreate();
/*ревизия массива со временем создания объектов и завершение работы
программы, если что-то не так:*/
   if(!RevisionCreateTime(QUANT_OBJ,timeCreateObj))
     {return(REASON_INITFAILED);}
/*устанавливаем антивандальные флаги в положение реагирования на 
события удаления и изменения объектов:*/
   flagAntivandal=flagResetEventDelete=0;

4.2.5. Перейдем в функцию OnChartEvent():

Заменим имевшийся там ранее блок обработки оповещений о событиях изменения и удаления объектов на нижеприведенный. В новом блоке действия идут по схеме, также общей для обработки событий удаления и изменения объектов:

  • Первым делом — проверка на то, что "охрана" включена. Если нет, то происходит выход из блока обработки событий.
  • Затем, если вспомогательный флаг для сброса лишних событий удаления больше 0, происходит сброс поступающих оповещений о событиях удаления и изменения объектов панели до тех пор, пока флаг не станет равен 0.
  • И лишь когда основной и вспомогательный флаги равны нулю, возможны восстановительные действия при несанкционированном изменении или удалении объектов.
else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagAntivandal==-1){return;}
      else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
/*сброс значений флага-счетчика событий для последующего предовращения 
зацикливания работы кода от очереди накопившихся событий удаления объектов
в результате восстановительных действий:*/
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }
      else if(flagAntivandal==0 && flagResetEventDelete==0)
        {
         //--- смотрим совпадение по префиксу:
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)//если есть совпадение по префиксу
           {
            string chartObject=sparam;//имя объекта в событии
            findPrefixObject=-1;
/*проверка, является ли объект "охраняемым" и если да, то мероприятия 
            по восстановлению:*/
            RestoringObjArrayAll(chartObject,prefixObj,flagAntivandal,
                                     flagResetEventDelete,QUANT_OBJ,nameObj,
                                     timeCreateObj);
            return;
           }//if(findPrefixObject==0)
         return;
        }//else if(flagAntivandal==0 && flagResetEventDelete==0)
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)

Основные мероприятия по восстановлению сконструированы в функции RestoringObjArrayAll(). В блоке может применяться сразу несколько подобных функций для разных групп объектов. Пример этого есть в пятом разделе статьи. Имя объекта по событию удаления или изменения попадает в эту функцию после проверки по префиксу, если префикс совпадает с "охраняемыми" объектами.

4.2.6. Функция RestoringObjArrayAll().

В ней происходит следующее:

  • Если проверка на полное совпадение по имени показывает, что объект по событию из "охраняемых", то:
    • основному флагу "сигнализации" присваивается значение -1 (снятие с "сигнализации" на время "своих" действий);
    • на случай, если это было событие удаления объекта, вспомогательный флаг приравнивается к общему количеству "охраняемых" объектов минус один, поскольку если обрабатывается событие удаления объекта, значит, к этому моменту как минимум один объект был удален.
  • После этих предварительных действий "оставшиеся в живых" охраняемые объекты с помощью приведенной ниже функции ObDeletePrefixFlag() удаляются по префиксу с попутным подсчетом количества удаляемых объектов. На основе этого подсчета значение вспомогательного флага flagResetEventDelete корректируется при необходимости.

А поскольку при удалении объектов возникают события удаления, то они в последующем не будут обрабатываться, пока значение вспомогательного флага не сбросится до 0 по мере их поступления в обработку в этой части кода:

else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
/*сброс значений флага-счетчика событий для последующего предовращения 
зацикливания работы кода от очереди накопившихся событий удаления объектов
в результате восстановительных действий:*/
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }
    • Когда объекты удалены по префиксу, и вспомогательный флаг принял значение, равное фактическому количеству "погашения" не требуемых к обработке событий, поставлена функция для перерисовки графика. И только потом идут дальнейшие действия.
    • После перерисовки графика следует поиск и удаление объектов со временем создания, равным времени создания объекта, по которому поступило событие. На случай, если событие образовалось от переименования объекта, и на графике появился "бесхозный" объект. Удаление выполняется с помощью уже знакомой по п.4.1.10 функции ObDelCreateTimeExcept0().
    • Из завершающих шагов обработки: пересоздание панели или той ее части, что восстанавливается в данное время.
    • Напоследок — перерисовка графика и присвоение основному флагу-семафору значения, равного 0 (постановка на "охрану"). У вспомогательного адаптивного флага к этому времени необходимое значение сформировалось автоматически, поэтому ничего ему не прописываем.

//+------------------------------------------------------------------+
//|Функция "самовосcтановления" объектов для OnChartEvent()          |
//+------------------------------------------------------------------+
//|string sparam = имя объекта, поступившее в OnChartEvent()         |
//|string prefix_obj = общий префикс охраняемых объектов             |
//|int &flag_antivandal = флаг для постановки/снятия сигнализации    |
//|int &flag_reset = флаг сброса событий                             |
//|int quant_obj = общее количество объектов в "охраняемом"          |
//|массиве                                                           |
//|string &name[] = массив имен "охраняемых" объектов                |
//|datetime &time_create[] = массив времени создания этих объектов   |
//|int quant_obj_all=-1 общее количество восстанавливаемых объектов  |
//|программы, >= quant_obj (если -1, то равно quant_obj)             |
//|const long chart_ID = 0 идентификатор графика                     |
//|const int sub_window = 0 индекс окна                              |
//|int start_pos=0 с какой позиции в названии объектов искать префикс|
//+------------------------------------------------------------------+
int RestoringObjArrayAll(const string sparam,
                         const string prefix_obj,
                         int &flag_antivandal,
                         int &flag_reset,
                         const int quant_obj,
                         string &name[],
                         datetime &time_create[],
                         int quant_obj_all=-1,
                         const long chart_ID=0,
                         const int sub_window=0,
                         const int start_pos=0
                         )
  {
//--- сюда примем результат:
   int res=-1;
/*Проверки на корректность внесения во внешние параметры функции количества
   объектов. Но то, что при конструировании кода были внесены неверные внешние
   параметры этой функции, станет известно лишь при наступлении "охранного" 
   события.
   Поэтому при конструировании кода проверяйте предварительно правильность
   установки вами внешних параметров этой функции.*/
   if(quant_obj<=0)//если задано количество охраняемых объектов <= 0
     {
      Print(LINE_NUMBER,__FUNCTION__,
            ", Error Code. Wrong value: quant_obj =",quant_obj);
      return(res);
     }
   else   if(quant_obj>quant_obj_all && quant_obj_all!=-1)
     {
      Print(LINE_NUMBER,__FUNCTION__,
            ", Error Code. Not the correct value: quant_obj_all");
      return(res);
     }
   else if(quant_obj_all==-1){quant_obj_all=quant_obj;}
//--- переменная для значения сравнения полного имени объекта:
   int comparison=-1;
/*цикл сверки имени поступившего по событию объекта с именами в 
 массиве охраняемых:*/
   for(int i=quant_obj-1;i>=0;i--)
     {
      comparison=StringCompare(sparam,name[i],true);
      //--- действия при полном совпадении имени:
      if(comparison==0)
        {
         comparison=-1;
         res=1;
         //--- оповещение о наступлении "страхового" случая:
         Print(LINE_NUMBER,textRestoring,sparam);
/*пока программа будет восстанавливать объекты, ставим флаг 
не реагировать на события удаления объектов:*/
         flag_antivandal=-1;
/*Начальная величина флага-счетчика событий, уже не требуемых 
для последующей обработки событий. Для последующего предовращения 
зацикливания работы кода от очереди накопившихся событий удаления
объектов в результате восстановительных действий:*/
         flag_reset=quant_obj_all-1;
         //--- удаление оставшихся, если они есть, охраняемых объектов массива:
         ObDeletePrefixFlag(flag_reset,prefix_obj,chart_ID,sub_window,start_pos);
         //--- перерисовка графика:
         ChartRedraw();
         //--- удаление, если они есть, переименованных объектов:
         ObDelCreateTimeExcept0(time_create[i],chart_ID,sub_window);
         //--- пересоздание объектов:
         PanelCreate();
         //--- завершающая перерисовка графика:
         ChartRedraw();
         //--- включение сигнализации:
         flag_antivandal=0;
         //---
         return(res);
        }
     }//for(int i=QUANT_OBJECTS-1;i>=0;i--)
   return(res);
  }
Примечание: В функции не случайно отсутствует выбор по типу объектов. Поскольку при "самовосстановлении" объектов желание уменьшить количество обработок за счет сужения круга обработок по типу объектов может привести к обратному эффекту, если при восстановительных работах все удаляемые и пересоздаваемые затем объекты панели могут быть не одного типа. Поэтому, с моей точки зрения, лучше просто не применять сужение круга обработки по конкретному типу объектов, если к моменту наступления несанкционированного вмешательства объекты панели на графике состоят из разных типов. И предполагая, что могут быть те, кто это предупреждение проигнорирует, в приведенной функции этот выбор убран.

Посмотреть на нежелательные эффекты вы можете самостоятельно, прописав и применив выбор по типу объектов при "самовосстановлении" в примере в пятом разделе статьи, где в панели присутствуют объекты разных типов. Только предварительно введите Print() в блоки кода по обработке событий и восстановления.

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

4.2.7. Функция ObDeletePrefixFlag() — это для удаления объектов по префиксу и, при необходимости, корректировки значения вспомогательного флага:

За основу была взята схема кода, приведенная в учебнике от Сергея Ковалева, и о которой в дальнейшем упоминали на форуме по MQL4 Артем Тришкин и неизвестный мне по фамилии-имени под ником 7777877. Я не обратила должного внимания на нее в свое время, поэтому благодарна тем, кто потом упоминал о ней на форуме, поскольку основа схемы достаточно универсальна и была мне подспорьем для разных задач.

Здесь применена, по сути, ее разновидность:

//+------------------------------------------------------------------+
//| int &flag_reset = флаг для сброса событий удаления объектов      |
//| string prefix_obj = общий префикс в именах объектов              |
//| long  chart_ID = идентификатор графика                           | 
//| int   sub_window=-1 = индекс окна (-1 = все)                     |
//| int start_pos=-1 = начальная позиция подстроки общего префикса в |
//| имени объектов                                                   |
//+------------------------------------------------------------------+
int ObDeletePrefixFlag(int &flag_reset,
                       string prefix_obj,
                       const long chart_ID=0,
                       const int sub_window=0,
                       int start_pos=0)

  {
   int quant_obj=ObjectsTotal(chart_ID,sub_window,-1);
   if(quant_obj>0)
     {
      int count=0;
      int prefix_len=StringLen(prefix_obj);
      string find_substr=NULL,obj_name=NULL;
//---
      for(int i=quant_obj-1;i>=0;i--)
        {
         obj_name=ObjectName(chart_ID,i,sub_window,-1);
         find_substr=StringSubstr(obj_name,start_pos,prefix_len);

         if(StringCompare(find_substr,prefix_obj,true)==0)
           {
            count=count+1;
            ObDelete(chart_ID,obj_name);
           }
        }
      if(count>0 && flag_reset<count)
        {flag_reset=count;}
     }
   return(flag_reset);
  }
Проверить работу кода можно следующим образом:
  • запустить на графике приложенный тестовый код;
  • пощелкать на объектах, принадлежащих к "Да" и "Нет", увеличив значения с нолей до каких-либо;
  • а затем попробовать изменить объекты этой панели через диалог свойств или удалить их.
После произведенных автоматических восстановительных действий в панели вновь будут те "накликанные" числа, что были к моменту изменения или удаления ее объектов, поскольку количество щелчков сохраняется в массив данных, применяемый при пересоздании панели.

Однако можно конструировать разные варианты реакции для разных объектов.

Мы изложили основную часть методики за исключением особых случаев, таких как:

  • пропуск обработки событий;
  • изменение свойств объектов другой программой.

Теперь перейдем к примеру практической реализации сразу двух вариантов реакции программы на удаление и изменение ее объектов.


5. Пример реализации в одной программе двух вариантов ответных реакций

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

  • Если приводить пример совместной реализации этих вариантов на основе более сложного кода, то можно ненамеренно запутать читателя.
  • Прилагаемый ниже вариант, хоть и прост, но дает много практической информации, в том числе в "боевых" условиях.
  • Кроме того, этот индикатор может быть полезен для работы с объектами, в том числе не только по тематике этой статьи.

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

Полный код прикреплен под именем id_name_object. Здесь же приведу:

  • карту по массивам и переменной для хранения имен объектов и карту по объектам;
  • общие схемы кода;
  • участки кода, непосредственно связанные с темой статьи.

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

Кроме того, предусмотрены:

  • "минимизация" основной части панели на графике;
  • "самовосстановление" объектов, если какой-либо объект основной части панели изменен или удален;
  • "самоуход" индикатора с графика, если удалена или изменена кнопка свернутой панели, исходя из: "раз свернули, значит и не сильно нужно сейчас";
  • последние две меры реализованы с учетом того, что необходимо убирать с графика объекты после возможного переименования;
  • отображение заголовочных надписей панели на русском языке, если терминал на русском языке, и английском, если на других.

Внешний вид панели управления в зависимости от языка терминала

Рис. 7. Внешний вид панели управления в зависимости от языка терминала

Карта по массивам и переменной для хранения создаваемых имен объектов панели

Рис. 8. Карта по массивам и переменной для хранения создаваемых имен объектов панели


Наименование
Тип объекта
Для чего предназначен
Где хранится текст
Шрифт
Цвет текста *
Фон * Цвет границы *
Время создания хранится
nameRectLabel
OBJ_RECTANGLE_LABEL
Полотно
---
--- ---
CLR_PANEL clrNONE
timeCreateRectLabel
nameButton[0]
OBJ_BUTTON
Кнопка для удаления индикатора с графика
textButtUchar[0]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[0]
nameButton[1] OBJ_BUTTON
Кнопка для "минимизации" панели
textButtUchar[1]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[1]
nameButton[2] OBJ_BUTTON
Кнопка для "разворачивания" панели после "минимизации"
textButtUchar[2]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[2]
nameEdit0[0]
OBJ_EDIT
Надпись "Имя объекта:"
textEdit0[0]
"Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[0]
nameEdit0[1]
OBJ_EDIT
Надпись "Время создания:"
textEdit0[1] "Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[1]
nameEdit0[2]
OBJ_EDIT
Надпись "Тип объекта:"
textEdit0[2] "Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[2]
nameEdit1[0]
OBJ_EDIT
Поле для отображения имени объекта textEdit1[0]
"Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[0]
nameEdit1[1] OBJ_EDIT
Поле для отображения времени его создания textEdit1[1] "Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[1]
nameEdit1[2] OBJ_EDIT
Поле для отображения типа объекта
textEdit1[2] "Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[2]

 

Таблица 2. Карта по объектам панели

* Цвета, заданные с помощью #define:

#define CLR_PANEL                  clrSilver//общий фон панели
#define CLR_TEXT                   C'39,39,39'//основной цвет текста
#define CLR_TEXT_BACK              C'150,150,150' //цвет затененного текста 
#define CLR_BORDER                 clrLavender//цвет рамок

** Символы "Wingdings":

uchar textButtUchar[3]={251,225,226};


5.1. Внешние переменные:

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

Внешние настраиваемые свойства индикатора

Рис. 9. Внешние настраиваемые свойства индикатора


5.2. Переменные для флагов, объявленные вне всяких функций, на уровне видимости всем частям программы, то есть до блока функции OnInit():

Объявлены два флага:

int flagClick, flagResetEventDelete;

flagClick — это флаг, определяющий отслеживание оповещений при щелчках по кнопкам панели. Его значения:

0
Отслеживание оповещений о событиях щелчка по кнопкам "минимизации" панели и удаления индикатора с графика, то есть это значение присваивается, когда на графике основная часть панели.
-1
Устанавливается перед действиями по "минимизации" или, наоборот, "разворачивания" панели на графике, а также перед действиями по удалению индикатора с графика после щелчка по соответствующей кнопке.
1 При отслеживании оповещений о событиях щелчка по кнопке "развернуть".

 

Кроме того, он выполняет функции основного "антивандального" флага (в предыдущих кодах статьи такой имел название flagAntivandal) на случай несанкционированного изменения или удаления объектов. Значение 0 — это постановка на "сигнализацию" основной части панели, 1 — кнопки "развернуть", -1 — это снятие с "сигнализации" на время проводимых действий в случае возникновения таких событий.

Если конструировать в коде основной "антивандальный" флаг адаптивным по значению, как и вспомогательный для сброса обработки событий, то тогда потребуется отдельная переменная для него.

flagResetEventDelete — флаг для сброса обработки ненужных событий с адаптивными значениями при реализации варианта "самовосстановления" измененных или удаленных объектов.

Остальные переменные, объявленные в этой области, можно посмотреть в прилагаемом коде.


5.3. В блоке функции OnInit():

  • До создания панели на графике флагам присваивается значение -1.
  • Массив для текстов полей панели, где в последующем будут отображаться значения тех объектов графика, по которым был сделан щелчок, заполняется пока пустыми значениями.
  • Создается общий префикс объектов и присваивается условно короткое имя индикатору.
  • Создаются имена для объектов панели на основе сформированного общего префикса.
  • Определяется, на каком языке, в зависимости от языка терминала, будут заголовочные надписи в панели, сообщения о восстановлении объектов и принудительном удалении индикатора.
  • Устанавливается ограничение по ширине и высоте размера в одну кнопку, если во внешних параметрах задан некорректный размер.
  • Производится создание основной части панели на графике.
  • Проводится ревизия массивов со временем создания объектов.
  • Флагам flagClick и flagResetEventDelete присваивается значение, равное 0.
  • Далее производится попытка включить оповещение на графике о событиях удаления объектов, если оно отключено в свойствах.
  • Устанавливается в секундах периодичность срабатывания таймера.

int OnInit()
  {
   flagClick=flagResetEventDelete=-1;
/*массив для текстов полей панели, где в последующем будут отображаться 
   значения тех графических объектов, по которым был сделан щелчок:*/
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--){textEdit1[i]=" ";}
//+------------------------------------------------------------------+
/*короткое имя индикатора:*/
   StringConcatenate(prefixObj,INDICATOR_NAME,"_",Symbol(),"_",
                     EnumToString(Period()));
   IndicatorSetString(INDICATOR_SHORTNAME,prefixObj);
//--- создание имен объектов
   NameObjectPanel(nameRectLabel,prefixObj,"rl");
   NameObjectPanel(QUANT_OBJ_BUTTON,nameButton,prefixObj,"but");
   NameObjectPanel(QUANT_OBJ_EDIT,nameEdit0,prefixObj,"ed0");
   NameObjectPanel(QUANT_OBJ_EDIT,nameEdit1,prefixObj,"ed1");
//+------------------------------------------------------------------+
/*тексты индикатора, в зависимости от языка торгового терминала:*/
   LanguageTerminal();
//+------------------------------------------------------------------+
/*ограничения по ширине и высоте размера в одну кнопку:*/
   if(objWidthHeight>=20 && objWidthHeight<=300)
     {obj0WidthHeight=objWidthHeight;}
   else if(objWidthHeight>300){obj0WidthHeight=300;}
   else {obj0WidthHeight=20;}
//--- создание на графике панели управления
   PanelCreate();
/*ревизия массивов со временем создания объектов и завершение работы
программы, если что-то не так:
(из трех кнопок присутствуют в основной части панели только две,
поэтому количество их при проведении ревизии за минусом одной)*/
   if(!RevisionCreateTime(QUANT_OBJ_BUTTON-1,timeCreateButton))
     {return(REASON_INITFAILED);}
   if(!RevisionCreateTime(QUANT_OBJ_EDIT,timeCreateEdit0))
     {return(REASON_INITFAILED);}
   if(!RevisionCreateTime(QUANT_OBJ_EDIT,timeCreateEdit1))
     {return(REASON_INITFAILED);}
//---
   flagClick=flagResetEventDelete=0;
//--- включение оповещения о событиях удаления графических объектов:
   bool res;
   ChartEventObjectDeleteGet(res);
   if(res!=true)
     {ChartEventObjectDeleteSet(true);}
//--- create timer
   EventSetTimer(8);
//---
   return(INIT_SUCCEEDED);
  }

5.4. В функции PanelCreate():

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

На случаи несанкционированного переименования объектов определяется время их создания и записывается в отведенные для этого массивы. Для полотна панели предусмотрена отдельная переменная.

void PanelCreate(const long chart_ID=0,// ID графика
                 const int sub_window=0)// номер подокна
  {
//--- ширина полотна панели:
   int widthPanel=(obj0WidthHeight*11)+(CONTROLS_GAP_XY*2);
//--- высота полотна панели:
   int heightPanel=(obj0WidthHeight*6)+(CONTROLS_GAP_XY*8);
//--- дистанция по оси х:
   int x_dist=X_DISTANCE+widthPanel;
//---
   if(ObjectFind(chart_ID,nameRectLabel)<0)//полотно
     {
      RectLabelCreate(chart_ID,nameRectLabel,sub_window,x_dist,
                      Y_DISTANCE,widthPanel,heightPanel,"\n",CLR_PANEL,
                      BORDER_RAISED,CORNER_PANEL,clrNONE,STYLE_SOLID,1,
                      false,false,true);
      //--- определяем и записываем время создания объекта:
      CreateTimeGet(chart_ID,nameRectLabel,timeCreateRectLabel);
     }
//---
   x_dist=X_DISTANCE+CONTROLS_GAP_XY;
   int y_dist=Y_DISTANCE+CONTROLS_GAP_XY;
//---
   for(int i=QUANT_OBJ_BUTTON-2;i>=0;i--)
     {
      //--- кнопки для удаления и минимизации индикатора:
      if(ObjectFind(chart_ID,nameButton[i])<0)
        {
         ButtonCreate(chart_ID,nameButton[i],sub_window,
                      x_dist+(obj0WidthHeight*(i+1)),y_dist,obj0WidthHeight,
                      obj0WidthHeight,CORNER_PANEL,
                      CharToString(textButtUchar[i]),"\n","Wingdings",
                      font_sz+1,CLR_TEXT,CLR_PANEL,clrNONE);
         //--- определяем и записываем время создания объекта:
         CreateTimeGet(chart_ID,nameButton[i],timeCreateButton[i]);
        }
     }
//---
   x_dist=X_DISTANCE+widthPanel-CONTROLS_GAP_XY;
   int y_dist_plus=(CONTROLS_GAP_XY*2)+(obj0WidthHeight*2);
//---
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
     {
      //--- наименования свойств объектов:
      if(ObjectFind(chart_ID,nameEdit0[i])<0)
        {
         EditCreate(chart_ID,nameEdit0[i],sub_window,x_dist,
                    y_dist+(y_dist_plus*i),obj0WidthHeight*8,
                    obj0WidthHeight,textEdit0[i],"Arial",font_sz,
                    "\n",ALIGN_CENTER,true,CORNER_RIGHT_UPPER,
                    CLR_TEXT_BACK,CLR_PANEL,CLR_BORDER);
         //--- определяем и записываем время создания объекта:
         CreateTimeGet(chart_ID,nameEdit0[i],timeCreateEdit0[i]);
        }
     }
//---
   y_dist=Y_DISTANCE+obj0WidthHeight+(CONTROLS_GAP_XY*2);
//---
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
     {
      //--- для отображения текстов значений свойств объектов:
      if(ObjectFind(chart_ID,nameEdit1[i])<0)
        {
         EditCreate(chart_ID,nameEdit1[i],sub_window,x_dist,
                    y_dist+(y_dist_plus*i),obj0WidthHeight*11,
                    obj0WidthHeight,textEdit1[i],"Arial",font_sz,
                    "\n",ALIGN_LEFT,true,CORNER_RIGHT_UPPER,
                    CLR_TEXT,CLR_PANEL,CLR_BORDER);
         //--- определяем и записываем время создания объекта:
         CreateTimeGet(chart_ID,nameEdit1[i],timeCreateEdit1[i]);
        }
     }
   return;
  }

5.5. В OnDeinit():

"Отключение" основного флага на время удаления индикатора с графика, удаление объектов по префиксу, остановка генерации событий от таймера и удаление индикатора с графика, если от OnInit() поступит причина деинициализации REASON_INITFAILED:

void OnDeinit(const int reason)
  {
   flagClick=-1;
   ObjectsDeleteAll(0,prefixObj,0,-1);
   EventKillTimer();
//--- удаление индикатора, если в OnInit REASON_INITFAILED.
   if(reason==REASON_INITFAILED)
     {
      ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
     }
//---
   ChartRedraw();
  }

5.6. В OnTimer():

Периодические проверки по времени, заданному в OnInit(), включено ли в свойствах графика оповещение о событиях удаления графических объектов:

void OnTimer()
  {
//--- включение оповещений о особытиях удаления графических объектов
   bool res;
   ChartEventObjectDeleteGet(res);
   if(res!=true)
     {ChartEventObjectDeleteSet(true);}
  }


5.7. Функция OnChartEvent():

5.7.1. В блоке для обработки оповещений о событиях щелчка мышью по объектам:
Если флаг-семафор flagClick равен 0, то:
  • Сначала выполняется проверка, не был ли это щелчок по кнопкам основной части панели.
  • Если был щелчок по кнопке минимизации панели, то флагу flagClick перед дальнейшими действиями присваивается значение -1.
  • Затем, если это был щелчок по кнопке минимизации, проводятся действия по "минимизации" основной части панели и созданию кнопки "развернуть" в правом верхнем углу графика.
  • После этого флагу flagClick присваивается значение 1, что означает отслеживать щелчки по кнопке "разворачивания" панели, а также ее несанкционированное изменение или удаление.
  • Если это был щелчок по кнопке удаления индикатора с графика, то флагу flagClick перед дальнейшими действиями также присваивается значение -1. И только после этого производится попытка удаления индикатора с графика.
  • Если это был щелчок не по кнопкам панели, а по какому-либо другому объекту графика, за исключением объектов этого индикатора, то определяются и отображаются в панели значения этого объекта: его имя, время создания и тип.

Если флаг-семафор flagClick равен 1, то при щелчке по кнопке "развернуть":

  • Этому флагу перед дальнейшими действиями присваивается значение -1.
  • Удаляется кнопка и создается панель.
  • Затем флагу flagClick присваивается значение 0. Вспомогательному флагу flagResetEventDelete для сброса события удаления кнопки "разворачивания" присваивается значение 1.
if(id==CHARTEVENT_OBJECT_CLICK)//id = 1
     {
      if(flagClick==0)//если на графике основная часть панели управления
        {
         string clickedChartObject=sparam;
         findPrefixObject=StringFind(clickedChartObject,prefixObj);
         //---
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
            //---
            for(int i=1;i>=0;i--)
              {
               if(StringCompare(clickedChartObject,nameButton[i],true)==0)
                 {
                  switch(i)
                    {
                     //--- кнопка "минимизации":
                     case 1:flagClick=-1;
                     MinimizeTable(nameButton,2,textButtUchar,2,
                                   timeCreateButton,obj0WidthHeight);
                     flagClick=1;return;
                     //--- кнопка удаления индикатора с графика:
                     case 0:flagClick=-1;
                     ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
                     return;
                     default:return;
                    }
                 }
              }
           }
         else//если щелчок не по кнопкам панели управления
           {
            SetValuesTable(clickedChartObject);//определяем значения объекта
            return;
           }
        }
      //--- если щелчок на кнопке "развернуть" панель:
      else if(flagClick==1)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            string clickedChartObject=sparam;
            findPrefixObject=-1;
            //--- кнопка "разворачивания" панели:
            if(StringCompare(clickedChartObject,nameButton[2],true)==0)
              {
               flagClick=-1;
               //--- основные действия при нажатии на кнопку "развернуть" панель
               ToExpandTheTable(clickedChartObject);
               //---
               flagClick=0;
               //--- для сброса события удаления этой кнопки:
               flagResetEventDelete=1;
               //---
               return;
              }
           }
         return;
        }
      return;
     }//if(id==CHARTEVENT_OBJECT_CLICK)
5.7.2. В блоке для обработки оповещений об окончании редактирования текста в графическом объекте Edit:
Когда в полях этой панели есть значения графических объектов, они становятся доступны для копирования из этих полей. Поэтому, если выводимые индикатором значения были в этих полях изменены или удалены при копировании или случайно/намеренно, то производится восстановление того текста, что был до изменения:
else  if(id==CHARTEVENT_OBJECT_ENDEDIT)//id = 3
     {
      findPrefixObject=StringFind(sparam,prefixObj);
      if(findPrefixObject==0)
        {
         string endEditChartObject=sparam;
         findPrefixObject=-1;
         int comparison=-1;
         //---
         for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
           {
            if(StringCompare(endEditChartObject,nameEdit1[i],true)==0)
              {
               string textNew=ObjectGetString(0,endEditChartObject,OBJPROP_TEXT);
               comparison=StringCompare(textEdit1[i],textNew,true);
               //---
               if(comparison!=0)
                 {
                  ObSetString(0,nameEdit1[i],OBJPROP_TEXT,textEdit1[i]);
                 }
               //---
               ChartRedraw();
               return;
              }
           }//for(int i=0;i<QUANT_OBJ_EDIT;i++)
         return;
        }//if(findPrefixObject==0)
      return;
     }//if(id==CHARTEVENT_OBJECT_ENDEDIT)
5.7.3. В блоке для обработки оповещений об удалении или изменении объектов:
  • Если флаг flagClick равен -1, то происходит выход из блока обработки событий, поскольку это значение присваивается флагу при проведении "своих" действий.
  • Если первая проверка пройдена и оказалось, что вспомогательный флаг для сброса событий flagResetEventDelete больше 0, и объект по событию относится к объектам панели, то выполняется сброс события и выход из блока обработки.
  • Если же флаги flagClick и flagResetEventDelete равны 0, то при совпадении имени объекта по событию с каким-либо "охраняемым" объектом проводятся восстановительные действия для основной части панели с помощью уже знакомой функции RestoringObjArrayAll() и ей сопутствующим.
  • Если же основной флаг flagClick равен 1 при событии, то при совпадении имени объекта по событию с именем кнопки "разворачивания" панели индикатор удаляется с графика.

else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagClick==-1)return;
      else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }
      //--- проверяем, наш ли объект удален/изменен:
      else if(flagClick==0 && flagResetEventDelete==0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         //---
         if(findPrefixObject==0)
           {
            string chartObject=sparam;
            findPrefixObject=-1;
            int res=-1;
            //--- действия, если объект является полотном панели управления:
            res=RestoringObjOneAll(chartObject,prefixObj,flagClick,
                                   flagResetEventDelete,nameRectLabel,
                                   timeCreateRectLabel,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- действия, если объект является кнопкой панели управления:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_BUTTON,
                                     nameButton,timeCreateButton,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- действия, если объект является надписью (Edit0) панели управления:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_EDIT,
                                     nameEdit0,timeCreateEdit0,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- действия, если объект является надписью (Edit1) панели управления:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_EDIT,
                                     nameEdit1,timeCreateEdit1,QUANT_OBJ_ALL);
            //---
            return;
           }
        }
      else if(flagClick==1)//если изменена или удалена кнопка свернутой панели
        {
         string clickedChartObject=sparam;
         if(StringCompare(clickedChartObject,nameButton[2],true)==0)
           {
            flagClick=-1;
            //--- удаление индикатора с графика:
            ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
            return;
           }
         return;
        }
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
Из нового в этом блоке — функция RestoringObjOneAll(), применяемая, если изменено или удалено полотно панели. Она аналогична имеющейся здесь и ранее приведенной функции RestoringObjArrayAll().
//+------------------------------------------------------------------+
//|string sparam = имя объекта, поступившее в OnChartEvent()         |
//|string prefix_obj = общий префикс охраняемых объектов             |
//|int &flag_antivandal = флаг для постановки/снятия сигнализации    |
//|int &flag_reset = флаг сброса событий                             |
//|string name = имя объекта                                         |
//|datetime time_create = время создания объекта                     |
//|int quant_obj_all = общее количество восстанавливаемых объектов   |
//|программы, >= quant_obj (если -1, то равно quant_obj)             |
//|const long chart_ID = 0 идентификатор графика                     |
//|const int sub_window = 0 индекс окна                              |
//|int start_pos=0 с какой позиции в названии объектов см.префикс    |
//+------------------------------------------------------------------+
int RestoringObjOneAll(const string sparam,
                       const string prefix_obj,
                       int &flag_antivandal,
                       int &flag_reset,
                       string name,
                       datetime time_create,
                       const int quant_obj_all,
                       const long chart_ID=0,
                       const int sub_window=0,
                       int start_pos=0
                       )
  {
   int res=-1;
   int comparison=-1;
//---
   comparison=StringCompare(sparam,name,true);
//--- действия при полном совпадении имени:
   if(comparison==0)
     {
      res=1;
      comparison=-1;
      //--- оповещение о наступлении "страхового" случая:
      Print(LINE_NUMBER,textRestoring,sparam);
      //---
      flag_antivandal=-1;
      flag_reset=quant_obj_all-1;
      //---
      ObDeletePrefixFlag(flag_reset,prefix_obj,chart_ID,sub_window,start_pos);
      //---
      ChartRedraw();
      //---
      ObDelCreateTimeExcept0(time_create,chart_ID,sub_window,-1);
      //---
      PanelCreate();
      //---
      ChartRedraw();
      //---
      flag_antivandal=0;
      //---
      return(res);
     }
   return(res);
  }

Полный код индикатора прикреплен под именем: id_name_object.


6. Заключение

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

При необходимости можно предусмотреть в коде другие сопутствующие действия для таких случаев, как:

  • изменение каких-либо объектов панели управления другой программой, например, за счет некорректности работы ее кода;
  • пропуск оповещений о событиях при большом их количестве.

Например, можно реализовать какие-либо дополнительные проверки при проведении "плановых" действий в панели, а также выборочные или тотальные проверки-ревизии по значимым отображаемым в панели данным по таймеру, если это оправдано применительно к коду.

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

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

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

Victor Nikolaev
Victor Nikolaev | 13 ноя 2015 в 11:27

Молодец. Статья хорошая. Примеры неплохие.

Но вроде есть простой способ проверки существования объекта и при необходимости всегда его можно восстановить. 

Alexander Puzanov
Alexander Puzanov | 13 ноя 2015 в 11:43
Victor Nikolaev:

Но вроде есть простой способ проверки существования объекта и при необходимости всегда его можно восстановить. 

Фишка в том чтобы сократить число проверок до минимума вместо проверок без перекуров. Для этого их привязали к конкретному событию. Если я правильно понял. Жаль тока что всё это не привязано к стандартной библиотеке
Dina Paches
Dina Paches | 13 ноя 2015 в 13:52

Спасибо вам всем за отзывы!

Victor Nikolaev:

Молодец. Статья хорошая. Примеры неплохие.

Но вроде есть простой способ проверки существования объекта и при необходимости всегда его можно восстановить. 

Спасибо, вам, Виктор!

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

Просто обычные привычные способы (простые или не очень) могут быть не всегда "своевременными". Тем более, что те же различные "шаловливые ручки" ведь никто не отменял. В том числе, и возможно в каких-то случаях наши собственные или пользовательские.

То бишь, если, к примеру, объекты панели управления программы случайно удалены через "Список объектов" вместе с другими выделением через Shift или же сторонней программой, где может применяться функция для удаления объектов (ObjectsDeleteAll() или созданная самостоятельно), производящая по заданным в ней параметрам:

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

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

Как-то так.

Т.е., для "охраны" объектов программы конечно же разные варианты, способы и их сочетания могут применяться.

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

Alexander Puzanov:
Фишка в том чтобы сократить число проверок до минимума вместо проверок без перекуров. Для этого их привязали к конкретному событию. Если я правильно понял. 

Да, спасибо, верно, сокращение числа обработок при проверках - это есть из фишек там.

Dina Paches
Dina Paches | 1 янв 2016 в 15:44

При компиляции в MetaEditor Version 5.00 build 1241 прилагаемых к статье тестовых кодов с именами:

  • test_count_click_0.mq5
  • test_count_click_1.mq5
  • test_count_click_2.mq5

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

Ниже описание проблемы, способ её решения и файлы с решением проблемы, взамен прилагаемых к статье:

Но сначала вновь подчеркну, что функции, относящиеся к антивандальным работали и работают корректно при компиляции в новом билде.

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

Однако эти же тестовые коды, скомпилированные у меня в более ранних версиях, продолжают работать корректно и в новом 1241-м билде, если их там не компилировать. Т.е., при кликах на объекты этих тестовых кодов идёт нормальный подсчёт кликов:

Выявила, что обнаруженная проблема связана с применением функции ArrayFill() в блоке обработки событий CHARTEVENT_OBJECT_CLICK в OnChartEvent().

               count=countClick[index]+count;
               int summ=countClick[NUMBER_ALL]+1;
               //---
               ArrayFill(countClick,index,1,count);
               ArrayFill(countClick,NUMBER_ALL,1,summ)

Однако если перед этой функцией поставить вывод на печать данных или ChartRedraw(), то названные коды из статьи начинают работать корректно после компиляции:

               count=countClick[index]+count;
               int summ=countClick[NUMBER_ALL]+1;
               //---
               //TEST_PRINT_TWO(count,summ);
               ChartRedraw();
               //---
               ArrayFill(countClick,index,1,count);
               ArrayFill(countClick,NUMBER_ALL,1,summ);

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

  • в файле test_count_click_0.mq5 - антивандальные защитные меры не применяются;
  • в файле test_count_click_1.mq5 - вариант «самоудаления» программы с чарта при несанкционированном вмешательстве в её объекты на графике;
  • в файле test_count_click_2.mq5 - вариант «самовосстановления» объектов программы при их несанкционированном изменении или удалении.

P./S.: Для корректной работы прилагаемых ниже кодов требуется в папке "Include" иметь в наличии файл objectcreateandset.mqh, прикреплённый к статье, где прикреплены и коды, на основе которых обнаружила не корректную работу названной функции. Так же этот файл можно скачать из Code Base

Dina Paches
Dina Paches | 1 янв 2016 в 18:30
Попутно, раз уж так привелось, дополню для тех, кто только начинает изучать обработку событий в функции MQL5 OnChartEvent(), что приводимые в статье схемы защитных вариантов экономно используют ресурсы компьютера и торгового терминала. Поэтому если будете читать эту статью, то обратите внимание в приводимых там схемах примеров на фильтры проверок, предусмотренные там для снижения количества обработок.

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

Обработка ошибок и логирование в MQL5 Обработка ошибок и логирование в MQL5

В статье рассматриваются общие вопросы обработки ошибок в программном обеспечении. Кроме того, затрагивается тема логирования и демонстрируется пример реализации логгера средствами MQL5.

Оценка и выбор переменных для моделей машинного обучения Оценка и выбор переменных для моделей машинного обучения

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

Еще раз о картах Кохонена Еще раз о картах Кохонена

Cтатья описывает приемы работы с картами Кохонена. Она будет интересна как исследователям рынка с начальными навыками программирования на MQL4 и MQL5, так и опытным программистам, испытывающим сложности с подключением карт Кохонена к своим проектам.

Индикатор "Канат" Эрика Наймана Индикатор "Канат" Эрика Наймана

В статье описывается построение индикатора «Канат» по книге Эрика Л. Наймана «Малая энциклопедия трейдера». Этот индикатор показывает направление тренда на основе расчетных величин быков и медведей за указанный период. В статье изложены принципы построения и расчета индикатора с примерами кода, на основе индикатора построен эксперт и произведена оптимизация внешних параметров.