Русский 中文 Español Deutsch 日本語 Português
Graphics in DoEasy library (Part 88): Graphical object collection — two-dimensional dynamic array for storing dynamically changing object properties

Graphics in DoEasy library (Part 88): Graphical object collection — two-dimensional dynamic array for storing dynamically changing object properties

MetaTrader 5Examples | 15 December 2021, 10:10
23 349 0
Artyom Trishkin
Artyom Trishkin

Contents


Concept

In all previous articles, I used simple arrays to store object property data. We have three arrays — the one-dimensional array of integer, real and string properties. Each cell of such an array stores the value of a certain object property corresponding to the object property enumeration set in Defines.mqh. So far, this has been working pretty well. But now I have decided that a single property may return values for individual units of this property in case of graphical (and some other) objects.

Let me explain. For example, we have the graphical object time property. The graphical object positioned on the chart by price/time coordinates features several pivot points used to place it on the chart. Suppose that an object, let it be Arrow, has only one pivot we can receive using the ObjectGetInteger() function by specifying OBJPROP_TIME as a property ID. Such a construction returns the chart time corresponding to the only pivot point of the object:

ObjectGetInteger(0, Name, OBJPROP_TIME);

But what if an object has two pivot points, like TrendLine? How can we get the time of both pivot points? The prop_modifier formal parameter of the ObjectGetInteger() function can help with that. Its default value is 0, which corresponds to the first pivot point. To get the data of the second point, we need to set the value of 1, for the third point — 2, etc:

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

All is simple and straightforward so far. All data obtained from the object is set to the arrays: integer data goes to the long array, real data — to the double array, while the string data — to the string array. This means we may simply use a two-dimensional array to save data that can be obtained by indicating the necessary pivot point in prop_modifier. All seems logical. The point 0 is stored in the zero dimension, point 1 — in the first, 2 — in the second, etc:

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);

However, there is a catch. First, the amount of obtained data may be different for each of the many object properties. For example, in case of the Fibo Lines object, we have two pivot points used to locate the object on a chart. However, the object has a different number of levels. There are nine of them by default. Besides, it can be changed by a user at any time. Second, some "multi-properties" of a single object property can be changed dynamically.

In other words, we cannot know in advance the size of the second dimension of the array, in which we are going to store object pivot points. Besides, these properties can change dynamically. Considering this, we cannot use two-dimensional arrays for storing the properties of different objects due to the following reasons:

  • All objects are descendants of their base objects that define the arrays for storing properties. Each property should have a predefined size of the second array dimension, which can be different for each object and each of its properties. When creating a two-dimensional array in MQL, we should specify the value of the second dimension we do not know in the abstract class for each property of each object;
  • Each "multidimensional" property of this kind can be dynamically changed both by a user and programmatically. However, in MQL, we cannot dynamically change the non-zero dimensionality of a multidimensional array.
There is a solution though. I will create a custom class of a multidimensional dynamic array that can dynamically change in any of its dimension.
To create such an array, I will use the class of dynamic array of pointers to instances of the CObject class and its descendantsCArrayObj.

Based on the newly created class, create an object class for storing object properties instead of usual arrays. This will allow us to change the number of stored data in the array second dimension at any time to change the object class properties when changing the properties of the appropriate graphical object.

The current task is as follows: create the class of a multidimensional dynamic array, use it to create the two-dimensional dynamic array for storing object properties and replace three arrays storing the object properties with it. Re-arrange the class of the abstract graphical object and test the new possibilities for storing object properties in the dynamic array and tracking property changes.

Dynamic multidimensional array class

CArrayObj class is, in fact, an array storing the pointers to instances of objects derived from the CObject base class. Thus, the array may store any object derived from CObject. This means the array cells may contain long, double or string type data, as well as another CArrayObj array, which may also contain either data, or other arrays. While everything is pretty clear with the CArrayObj arrays, the situation is more complex with the data. The data is not derived from the CObject class, therefore we need to create the classes for storing such data. Besides, each of the objects (as well as the array object itself) in the array should have a specified type. This is necessary for a clear understanding of what exactly is stored in an array cell — a simple object with integer, real or string data or yet another object, which, in turn, contains objects with data or another object array. This is necessary to create methods for copying one class object to another — in order to know exactly whether to copy data into a cell (if there is data in the corresponding cell of the copied array), or create a new data array in the source array (if the corresponding cell of the copied array contains the array) and copy data from it. Let's set the necessary object types right away. In \MQL5\Include\DoEasy\Defines.mqh, add the constants of the necessary types to the list of library object types (the full enumeration is shown below for better understanding):

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of library object types                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Graphics
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                       // "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                          // Form object type
   OBJECT_DE_TYPE_GSHADOW,                                        // Shadow object type
//--- Animation
   OBJECT_DE_TYPE_GFRAME,                                         // "Single animation frame" object type
   OBJECT_DE_TYPE_GFRAME_TEXT,                                    // "Single text animation frame" object type
   OBJECT_DE_TYPE_GFRAME_QUAD,                                    // "Single rectangular animation frame" object type
   OBJECT_DE_TYPE_GFRAME_GEOMETRY,                                // "Single geometric animation frame" object type
   OBJECT_DE_TYPE_GANIMATIONS,                                    // "Animations" object type
//--- Managing graphical objects
   OBJECT_DE_TYPE_GELEMENT_CONTROL,                               // "Managing graphical objects" object type
//--- Standard graphical objects
   OBJECT_DE_TYPE_GSTD_OBJ,                                       // "Standard graphical object" object type
   OBJECT_DE_TYPE_GSTD_VLINE              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_VLINE,            // "Vertical line" object type
   OBJECT_DE_TYPE_GSTD_HLINE              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_HLINE,            // "Horizontal line" object type
   OBJECT_DE_TYPE_GSTD_TREND              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_TREND,            // "Trend line" object type
   OBJECT_DE_TYPE_GSTD_TRENDBYANGLE       =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_TRENDBYANGLE,     // "Trend line by angle" object type
   OBJECT_DE_TYPE_GSTD_CYCLES             =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_CYCLES,           // Cyclic lines" object type
   OBJECT_DE_TYPE_GSTD_ARROWED_LINE       =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROWED_LINE,     // "Arrowed line" object type
   OBJECT_DE_TYPE_GSTD_CHANNEL            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_CHANNEL,          // "Equidistant channel" object type
   OBJECT_DE_TYPE_GSTD_STDDEVCHANNEL      =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_STDDEVCHANNEL,    // "Standard deviation channel" object type
   OBJECT_DE_TYPE_GSTD_REGRESSION         =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_REGRESSION,       // "Linear regression channel" object type
   OBJECT_DE_TYPE_GSTD_PITCHFORK          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_PITCHFORK,        // "Andrews' pitchfork" object type
   OBJECT_DE_TYPE_GSTD_GANNLINE           =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_GANNLINE,         // "Gann line" object type
   OBJECT_DE_TYPE_GSTD_GANNFAN            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_GANNFAN,          // "Gann fan" object type
   OBJECT_DE_TYPE_GSTD_GANNGRID           =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_GANNGRID,         // "Gann grid" object type
   OBJECT_DE_TYPE_GSTD_FIBO               =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBO,             // "Fibo levels" object type
   OBJECT_DE_TYPE_GSTD_FIBOTIMES          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBOTIMES,        // "Fibo time zones" object type
   OBJECT_DE_TYPE_GSTD_FIBOFAN            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBOFAN,          // "Fibo fan" object type
   OBJECT_DE_TYPE_GSTD_FIBOARC            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBOARC,          // "Fibo arcs" object type
   OBJECT_DE_TYPE_GSTD_FIBOCHANNEL        =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_FIBOCHANNEL,      // "Fibo channel" object type
   OBJECT_DE_TYPE_GSTD_EXPANSION          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_EXPANSION,        // "Fibo expansion" object type
   OBJECT_DE_TYPE_GSTD_ELLIOTWAVE5        =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ELLIOTWAVE5,      // "Elliott 5 waves" object type
   OBJECT_DE_TYPE_GSTD_ELLIOTWAVE3        =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ELLIOTWAVE3,      // "Elliott 3 waves" object type
   OBJECT_DE_TYPE_GSTD_RECTANGLE          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_RECTANGLE,        // "Rectangle" object type
   OBJECT_DE_TYPE_GSTD_TRIANGLE           =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_TRIANGLE,         // "Triangle" object type
   OBJECT_DE_TYPE_GSTD_ELLIPSE            =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ELLIPSE,          // "Ellipse" object type
   OBJECT_DE_TYPE_GSTD_ARROW_THUMB_UP     =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_THUMB_UP,   // "Thumb up" object type
   OBJECT_DE_TYPE_GSTD_ARROW_THUMB_DOWN   =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_THUMB_DOWN, // "Thumb down" object type
   OBJECT_DE_TYPE_GSTD_ARROW_UP           =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_UP,         // "Arrow up" object type
   OBJECT_DE_TYPE_GSTD_ARROW_DOWN         =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_DOWN,       // "Arrow down" object type
   OBJECT_DE_TYPE_GSTD_ARROW_STOP         =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_STOP,       // "Stop sign" object type
   OBJECT_DE_TYPE_GSTD_ARROW_CHECK        =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_CHECK,      // "Check mark" object type
   OBJECT_DE_TYPE_GSTD_ARROW_LEFT_PRICE   =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_LEFT_PRICE, // "Left price label" object type
   OBJECT_DE_TYPE_GSTD_ARROW_RIGHT_PRICE  =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_RIGHT_PRICE,// "Right price label" object type
   OBJECT_DE_TYPE_GSTD_ARROW_BUY          =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_BUY,        // "Buy sign" object type
   OBJECT_DE_TYPE_GSTD_ARROW_SELL         =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW_SELL,       // "Sell sign" object type
   OBJECT_DE_TYPE_GSTD_ARROW              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_ARROW,            // "Arrow" object type
   OBJECT_DE_TYPE_GSTD_TEXT               =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_TEXT,             // "Text" object type
   OBJECT_DE_TYPE_GSTD_LABEL              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_LABEL,            // "Text label" object type
   OBJECT_DE_TYPE_GSTD_BUTTON             =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_BUTTON,           // "Button" object type
   OBJECT_DE_TYPE_GSTD_CHART              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_CHART,            // "Chart" object type
   OBJECT_DE_TYPE_GSTD_BITMAP             =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_BITMAP,           // "Bitmap" object type
   OBJECT_DE_TYPE_GSTD_BITMAP_LABEL       =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_BITMAP_LABEL,     // "Bitmap label" object type
   OBJECT_DE_TYPE_GSTD_EDIT               =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_EDIT,             // "Input field" object type
   OBJECT_DE_TYPE_GSTD_EVENT              =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_EVENT,            // "Event object which corresponds to an event in Economic Calendar" object type
   OBJECT_DE_TYPE_GSTD_RECTANGLE_LABEL    =  OBJECT_DE_TYPE_GSTD_OBJ+1+OBJ_RECTANGLE_LABEL,  // "Rectangle Label object used to create and design the custom graphical interface" object type
   
//--- Objects
   OBJECT_DE_TYPE_BASE  =  OBJECT_DE_TYPE_GSTD_RECTANGLE_LABEL+1, // Base object for all library objects
   OBJECT_DE_TYPE_BASE_EXT,                                       // Extended base object for all library objects
   
   OBJECT_DE_TYPE_ACCOUNT,                                        // "Account" object type
   OBJECT_DE_TYPE_BOOK_ORDER,                                     // "Book order" object type
   OBJECT_DE_TYPE_BOOK_BUY,                                       // "Book buy order" object type
   OBJECT_DE_TYPE_BOOK_BUY_MARKET,                                // "Book buy order at market price" object type
   OBJECT_DE_TYPE_BOOK_SELL,                                      // "Book sell order" object type
   OBJECT_DE_TYPE_BOOK_SELL_MARKET,                               // "Book sell order at market price" object type
   OBJECT_DE_TYPE_BOOK_SNAPSHOT,                                  // "Book snapshot" object type
   OBJECT_DE_TYPE_BOOK_SERIES,                                    // "Book snapshot series" object type
   
   OBJECT_DE_TYPE_CHART,                                          // "Chart" object type
   OBJECT_DE_TYPE_CHART_WND,                                      // "Chart window" object type
   OBJECT_DE_TYPE_CHART_WND_IND,                                  // "Chart window indicator" object type
   
   OBJECT_DE_TYPE_EVENT,                                          // "Event" object type
   OBJECT_DE_TYPE_EVENT_BALANCE,                                  // "Balance operation event" object type
   OBJECT_DE_TYPE_EVENT_MODIFY,                                   // "Pending order/position modification event" object type
   OBJECT_DE_TYPE_EVENT_ORDER_PLASED,                             // "Placing a pending order event" object type
   OBJECT_DE_TYPE_EVENT_ORDER_REMOVED,                            // "Pending order removal event" object type
   OBJECT_DE_TYPE_EVENT_POSITION_CLOSE,                           // "Position closure event" object type
   OBJECT_DE_TYPE_EVENT_POSITION_OPEN,                            // "Position opening event" object type
   
   OBJECT_DE_TYPE_IND_BUFFER,                                     // "Indicator buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_ARROW,                               // "Arrow rendering buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_BAR,                                 // "Bar buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_CALCULATE,                           // "Calculated buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_CANDLE,                              // "Candle buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_FILLING,                             // "Filling buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_HISTOGRAMM,                          // "Histogram buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_HISTOGRAMM2,                         // "Histogram 2 buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_LINE,                                // "Line buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_SECTION,                             // "Section buffer" object type
   OBJECT_DE_TYPE_IND_BUFFER_ZIGZAG,                              // "Zigzag buffer" object type
   OBJECT_DE_TYPE_INDICATOR,                                      // "Indicator" object type
   OBJECT_DE_TYPE_IND_DATA,                                       // "Indicator data" object type
   OBJECT_DE_TYPE_IND_DATA_LIST,                                  // "Indicator data list" object type
   
   OBJECT_DE_TYPE_IND_AC,                                         // "Accelerator Oscillator indicator" object type
   OBJECT_DE_TYPE_IND_AD,                                         // "Accumulation/Distribution indicator" object type
   OBJECT_DE_TYPE_IND_ADX,                                        // "Average Directional Index indicator" object type
   OBJECT_DE_TYPE_IND_ADXW,                                       // "ADX indicator by Welles Wilder" object type
   OBJECT_DE_TYPE_IND_ALLIGATOR,                                  // "Alligator indicator" object type
   OBJECT_DE_TYPE_IND_AMA,                                        // "Adaptive Moving Average indicator" object type
   OBJECT_DE_TYPE_IND_AO,                                         // "Awesome Oscillator indicator" object type
   OBJECT_DE_TYPE_IND_ATR,                                        // "Average True Range" object type
   OBJECT_DE_TYPE_IND_BANDS,                                      // "Bollinger Bands® indicator" object type
   OBJECT_DE_TYPE_IND_BEARS,                                      // "Bears Power indicator" object type
   OBJECT_DE_TYPE_IND_BULLS,                                      // "Bulls Power indicator" object type
   OBJECT_DE_TYPE_IND_BWMFI,                                      // "Market Facilitation Index indicator" object type
   OBJECT_DE_TYPE_IND_CCI,                                        // "Commodity Channel Index indicator" object type
   OBJECT_DE_TYPE_IND_CHAIKIN,                                    // "Chaikin Oscillator indicator" object type
   OBJECT_DE_TYPE_IND_CUSTOM,                                     // "Custom indicator" object type
   OBJECT_DE_TYPE_IND_DEMA,                                       // "Double Exponential Moving Average indicator" object type
   OBJECT_DE_TYPE_IND_DEMARKER,                                   // "DeMarker indicator" object type
   OBJECT_DE_TYPE_IND_ENVELOPES,                                  // "Envelopes indicator" object type
   OBJECT_DE_TYPE_IND_FORCE,                                      // "Force Index indicator" object type
   OBJECT_DE_TYPE_IND_FRACTALS,                                   // "Fractals indicator" object type
   OBJECT_DE_TYPE_IND_FRAMA,                                      // "Fractal Adaptive Moving Average indicator" object type
   OBJECT_DE_TYPE_IND_GATOR,                                      // "Gator Oscillator indicator" object type
   OBJECT_DE_TYPE_IND_ICHIMOKU,                                   // "Ichimoku Kinko Hyo indicator" object type
   OBJECT_DE_TYPE_IND_MA,                                         // "Moving Average indicator" object type
   OBJECT_DE_TYPE_IND_MACD,                                       // "Moving Average Convergence/Divergence indicator" object type
   OBJECT_DE_TYPE_IND_MFI,                                        // "Money Flow Index indicator" object type
   OBJECT_DE_TYPE_IND_MOMENTUM,                                   // "Momentum indicator" object type
   OBJECT_DE_TYPE_IND_OBV,                                        // "On Balance Volume indicator" object type
   OBJECT_DE_TYPE_IND_OSMA,                                       // "Moving Average of Oscillator indicator" object type
   OBJECT_DE_TYPE_IND_RSI,                                        // "Relative Strength Index indicator" object type
   OBJECT_DE_TYPE_IND_RVI,                                        // "Relative Vigor Index indicator" object type
   OBJECT_DE_TYPE_IND_SAR,                                        // "Parabolic SAR indicator" object type
   OBJECT_DE_TYPE_IND_STDEV,                                      // "Standard Deviation indicator" object type
   OBJECT_DE_TYPE_IND_STOCH,                                      // "Stochastic Oscillator indicator" object type
   OBJECT_DE_TYPE_IND_TEMA,                                       // "Triple Exponential Moving Average indicator" object
   OBJECT_DE_TYPE_IND_TRIX,                                       // "Triple Exponential Moving Averages Oscillator indicator" object type
   OBJECT_DE_TYPE_IND_VIDYA,                                      // "Variable Index Dynamic Average indicator" object type
   OBJECT_DE_TYPE_IND_VOLUMES,                                    // "Volumes indicator" object type
   OBJECT_DE_TYPE_IND_WPR,                                        // "Williams' Percent Range indicator" object type
   
   OBJECT_DE_TYPE_MQL5_SIGNAL,                                    // "mql5 signal" object type
   
   OBJECT_DE_TYPE_ORDER_DEAL_POSITION,                            // "Order/Deal/Position" object type
   OBJECT_DE_TYPE_HISTORY_BALANCE,                                // "Historical balance operation" object type
   OBJECT_DE_TYPE_HISTORY_DEAL,                                   // "Historical deal" object type
   OBJECT_DE_TYPE_HISTORY_ORDER_MARKET,                           // "Historical market order" object type
   OBJECT_DE_TYPE_HISTORY_ORDER_PENDING,                          // "Historical removed pending order" object type
   OBJECT_DE_TYPE_MARKET_ORDER,                                   // "Market order" object type
   OBJECT_DE_TYPE_MARKET_PENDING,                                 // "Pending order" object type
   OBJECT_DE_TYPE_MARKET_POSITION,                                // "Market position" object type
   
   OBJECT_DE_TYPE_PENDING_REQUEST,                                // "Pending trading request" object type
   OBJECT_DE_TYPE_PENDING_REQUEST_POSITION_OPEN,                  // "Pending request to open a position" object type
   OBJECT_DE_TYPE_PENDING_REQUEST_POSITION_CLOSE,                 // "Pending request to close a position" object type
   OBJECT_DE_TYPE_PENDING_REQUEST_POSITION_SLTP,                  // "Pending request to modify position stop orders" object type
   OBJECT_DE_TYPE_PENDING_REQUEST_ORDER_PLACE,                    // "Pending request to place a pending order" object type
   OBJECT_DE_TYPE_PENDING_REQUEST_ORDER_REMOVE,                   // "Pending request to delete a pending order" object type
   OBJECT_DE_TYPE_PENDING_REQUEST_ORDER_MODIFY,                   // "Pending request to modify pending order parameters" object type
   
   OBJECT_DE_TYPE_SERIES_BAR,                                     // "Bar" object type
   OBJECT_DE_TYPE_SERIES_PERIOD,                                  // "Period timeseries" object type
   OBJECT_DE_TYPE_SERIES_SYMBOL,                                  // "Symbol timeseries" object type
   
   OBJECT_DE_TYPE_SYMBOL,                                         // "Symbol" object type
   OBJECT_DE_TYPE_SYMBOL_BONDS,                                   // "Bond symbol" object type
   OBJECT_DE_TYPE_SYMBOL_CFD,                                     // "CFD (contract for difference) symbol" object type
   OBJECT_DE_TYPE_SYMBOL_COLLATERAL,                              // "Non-tradable asset symbol" object type" object type
   OBJECT_DE_TYPE_SYMBOL_COMMODITY,                               // "Commodity symbol" object type
   OBJECT_DE_TYPE_SYMBOL_COMMON,                                  // "Common group symbol" object type
   OBJECT_DE_TYPE_SYMBOL_CRYPTO,                                  // "Cryptocurrency symbol" object type
   OBJECT_DE_TYPE_SYMBOL_CUSTOM,                                  // "Custom symbol" object type
   OBJECT_DE_TYPE_SYMBOL_EXCHANGE,                                // "Exchange symbol" object type
   OBJECT_DE_TYPE_SYMBOL_FUTURES,                                 // "Futures symbol" object type
   OBJECT_DE_TYPE_SYMBOL_FX,                                      // "Forex symbol" object type
   OBJECT_DE_TYPE_SYMBOL_FX_EXOTIC,                               // "Exotic Forex symbol" object type
   OBJECT_DE_TYPE_SYMBOL_FX_MAJOR,                                // "Major Forex symbol" object type
   OBJECT_DE_TYPE_SYMBOL_FX_MINOR,                                // "Minor Forex symbol" object type
   OBJECT_DE_TYPE_SYMBOL_FX_RUB,                                  // "RUB Forex symbol" object type
   OBJECT_DE_TYPE_SYMBOL_INDEX,                                   // "Index symbol" object type
   OBJECT_DE_TYPE_SYMBOL_INDICATIVE,                              // "Indicative symbol" object type
   OBJECT_DE_TYPE_SYMBOL_METALL,                                  // "Metal symbol" object type
   OBJECT_DE_TYPE_SYMBOL_OPTION,                                  // "Option symbol" object type
   OBJECT_DE_TYPE_SYMBOL_STOCKS,                                  // "Stock symbol" object type
   
   OBJECT_DE_TYPE_TICK,                                           // "Tick" object type
   OBJECT_DE_TYPE_NEW_TICK,                                       // "New tick" object type
   OBJECT_DE_TYPE_TICKSERIES,                                     // "Tick data series" object type
   
   OBJECT_DE_TYPE_TRADE,                                          // "Trading object" object type
   
   OBJECT_DE_TYPE_LONG,                                           // "Long type data" object type
   OBJECT_DE_TYPE_DOUBLE,                                         // "Double type data" object type
   OBJECT_DE_TYPE_STRING,                                         // "String type data" object type
   OBJECT_DE_TYPE_OBJECT,                                         // "Object type data" object type
  };

//+------------------------------------------------------------------+
//| Search and sorting data                                          |
//+------------------------------------------------------------------+

In \MQL5\Include\DoEasy\Data.mqh, add new message indices:

   MSG_LIB_SYS_FAILED_ADD_BUFFER,                     // Failed to add buffer object to the list
   MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ,              // Failed to create \"Indicator buffer\" object
   MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST,                // Failed to add object to the list
   
   MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ,           // Failed to create long data object
   MSG_LIB_SYS_FAILED_CREATE_DOUBLE_DATA_OBJ,         // Failed to create double data object
   MSG_LIB_SYS_FAILED_CREATE_STRING_DATA_OBJ,         // Failed to create string data object
   
   MSG_LIB_SYS_FAILED_DECREASE_LONG_ARRAY,            // Failed to decrease long array size
   MSG_LIB_SYS_FAILED_DECREASE_DOUBLE_ARRAY,          // Failed to decrease double array size
   MSG_LIB_SYS_FAILED_DECREASE_STRING_ARRAY,          // Failed to decrease string array size
   
   MSG_LIB_SYS_FAILED_GET_LONG_DATA_OBJ,              // Failed to get long data object
   MSG_LIB_SYS_FAILED_GET_DOUBLE_DATA_OBJ,            // Failed to get double data object
   MSG_LIB_SYS_FAILED_GET_STRING_DATA_OBJ,            // Failed to get string data object
   
   MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY,            // Request outside long array
   MSG_LIB_SYS_REQUEST_OUTSIDE_DOUBLE_ARRAY,          // Request outside double array
   MSG_LIB_SYS_REQUEST_OUTSIDE_STRING_ARRAY,          // Request outside string array
   
   MSG_LIB_TEXT_YES,                                  // Yes
   MSG_LIB_TEXT_NO,                                   // No
   MSG_LIB_TEXT_AND,                                  // and

...

   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_UPPER,             // Anchor point at the upper right corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_UPPER,                   // Anchor point at the upper center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_CENTER,                  // Anchor point at the very center of the object
   
   MSG_GRAPH_OBJ_TEXT_TIME_PRICE,                     // Price/time coordinates
   MSG_GRAPH_OBJ_TEXT_PIVOT,                          // Pivot point
   MSG_GRAPH_OBJ_TEXT_LEVELSVALUE_ALL,                // Level values
   MSG_GRAPH_OBJ_TEXT_LEVEL,                          // Level
   MSG_GRAPH_OBJ_TEXT_BMP_FILE_STATE_ON,              // On state
   MSG_GRAPH_OBJ_TEXT_BMP_FILE_STATE_OFF,             // Off state

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Failed to get the list of newly added objects
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   
   MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR,           // Indicator for controlling and sending events created
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR,    // Failed to create the indicator for controlling and sending events
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                       // Chart windows closed:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,            // Objects removed together with charts:
   
  };
//+------------------------------------------------------------------+

and message texts corresponding to newly added indices:

   {"Не удалось добавить объект-буфер в список","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: "},
   
  };
//+---------------------------------------------------------------------+


In the \MQL5\Include\DoEasy\Services\ service functions folder, create a new file XDimArray.mqh of the CXDimArray class.

Since we want to introduce a replacement for ordinary arrays for storing integer, real and string properties, while making these arrays multi-dimensional and dynamic in any dimension, we will create three absolutely identical classes of a multidimensional dynamic array. Each one is to store its own type (long — for integer data, double — for real data and string — for text data).

The class hierarchy is as follows:

  • CObject --> Abstract data type class --> data type class,
  • CArrayObj class --> class of one dimension storing the list of data type classes,
  • CArrayObj class --> multidimensional array class storing the list of class objects of one dimension

Each data type is to have a separate class for storing integer, real and string data. In any case, the class is derived from the base data type class, in which we set the data type stored in the derived class.

Class of a single array dimension is derived from the CArrayObj class. In fact, it is a list storing pointers to data objects or other lists.

The multidimensional array class is the CArrayObj list storing the pointers to instances of class objects of a single array dimension. In other words, the list is the first array dimension, while the classes of a single dimension are dynamically expandable objects of the first dimension. If it contains only one object of one dimension having size 1, accessing it corresponds to the entry array[0][0]. If it has two objects of the same dimension having size 1, accessing the first one corresponds to the entry array[0][0], while accessing the second one corresponds to array[0][1].
Naturally, if objects of one dimension have sizes exceeding 1, accessing them will correspond to the entries

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

The access is, of course, performed using the appropriate methods. We are able to dynamically change both the first dimensionality of the array and the second one.

I will not consider adding the third and further dimensions here, since we only need a two-dimensional dynamic array. Based on these classes, it is possible to develop dynamic arrays with any number of dimensions. It is possible to change (increase/decrease) dimensionality or add a new one in any array cell of any dimension.

Since I will make three identical classes, each intended to store its own data type, let's consider in detail the classes of only one type.

In the already created file XDimArray.mqh of the CXDimArray class, include the files of the CArrayObj class and the library message class, as well as write the class of the abstract data unit derived from the CObject base object:

//+------------------------------------------------------------------+
//|                                                    XDimArray.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Message.mqh"
//+------------------------------------------------------------------+
//| Abstract data unit class                                         |
//+------------------------------------------------------------------+
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; }
  };
//+------------------------------------------------------------------+

Here we have a private variable for storing a stored data type in descendant objects, protected parametric constructor the descendant object stored data type is passed to and the public virtual method returning the stored data type set in the m_type variable.
In the default constructor, OBJECT_DE_TYPE_OBJECT data type is set to the m_type variable by default.

Next, add the integer data unit under this class:

//+------------------------------------------------------------------+
//| Integer data unit class                                          |
//+------------------------------------------------------------------+
class CDataUnitLong : public CDataUnit
  {
public:
   long              Value;
                     CDataUnitLong() : CDataUnit(OBJECT_DE_TYPE_LONG){}
  };
//+------------------------------------------------------------------+

The class has a public field variable for storing an integer value, as well as the class constructor. OBJECT_DE_TYPE_LONG type, indicating that the object stores integer data types, is passed to the parent class constructor in the class constructor initialization list.

Now, create the class of the object of a single long array dimension:

//+------------------------------------------------------------------+
//| Class of a single long array dimension                           |
//+------------------------------------------------------------------+
class CDimLong : public CArrayObj
  {

  }

I will write all methods in the class body. Each method will have detailed comments in the listing.

In the private section of the class, add the method receiving the long data object from the array:

//+------------------------------------------------------------------+
//| Class of a single long array dimension                           |
//+------------------------------------------------------------------+
class CDimLong : public CArrayObj
  {
private:
//--- Get long data object from the array
   CDataUnitLong    *GetData(const string source,const int index) const
                       {
                        //--- Get the pointer to the object by the index passed to the method. If the passed index is less than zero, it is set to zero
                        CDataUnitLong *data=this.At(index<0 ? 0 : index);
                        //--- If failed to receive the object
                        if(data==NULL)
                          {
                           //--- if the passed index exceeds the number of objects in the list, display a message informing of exceeding the list size
                           if(index>this.Total()-1)
                              ::Print(source,CMessage::Text(MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY)," (",index,"/",this.Total(),")");
                           //--- otherwise, display the message informing of failing to get the pointer to the object
                           else
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_LONG_DATA_OBJ);
                          }
                        //--- Return the pointer to the object or NULL in case of an error
                        return data;
                       }

This method implements control over the array out of range error. In other words, if a passed index indicates a non-existing array cell, the journal message informs of that. If the index passed to the method is less than zero (which should not happen), the value is adjusted to zero. If we access the valid array index but are unable to get the object, display the appropriate error message in the journal. As a result, we either return the pointer to the object or NULL in case of an error. Logging such errors helps to correctly specify the indices of the required data when using this class. We will access the method from other class methods for receiving array data. This method will inform of errors when accessing array objects.

Next, implement the method adding the specified number of cells with objects to the end of the array:

//--- Add the specified number of cells with objects to the end of the array
   bool              AddQuantity(const string source,const int total,CObject *object)
                       {
                        //--- Declare the variable for storing the result of adding objects to the list
                        bool res=true;
                        //--- in the list by the number of added objects passed to the method
                        for(int i=0;i<total;i++)
                          {
                           //--- if failed to add the object to the list
                           if(!this.Add(object))
                             {
                              //--- display the appropriate message, add 'false' to the variable value
                              //--- and move on to the loop next iteration
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                              res &=false;
                              continue;
                             }
                          }
                        //--- Return the total result of adding the specified number of objects to the list
                        return res;
                       }

The method receives the number of objects to be added to the array end and the pointer to the object whose instances are to be added. Next, in the loop by the specified number, add pointers to the object into the list and return the result of adding objects to the list.

The method has a logical error preventing from working with the number of added objects to the array exceeding two. I will fix it in the next article. If you have already found it then let me congratulate you. It means that my work is not in vain.

In the public section of the class, add the method for initializing the array:

public:
//--- Initialize the array
   void              Initialize(const int total,const long value=0)
                       {
                        //--- Clear the array and increase its size by the value specified in the parameters by setting the default value
                        this.Clear();
                        this.Increase(total,value);
                       }

Clear the array using the Clear() method of the parent class and increase its size by the specified value using the Increase() method:

//--- Increase the number of data cells by the specified value, return the number of added elements
   int               Increase(const int total,const long value=0)
                       {
                        //--- Save the current array size
                        int size_prev=this.Total();
                        //--- Create a new long data object
                        CDataUnitLong *data=new CDataUnitLong();
                        //--- If failed to create an object, inform of that and return zero
                        if(data==NULL)
                          {
                           ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ));
                           return 0;
                          }
                        //--- Set the specified value to a newly created object
                        data.Value=value;
                        //--- Add the specified number of object instances to the list
                        //--- and return the difference between the obtained and previous array size
                        this.AddQuantity(DFUN,total,data);
                        return this.Total()-size_prev;
                       }

The method adds the specified amount of long data to the array and returns the amount of added data.

Let's write the method decreasing the number of data cells by the specified value:

//--- Decrease the number of data cells by the specified value, return the number of removed elements. The very first element always remains
   int               Decrease(const int total)
                       {
                        //--- If not a single cell remains after removing array cells, return zero
                        if(total>this.Total()-1)
                           return 0;
                        //--- Save the current array size
                        int total_prev=this.Total();
                        //--- Calculate the initial index the array cells should be removed from
                        int from=this.Total()-total;
                        //--- The final index is always an array end
                        int to=this.Total()-1;
                        //--- If failed to remove the specified number of array cells, inform of that in the journal
                        if(!this.DeleteRange(from,to))
                           CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_DECREASE_LONG_ARRAY);
                        //--- return the difference between the previous array size and the one obtained as a result of deleting cells
                        return total_prev-this.Total();
                       }

Since the array should have at least one element, first of all, make sure that removing the required number leaves at least one or more elements in the array. Next, remove elements from the end of the list in the specified number using the DeleteRange() method of the parent class. As a result, return the number of removed elements.

Write the method setting a new array size:

//--- Set a new array size
   bool              SetSize(const int size,const long initial_value=0)
                       {
                        //--- If the zero size is passed, return 'false'
                        if(size==0)
                           return false;
                        //--- Calculate the number of cells to be added or removed for receiving the required array size
                        int total=fabs(size-this.Total());
                        //--- If the passed array size exceeds the current one,
                        //--- return the result of adding the calculated number of arrays to the array
                        if(size>this.Total())
                           return(this.Increase(total,initial_value)==total);
                        //--- otherwise, if the passed array size is less than the current one, 
                        //--- return the result of removing the calculated number of arrays from the array
                        else if(size<this.Total())
                           return(Decrease(total)==total);
                        //--- If the passed size is equal to the current one, simply return 'true'
                        return true;
                       }

The method uses the methods considered above for removing/adding elements to the array to get its size equal to the specified one.

Add the method setting the value to the specified array cell:

//--- Set the value to the specified array cell
   bool              Set(const int index,const long value)
                       {
                        //--- Get the pointer to the data object by a specified index
                        CDataUnitLong *data=this.GetData(DFUN,index);
                        //--- If failed to get the object, return 'false'
                        if(data==NULL)
                           return false;
                        //--- Return the value, passed to the method, to the obtained object and return 'true'
                        data.Value=value;
                        return true;
                       }

Here we obtain the data object by a specified index and set to it the value passed to the method. When the object is received successfully, return true. In case of a failure (the GetData() private method informs of that in the journal), return false.

The method returning the number of data in the array:

//--- Return the amount of data (the array size is defined using the Total() method of the CArrayObj parent class)
   int               Size(void) const { return this.Total(); }

The method simply returns the number of elements in the list using the Total() method of the CArray class, which is a parent of the CArrayObj class.

Let's write the method returning the value by the specified index:

//--- Returns the value at the specified index
   long              Get(const int index) const
                       {
                        //--- Get the pointer to the data object by index
                        CDataUnitLong *data=this.GetData(DFUN,index);
                        //--- If the object is received successfully,
                        //--- return the value set in the object,
                        //--- otherwise, return zero
                        return(data!=NULL ? data.Value : 0);
                       }

Get the pointer to the data object from the list by index and return the value set in the specified object. In case of an error, return zero.

The overloaded Get() method returning the boolean value when receiving data to the variable, passed to the method via a link:

   bool              Get(const int index, long &value) const
                       {
                        //--- Set the initial value to 'value' passed to the method via a link
                        value=0;
                        //--- Get the pointer to the data object by index
                        CDataUnitLong *data=this.GetData(DFUN,index);
                        //--- If failed to get the object, return 'false'
                        if(data==NULL)
                           return false;
                        //--- Set the value stored in the object to 'value' and return 'true'
                        value = data.Value;
                        return true;
                       }


In the default constructor, simply initialize the array using the Initialize() method, while specifying the array size equal to 1 and the default value equal to 0.
In the parametric constructor
, specify the necessary array size and the default value.
In the class destructor, remove the array elements and clear the array while completely freeing the array memory.

//--- Constructors
                     CDimLong(void)                               { this.Initialize(1);            }
                     CDimLong(const int total,const long value=0) { this.Initialize(total,value);  }
//--- Destructor
                    ~CDimLong(void)
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+


Next, create the class of the dynamic multidimensional long array in the same file:

//+------------------------------------------------------------------+
//| Dynamic multidimensional long array class                        |
//+------------------------------------------------------------------+
class CXDimArrayLong : public CArrayObj
  {
  
  }

The class is a list storing the objects of the above class, which in turn are also lists that store objects with data. Thus, the class is the first dimension, while the lists stored in it are the lists of the dimension data.

To make it more clear:

Index 0 of the CXDimArrayLong class list points to the CDimLong class object located first in the list; index 0 of the CDimLong class points to the CDataUnitLong class object located first in the CDimLong class list.

This is equal to the entry array[0][0];

Index 1 of the CXDimArrayLong class list points to the CDimLong class object located second in the list; index 0 of the CDimLong class points to the CDataUnitLong class object located first in the CDimLong class list.

This is equal to the entry array[1][0];

------

Index 0 of the CXDimArrayLong class list points to the CDimLong class object located first in the list; index 1 of the CDimLong class points to the CDataUnitLong class object located second in the CDimLong class list.

This is equal to the entry array[0][1];

Index 1 of the CXDimArrayLong class list points to the CDimLong class object located second in the list; index 1 of the CDimLong class points to the CDataUnitLong class object located second in the CDimLong class list.

This is equal to the entry array[1][1];

etc.


In the private section of the class, add the method returning the data array from the first dimension:

//+------------------------------------------------------------------+
//| Dynamic multidimensional long array class                        |
//+------------------------------------------------------------------+
class CXDimArrayLong : public CArrayObj
  {
private:
//--- Return the data array from the first dimensionality
   CDimLong         *GetDim(const string source,const int index) const
                       {
                        //--- Get the first dimension array object by index
                        CDimLong *dim=this.At(index<0 ? 0 : index);
                        //--- If failed to get the pointer to the object,
                        if(dim==NULL)
                          {
                           //--- if the index is outside the array, inform of the request outside the array
                           if(index>this.Total()-1)
                              ::Print(source,CMessage::Text(MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY)," (",index,"/",this.Total(),")");
                           //--- otherwise, inform of the error when receiving the pointer to the array
                           else
                              CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_LONG_DATA_OBJ);
                          }
                        //--- Return either the pointer to the object or NULL in case of an error
                        return dim;
                       }

The method receives the pointer to the CDimLong class object by the specified index. If the index is less than zero, use the index equal to zero. If the index points outside the array, inform of the request outside the array in the journal, or, if failed to receive the object, inform of the object receiving error in the journal. As a result, we either return the pointer to the object or NULL in case of an error.

In the private section of the class, write the method adding a new dimension to the first dimensionality:

//--- Add a new dimension to the first dimensionality
   bool              AddNewDim(const string source,const int size,const long initial_value=0)
                       {
                        //--- Create a new array object 
                        CDimLong *dim=new CDimLong(size,initial_value);
                        //--- If failed to create an object, inform of that and return 'false'
                        if(dim==NULL)
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ);
                           return false;
                          }
                        //--- If failed to add the object to the list, remove the object, inform of the error in the journal and return 'false'
                        if(!this.Add(dim))
                          {
                           delete dim;
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                           return false;
                          }
                        //--- Object successfully created and added to the list
                        return true;
                       }

The method creates the new CDimLong class object and adds it to the end of the list increasing the size of the first dimension.

In the public section of the class, write the methods for working with arrays. All methods are described in detail in the code comments. I believe, there is no point in repeating them here. Let's just consider them as they are:

public:
//--- Increase the number of data cells by the specified 'total' value in the first dimensionality,
//--- return the number of added elements to the dimensionality. Added cells' size is 'size'
   int               IncreaseRangeFirst(const int total,const int size,const long initial_value=0)
                       {
                        //--- Save the current array size
                        int total_prev=this.Total();
                        //--- In the loop by the specified number, add new objects to the array
                        for(int i=0;i<total;i++)
                           this.AddNewDim(DFUN,size,initial_value);
                        //--- Return the difference between the obtained and previous array size
                        return(this.Total()-total_prev);
                       }
//--- Increase the number of data cells by the specified 'total' value in the specified 'range' dimensionality,
//--- return the number of added elements to the changed dimensionality
   int               IncreaseRange(const int range,const int total,const long initial_value=0)
                       {
                        //--- Get the pointer to the array by 'range' index
                        CDimLong *dim=this.GetDim(DFUN,range);
                        //--- Return the result of increasing the array size by 'total' or zero in case of an error
                        return(dim!=NULL ? dim.Increase(total,initial_value) : 0);
                       }
//--- Decrease the number of cells with data in the first dimensionality by the specified value,
//--- return the number of removed elements. The very first element always remains
   int               DecreaseRangeFirst(const int total)
                       {
                        //--- Make sure at least one element remains in the array after the decrease,
                        //--- if not, return 'false'
                        if(total>this.Total()-1)
                           return 0;
                        //--- Save the current array size
                        int total_prev=this.Total();
                        //--- Calculate the initial index to remove the array elements from
                        int from=this.Total()-total;
                        //--- The final index is always the last array element
                        int to=this.Total()-1;
                        //--- If failed to remove the specified number of elements, inform of that in the journal
                        if(!this.DeleteRange(from,to))
                           CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_DECREASE_LONG_ARRAY);
                        //--- Return the number of removed array elements
                        return total_prev-this.Total();
                       }                      
//--- Decrease the number of data cells by the specified value in the specified dimensionality,
//--- return the number of removed elements. The very first element always remains
   int               DecreaseRange(const int range,const int total)
                       {
                        //--- Get the pointer to the array by 'range' index
                        CDimLong *dim=this.GetDim(DFUN,range);
                        //--- Return the result of decreasing the array size by 'total' or zero in case of an error
                        return(dim!=NULL ? dim.Decrease(total) : 0);
                       }
//--- Set the new array size in the specified dimensionality
   bool              SetSizeRange(const int range,const int size,const long initial_value=0)
                       {
                        //--- Get the pointer to the array by 'range' index
                        CDimLong *dim=this.GetDim(DFUN,range);
                        //--- Return the result of setting the array size to 'size' or 'false' in case of an error
                        return(dim!=NULL ? dim.SetSize(size,initial_value) : false);
                       }
//--- Set the value to the specified array cell of the specified dimension
   bool              Set(const int index,const int range,const long value)
                       {
                        //--- Get the pointer to the array by 'index'
                        CDimLong *dim=this.GetDim(DFUN,index);
                        //--- Return the result of setting the value to the array cell by 'range' index or 'false' in case of an error
                        return(dim!=NULL ? dim.Set(range,value) : false);
                       }
//--- Return the value at the specified index of the specified dimension
   long              Get(const int index,const int range) const
                       {
                        //--- Get the pointer to the array by 'index'
                        CDimLong *dim=this.GetDim(DFUN,index);
                        //--- Return the result of receiving the value from the array cell by 'range' index or 0 in case of an error
                        return(dim!=NULL ? dim.Get(range) : 0);
                       }
   bool              Get(const int index,const int range,long &value) const
                       {
                        //--- Get the pointer to the array by 'index'
                        CDimLong *dim=this.GetDim(DFUN,index);
                        //--- Return the result of receiving the value from the array cell by 'range' index to the 'value' variable
                        //--- or 'false' in case of an error ('value' is set to zero)
                        return(dim!=NULL ? dim.Get(range,value) : false);
                       }
//--- Return the amount of data (size of the specified dimension array)
   int               Size(const int range) const
                       {
                        //--- Get the pointer to the array by 'range' index
                        CDimLong *dim=this.GetDim(DFUN,range);
                        //--- Return the size of the obtained array by index or zero in case of an error
                        return(dim!=NULL ? dim.Size() : 0);
                       }
//--- Return the total amount of data (the total size of all dimensions)
   int               Size(void) const
                       {
                        //--- Set the initial size
                        int size=0;
                        //--- In the loop by all arrays in the list,
                        for(int i=0;i<this.Total();i++)
                          {
                           //--- get the next array.
                           CDimLong *dim=this.GetDim(DFUN,i);
                           //--- If failed to get the array, move on to the next one
                           if(dim==NULL)
                              continue;
                           //--- Add the array size to the size value
                           size+=dim.Size();
                          }
                        //--- Return the obtained value
                        return size;
                       }
//--- Constructor
                     CXDimArrayLong()
                       {
                        //--- Clear the list and add a single array to it
                        this.Clear();
                        this.Add(new CDimLong(1));
                       }
                     CXDimArrayLong(int first_dim_size,const int dim_size,const long initial_value=0)
                       {
                        //--- Clear the list
                        this.Clear();
                        int total=(first_dim_size<1 ? 1 : first_dim_size);
                        //--- In the loop by the necessary number of arrays calculated in 'total' from first_dim_size,
                        //--- add new arrays with the specified number of elements in dim_size to the list
                        for(int i=0;i<total;i++)
                           this.Add(new CDimLong(dim_size,initial_value));
                       }
//--- Destructor
                    ~CXDimArrayLong()
                       {
                        //--- Remove array elements and
                        //--- clear the array while completely freeing the array memory
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+

As you can see, we mainly work with the methods of the obtained CDimLong class object I have already considered above when writing the CDimLong class. If you have any questions, feel free to ask them in the comments below.

Now we need to write exactly the same classes for data of double and string types. The classes are completely identical to the ones discussed above:

//+------------------------------------------------------------------+
//| Real data unit class                                             |
//+------------------------------------------------------------------+
class CDataUnitDouble : public CDataUnit
  {
public:
   double            Value;
//--- Constructor
                     CDataUnitDouble() : CDataUnit(OBJECT_DE_TYPE_DOUBLE){}
  };
//+------------------------------------------------------------------+
//| Class of a single double array dimension                         |
//+------------------------------------------------------------------+
class CDimDouble : public CArrayObj
  {
private:
//--- Get long data object from the array
   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;
                       }
//--- Add the specified number of cells with objects to the end of the array
   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:
//--- Initialize the array
   void              Initialize(const int total,const double value=0)
                       {
                        this.Clear();
                        this.Increase(total,value);
                       }
//--- Increase the number of data cells by the specified value, return the number of added elements
   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;
                       }
//--- Decrease the number of data cells by the specified value, return the number of removed elements. The very first element always remains
   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();
                       }
//--- Set a new array size
   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;
                       }
//--- Set the value to the specified array cell
   bool              Set(const int index,const double value)
                       {
                        CDataUnitDouble *data=this.GetData(DFUN,index);
                        if(data==NULL)
                           return false;
                        data.Value=value;
                        return true;
                       }
//--- Return the amount of data (array size)
   int               Size(void) const { return this.Total(); }
   
//--- Returns the value at the specified index
   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;
                       }
//--- Constructors
                     CDimDouble(void)                                { this.Initialize(1);            }
                     CDimDouble(const int total,const double value=0){ this.Initialize(total,value);  }
//--- Destructor
                    ~CDimDouble(void)
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+
//| Dynamic multidimensional double array class                      |
//+------------------------------------------------------------------+
class CXDimArrayDouble : public CArrayObj
  {
private:
//--- Return the data array from the first dimensionality
   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;
                       }
//--- Add a new dimension to the first dimensionality
   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:
//--- Increase the number of data cells by the specified 'total' value in the first dimensionality,
//--- return the number of added elements to the dimensionality. Added cells' size is '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);
                       }
//--- Increase the number of data cells by the specified 'total' value in the specified 'range' dimensionality,
//--- return the number of added elements to the changed dimensionality
   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);
                       }
//--- Decrease the number of cells with data in the first dimensionality by the specified value,
//--- return the number of removed elements. The very first element always remains
   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();
                       }                      
//--- Decrease the number of data cells by the specified value in the specified dimensionality,
//--- return the number of removed elements. The very first element always remains
   int               DecreaseRange(const int range,const int total)
                       {
                        CDimDouble *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Decrease(total) : 0);
                       }
//--- Set the new array size in the specified dimensionality
   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);
                       }
//--- Set the value to the specified array cell of the specified dimension
   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);
                       }
//--- Return the value at the specified index of the specified dimension
   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);
                       }
//--- Return the amount of data (size of the specified dimension array)
   int               Size(const int range) const
                       {
                        CDimDouble *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Size() : 0);
                       }
//--- Return the total amount of data (the total size of all dimensions)
   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;
                       }
//--- Constructor
                     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));
                       }
//--- Destructor
                    ~CXDimArrayDouble()
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| String data unit class                                           |
//+------------------------------------------------------------------+
class CDataUnitString : public CDataUnit
  {
public:
   string            Value;
                     CDataUnitString() : CDataUnit(OBJECT_DE_TYPE_STRING){}
  };
//+------------------------------------------------------------------+
//| Class of a single string array dimension                         |
//+------------------------------------------------------------------+
class CDimString : public CArrayObj
  {
private:
//--- Get long data object from the array
   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;
                       }
//--- Add the specified number of cells with objects to the end of the array
   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:
//--- Initialize the array
   void              Initialize(const int total,const string value="")
                       {
                        this.Clear();
                        this.Increase(total,value);
                       }
//--- Increase the number of data cells by the specified value, return the number of added elements
   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;
                       }
//--- Decrease the number of data cells by the specified value, return the number of removed elements. The very first element always remains
   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();
                       }
//--- Set a new array size
   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;
                       }
//--- Set the value to the specified array cell
   bool              Set(const int index,const string value)
                       {
                        CDataUnitString *data=this.GetData(DFUN,index);
                        if(data==NULL)
                           return false;
                        data.Value=value;
                        return true;
                       }
//--- Return the amount of data (array size)
   int               Size(void) const { return this.Total(); }
   
//--- Returns the value at the specified index
   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;
                       }
//--- Constructors
                     CDimString(void)                                   { this.Initialize(1);            }
                     CDimString(const int total,const string value="")  { this.Initialize(total,value);  }
//--- Destructor
                    ~CDimString(void)
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+
//| Dynamic multidimensional string array class                      |
//+------------------------------------------------------------------+
class CXDimArrayString : public CArrayObj
  {
private:
//--- Return the data array from the first dimensionality
   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;
                       }
//--- Add a new dimension to the first dimensionality
   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:
//--- Increase the number of data cells by the specified 'total' value in the first dimensionality,
//--- return the number of added elements to the dimensionality. Added cells' size is '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);
                       }
//--- Increase the number of data cells by the specified 'total' value in the specified 'range' dimensionality,
//--- return the number of added elements to the changed dimensionality
   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);
                       }
//--- Decrease the number of cells with data in the first dimensionality by the specified value,
//--- return the number of removed elements. The very first element always remains
   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();
                       }                      
//--- Decrease the number of data cells by the specified value in the specified dimensionality,
//--- return the number of removed elements. The very first element always remains
   int               DecreaseRange(const int range,const int total)
                       {
                        CDimString *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Decrease(total) : 0);
                       }
//--- Set the new array size in the specified dimensionality
   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);
                       }
//--- Set the value to the specified array cell of the specified dimension
   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);
                       }
//--- Return the value at the specified index of the specified dimension
   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);
                       }
//--- Return the amount of data (size of the specified dimension array)
   int               Size(const int range) const
                       {
                        CDimString *dim=this.GetDim(DFUN,range);
                        return(dim!=NULL ? dim.Size() : 0);
                       }
//--- Return the total amount of data (the total size of all dimensions)
   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;
                       }
//--- Constructor
                     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));
                       }
//--- Destructor
                    ~CXDimArrayString()
                       {
                        this.Clear();
                        this.Shutdown();
                       }
  };
//+------------------------------------------------------------------+


Now we are ready to raise the abstract graphical object class to a new level by replacing handling simple arrays that store integer, real and string properties with handling dynamic arrays provided by the classes considered above.

Before we start changing the graphical object class, we need to make the newly created classes visible in the library just like the rest of its classes. To do this, include the file with the classes to the library service functions file \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//|                                                        DELib.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property strict  // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Defines.mqh"
#include "Message.mqh"
#include "TimerCounter.mqh"
#include "Pause.mqh"
#include "Colors.mqh"
#include "XDimArray.mqh"
//+------------------------------------------------------------------+
//| Service functions                                                |
//+------------------------------------------------------------------+

Now these classes will be visible to all library classes.

Two-dimensional dynamic array for storing object properties

Almost all library objects have arrays storing integer, real and string object properties. Arrays are one-dimensional and have a rigidly set size corresponding to the number of necessary properties. This structure was convenient till we faced the need to manage the dynamically changing number of object properties. I will start making adjustments in the library objects from the abstract standard graphical object class. All subsequent objects will be created using the new functionality based on the dynamic multidimensional array class. Most probably, I will subsequently make the previously created objects to handle dynamic arrays as well.

The private section of the abstract standard graphical object file \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh features the arrays for storing properties (the current and previous ones) and the methods for receiving a real index of the specified property in the array :

//+------------------------------------------------------------------+
//|                                                 GStdGraphObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\GBaseObj.mqh"
//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   long              m_long_prop[GRAPH_OBJ_PROP_INTEGER_TOTAL];         // Integer properties
   double            m_double_prop[GRAPH_OBJ_PROP_DOUBLE_TOTAL];        // Real properties
   string            m_string_prop[GRAPH_OBJ_PROP_STRING_TOTAL];        // String properties

   long              m_long_prop_prev[GRAPH_OBJ_PROP_INTEGER_TOTAL];    // Integer properties before change
   double            m_double_prop_prev[GRAPH_OBJ_PROP_DOUBLE_TOTAL];   // Real properties before change
   string            m_string_prop_prev[GRAPH_OBJ_PROP_STRING_TOTAL];   // String properties before change

//--- Return the index of the array the (1) double and (2) string properties are actually located at
   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:

Now all these arrays and methods should be transferred to a new class which is to be a dynamic two-dimensional array for storing the properties of the object whose second dimension should change dynamically to track the change of a single object property, such as the object pivot point price and time or color, style, width and description of object levels whose number can change dynamically.

Till the class is fully debugged, I will place it directly in the private section of the abstract standard graphical object class. After the entire functionality is debugged and ready, I will transfer the class of the dynamic two-dimensional object property array to a separate file to access it in other objects rather than writing it anew inside the object.

Remove all arrays and methods specified above from the private section and define the new class of object properties in their place:

//+------------------------------------------------------------------+
//|                                                 GStdGraphObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\GBaseObj.mqh"
//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   //--- Object property class
   class CDataPropObj
     {
      
     }

In the private section of the new class, declare the list for storing property objects to be introduced by the CXDimArrayLong dynamic multidimensional array class, the variables for storing the number of integer, real and string object properties and two methods returning the index of the array, which actually stores double and string object properties:

//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   //--- Object property class
   class CDataPropObj
     {
   private:
      CArrayObj         m_list;        // list of property objects
      int               m_total_int;   // Number of integer parameters
      int               m_total_dbl;   // Number of real parameters
      int               m_total_str;   // Number of string parameters
      //--- Return the index of the array the (1) double and (2) string properties are actually located at
      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;    }

In the public section of the class, declare the method returning the list of property objects, pointers to objects of integer, real and string properties and the methods for setting and receiving the specified object properties:

   //--- Object property class
   class CDataPropObj
     {
   private:
      CArrayObj         m_list;        // list of property objects
      int               m_total_int;   // Number of integer parameters
      int               m_total_dbl;   // Number of real parameters
      int               m_total_str;   // Number of string parameters
      //--- Return the index of the array the (1) double and (2) string properties are actually located at
      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:
      //--- Return the pointer to (1) the list of property objects, as well as to the object of (2) integer, (3) real and (4) string properties
      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);                                 }
      //--- Set object's (1) integer, (2) real and (3) string properties
      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);  }
      //--- Return object’s (1) integer, (2) real and (3) string property from the properties array
      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); }

The CArrayObj list is to store three pointers to the instances of class objects of the dynamic array created above — integer, real and string ones to be created in the class constructor and placed to the list. The Set and Get methods are similar to the previously used ones except that they now have one more parameter. Apart from the object property index, we also indicate the property index in the array second dimension. For most properties, the index is always equal to zero. But for several same-type object properties, I will specify the property modifier index for receiving the first, second, third and subsequent properties of price, time, etc.

Also, in the public section of the class, write the method returning the size of the specified first dimension data array:

      //--- Return the size of the specified first dimension data array
      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;
                          }

The method passes the index of the necessary property of the property array first dimension (range). Next, check the following:
if the property value is less than the total number of integer properties, this is an integer property request — return the size of the integer property array using the Size() method of the Long object;
if the property value is less than the total number of integer properties + the number of real properties, this is a request for a real property — return the size of the real property array using the Size() method of the Double object;
if the property value is less than the total number of integer properties + the number of real properties + the number of string properties, this is a request for a string property — return the size of the string property array of the String object.
We have considered the Size() method above when creating the object classes of dynamic arrays.

In the public section of the class, write the method setting the array size in the specified dimensionality:

      //--- Set the array size in the specified dimensionality
      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;
                          }

The method logic is identical to the size receiving method considered above.

The class constructor receives the number of integer, real and string object properties. These values are assigned to the appropriate class variables. Next, new objects of the integer, real and string dynamic multidimensional array are added to the list. The array has the dimensionality of the first dimension equal to the number of the appropriate properties passed in the parameters, while the size of the second dimension is set to 1 for each property array.

      //--- Constructor
                        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));
                          }

In the class destructor, remove the elements from the list and free the array memory:

      //--- Destructor
                       ~CDataPropObj()
                          {
                           m_list.Clear();
                           m_list.Shutdown();
                          }

As a result, the object property class looks as follows:

   //--- Object property class
   class CDataPropObj
     {
   private:
      CArrayObj         m_list;        // list of property objects
      int               m_total_int;   // Number of integer parameters
      int               m_total_dbl;   // Number of real parameters
      int               m_total_str;   // Number of string parameters
      //--- Return the index of the array the (1) double and (2) string properties are actually located at
      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:
      //--- Return the pointer to (1) the list of property objects, as well as to the object of (2) integer, (3) real and (4) string properties
      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);                                 }
      //--- Set object's (1) integer, (2) real and (3) string properties
      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);  }
      //--- Return object’s (1) integer, (2) real and (3) string property from the properties array
      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); }
      
      //--- Return the size of the specified first dimension data array
      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;
                          }
      //--- Set the array size in the specified dimensionality
      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;
                          }
      //--- Constructor
                        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));
                          }
      //--- Destructor
                       ~CDataPropObj()
                          {
                           m_list.Clear();
                           m_list.Shutdown();
                          }
     };

However, in order to track changes in the object properties, we need to remember the past state of all its properties and compare it with the current one. To achieve this, I previously held two sets of object properties. Now I will simply create another class to contain two objects of the newly written class. The first object is meant to store the current properties, while the second one is to store the previous properties.

Also in the private section of the class, declare a new data class of the current and previous properties:

   class CProperty
     {
      
     }

All class fields and methods are to be public. Let's have a look at the class listing. It is relatively small and stores objects of the previously considered classes:

   //--- Data class of the current and previous properties
   class CProperty
     {
   public:
      CDataPropObj     *Curr;    // Pointer to the current properties object
      CDataPropObj     *Prev;    // Pointer to the previous properties object
      //--- Set the array size ('size') in the specified dimension ('range')
      bool              SetSizeRange(const int range,const int size)
                          {
                           return(this.Curr.SetSizeRange(range,size) && this.Prev.SetSizeRange(range,size) ? true : false);
                          }
      //--- Return the size of the specified array of the (1) current and (2) previous first dimension data
      int               CurrSize(const int range)  const { return Curr.Size(range); }
      int               PrevSize(const int range)  const { return Prev.Size(range); }
      //--- Copy the current data to the previous one
      void              CurrentToPrevious(void)
                          {
                           //--- Copy all integer properties
                           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));
                           //--- Copy all real properties
                           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));
                           //--- Copy all string properties
                           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));
                          }
      //--- Constructor
                        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);
                          }
     };

Apart from handling the methods of the previously created classes, I have added the method for copying the current properties to the previous ones. Copying is performed element by element, since we need to have two independent copies of the same object. If we had resorted to the AssignArray method of the CArrayObj class, we would have copied the pointers rather than property values. This would have led to creating an exact copy of the object, while changing a property in one object would have led to changing it in another one, and I do not need this.

So, replacing simple arrays with dynamic array class objects I have just implemented (albeit not in full yet) implies using the CProperty class object as a pointer to the graphical object properties, as well as involves making multiple changes in the library classes, since now, in addition to a property index, we also need to specify an index of the price and time pivot point or an index of the level whose property we want to get or set.


Improving library classes

In the private section of the abstract graphical object class (we are currently working in), declare the pointer to the object of graphical object properties, the variable for storing the number of object pivot points and the methods for setting multiple values of a single object property immediately after the newly written property class:

   CProperty        *Prop;                                              // Pointer to the properties object
   int               m_pivots;                                          // Number of object reference points
//--- Read and set (1) the time and (2) the price of the specified object pivot point
   void              SetTimePivot(const int index);
   void              SetPricePivot(const int index);
//--- Read and set (1) color, (2) style, (3) width, (4) value, (5) text of the specified object level
   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);
//--- Read and set the BMP file name for the "Bitmap Level" object. Index: 0 - ON, 1 - OFF
   void              SetBMPFile(const int index);

In the public section of the class, supplement all Get and Set methods with one more variable indicating the index of the property array second dimension. The properties will be returned and received using the Get and Set methods from the Curr and Prev fields of the CProperty class storing the arrays of the current and previous properties:

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   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);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   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); }
      
//--- Set object's previous (1) integer, (2) real and (3) string properties
   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);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array
   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); }

The changes should be made along the entire class code where GetProperty(), SetProperty(), GetPropertyPrev() and SetPropertyPrev() methods are used since the index of the second dimension is now specified in them. I will not describe all multiple changes here since this will take a lot of article space. They have all been included into the class code attached below. Instead, let's consider new and some old methods.

In the public section of the class, declare the methods returning descriptions of pivot points and object levels:

//--- Return the description of the (1) (ENUM_OBJECT) graphical object type
   virtual string    TypeDescription(void)                                    const { return CMessage::Text(MSG_GRAPH_STD_OBJ_ANY);          }
//--- Return the description of the coordinate of the specified graphical object (1) price and (2) time
   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); }
//--- Return the description of the specified level (1) color, (2) style, (3) width, (4) value
   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); }

//--- Return the descriptions of all (1) times, (2) prices, (3) pivot point times and prices,
//--- (4) level values, (6) all properties of all levels, (5) BMP files of the graphical object
   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;

In the protected parametric class constructor, pass yet another parameter — the number of object pivot points required for its construction (this means we need to additionally improve the descendant object classes so that they pass the number of pivot points to this constructor):

//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; m_group=WRONG_VALUE; }
protected:
//--- Protected parametric constructor
                     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);

Let's have a look at the implementation of the class constructor:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
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)
  {
   //--- Create the property object with the default values
   this.Prop=new CProperty(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL);
   
//--- Set the number of pivot points and object levels
   this.m_pivots=pivots;
   int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS);

//--- Set the property array dimensionalities according to the number of pivot points and 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);
   
//--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol 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));
   
//--- Save the integer properties inherent in all graphical objects but not present in the current one
   this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID());                // Chart ID
   this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());               // Chart subwindow index
   this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject());            // Graphical object type (ENUM_OBJECT)
   this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement());   // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong());                   // Graphical object affiliation
   this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,CGBaseObj::Group());                     // Graphical object group
   this.SetProperty(GRAPH_OBJ_PROP_ID,0,0);                                         // Object ID
   this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0);                                        // Object index in the list
   
//--- Save the properties inherent in all graphical objects and present in a graphical object
   this.PropertiesRefresh();
   
//--- Save basic properties in the parent object
   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);

//--- Save the current properties to the previous ones
   this.PropertiesCopyToPrevData();
  }
//+-------------------------------------------------------------------+

Here I first created the object of the graphical object properties class by specifying the number of integer, real and string properties. Next, for the properties featuring several parameters (pivot point price and time, number of levels and properties of each of the levels), set the size of the arrays in the second dimension. When setting and getting properties having only one parameter, specify the parameter index in the second dimension equal to zero.

This time, in the method comparing CGStdGraphObj objects by all properties, compare the properties in the loop by the second dimension size:

//+------------------------------------------------------------------+
//| Compare CGStdGraphObj objects by all properties                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

In the methods getting and saving graphical object properties, all properties having multiple values are now filled out in the loop by the number of properties:

//+------------------------------------------------------------------+
//| Get and save the integer properties                              |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveINT(void)
  {
   //--- Properties inherent in all graphical objects and present in a graphical object
   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));  // Object creation time
   this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES));  // Object visibility on timeframes
   this.SetProperty(GRAPH_OBJ_PROP_BACK,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK));              // Background object
   this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER));          // Priority of a graphical object for receiving the event of clicking on a chart
   this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN));          // Disable displaying the name of a graphical object in the terminal object list
   this.SetProperty(GRAPH_OBJ_PROP_SELECTED,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED));      // Object selection
   this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE));  // Object availability
   for(int i=0;i<this.m_pivots;i++)                                                                                  // Point time coordinates
      this.SetTimePivot(i);
   this.SetProperty(GRAPH_OBJ_PROP_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR));            // Color
   this.SetProperty(GRAPH_OBJ_PROP_STYLE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE));            // Style
   this.SetProperty(GRAPH_OBJ_PROP_WIDTH,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH));            // Line width
   //--- Properties belonging to different graphical objects
   this.SetProperty(GRAPH_OBJ_PROP_FILL,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL));              // Fill an object with color
   this.SetProperty(GRAPH_OBJ_PROP_READONLY,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY));      // Ability to edit text in the Edit object
   this.SetProperty(GRAPH_OBJ_PROP_LEVELS,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS));          // Number of levels
   for(int i=0;i<this.Levels();i++)                                                                                  // Level data
     {
      this.SetLevelColor(i);
      this.SetLevelStyle(i);
      this.SetLevelWidth(i);
     }
   this.SetProperty(GRAPH_OBJ_PROP_ALIGN,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN));            // Horizontal text alignment in the Edit object (OBJ_EDIT)
   this.SetProperty(GRAPH_OBJ_PROP_FONTSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE));      // Font size
   this.SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT));      // Ray goes to the left
   this.SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT));    // Ray goes to the right
   this.SetProperty(GRAPH_OBJ_PROP_RAY,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY));                // Vertical line goes through all windows of a chart
   this.SetProperty(GRAPH_OBJ_PROP_ELLIPSE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE));        // Display the full ellipse of the Fibonacci Arc object
   this.SetProperty(GRAPH_OBJ_PROP_ARROWCODE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE));    // Arrow code for the "Arrow" object
   this.SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR));          // Position of the binding point of the graphical object
   this.SetProperty(GRAPH_OBJ_PROP_XDISTANCE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE));    // Distance from the base corner along the X axis in pixels
   this.SetProperty(GRAPH_OBJ_PROP_YDISTANCE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE));    // Distance from the base corner along the Y axis in pixels
   this.SetProperty(GRAPH_OBJ_PROP_DIRECTION,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION));    // Gann object trend
   this.SetProperty(GRAPH_OBJ_PROP_DEGREE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE));          // Elliott wave marking level
   this.SetProperty(GRAPH_OBJ_PROP_DRAWLINES,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES));    // Display lines for Elliott wave marking
   this.SetProperty(GRAPH_OBJ_PROP_STATE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE));            // Button state (pressed/released)
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID));// Chart object ID (OBJ_CHART).
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD));    // Chart object period
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE));  // Time scale display flag for the Chart object
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE));// Price scale display flag for the Chart object
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE));// Chart object scale
   this.SetProperty(GRAPH_OBJ_PROP_XSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE));            // Object width along the X axis in pixels.
   this.SetProperty(GRAPH_OBJ_PROP_YSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE));            // Object height along the Y axis in pixels.
   this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET));        // X coordinate of the upper-left corner of the visibility area.
   this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET));        // Y coordinate of the upper-left corner of the visibility area.
   this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR));        // Background color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
   this.SetProperty(GRAPH_OBJ_PROP_CORNER,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER));          // Chart corner for binding a graphical object
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE));// Border type for "Rectangle border"
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR));// Border color for OBJ_EDIT and OBJ_BUTTON
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get and save the real properties                                 |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveDBL(void)
  {
   for(int i=0;i<this.m_pivots;i++)                                                                               // Point prices coordinates
      SetPricePivot(i);
   for(int i=0;i<this.Levels();i++)                                                                               // Level values
      this.SetLevelValue(i);
   this.SetProperty(GRAPH_OBJ_PROP_SCALE,0,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_SCALE));          // Scale (property of Gann objects and Fibonacci Arcs objects)
   this.SetProperty(GRAPH_OBJ_PROP_ANGLE,0,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_ANGLE));          // Angle
   this.SetProperty(GRAPH_OBJ_PROP_DEVIATION,0,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_DEVIATION));  // Deviation of the standard deviation channel
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get and save the string properties                               |
//+------------------------------------------------------------------+
void CGStdGraphObj::GetAndSaveSTR(void)
  {
   this.SetProperty(GRAPH_OBJ_PROP_TEXT,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TEXT));            // Object description (the text contained in the object)
   this.SetProperty(GRAPH_OBJ_PROP_TOOLTIP,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_TOOLTIP));      // Tooltip text
   for(int i=0;i<this.Levels();i++)                                                                               // Level descriptions
      this.SetLevelText(i);
   this.SetProperty(GRAPH_OBJ_PROP_FONT,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_FONT));            // Font
   this.SetProperty(GRAPH_OBJ_PROP_BMPFILE,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_BMPFILE));      // BMP file name for the "Bitmap Level" object
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_SYMBOL));// Chart object symbol 
  }
//+------------------------------------------------------------------+

In the method copying the current data to the previous one, call the method of the current properties object specifically created to copy the current properties to the previous ones:

//+------------------------------------------------------------------+
//| Copy the current data to the previous one                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCopyToPrevData(void)
  {
   this.Prop.CurrentToPrevious();
  }
//+------------------------------------------------------------------+

In the method checking the changes in the object properties, first check if the property is multiple. If yes, check the change in the loop by the number of property instances. Otherwise, simply compare the properties of two objects:

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
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(),", property index: ",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(),", property index: ",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(),", property index: ",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();
  }
//+------------------------------------------------------------------+


The methods reading and setting object properties and returning property descriptions:

//+------------------------------------------------------------------+
//| Read and set the time of the specified pivot point               |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetTimePivot(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_TIME,index,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIME,index));
  }
//+------------------------------------------------------------------+
//| Read and set the price of the specified pivot point              |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetPricePivot(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_PRICE,index,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_PRICE,index));
  }
//+------------------------------------------------------------------+
//| Read and set the color of the specified level                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelColor(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,index,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELCOLOR,index));
  }
//+------------------------------------------------------------------+
//| Read and set the style of the specified level                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelStyle(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,index,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELSTYLE,index));
  }
//+------------------------------------------------------------------+
//| Read and set the width of the specified level                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelWidth(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,index,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELWIDTH,index));
  }
//+------------------------------------------------------------------+
//| Read and set the value of the specified level                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelValue(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELVALUE,index,::ObjectGetDouble(this.ChartID(),this.Name(),OBJPROP_LEVELVALUE,index));
  }
//+------------------------------------------------------------------+
//| Read and set the text of the specified level                     |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetLevelText(const int index)
  {
   this.SetProperty(GRAPH_OBJ_PROP_LEVELTEXT,index,::ObjectGetString(this.ChartID(),this.Name(),OBJPROP_LEVELTEXT,index));
  }
//+------------------------------------------------------------------+
//| Read and set the BMP file name                                   |
//| for the Bitmap Label object.                                     |
//| Index: 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));
  }
//+------------------------------------------------------------------+
//| Return the descriptions of all pivot point times                 |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the prices of all pivot points                            |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the descriptions of all pivot points                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the descriptions of all level colors                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the descriptions of all level styles                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the descriptions of all level width                       |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the descriptions of all level values                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the descriptions of all level texts                       |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the descriptions of the graphical object BMP files        |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the descriptions of all values of all levels              |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

I believe, these methods hardly need any comments. Apart from all considered methods and changes, the class features multiple changes for specifying the second dimension index. I will not describe them here. You can find them in the files attached below.

Since the class constructor has a new variable passing the number of pivot points when creating a child class, we need to revise the constructors of each such object. I will consider only two graphical object class files.

Open the Buy sign graphical object class file from \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdArrowBuyObj.mqh and add passing the parameter of the number of object pivot points to the parent class constructor:

//+------------------------------------------------------------------+
//| Buy graphical object                                             |
//+------------------------------------------------------------------+
class CGStdArrowBuyObj : public CGStdGraphObj
  {
private:

public:
   //--- Constructor
                     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)
                          {
                           //--- Specify the object property
                           CGStdGraphObj::SetProperty(GRAPH_OBJ_PROP_ANCHOR,0,ANCHOR_TOP);
                          }
   //--- Supported object properties (1) real, (2) integer
   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);
//--- Return the graphical object anchor point position
   ENUM_ARROW_ANCHOR Anchor(void)            const { return (ENUM_ARROW_ANCHOR)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,0); }
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
//--- Return the object short name
   virtual string    Header(const bool symbol=false);
//--- Return the description of the (ENUM_OBJECT) graphical object type
   virtual string    TypeDescription(void)   const { return StdGraphObjectTypeDescription(OBJ_ARROW_BUY);  }
//--- Return the description of the graphical object anchor point position
   virtual string    AnchorDescription(void) const { return AnchorForArrowObjDescription(this.Anchor()); }

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

This object has only one pivot point. Now let's consider the object with several pivot points.

Open the "Trend line" object class file from \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdTrendObj.mqh and add passing the number of pivot points (equal to two) to the parent class constructor:

//+------------------------------------------------------------------+
//| "Trend line" graphical object                                    |
//+------------------------------------------------------------------+
class CGStdTrendObj : public CGStdGraphObj
  {
private:

public:
   //--- Constructor
                     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)
                          {
                           //--- Get and save the object properties
                           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));
                          }
   //--- Supported object properties (1) real, (2) integer
   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);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false,const bool symbol=false);
//--- Return the object short name
   virtual string    Header(const bool symbol=false);
//--- Return the description of the (ENUM_OBJECT) graphical object type
   virtual string    TypeDescription(void)   const { return StdGraphObjectTypeDescription(OBJ_TREND); }
  };
//+------------------------------------------------------------------+

Such changes have been made for each class in \MQL5\Include\DoEasy\Objects\Graph\Standard\. Each object has the exact number of pivot points used to construct it. You can view all the changes in the files attached below.

Now let's improve the CSelect class in \MQL5\Include\DoEasy\Services\Select.mqh. Here we simply need to add the second dimension indices to each method which is to access the GetProperty() methods:

//+------------------------------------------------------------------+
//| Methods of working with standard graphical object data           |
//+------------------------------------------------------------------+
   //--- Return the list of objects with one of (1) integer, (2) real and (3) string properties meeting a specified criterion
   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);
   //--- Return the graphical object index in the list with the maximum value of the (1) integer, (2) real and (3) string properties
   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);
   //--- Return the graphical object index in the list with the minimum value of the (1) integer, (2) real and (3) string properties
   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);
//---
  };
//+------------------------------------------------------------------+

Let's consider the implementation of several methods:

//+------------------------------------------------------------------+
//| Return the list of objects with one integer                      |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Return the object index in the list                              |
//| with the maximum integer property value                          |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

All changes are highlighted with color. The remaining methods are modified identically to the ones presented above. You can familiarize yourself with them in the files attached below.

Now let's modify the graphical object collection class in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

In the GetList() public methods, add the indication of the array second dimension index:

public:
//--- Return itself
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
//--- Return the full collection list of standard graphical objects "as is"
   CArrayObj        *GetListGraphObj(void)                                                               { return &this.m_list_all_graph_obj;   }
//--- Return the full collection list of graphical elements on canvas "as is"
   CArrayObj        *GetListCanvElm(void)                                                                { return &this.m_list_all_canv_elm_obj;}
//--- Return the list of graphical elements by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   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);  }
//--- Return the list of graphical objects by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   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); }

All methods featuring access to the improved CSelect class methods, add the indication of the array second dimension index:

//+------------------------------------------------------------------+
//| Return the first free graphical object ID                        |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|Find an object present in the collection but not on a chart       |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------------------+
//| Return the flag indicating the presence of the graphical object class object |
//| in the graphical object collection list                                      |
//+------------------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Remove a graphical object by a chart ID                          |
//| from the graphical object collection list                        |
//+------------------------------------------------------------------+
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);
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Return a graphical object by chart name and ID                   |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

In the method adding the graphical object to the collection, display the full object description instead of the short one so that we are able to see the full list of the object properties during the test:

//+------------------------------------------------------------------+
//| Add a graphical object to the collection                         |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control)
  {
   //--- Get the list of the last added graphical objects from the class for managing graphical objects
   CArrayObj *list=obj_control.GetListNewAddedObj();
   //--- If failed to obtain the list, inform of that and return 'false'
   if(list==NULL)
     {
      CMessage::ToLog(DFUN_ERR_LINE,MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST);
      return false;
     }
   //--- If the list is empty, return 'false'
   if(list.Total()==0)
      return false;
   //--- Declare the variable for storing the result
   bool res=true;
   //--- In the loop by the list of newly added standard graphical objects,
   for(int i=0;i<list.Total();i++)
     {
      //--- retrieve the next object from the list and
      CGStdGraphObj *obj=list.Detach(i);
      //--- if failed to get the object, inform of that, add 'false' to the resulting variable and move on to the next one
      if(obj==NULL)
        {
         CMessage::ToLog(source,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
         res &=false;
         continue;
        }
      //--- if failed to add the object to the collection list, inform of that,
      //--- remove the object, add 'false' to the resulting variable and move on to the next one
      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;
        }
      //--- The object has been successfully retrieved from the list of newly added graphical objects and introduced into the collection -
      //--- find the next free object ID, write it to the property and display the short object description in the journal
      else
        {
         obj.SetObjectID(this.GetFreeGraphObjID());
         obj.Print();
        }
     }
   //--- Return the result of adding the object to the collection
   return res;
  }
//+------------------------------------------------------------------+

These are all the improvements I was going to implement here. Small but numerous changes that I have not considered in the article can be found in the files below.


Testing object events with two anchor points

To perform the test, let's use the EA from the previous article and save it to \MQL5\Experts\TestDoEasy\Part88\ as TestDoEasyPart88.mq5.

No changes in the EA are needed since they have been made in the library files.

Compile the EA and launch it on the chart:


When adding objects having two pivot points and when modifying any of the two pivot points, the EA displays the appropriate event entries in the journal. If we add the object consisting of more than two pivot points to the chart, there will be no point modification entries or the data of one point will be changed when changing another one. This occurs because of the logical error mentioned above. I am going to fix it in the next article.


What's next?

In the next article, I will continue my work on standard graphical object events and dynamic array classes.

All files of the current version of the library are attached below together with the test EA file for MQL5 for you to test and download. Please note that you need the indicator file for the library from the previous article for the EA to work correctly.

Leave your questions, comments and suggestions in the comments.

Back to contents

*Previous articles within the series:

Graphics in DoEasy library (Part 82): Library objects refactoring and collection of graphical objects
Graphics in DoEasy library (Part 83): Class of the abstract standard graphical object
Graphics in DoEasy library (Part 84): Descendant classes of the abstract standard graphical object
Graphics in DoEasy library (Part 85): Graphical object collection - adding newly created objects
Graphics in DoEasy library (Part 86): Graphical object collection - managing property modification
Graphics in DoEasy library (Part 87): Graphical object collection - managing object property modification on all open charts

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/10091

Attached files |
MQL5.zip (4164.99 KB)
An attempt at developing an EA constructor An attempt at developing an EA constructor
In this article, I offer my set of trading functions in the form of a ready-made EA. This method allows getting multiple trading strategies by simply adding indicators and changing inputs.
Using AutoIt With MQL5 Using AutoIt With MQL5
Short description. In this article we will explore scripting the MetraTrader 5 terminal by integrating MQL5 with AutoIt. In it we will cover how to automate various tasks by manipulating the terminals' user interface and also present a class that uses the AutoItX library.
MQL5 Cookbook – Economic Calendar MQL5 Cookbook – Economic Calendar
The article highlights the programming features of the Economic Calendar and considers creating a class for a simplified access to the calendar properties and receiving event values. Developing an indicator using CFTC non-commercial net positions serves as a practical example.
Fix PriceAction Stoploss or Fixed RSI (Smart StopLoss) Fix PriceAction Stoploss or Fixed RSI (Smart StopLoss)
Stop-loss is a major tool when it comes to money management in trading. Effective use of stop-loss, take profit and lot size can make a trader more consistent in trading and overall more profitable. Although stop-loss is a great tool, there are challenges that are encountered when being used. The major one being stop-loss hunt. This article looks on how to reduce stop-loss hunt in trade and compare with the classical stop-loss usage to determine its profitability.