English 中文 Español Deutsch 日本語 Português
Графика в библиотеке DoEasy (Часть 88): Коллекция графических объектов — двумерный динамический массив для хранения динамически изменяемых свойств объектов

Графика в библиотеке DoEasy (Часть 88): Коллекция графических объектов — двумерный динамический массив для хранения динамически изменяемых свойств объектов

MetaTrader 5Примеры | 19 ноября 2021, 14:49
1 861 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

Поясню. Например, свойство времени графического объекта. У графического объекта, позиционируемого на графике по координатам цена/время, есть несколько опорных точек, по которым он располагается на графике. Объект, допустим, "Стрелка" имеет всего одну опорную точку, и её мы можем получить при помощи функции ObjectGetInteger(), в качестве идентификатора свойства указав OBJPROP_TIME. Такая конструкция вернёт нам время на графике, соответствующее единственной опорной точке объекта:

ObjectGetInteger(0, Name, OBJPROP_TIME);

Но что делать, если у объекта две опорные точки как, например, у объекта TrendLine ? Как получить время обеих опорных точек? Для этого служит формальный параметр prop_modifier у функции ObjectGetInteger(). По умолчанию для него уже указано значение 0, что соответствует первой опорной точке. Для получения данных второй точки нам нужно указать значение 1. Для получение данных третьей точки — указать 2, и т.д:

ObjectGetInteger(0, Name, OBJPROP_TIME, 0);
ObjectGetInteger(0, Name, OBJPROP_TIME, 1);
ObjectGetInteger(0, Name, OBJPROP_TIME, 2);
ObjectGetInteger(0, Name, OBJPROP_TIME, n);

Это всё просто и понятно. Но мы все получаемые от объекта данные записываем в массивы: целочисленные данные в long-массив, вещественные данные — в double-массив, и строковые — в string-массив. Значит, чтобы сохранить данные, которые можно получить с указанием в prop_modifier нужной по счёту опорной точки, мы можем просто использовать двумерный массив. И вроде бы всё логично — точку 0 мы храним в нулевом измерении, точку 1 — в первом, 2 — во втором, и так далее:

array[TIME][0] = ObjectGetInteger(0, Name, OBJPROP_TIME, 0);
array[TIME][1] = ObjectGetInteger(0, Name, OBJPROP_TIME, 1);
array[TIME][2] = ObjectGetInteger(0, Name, OBJPROP_TIME, 2);
//...
array[TIME][n] = ObjectGetInteger(0, Name, OBJPROP_TIME, n);

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

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

  • Все объекты являются наследниками их базовых объектов, в которых определены массивы для хранения свойств, и у каждого свойства должен быть заранее указан размер второго измерения массива, который для каждого объекта и каждого его свойства может быть своим. А в MQL мы должны при создании двумерного массива обязательно указать значение второй размерности, которую мы как раз и не знаем в абстрактном классе для каждого свойства каждого объекта;
  • Каждое такое "многомерное" свойство может быть динамически изменено как пользователем, так и программно. Но в MQL мы не можем динамически менять не нулевую размерность многомерного массива.
Но выход есть. Мы создадим свой класс многомерного динамического массива, который может динамически меняться в любом его измерении.
Для создания такого массива будем использовать класс динамического массива указателей на экземпляры класса CObject и его наследников CArrayObj.

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

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

Класс динамического многомерного массива

Класс CArrayObj — это по сути массив, в котором лежат указатели на экземпляры объектов, унаследованных от базового класса CObject. Соответственно, в такой массив мы можем положить любой объект, являющийся потомком объекта CObject, а это означает, что у нас могут лежать в ячейках массива как данные типа long, double или string, так и другой массив CArrayObj, в котором также могут находиться либо данные, либо другие массивы. Если с массивами CArrayObj всё понятно, то с данными не совсем — они не являются потомками класса CObject, поэтому нам необходимо создать классы для хранения таких данных. Кроме того, каждому из объектов (и самому объекту-массиву), лежащих в массиве, нам нужно указать его тип — это необходимо для однозначного понимания что именно хранится в ячейке массива — простой объект с целочисленными, вещественными или строковыми данными, или ещё один объект, в свою очередь содержащий либо объекты с данными, либо ещё один массив с объектами. Это необходимо для создания методов копирования одного объекта класса в другой — чтобы точно знать, копировать ли данные в ячейку (если в соответствующей ячейке копируемого массива находятся данные), либо в массиве-источнике создавать новый массив с данными (если в соответствующей ячейке копируемого массива находится массив) и копировать данные из него. Давайте сразу зададим нужные там типы объектов, в файле \MQL5\Include\DoEasy\Defines.mqh впишем в список типов объектов библиотеки константы нужных там типов (привожу полный вид перечисления для более полного понимания):

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список типов объектов библиотеки                                 |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Графика
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // Тип объекта "Базовый объект всех графических объектов библиотеки"
   OBJECT_DE_TYPE_GELEMENT,                                       // Тип объекта "Графический элемент"
   OBJECT_DE_TYPE_GFORM,                                          // Тип объекта "Форма"
   OBJECT_DE_TYPE_GSHADOW,                                        // Тип объекта "Тень"
//--- Анимация
   OBJECT_DE_TYPE_GFRAME,                                         // Тип объекта "Один кадр анимации"
   OBJECT_DE_TYPE_GFRAME_TEXT,                                    // Тип объекта "Один кадр текстовой анимации"
   OBJECT_DE_TYPE_GFRAME_QUAD,                                    // Тип объекта "Один кадр прямоугольной анимации"
   OBJECT_DE_TYPE_GFRAME_GEOMETRY,                                // Тип объекта "Один кадр геометрической анимации"
   OBJECT_DE_TYPE_GANIMATIONS,                                    // Тип объекта "Анимации"
//--- Управление графическими объектами
   OBJECT_DE_TYPE_GELEMENT_CONTROL,                               // Тип объекта "Управление графическими элементами"
//--- Стандартные графические объекты
   OBJECT_DE_TYPE_GSTD_OBJ,                                       // Тип объекта "Стандартный графический объект"
   OBJECT_DE_TYPE_GSTD_VLINE              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_VLINE,            // Тип объекта "Вертикальная линия
   OBJECT_DE_TYPE_GSTD_HLINE              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_HLINE,            // Тип объекта "Горизонтальная линия
   OBJECT_DE_TYPE_GSTD_TREND              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_TREND,            // Тип объекта "Трендовая линия
   OBJECT_DE_TYPE_GSTD_TRENDBYANGLE       =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_TRENDBYANGLE,     // Тип объекта "Трендовая линия по углу
   OBJECT_DE_TYPE_GSTD_CYCLES             =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_CYCLES,           // Тип объекта "Циклические линии
   OBJECT_DE_TYPE_GSTD_ARROWED_LINE       =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROWED_LINE,     // Тип объекта "Линия со стрелкой"
   OBJECT_DE_TYPE_GSTD_CHANNEL            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_CHANNEL,          // Тип объекта "Равноудаленный канал
   OBJECT_DE_TYPE_GSTD_STDDEVCHANNEL      =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_STDDEVCHANNEL,    // Тип объекта "Канал стандартного отклонения
   OBJECT_DE_TYPE_GSTD_REGRESSION         =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_REGRESSION,       // Тип объекта "Канал на линейной регрессии
   OBJECT_DE_TYPE_GSTD_PITCHFORK          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_PITCHFORK,        // Тип объекта "Вилы Эндрюса
   OBJECT_DE_TYPE_GSTD_GANNLINE           =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_GANNLINE,         // Тип объекта "Линия Ганна
   OBJECT_DE_TYPE_GSTD_GANNFAN            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_GANNFAN,          // Тип объекта "Веер Ганна
   OBJECT_DE_TYPE_GSTD_GANNGRID           =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_GANNGRID,         // Тип объекта "Сетка Ганна
   OBJECT_DE_TYPE_GSTD_FIBO               =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBO,             // Тип объекта "Уровни Фибоначчи
   OBJECT_DE_TYPE_GSTD_FIBOTIMES          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBOTIMES,        // Тип объекта "Временные зоны Фибоначчи
   OBJECT_DE_TYPE_GSTD_FIBOFAN            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBOFAN,          // Тип объекта "Веер Фибоначчи
   OBJECT_DE_TYPE_GSTD_FIBOARC            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBOARC,          // Тип объекта "Дуги Фибоначчи
   OBJECT_DE_TYPE_GSTD_FIBOCHANNEL        =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBOCHANNEL,      // Тип объекта "Канал Фибоначчи
   OBJECT_DE_TYPE_GSTD_EXPANSION          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_EXPANSION,        // Тип объекта "Расширение Фибоначчи
   OBJECT_DE_TYPE_GSTD_ELLIOTWAVE5        =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ELLIOTWAVE5,      // Тип объекта "5-волновка Эллиотта
   OBJECT_DE_TYPE_GSTD_ELLIOTWAVE3        =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ELLIOTWAVE3,      // Тип объекта "3-волновка Эллиотта
   OBJECT_DE_TYPE_GSTD_RECTANGLE          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_RECTANGLE,        // Тип объекта "Прямоугольник
   OBJECT_DE_TYPE_GSTD_TRIANGLE           =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_TRIANGLE,         // Тип объекта "Треугольник
   OBJECT_DE_TYPE_GSTD_ELLIPSE            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ELLIPSE,          // Тип объекта "Эллипс
   OBJECT_DE_TYPE_GSTD_ARROW_THUMB_UP     =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_THUMB_UP,   // Тип объекта "Знак "Хорошо" (большой палец вверх)
   OBJECT_DE_TYPE_GSTD_ARROW_THUMB_DOWN   =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_THUMB_DOWN, // Тип объекта "Знак "Плохо" (большой палец вниз)
   OBJECT_DE_TYPE_GSTD_ARROW_UP           =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_UP,         // Тип объекта "Знак "Стрелка вверх"
   OBJECT_DE_TYPE_GSTD_ARROW_DOWN         =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_DOWN,       // Тип объекта "Знак "Стрелка вниз"
   OBJECT_DE_TYPE_GSTD_ARROW_STOP         =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_STOP,       // Тип объекта "Знак "Стоп"
   OBJECT_DE_TYPE_GSTD_ARROW_CHECK        =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_CHECK,      // Тип объекта "Знак "Птичка" (галка)
   OBJECT_DE_TYPE_GSTD_ARROW_LEFT_PRICE   =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_LEFT_PRICE, // Тип объекта "Левая ценовая метка
   OBJECT_DE_TYPE_GSTD_ARROW_RIGHT_PRICE  =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_RIGHT_PRICE,// Тип объекта "Правая ценовая метка
   OBJECT_DE_TYPE_GSTD_ARROW_BUY          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_BUY,        // Тип объекта "Знак "Buy"
   OBJECT_DE_TYPE_GSTD_ARROW_SELL         =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_SELL,       // Тип объекта "Знак "Sell"
   OBJECT_DE_TYPE_GSTD_ARROW              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW,            // Тип объекта "Стрелка"
   OBJECT_DE_TYPE_GSTD_TEXT               =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_TEXT,             // Тип объекта "Текст"
   OBJECT_DE_TYPE_GSTD_LABEL              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_LABEL,            // Тип объекта "Текстовая метка"
   OBJECT_DE_TYPE_GSTD_BUTTON             =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_BUTTON,           // Тип объекта "Кнопка"
   OBJECT_DE_TYPE_GSTD_CHART              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_CHART,            // Тип объекта "График"
   OBJECT_DE_TYPE_GSTD_BITMAP             =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_BITMAP,           // Тип объекта "Рисунок"
   OBJECT_DE_TYPE_GSTD_BITMAP_LABEL       =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_BITMAP_LABEL,     // Тип объекта "Графическая метка"
   OBJECT_DE_TYPE_GSTD_EDIT               =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_EDIT,             // Тип объекта "Поле ввода"
   OBJECT_DE_TYPE_GSTD_EVENT              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_EVENT,            // Тип объекта "Событие, соответствующий событию в экономическом календаре"
   OBJECT_DE_TYPE_GSTD_RECTANGLE_LABEL    =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_RECTANGLE_LABEL,  // Тип объекта "Прямоугольная метка для создания и оформления пользовательского графического интерфейса"
   
//--- Объекты
   OBJECT_DE_TYPE_BASE  =  OBJECT_DE_TYPE_GSTD_RECTANGLE_LABEL+1, // Базовый объект всех объектов библиотеки
   OBJECT_DE_TYPE_BASE_EXT,                                       // Расширенный базовый объект всех объектов библиотеки
   
   OBJECT_DE_TYPE_ACCOUNT,                                        // Тип объекта "Аккаунт"
   OBJECT_DE_TYPE_BOOK_ORDER,                                     // Тип объекта "Заявка стакана цен"
   OBJECT_DE_TYPE_BOOK_BUY,                                       // Тип объекта "Заявка на покупку в стакане"
   OBJECT_DE_TYPE_BOOK_BUY_MARKET,                                // Тип объекта "Заявка на покупку по рыночной цене в стакане"
   OBJECT_DE_TYPE_BOOK_SELL,                                      // Тип объекта "Заявка на продажу в стакане"
   OBJECT_DE_TYPE_BOOK_SELL_MARKET,                               // Тип объекта "Заявка на продажу по рыночной цене в стакане"
   OBJECT_DE_TYPE_BOOK_SNAPSHOT,                                  // Тип объекта "Снимок стакана цен"
   OBJECT_DE_TYPE_BOOK_SERIES,                                    // Тип объекта "Серия снимков стакана цен"
   
   OBJECT_DE_TYPE_CHART,                                          // Тип объекта "Чарт"
   OBJECT_DE_TYPE_CHART_WND,                                      // Тип объекта "Окно графика"
   OBJECT_DE_TYPE_CHART_WND_IND,                                  // Тип объекта "Индикатор окна графика"
   
   OBJECT_DE_TYPE_EVENT,                                          // Тип объекта "Событие"
   OBJECT_DE_TYPE_EVENT_BALANCE,                                  // Тип объекта "Событие балансовой операции"
   OBJECT_DE_TYPE_EVENT_MODIFY,                                   // Тип объекта "Событие модификации отложенного ордера или позиции"
   OBJECT_DE_TYPE_EVENT_ORDER_PLASED,                             // Тип объекта "Событие установки отложенного ордера"
   OBJECT_DE_TYPE_EVENT_ORDER_REMOVED,                            // Тип объекта "Событие удаления отложенного ордера"
   OBJECT_DE_TYPE_EVENT_POSITION_CLOSE,                           // Тип объекта "Событие закрытия позиции"
   OBJECT_DE_TYPE_EVENT_POSITION_OPEN,                            // Тип объекта "Событие открытия позиции"
   
   OBJECT_DE_TYPE_IND_BUFFER,                                     // Тип объекта "Индикаторный буфер"
   OBJECT_DE_TYPE_IND_BUFFER_ARROW,                               // Тип объекта "Буфер отрисовки стрелками"
   OBJECT_DE_TYPE_IND_BUFFER_BAR,                                 // Тип объекта "<Буфер баров"
   OBJECT_DE_TYPE_IND_BUFFER_CALCULATE,                           // Тип объекта "Расчётный буфер"
   OBJECT_DE_TYPE_IND_BUFFER_CANDLE,                              // Тип объекта "Буфер свечей"
   OBJECT_DE_TYPE_IND_BUFFER_FILLING,                             // Тип объекта "Буфер заливки"
   OBJECT_DE_TYPE_IND_BUFFER_HISTOGRAMM,                          // Тип объекта "Буфер гистограммы"
   OBJECT_DE_TYPE_IND_BUFFER_HISTOGRAMM2,                         // Тип объекта "Буфер гистограммы2"
   OBJECT_DE_TYPE_IND_BUFFER_LINE,                                // Тип объекта "Буфер линии"
   OBJECT_DE_TYPE_IND_BUFFER_SECTION,                             // Тип объекта "Буфер секции"
   OBJECT_DE_TYPE_IND_BUFFER_ZIGZAG,                              // Тип объекта "Буфер зигзага"
   OBJECT_DE_TYPE_INDICATOR,                                      // Тип объекта "Индикатор"
   OBJECT_DE_TYPE_IND_DATA,                                       // Тип объекта "Данные индикатора"
   OBJECT_DE_TYPE_IND_DATA_LIST,                                  // Тип объекта "Список данных индикатора"
   
   OBJECT_DE_TYPE_IND_AC,                                         // Тип объекта "Индикатор Accelerator Oscillator"
   OBJECT_DE_TYPE_IND_AD,                                         // Тип объекта "Индикатор Accumulation/Distribution"
   OBJECT_DE_TYPE_IND_ADX,                                        // Тип объекта "Индикатор Average Directional Index"
   OBJECT_DE_TYPE_IND_ADXW,                                       // Тип объекта "Индикатор ADX by Welles Wilder"
   OBJECT_DE_TYPE_IND_ALLIGATOR,                                  // Тип объекта "Индикатор Alligator"
   OBJECT_DE_TYPE_IND_AMA,                                        // Тип объекта "Индикатор Adaptive Moving Average"
   OBJECT_DE_TYPE_IND_AO,                                         // Тип объекта "Индикатор Awesome Oscillator"
   OBJECT_DE_TYPE_IND_ATR,                                        // Тип объекта "Индикатор Average True Range"
   OBJECT_DE_TYPE_IND_BANDS,                                      // Тип объекта "Индикатор Bollinger Bands®"
   OBJECT_DE_TYPE_IND_BEARS,                                      // Тип объекта "Индикатор Bears Power"
   OBJECT_DE_TYPE_IND_BULLS,                                      // Тип объекта "Индикатор Bulls Power"
   OBJECT_DE_TYPE_IND_BWMFI,                                      // Тип объекта "Индикатор Market Facilitation Index"
   OBJECT_DE_TYPE_IND_CCI,                                        // Тип объекта "Индикатор Commodity Channel Index"
   OBJECT_DE_TYPE_IND_CHAIKIN,                                    // Тип объекта "Индикатор Chaikin Oscillator"
   OBJECT_DE_TYPE_IND_CUSTOM,                                     // Тип объекта "Пользовательский индикатор"
   OBJECT_DE_TYPE_IND_DEMA,                                       // Тип объекта "Индикатор Double Exponential Moving Average"
   OBJECT_DE_TYPE_IND_DEMARKER,                                   // Тип объекта "Индикатор DeMarker"
   OBJECT_DE_TYPE_IND_ENVELOPES,                                  // Тип объекта "Индикатор Envelopes"
   OBJECT_DE_TYPE_IND_FORCE,                                      // Тип объекта "Индикатор Force Index"
   OBJECT_DE_TYPE_IND_FRACTALS,                                   // Тип объекта "Индикатор Fractals"
   OBJECT_DE_TYPE_IND_FRAMA,                                      // Тип объекта "Индикатор Fractal Adaptive Moving Average"
   OBJECT_DE_TYPE_IND_GATOR,                                      // Тип объекта "Индикатор Gator Oscillator"
   OBJECT_DE_TYPE_IND_ICHIMOKU,                                   // Тип объекта "Индикатор Ichimoku Kinko Hyo"
   OBJECT_DE_TYPE_IND_MA,                                         // Тип объекта "Индикатор Moving Average"
   OBJECT_DE_TYPE_IND_MACD,                                       // Тип объекта "Индикатор Moving Average Convergence/Divergence"
   OBJECT_DE_TYPE_IND_MFI,                                        // Тип объекта "Индикатор Money Flow Index"
   OBJECT_DE_TYPE_IND_MOMENTUM,                                   // Тип объекта "Индикатор Momentum"
   OBJECT_DE_TYPE_IND_OBV,                                        // Тип объекта "Индикатор On Balance Volume"
   OBJECT_DE_TYPE_IND_OSMA,                                       // Тип объекта "Индикатор Moving Average of Oscillator"
   OBJECT_DE_TYPE_IND_RSI,                                        // Тип объекта "Индикатор Relative Strength Index"
   OBJECT_DE_TYPE_IND_RVI,                                        // Тип объекта "Индикатор Relative Vigor Index"
   OBJECT_DE_TYPE_IND_SAR,                                        // Тип объекта "Индикатор Parabolic SAR"
   OBJECT_DE_TYPE_IND_STDEV,                                      // Тип объекта "Индикатор Standard Deviation"
   OBJECT_DE_TYPE_IND_STOCH,                                      // Тип объекта "Индикатор Stochastic Oscillator"
   OBJECT_DE_TYPE_IND_TEMA,                                       // Тип объекта "Индикатор Triple Exponential Moving Average"
   OBJECT_DE_TYPE_IND_TRIX,                                       // Тип объекта "Индикатор Triple Exponential Moving Averages Oscillator"
   OBJECT_DE_TYPE_IND_VIDYA,                                      // Тип объекта "Индикатор Variable Index Dynamic Average"
   OBJECT_DE_TYPE_IND_VOLUMES,                                    // Тип объекта "Индикатор Volumes"
   OBJECT_DE_TYPE_IND_WPR,                                        // Тип объекта "Индикатор Williams' Percent Range"
   
   OBJECT_DE_TYPE_MQL5_SIGNAL,                                    // Тип объекта "mql5-сигнал"
   
   OBJECT_DE_TYPE_ORDER_DEAL_POSITION,                            // Тип объекта "Ордер/Сделка/Позиция"
   OBJECT_DE_TYPE_HISTORY_BALANCE,                                // Тип объекта "Историческая балансовая операция"
   OBJECT_DE_TYPE_HISTORY_DEAL,                                   // Тип объекта "Историческая сделка"
   OBJECT_DE_TYPE_HISTORY_ORDER_MARKET,                           // Тип объекта "Исторический маркет-ордер"
   OBJECT_DE_TYPE_HISTORY_ORDER_PENDING,                          // Тип объекта "Исторический удалённый отложенный ордер"
   OBJECT_DE_TYPE_MARKET_ORDER,                                   // Тип объекта "Рыночный маркет-ордер"
   OBJECT_DE_TYPE_MARKET_PENDING,                                 // Тип объекта "Рыночный отложенный ордер"
   OBJECT_DE_TYPE_MARKET_POSITION,                                // Тип объекта "Рыночная позиция"
   
   OBJECT_DE_TYPE_PENDING_REQUEST,                                // Тип объекта "Отложенный торговый запрос"
   OBJECT_DE_TYPE_PENDING_REQUEST_POSITION_OPEN,                  // Тип объекта "Отложенный запрос на открытие позиции"
   OBJECT_DE_TYPE_PENDING_REQUEST_POSITION_CLOSE,                 // Тип объекта "Отложенный запрос на закрытие позиции"
   OBJECT_DE_TYPE_PENDING_REQUEST_POSITION_SLTP,                  // Тип объекта "Отложенный запрос на модификацию стоп-приказов позиции"
   OBJECT_DE_TYPE_PENDING_REQUEST_ORDER_PLACE,                    // Тип объекта "Отложенный запрос на установку отложенного ордера"
   OBJECT_DE_TYPE_PENDING_REQUEST_ORDER_REMOVE,                   // Тип объекта "Отложенный запрос на удаление отложенного ордера"
   OBJECT_DE_TYPE_PENDING_REQUEST_ORDER_MODIFY,                   // Тип объекта "Отложенный запрос на мидификацию параметров отложенного ордера"
   
   OBJECT_DE_TYPE_SERIES_BAR,                                     // Тип объекта "Бар"
   OBJECT_DE_TYPE_SERIES_PERIOD,                                  // Тип объекта "Таймсерия периода"
   OBJECT_DE_TYPE_SERIES_SYMBOL,                                  // Тип объекта "Таймсерии символа"
   
   OBJECT_DE_TYPE_SYMBOL,                                         // Тип объекта "Символ"
   OBJECT_DE_TYPE_SYMBOL_BONDS,                                   // Тип объекта "Символ облигация"
   OBJECT_DE_TYPE_SYMBOL_CFD,                                     // Тип объекта "CFD символ (Контракт на разницу цен)"
   OBJECT_DE_TYPE_SYMBOL_COLLATERAL,                              // Тип объекта "Символ неторгуемый актив"
   OBJECT_DE_TYPE_SYMBOL_COMMODITY,                               // Тип объекта "Товарный символ"
   OBJECT_DE_TYPE_SYMBOL_COMMON,                                  // Тип объекта "Символ общей группы"
   OBJECT_DE_TYPE_SYMBOL_CRYPTO,                                  // Тип объекта "Криптовалютный символ"
   OBJECT_DE_TYPE_SYMBOL_CUSTOM,                                  // Тип объекта "Пользовательский символ"
   OBJECT_DE_TYPE_SYMBOL_EXCHANGE,                                // Тип объекта "Биржевой символ"
   OBJECT_DE_TYPE_SYMBOL_FUTURES,                                 // Тип объекта "Символ фьючерс"
   OBJECT_DE_TYPE_SYMBOL_FX,                                      // Тип объекта "Форекс символ"
   OBJECT_DE_TYPE_SYMBOL_FX_EXOTIC,                               // Тип объекта "Форекс символ экзотик"
   OBJECT_DE_TYPE_SYMBOL_FX_MAJOR,                                // Тип объекта "Форекс символ мажор"
   OBJECT_DE_TYPE_SYMBOL_FX_MINOR,                                // Тип объекта "Форекс символ минор"
   OBJECT_DE_TYPE_SYMBOL_FX_RUB,                                  // Тип объекта "Форекс символ рубль"
   OBJECT_DE_TYPE_SYMBOL_INDEX,                                   // Тип объекта "Символ индекс"
   OBJECT_DE_TYPE_SYMBOL_INDICATIVE,                              // Тип объекта "Символ индикатив"
   OBJECT_DE_TYPE_SYMBOL_METALL,                                  // Тип объекта "Символ металл"
   OBJECT_DE_TYPE_SYMBOL_OPTION,                                  // Тип объекта "Символ опцион"
   OBJECT_DE_TYPE_SYMBOL_STOCKS,                                  // Тип объекта "Символ ценная бумага"
   
   OBJECT_DE_TYPE_TICK,                                           // Тип объекта "Тик"
   OBJECT_DE_TYPE_NEW_TICK,                                       // Тип объекта "Новый тик"
   OBJECT_DE_TYPE_TICKSERIES,                                     // Тип объекта "Серия тиковых данных"
   
   OBJECT_DE_TYPE_TRADE,                                          // Тип объекта "Торговый объект"
   
   OBJECT_DE_TYPE_LONG,                                           // Тип объекта "Данные типа long"
   OBJECT_DE_TYPE_DOUBLE,                                         // Тип объекта "Данные типа double"
   OBJECT_DE_TYPE_STRING,                                         // Тип объекта "Данные типа string"
   OBJECT_DE_TYPE_OBJECT,                                         // Тип объекта "Данные типа object"
  };

//+------------------------------------------------------------------+
//| Данные для поиска и сортировки                                   |
//+------------------------------------------------------------------+

В файле \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений:

   MSG_LIB_SYS_FAILED_ADD_BUFFER,                     // Не удалось добавить объект-буфер в список
   MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ,              // Не удалось создать объект \"Индикаторный буфер\"
   MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST,                // Не удалось добавить объект в список
   
   MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ,           // Не удалось создать объект long-данных
   MSG_LIB_SYS_FAILED_CREATE_DOUBLE_DATA_OBJ,         // Не удалось создать объект double-данных
   MSG_LIB_SYS_FAILED_CREATE_STRING_DATA_OBJ,         // Не удалось создать объект string-данных
   
   MSG_LIB_SYS_FAILED_DECREASE_LONG_ARRAY,            // Не удалось уменьшить размер long-массива
   MSG_LIB_SYS_FAILED_DECREASE_DOUBLE_ARRAY,          // Не удалось уменьшить размер double-массива
   MSG_LIB_SYS_FAILED_DECREASE_STRING_ARRAY,          // Не удалось уменьшить размер string-массива
   
   MSG_LIB_SYS_FAILED_GET_LONG_DATA_OBJ,              // Не удалось получить объект long-данных
   MSG_LIB_SYS_FAILED_GET_DOUBLE_DATA_OBJ,            // Не удалось получить объект double-данных
   MSG_LIB_SYS_FAILED_GET_STRING_DATA_OBJ,            // Не удалось получить объект string-данных
   
   MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY,            // Запрос за пределами long-массива
   MSG_LIB_SYS_REQUEST_OUTSIDE_DOUBLE_ARRAY,          // Запрос за пределами double-массива
   MSG_LIB_SYS_REQUEST_OUTSIDE_STRING_ARRAY,          // Запрос за пределами string-массива
   
   MSG_LIB_TEXT_YES,                                  // Да
   MSG_LIB_TEXT_NO,                                   // Нет
   MSG_LIB_TEXT_AND,                                  // и

...

   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_UPPER,             // Точка привязки в правом верхнем углу
   MSG_GRAPH_OBJ_TEXT_ANCHOR_UPPER,                   // Точка привязки сверху по центру
   MSG_GRAPH_OBJ_TEXT_ANCHOR_CENTER,                  // Точка привязки строго по центру объекта
   
   MSG_GRAPH_OBJ_TEXT_TIME_PRICE,                     // Координаты цена/время
   MSG_GRAPH_OBJ_TEXT_PIVOT,                          // Опорная точка
   MSG_GRAPH_OBJ_TEXT_LEVELSVALUE_ALL,                // Значения уровней
   MSG_GRAPH_OBJ_TEXT_LEVEL,                          // Уровень
   MSG_GRAPH_OBJ_TEXT_BMP_FILE_STATE_ON,              // Состояние "On"
   MSG_GRAPH_OBJ_TEXT_BMP_FILE_STATE_OFF,             // Состояние "Off"

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Не удалось получить список вновь добавленных объектов
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Не удалось изъять графический объект из списка
   
   MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR,           // Создан индикатор контроля и отправки событий
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR,    // Не удалось создать индикатор контроля и отправки событий
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                       // Закрыто окон графиков:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,            // С ними удалено объектов:
   
  };
//+------------------------------------------------------------------+

и тексты сообщений, соответствующие вновь добавленным индексам:

   {"Не удалось добавить объект-буфер в список","Failed to add buffer object to list"},
   {"Не удалось создать объект \"Индикаторный буфер\"","Failed to create object \"Indicator buffer\""},
   {"Не удалось добавить объект в список","Failed to add object to the list"},
   
   {"Не удалось создать объект long-данных","Failed to create long-data object"},
   {"Не удалось создать объект double-данных","Failed to create double-data object"},
   {"Не удалось создать объект string-данных","Failed to create string-data object"},
   
   {"Не удалось уменьшить размер long-массива","Failed to reduce the size of the long-array"},
   {"Не удалось уменьшить размер double-массива","Failed to reduce the size of the double-array"},
   {"Не удалось уменьшить размер string-массива","Failed to reduce the size of the string-array"},

   {"Не удалось получить объект long-данных","Failed to get long-data object"},
   {"Не удалось получить объект double-данных","Failed to get double-data object"},
   {"Не удалось получить объект string-данных","Failed to get string-data object"},
   
   {"Запрос за пределами long-массива","Data requested outside the long-array"},
   {"Запрос за пределами double-массива","Data requested outside the double-array"},
   {"Запрос за пределами string-массива","Data requested outside the string-array"},
   

   {"Да","Yes"},
   {"Нет","No"},
   {"и","and"},

...

   {"Точка привязки в правом верхнем углу","Anchor point at the upper right corner"},
   {"Точка привязки сверху по центру","Anchor point above in the center"},
   {"Точка привязки строго по центру объекта","Anchor point strictly in the center of the object"},
   
   {"Координаты цена/время","Price/time coordinates"},
   {"Опорная точка ","Pivot point "},
   {"Значения уровней","Level values"},
   {"Уровень ","Level "},
   {"Состояние \"On\"","State \"On\""},
   {"Состояние \"Off\"","State \"Off\""},
   
//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   
   {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"},
   {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"},
   {"Закрыто окон графиков: ","Closed chart windows: "},
   {"С ними удалено объектов: ","Objects removed with them: "},
   
  };
//+---------------------------------------------------------------------+


В папке сервисных функций \MQL5\Include\DoEasy\Services\ создадим новый файл XDimArray.mqh класса CXDimArray.

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

Иерархия классов будет такой:

  • CObject --> Класс абстрактного типа данных --> класс типа данных,
  • Класс CArrayObj --> класс одного измерения, хранящий в себе список классов типов данных
  • Класс CArrayObj --> класс многомерного массива, хранящий в себе список объектов класса одного измерения

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

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

Класс многомерного массива будет являться списком CArrayObj, хранящим в себе указатели на экземпляры объектов классов одного измерения массива. То есть сам список будет первым измерением массива, а классы одного измерения будут динамически расширяемыми объектами первого измерения. Если в нём будет лишь один объект одного измерения размером 1, то обращение к нему будет соответствовать записи array[0][0], если в нём будет два объекта одного измерения размером 1, то обращение к первому соответствует записи array[0][0], а обращение ко второму — записи array[0][1].
Естественно, если объекты одного измерения будут иметь размеры более 1, то обращение к ним будет соответствовать записям

array[0][0],  array[0][1],  array[0][2], ...,  array[0][N],
array[1][0],  array[1][1],  array[1][2], ...,  array[1][N].

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

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

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

В уже созданном файле XDimArray.mqh класса CXDimArray подключим файлы класса CArrayObj и класса сообщений библиотеки и напишем класс абстрактной единицы данных, унаследованный от базового объекта CObject:

//+------------------------------------------------------------------+
//|                                                    XDimArray.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Message.mqh"
//+------------------------------------------------------------------+
//| Класс абстрактной единицы данных                                 |
//+------------------------------------------------------------------+
class CDataUnit : public CObject
  {
private:
   int               m_type;  
protected:
                     CDataUnit(int type)  { this.m_type=type;        }
public:
   virtual int       Type(void)     const { return this.m_type;      }
                     CDataUnit(){ this.m_type=OBJECT_DE_TYPE_OBJECT; }
  };
//+------------------------------------------------------------------+

Здесь мы имеем приватную переменную для хранения типа хранимых данных в объектах-наследниках, защищённый параметрический конструктор, в который передаётся тип хранимых данных объекта-наследника и публичный виртуальный метод, возвращающий тип хранимых данных, записанный в переменную m_type.
В конструкторе по умолчанию в переменную m_type записываем тип данных OBJECT_DE_TYPE_OBJECT.

Далее под этим классом в листинге, напишем класс единицы целочисленных данных:

//+------------------------------------------------------------------+
//| Класс единицы целочисленных данных                               |
//+------------------------------------------------------------------+
class CDataUnitLong : public CDataUnit
  {
public:
   long              Value;
                     CDataUnitLong() : CDataUnit(OBJECT_DE_TYPE_LONG){}
  };
//+------------------------------------------------------------------+

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

Теперь, ниже по листингу кода, создадим класс объекта одного измерения long-массива:

//+------------------------------------------------------------------+
//| Класс одного измерения long-массива                              |
//+------------------------------------------------------------------+
class CDimLong : public CArrayObj
  {

  }

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

В приватной секции класса добавим метод, получающий объект long-данных из массива:

//+------------------------------------------------------------------+
//| Класс одного измерения long-массива                              |
//+------------------------------------------------------------------+
class CDimLong : public CArrayObj
  {
private:
//--- Получает объект long-данных из массива
   CDataUnitLong    *GetData(const string source,const int index) const
                       {
                        //--- Получаем указатель на объект по переданному в метод индексу. Если передан индекс меньше нуля, то будет ноль
                        CDataUnitLong *data=this.At(index<0 ? 0 : index);
                        //--- Если объект получить не удалось
                        if(data==NULL)
                          {
                           //--- если передан индекс больше, чем есть объектов в списке - выводим сообщение о превышении размера списка
                           if(index>this.Total()-1)
                              ::Print(source,CMessage::Text(MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY)," (",index,"/",this.Total(),")");
                           //--- иначе - выводим сообщение, что указатель на объект получить не удалось
                           else
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_LONG_DATA_OBJ);
                          }
                        //--- Возвращаем указатель на объект, либо NULL в случае ошибки
                        return data;
                       }

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

Далее напишем метод, добавляющий указанное количество ячеек с объектами в конец массива:

//--- Добавляет указанное количество ячеек с объектами в конец массива
   bool              AddQuantity(const string source,const int total,CObject *object)
                       {
                        //--- Объявляем переменную для хранения результата добавления объектов в список
                        bool res=true;
                        //--- в списке по переданному в метод количеству добавляемых объектов
                        for(int i=0;i<total;i++)
                          {
                           //--- если объект добавить в список не удалось
                           if(!this.Add(object))
                             {
                              //--- выодим об этом сообщение, добавляем к значению переменной false
                              //--- и идём на следующую итерацию цикла
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                              res &=false;
                              continue;
                             }
                          }
                        //--- Возвращаем общий результат добавления заданного количества объектов в список
                        return res;
                       }

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

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

В публичной секции класса добавим метод для инициализации массива:

public:
//--- Инициализирует массив
   void              Initialize(const int total,const long value=0)
                       {
                        //--- Очищаем массив и увеличиваем его размер на указанное в параметрах количество, указывая значение по умолчанию
                        this.Clear();
                        this.Increase(total,value);
                       }

Очищаем массив при помощи метода Clear() родительского класса и увеличиваем его размер на указанную величину при помощи метода Increase():

//--- Увеличивает количество ячеек с данными на указанную величину, возвращает количество добавленных элементов
   int               Increase(const int total,const long value=0)
                       {
                        //--- Запоминаем текущий размер массива
                        int size_prev=this.Total();
                        //--- Создаём новый объект long-данных
                        CDataUnitLong *data=new CDataUnitLong();
                        //--- Если объект создать не удалось - сообщаем об этом и возвращаем ноль
                        if(data==NULL)
                          {
                           ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ));
                           return 0;
                          }
                        //--- Записываем указанное значение во вновь созданный объект
                        data.Value=value;
                        //--- Добавляем указанное количество экземпляров объекта в список
                        //--- и возвращаем разницу между полученным размером массива и прошлым
                        this.AddQuantity(DFUN,total,data);
                        return this.Total()-size_prev;
                       }

Метод добавляет к массиву указанное количество long-данных и возвращает количество добавленных данных.

Напишем метод, уменьшающий количество ячеек с данными на указанную величину:

//--- Уменьшает количество ячеек с данными на указанную величину, возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               Decrease(const int total)
                       {
                        //--- Если после удаления ячеек массива, в массиве не останется ни одной ячейки - возвращаем ноль
                        if(total>this.Total()-1)
                           return 0;
                        //--- Запоминаем текущий размер массива
                        int total_prev=this.Total();
                        //--- Рассчитываем начальный индекс, с которого нужно удалить ячейки массива
                        int from=this.Total()-total;
                        //--- Конечный индекс - всегда конец массива
                        int to=this.Total()-1;
                        //--- Если не удалось удалить указанное количество ячеек массива - сообщаем об этом в журнал
                        if(!this.DeleteRange(from,to))
                           CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_DECREASE_LONG_ARRAY);
                        //--- возвращаем разницу между прошлым размером массива и полученным в результате удаления ячеек
                        return total_prev-this.Total();
                       }

Так как в массиве всегда должен быть хотя бы один элемент, то сначала проверяем, что удаление требуемого количества оставит один и более элементов в массиве. Далее удаляем элементы из конца списка в указанном количестве при помощи метода DeleteRange() родительского класса. В итоге возвращаем количество удалённых элементов.

Напишем метод, устанавливающий новый размер массива:

//--- Устанавливает новый размер массива
   bool              SetSize(const int size,const long initial_value=0)
                       {
                        //--- Если передан нулевой размер - возвращаем false
                        if(size==0)
                           return false;
                        //--- Рассчитываем количество ячеек, которое требуется добавить или удалить для получения требуемого размера массива
                        int total=fabs(size-this.Total());
                        //--- Если переданный размер массива больше текущего -
                        //--- возвращаем результат добавления рассчитанного количества ячеек к массиву
                        if(size>this.Total())
                           return(this.Increase(total,initial_value)==total);
                        //--- иначе, если переданный размер массива меньше текущего - 
                        //--- возвращаем результат удаления рассчитанного количества ячеек из массива
                        else if(size<this.Total())
                           return(Decrease(total)==total);
                        //--- Если переданный размер равен текущему - просто возвращаем true
                        return true;
                       }

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

Добавим метод, устанавливающий значение в указанную ячейку массива:

//--- Устанавливает значение в указанную ячейку массива
   bool              Set(const int index,const long value)
                       {
                        //--- Получаем указатель на объект данных по указанному индексу
                        CDataUnitLong *data=this.GetData(DFUN,index);
                        //--- Если объект получить не удалось - возвращаем false
                        if(data==NULL)
                           return false;
                        //--- Устанавливаем в полученный объект переданное в метод значение и возвращаем true
                        data.Value=value;
                        return true;
                       }

Здесь: получаем объект данных по указанному индексу и устанавливаем ему переданное в метод значение. При успешном получении объекта возвращаем true, в случае неудачи (об этом сообщит в журнал приватный метод GetData()) — возвращаем false.

Метод, возвращающий количество данных в массиве:

//--- Возвращает количество данных (размер массива методом Total() ролительского класса CArrayObj)
   int               Size(void) const { return this.Total(); }

Метод просто возвращает значение количества элементов в списке методом Total() класса CArray, являющимся родителем класса CArrayObj.

Напишем метод, возвращающий значение по указанному индексу:

//--- Возвращает значение по указанному индексу
   long              Get(const int index) const
                       {
                        //--- Получаем указатель на объект данных по индексу
                        CDataUnitLong *data=this.GetData(DFUN,index);
                        //--- В случае, если объект получить удалось,
                        //--- возвращаем значение, записанное в объект,
                        //--- иначе - возвращаем ноль
                        return(data!=NULL ? data.Value : 0);
                       }

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

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

   bool              Get(const int index, long &value) const
                       {
                        //--- Записываем начальное значение в value, переданное в метод по ссылке
                        value=0;
                        //--- Получаем указатель на объект данных по индексу
                        CDataUnitLong *data=this.GetData(DFUN,index);
                        //--- Если объект получить не удалось - возвращаем false
                        if(data==NULL)
                           return false;
                        //--- Записываем в value значение, хранящееся в объекте, и возвращаем true
                        value = data.Value;
                        return true;
                       }


В конструкторе по умолчанию просто инициализируем массив методом Initialize() с указанием размера массива, равным 1 и значением по умолчанию, равным 0.
В параметрическом конструкторе
указываем, каким размером должен быть массив и какое должно быть значение по умолчанию.
В деструкторе класса удаляем элементы массива и очищаем массив с полным освобождением памяти массива.

//--- Конструкторы
                     CDimLong(void)                               { this.Initialize(1);            }
                     CDimLong(const int total,const long value=0) { this.Initialize(total,value);  }
//--- Деструктор
                    ~CDimLong(void)
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+


Далее, в этом же файле создадим класс динамического многомерного long-массива:

//+------------------------------------------------------------------+
//| Класс динамического многомерного long-массива                    |
//+------------------------------------------------------------------+
class CXDimArrayLong : public CArrayObj
  {
  
  }

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

Для понимания:

Индекс 0 списка класса CXDimArrayLong указывает на объект класса CDimLong, находящийся первым в этом списке; индекс 0 класса CDimLong указывает на объект класса CDataUnitLong, находящийся первым в списке класса CDimLong.

Это равносильно записи array[0][0];

Индекс 1 списка класса CXDimArrayLong указывает на объект класса CDimLong, находящийся вторым в этом списке; индекс 0 класса CDimLong указывает на объект класса CDataUnitLong, находящийся первым в списке класса CDimLong.

Это равносильно записи array[1][0];

------

Индекс 0 списка класса CXDimArrayLong указывает на объект класса CDimLong, находящийся первым в этом списке; индекс 1 класса CDimLong указывает на объект класса CDataUnitLong, находящийся вторым в списке класса CDimLong.

Это равносильно записи array[0][1];

Индекс 1 списка класса CXDimArrayLong указывает на объект класса CDimLong, находящийся вторым в этом списке; индекс 1 класса CDimLong указывает на объект класса CDataUnitLong, находящийся вторым в списке класса CDimLong.

Это равносильно записи array[1][1];

и т.д.


В приватной секции класса добавим метод, возвращающий массив данных из первой размерности:

//+------------------------------------------------------------------+
//| Класс динамического многомерного long-массива                    |
//+------------------------------------------------------------------+
class CXDimArrayLong : public CArrayObj
  {
private:
//--- Возвращает массив данных из первой измерности
   CDimLong         *GetDim(const string source,const int index) const
                       {
                        //--- Получаем объект массива первого измерения по индексу
                        CDimLong *dim=this.At(index<0 ? 0 : index);
                        //--- Если указатель на объект получить не удалось
                        if(dim==NULL)
                          {
                           //--- если индекс за пределами массива - сообщаем о запросе за пределами массива
                           if(index>this.Total()-1)
                              ::Print(source,CMessage::Text(MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY)," (",index,"/",this.Total(),")");
                           //--- иначе - сообщаем об ошибке получения указателя на массив
                           else
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_LONG_DATA_OBJ);
                          }
                        //--- Возвращаем либо указатель на объект, либо NULL при ошибке
                        return dim;
                       }

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

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

//--- Добавляет новое измерение в первую размерность
   bool              AddNewDim(const string source,const int size,const long initial_value=0)
                       {
                        //--- Создаём новый объект-массив 
                        CDimLong *dim=new CDimLong(size,initial_value);
                        //--- Если объект создать не удалось - сообщаем об этом в журнал и возвращаем false
                        if(dim==NULL)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ);
                           return false;
                          }
                        //--- Если не удалось добавить объект в список - удаляем объект, сообщаем в журнал об ошибке и возвращаем false
                        if(!this.Add(dim))
                          {
                           delete dim;
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                           return false;
                          }
                        //--- Объект успешно создан и добавлен в список
                        return true;
                       }

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

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

public:
//--- Увеличивает количество ячеек с данными на указанную величину total в первой размерности,
//--- возвращает количество добавленных элементов в размерность. Добавляемые ячейки имеют размер size
   int               IncreaseRangeFirst(const int total,const int size,const long initial_value=0)
                       {
                        //--- Запоминаем текущей размер массива
                        int total_prev=this.Total();
                        //--- В цикле по указанному количеству добавляем новые объекты в массив
                        for(int i=0;i<total;i++)
                           this.AddNewDim(DFUN,size,initial_value);
                        //--- Возвращаем разницу между полученным размером массива и прошлым
                        return(this.Total()-total_prev);
                       }
//--- Увеличивает количество ячеек с данными на указанную величину total в указанной размерности range,
//--- возвращает количество добавленных элементов в изменённую размерность
   int               IncreaseRange(const int range,const int total,const long initial_value=0)
                       {
                        //--- Получаем указатель на массив по индексу range
                        CDimLong *dim=this.GetDim(DFUN,range);
                        //--- Возвращаем результат увеличения размера массива на значение total, либо ноль при ошибке
                        return(dim!=NULL ? dim.Increase(total,initial_value) : 0);
                       }
//--- Уменьшает количество ячеек с данными в первой размерности на указанную величину,
//--- возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               DecreaseRangeFirst(const int total)
                       {
                        //--- Проверяем, что после уменьшения останется хотя бы один элемент в массиве,
                        //--- если нет - возвращаем false
                        if(total>this.Total()-1)
                           return 0;
                        //--- Запоминаем текущий размер массива
                        int total_prev=this.Total();
                        //--- Рассчитываем начальный индекс, с которого удалять элементы массива
                        int from=this.Total()-total;
                        //--- Конечный индекс - всегда последний элемент массива
                        int to=this.Total()-1;
                        //--- Если не удалось удалить заданное количество элементов - сообщаем об этом в журнал
                        if(!this.DeleteRange(from,to))
                           CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_DECREASE_LONG_ARRAY);
                        //--- Возвращаем количество удалённых элементов массива
                        return total_prev-this.Total();
                       }                      
//--- Уменьшает количество ячеек с данными на указанную величину в указанной размерности,
//--- возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               DecreaseRange(const int range,const int total)
                       {
                        //--- Получаем указатель на массив по индексу range
                        CDimLong *dim=this.GetDim(DFUN,range);
                        //--- Возвращаем результат уменьшения размера массива на значение total, либо ноль при ошибке
                        return(dim!=NULL ? dim.Decrease(total) : 0);
                       }
//--- Устанавливает новый размер массива в указанной размерности
   bool              SetSizeRange(const int range,const int size,const long initial_value=0)
                       {
                        //--- Получаем указатель на массив по индексу range
                        CDimLong *dim=this.GetDim(DFUN,range);
                        //--- Возвращаем результат установки размера массива на значение size, либо false при ошибке
                        return(dim!=NULL ? dim.SetSize(size,initial_value) : false);
                       }
//--- Устанавливает значение в указанную ячейку массива указанного измерения
   bool              Set(const int index,const int range,const long value)
                       {
                        //--- Получаем указатель на массив по индексу index
                        CDimLong *dim=this.GetDim(DFUN,index);
                        //--- Возвращаем результат установки значения в ячейку массива по индексу range, либо false при ошибке
                        return(dim!=NULL ? dim.Set(range,value) : false);
                       }
//--- Возвращает значение по указанному индексу указанного измерения
   long              Get(const int index,const int range) const
                       {
                        //--- Получаем указатель на массив по индексу index
                        CDimLong *dim=this.GetDim(DFUN,index);
                        //--- Возвращаем результат получения значения из ячейки массива по индексу range, либо 0 при ошибке
                        return(dim!=NULL ? dim.Get(range) : 0);
                       }
   bool              Get(const int index,const int range,long &value) const
                       {
                        //--- Получаем указатель на массив по индексу index
                        CDimLong *dim=this.GetDim(DFUN,index);
                        //--- Возвращаем результат получения значения из ячейки массива по индексу range в переменную value,
                        //--- либо false при ошибке (в value будет записан ноль)
                        return(dim!=NULL ? dim.Get(range,value) : false);
                       }
//--- Возвращает количество данных (размер массива указанного измерения)
   int               Size(const int range) const
                       {
                        //--- Получаем указатель на массив по индексу range
                        CDimLong *dim=this.GetDim(DFUN,range);
                        //--- Возвращаем размер полученного массива по индексу, либо ноль при ошибке
                        return(dim!=NULL ? dim.Size() : 0);
                       }
//--- Возвращает общее количество данных (совокупный размер всех измерений)
   int               Size(void) const
                       {
                        //--- Устанавливаем начальный размер
                        int size=0;
                        //--- В цикле по всем массивам в списке
                        for(int i=0;i<this.Total();i++)
                          {
                           //--- получаем очередной массив.
                           CDimLong *dim=this.GetDim(DFUN,i);
                           //--- Если массив получить не удалось - идём к следующему
                           if(dim==NULL)
                              continue;
                           //--- Прибавляем к значению размера размер массива
                           size+=dim.Size();
                          }
                        //--- Возвращаем полученное значение
                        return size;
                       }
//--- Конструктор
                     CXDimArrayLong()
                       {
                        //--- Очищаем список и добавляем к нему один массив
                        this.Clear();
                        this.Add(new CDimLong(1));
                       }
                     CXDimArrayLong(int first_dim_size,const int dim_size,const long initial_value=0)
                       {
                        //--- Очищаем список
                        this.Clear();
                        int total=(first_dim_size<1 ? 1 : first_dim_size);
                        //--- В цикле по нужному количеству массивов, рассчитанному в total из first_dim_size
                        //--- добавляем к списку новые массивы с указанным количеством элементов в dim_size
                        for(int i=0;i<total;i++)
                           this.Add(new CDimLong(dim_size,initial_value));
                       }
//--- Деструктор
                    ~CXDimArrayLong()
                       {
                        //--- Удаляем элементы массива и
                        //--- очищаем массив с полным освобождением памяти массива
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+

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

Теперь нам нужно написать точно такие же классы для данных типа double и string. Классы совершенно идентичны вышерассмотренным, поэтому оставим их для самостоятельного изучения:

//+------------------------------------------------------------------+
//| Класс единицы вещественных данных                                |
//+------------------------------------------------------------------+
class CDataUnitDouble : public CDataUnit
  {
public:
   double            Value;
//--- Конструктор
                     CDataUnitDouble() : CDataUnit(OBJECT_DE_TYPE_DOUBLE){}
  };
//+------------------------------------------------------------------+
//| Класс одного измерения double-массива                            |
//+------------------------------------------------------------------+
class CDimDouble : public CArrayObj
  {
private:
//--- Получает объект long-данных из массива
   CDataUnitDouble  *GetData(const string source,const int index) const
                       {
                        CDataUnitDouble *data=this.At(index<0 ? 0 : index);
                        if(data==NULL)
                          {
                           if(index>this.Total()-1)
                              ::Print(source,CMessage::Text(MSG_LIB_SYS_REQUEST_OUTSIDE_DOUBLE_ARRAY)," (",index,"/",this.Total(),")");
                           else
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_DOUBLE_DATA_OBJ);
                          }
                        return data;
                       }
//--- Добавляет указанное количество ячеек с объектами в конец массива
   bool              AddQuantity(const string source,const int total,CObject *object)
                       {
                        bool res=true;
                        for(int i=0;i<total;i++)
                          {
                           if(!this.Add(object))
                             {
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                              res &=false;
                              continue;
                             }
                          }
                        return res;
                       }
public:
//--- Инициализирует массив
   void              Initialize(const int total,const double value=0)
                       {
                        this.Clear();
                        this.Increase(total,value);
                       }
//--- Увеличивает количество ячеек с данными на указанную величину, возвращает количество добавленных элементов
   int               Increase(const int total,const double value=0)
                       {
                        int size_prev=this.Total();
                        CDataUnitDouble *data=new CDataUnitDouble();
                        if(data==NULL)
                          {
                           ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_DOUBLE_DATA_OBJ));
                           return 0;
                          }
                        data.Value=value;
                        this.AddQuantity(DFUN,total,data);
                        return this.Total()-size_prev;
                       }
//--- Уменьшает количество ячеек с данными на указанную величину, возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               Decrease(const int total)
                       {
                        if(total>this.Total()-1)
                           return 0;
                        int total_prev=this.Total();
                        int from=this.Total()-total;
                        int to=this.Total()-1;
                        if(!this.DeleteRange(from,to))
                           CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_DECREASE_DOUBLE_ARRAY);
                        return total_prev-this.Total();
                       }
//--- Устанавливает новый размер массива
   bool              SetSize(const int size,const double initial_value=0)
                       {
                        if(size==0)
                           return false;
                        int total=fabs(size-this.Total());
                        if(size>this.Total())
                           return(this.Increase(total,initial_value)==total);
                        else if(size<this.Total())
                           return(Decrease(total)==total);
                        return true;
                       }
//--- Устанавливает значение в указанную ячейку массива
   bool              Set(const int index,const double value)
                       {
                        CDataUnitDouble *data=this.GetData(DFUN,index);
                        if(data==NULL)
                           return false;
                        data.Value=value;
                        return true;
                       }
//--- Возвращает количество данных (размер массива)
   int               Size(void) const { return this.Total(); }
   
//--- Возвращает значение по указанному индексу
   double            Get(const int index) const
                       {
                        CDataUnitDouble *data=this.GetData(DFUN,index);
                        return(data!=NULL ? data.Value : 0);
                       }
   bool              Get(const int index, double &value) const
                       {
                        value=0;
                        CDataUnitDouble *data=this.GetData(DFUN,index);
                        if(data==NULL)
                           return false;
                        value = data.Value;
                        return true;
                       }
//--- Конструкторы
                     CDimDouble(void)                                { this.Initialize(1);            }
                     CDimDouble(const int total,const double value=0){ this.Initialize(total,value);  }
//--- Деструктор
                    ~CDimDouble(void)
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+
//| Класс динамического многомерного double-массива                  |
//+------------------------------------------------------------------+
class CXDimArrayDouble : public CArrayObj
  {
private:
//--- Возвращает массив данных из первой размерности
   CDimDouble       *GetDim(const string source,const int index) const
                       {
                        CDimDouble *dim=this.At(index<0 ? 0 : index);
                        if(dim==NULL)
                          {
                           if(index>this.Total()-1)
                              ::Print(source,CMessage::Text(MSG_LIB_SYS_REQUEST_OUTSIDE_DOUBLE_ARRAY)," (",index,"/",this.Total(),")");
                           else
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_DOUBLE_DATA_OBJ);
                          }
                        return dim;
                       }
//--- Добавляет новое измерение в первую размерность
   bool              AddNewDim(const string source,const int size,const double initial_value=0)
                       {
                        CDimDouble *dim=new CDimDouble(size,initial_value);
                        if(dim==NULL)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_CREATE_DOUBLE_DATA_OBJ);
                           return false;
                          }
                        if(!this.Add(dim))
                          {
                           delete dim;
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                           return false;
                          }
                        return true;
                       }
public:
//--- Увеличивает количество ячеек с данными на указанную величину total в первой размерности,
//--- возвращает количество добавленных элементов в размерность. Добавляемые ячейки имеют размер size
   int               IncreaseRangeFirst(const int total,const int size,const long initial_value=0)
                       {
                        int total_prev=this.Total();
                        for(int i=0;i<total;i++)
                           this.AddNewDim(DFUN,size,initial_value);
                        return(this.Total()-total_prev);
                       }
//--- Увеличивает количество ячеек с данными на указанную величину total в указанной размерности range,
//--- возвращает количество добавленных элементов в изменённую размерность
   int               IncreaseRange(const int range,const int total,const double initial_value=0)
                       {
                        CDimDouble *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Increase(total,initial_value) : 0);
                       }
//--- Уменьшает количество ячеек с данными в первой размерности на указанную величину,
//--- возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               DecreaseRangeFirst(const int total)
                       {
                        if(total>this.Total()-1)
                           return 0;
                        int total_prev=this.Total();
                        int from=this.Total()-total;
                        int to=this.Total()-1;
                        if(!this.DeleteRange(from,to))
                           CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_DECREASE_DOUBLE_ARRAY);
                        return total_prev-this.Total();
                       }                      
//--- Уменьшает количество ячеек с данными на указанную величину в указанной размерности,
//--- возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               DecreaseRange(const int range,const int total)
                       {
                        CDimDouble *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Decrease(total) : 0);
                       }
//--- Устанавливает новый размер массива в указанной размерности
   bool              SetSizeRange(const int range,const int size,const double initial_value=0)
                       {
                        CDimDouble *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.SetSize(size,initial_value) : false);
                       }
//--- Устанавливает значение в указанную ячейку массива указанного измерения
   bool              Set(const int index,const int range,const double value)
                       {
                        CDimDouble *dim=this.GetDim(DFUN,index);
                        return(dim!=NULL ? dim.Set(range,value) : false);
                       }
//--- Возвращает значение по указанному индексу указанного измерения
   double            Get(const int index,const int range) const
                       {
                        CDimDouble *dim=this.GetDim(DFUN,index);
                        return(dim!=NULL ? dim.Get(range) : 0);
                       }
   bool              Get(const int index,const int range,double &value) const
                       {
                        CDimDouble *dim=this.GetDim(DFUN,index);
                        return(dim!=NULL ? dim.Get(range,value) : false);
                       }
//--- Возвращает количество данных (размер массива указанного измерения)
   int               Size(const int range) const
                       {
                        CDimDouble *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Size() : 0);
                       }
//--- Возвращает общее количество данных (совокупный размер всех измерений)
   int               Size(void) const
                       {
                        int size=0;
                        for(int i=0;i<this.Total();i++)
                          {
                           CDimDouble *dim=this.GetDim(DFUN,i);
                           if(dim==NULL)
                              continue;
                           size+=dim.Size();
                          }
                        return size;
                       }
//--- Конструктор
                     CXDimArrayDouble()
                       {
                        this.Clear();
                        this.Add(new CDimDouble(1));
                       }
                     CXDimArrayDouble(int first_dim_size,const int dim_size,const double initial_value=0)
                       {
                        this.Clear();
                        int total=(first_dim_size<1 ? 1 : first_dim_size);
                        for(int i=0;i<total;i++)
                           this.Add(new CDimDouble(dim_size,initial_value));
                       }
//--- Деструктор
                    ~CXDimArrayDouble()
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс единицы строковых данных                                   |
//+------------------------------------------------------------------+
class CDataUnitString : public CDataUnit
  {
public:
   string            Value;
                     CDataUnitString() : CDataUnit(OBJECT_DE_TYPE_STRING){}
  };
//+------------------------------------------------------------------+
//| Класс одного измерения string-массива                            |
//+------------------------------------------------------------------+
class CDimString : public CArrayObj
  {
private:
//--- Получает объект long-данных из массива
   CDataUnitString  *GetData(const string source,const int index)
                       {
                        CDataUnitString *data=this.At(index<0 ? 0 : index);
                        if(data==NULL)
                          {
                           if(index>this.Total()-1)
                              ::Print(source,CMessage::Text(MSG_LIB_SYS_REQUEST_OUTSIDE_STRING_ARRAY)," (",index,"/",this.Total(),")");
                           else
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_STRING_DATA_OBJ);
                          }
                        return data;
                       }
//--- Добавляет указанное количество ячеек с объектами в конец массива
   bool              AddQuantity(const string source,const int total,CObject *object)
                       {
                        bool res=true;
                        for(int i=0;i<total;i++)
                          {
                           if(!this.Add(object))
                             {
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                              res &=false;
                              continue;
                             }
                          }
                        return res;
                       }
public:
//--- Инициализирует массив
   void              Initialize(const int total,const string value="")
                       {
                        this.Clear();
                        this.Increase(total,value);
                       }
//--- Увеличивает количество ячеек с данными на указанную величину, возвращает количество добавленных элементов
   int               Increase(const int total,const string value="")
                       {
                        int size_prev=this.Total();
                        CDataUnitString *data=new CDataUnitString();
                        if(data==NULL)
                          {
                           ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_STRING_DATA_OBJ));
                           return 0;
                          }
                        data.Value=value;
                        this.AddQuantity(DFUN,total,data);
                        return this.Total()-size_prev;
                       }
//--- Уменьшает количество ячеек с данными на указанную величину, возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               Decrease(const int total)
                       {
                        if(total>this.Total()-1)
                           return 0;
                        int total_prev=this.Total();
                        int from=this.Total()-total;
                        int to=this.Total()-1;
                        if(!this.DeleteRange(from,to))
                           CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_DECREASE_STRING_ARRAY);
                        return total_prev-this.Total();
                       }
//--- Устанавливает новый размер массива
   bool              SetSize(const int size,const string initial_value="")
                       {
                        if(size==0)
                           return false;
                        int total=fabs(size-this.Total());
                        if(size>this.Total())
                           return(this.Increase(total,initial_value)==total);
                        else if(size<this.Total())
                           return(Decrease(total)==total);
                        return true;
                       }
//--- Устанавливает значение в указанную ячейку массива
   bool              Set(const int index,const string value)
                       {
                        CDataUnitString *data=this.GetData(DFUN,index);
                        if(data==NULL)
                           return false;
                        data.Value=value;
                        return true;
                       }
//--- Возвращает количество данных (размер массива)
   int               Size(void) const { return this.Total(); }
   
//--- Возвращает значение по указанному индексу
   string            Get(const int index)
                       {
                        CDataUnitString *data=this.GetData(DFUN,index);
                        return(data!=NULL ? data.Value : "");
                       }
   bool              Get(const int index, string &value)
                       {
                        value="";
                        CDataUnitString *data=this.GetData(DFUN,index);
                        if(data==NULL)
                           return false;
                        value = data.Value;
                        return true;
                       }
//--- Конструкторы
                     CDimString(void)                                   { this.Initialize(1);            }
                     CDimString(const int total,const string value="")  { this.Initialize(total,value);  }
//--- Деструктор
                    ~CDimString(void)
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+
//| Класс динамического многомерного string-массива                  |
//+------------------------------------------------------------------+
class CXDimArrayString : public CArrayObj
  {
private:
//--- Возвращает массив данных из первой размерности
   CDimString       *GetDim(const string source,const int index) const
                       {
                        CDimString *dim=this.At(index<0 ? 0 : index);
                        if(dim==NULL)
                          {
                           if(index>this.Total()-1)
                              ::Print(source,CMessage::Text(MSG_LIB_SYS_REQUEST_OUTSIDE_STRING_ARRAY)," (",index,"/",this.Total(),")");
                           else
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_STRING_DATA_OBJ);
                          }
                        return dim;
                       }
//--- Добавляет новое измерение в первую размерность
   bool              AddNewDim(const string source,const int size,const string initial_value="")
                       {
                        CDimString *dim=new CDimString(size,initial_value);
                        if(dim==NULL)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_CREATE_STRING_DATA_OBJ);
                           return false;
                          }
                        if(!this.Add(dim))
                          {
                           delete dim;
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                           return false;
                          }
                        return true;
                       }
public:
//--- Увеличивает количество ячеек с данными на указанную величину total в первой размерности,
//--- возвращает количество добавленных элементов в размерность. Добавляемые ячейки имеют размер size
   int               IncreaseRangeFirst(const int total,const int size,const string initial_value="")
                       {
                        int total_prev=this.Total();
                        for(int i=0;i<total;i++)
                           this.AddNewDim(DFUN,size,initial_value);
                        return(this.Total()-total_prev);
                       }
//--- Увеличивает количество ячеек с данными на указанную величину total в указанной размерности range,
//--- возвращает количество добавленных элементов в изменённую размерность
   int               IncreaseRange(const int range,const int total,const string initial_value="")
                       {
                        CDimString *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Increase(total,initial_value) : 0);
                       }
//--- Уменьшает количество ячеек с данными в первой размерности на указанную величину,
//--- возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               DecreaseRangeFirst(const int total)
                       {
                        if(total>this.Total()-1)
                           return 0;
                        int total_prev=this.Total();
                        int from=this.Total()-total;
                        int to=this.Total()-1;
                        if(!this.DeleteRange(from,to))
                           CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_DECREASE_STRING_ARRAY);
                        return total_prev-this.Total();
                       }                      
//--- Уменьшает количество ячеек с данными на указанную величину в указанной размерности,
//--- возвращает количество удалённых элементов. Самый первый элемент всегда остаётся
   int               DecreaseRange(const int range,const int total)
                       {
                        CDimString *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Decrease(total) : 0);
                       }
//--- Устанавливает новый размер массива в указанной размерности
   bool              SetSizeRange(const int range,const int size,const string initial_value="")
                       {
                        CDimString *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.SetSize(size,initial_value) : false);
                       }
//--- Устанавливает значение в указанную ячейку массива указанного измерения
   bool              Set(const int index,const int range,const string value)
                       {
                        CDimString *dim=this.GetDim(DFUN,index);
                        return(dim!=NULL ? dim.Set(range,value) : false);
                       }
//--- Возвращает значение по указанному индексу указанного измерения
   string            Get(const int index,const int range) const
                       {
                        CDimString *dim=this.GetDim(DFUN,index);
                        return(dim!=NULL ? dim.Get(range) : "");
                       }
   bool              Get(const int index,const int range, string &value) const
                       {
                        CDimString *dim=this.GetDim(DFUN,index);
                        return(dim!=NULL ? dim.Get(range,value) : false);
                       }
//--- Возвращает количество данных (размер массива указанного измерения)
   int               Size(const int range) const
                       {
                        CDimString *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Size() : 0);
                       }
//--- Возвращает общее количество данных (совокупный размер всех измерений)
   int               Size(void) const
                       {
                        int size=0;
                        for(int i=0;i<this.Total();i++)
                          {
                           CDimString *dim=this.GetDim(DFUN,i);
                           if(dim==NULL)
                              continue;
                           size+=dim.Size();
                          }
                        return size;
                       }
//--- Конструктор
                     CXDimArrayString()
                       {
                        this.Clear();
                        this.Add(new CDimString(1));
                       }
                     CXDimArrayString(int first_dim_size,const int dim_size,const string initial_value="")
                       {
                        this.Clear();
                        int total=(first_dim_size<1 ? 1 : first_dim_size);
                        for(int i=0;i<total;i++)
                           this.Add(new CDimString(dim_size,initial_value));
                       }
//--- Деструктор
                    ~CXDimArrayString()
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+


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

Но, прежде чем начнём менять класс графического объекта, нам необходимо сделать, чтобы созданные новые классы были видны в библиотеке на уровне остальных её классов. Для этого подключим файл с этими классами к файлу сервисных функций библиотеки \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//|                                                        DELib.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property strict  // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "Message.mqh"
#include "TimerCounter.mqh"
#include "Pause.mqh"
#include "Colors.mqh"
#include "XDimArray.mqh"
//+------------------------------------------------------------------+
//| Сервисные функции                                                |
//+------------------------------------------------------------------+

Теперь эти классы будут видны всем классам библиотеки.

Двумерный динамический массив для хранения свойств объектов

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

В файле абстрактного стандартного графического объекта \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh в приватной секции расположены массивы для хранения свойств (текущих и прошлых) и методы для получения реального индекса указанного свойства в массиве :

//+------------------------------------------------------------------+
//|                                                 GStdGraphObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\GBaseObj.mqh"
//+------------------------------------------------------------------+
//| Класс абстрактного стандартного графического объекта             |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   long              m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL];         // Целочисленные свойства
   double            m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL];        // Вещественные свойства
   string            m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL];        // Строковые свойства

   long              m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL];    // Целочисленные свойства до изменения
   double            m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL];   // Вещественные свойства до изменения
   string            m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL];   // Строковые свойства до изменения

//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство
   int               IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL;                              }
   int               IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property)  const { return(int)property-GRAPH_OBJ_PROP_INTEGER_TOTAL-GRAPH_OBJ_PROP_DOUBLE_TOTAL;  }

public:

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

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

Удалим из приватной секции все указанные выше массивы и методы и определим на их месте новый класс свойств объекта:

//+------------------------------------------------------------------+
//|                                                 GStdGraphObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\GBaseObj.mqh"
//+------------------------------------------------------------------+
//| Класс абстрактного стандартного графического объекта             |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   //--- Класс свойств объекта
   class CDataPropObj
     {
      
     }

В приватной секции нового класса объявим список для хранения объектов свойств, которые будут представлены классом динамического многомерного массива CXDimArrayLong, переменные для хранения количества целочисленных, вещественных и строковых свойств объекта и два метода, возвращающие индекс массива, по которому фактически расположены double- и string-свойства объекта:

//+------------------------------------------------------------------+
//| Класс абстрактного стандартного графического объекта             |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   //--- Класс свойств объекта
   class CDataPropObj
     {
   private:
      CArrayObj         m_list;        // список объектов-свойств
      int               m_total_int;   // Количество целочисленных параметров
      int               m_total_dbl;   // Количество вещественных параметров
      int               m_total_str;   // Количество строковых параметров
      //--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство
      int               IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property)              const { return(int)property-this.m_total_int;                     }
      int               IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property)              const { return(int)property-this.m_total_int-this.m_total_dbl;    }

В публичной секции класса объявим метод, возвращающий список объектов-свойств, указатели на объекты целочисленных, вещественных и строковых свойств и методы для установки и получения указанных свойств объекта:

   //--- Класс свойств объекта
   class CDataPropObj
     {
   private:
      CArrayObj         m_list;        // список объектов-свойств
      int               m_total_int;   // Количество целочисленных параметров
      int               m_total_dbl;   // Количество вещественных параметров
      int               m_total_str;   // Количество строковых параметров
      //--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство
      int               IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property)              const { return(int)property-this.m_total_int;                     }
      int               IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property)              const { return(int)property-this.m_total_int-this.m_total_dbl;    }
   public:
      //--- Возвращает указатель на (1) список объектов-свойств, объект (2) целочисленных, (3) вещественных, (4) строковых свойств
      CArrayObj        *GetList(void)                                                     { return &this.m_list;                                      }
      CXDimArrayLong   *Long()                                                      const { return this.m_list.At(0);                                 }
      CXDimArrayDouble *Double()                                                    const { return this.m_list.At(1);                                 }
      CXDimArrayString *String()                                                    const { return this.m_list.At(2);                                 }
      //--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
      void              Set(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value)    { this.Long().Set(property,index,value);                    }
      void              Set(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value)   { this.Double().Set(this.IndexProp(property),index,value);  }
      void              Set(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value)   { this.String().Set(this.IndexProp(property),index,value);  }
      //--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
      long              Get(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)         const { return this.Long().Get(property,index);                   }
      double            Get(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)          const { return this.Double().Get(this.IndexProp(property),index); }
      string            Get(ENUM_GRAPH_OBJ_PROP_STRING property,int index)          const { return this.String().Get(this.IndexProp(property),index); }

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

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

      //--- Возвращает размер указанного массива данных первого измерения
      int               Size(const int range) const
                          {
                           if(range<this.m_total_int)
                              return this.Long().Size(range);
                           else if(range<this.m_total_int+this.m_total_dbl)
                              return this.Double().Size(this.IndexProp((ENUM_GRAPH_OBJ_PROP_DOUBLE)range));
                           else if(range<this.m_total_int+this.m_total_dbl+this.m_total_str)
                              return this.String().Size(this.IndexProp((ENUM_GRAPH_OBJ_PROP_STRING)range));
                           return 0;
                          }

В метод передаётся индекс нужного свойства первого измерения массива свойств (range), далее проверяем:
если значение свойства меньше общего количества целочисленных свойств, значит это запрос целочисленного свойства — возвращаем размер массива целочисленных свойств методом Size() объекта Long;
если значение свойства меньше общего количества целочисленных свойств + количество вещественных свойств, значит это запрос вещественного свойства — возвращаем размер массива вещественных свойств методом Size() объекта Double;
если значение свойства меньше общего количества целочисленных свойств + количество вещественных свойств + количество строковых свойств, значит это запрос строкового свойства — возвращаем размер массива строковых свойств методом Size() объекта String.
Метод Size() мы рассматривали выше при создании классов объектов динамических массивов.

В публичной секции класса напишем метод, устанавливающий размер массива в указанной размерности:

      //--- Устанавливает размер массива в указанной размерности
      bool              SetSizeRange(const int range,const int size)
                          {
                           if(range<this.m_total_int)
                              return this.Long().SetSizeRange(range,size);
                           else if(range<this.m_total_int+this.m_total_dbl)
                              return this.Double().SetSizeRange(this.IndexProp((ENUM_GRAPH_OBJ_PROP_DOUBLE)range),size);
                           else if(range<this.m_total_int+this.m_total_dbl+this.m_total_str)
                              return this.String().SetSizeRange(this.IndexProp((ENUM_GRAPH_OBJ_PROP_STRING)range),size);
                           return false;
                          }

Логика метода идентична вышерассмотренному методу получения размера.

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

      //--- Конструктор
                        CDataPropObj(const int prop_total_integer,const int prop_total_double,const int prop_total_string)
                          {
                           this.m_total_int=prop_total_integer;
                           this.m_total_dbl=prop_total_double;
                           this.m_total_str=prop_total_string;
                           this.m_list.Add(new CXDimArrayLong(this.m_total_int, 1));
                           this.m_list.Add(new CXDimArrayDouble(this.m_total_dbl,1));
                           this.m_list.Add(new CXDimArrayString(this.m_total_str,1));
                          }

В деструкторе класса удаляем элементы из списка и высвобождаем память массива:

      //--- Деструктор
                       ~CDataPropObj()
                          {
                           m_list.Clear();
                           m_list.Shutdown();
                          }

В итоге класс свойств объекта у нас получился таким:

   //--- Класс свойств объекта
   class CDataPropObj
     {
   private:
      CArrayObj         m_list;        // список объектов-свойств
      int               m_total_int;   // Количество целочисленных параметров
      int               m_total_dbl;   // Количество вещественных параметров
      int               m_total_str;   // Количество строковых параметров
      //--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство
      int               IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE property)              const { return(int)property-this.m_total_int;                     }
      int               IndexProp(ENUM_GRAPH_OBJ_PROP_STRING property)              const { return(int)property-this.m_total_int-this.m_total_dbl;    }
   public:
      //--- Возвращает указатель на (1) список объектов-свойств, объект (2) целочисленных, (3) вещественных, (4) строковых свойств
      CArrayObj        *GetList(void)                                                     { return &this.m_list;                                      }
      CXDimArrayLong   *Long()                                                      const { return this.m_list.At(0);                                 }
      CXDimArrayDouble *Double()                                                    const { return this.m_list.At(1);                                 }
      CXDimArrayString *String()                                                    const { return this.m_list.At(2);                                 }
      //--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
      void              Set(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value)    { this.Long().Set(property,index,value);                    }
      void              Set(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value)   { this.Double().Set(this.IndexProp(property),index,value);  }
      void              Set(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value)   { this.String().Set(this.IndexProp(property),index,value);  }
      //--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
      long              Get(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)         const { return this.Long().Get(property,index);                   }
      double            Get(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)          const { return this.Double().Get(this.IndexProp(property),index); }
      string            Get(ENUM_GRAPH_OBJ_PROP_STRING property,int index)          const { return this.String().Get(this.IndexProp(property),index); }
      
      //--- Возвращает размер указанного массива данных первого измерения
      int               Size(const int range) const
                          {
                           if(range<this.m_total_int)
                              return this.Long().Size(range);
                           else if(range<this.m_total_int+this.m_total_dbl)
                              return this.Double().Size(this.IndexProp((ENUM_GRAPH_OBJ_PROP_DOUBLE)range));
                           else if(range<this.m_total_int+this.m_total_dbl+this.m_total_str)
                              return this.String().Size(this.IndexProp((ENUM_GRAPH_OBJ_PROP_STRING)range));
                           return 0;
                          }
      //--- Устанавливает размер массива в указанной размерности
      bool              SetSizeRange(const int range,const int size)
                          {
                           if(range<this.m_total_int)
                              return this.Long().SetSizeRange(range,size);
                           else if(range<this.m_total_int+this.m_total_dbl)
                              return this.Double().SetSizeRange(this.IndexProp((ENUM_GRAPH_OBJ_PROP_DOUBLE)range),size);
                           else if(range<this.m_total_int+this.m_total_dbl+this.m_total_str)
                              return this.String().SetSizeRange(this.IndexProp((ENUM_GRAPH_OBJ_PROP_STRING)range),size);
                           return false;
                          }
      //--- Конструктор
                        CDataPropObj(const int prop_total_integer,const int prop_total_double,const int prop_total_string)
                          {
                           this.m_total_int=prop_total_integer;
                           this.m_total_dbl=prop_total_double;
                           this.m_total_str=prop_total_string;
                           this.m_list.Add(new CXDimArrayLong(this.m_total_int, 1));
                           this.m_list.Add(new CXDimArrayDouble(this.m_total_dbl,1));
                           this.m_list.Add(new CXDimArrayString(this.m_total_str,1));
                          }
      //--- Деструктор
                       ~CDataPropObj()
                          {
                           m_list.Clear();
                           m_list.Shutdown();
                          }
     };

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

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

   class CProperty
     {
      
     }

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

   //--- Класс данных текущих и прошлых свойств
   class CProperty
     {
   public:
      CDataPropObj     *Curr;    // Указатель на объект текущих свойств
      CDataPropObj     *Prev;    // Указатель на объект прошлых свойств
      //--- Устанавливает размера массива (size) в указанном измерении (range)
      bool              SetSizeRange(const int range,const int size)
                          {
                           return(this.Curr.SetSizeRange(range,size) && this.Prev.SetSizeRange(range,size) ? true : false);
                          }
      //--- Возвращает размер указанного массива (1) текущих, (2) прошлых данных первого измерения
      int               CurrSize(const int range)  const { return Curr.Size(range); }
      int               PrevSize(const int range)  const { return Prev.Size(range); }
      //--- Копирование текущих данных в прошлые
      void              CurrentToPrevious(void)
                          {
                           //--- Копируем все целочисленные свойства
                           for(int i=0;i<this.Curr.Long().Total();i++)
                              for(int r=0;r<this.Curr.Long().Size(i);r++)
                                 this.Prev.Long().Set(i,r,this.Curr.Long().Get(i,r));
                           //--- Копируем все вещественные свойства
                           for(int i=0;i<this.Curr.Double().Total();i++)
                              for(int r=0;r<this.Curr.Double().Size(i);r++)
                                 this.Prev.Double().Set(i,r,this.Curr.Double().Get(i,r));
                           //--- Копируем все строковые свойства
                           for(int i=0;i<this.Curr.String().Total();i++)
                              for(int r=0;r<this.Curr.String().Size(i);r++)
                                 this.Prev.String().Set(i,r,this.Curr.String().Get(i,r));
                          }
      //--- Конструктор
                        CProperty(const int prop_int_total,const int prop_double_total,const int prop_string_total)
                          {
                           this.Curr=new CDataPropObj(prop_int_total,prop_double_total,prop_string_total);
                           this.Prev=new CDataPropObj(prop_int_total,prop_double_total,prop_string_total);
                          }
     };

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

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


Доработка классов библиотеки

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

   CProperty        *Prop;                                              // Указатель на объект свойств
   int               m_pivots;                                          // Количество опорных точек объекта
//--- Считывает и устанавливает (1) время, (2) цену указанной опорной точки объекта
   void              SetTimePivot(const int index);
   void              SetPricePivot(const int index);
//--- Считывает и устанавливает (1) цвет, (2) стиль, (3) толщину, (4) значение, (5) текст указанного уровня объекта
   void              SetLevelColor(const int index);
   void              SetLevelStyle(const int index);
   void              SetLevelWidth(const int index);
   void              SetLevelValue(const int index);
   void              SetLevelText(const int index);
//--- Считывает и устанавливает имя BMP-файла для объекта "Графическая метка". Индекс: 0-состояние ON, 1-состояние OFF
   void              SetBMPFile(const int index);

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

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value)     { this.Prop.Curr.Set(property,index,value);  }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value)    { this.Prop.Curr.Set(property,index,value);  }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value)    { this.Prop.Curr.Set(property,index,value);  }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)          const { return this.Prop.Curr.Get(property,index); }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)           const { return this.Prop.Curr.Get(property,index); }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index)           const { return this.Prop.Curr.Get(property,index); }
      
//--- Устанавливает прошлое (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Prev.Set(property,index,value);  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value){ this.Prop.Prev.Set(property,index,value);  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value){ this.Prop.Prev.Set(property,index,value);  }
//--- Возвращает из массива прошлых свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)      const { return this.Prop.Prev.Get(property,index); }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)       const { return this.Prop.Prev.Get(property,index); }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index)       const { return this.Prop.Prev.Get(property,index); }

По всему коду класса, где используются методы GetProperty(), SetProperty(), GetPropertyPrev() и SetPropertyPrev() нужно внести изменения, так как теперь в них указывается индекс второго измерения. Все эти множественные изменения здесь описывать не будем, так как это займёт много пространства статьи, и все они уже внесены в код класса, с которым можно ознакомиться в конце статьи. Рассмотрим лишь новые и некоторые старые методы.

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

//--- Возвращает описание (1) типа (ENUM_OBJECT) графического объекта
   virtual string    TypeDescription(void)                                    const { return CMessage::Text(MSG_GRAPH_STD_OBJ_ANY);          }
//--- Возвращает описание координаты указанных (1) цены, (2) времени графического объекта
   virtual string    PriceDescription(const int index)      const { return ::DoubleToString(this.GetProperty(GRAPH_OBJ_PROP_PRICE,index),this.m_digits);       }
   virtual string    TimeDescription(const int index)       const { return ::TimeToString(this.GetProperty(GRAPH_OBJ_PROP_TIME,index),TIME_DATE|TIME_MINUTES); }
//--- Возвращает описание (1) цвета, (2) стиля, (3) толщины, (4) значения указанного уровня
   virtual string    LevelColorDescription(const int index) const { return ::ColorToString((color)this.GetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,index),true); }
   virtual string    LevelStyleDescription(const int index) const { return LineStyleDescription((ENUM_LINE_STYLE)this.GetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,index)); }
   virtual string    LevelWidthDescription(const int index) const { return (string)this.GetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,index); }
   virtual string    LevelValueDescription(const int index) const { return ::DoubleToString(this.GetProperty(GRAPH_OBJ_PROP_LEVELVALUE,index),5); }

//--- Возвращает описания всех (1) времён, (2) цен, (3) врремён и цен опорных точек,
//--- (4) значения уровней, (6) всех свойств всех уровней, (5) BMP-файлов графического объекта
   string            TimesDescription(void)        const;
   string            PricesDescription(void)       const;
   string            TimePricesDescription(void)   const;
   string            LevelColorsDescription(void)  const;
   string            LevelStylesDescription(void)  const;
   string            LevelWidthsDescription(void)  const;
   string            LevelValuesDescription(void)  const;
   string            LevelTextsDescription(void)   const;
   string            LevelsAllDescription(void)    const;
   string            BMPFilesDescription(void)     const;

В защищённый параметрический конструктор класса будем передавать ещё один параметр — количество опорных точек объекта, требуемых для его построения (а это означает, что придётся доработать ещё и классы объектов-наследников так, чтобы они передавали в этот конструктор количество опорных точек):

//--- Конструктор по умолчанию
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; m_group=WRONG_VALUE; }
protected:
//--- Защищённый параметрический конструктор
                     CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                                   const ENUM_GRAPH_OBJ_BELONG belong,
                                   const ENUM_GRAPH_OBJ_GROUP group,
                                   const long chart_id, const int pivots,
                                   const string name);

Рассмотрим реализацию конструктора класса:

//+------------------------------------------------------------------+
//| Защищённый параметрический конструктор                           |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_GROUP group,
                             const long chart_id,const int pivots,
                             const string name)
  {
   //--- Создаём объект свойств со значениями по умолчанию
   this.Prop=new CProperty(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL);
   
//--- Устанавливаем количество опорных точек и уровней объекта
   this.m_pivots=pivots;
   int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS);

//--- Устанавливаем размерности массивов свойств в соответствии с количеством опорных точек и уровней
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_TIME,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_PRICE,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_BMPFILE,2);
   
//--- Устанавливаем объекту (1) его тип, тип графического (2) объекта, (3) елемента, (4) принадлежность и (5) номер подокна, (6) Digits символа графика
   this.m_type=obj_type;
   this.SetName(name);
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetGroup(group);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Сохранение целочисленных свойств, присущих всем графическим объектам, но не имеющиеся у графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID());                // Идентификатор графика
   this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());               // Номер подокна графика
   this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject());            // Тип графического объекта (ENUM_OBJECT)
   this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement());   // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE)
   this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong());                   // Принадлежность графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,CGBaseObj::Group());                     // Группа графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_ID,0,0);                                         // Идентификатор объекта
   this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0);                                        // Номер объекта в списке
   
//--- Сохранение свойств, присущих всем графическим объектам, имеющимся у графического объекта
   this.PropertiesRefresh();
   
//--- Сохранение базовых свойств в родительском объекте
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME,0);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);

//--- Сохранение текущих свойств в прошлые
   this.PropertiesCopyToPrevData();
  }
//+-------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Сравнивает объекты CGStdGraphObj между собой по всем свойствам   |
//+------------------------------------------------------------------+
bool CGStdGraphObj::IsEqual(CGStdGraphObj *compared_obj) const
  {
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      for(int j=0;j<Prop.CurrSize(prop);j++)
         if(this.GetProperty(prop,j)!=compared_obj.GetProperty(prop,j)) return false; 
     }
   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      for(int j=0;j<Prop.CurrSize(prop);j++)
         if(this.GetProperty(prop,j)!=compared_obj.GetProperty(prop,j)) return false; 
     }
   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      for(int j=0;j<Prop.CurrSize(prop);j++)
         if(this.GetProperty(prop,j)!=compared_obj.GetProperty(prop,j)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Получает и сохраняет целочисленные свойства                      |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveINT(void)
  {
   //--- Свойства, присущие всем графическим объектам, имеющиеся у графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_CREATETIME,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME));
   this.SetProperty(GRAPH_OBJ_PROP_CREATETIME,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME));  // Время создания объекта
   this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES));  // Видимость объекта на таймфреймах
   this.SetProperty(GRAPH_OBJ_PROP_BACK,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK));              // Объект на заднем плане
   this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER));          // Приоритет графического объекта на получение события нажатия мышки на графике
   this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN));          // Запрет на показ имени графического объекта в списке объектов терминала
   this.SetProperty(GRAPH_OBJ_PROP_SELECTED,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED));      // Выделенность объекта
   this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE));  // Доступность объекта
   for(int i=0;i<this.m_pivots;i++)                                                                                  // Координаты времени точек
      this.SetTimePivot(i);
   this.SetProperty(GRAPH_OBJ_PROP_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR));            // Цвет
   this.SetProperty(GRAPH_OBJ_PROP_STYLE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE));            // Стиль
   this.SetProperty(GRAPH_OBJ_PROP_WIDTH,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH));            // Толщина линии
   //--- Свойства, принадлежащие разным графическим объектам
   this.SetProperty(GRAPH_OBJ_PROP_FILL,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL));              // Заливка объекта цветом
   this.SetProperty(GRAPH_OBJ_PROP_READONLY,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY));      // Возможность редактирования текста в объекте Edit
   this.SetProperty(GRAPH_OBJ_PROP_LEVELS,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS));          // Количество уровней
   for(int i=0;i<this.Levels();i++)                                                                                  // Данные уровней
     {
      this.SetLevelColor(i);
      this.SetLevelStyle(i);
      this.SetLevelWidth(i);
     }
   this.SetProperty(GRAPH_OBJ_PROP_ALIGN,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN));            // Горизонтальное выравнивание текста в объекте "Поле ввода" (OBJ_EDIT)
   this.SetProperty(GRAPH_OBJ_PROP_FONTSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE));      // Размер шрифта
   this.SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT));      // Луч продолжается влево
   this.SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT));    // Луч продолжается вправо
   this.SetProperty(GRAPH_OBJ_PROP_RAY,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY));                // Вертикальная линия продолжается на все окна графика
   this.SetProperty(GRAPH_OBJ_PROP_ELLIPSE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE));        // Отображение полного эллипса для объекта "Дуги Фибоначчи"
   this.SetProperty(GRAPH_OBJ_PROP_ARROWCODE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE));    // Код стрелки для объекта "Стрелка"
   this.SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR));          // Положение точки привязки графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_XDISTANCE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE));    // Дистанция в пикселях по оси X от угла привязки
   this.SetProperty(GRAPH_OBJ_PROP_YDISTANCE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE));    // Дистанция в пикселях по оси Y от угла привязки
   this.SetProperty(GRAPH_OBJ_PROP_DIRECTION,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION));    // Тренд объекта Ганна
   this.SetProperty(GRAPH_OBJ_PROP_DEGREE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE));          // Уровень волновой разметки Эллиотта
   this.SetProperty(GRAPH_OBJ_PROP_DRAWLINES,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES));    // Отображение линий для волновой разметки Эллиотта
   this.SetProperty(GRAPH_OBJ_PROP_STATE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE));            // Состояние кнопки (Нажата/Отжата)
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID));// Идентификатор объекта "График" (OBJ_CHART).
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD));    // Период для объекта "График"
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE));  // Признак отображения шкалы времени для объекта "График"
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE));// Признак отображения ценовой шкалы для объекта "График"
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE));// Масштаб для объекта "График"
   this.SetProperty(GRAPH_OBJ_PROP_XSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE));            // Ширина объекта по оси X в пикселях.
   this.SetProperty(GRAPH_OBJ_PROP_YSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE));            // Высота объекта по оси Y в пикселях.
   this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET));        // X-координата левого верхнего угла прямоугольной области видимости.
   this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET));        // Y-координата левого верхнего угла прямоугольной области видимости.
   this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR));        // Цвет фона для OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.SetProperty(GRAPH_OBJ_PROP_CORNER,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER));          // Угол графика для привязки графического объекта
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE));// Тип рамки для объекта "Прямоугольная рамка"
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR));// Цвет рамки для объекта OBJ_EDIT и OBJ_BUTTON
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Получает и сохраняет вещественные свойства                       |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveDBL(void)
  {
   for(int i=0;i<this.m_pivots;i++)                                                                               // Координаты цен точек
      SetPricePivot(i);
   for(int i=0;i<this.Levels();i++)                                                                               // Значения уровней
      this.SetLevelValue(i);
   this.SetProperty(GRAPH_OBJ_PROP_SCALE,0,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE));          // Масштаб (свойство объектов Ганна и объекта "Дуги Фибоначчи")
   this.SetProperty(GRAPH_OBJ_PROP_ANGLE,0,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE));          // Угол
   this.SetProperty(GRAPH_OBJ_PROP_DEVIATION,0,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION));  // Отклонение для канала стандартного отклонения
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Получает и сохраняет строковые свойства                          |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveSTR(void)
  {
   this.SetProperty(GRAPH_OBJ_PROP_TEXT,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT));            // Описание объекта (текст, содержащийся в объекте)
   this.SetProperty(GRAPH_OBJ_PROP_TOOLTIP,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP));      // Текст всплывающей подсказки
   for(int i=0;i<this.Levels();i++)                                                                               // Описания уровней
      this.SetLevelText(i);
   this.SetProperty(GRAPH_OBJ_PROP_FONT,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT));            // Шрифт
   this.SetProperty(GRAPH_OBJ_PROP_BMPFILE,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE));      // Имя BMP-файла для объекта "Графическая метка"
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL));// Символ для объекта "График" 
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Копирует текущие данные в прошлые                                |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCopyToPrevData(void)
  {
   this.Prop.CurrentToPrevious();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Проверяет изменения свойств объекта                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   bool changed=false;
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      if(prop==GRAPH_OBJ_PROP_TIME || (prop>=GRAPH_OBJ_PROP_LEVELCOLOR && prop<=GRAPH_OBJ_PROP_LEVELWIDTH))
        {
         int total=(prop==GRAPH_OBJ_PROP_TIME ? this.m_pivots : this.Levels());
         for(int j=0;j<Prop.CurrSize(prop);j++)
           {
            if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
              {
               changed=true;
               ::Print(DFUN,this.Name(),", индекс свойства: ",j," ",TextByLanguage(" Изменённое свойство: "," Modified property: "),this.GetPropertyDescription(prop));
              }
           }
        }
      else if(this.GetProperty(prop,0)!=this.GetPropertyPrev(prop,0))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),this.GetPropertyDescription(prop));
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      if(prop==GRAPH_OBJ_PROP_PRICE || GRAPH_OBJ_PROP_LEVELVALUE)
        {
         int total=(prop==GRAPH_OBJ_PROP_PRICE ? this.m_pivots : this.Levels());
         for(int j=0;j<Prop.CurrSize(prop);j++)
           {
            if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
              {
               changed=true;
               ::Print(DFUN,this.Name(),", индекс свойства: ",j," ",TextByLanguage(" Изменённое свойство: "," Modified property: "),this.GetPropertyDescription(prop));
              }
           }
        }
      else if(this.GetProperty(prop,0)!=this.GetPropertyPrev(prop,0))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      if(prop==GRAPH_OBJ_PROP_LEVELTEXT || prop==GRAPH_OBJ_PROP_BMPFILE)
        {
         int total=(prop==GRAPH_OBJ_PROP_LEVELTEXT ? this.Levels() : 2);
         for(int j=0;j<Prop.CurrSize(prop);j++)
           {
            if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
              {
               changed=true;
               ::Print(DFUN,this.Name(),", индекс свойства: ",j," ",TextByLanguage(" Изменённое свойство: "," Modified property: "),this.GetPropertyDescription(prop));
              }
           }
        }
      else if(this.GetProperty(prop,0)!=this.GetPropertyPrev(prop,0))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }
   if(changed)
      PropertiesCopyToPrevData();
  }
//+------------------------------------------------------------------+


Методы, считывающие и устанавливающие свойства объекта, и возвращающие описания свойств:

//+------------------------------------------------------------------+
//| Считывает и устанавливает время указанной опорной точки          |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetTimePivot(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_TIME,index,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME,index));
  }
//+------------------------------------------------------------------+
//| Считывает и устанавливает цену указанной опорной точки           |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetPricePivot(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_PRICE,index,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE,index));
  }
//+------------------------------------------------------------------+
//| Считывает и устанавливает цвет указанного уровня                 |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelColor(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,index,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR,index));
  }
//+------------------------------------------------------------------+
//| Считывает и устанавливает стиль указанного уровня                |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelStyle(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,index,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE,index));
  }
//+------------------------------------------------------------------+
//| Считывает и устанавливает толщину указанного уровня              |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelWidth(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,index,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH,index));
  }
//+------------------------------------------------------------------+
//| Считывает и устанавливает значение указанного уровня             |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelValue(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELVALUE,index,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE,index));
  }
//+------------------------------------------------------------------+
//| Считывает и устанавливает текст указанного уровня                |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelText(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELTEXT,index,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT,index));
  }
//+------------------------------------------------------------------+
//| Считывает и устанавливает имя BMP-файла                          |
//| для объекта "Графическая метка".                                 |
//| Индекс: 0-состояние ON, 1-состояние OFF                          |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetBMPFile(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_BMPFILE,index,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE,index));
  }
//+------------------------------------------------------------------+
//| Возвращает описания времён всех опорных точек                    |
//+------------------------------------------------------------------+
string CGStdGraphObj::TimesDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.m_pivots;i++)
      txt+=" - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_PIVOT)+(string)i+": "+this.TimeDescription(i)+(i<this.m_pivots-1 ? "\n" : "");
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания цен всех опорных точек                       |
//+------------------------------------------------------------------+
string CGStdGraphObj::PricesDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.m_pivots;i++)
      txt+=" - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_PIVOT)+(string)i+": "+this.PriceDescription(i)+(i<this.m_pivots-1 ? "\n" : "");
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания всех опорных точек                           |
//+------------------------------------------------------------------+
string CGStdGraphObj::TimePricesDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.m_pivots;i++)
      txt+=(" - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_PIVOT)+(string)i+": "+this.TimeDescription(i)+" / "+this.PriceDescription(i)+(i<this.m_pivots-1 ? "\n" : ""));
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания цветов всех уровней                          |
//+------------------------------------------------------------------+
string CGStdGraphObj::LevelColorsDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.Levels();i++)
      txt+=(" - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_LEVEL)+(string)i+": "+this.LevelColorDescription(i)+(i<this.Levels()-1 ? "\n" : ""));
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания стилей всех уровней                          |
//+------------------------------------------------------------------+
string CGStdGraphObj::LevelStylesDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.Levels();i++)
      txt+=(" - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_LEVEL)+(string)i+": "+LineStyleDescription(this.LevelStyle(i))+(i<this.Levels()-1 ? "\n" : ""));
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания ширины всех уровней                          |
//+------------------------------------------------------------------+
string CGStdGraphObj::LevelWidthsDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.Levels();i++)
      txt+=(" - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_LEVEL)+(string)i+": "+this.LevelWidthDescription(i)+(i<this.Levels()-1 ? "\n" : ""));
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания значений всех уровней                        |
//+------------------------------------------------------------------+
string CGStdGraphObj::LevelValuesDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.Levels();i++)
      txt+=(" - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_LEVEL)+(string)i+": "+this.LevelValueDescription(i)+(i<this.Levels()-1 ? "\n" : ""));
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания текстов всех уровней                         |
//+------------------------------------------------------------------+
string CGStdGraphObj::LevelTextsDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.Levels();i++)
      txt+=(" - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_LEVEL)+(string)i+": "+this.LevelText(i)+(i<this.Levels()-1 ? "\n" : ""));
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания BMP-файлов графического объекта              |
//+------------------------------------------------------------------+
string CGStdGraphObj::BMPFilesDescription(void) const
  {
   string txt=
     (
      " - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_BMP_FILE_STATE_ON) +" (0): \""+BMPFile(0)+"\"\n"+
      " - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_BMP_FILE_STATE_OFF)+" (1): \""+BMPFile(1)+"\""
     );
   return txt;
  }
//+------------------------------------------------------------------+
//| Возвращает описания всех значений всех уровней                   |
//+------------------------------------------------------------------+
string CGStdGraphObj::LevelsAllDescription(void) const
  {
   string txt="";
   for(int i=0;i<this.Levels();i++)
     {
      txt+=
        (
         " - "+CMessage::Text(MSG_GRAPH_OBJ_TEXT_LEVEL)+" "+(string)i+"\n"+
         "    "+CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELCOLOR)+": "+LevelColorDescription(i)+"\n"+
         "    "+CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELSTYLE)+": "+LevelStyleDescription(i)+"\n"+
         "    "+CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELWIDTH)+": "+LevelWidthDescription(i)+"\n"+
         "    "+CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELVALUE)+": "+LevelValueDescription(i)+"\n"+
         "    "+CMessage::Text(MSG_GRAPH_OBJ_PROP_LEVELTEXT) +": "+LevelText(i)
        );
     }
   return txt;
  }
//+------------------------------------------------------------------+

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

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

Откроем файл класса графического объекта знак "Buy" из \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdArrowBuyObj.mqh и допишем в его конструкторе передачу параметра количества опорных точек объекта в конструктор родительского класса:

//+------------------------------------------------------------------+
//| Графический объект знак "Buy"                                    |
//+------------------------------------------------------------------+
class CGStdArrowBuyObj : public CGStdGraphObj
  {
private:

public:
   //--- Конструктор
                     CGStdArrowBuyObj(const long chart_id,const string name) : 
                        CGStdGraphObj(OBJECT_DE_TYPE_GSTD_ARROW_BUY,GRAPH_OBJ_BELONG_NO_PROGRAM,GRAPH_OBJ_GROUP_ARROWS,chart_id,1,name)
                          {
                           //--- Укажем свойство объекта
                           CGStdGraphObj::SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,ANCHOR_TOP);
                          }
   //--- Поддерживаемые свойства объекта (1) вещественные, (2) целочисленные
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property);
//--- Возвращает положение точки привязки графического объекта
   ENUM_ARROW_ANCHOR Anchor(void)            const { return (ENUM_ARROW_ANCHOR)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,0); }
//--- Выводит в журнал краткое описание объекта
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
//--- Возвращает краткое наименование объекта
   virtual string    Header(const bool symbol=false);
//--- Возвращает описание типа (ENUM_OBJECT) графического объекта
   virtual string    TypeDescription(void)   const { return StdGraphObjectTypeDescription(OBJ_ARROW_BUY);  }
//--- Возвращает описание положения точки привязки графического объекта
   virtual string    AnchorDescription(void) const { return AnchorForArrowObjDescription(this.Anchor()); }

  };
//+------------------------------------------------------------------+

Этот объект имеет лишь одну опорную точку. Теперь рассмотрим объект с несколькими опорными точками.

Откроем файл класса объекта "Трендовая линия" из \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdTrendObj.mqh и точно так же впишем передачу в конструктор родительского класса передачу значения количества опорных точек, равную двум:

//+------------------------------------------------------------------+
//| Графический объект "Трендовая линия"                             |
//+------------------------------------------------------------------+
class CGStdTrendObj : public CGStdGraphObj
  {
private:

public:
   //--- Конструктор
                     CGStdTrendObj(const long chart_id,const string name) :
                        CGStdGraphObj(OBJECT_DE_TYPE_GSTD_TREND,GRAPH_OBJ_BELONG_NO_PROGRAM,GRAPH_OBJ_GROUP_LINES,chart_id,2,name)
                          {
                           //--- Получим и сохраним свойства объекта
                           CGStdGraphObj::SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0,::ObjectGetInteger(chart_id,name,OBJPROP_RAY_LEFT));
                           CGStdGraphObj::SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0,::ObjectGetInteger(chart_id,name,OBJPROP_RAY_RIGHT));
                          }
   //--- Поддерживаемые свойства объекта (1) вещественные, (2) целочисленные
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property);
//--- Выводит в журнал краткое описание объекта
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
//--- Возвращает краткое наименование объекта
   virtual string    Header(const bool symbol=false);
//--- Возвращает описание типа (ENUM_OBJECT) графического объекта
   virtual string    TypeDescription(void)   const { return StdGraphObjectTypeDescription(OBJ_TREND); }
  };
//+------------------------------------------------------------------+

Такие изменения были проведены для каждого класса в папке \MQL5\Include\DoEasy\Objects\Graph\Standard\. Для каждого объекта вписано то количество его опорных точек, которое используется для его построения. Со всеми изменениями можно ознакомиться в прилагаемых к статье файлах.

Теперь доработаем класс CSelect по адресу \MQL5\Include\DoEasy\Services\Select.mqh, в котором нам нужно просто добавить индексы второго измерения в каждые методы, где есть или предполагается обращение к методам GetProperty():

//+------------------------------------------------------------------+
//| Методы работы с данными стандартных графических объектов         |
//+------------------------------------------------------------------+
   //--- Возвращает список объектов, где одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByGraphicStdObjectProperty(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByGraphicStdObjectProperty(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByGraphicStdObjectProperty(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс графического объекта в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства
   static int        FindGraphicStdObjectMax(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_INTEGER property,int index);
   static int        FindGraphicStdObjectMax(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index);
   static int        FindGraphicStdObjectMax(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_STRING property,int index);
   //--- Возвращает индекс графического объекта в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства
   static int        FindGraphicStdObjectMin(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_INTEGER property,int index);
   static int        FindGraphicStdObjectMin(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index);
   static int        FindGraphicStdObjectMin(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_STRING property,int index);
//---
  };
//+------------------------------------------------------------------+

Рассмотрим выборочно реализацию нескольких методов:

//+------------------------------------------------------------------+
//| Возвращает список объектов, где одно из целочисленных            |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByGraphicStdObjectProperty(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      CGStdGraphObj *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      long obj_prop=obj.GetProperty(property,index);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает индекс объекта в списке                               |
//| с максимальным значением целочисленного свойства                 |
//+------------------------------------------------------------------+
int CSelect::FindGraphicStdObjectMax(CArrayObj *list_source,ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int idx=0;
   CGStdGraphObj *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CGStdGraphObj *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property,index);
      max_obj=list_source.At(idx);
      long obj2_prop=max_obj.GetProperty(property,index);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) idx=i;
     }
   return idx;
  }
//+------------------------------------------------------------------+

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

Теперь доработаем класс коллекции графических объектов в файле \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

В публичных методах GetList() впишем указание индекса второго измерения массива:

public:
//--- Возвращает себя
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
//--- Возвращает полный список-коллекцию стандартных графических объектов "как есть"
   CArrayObj        *GetListGraphObj(void)                                                               { return &this.m_list_all_graph_obj;   }
//--- Возвращает полный список-коллекцию графических элеменов на канвасе "как есть"
   CArrayObj        *GetListCanvElm(void)                                                                { return &this.m_list_all_canv_elm_obj;}
//--- Возвращает список графических элементов по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);  }
//--- Возвращает список графических объектов по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value,ENUM_COMPARER_TYPE mode=EQUAL)    { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode); }
   CArrayObj        *GetList(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value,ENUM_COMPARER_TYPE mode=EQUAL)   { return CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),property,index,value,mode); }

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

//+------------------------------------------------------------------+
//| Возвращает первый свободный идентификатор графического объекта   |
//+------------------------------------------------------------------+
long CGraphElementsCollection::GetFreeGraphObjID(void)
  {
   int index=CSelect::FindGraphicStdObjectMax(this.GetListGraphObj(),GRAPH_OBJ_PROP_ID,0);
   CGStdGraphObj *obj=this.m_list_all_graph_obj.At(index);
   return(obj!=NULL ? obj.ObjectID()+1 : 1);
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|Находит объект, имеющийся в коллекции, но отсутствующий на графике|
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
   if(list==NULL)
      return NULL;
   for(int i=0;i<list.Total();i++)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name()))
         return obj;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает флаг наличия объекта класса графического объекта      |
//| в списке-коллекции графических объектов                          |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentGraphObjInList(const long chart_id,const string name)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(this.GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,0,name,EQUAL);
   return(list==NULL || list.Total()==0 ? false : true);
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Удаляет графический объект по идентификатору графика             |
//| из списка-коллекции графических объектов                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteGraphObjectsFromList(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
   if(list==NULL)
      return;
   for(int i=list.Total();i>WRONG_VALUE;i--)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      this.DeleteGraphObjFromList(obj);
     }
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает графический объект по имени и идентификатору графика  |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const string name,const long chart_id)
  {
   CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_NAME,0,name,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Добавляет графический объект в коллекцию                         |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control)
  {
   //--- Из класса управления графическими объектами получаем список последних добавленных графических объектов
   CArrayObj *list=obj_control.GetListNewAddedObj();
   //--- Если список получить не удалось - сообщаем об этом и возвращаем false
   if(list==NULL)
     {
      CMessage::ToLog(DFUN_ERR_LINE,MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST);
      return false;
     }
   //--- Если список пустой - возвращаем false
   if(list.Total()==0)
      return false;
   //--- Объявляем переменную для хранения результата
   bool res=true;
   //--- В цикле по списку вновь добавленных стандартных графических объектов
   for(int i=0;i<list.Total();i++)
     {
      //--- извлекаем из списка очередной объект и
      CGStdGraphObj *obj=list.Detach(i);
      //--- если объект получить не удалось - сообщаем об этом, добавляем в результирующую переменную false и идём к следующему
      if(obj==NULL)
        {
         CMessage::ToLog(source,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
         res &=false;
         continue;
        }
      //--- если объект не удалось добавить в список-коллекцию - сообщаем об этом,
      //--- удаляем объект, добавляем в результирующую переменную false и идём к следующему
      if(!this.m_list_all_graph_obj.Add(obj))
        {
         CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
         delete obj;
         res &=false;
         continue;
        }
      //--- Успешно извлекли объект из списка вновь добавленных графических объектов и добавили его в коллекцию -
      //--- находим очередной свободный идентификатор объекта, записываем его в свойство и выводим краткое описание объекта в журнал
      else
        {
         obj.SetObjectID(this.GetFreeGraphObjID());
         obj.Print();
        }
     }
   //--- Возвращаем результат добавления объекта в коллекцию
   return res;
  }
//+------------------------------------------------------------------+

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


Тестирование событий объектов с двумя опорными точками

Для тестирования возьмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part88\ под новым именем TestDoEasyPart88.mq5.

Никаких изменений в советник вносить не нужно — все изменения были проделаны в файлах библиотеки.

Скомпилируем советник и запустим его на графике:


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


Что дальше

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

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

При возникновении вопросов, замечаний и пожеланий вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

Графика в библиотеке DoEasy (Часть 82): Рефакторинг объектов библиотеки и коллекция графических объектов
Графика в библиотеке DoEasy (Часть 83): Класс абстрактного стандартного графического объекта
Графика в библиотеке DoEasy (Часть 84): Классы-наследники абстрактного стандартного графического объекта
Графика в библиотеке DoEasy (Часть 85): Коллекция графических объектов - добавляем вновь создаваемые
Графика в библиотеке DoEasy (Часть 86): Коллекция графических объектов - контролируем модификацию свойств
Графика в библиотеке DoEasy (Часть 87): Коллекция графических объектов - контроль модификации свойств объектов на всех открытых графиках

Прикрепленные файлы |
MQL5.zip (4225.21 KB)
Пользуйтесь каналами и групповыми чатами MQL5.community Пользуйтесь каналами и групповыми чатами MQL5.community
На сайте MQL5.com встречаются трейдеры со всего мира — публикуют статьи, бесплатные коды и продукты в Маркете, выполняют работы на фриланс бирже и копируют торговые сигналы. Вы можете общаться с ними на форуме, в трейдерские чатах и каналах MetaTrader.
Комбинаторика и теория вероятностей для трейдинга (Часть IV): Логика Бернулли Комбинаторика и теория вероятностей для трейдинга (Часть IV): Логика Бернулли
В данной статье я решил осветить всем известную схему Бернулли и показать как можно ее использовать в рамках описания массивов данных связанных с торговлей, для дальнейшего использования на пути создания самостоятельно адаптирующейся торговой системы. Также будем искать более общий алгоритм, частным случаем которой является формула Бернулли и найдем ему применение.
Комбинаторика и теория вероятностей для трейдинга (Часть V): Анализ кривых Комбинаторика и теория вероятностей для трейдинга (Часть V): Анализ кривых
В данной статье я решил провести исследование на тему сведения множественных состояний к двойным. Основная цель — это анализ и полезные выводы, которые могут помочь в дальнейшей разработке масштабируемых торговых алгоритмов на базе теории вероятностей. Конечно, не обошлось и без математики, но учитывая опыт предыдущих статей, вижу, что более общая информация гораздо полезнее деталей.
Стать хорошим программистом (Часть 5): повышаем скорость программирования Стать хорошим программистом (Часть 5): повышаем скорость программирования
Я полагаю, каждый разработчик хочет писать код быстрее. При этом возможность быстро и эффективно писать код — это не какая-то особая врожденная способность, доступная только избранным. Это навык, которым может овладеть любой программист, чем мы и займемся в этой статье.