English Русский 中文 Español Deutsch 日本語
Gráficos na biblioteca DoEasy (Parte 88): coleção de objetos gráficos, matriz dinâmica bidimensional para armazenar propriedades de objetos que mudam dinamicamente

Gráficos na biblioteca DoEasy (Parte 88): coleção de objetos gráficos, matriz dinâmica bidimensional para armazenar propriedades de objetos que mudam dinamicamente

MetaTrader 5Exemplos | 22 dezembro 2021, 12:03
1 110 0
Artyom Trishkin
Artyom Trishkin

Sumário


Ideia

Em todos os artigos até agora, usamos matrizes simples para armazenar dados sobre as propriedades do objeto. Temos três matrizes - uma matriz unidimensional de propriedades inteiras, reais e de string. Em cada célula dessa matriz, armazenamos o valor de uma propriedade do objeto correspondente à enumeração de propriedades do objeto escrita no arquivo Defines.mqh. O esquema funciona, e até agora tem funcionado. Mas agora chegamos à conclusão de que, para objetos gráficos (e alguns outros), uma propriedade pode retornar valores para unidades individuais dessa propriedade.

Deixe-me explicar. Por exemplo, a propriedade de tempo do objeto gráfico. Um objeto gráfico posicionado nas coordenadas de preço/tempo do gráfico possui vários pontos de pivô. O objeto, por exemplo, "Seta" tem apenas um ponto de pivô, e podemos obtê-lo usando a função ObjectGetInteger(), especificando OBJPROP_TIME como o identificador de propriedade. Este tipo de construção nos retornará o tempo no gráfico, que corresponde ao único ponto pivô do objeto:

ObjectGetInteger(0, Name, OBJPROP_TIME);

Mas e se um objeto tiver dois pontos de pivô, como um objeto TrendLine? Como obtemos o tempo de ambos os pontos? Isso é feito usando o parâmetro formal prop_modifier da função ObjectGetInteger(). Por padrão, ele já está definido como 0, que corresponde ao primeiro ponto de pivô. Para obter os dados do segundo ponto, precisamos especificar 1. Para obter os dados do terceiro ponto, especifique 2 e assim por diante:

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

É tudo simples e fácil de entender. Mas escrevemos todos os dados recebidos desde o objeto em matrizes: dados inteiros em matrizes long, dados reais em matrizes double e strings em matrizes string. Isso significa que para salvar os dados que podem ser obtidos com a indicação - em prop_modifier - do ponto de pivô desejado, podemos simplesmente usar uma matriz bidimensional. E tudo parece lógico, uma vez que armazenamos o ponto 0 na dimensão zero, o ponto 1 na primeira, 2 na segunda e assim por diante:

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

... mas há um "mas" aqui. Em primeiro lugar, para cada uma das muitas propriedades do objeto, a quantidade de dados recebidos pode ser diferente. Por exemplo, para o objeto "Linhas de Fibonacci", temos dois pontos de pivô sobre os quais o objeto está localizado no gráfico, mas o número de níveis do objeto é completamente diferente - há nove por padrão e, além disso, sua quantidade pode ser alterada pelo usuário a qualquer momento. Em segundo lugar, algumas "multipropriedades" de uma propriedade do objeto podem mudar dinamicamente.

Acontece que não podemos saber com antecedência qual deve ser o tamanho da segunda dimensão para a matriz na qual queremos armazenar os pontos de pivô do objeto, e essas propriedades também podem mudar dinamicamente. Com base nisso, não podemos usar matrizes bidimensionais para armazenar propriedades de objetos diferentes porque:

  • Todos os objetos são herdeiros de seus objetos base, nos quais são definidas matrizes de armazenamento de propriedades, e cada propriedade deve ter um tamanho predefinido da segunda dimensão da matriz, que pode ser diferente para cada objeto e cada uma de suas propriedades. E em MQL, ao criar uma matriz bidimensional, devemos especificar o valor da segunda dimensão, que simplesmente não conhecemos na classe abstrata para cada propriedade de cada objeto;
  • Cada uma dessas propriedades "multidimensionais" pode ser alterada dinamicamente pelo usuário e programaticamente. Mas em MQL não podemos alterar dinamicamente uma dimensão diferente de zero de uma matriz multidimensional.
Mas existe uma saída. Criaremos nossa própria classe de matriz dinâmica multidimensional que pode mudar dinamicamente em qualquer uma de suas dimensões.
Para criar essa matriz, vamos usar uma classe de matriz dinâmica de ponteiros para instâncias da classe CObject e seus herdeiros - CArrayObj.

Com base na classe criada, geraremos uma classe-objeto para armazenar propriedades de objeto em vez de matrizes comuns. Assim, podemos alterar a qualquer momento a quantidade de dados armazenados na segunda dimensão da matriz, a fim de alterar as propriedades da classe-objeto a tempo ao alterar as propriedades do objeto gráfico correspondente.

Ou seja, hoje nossa tarefa é: criamos uma classe de uma matriz dinâmica multidimensional, com ela geramos um objeto de matriz dinâmica bidimensional para armazenar as propriedades do objeto e substituí-los por três matrizes nas quais armazenamos as propriedades do objeto. Colocamos a classe do objeto gráfico abstrato em "novos trilhos", testamos as possibilidades recém-descobertas para armazenar as propriedades do objeto numa matriz dinâmica e rastreamos as alterações nessas propriedades.

Classe de matriz multidimensional dinâmica

A classe CArrayObj é essencialmente uma matriz que contém ponteiros para instâncias de objetos herdados da classe base CObject. Consequentemente, em essa matriz podemos colocar qualquer objeto que seja descendente do objeto CObject, o que significa que as células da nossa matriz podem conter dados do tipo long, double ou string, e a outra matriz CArrayObj também pode conter dados ou outras matrizes. Se tudo estiver claro com as matrizes CArrayObj, então com os dados não tanto - eles não são descendentes da classe CObject, portanto, precisamos criar classes para armazená-los. Além disso, para cada um dos objetos (e para o próprio objeto-matriz) na matriz, precisamos indicar o tipo. Isso é necessário para uma compreensão clara do que exatamente está armazenado na célula em questão da matriz - um objeto simples com número inteiro, dados reais ou de string, ou outro objeto, por sua vez contendo objetos com dados ou outra matriz com objetos. Isso é necessário para criar métodos que possibilitem a cópida de um objeto de classe para outro - a fim de saber exatamente se copiar dados numa célula (se houver dados na célula correspondente da matriz copiada) ou criar uma nova matriz com dados na matriz de origem (se a célula correspondente da matriz copiada contiver matriz) e copiar os dados dela. Definamos os tipos de objeto necessários, no arquivo \MQL5\Include\DoEasy\Defines.mqh escrevemos na lista de tipos de objetos de biblioteca as constantes com os tipos necessários (apresento toda a enumeração para uma melhor compreensão):

//+------------------------------------------------------------------+
//| 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                                          |
//+------------------------------------------------------------------+

No arquivo \MQL5\Include\DoEasy\Data.mqh escrevemos os índices da novas mensagens:

   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:
   
  };
//+------------------------------------------------------------------+

e os textos que correspondem aos índices recém-adicionados:

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


Na pasta de funções de serviço \MQL5\Include\DoEasy\Services\ criamos um novo arquivo XDimArray.mqh da classe CXDimArray.

Como queremos criar uma substituição para matrizes comuns para armazenar propriedades inteiras, reais e de string, e, ao mesmo tempo, queremos tornar essas matrizes multidimensionais e dinâmicas em qualquer dimensão, criaremos três classes idênticas de uma matriz dinâmica multidimensional - cada uma para armazenar seu próprio tipo (long para dados inteiros, double para dados reais e string para dados de texto).

A hierarquia de classes ficará assim:

  • CObject --> classe de tipo de dados abstratos --> classe de tipo de dados,
  • Classe CArrayObj --> classe de uma dimensão que armazena uma lisa de classes de tipos de dados
  • Classe CArrayObj --> classe de matriz multidimensional que armazena uma lista de objetos de uma classe de uma dimensão

A classe para armazenar dados inteiros, reais e de string para cada tipo de dados terá os seus próprios, mas herdados da classe de tipo de dados base, na qual escreveremos o tipo de dados armazenado na classe herdada.

A classe de uma dimensão da matriz será herdada da classe CArrayObj e, na verdade, será uma lista que armazenará ponteiros para objetos de dados ou outras listas.

A classe de uma matriz multidimensional será uma lista CArrayObj que armazena ponteiros para instâncias de objetos de classes de uma dimensão da matriz. Ou seja, a própria lista será a primeira dimensão da matriz e as classes de uma dimensão serão objetos dinamicamente expansíveis da primeira dimensão. Se ela contiver apenas um objeto de dimensão de tamanho 1, a chamada para ela corresponderá ao registro registro array[0][0], se ela contiver dois objetos de mesma dimensão de tamanho 1, a chamada para o primeiro registro array[0][0] e a chamada para o segundo o registro array[0][1].
Naturalmente, se os objetos de uma dimensão têm dimensões maiores que 1, então o acesso a eles corresponderá aos registros

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

Mas a chamada, é claro, usará os métodos apropriados e poderemos alterar dinamicamente a primeira dimensão da matriz e a segunda.

Não consideraremos adicionar a terceira e outras dimensões aqui, uma vez que precisamos apenas de uma matriz dinâmica bidimensional. Mas, com base nessas classes, será possível fazer matrizes dinâmicas com qualquer número de dimensões, e em qualquer célula de uma matriz de qualquer dimensão será possível alterar a dimensão (diminuir/aumentar), ou adicionar uma nova.

Como faremos três classes idênticas, cada uma destinada a armazenar seu próprio tipo de dados, veremos em detalhes um tipo de classe.

No arquivo XDimArray.mqh já criado da classe CXDimArray incluímos os arquivos da classe CArrayObj e da classe de mensagens da biblioteca e escrevemos uma classe de unidade de dados abstratos, herdada do objeto base CObject:

//+------------------------------------------------------------------+
//|                                                    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; }
  };
//+------------------------------------------------------------------+

Aqui temos uma variável privada para armazenar o tipo de dados armazenados em objetos-herdeiros, um construtor paramétrico protegido, para o qual é passado o tipo de dados armazenados do objeto-herdeiro e um método virtual público que retorna o tipo de dados armazenados escrito numa variável m_type.
Dentro do construtor padrão, escrevemos o tipo de dados OBJECT_DE_TYPE_OBJECT na variável m_type.

Em seguida, sob esta classe na listagem, escrevemos a classe da unidade de dados inteiros:

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

A classe tem uma variável-campo pública para armazenar um valor inteiro e um construtor de classe, em cuja lista de inicialização passamos o tipo OBJECT_DE_TYPE_LONG para o construtor da classe pai que indica que este objeto armazena tipos de dados inteiros.

Agora, abaixo da listagem de código, vamos criar a classe de um objeto para uma matriz long de uma dimensão:

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

  }

Vamos escrever todos os métodos no corpo da classe, sendo que cada método será comentado em detalhes na listagem.

Na seção privada da classe, adicionamos um método que recebe um objeto de dados long desde a matriz:

//+------------------------------------------------------------------+
//| 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;
                       }

Neste método, fizemos um controle de saída para fora da matriz. Ou seja, se for passado um índice que aponta para uma célula inexistente na matriz, uma mensagem sobre isso será exibida no log. Além disso, se para o método foi passado um índice menor que zero (o que não deveria acontecer), ajustamos o valor para zero. Se acessarmos pelo índice válido da matriz sem conseguirmos obter o objeto, imprimimos no log uma mensagem de erro sobre a obtenção do objeto. Como resultado, retornamos um ponteiro para o objeto ou NULL no caso de erro. O registro no log desses erros ajuda a especificar corretamente os índices dos dados necessários ao usar esta classe. Chamaremos este método desde outros métodos da classe para obter dados a partir da matriz, e este método nos informará no log sobre erros ao acessar objetos na matriz.

A seguir, escreveremos um método que adiciona o número especificado de células com objetos ao final da matriz:

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

Ao método é transferido o número de objetos, que é necessário adicionar ao final da matriz, e o ponteiro para o objeto, cujas instâncias devem ser adicionadas. Em seguida, num loop através do número especificado adicionamos os ponteiros para o objeto à lista e retornamos o resultado da adição de objetos à lista.

No método existe um método lógico, que não permite trabalhar com mais de objetos adicionados à lista. Trataremos desse erro no seguinte artigo para exemplificar a falta de atenção da parte do programador. Se você não teve dificuldade em encontrá-lo, parabéns, significa que não perco tempo a escrever estes artigos.

Na seção pública da classe adicionamos o método para inicialização da matriz:

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

Limpamos a matriz por meio do método Clear() da classe pai e aumentamos seu tamanho com ajuda do método Increase():

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

O método adiciona o número de dados long especificado à matriz e retorna o número de dados adicionados.

Escrevemos o método que reduz o número de células com dados para o número desejado:

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

Como na matriz sempre deve existir pelo menos um elemento, primeiro verificamos que a remoção do número desejado deixa um ou mais elementos na matriz. Em seguida, removemos os elementos do final da lista por meio do método DeleteRange() da classe pai. Como resultado, retornamos o número de elementos removidos.

Vamos escrever um método que defina o novo tamanho da matriz:

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

O método usa os métodos acima para remover ou adicionar elementos à matriz para obter um tamanho igual ao especificado.

Vamos adicionar um método que define o valor para a célula especificada da matriz:

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

Aqui: obtemos o objeto de dados com base no índice especificado e definimos o valor passado para o método. Após o recebimento bem-sucedido do objeto, devolvemos true, em caso de falha (o método privado GetData() imprime isso no log) retornamos false.

Método que retorna a quantidade de dados na matriz:

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

O método simplesmente retorna o valor do número de elementos na lista por meio do métodoTotal() da classe CArray, que é o pai da classe CArrayObj.

Vamos escrever um método que retorna o valor com base no índice especificado:

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

Obtemos um ponteiro para um objeto de dados da lista por índice e retornamos o valor escrito no objeto resultante. Retornamos zero em caso de erro.

Um método Get() sobrecarregado que retorna um valor booleano quando os dados são recebidos na variável passada ao método por referência:

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


No construtor padrão apenas inicializamos a matriz com o método Initialize() com o tamanho de matriz igual 1 e o valor padrão sendo 0.
No construtor paramétrico
especificamos o quão grande a matriz deve ser e qual deve ser o padrão.
No destruidor da classe removemos os elementos da matrizlimpamos a matriz liberando toda a memória da matriz.

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


Além disso, no mesmo arquivo, vamos criar uma classe de matriz multidimensional dinâmica long:

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

Esta classe é uma lista que contém objetos da classe acima, que por sua vez são listas que armazenam objetos com dados. Assim, esta classe é a primeira dimensão, enquanto as listas que estão nela são listas de dados para esta dimensão.

Para entendimento:

O índice 0 da lista da classe CXDimArrayLong aponta para o objeto da classe CDimLong, que é o primeiro da lista; o índice 0 da classe CDimLong aponta para o objeto da classe CDataUnitLong, que é o primeiro na lista da classe CDimLong.

Isto equivale a escrever array[0][0];

O índice 1 da lista da classe CXDimArrayLong aponta para o objeto da classe CDimLong, que é o segundo da lista; o índice 0 da classe CDimLong aponta para o objeto da classe CDataUnitLong, que é o primeiro da lista da classe CDimLong.

Isto equivale a escrever array[1][0];

------

O índice 0 da lista da classe CXDimArrayLong aponta para o objeto da classe CDimLong, que é o primeiro da lista; o índice 1 da classe CDimLong aponta para o objeto da classe CDataUnitLong, que é o primeiro da lista da classe CDimLong.

Isto equivale a escrever array[0][1];

O índice 1 da lista da classe CXDimArrayLong aponta para o objeto da classe CDimLong, que é o segundo nesta lista; o índice 1 da classe CDimLong aponta para o objeto da classe CDataUnitLong, que é o segundo na lista da classe CDimLong.

Isto equivale a escrever array[1][1];

etc.


Na seção privada da classe, adicionamos um método que retorna uma matriz de dados desde a primeira dimensão:

//+------------------------------------------------------------------+
//| 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;
                       }

Este método obtém um ponteiro para um objeto da classe CDimLong pelo índice especificado. Se o índice for menor que zero, usamos o índice igual a zero, e se o índice aponta para fora da matriz, imprimimos no log sobre a solicitação fora da matriz, ou, se o objeto não pôde ser recebido, imprimimos no log o erro sobre recepção do objeto. Como resultado, retornamos um ponteiro para o objeto, ou NULL em caso de erro.

Na seção privada da classe, escreveremos um método que adiciona uma nova dimensão à primeira dimensão:

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

O método cria um novo objeto da classe CDimLong e o adiciona ao final da lista, aumentando assim o tamanho da primeira dimensão.

Na seção pública da classe, escreveremos métodos para trabalhar com matrizes. Todos os métodos são descritos em detalhes nos comentários ao código:

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

Como se pode ver, trabalhamos principalmente com os métodos do objeto resultante da classe CDimLong, que já consideramos acima ao escrever a classe CDimLong. De qualquer forma, na discussão do artigo podem ser feitas todas as perguntas que surgirem.

Agora precisamos escrever exatamente as mesmas classes para dados do tipo double e string. As classes são completamente idênticas às discutidas acima:

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


Agora estamos prontos para colocar a classe do objeto gráfico abstrato "em novos trilhos" - para substituir nele o trabalho com matrizes simples que armazenam propriedades inteiras, reais e de string pelo o trabalho com matrizes dinâmicas fornecidas por meio das classes discutidas acima.

Mas, antes de começarmos a mudar a classe do objeto gráfico, precisamos tornar as novas classes criadas visíveis na biblioteca no nível do resto de suas classes. Para faze isso, incorporamos um arquivo com estas classes ao arquivo de funções de serviço da biblioteca \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                                                |
//+------------------------------------------------------------------+

Agora, essas classes estarão visíveis para todas as classes da biblioteca.

Matriz dinâmica bidimensional para armazenar propriedades de objetos

Quase todos os objetos de biblioteca têm matrizes nas quais armazenamos propriedades inteiras, reais e de string dos objetos. As matrizes são unidimensionais e têm um tamanho embutido em código correspondente ao devido número de propriedades. Era conveniente até que nos deparamos com a necessidade de controlar o número de alterações dinâmicas das propriedades do objeto. Começaremos fazendo ajustes nos objetos da biblioteca com a classe abstrata de objetos gráficos padrão. Todos os objetos posteriores serão criados utilizando a nova funcionalidade baseada na classe da matriz multidimensional dinâmica. E, provavelmente mais tarde, também converteremos objetos criados previamente para trabalhar com matrizes dinâmicas.

No arquivo do objeto gráfico padrão abstrato \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh, na seção privada estão as matrizes para armazenamento de propriedades (atuais e prévias) e os métodos para obter um índice real da propriedade especificada na matriz:

//+------------------------------------------------------------------+
//|                                                 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:

Agora precisamos transferir todas essas matrizes e métodos para uma nova classe, que será uma matriz bidimensional dinâmica para armazenar propriedades de objetos, cuja segunda dimensão deve mudar dinamicamente para rastrear a mudança na mesma propriedade múltipla do objeto, como o preço, o tempo do ponto de pivô do objeto, a cor, o estilo, a espessura ou a descrição dos níveis do objeto, cujo número pode ser alterado dinamicamente.

Por enquanto, a classe não será totalmente depurada, iremos colocá-la diretamente na seção privada da classe do objeto gráfico abstrato padrão. Depois que toda a funcionalidade estiver pronta e depurada, iremos transferir a classe da matriz bidimensional dinâmica de propriedades do objetos para um arquivo separado, para que possamos acessá-la em outros objetos, e, assim, evitar escrevê-la novamente dentro do objeto.

Removeremos da seção privada todas as matrizes e métodos acima e definiremos em seu lugar uma nova classe de propriedades do objeto:

//+------------------------------------------------------------------+
//|                                                 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
     {
      
     }

Na seção privada da nova classe, declaramos uma lista para armazenar objetos de propriedades, que serão representados pela classe da matriz multidimensional dinâmica CXDimArrayLong, as variáveis para armazenar o número de propriedades inteiras, reais e de string do objeto e dois métodos que retornam o índice da matriz em que estão realmente localizadas as propriedades double e string do objeto:

//+------------------------------------------------------------------+
//| 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;    }

Na seção pública da classe, declaramos um método que retorna uma lista de objetos de propriedade, os ponteiros para objetos de propriedades inteiras, reais e de string e os métodos para definir e obter as propriedades do objeto especificadas:

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

A lista CArrayObj armazenará três ponteiros para instâncias dos objetos das classes da matriz dinâmica que criamos acima - inteiros, reais e string, que criaremos no construtor de classe e colocaremos na lista. Os métodos Set e Get são semelhantes aos métodos Set e Get que usamos anteriormente, exceto que agora eles têm mais um parâmetro - além do índice da propriedade do objeto, também especificamos o índice dessa propriedade na segunda dimensão da matriz. Para a maioria das propriedades, este índice será sempre zero, mas para aquelas propriedades de objeto que são várias do mesmo tipo, indicaremos o índice do modificador de propriedade para obter a primeira, segunda, terceira, etc. propriedade de preço, tempo, etc.

No mesmo lugar, na seção pública da classe, escreveremos um método que retorna o tamanho da matriz de dados especificada da primeira dimensão:

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

Ao método é transferido o índice da propriedade desejada da primeira dimensão da matriz de propriedades (range) - verificamos:
se o valor da propriedade for menor que o número total de propriedades inteiras, então trata-se de uma solicitação de propriedade inteira, portanto retornamos o tamanho de matriz de propriedades inteiras usando o método Size() de objeto Long;
se o valor da propriedade for menor que o número total de propriedades inteiras + o número de propriedades reais, então trata-se de uma solicitação de propriedade real, portanto retornamos o tamanho da matriz de propriedades reais usando o método Size() de objeto Double;
se o valor da propriedade for menor que o número total de propriedades inteiras + o número de propriedades reais + o número de propriedades de string, então trata-se de uma solicitação de propriedade de string, portanto retornamos o tamanho da matriz de propriedades de string usando o método Size() de objeto String.
Vimos o método Size() acima ao criar classes de objetos para matrizes dinâmicas.

Na seção pública da classe, escreveremos um método que define o tamanho da matriz na dimensão especificada:

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

A lógica do método é idêntica ao método acima para obter o tamanho.

Ao construtor de classe é passado o número de propriedades inteiras, reais e de string do objeto e esses valores são atribuídos às variáveis de classe correspondentes. Em seguida, à lista são adicionados novos objetos de matriz multidimensional dinâmica inteira, real e de string com a primeira dimensão igual ao número de propriedades correspondentes passadas para parâmetros, enquanto o tamanho da segunda dimensão é definido como 1 para cada matriz de propriedades.

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

No destruidor da classe, removemos os elementos da lista e liberamos a memória da matriz:

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

Como resultado, a classe de propriedades do objeto acabou ficando assim:

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

Mas, para rastrear mudanças nas propriedades de um objeto, precisamos lembrar o estado anterior de todas as suas propriedades e comparar com o atual. Para isso, mantínhamos anteriormente dois conjuntos de propriedades do objeto. Agora vamos criar outra classe que conterá dois objetos da classe que acabamos de escrever. Um objeto é para armazenar propriedades atuais, o segundo é para armazenar propriedades prévias.

Além disso, na seção privada da classe, declararemos uma nova classe com todos os dados das propriedades atuais e prévias:

   class CProperty
     {
      
     }

Todos os campos e métodos da classe serão públicos. Como a classe é pequena e armazena objetos das classes que consideramos anteriormente, sua lista pode ser vista de maneira independente:

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

Além de trabalhar com métodos de classes criadas previamente, adicionamos um método para copiar as propriedades atuais para as prévias. A cópia é realizada elemento por elemento, uma vez que precisamos ter duas cópias independentes do mesmo objeto. Se tivéssemos recorrido ao método AssignArray da classe CArrayObj, copiaríamos os ponteiros, não valores de propriedade, como aqui, o que levaria a uma cópia exata de objetos, enquanto a mudança de uma propriedade num objeto levaria a uma mudança nas propriedades em outro, e não precisamos disso.

Assim, a substituição de matrizes simples por objetos de classe de matriz dinâmica, que não acabamos de fazer completamente, implica no uso de um objeto da classe CProperty como um ponteiro para as propriedades de objeto gráfico e envolve fazer muitas alterações nas classes da biblioteca, já que agora precisamos especificar, além do índice de propriedade, o índice do ponto de pivô de preço e tempo ou o número do nível cuja propriedade queremos obter ou definir.


Aprimorando as classes da biblioteca

Na seção privada da classe do objeto gráfico abstrato (na qual estamos trabalhando agora), imediatamente após a classe de propriedades recém-escrita declaramos um ponteiro para o objeto das propriedades do objeto gráfico, uma variável para armazenar o número de pontos de pivô do objeto e os métodos para definir vários valores para a propriedade de um objeto:

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

Na seção pública da classe, iremos complementar todos os métodos Get e Set com mais uma variável, para a qual iremos indicar o índice da segunda dimensão da matriz de propriedades, e agora iremos retornar e receber propriedades usando os métodos Get e Set desde os campos Curr e Prev da classe CProperty que armazenam matrizes de propriedades atuais e prévias:

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

Ao longo do código da classe, onde são utilizados os métodos GetProperty(), SetProperty(), GetPropertyPrev() e SetPropertyPrev(), é necessário fazer mudanças, já que agora incluem um índice da segunda dimensão. Não descreveremos todas essas mudanças múltiplas, pois isso vai ocupar muito espaço no artigo, e todas elas já foram incluídas no código da classe, que pode ser encontrado no final do artigo. Vamos considerar apenas métodos novos e alguns antigos.

Na seção pública da classe declaramos métodos que retornam descrições de pontos de pivô e níveis do objeto:

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

Vamos passar mais um parâmetro para o construtor da classe paramétrica protegida, em particular o número de pontos de pivô do objeto necessários para sua construção (o que significa que também teremos que modificar as classes de objetos-herdeiros para que eles passem o número de pontos de controle para este construtor):

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

Vejamos a implementação do construtor de classe:

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

Aqui, primeiro criamos um objeto da classe de propriedades do objeto gráfico, especificando o número de propriedades inteiras, reais e de string. Em seguida, para propriedades que possuem vários parâmetros (preço, tempo do pontos de pivô, número de níveis e propriedades de cada um dos níveis), definimos os tamanhos das matrizes na segunda dimensão. Ao definir e obter propriedades que têm apenas um parâmetro, especificamos o índice do parâmetro na segunda dimensão para ser igual a zero.

No método que compara todas as propriedades de objetos CGStdGraphObj, agora comparamos propriedades num loop através do tamanho da segunda dimensão:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Nos métodos que recebem e salvam as propriedades do objeto gráfico, agora todas as propriedades que possuem vários valores são preenchidas num loop percorrendo o número de propriedades:

//+------------------------------------------------------------------+
//| 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 
  }
//+------------------------------------------------------------------+

No método que copia os dados atuais para os anteriores, chamamos o método do objeto das propriedades atuais, método esse especialmente criado para copiar as propriedades atuais para as prévias:

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

No método que verifica as alterações nas propriedades do objeto, primeiro verificamos se a propriedade é múltipla e, caso seja assim, verificamos sua mudança por meio de um loop através do número de instâncias de propriedade, caso contrário, simplesmente comparamos as propriedades de dois objetos:

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


Métodos que leem e definem as propriedades do objeto e retornam as descrições das propriedades:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Estes métodos são simples e fáceis de entender e quase não precisam de comentários. Além de todos os métodos e alterações considerados, na classe foram feitas inúmeras alterações para indicar o índice da segunda dimensão, e não consideramos essas modificações aqui, pois podem ser encontradas nos arquivos anexados ao artigo.

Como ao construtor da classe foi adicionada uma nova variável, à qual devemos passar o número de pontos de pivô para a classe ao criar uma classe filha, precisamos adicionar adições aos seus construtores em cada um desses objetos. Em vez de examinarmos cada arquivo das classes de objetos gráficos, veremos apenas dois.

Abrimos o arquivo da classe do objeto gráfico sinal "Buy" desde \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdArrowBuyObj.mqh e da mesma maneira incorporamos a transferência de pontos de pivô para o construtor da classe pai:

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

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

Este objeto possui apenas um ponto de pivô. Agora vamos olhar para um objeto com vários pontos de pivô.

Abrimos o arquivo de classe de objeto "Linha de tendência" desde \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdTrendObj.mqh e da mesma maneira incorporamos a transferência de pontos de pivô (igual a dois) para o construtor da classe pai:

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

Esse tipo de alterações foram realizadas para cada classe na pasta \MQL5\Include\DoEasy\Objects\Graph\Standard\. Para cada objeto, é incorporado o número de pontos de pivô que é usado para sua construção. Todas as alterações podem ser encontradas nos arquivos anexados ao artigo.

Agora modificamos a classe CSelect no caminho \MQL5\Include\DoEasy\Services\Select.mqh, aqui simplesmente precisamos adicionar os índices da segunda dimensão a cada método em que existe ou é suposto acessar os método GetProperty():

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

Vejamos a implementação seletiva de alguns métodos:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Todas as mudanças são marcadas em cor. Os demais métodos são modificados de forma idêntica aos apresentados acima, e podem ser encontrados nos arquivos anexados ao artigo.

Agora vamos modificar a classe da coleção de objetos gráficos no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

Nos métodos públicos GetList() escrevemos a indicação do índice da segunda dimensão da matriz:

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

Em todos os métodos onde há uma chamada para os métodos modificados da classe CSelect, adicionamos a indicação do índice da segunda dimensão da matriz:

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

No método que adiciona o objeto gráfico à coleção, em vez de uma breve descrição do objeto, exibiremos uma completa, para que possamos ver toda a lista de propriedades do objeto durante o teste:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Essas são todas as modificações planejadas para hoje. Pequenas, mas numerosas mudanças que não consideramos no artigo podem ser encontradas nos arquivos anexados ao artigo.


Tese de eventos de objetos com dois pontos de pivô

Para o teste, usaremos o Expert Advisor do último artigo e o salvaremos na nova pasta \MQL5\Experts\TestDoEasy\Part88\ com o novo nome TestDoEasyPart88.mq5.

Não faremos nenhuma alteração no EA - todas foram feitas nos arquivos de biblioteca.

Vamos compilar o Expert Advisor e executá-lo no gráfico:


Ao adicionar objetos que possuem dois pontos de pivô e ao modificar qualquer um deles, o Expert Advisor exibe os registros respectivos no log. Se ao gráfico adicionarmos um objeto que é construído usando mais de dois pontos de pivô, não veremos registros sobre a modificação de um dos pontos, ou quando um muda, os dados do segundo vão mudar. Isso ocorre porque temos um erro lógico que deixei para correção no próximo artigo e sobre o qual falei acima.


O que vem agora?

No próximo artigo, continuaremos trabalhando com eventos de objetos gráficos padrão e classes de matrizes dinâmicas.

Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo por conta própria. Deve-se observar que para o EA funcionar corretamente, precisamos do arquivo de indicador para a biblioteca que vimos no artigo anterior.

Se você tiver dúvidas, comentários e sugestões, pode expressá-los nos comentários ao artigo.

Complementos

*Artigos desta série:

Gráficos na biblioteca DoEasy (Parte 82): refatoração dos objetos da biblioteca e da coleção de objetos gráficos
Gráficos na biblioteca DoEasy (Parte 83): classe abstrata de objetos gráficos padrão
Gráficos na biblioteca DoEasy (Parte 84): classes herdeiras do objeto gráfico abstrato padrão
Gráficos na biblioteca DoEasy (Parte 85): coleção de objetos gráficos, adicionamos recém-criados
Gráficos na biblioteca DoEasy (Parte 86): coleção de objetos gráficos, controlamos a modificação de propriedades
Gráficos na biblioteca DoEasy (Parte 87): coleção de objetos gráficos, controlamos a modificação de propriedades de objetos em todos os gráficos abertos

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10091

Arquivos anexados |
MQL5.zip (4164.99 KB)
Trabalhando com o tempo (Parte 1): princípios básicos Trabalhando com o tempo (Parte 1): princípios básicos
As funções e o código discutidos no artigo o ajudarão a entender melhor os princípios de processamento de tempo, de mudança de horário da corretora e de horário de verão ou de inverno. O uso adequado do tempo é um aspecto muito importante do trading. Este nos permite saber, por exemplo, se a Bolsa de Londres ou Nova Iorque já abriu ou ainda não ou a que horas começa/termina o pregão no mercado de moedas.
Gráficos na biblioteca DoEasy (Parte 87): coleção de objetos gráficos, controlamos a modificação de propriedades de objetos em todos os gráficos abertos Gráficos na biblioteca DoEasy (Parte 87): coleção de objetos gráficos, controlamos a modificação de propriedades de objetos em todos os gráficos abertos
No artigo, continuaremos a trabalhar no rastreamento dos eventos de objetos gráficos padrão e na criação de funcionalidades que permitem controlar as alterações nas propriedades dos objetos gráficos localizados em qualquer gráfico aberto no terminal.
Quase-construtor para criar um Expert Advisor Quase-construtor para criar um Expert Advisor
Disponibilizo meu próprio conjunto de funções de negociação na forma de um Expert Advisor pronto para uso. O método agora proposto permite gerar diversas estratégias de negociação simplesmente adicionando indicadores e mudando os parâmetros de entrada.
Desenvolvendo um EA de negociação do zero Desenvolvendo um EA de negociação do zero
Entenda como se dá o desenvolvimento de um EA para negociação programando o mínimo possível.