English Русский 中文 Deutsch 日本語 Português
Otras clases en la biblioteca DoEasy (Parte 71): Eventos de la colección de objetos de gráfico

Otras clases en la biblioteca DoEasy (Parte 71): Eventos de la colección de objetos de gráfico

MetaTrader 5Ejemplos | 1 julio 2021, 16:30
490 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

En el último artículo implementamos la actualización automática de algunas propiedades de los objetos del gráfico y sus objetos relacionados: abrir un gráfico nuevo/cerrar un gráfico existente de un símbolo (objeto del gráfico), añadir una ventana de indicador nueva o eliminar una existente de un objeto de gráfico y añadir/eliminar o cambiar un indicador existente en la ventana del gráfico.

Hoy, vamos a crear en la biblioteca la funcionalidad de eventos necesaria para los objetos de gráfico, los objetos de ventana de gráfico y los objetos de indicador en la ventana del gráfico, encargados de enviar los eventos personalizados al gráfico del programa de control al registrar los eventos anteriores con objetos.

Debemos señalar de inmediato que hemos implementado el trabajo con los gráficos para el control manual de los mismos, es decir, cuando el propio usuario, manualmente o usando los controles de su programa, realiza los cambios en los gráficos de su terminal paso a paso, efectuando operaciones con un objeto a la vez. El cambio programático de varios objetos a la vez en un tick del temporizador puede provocar una definición incorrecta de los eventos ocurridos; lo más probable es que, en el mejor de los casos, solo se registre el último evento, y que, en el peor de los casos, se dé una definición incorrecta de los objetos sobre los que se han realizado las operaciones de cambio. Obviamente, nosotros escribiremos el código considerando esta probabilidad, y donde sea posible implementar fácilmente un código que tenga en cuenta la posibilidad de cambiar por lotes los parámetros de los objetos, lo haremos así. No obstante, todavía no vamos a trabajar y a poner a prueba el funcionamiento simultáneo del software con múltiples elementos gráficos; si los usuarios de la biblioteca se pronuncian a favor en este punto, mejoraremos de nuevo la funcionalidad que creemos hoy.


Mejorando las clases de la biblioteca

Vamos a añadir los nuevos mensajes a la biblioteca. En el archivo \MQL5\Include\DoEasy\Data.mqh, añadimos los índices de los nuevos mensajes:

   MSG_CHART_OBJ_TEMPLATE_SAVED,                      // Chart template saved
   MSG_CHART_OBJ_TEMPLATE_APPLIED,                    // Template applied to chart
   
   MSG_CHART_OBJ_INDICATOR_ADDED,                     // Indicator added
   MSG_CHART_OBJ_INDICATOR_REMOVED,                   // Indicator removed
   MSG_CHART_OBJ_INDICATOR_CHANGED,                   // Indicator changed
   MSG_CHART_OBJ_WINDOW_ADDED,                        // Subwindow added
   MSG_CHART_OBJ_WINDOW_REMOVED,                      // Subwindow removed

//--- CChartObjCollection
   MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION,        // Chart collection
   MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,  // Failed to create a new chart object
   MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,         // Failed to add a chart object to the collection
   MSG_CHART_COLLECTION_ERR_CHARTS_MAX,               // Cannot open new chart. Number of open charts at maximum
   
   MSG_CHART_COLLECTION_CHART_OPENED,                 // Chart opened
   MSG_CHART_COLLECTION_CHART_CLOSED,                 // Chart closed
  
  };
//+------------------------------------------------------------------+

y los textos de los mensajes que se corresponden con los índices nuevamente añadidos:

   {"Шаблон графика сохранён","Chart template saved"},
   {"Шаблон применён к графику","Template applied to the chart"},
   
   {"Добавлен индикатор","Added indicator"},
   {"Удалён индикатор","Removed indicator"},
   {"Изменён индикатор","Changed indicator"},
   
   {"Добавлено подокно","Added subwindow"},
   {"Удалено подокно","Removed subwindow"},
   
//--- CChartObjCollection
   {"Коллекция чартов","Chart collection"},
   {"Не удалось создать новый объект-чарт","Failed to create new chart object"},
   {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"},
   {"Нельзя открыть новый график, так как количество открытых графиков уже максимальное","You cannot open a new chart, since the number of open charts is already maximum"},
   
   {"Открыт график","Open chart"},
   {"Закрыт график","Closed chart"},
   
  };
//+---------------------------------------------------------------------+


Hoy vamos a procesar algunos eventos de los gráficos. Para monitorearlos e indicar qué evento ha sucedido,
en el archivo \MQL5\Include\DoEasy\Defines.mqh, creamos una nueva enumeración de los posibles eventos de los gráficos:

//+------------------------------------------------------------------+
//| Data for working with charts                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of possible chart events                                    |
//+------------------------------------------------------------------+
enum ENUM_CHART_OBJ_EVENT
  {
   CHART_OBJ_EVENT_NO_EVENT = SIGNAL_MQL5_EVENTS_NEXT_CODE, // No event
   CHART_OBJ_EVENT_CHART_OPEN,                        // "New chart opening" event
   CHART_OBJ_EVENT_CHART_CLOSE,                       // "Chart closure" event
   CHART_OBJ_EVENT_CHART_WND_ADD,                     // "Adding a new window on the chart" event
   CHART_OBJ_EVENT_CHART_WND_DEL,                     // "Removing a window from the chart" event
   CHART_OBJ_EVENT_CHART_WND_IND_ADD,                 // "Adding a new indicator to the chart window" event
   CHART_OBJ_EVENT_CHART_WND_IND_DEL,                 // "Removing an indicator from the chart window" event
   CHART_OBJ_EVENT_CHART_WND_IND_CHANGE,              // "Changing indicator parameters in the chart window" event
  };
#define CHART_OBJ_EVENTS_NEXT_CODE  (CHART_OBJ_EVENT_CHART_WND_IND_CHANGE+1)  // The code of the next event after the last chart event code
//+------------------------------------------------------------------+

Al registrarse los eventos del gráfico especificados en esta enumeración, enviaremos un evento personalizado al gráfico del programa. Este evento personalizado deberá contener el tipo de evento ocurrido utilizando la constante apropiada de la enumeración. A continuación, el programa analizará el código del evento y lo procesará en consecuencia.

Al implementar el procesamiento de los eventos, hemos encontrado dificultades a la hora de determinar el número de la subventana ya eliminada de un gráfico, así como del indicador que estaba en ella. Para definirlos de forma más adecuada, hemos decidido introducir una nueva propiedad para el objeto de indicador en la ventana del gráfico: el número de la ventana en la que se encuentra.

Todos los gráficos eliminados, las ventanas de los gráficos y los indicadores correspondientes (objetos que los describen) deberán almacenarse en listas especiales que nos permitan obtener un objeto previamente eliminado en cualquier momento. Y ya de este objeto (si se ha eliminado el indicador), podemos obtener el número de la ventana del indicador.

En la enumeración de las propiedades de tipo entero del objeto de gráfico, añadimos una nueva constante de propiedad para el objeto de indicador en la ventana del gráfico:

//+------------------------------------------------------------------+
//| Chart integer property                                           |
//+------------------------------------------------------------------+
enum ENUM_CHART_PROP_INTEGER
  {
   CHART_PROP_ID = 0,                                 // Chart ID
   CHART_PROP_TIMEFRAME,                              // Chart timeframe
   CHART_PROP_SHOW,                                   // Price chart drawing
   CHART_PROP_IS_OBJECT,                              // Chart object (OBJ_CHART) identification attribute
   CHART_PROP_BRING_TO_TOP,                           // Show chart above all others
   CHART_PROP_CONTEXT_MENU,                           // Enable/disable access to the context menu using the right click 
   CHART_PROP_CROSSHAIR_TOOL,                         // Enable/disable access to the Crosshair tool using the middle click
   CHART_PROP_MOUSE_SCROLL,                           // Scroll the chart horizontally using the left mouse button
   CHART_PROP_EVENT_MOUSE_WHEEL,                      // Send messages about mouse wheel events (CHARTEVENT_MOUSE_WHEEL) to all MQL5 programs on a chart
   CHART_PROP_EVENT_MOUSE_MOVE,                       // Send messages about mouse button click and movement events (CHARTEVENT_MOUSE_MOVE) to all MQL5 programs on a chart
   CHART_PROP_EVENT_OBJECT_CREATE,                    // Send messages about the graphical object creation event (CHARTEVENT_OBJECT_CREATE) to all MQL5 programs on a chart
   CHART_PROP_EVENT_OBJECT_DELETE,                    // Send messages about the graphical object destruction event (CHARTEVENT_OBJECT_DELETE) to all MQL5 programs on a chart
   CHART_PROP_MODE,                                   // Type of the chart (candlesticks, bars or line (ENUM_CHART_MODE))
   CHART_PROP_FOREGROUND,                             // Price chart in the foreground
   CHART_PROP_SHIFT,                                  // Mode of shift of the price chart from the right border
   CHART_PROP_AUTOSCROLL,                             // The mode of automatic shift to the right border of the chart
   CHART_PROP_KEYBOARD_CONTROL,                       // Allow managing the chart using a keyboard
   CHART_PROP_QUICK_NAVIGATION,                       // Allow the chart to intercept Space and Enter key strokes to activate the quick navigation bar
   CHART_PROP_SCALE,                                  // Scale
   CHART_PROP_SCALEFIX,                               // Fixed scale mode
   CHART_PROP_SCALEFIX_11,                            // 1:1 scale mode
   CHART_PROP_SCALE_PT_PER_BAR,                       // The mode of specifying the scale in points per bar
   CHART_PROP_SHOW_TICKER,                            // Display a symbol ticker in the upper left corner
   CHART_PROP_SHOW_OHLC,                              // Display OHLC values in the upper left corner
   CHART_PROP_SHOW_BID_LINE,                          // Display Bid value as a horizontal line on the chart
   CHART_PROP_SHOW_ASK_LINE,                          // Display Ask value as a horizontal line on a chart
   CHART_PROP_SHOW_LAST_LINE,                         // Display Last value as a horizontal line on a chart
   CHART_PROP_SHOW_PERIOD_SEP,                        // Display vertical separators between adjacent periods
   CHART_PROP_SHOW_GRID,                              // Display a grid on the chart
   CHART_PROP_SHOW_VOLUMES,                           // Display volumes on a chart
   CHART_PROP_SHOW_OBJECT_DESCR,                      // Display text descriptions of objects
   CHART_PROP_VISIBLE_BARS,                           // Number of bars on a chart that are available for display
   CHART_PROP_WINDOWS_TOTAL,                          // The total number of chart windows including indicator subwindows
   CHART_PROP_WINDOW_HANDLE,                          // Chart window handle
   CHART_PROP_WINDOW_YDISTANCE,                       // Distance in Y axis pixels between the upper frame of the indicator subwindow and the upper frame of the chart main window
   CHART_PROP_FIRST_VISIBLE_BAR,                      // Number of the first visible bar on the chart
   CHART_PROP_WIDTH_IN_BARS,                          // Width of the chart in bars
   CHART_PROP_WIDTH_IN_PIXELS,                        // Width of the chart in pixels
   CHART_PROP_HEIGHT_IN_PIXELS,                       // Height of the chart in pixels
   CHART_PROP_COLOR_BACKGROUND,                       // Color of background of the chart
   CHART_PROP_COLOR_FOREGROUND,                       // Color of axes, scale and OHLC line
   CHART_PROP_COLOR_GRID,                             // Grid color
   CHART_PROP_COLOR_VOLUME,                           // Color of volumes and position opening levels
   CHART_PROP_COLOR_CHART_UP,                         // Color for the up bar, shadows and body borders of bull candlesticks
   CHART_PROP_COLOR_CHART_DOWN,                       // Color of down bar, its shadow and border of body of the bullish candlestick
   CHART_PROP_COLOR_CHART_LINE,                       // Color of the chart line and the Doji candlesticks
   CHART_PROP_COLOR_CANDLE_BULL,                      // Color of body of a bullish candlestick
   CHART_PROP_COLOR_CANDLE_BEAR,                      // Color of body of a bearish candlestick
   CHART_PROP_COLOR_BID,                              // Color of the Bid price line
   CHART_PROP_COLOR_ASK,                              // Color of the Ask price line
   CHART_PROP_COLOR_LAST,                             // Color of the last performed deal's price line (Last)
   CHART_PROP_COLOR_STOP_LEVEL,                       // Color of stop order levels (Stop Loss and Take Profit)
   CHART_PROP_SHOW_TRADE_LEVELS,                      // Display trade levels on the chart (levels of open positions, Stop Loss, Take Profit and pending orders)
   CHART_PROP_DRAG_TRADE_LEVELS,                      // Enable the ability to drag trading levels on a chart using mouse
   CHART_PROP_SHOW_DATE_SCALE,                        // Display the time scale on a chart
   CHART_PROP_SHOW_PRICE_SCALE,                       // Display a price scale on a chart
   CHART_PROP_SHOW_ONE_CLICK,                         // Display the quick trading panel on the chart
   CHART_PROP_IS_MAXIMIZED,                           // Chart window maximized
   CHART_PROP_IS_MINIMIZED,                           // Chart window minimized
   CHART_PROP_IS_DOCKED,                              // Chart window docked
   CHART_PROP_FLOAT_LEFT,                             // Left coordinate of the undocked chart window relative to the virtual screen
   CHART_PROP_FLOAT_TOP,                              // Upper coordinate of the undocked chart window relative to the virtual screen
   CHART_PROP_FLOAT_RIGHT,                            // Right coordinate of the undocked chart window relative to the virtual screen
   CHART_PROP_FLOAT_BOTTOM,                           // Bottom coordinate of the undocked chart window relative to the virtual screen
   //--- CWndInd
   CHART_PROP_WINDOW_IND_HANDLE,                      // Indicator handle in the chart window
   CHART_PROP_WINDOW_IND_INDEX,                       // Indicator index in the chart window
   CHART_PROP_WINDOW_NUM,                             // Chart window index
  };
#define CHART_PROP_INTEGER_TOTAL (67)                 // Total number of integer properties
#define CHART_PROP_INTEGER_SKIP  (0)                  // Number of integer DOM properties not used in sorting
//+------------------------------------------------------------------+

Como el número de propiedades de tipo entero ha aumentado, no debemos olvidar incrementar e indicar su número: de 66, pasará a 67.

Y, por consiguiente, en la enumeración de los criterios para clasificar los objetos de gráfico, añadiremos la clasificación según el número de la ventana del gráfico:

//+------------------------------------------------------------------+
//| Possible chart sorting criteria                                  |
//+------------------------------------------------------------------+
#define FIRST_CHART_DBL_PROP  (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP)
#define FIRST_CHART_STR_PROP  (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP+CHART_PROP_DOUBLE_TOTAL-CHART_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CHART_MODE
  {
//--- Sort by integer properties
   SORT_BY_CHART_ID = 0,                              // Sort by chart ID
   SORT_BY_CHART_TIMEFRAME,                           // Sort by chart timeframe
   SORT_BY_CHART_SHOW,                                // Sort by the price chart drawing attribute
   SORT_BY_CHART_IS_OBJECT,                           // Sort by chart object (OBJ_CHART) identification attribute
   SORT_BY_CHART_BRING_TO_TOP,                        // Sort by the flag of displaying a chart above all others
   SORT_BY_CHART_CONTEXT_MENU,                        // Sort by the flag of enabling/disabling access to the context menu using the right click
   SORT_BY_CHART_CROSSHAIR_TOO,                       // Sort by the flag of enabling/disabling access to the Crosshair tool using the middle click
   SORT_BY_CHART_MOUSE_SCROLL,                        // Sort by the flag of scrolling the chart horizontally using the left mouse button
   SORT_BY_CHART_EVENT_MOUSE_WHEEL,                   // Sort by the flag of sending messages about mouse wheel events to all MQL5 programs on a chart
   SORT_BY_CHART_EVENT_MOUSE_MOVE,                    // Sort by the flag of sending messages about mouse button click and movement events to all MQL5 programs on a chart
   SORT_BY_CHART_EVENT_OBJECT_CREATE,                 // Sort by the flag of sending messages about the graphical object creation event to all MQL5 programs on a chart
   SORT_BY_CHART_EVENT_OBJECT_DELETE,                 // Sort by the flag of sending messages about the graphical object destruction event to all MQL5 programs on a chart
   SORT_BY_CHART_MODE,                                // Sort by chart type
   SORT_BY_CHART_FOREGROUND,                          // Sort by the "Price chart in the foreground" flag
   SORT_BY_CHART_SHIFT,                               // Sort by the "Mode of shift of the price chart from the right border" flag
   SORT_BY_CHART_AUTOSCROLL,                          // Sort by the "The mode of automatic shift to the right border of the chart" flag
   SORT_BY_CHART_KEYBOARD_CONTROL,                    // Sort by the flag allowing the chart management using a keyboard
   SORT_BY_CHART_QUICK_NAVIGATION,                    // Sort by the flag allowing the chart to intercept Space and Enter key strokes to activate the quick navigation bar
   SORT_BY_CHART_SCALE,                               // Sort by scale
   SORT_BY_CHART_SCALEFIX,                            // Sort by the fixed scale flag
   SORT_BY_CHART_SCALEFIX_11,                         // Sort by the 1:1 scale flag
   SORT_BY_CHART_SCALE_PT_PER_BAR,                    // Sort by the flag of specifying the scale in points per bar
   SORT_BY_CHART_SHOW_TICKER,                         // Sort by the flag displaying a symbol ticker in the upper left corner
   SORT_BY_CHART_SHOW_OHLC,                           // Sort by the flag displaying OHLC values in the upper left corner
   SORT_BY_CHART_SHOW_BID_LINE,                       // Sort by the flag displaying Bid value as a horizontal line on the chart
   SORT_BY_CHART_SHOW_ASK_LINE,                       // Sort by the flag displaying Ask value as a horizontal line on the chart
   SORT_BY_CHART_SHOW_LAST_LINE,                      // Sort by the flag displaying Last value as a horizontal line on the chart
   SORT_BY_CHART_SHOW_PERIOD_SEP,                     // Sort by the flag displaying vertical separators between adjacent periods
   SORT_BY_CHART_SHOW_GRID,                           // Sort by the flag of displaying a grid on the chart
   SORT_BY_CHART_SHOW_VOLUMES,                        // Sort by the mode of displaying volumes on a chart
   SORT_BY_CHART_SHOW_OBJECT_DESCR,                   // Sort by the flag of displaying object text descriptions
   SORT_BY_CHART_VISIBLE_BARS,                        // Sort by the number of bars on a chart that are available for display
   SORT_BY_CHART_WINDOWS_TOTAL,                       // Sort by the total number of chart windows including indicator subwindows
   SORT_BY_CHART_WINDOW_HANDLE,                       // Sort by the chart handle
   SORT_BY_CHART_WINDOW_YDISTANCE,                    // Sort by the distance in Y axis pixels between the upper frame of the indicator subwindow and the upper frame of the chart main window
   SORT_BY_CHART_FIRST_VISIBLE_BAR,                   // Sort by the number of the first visible bar on the chart
   SORT_BY_CHART_WIDTH_IN_BARS,                       // Sort by the width of the chart in bars
   SORT_BY_CHART_WIDTH_IN_PIXELS,                     // Sort by the width of the chart in pixels
   SORT_BY_CHART_HEIGHT_IN_PIXELS,                    // Sort by the height of the chart in pixels
   SORT_BY_CHART_COLOR_BACKGROUND,                    // Sort by the color of the chart background
   SORT_BY_CHART_COLOR_FOREGROUND,                    // Sort by color of axes, scale and OHLC line
   SORT_BY_CHART_COLOR_GRID,                          // Sort by grid color
   SORT_BY_CHART_COLOR_VOLUME,                        // Sort by the color of volumes and position opening levels
   SORT_BY_CHART_COLOR_CHART_UP,                      // Sort by the color for the up bar, shadows and body borders of bull candlesticks
   SORT_BY_CHART_COLOR_CHART_DOWN,                    // Sort by the color of down bar, its shadow and border of body of the bullish candlestick
   SORT_BY_CHART_COLOR_CHART_LINE,                    // Sort by the color of the chart line and the Doji candlesticks
   SORT_BY_CHART_COLOR_CANDLE_BULL,                   // Sort by the color of a bullish candlestick body
   SORT_BY_CHART_COLOR_CANDLE_BEAR,                   // Sort by the color of a bearish candlestick body
   SORT_BY_CHART_COLOR_BID,                           // Sort by the color of the Bid price line
   SORT_BY_CHART_COLOR_ASK,                           // Sort by the color of the Ask price line
   SORT_BY_CHART_COLOR_LAST,                          // Sort by the color of the last performed deal's price line (Last)
   SORT_BY_CHART_COLOR_STOP_LEVEL,                    // Sort by the color of stop order levels (Stop Loss and Take Profit)
   SORT_BY_CHART_SHOW_TRADE_LEVELS,                   // Sort by the flag of displaying trading levels on the chart
   SORT_BY_CHART_DRAG_TRADE_LEVELS,                   // Sort by the flag enabling the ability to drag trading levels on a chart using mouse
   SORT_BY_CHART_SHOW_DATE_SCALE,                     // Sort by the flag of displaying the time scale on the chart
   SORT_BY_CHART_SHOW_PRICE_SCALE,                    // Sort by the flag of displaying the price scale on the chart
   SORT_BY_CHART_SHOW_ONE_CLICK,                      // Sort by the flag of displaying the quick trading panel on the chart
   SORT_BY_CHART_IS_MAXIMIZED,                        // Sort by the "Chart window maximized" flag
   SORT_BY_CHART_IS_MINIMIZED,                        // Sort by the "Chart window minimized" flag
   SORT_BY_CHART_IS_DOCKED,                           // Sort by the "Chart window docked" flag
   SORT_BY_CHART_FLOAT_LEFT,                          // Sort by the left coordinate of the undocked chart window relative to the virtual screen
   SORT_BY_CHART_FLOAT_TOP,                           // Sort by the upper coordinate of the undocked chart window relative to the virtual screen
   SORT_BY_CHART_FLOAT_RIGHT,                         // Sort by the right coordinate of the undocked chart window relative to the virtual screen
   SORT_BY_CHART_FLOAT_BOTTOM,                        // Sort by the bottom coordinate of the undocked chart window relative to the virtual screen
   SORT_BY_CHART_WINDOW_IND_HANDLE,                   // Sort by the indicator handle in the chart window
   SORT_BY_CHART_WINDOW_IND_INDEX,                    // Sort by the indicator index in the chart window
   SORT_BY_CHART_WINDOW_NUM,                          // Sort by chart window index
//--- Sort by real properties
   SORT_BY_CHART_SHIFT_SIZE = FIRST_CHART_DBL_PROP,   // Sort by the shift size of the zero bar from the right border in %
   SORT_BY_CHART_FIXED_POSITION,                      // Sort by the chart fixed position from the left border in %
   SORT_BY_CHART_FIXED_MAX,                           // Sort by the fixed chart maximum
   SORT_BY_CHART_FIXED_MIN,                           // Sort by the fixed chart minimum
   SORT_BY_CHART_POINTS_PER_BAR,                      // Sort by the scale value in points per bar
   SORT_BY_CHART_PRICE_MIN,                           // Sort by the chart minimum
   SORT_BY_CHART_PRICE_MAX,                           // Sort by the chart maximum
//--- Sort by string properties
   SORT_BY_CHART_COMMENT = FIRST_CHART_STR_PROP,      // Sort by a comment text on the chart
   SORT_BY_CHART_EXPERT_NAME,                         // Sort by a name of an EA launched on the chart
   SORT_BY_CHART_SCRIPT_NAME,                         // Sort by a name of a script launched on the chart
   SORT_BY_CHART_WINDOW_IND_NAME,                     // Sort by a name of an indicator launched in the chart window
   SORT_BY_CHART_SYMBOL,                              // Sort by chart symbol
  };
//+------------------------------------------------------------------+


Como ya hemos mencionado anteriormente, usaremos listas especiales para guardar las copias de los objetos de indicador, las ventanas y los gráficos ya eliminados. Necesitaremos acceder a estas listas en cada objeto de gráfico, sus ventanas y los indicadores pertenecientes a las ventanas. Para no guardar nuestras listas en la clase de cada objeto, y luego poder organizar el acceso a ellas desde otros objetos donde se necesita información sobre un objeto eliminado en particular, declararemos todas estas listas en la clase de colección de objetos de gráfico (desde esta clase tenemos acceso a todos los objetos del gráfico), mientras que transmitiremos a los otros objetos (gráfico, ventana del gráfico, indicador en la ventana del gráfico) los punteros a estas listas. Así, todas las listas estarán disponibles para cada uno de los objetos almacenados en la colección.

Esto impone algunas restricciones sobre el uso de estas listas dentro de los objetos de la colección, aparte del objeto de colección en sí mismo. No podremos eliminar objetos en las listas, resetear las listas mientras trabajamos dentro de todos los objetos de la colección (excepto la colección en sí), ni modificar de ninguna forma los punteros a las listas. Pero, al mismo tiempo, si tenemos en cuenta esta peculiaridad a la hora de trabajar con un puntero a una lista, es decir, si en la práctica lo usamos en el modo read-only, entonces resultará más fácil para nosotros organizar el acceso a dicha lista desde diferentes objetos; bastará con transmitirles un puntero al objeto de la lista y leer tranquilamente su contenido.

Vamos a introducir mejoras en el archivo de clase del objeto de indicador en la ventana del gráfico y en el objeto de ventana del gráfico
(ambas clases están en el mismo archivo \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh).

En la sección privada de la clase CWndInd, declaramos una variable para almacenar el número de la subventana en la que se encuentra el indicador, mientras que en la sección pública de la clase, escribimos los métodos para configurar y retornar todas las propiedades del objeto (antes podíamos establecer solo una propiedad: el índice del indicador en la lista de la ventana):

//+------------------------------------------------------------------+
//| Chart window indicator object class                              |
//+------------------------------------------------------------------+
class CWndInd : public CObject
  {
private:
   long              m_chart_id;                         // Chart ID
   string            m_name;                             // Indicator short name
   int               m_index;                            // indicator index in the list
   int               m_window_num;                       // Indicator subwindow index
   int               m_handle;                           // Indicator handle
public:
//--- Return itself
   CWndInd          *GetObject(void)                     { return &this;               }
//--- Return (1) indicator name, (2) index in the list, (3) indicator handle and (4) subwindow index
   string            Name(void)                    const { return this.m_name;         }
   int               Index(void)                   const { return this.m_index;        }
   int               Handle(void)                  const { return this.m_handle;       }
   int               WindowNum(void)               const { return this.m_window_num;   }
//--- Set (1) subwindow name, (2) window index on the chart, (3) handle, (4) index
   void              SetName(const string name)          { this.m_name=name;           }
   void              SetIndex(const int index)           { this.m_index=index;         }
   void              SetHandle(const int handle)         { this.m_handle=handle;       }
   void              SetWindowNum(const int win_num)     { this.m_window_num=win_num;  }
   
//--- Display the description of object properties in the journal (dash=true - hyphen before the description, false - description only)
   void              Print(const bool dash=false)        { ::Print((dash ? "- " : "")+this.Header());                      }
//--- Return the object short name
   string            Header(void)                  const { return CMessage::Text(MSG_CHART_OBJ_INDICATOR)+" "+this.Name(); }
   
//--- Compare CWndInd objects with each other by the specified property
   virtual int       Compare(const CObject *node,const int mode=0) const;
   
//--- Constructors
                     CWndInd(void){;}
                     CWndInd(const int handle,const string name,const int index,const int win_num) : m_handle(handle),
                                                                                                     m_name(name),
                                                                                                     m_index(index),
                                                                                                     m_window_num(win_num) {}
  };
//+------------------------------------------------------------------+

Además, en el constructor paramétrico, añadimos la transmisión del número de la subventana del gráfico en la que se halla el indicador, y la asignación del valor transferido a la variable correspondiente.

Ahora, al crear un objeto de este tipo, necesitaremos indicar de manera adicional el número de la subventana en la que está el indicador para el que se crea este objeto. Así, tendremos una indicación de la ventana en la que se encontraba el indicador después de eliminar tanto el indicador en sí como su ventana. Esto hará más fácil encontrar el número de una ventana ya inexistente para entender en cuál de ellas estaba el indicador eliminado junto con la ventana cuyos objetos estarán en las listas de objetos de gráfico eliminados.

También introduciremos cambios en la clase del objeto de ventana del gráfico. En la sección privada de la clase, declaramos los punteros a las listas de indicadores cambiados en la ventana y eliminados de la misma, y luego declaramos la variable para almacenar el símbolo del gráfico al que pertenece la ventana. A continuación, declaramos el método que retorna la bandera sobre la presencia de un indicador desde una ventana en la lista del objeto de gráfico, el método que retorna un objeto indicador que está en la lista pero no en la ventana del gráfico y el método para comprobar los cambios en los parámetros de los indicadores existentes en la ventana:

//+------------------------------------------------------------------+
//| Chart window object class                                        |
//+------------------------------------------------------------------+
class CChartWnd : public CBaseObjExt
  {
private:
   CArrayObj         m_list_ind;                                        // Indicator list
   CArrayObj        *m_list_ind_del;                                    // Pointer to the list of indicators removed from the indicator window
   CArrayObj        *m_list_ind_param;                                  // Pointer to the list of changed indicators
   int               m_window_num;                                      // Subwindow index
   int               m_wnd_coord_x;                                     // The X coordinate for the time on the chart in the window
   int               m_wnd_coord_y;                                     // The Y coordinate for the price on the chart in the window
   string            m_symbol;                                          // Symbol of a chart the window belongs to
//--- Return the flag indicating the presence of an indicator (1) from the list in the window and (2) from the window in the list
   bool              IsPresentInWindow(const CWndInd *ind);
   bool              IsPresentInList(const string name);
//--- Return the indicator object present in the list but not present on the chart
   CWndInd          *GetMissingInd(void);
//--- Remove indicators not present in the window from the list
   void              IndicatorsDelete(void);
//--- Add new indicators to the list
   void              IndicatorsAdd(void);
//--- Check the changes of the parameters of existing indicators
   void              IndicatorsChangeCheck(void);
   
public:

Ahora, la clase hereda de la clase básica extendida de todos los objetos de la biblioteca, lo cual proporciona a sus descendientes una funcionalidad de eventos que se puede crear de forma económica para cada uno de estos objetos.

En la sección pública de la clase, en el método que retorna la bandera que indica si el objeto ofrece soporte a la propiedad indicada, añadimos otra propiedad soportada más: el símbolo del gráfico, mientras que trasladamos fuera del cuerpo de la clase la implementación del método que retorna la descripción de la propiedad de la línea (que analizaremos más tarde):

public:
//--- Return itself
   CChartWnd        *GetObject(void)                                    { return &this;            }

//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_CHART_PROP_INTEGER property)  { return(property==CHART_PROP_WINDOW_YDISTANCE || property==CHART_PROP_HEIGHT_IN_PIXELS ? true : false); }
   virtual bool      SupportProperty(ENUM_CHART_PROP_DOUBLE property)   { return false; }
   virtual bool      SupportProperty(ENUM_CHART_PROP_STRING property)   { return (property==CHART_PROP_WINDOW_IND_NAME || property==CHART_PROP_SYMBOL ? true : false);  }

//--- Get description of (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_CHART_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_CHART_PROP_DOUBLE property)  { return CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED);        }
   string            GetPropertyDescription(ENUM_CHART_PROP_STRING property);

En el mismo lugar, en la sección pública de la clase, declararemos el método para crear y enviar un evento de gráfico al gráfico del programa de control; en el constructor paramétrico de la clase, ahora transmitiremos el nombre del símbolo del gráfico y los punteros a listas de indicadores eliminados y modificados de esta ventana, y también declararemos el destructor de la clase:

//--- Return the object short name
   virtual string    Header(void);
   
//--- Create and send the chart event to the control program chart
   void              SendEvent(ENUM_CHART_OBJ_EVENT event);
  
//--- Compare CChartWnd objects by a specified property (to sort the list by an MQL5 signal object)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CChartWnd objects by all properties (to search for equal MQL5 signal objects)
   bool              IsEqual(CChartWnd* compared_obj) const;
   
//--- Constructors
                     CChartWnd(void){;}
                     CChartWnd(const long chart_id,const int wnd_num,const string symbol,CArrayObj *list_ind_del,CArrayObj *list_ind_param);
//--- Destructor
                    ~CChartWnd(void);

A continuación, añadiremos el resto de los métodos necesarios, cuyo propósito queda claro gracias a los comentarios en el listado:

//--- Return (1) the subwindow index, (2) the number of indicators attached to the window and (3) the name of a symbol chart
   int               WindowNum(void)                              const { return this.m_window_num;                                       }
   int               IndicatorsTotal(void)                        const { return this.m_list_ind.Total();                                 }
   string            Symbol(void)                                 const { return m_symbol;}
//--- Set (1) the subwindow index and (2) the chart symbol
   void              SetWindowNum(const int num)                        { this.m_window_num=num;                                          }
   void              SetSymbol(const string symbol)                     { this.m_symbol=symbol;                                           }
   
//--- Return (1) the indicator list, the window indicator object from the list by (2) index in the list and (3) by handle
   CArrayObj        *GetIndicatorsList(void)                            { return &this.m_list_ind;                                        }
   CWndInd          *GetIndicatorByIndex(const int index);
   CWndInd          *GetIndicatorByHandle(const int handle);
//--- Return (1) the last one added to the window, (2) the last one removed from the window and (3) the changed indicator
   CWndInd          *GetLastAddedIndicator(void)                        { return this.m_list_ind.At(this.m_list_ind.Total()-1);           }
   CWndInd          *GetLastDeletedIndicator(void)                      { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1);   }
   CWndInd          *GetLastChangedIndicator(void)                      { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);}


Vamos a analizar con mayor detalle la implementación de los métodos nuevos y mejorados.

En el constructor paramétrico de la clase, en su lista de inicialización, asignamos a la variable m_symbol el valor transmitido en los parámetros,
y en el cuerpo de la clase, asignamos a las variables de puntero a listas los valores de los punteros transmitidos ​​al método:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CChartWnd::CChartWnd(const long chart_id,const int wnd_num,const string symbol,CArrayObj *list_ind_del,CArrayObj *list_ind_param) : m_window_num(wnd_num),
                                                                                                                                    m_symbol(symbol),
                                                                                                                                    m_wnd_coord_x(0),
                                                                                                                                    m_wnd_coord_y(0)
  {
   this.m_list_ind_del=list_ind_del;
   this.m_list_ind_param=list_ind_param;
   CBaseObj::SetChartID(chart_id);
   this.IndicatorsListCreate();
  }
//+------------------------------------------------------------------+

En el artículo anterior, en los métodos en los que recibimos los datos sobre los indicadores adjuntos a la ventana, tomábamos de la lista de indicadores sus manejadores y, después de leer los datos del indicador, liberábamos de inmediato el manejador. Como resultado, se creaba un nuevo manejador cada vez para el mismo indicador (debido a la incorrecta comprensión de la información en la guía de ayuda: el manejador del indicador debe liberarse solo cuando realmente ya no sea necesario, es decir, cuando se complete la funcionamiento del programa, y no inmediatamente después de recibir los datos según el manejador, mientras que el programa seguirá usando el indicador). Por consiguiente, hoy arreglaremos este descuido: para ello, liberaremos los manejadores de todos los indicadores en el destructor de la clase.

Destructor de la clase:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CChartWnd::~CChartWnd(void)
  {
   int total=this.m_list_ind.Total();
   for(int i=total-1;i>WRONG_VALUE;i--)
     {
      CWndInd *ind=this.m_list_ind.At(i);
      if(ind==NULL)
         continue;
      ::IndicatorRelease(ind.Handle());
      this.m_list_ind.Delete(i);
     }
  }
//+------------------------------------------------------------------+

Aquí, en un ciclo por la lista de objetos de indicador de la ventana, obtenemos el siguiente objeto y liberamos el manejador del indicador escrito en las propiedades del objeto,
e inmediatamente eliminamos el objeto mismo.

En el método virtual para comparar dos objetos según la propiedad indicada, escribimos la comparación según el número de ventana y según el símbolo del gráfico:

//+------------------------------------------------------------------+
//| Compare CChartWnd objects with each other by a specified property|
//+------------------------------------------------------------------+
int CChartWnd::Compare(const CObject *node,const int mode=0) const
  {
   const CChartWnd *obj_compared=node;
   if(mode==CHART_PROP_WINDOW_YDISTANCE)
      return(this.YDistance()>obj_compared.YDistance() ? 1 : this.YDistance()<obj_compared.YDistance() ? -1 : 0);
   else if(mode==CHART_PROP_HEIGHT_IN_PIXELS)
      return(this.HeightInPixels()>obj_compared.HeightInPixels() ? 1 : this.HeightInPixels()<obj_compared.HeightInPixels() ? -1 : 0);
   else if(mode==CHART_PROP_WINDOW_NUM)
      return(this.WindowNum()>obj_compared.WindowNum() ? 1 : this.WindowNum()<obj_compared.WindowNum() ? -1 : 0);
   else if(mode==CHART_PROP_SYMBOL)
      return(this.Symbol()==obj_compared.Symbol() ? 0 : this.Symbol()>obj_compared.Symbol() ? 1 : -1);
   return -1;
  }
//+------------------------------------------------------------------+

Esto es necesario para clasificar y buscar objetos en las listas según el símbolo del gráfico y la propiedad del objeto recién introducida: el número de ventana.

Hemos implementado el método que retorna la descripción de una propiedad de tipo string del objeto fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Return description of object's string property                   |
//+------------------------------------------------------------------+
string CChartWnd::GetPropertyDescription(ENUM_CHART_PROP_STRING property)
  {
   return
     (
      property==CHART_PROP_SYMBOL  ?  CMessage::Text(MSG_LIB_PROP_SYMBOL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.Symbol()
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Aquí, como en todos los métodos similares de todos los objetos de biblioteca, creamos una descripción textual de la propiedad transmitida al método de esta forma:
si esta propiedad es "Símbolo del gráfico", retornamos su descripción de tipo string,
para cualquier otra propiedad, retornaremos una línea vacía.
Si hemos establecido para la propiedad la bandera "propiedad no soportada", retornaremos una línea con el mismo contenido.

En el método que crea la lista de indicadores adjuntos a la ventana, borramos en el ciclo la línea en la que se libera el manejador del indicador actualmente seleccionado (ya hemos discutido los motivos justo arriba):

      //--- get and save the indicator handle by its short name
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Free the indicator handle
      ::IndicatorRelease(handle);
      //--- Create the new indicator object in the chart window

y en la línea donde creamos un nuevo objeto de indicador en la ventana del gráfico, añadimos la transmisión del número de ventana actual al constructor de la clase:

//+------------------------------------------------------------------+
//| Create the list of indicators attached to the window             |
//+------------------------------------------------------------------+
void CChartWnd::IndicatorsListCreate(void)
  {
   //--- Clear the indicator lists
   this.m_list_ind.Clear();
   //--- Get the total number of indicators in the window
   int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num);
   //--- In the loop by the number of indicators,
   for(int i=0;i<total;i++)
     {
      //--- obtain and save the short indicator name,
      string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i);
      //--- get and save the indicator handle by its short name
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Create the new indicator object in the chart window
      CWndInd *ind=new CWndInd(handle,name,i,this.WindowNum());
      if(ind==NULL)
         continue;
      //--- set the sorted list flag to the list
      this.m_list_ind.Sort();
      //--- If failed to add the object to the list, remove it
      if(!this.m_list_ind.Add(ind))
         delete ind;
     }
  }
//+------------------------------------------------------------------+

Ahora, cada objeto de indicador en la ventana del gráfico "sabrá" en qué ventana se encuentra.

Vamos a realizar las mismas manipulaciones con el método que añade nuevos indicadores a la lista:

Borramos la línea

      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Free the indicator handle
      ::IndicatorRelease(handle);
      //--- Create the new indicator object in the chart window

y añadimos la transmisión del número de ventana al crear un nuevo objeto de indicador en la ventana del gráfico:

//+------------------------------------------------------------------+
//| Add new indicators to the list                                   |
//+------------------------------------------------------------------+
void CChartWnd::IndicatorsAdd(void)
  {
   //--- Get the total number of indicators in the window
   int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num);
   //--- In the loop by the number of indicators,
   for(int i=0;i<total;i++)
     {
      //--- obtain and save the short indicator name,
      string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i);
      //--- get and save the indicator handle by its short name
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- Create the new indicator object in the chart window
      CWndInd *ind=new CWndInd(handle,name,i,this.WindowNum());
      if(ind==NULL)
         continue;
      //--- set the sorted list flag to the list
      this.m_list_ind.Sort();
      //--- If the object is already in the list or an attempt to add it to the list failed, remove it
      if(this.m_list_ind.Search(ind)>WRONG_VALUE || !this.m_list_ind.Add(ind))
         delete ind;
     }
  }
//+------------------------------------------------------------------+

En el método que retorna la bandera sobre la presencia de un indicador de la lista en la ventana, también eliminamos la línea:

      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      ::IndicatorRelease(handle);

Ahora, tampoco liberamos el manejador del indicador en este método:

//+-------------------------------------------------------------------------------------+
//| Return the flag indicating the presence of an indicator from the list in the window |
//+-------------------------------------------------------------------------------------+
bool CChartWnd::IsPresentInWindow(const CWndInd *ind)
  {
   int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num);
   for(int i=0;i<total;i++)
     {
      string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i);
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      if(ind.Name()==name && ind.Handle()==handle)
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Método para comprobar los cambios en los parámetros de los indicadores existentes:

//+------------------------------------------------------------------+
//| Check the changes of the parameters of existing indicators       |
//+------------------------------------------------------------------+
void CChartWnd::IndicatorsChangeCheck(void)
  {
   //--- Get the total number of indicators in the window
   int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num);
   //--- In the loop by all window indicators,
   for(int i=0;i<total;i++)
     {
      //--- get the indicator name and get its handle by a name
      string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i);
      int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name);
      //--- If the indicator with such a name is present in the object indicator list, move on to the next one
      if(this.IsPresentInList(name))
         continue;
      //--- Get the indicator object present in the list but not present in the window
      CWndInd *ind=this.GetMissingInd();
      if(ind==NULL)
         continue;
      //--- If the indicator and the detected object have the same index, this is the indicator with changed parameters
      if(ind.Index()==i)
        {
         //--- Create a new indicator object based on the detected indicator object,
         CWndInd *changed=new CWndInd(ind.Handle(),ind.Name(),ind.Index(),ind.WindowNum());
         if(changed==NULL)
            continue;
         //--- set the sorted list flag to the list of changed indicators
         this.m_list_ind_param.Sort();
         //--- If failed to add a newly created indicator object to the list of changed indicators,
         //--- remove the created object and move on to the next indicator in the window
         if(!this.m_list_ind_param.Add(changed))
           {
            delete changed;
            continue;
           }
         //--- Set the new parameters for the detected "lost" indicator - short name and handle
         ind.SetName(name);
         ind.SetHandle(handle);
         //--- and call the method of sending a custom event to the control program chart
         this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_CHANGE);
        }
     }
  }
//+------------------------------------------------------------------+

Toda la lógica del método se describe detalladamente en su listado. Aclaremos este punto: los indicadores en la ventana del gráfico se identifican según su nombre breve. Si ha cambiado algún parámetro en algún indicador, deberemos cambiar su nombre breve (esto se aplica a los indicadores personalizados correctamente creados, mientras que los indicadores estándar tienen en cuenta esta característica). Por consiguiente, aquí la búsqueda del indicador modificado se basará en que el índice del indicador modificado en la ventana no cambia, aunque sí lo hace su nombre breve. En consecuencia, si encontramos un indicador que está en la lista del objeto de ventana, pero no está en la ventana del gráfico en el terminal del cliente, deberemos comprobar si los índices coinciden: si el índice del indicador y el índice del objeto encontrado coinciden (y al eliminar el indicador, se reconstruyen los índices de los otros indicadores en la ventana), entonces este será el indicador buscado, para el cual se han cambiado los parámetros.

Como necesitamos almacenar los indicadores eliminados de la ventana en la lista de indicadores eliminados (para su posterior búsqueda al procesar los eventos), necesitaremos modificar el método que elimina de la lista los indicadores ya ausentes en la ventana:

//+------------------------------------------------------------------+
//| Remove indicators not present in the window from the list        |
//+------------------------------------------------------------------+
void CChartWnd::IndicatorsDelete(void)
  {
   //--- In the loop by the list of window indicator objects,
   int total=this.m_list_ind.Total();
   for(int i=total-1;i>WRONG_VALUE;i--)
     {
      //--- get the next indicator object
      CWndInd *ind=this.m_list_ind.At(i);
      if(ind==NULL)
         continue;
      //--- If such an indicator is present in the chart window, move on to the next object in the list
      if(this.IsPresentInWindow(ind))
         continue;
      //--- Create a copy of a removed indicator
      CWndInd *ind_del=new CWndInd(ind.Handle(),ind.Name(),ind.Index(),ind.WindowNum());
      if(ind_del==NULL)
         continue;
      //--- If failed to place a created object to the list of indicators removed from the window,
      //--- remove it and go to the next object in the list
      if(!this.m_list_ind_del.Add(ind_del))
        {
         delete ind_del;
         continue;
        }
      //--- Remove the indicator, which was deleted from the window, from the list
      this.m_list_ind.Delete(i);
     }
  }
//+------------------------------------------------------------------+

Ya hemos descrito con detalle la lógica del método en el listado de códigos, por lo que esperamos que no necesite explicaciones especiales. En cualquier caso, el lector podrá escribir cualquier duda en los comentarios al artículo.

Método que retorna la bandera sobre la presencia de un indicador de la ventana en la lista:

//+-----------------------------------------------------------------------------+
//| Return the flag of the presence of an indicator from the window in the list |
//+-----------------------------------------------------------------------------+
bool CChartWnd::IsPresentInList(const string name)
  {
   CWndInd *ind=new CWndInd();
   if(ind==NULL)
      return false;
   ind.SetName(name);
   this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_NAME);
   int index=this.m_list_ind.Search(ind);
   delete ind;
   return(index>WRONG_VALUE);
  }
//+------------------------------------------------------------------+

El método nos permite conocer según su nombre breve la presencia del objeto de indicador correspondiente en la lista del objeto de ventana.
Aquí, creamos un objeto de indicador temporal y le asignamos el nombre breve transmitido al método. Asignamos a la lista de objetos de indicador la bandera de clasificación según el nombre del indicador y obtenemos el índice del indicador con este nombre en la lista. Asegúrese de eliminar el objeto temporal y retornar la bandera que indica que el índice del indicador encontrado en la lista es mayor que -1 (si se encuentra un indicador con un nombre así, su índice será mayor que -1)

Método que retorna un objeto de indicador que está en la lista pero no en la ventana del gráfico:

//+------------------------------------------------------------------+
//| Return the indicator object present in the list                  |
//| but not on the chart                                             |
//+------------------------------------------------------------------+
CWndInd *CChartWnd::GetMissingInd(void)
  {
   for(int i=0;i<this.m_list_ind.Total();i++)
     {
      CWndInd *ind=this.m_list_ind.At(i);
      if(!this.IsPresentInWindow(ind))
         return ind;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Aquí, en un ciclo por todos los objetos de indicador en la lista, obtenemos el siguiente objeto indicador y, si no existe tal indicador en la ventana del gráfico, retornamos el puntero al objeto de indicador encontrado. De lo contrario, retornamos NULL.

Método que retorna el objeto indicador de la lista de objetos según el índice del indicador en la lista de la ventana del gráfico:

//+------------------------------------------------------------------+
//| Return the indicator object by the index in the window list      |
//+------------------------------------------------------------------+
CWndInd *CChartWnd::GetIndicatorByIndex(const int index)
  {
   CWndInd *ind=new CWndInd();
   if(ind==NULL)
      return NULL;
   ind.SetIndex(index);
   this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_INDEX);
   int n=this.m_list_ind.Search(ind);
   delete ind;
   return this.m_list_ind.At(n);
  }
//+------------------------------------------------------------------+

Aquí, creamos un objeto de indicador temporal y le asignamos el índice transmitido al método. Asignamos a la lista de objetos de indicador la bandera de clasificación según el índice del indicador en la ventana del gráfico y obtenemos el índice del indicador en la lista del objeto de ventana con este índice en la lista de la ventana del gráfico. Asegúrese de eliminar el objeto temporal y retornar el puntero al objeto según el índice encontrado en la lista de objetos de indicador.
Si no hemos encontrado el objeto, el método Search() retornará -1, mientras que el método At() con este valor de índice retornará NULL. Por consiguiente, el método retornará el puntero al objeto encontrado en la lista o NULL si el objeto de indicador con el índice especificado en la ventana del gráfico no se encuentra en la lista.

Método que retorna un objeto de indicador de la ventana desde la lista según el identificador:

//+------------------------------------------------------------------+
//| Return the window indicator object from the list by handle       |
//+------------------------------------------------------------------+
CWndInd *CChartWnd::GetIndicatorByHandle(const int handle)
  {
   CWndInd *ind=new CWndInd();
   if(ind==NULL)
      return NULL;
   ind.SetHandle(handle);
   this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_HANDLE);
   int index=this.m_list_ind.Search(ind);
   delete ind;
   return this.m_list_ind.At(index);
  }
//+------------------------------------------------------------------+

El método es idéntico al anterior, salvo que transmitimos al método el valor del identificador del objeto de indicador requerido y, en consecuencia, la lista se ordenará según la propiedad "manejador del indicador", para que la búsqueda en la lista se realice según esta propiedad del objeto.

Vamos a modificar el método que actualiza los datos de los indicadores adjuntos a la ventana para enviar eventos personalizados al gráfico del programa de control si se dan cambios en sus parámetros o en su número:

//+------------------------------------------------------------------+
//| Update data on attached indicators                               |
//+------------------------------------------------------------------+
void CChartWnd::Refresh(void)
  {
   //--- Calculate the change of the indicator number in the "now and during the previous check" window
   int change=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num)-this.m_list_ind.Total();
   //--- If there is no change in the number of indicators in the window,
   if(change==0)
     {
      //--- check the change of parameters of all indicators and exit
      this.IndicatorsChangeCheck();
      return;
     }
   //--- If indicators are added
   if(change>0)
     {
      //--- Call the method of adding new indicators to the list
      this.IndicatorsAdd();
      //--- In the loop by the number of indicators added to the window,
      for(int i=0;i<change;i++)
        {
         //--- get the new indicator in the list by the index calculated from the end of the list
         int index=this.m_list_ind.Total()-(1+i);
         //--- and if failed to obtain the object, move on to the next one
         CWndInd *ind=this.m_list_ind.At(index);
         if(ind==NULL)
            continue;
         //--- call the method of sending an event to the control program chart
         this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_ADD);
        }
     }
   //--- If there are removed indicators
   if(change<0)
     {
      //--- Call the method of removing unnecessary indicators from the list
      this.IndicatorsDelete();
      //--- In the loop by the number of indicators removed from the window,
      for(int i=0;i<-change;i++)
        {
         //--- get a new removed indicator in the list of removed indicators by index calculated from the end of the list
         int index=this.m_list_ind_del.Total()-(1+i);
         //--- and if failed to obtain the object, move on to the next one
         CWndInd *ind=this.m_list_ind_del.At(index);
         if(ind==NULL)
            continue;
         //--- call the method of sending an event to the control program chart
         this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_DEL);
        }
     }
  }
//+------------------------------------------------------------------+

La lógica del método se describe en el listado de códigos. No olvidemos que los ciclos en los objetos de indicador recién añadidos a la lista de indicadores, o de los indicadores eliminados aquí (en esta implementación del procesamiento de eventos) no son necesarios. Podemos llamar directamente al método de envío de eventos. Pero el ciclo de búsqueda de los objetos de indicador recién añadidos a las listas puede ser necesario si la clase necesita ser reelaborada para monitorear correctamente los cambios en varios indicadores a la vez en un tick del temporizador, lo cual podemos implementar de forma programática, pero difícilmente de forma manual. Por el momento, procesaremos los cambios manuales en el gráfico, así que no necesitaremos estos ciclos adicionales, aunque podrían resultar útiles para futuras mejoras.

Método que crea y envía un evento de la ventana del gráfico al gráfico del programa de control:

//+------------------------------------------------------------------+
//| Create and send a chart window event                             |
//| to the control program chart                                     |
//+------------------------------------------------------------------+
void CChartWnd::SendEvent(ENUM_CHART_OBJ_EVENT event)
  {
   //--- If an indicator is added
   if(event==CHART_OBJ_EVENT_CHART_WND_IND_ADD)
     {
      //--- Get the last indicator object added to the list
      CWndInd *ind=this.GetLastAddedIndicator();
      if(ind==NULL)
         return;
      //--- Send the CHART_OBJ_EVENT_CHART_WND_IND_ADD event to the control program chart
      //--- pass the chart ID to lparam,
      //--- pass the chart window index to dparam,
      //--- pass the short name of the added indicator to sparam
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
     }
   //--- If the indicator is removed
   else if(event==CHART_OBJ_EVENT_CHART_WND_IND_DEL)
     {
      //--- Get the last indicator object added to the list of removed indicators
      CWndInd *ind=this.GetLastDeletedIndicator();
      if(ind==NULL)
         return;
      //--- Send the CHART_OBJ_EVENT_CHART_WND_IND_DEL event to the control program chart
      //--- pass the chart ID to lparam,
      //--- pass the chart window index to dparam,
      //--- pass the short name of a deleted indicator to sparam
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
     }
   //--- If the indicator has changed
   else if(event==CHART_OBJ_EVENT_CHART_WND_IND_CHANGE)
     {
      //--- Get the last indicator object added to the list of changed indicators
      CWndInd *ind=this.GetLastChangedIndicator();
      if(ind==NULL)
         return;
      //--- Send the CHART_OBJ_EVENT_CHART_WND_IND_CHANGE event to the control program chart
      //--- pass the chart ID to lparam,
      //--- pass the chart window index to dparam,
      //--- pass the short name of a changed indicator to sparam
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
     }
  }
//+------------------------------------------------------------------+

Toda la lógica del método se describe detalladamente en su listado. Si el lector tiene dudas, podrá plantearlas en los comentarios al artículo.

Ahora, vamos a mejorar la clase del objeto de gráfico en el archivo \MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqh.

Al igual que la clase del objeto de ventana del gráfico, haremos a esta clase heredera de la clase del objeto extendido de todos los objetos de la biblioteca. En la sección privada de la clase, declararemos los punteros a las listas de ventanas eliminadas del gráfico y de los indicadores eliminados y modificados, así como el método para crear de nuevo las ventanas del gráfico:

//+------------------------------------------------------------------+
//| Chart object class                                               |
//+------------------------------------------------------------------+
class CChartObj : public CBaseObjExt
  {
private:
   CArrayObj         m_list_wnd;                                  // List of chart window objects
   CArrayObj        *m_list_wnd_del;                              // Pointer to the list of chart window objects
   CArrayObj        *m_list_ind_del;                              // Pointer to the list of indicators removed from the indicator window
   CArrayObj        *m_list_ind_param;                            // Pointer to the list of changed indicators
   long              m_long_prop[CHART_PROP_INTEGER_TOTAL];       // Integer properties
   double            m_double_prop[CHART_PROP_DOUBLE_TOTAL];      // Real properties
   string            m_string_prop[CHART_PROP_STRING_TOTAL];      // String properties
   int               m_digits;                                    // Symbol's Digits()
   datetime          m_wnd_time_x;                                // Time for X coordinate on the windowed chart
   double            m_wnd_price_y;                               // Price for Y coordinate on the windowed chart
   
//--- Return the index of the array the (1) double and (2) string properties are actually located at
   int               IndexProp(ENUM_CHART_PROP_DOUBLE property)   const { return(int)property-CHART_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_CHART_PROP_STRING property)   const { return(int)property-CHART_PROP_INTEGER_TOTAL-CHART_PROP_DOUBLE_TOTAL; }

//--- The methods of setting parameter flags 
   bool              SetShowFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetBringToTopFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetContextMenuFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetCrosshairToolFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetMouseScrollFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetEventMouseWhellFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetEventMouseMoveFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetEventObjectCreateFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetEventObjectDeleteFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetForegroundFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShiftFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetAutoscrollFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetKeyboardControlFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetQuickNavigationFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetScaleFixFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetScaleFix11Flag(const string source,const bool flag,const bool redraw=false);
   bool              SetScalePTPerBarFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowTickerFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowOHLCFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowBidLineFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowAskLineFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowLastLineFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowPeriodSeparatorsFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowGridFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowObjectDescriptionsFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowTradeLevelsFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetDragTradeLevelsFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowDateScaleFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowPriceScaleFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetShowOneClickPanelFlag(const string source,const bool flag,const bool redraw=false);
   bool              SetDockedFlag(const string source,const bool flag,const bool redraw=false);

//--- The methods of setting property values
   bool              SetMode(const string source,const ENUM_CHART_MODE mode,const bool redraw=false);
   bool              SetScale(const string source,const int scale,const bool redraw=false);
   bool              SetModeVolume(const string source,const ENUM_CHART_VOLUME_MODE mode,const bool redraw=false);
   void              SetVisibleBars(void);
   void              SetWindowsTotal(void);
   void              SetFirstVisibleBars(void);
   void              SetWidthInBars(void);
   void              SetWidthInPixels(void);
   void              SetMaximizedFlag(void);
   void              SetMinimizedFlag(void);
   void              SetExpertName(void);
   void              SetScriptName(void);
   
//--- (1) Create, (2) check and re-create the chart window list
   void              CreateWindowsList(void);
   void              RecreateWindowsList(const int change);
//--- Add an extension to the screenshot file if it is missing
   string            FileNameWithExtention(const string filename);
   
public:

En la sección pública de la clase, escribimos y declaramos los nuevos métodos para trabajar con los eventos de la clase.
En el constructor paramétrico, transmitimos los punteros a las nuevas listas:

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_CHART_PROP_INTEGER property,long value)    { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_CHART_PROP_DOUBLE property,double value)   { this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_CHART_PROP_STRING property,string value)   { this.m_string_prop[this.IndexProp(property)]=value;    }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   long              GetProperty(ENUM_CHART_PROP_INTEGER property)         const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_CHART_PROP_DOUBLE property)          const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_CHART_PROP_STRING property)          const { return this.m_string_prop[this.IndexProp(property)];   }
//--- Return (1) itself, (2) the window object list and (3) the list of removed window objects
   CChartObj        *GetObject(void)                                             { return &this;               }
   CArrayObj        *GetList(void)                                               { return &this.m_list_wnd;    }

//--- Return the last (1) added (removed) chart window
   CChartWnd        *GetLastAddedWindow(void)                                    { return this.m_list_wnd.At(this.m_list_wnd.Total()-1);           }
   CChartWnd        *GetLastDeletedWindow(void)                                  { return this.m_list_wnd_del.At(this.m_list_wnd_del.Total()-1);   }
   
//--- Return (1) the last one added to the window, (2) the last one removed from the window and (3) the changed indicator,
   CWndInd          *GetLastAddedIndicator(const int win_num);
   CWndInd          *GetLastDeletedIndicator(void)                               { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1);   }
   CWndInd          *GetLastChangedIndicator(void)                               { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);}
//--- Return the indicator by index from the specified chart window
   CWndInd          *GetIndicator(const int win_num,const int ind_index);
   
//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_CHART_PROP_INTEGER property)           { return (property!=CHART_PROP_WINDOW_YDISTANCE ? true : false);  }
   virtual bool      SupportProperty(ENUM_CHART_PROP_DOUBLE property)            { return true; }
   virtual bool      SupportProperty(ENUM_CHART_PROP_STRING property)            { return true; }

//--- Get description of (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_CHART_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_CHART_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_CHART_PROP_STRING property);

//--- Display the description of object properties in the journal (full_prop=true - all properties, false - supported ones only)
   void              Print(const bool full_prop=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false);
//--- Return the object short name
   virtual string    Header(void);
   
//--- Create and send the chart event to the control program chart
   void              SendEvent(ENUM_CHART_OBJ_EVENT event);
  
//--- Compare CChartObj objects by a specified property (to sort the list by a specified chart object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CChartObj objects by all properties (to search for equal chart objects)
   bool              IsEqual(CChartObj* compared_obj) const;
   
//--- Update the chart object and its list of indicator windows
   void              Refresh(void);
   
//--- Constructors
                     CChartObj(){;}
                     CChartObj(const long chart_id,CArrayObj *list_wnd_del,CArrayObj *list_ind_del,CArrayObj *list_ind_param);
//+------------------------------------------------------------------+ 

Vamos a echar un vistazo a los nuevos métodos mejorados de la clase.

En el constructor paramétrico, dejamos que las variables que almacenan los punteros a los objetos de lista reciban los punteros a ellas transmitidos al método:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CChartObj::CChartObj(const long chart_id,CArrayObj *list_wnd_del,CArrayObj *list_ind_del,CArrayObj *list_ind_param) : m_wnd_time_x(0),m_wnd_price_y(0)
  {
   this.m_list_wnd_del=list_wnd_del;
   this.m_list_ind_del=list_ind_del;
   this.m_list_ind_param=list_ind_param;
//--- Set chart ID to the base object

al final del listado del constructor, asignamos a la lista de ventanas eliminadas del objeto de gráfico la bandera de clasificación según el número de la ventana del gráfico:

   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   this.m_list_wnd_del.Sort(SORT_BY_CHART_WINDOW_NUM);
   this.CreateWindowsList();
  }
//+------------------------------------------------------------------+

Método que retorna el último indicador añadido a la ventana:

//+------------------------------------------------------------------+
//| Return the last indicator added to the window                    |
//+------------------------------------------------------------------+
CWndInd *CChartObj::GetLastAddedIndicator(const int win_num)
  {
   CChartWnd *wnd=this.GetWindowByNum(win_num);
   return(wnd!=NULL ? wnd.GetLastAddedIndicator() : NULL);
  }
//+------------------------------------------------------------------+

Transmitimos al método el número de la ventana del gráfico de la que queremos obtener el último indicador añadido. Utilizando el método GetWindowByNum(), obtenemos la ventana de gráfico necesaria, y de ella, obtenemos el último indicador añadido. Si no hemos obtenido el objeto de la ventana del gráfico, retornamos NULL. No olvidemos que el método GetLastAddedIndicator() del objeto de ventana del gráfico también puede retornar NULL.

Método que retorna un indicador según el índice de la ventana de gráfico especificada:

//+------------------------------------------------------------------+
//| Return the indicator by index from the specified chart window    |
//+------------------------------------------------------------------+
CWndInd *CChartObj::GetIndicator(const int win_num,const int ind_index)
  {
   CChartWnd *wnd=this.GetWindowByNum(win_num);
   return(wnd!=NULL ? wnd.GetIndicatorByIndex(ind_index) : NULL);
  }
//+------------------------------------------------------------------+

Transmitimos al método el número de la ventana del gráfico de la cual queremos obtener el último indicador añadido, así como el índice del indicador en la lista de esta ventana.
Usando el método GetWindowByNum(), obtenemos la ventana de gráfico necesaria, y luego obtenemos de esta el indicador según su índice en la ventana de gráfico. Si no hemos obtenido el objeto de la ventana del gráfico, retornaremos NULL. Debemos tener en cuenta que el método GetIndicatorByIndex() del objeto de ventana del gráfico también puede retornar NULL.

En el método que actualiza el objeto gráfico y la lista de sus ventanas, sustituimos el método CreateWindowsList() por el nuevo método RecreateWindowsList():

//+------------------------------------------------------------------+
//| Update the chart object and its window list                      |
//+------------------------------------------------------------------+
void CChartObj::Refresh(void)
  {
   for(int i=0;i<this.m_list_wnd.Total();i++)
     {
      CChartWnd *wnd=this.m_list_wnd.At(i);
      if(wnd==NULL)
         continue;
      wnd.Refresh();
     }
   int change=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL)-this.WindowsTotal();
   if(change==0)
      return;
   this.RecreateWindowsList(change);
  }
//+------------------------------------------------------------------+

Usaremos el método para crear una lista de ventanas de gráficos CreateWindowsList() solo para crear ventanas al iniciarse el programa, mientras que el método para reconstruir las ventanas modificadas (que analizaremos a continuación), se usará al actualizar el objeto del gráfico.

Como ahora, al crear un nuevo objeto de ventana del gráfico, deberemos transmitir el nombre del símbolo del gráfico y los punteros a las listas, vamos a añadir su transmisión al constructor de la clase del objeto de ventana del gráfico al crear un nuevo objeto de ventana del gráfico en el método para crear una lista de ventanas del gráfico:

//+------------------------------------------------------------------+
//| Create the list of chart windows                                 |
//+------------------------------------------------------------------+
void CChartObj::CreateWindowsList(void)
  {
   //--- Clear the chart window list
   this.m_list_wnd.Clear();
   //--- Get the total number of chart windows from the environment
   int total=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL);
   //--- In the loop by the total number of windows
   for(int i=0;i<total;i++)
     {
      //--- Create a new chart window object
      CChartWnd *wnd=new CChartWnd(this.m_chart_id,i,this.Symbol(),this.m_list_ind_del,this.m_list_ind_param);
      if(wnd==NULL)
         continue;
      //--- If the window index exceeds 0 (not the main chart window) and it still has no indicator,
      //--- remove the newly created chart window object and go to the next loop iteration
      if(wnd.WindowNum()!=0 && wnd.IndicatorsTotal()==0)
        {
         delete wnd;
         continue;
        }
      //--- If the object was not added to the list, remove that object
      this.m_list_wnd.Sort();
      if(!this.m_list_wnd.Add(wnd))
         delete wnd;
     }
   //--- If the number of objects in the list corresponds to the number of windows on the chart,
   //--- write that value to the chart object property
   //--- If the number of objects in the list does not correspond to the number of windows on the chart,
   //--- write the number of objects in the list to the chart object property.
   int value=int(this.m_list_wnd.Total()==total ? total : this.m_list_wnd.Total());
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,value);
  }
//+------------------------------------------------------------------+

Método para comprobar los cambios en el número de ventanas del gráfico y crear de nuevo su lista:

//+------------------------------------------------------------------+
//| Check and re-create the chart window list                        |
//+------------------------------------------------------------------+
void CChartObj::RecreateWindowsList(const int change)
  {
//--- If the window is removed
   if(change<0)
     {
      //--- If the chart has only one window, this means we have only the main chart with no subwindows,
      //--- while the change in the number of chart windows indicates the removal of a symbol chart in the terminal.
      //--- This situation is handled in the collection class of chart objects - leave the method
      if(this.WindowsTotal()==1)
         return;
      //--- Get the last removed indicator from the list of removed indicators
      CWndInd *ind=this.m_list_ind_del.At(this.m_list_ind_del.Total()-1);
      //--- If managed to get the indicator,
      if(ind!=NULL)
        {
         //--- create a new chart window object
         CChartWnd *wnd=new CChartWnd();
         if(wnd!=NULL)
           {
            //--- Set the subwindow index from the last removed indicator object,
            //--- ID and the chart object symbol name for a new object
            wnd.SetWindowNum(ind.WindowNum());
            wnd.SetChartID(this.ID());
            wnd.SetSymbol(this.Symbol());
            //--- If failed to add the created object to the list of removed chart window objects, remove it
            if(!this.m_list_wnd_del.Add(wnd))
               delete wnd;
           }
        }
      //--- Call the method of sending an event to the control program chart and re-create the chart window list
      this.SendEvent(CHART_OBJ_EVENT_CHART_WND_DEL);
      this.CreateWindowsList();
      return;
     }
//--- If there are no changes, leave
   else if(change==0)
      return;

//--- If a window is added
   //--- Get the total number of chart windows from the environment
   int total=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL);
   //--- In the loop by the total number of windows
   for(int i=0;i<total;i++)
     {
      //--- Create a new chart window object
      CChartWnd *wnd=new CChartWnd(this.m_chart_id,i,this.Symbol(),this.m_list_ind_del,this.m_list_ind_param);
      if(wnd==NULL)
         continue;
      this.m_list_wnd.Sort(SORT_BY_CHART_WINDOW_NUM);
      //--- If the window index exceeds 0 (not the main chart window) and it still has no indicator,
      //--- or such a window is already present in the list or the window object is not added to the list
      //--- remove the newly created chart window object and go to the next loop iteration
      if((wnd.WindowNum()!=0 && wnd.IndicatorsTotal()==0) || this.m_list_wnd.Search(wnd)>WRONG_VALUE || !this.m_list_wnd.Add(wnd))
        {
         delete wnd;
         continue;
        }
      //--- If added the window, call the method of sending an event to the control program chart
      this.SendEvent(CHART_OBJ_EVENT_CHART_WND_ADD);
     }
   //--- If the number of objects in the list corresponds to the number of windows on the chart,
   //--- write that value to the chart object property
   //--- If the number of objects in the list does not correspond to the number of windows on the chart,
   //--- write the number of objects in the list to the chart object property.
   int value=int(this.m_list_wnd.Total()==total ? total : this.m_list_wnd.Total());
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,value);
  }
//+------------------------------------------------------------------+

Método que crea y envía un evento de gráfico al gráfico del programa de control:

//+------------------------------------------------------------------+
//| Create and send a chart event                                    |
//| to the control program chart                                     |
//+------------------------------------------------------------------+
void CChartObj::SendEvent(ENUM_CHART_OBJ_EVENT event)
  {
   //--- If a window is added
   if(event==CHART_OBJ_EVENT_CHART_WND_ADD)
     {
      //--- Get the last chart window object added to the list
      CChartWnd *wnd=this.GetLastAddedWindow();
      if(wnd==NULL)
         return;
      //--- Send the CHART_OBJ_EVENT_CHART_WND_ADD event to the control program chart
      //--- pass the chart ID to lparam,
      //--- pass the chart window index to dparam,
      //--- pass the chart symbol to sparam
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol());
     }
   //--- If the window is removed
   else if(event==CHART_OBJ_EVENT_CHART_WND_DEL)
     {
      //--- Get the last chart window object added to the list of removed windows
      CChartWnd *wnd=this.GetLastDeletedWindow();
      if(wnd==NULL)
         return;
      //--- Send the CHART_OBJ_EVENT_CHART_WND_DEL event to the control program chart
      //--- pass the chart ID to lparam,
      //--- pass the chart window index to dparam,
      //--- pass the chart symbol to sparam
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol());
     }
  }
//+------------------------------------------------------------------+

La lógica completa de los dos últimos métodos se describe por entero en sus listados y, a nuestro parecer, no necesita ninguna explicación.

El lector podrá formular cualquier pregunta sobre los métodos en los comentarios al artículo.

Vamos a modificar la clase de la colección de objetos de gráfico en el archivo \MQL5\Include\DoEasy\Collections\ChartObjCollection.mqh.

En la sección privada de la clase, declaramos los objetos de lista cuyos punteros hemos transmitido a los objetos de las ventanas del gráfico y a los objetos de indicador en las ventanas del gráfico:

//+------------------------------------------------------------------+
//| MQL5 signal object collection                                    |
//+------------------------------------------------------------------+
class CChartObjCollection : public CBaseObj
  {
private:
   CListObj                m_list;                                   // List of chart objects
   CListObj                m_list_del;                               // List of deleted chart objects
   CArrayObj               m_list_wnd_del;                           // List of deleted chart window objects
   CArrayObj               m_list_ind_del;                           // List of indicators removed from the indicator window
   CArrayObj               m_list_ind_param;                         // List of changed indicators
   int                     m_charts_total_prev;                      // Previous number of charts in the terminal
   //--- Return the number of charts in the terminal
   int                     ChartsTotal(void) const;
   //--- Return the flag indicating the existence of (1) a chart object and (2) a chart
   bool                    IsPresentChartObj(const long chart_id);
   bool                    IsPresentChart(const long chart_id);
   //--- Create a new chart object and add it to the list
   bool                    CreateNewChartObj(const long chart_id,const string source);
   //--- Find the missing chart object, create it and add it to the collection list
   bool                    FindAndCreateMissingChartObj(void);
   //--- Find a chart object not present in the terminal and remove it from the list
   void                    FindAndDeleteExcessChartObj(void);
public:

Aquí hemos declarado no los punteros a las listas, sino los propios objetos CArrayObj en los que almacenaremos todos los objetos de gráfico eliminados.

En la sección pública de la clase, declaramos los nuevos métodos necesarios para trabajar con los eventos de los objetos de gráfico:

public:
//--- Return (1) itself, (2) chart object collection list, (3) the list of deleted chart objects, 
//--- the list (4) of deleted window objects, (5) deleted and (6) changed indicators
   CChartObjCollection    *GetObject(void)                                 { return &this;                  }
   CArrayObj              *GetList(void)                                   { return &this.m_list;           }
   CArrayObj              *GetListDeletedCharts(void)                      { return &this.m_list_del;       }
   CArrayObj              *GetListDeletedWindows(void)                     { return &this.m_list_wnd_del;   }
   CArrayObj              *GetListDeletedIndicators(void)                  { return &this.m_list_ind_del;   }
   CArrayObj              *GetListChangedIndicators(void)                  { return &this.m_list_ind_param; }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj              *GetList(ENUM_CHART_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_CHART_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_CHART_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
//--- Return the number of chart objects in the list
   int                     DataTotal(void)                           const { return this.m_list.Total();    }
//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(void);
   void                    PrintShort(void);
//--- Constructor
                           CChartObjCollection();

//--- Return the list of chart objects by (1) symbol and (2) timeframe
   CArrayObj              *GetChartsList(const string symbol)              { return this.GetList(CHART_PROP_SYMBOL,symbol,EQUAL);      }
   CArrayObj              *GetChartsList(const ENUM_TIMEFRAMES timeframe)  { return this.GetList(CHART_PROP_TIMEFRAME,timeframe,EQUAL);}
//--- Return the pointer to the chart object (1) by ID and (2) by an index in the list
   CChartObj              *GetChart(const long id);
   CChartObj              *GetChart(const int index)                       { return this.m_list.At(index);                             }
//--- Return (1) the last added chart and (2) the last removed chart
   CChartObj              *GetLastAddedChart(void)                         { return this.m_list.At(this.m_list.Total()-1);             }
   CChartObj              *GetLastDeletedChart(void)                       { return this.m_list_del.At(this.m_list_del.Total()-1);     }
   
//--- Return (1) the last added window on the chart by chart ID and (2) the last removed chart window
   CChartWnd              *GetLastAddedChartWindow(const long chart_id); 
   CChartWnd              *GetLastDeletedChartWindow(void)                 { return this.m_list_wnd_del.At(this.m_list_wnd_del.Total()-1);}
   
//--- Return (1) the last one added to the specified window of the specified chart, (2) the last one removed from the window and (3) the changed indicator
   CWndInd                *GetLastAddedIndicator(const long chart_id,const int win_num);
   CWndInd                *GetLastDeletedIndicator(void)                   { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1);   }
   CWndInd                *GetLastChangedIndicator(void)                   { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);}
//--- Return the indicator by index from the specified window of the specified chart
   CWndInd                *GetIndicator(const long chart_id,const int win_num,const int ind_index);

//--- Return the chart ID with the program
   long                    GetMainChartID(void)                      const { return CBaseObj::GetMainChartID();                        }
//--- Create the collection list of chart objects
   bool                    CreateCollection(void);
//--- Update (1) the chart object collection list and (2) the specified chart object
   void                    Refresh(void);
   void                    Refresh(const long chart_id);

//--- (1) Open a new chart with the specified symbol and period, (2) close the specified chart
   bool                    Open(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool                    Close(const long chart_id);
   
//--- Create and send the chart event to the control program chart
   void                    SendEvent(ENUM_CHART_OBJ_EVENT event);
  
  };
//+------------------------------------------------------------------+

Vamos a analizar la implementación de los nuevos métodos y la mejora de los ya existentes.

En el constructor de la clase, eliminamos las nuevas listas y les asignamos las banderas de listas clasificadas:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CChartObjCollection::CChartObjCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list_ind_del.Clear();
   this.m_list_ind_del.Sort();
   this.m_list_ind_param.Clear();
   this.m_list_ind_param.Sort();
   this.m_list_wnd_del.Clear();
   this.m_list_wnd_del.Sort();
   this.m_list_del.Clear();
   this.m_list_del.Sort();
   this.m_list.Type(COLLECTION_CHARTS_ID);
   this.m_charts_total_prev=this.ChartsTotal();
  }
//+------------------------------------------------------------------+

En el método que actualiza la lista de colección de los objetos de gráfico, añadimos la llamada al método para enviar eventos al gráfico del programa de control:

//+------------------------------------------------------------------+
//| Update the collection list of chart objects                      |
//+------------------------------------------------------------------+
void CChartObjCollection::Refresh(void)
  {
   //--- In the loop by the number of chart objects in the list,
   for(int i=0;i<this.m_list.Total();i++)
     {
      //--- get the next chart object and
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      //--- update it
      chart.Refresh();
     }
   //--- Get the number of open charts in the terminal and
   int charts_total=this.ChartsTotal();
   //--- calculate the difference between the number of open charts in the terminal
   //--- and chart objects in the collection list
   int change=charts_total-this.m_list.Total();
   //--- If there are no changes, leave
   if(change==0)
      return;
   //--- If a chart is added in the terminal
   if(change>0)
     {
      //--- Find the missing chart object, create and add it to the collection list
      this.FindAndCreateMissingChartObj();
      //--- Get the current chart and return to it since
      //--- adding a new chart switches the focus to it
      CChartObj *chart=this.GetChart(GetMainChartID());
      if(chart!=NULL)
         chart.SetBringToTopON(true);
      for(int i=0;i<change;i++)
        {
         chart=m_list.At(m_list.Total()-(1+i));
         if(chart==NULL)
            continue;
         this.SendEvent(CHART_OBJ_EVENT_CHART_OPEN);
        }
     }
   //--- If a chart is removed in the terminal
   else if(change<0)
     {
      //--- Find an extra chart object in the collection list and remove it from the list
      this.FindAndDeleteExcessChartObj();
      for(int i=0;i<-change;i++)
        {
         CChartObj *chart=this.m_list_del.At(this.m_list_del.Total()-(1+i));
         if(chart==NULL)
            continue;
         this.SendEvent(CHART_OBJ_EVENT_CHART_CLOSE);
        }
     }
  }
//+------------------------------------------------------------------+

Los bloques de código en los que se llama al método SendEvent() son idénticos a los analizados previamente para la clase del objeto de ventana del gráfico, y se han creado exactamente con el mismo propósito: para una posible mejora en el futuro. Para procesar los cambios manuales en los gráficos del terminal, no necesitamos estos ciclos; podemos llamar directamente al método de envío de eventos.

En el método que crea un nuevo objeto de gráfico y lo añade a la lista, ahora debemos transmitir los punteros a las nuevas listas al crear un nuevo objeto de gráfico. Vamos a añadir estos cambios:

//+------------------------------------------------------------------+
//| Create a new chart object and add it to the list                 |
//+------------------------------------------------------------------+
bool CChartObjCollection::CreateNewChartObj(const long chart_id,const string source)
  {
   ::ResetLastError();
   CChartObj *chart_obj=new CChartObj(chart_id,this.GetListDeletedWindows(),this.GetListDeletedIndicators(),this.GetListChangedIndicators());
   if(chart_obj==NULL)
     {
      CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,true);
      return false;
     }
   this.m_list.Sort(SORT_BY_CHART_ID);
   if(!this.m_list.InsertSort(chart_obj))
     {
      CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,true);
      delete chart_obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Método que retorna la última ventana añadida al gráfico según el identificador del gráfico:

//+------------------------------------------------------------------+
//| Return the last added window to the chart by ID                  |
//+------------------------------------------------------------------+
CChartWnd* CChartObjCollection::GetLastAddedChartWindow(const long chart_id)
  {
   CChartObj *chart=this.GetChart(chart_id);
   if(chart==NULL)
      return NULL;
   CArrayObj *list=chart.GetList();
   return(list!=NULL ? list.At(list.Total()-1) : NULL);
  }
//+------------------------------------------------------------------+

Al método se transmite el identificador del gráfico cuya última ventana añadida debemos obtener.
Obtenemos el objeto gráfico según el identificador transmitido al método y tomamos del objeto gráfico recibido la lista de sus ventanas.
La última ventana añadida siempre se encuentra al final de la lista: retorna el objeto al final de la lista de ventanas del gráfico, o NULL, en caso de fallo.

Método que retorna el último indicador añadido a la ventana indicada del gráfico especificado:

//+------------------------------------------------------------------+
//| Return the last indicator added                                  |
//| to the specified window of the specified chart                   |
//+------------------------------------------------------------------+
CWndInd* CChartObjCollection::GetLastAddedIndicator(const long chart_id,const int win_num)
  {
   CChartObj *chart=this.GetChart(chart_id);
   return(chart!=NULL ? chart.GetLastAddedIndicator(win_num) : NULL);
  }
//+------------------------------------------------------------------+

El método se transmite el identificador del gráfico y el número de subventana cuyo último indicador añadido debemos obtener.
Obtenemos el objeto de gráfico según el identificador transmitido al método y, utilizando el método GetLastAddedIndicator() del objeto de gráfico, retornamos el puntero al último indicador añadido a la ventana especificada de este gráfico. En caso de fallo, se retornará NULL.

Método que retorna un indicador según el índice de la ventana indicada del gráfico especificado:

//+------------------------------------------------------------------+
//| Return the indicator by index                                    |
//| from the specified window of the specified chart                 |
//+------------------------------------------------------------------+
CWndInd* CChartObjCollection::GetIndicator(const long chart_id,const int win_num,const int ind_index)
  {
   CChartObj *chart=this.GetChart(chart_id);
   return(chart!=NULL ? chart.GetIndicator(win_num,ind_index) : NULL);
  }
//+------------------------------------------------------------------+

Transmitimos al método el identificador del gráfico, el número de subventana y el índice del indicador que debemos obtener.
Obtenemos el objeto de gráfico según el identificador transmitido al método
y, utilizando el método GetIndicator() del objeto de gráfico, retornamos el puntero al indicador de la ventana especificada de este gráfico. En caso de fallo, se retornará NULL.

En el método que encuentra y elimina de la lista un objeto de gráfico que no se encuentra en el terminal, añadimos un bloque de código que coloca el objeto de gráfico encontrado en la lista de gráficos eliminados:

//+-----------------------------------------------------------------------------+
//|Find a chart object not present in the terminal and remove it from the list  |
//+-----------------------------------------------------------------------------+
void CChartObjCollection::FindAndDeleteExcessChartObj(void)
  {
   for(int i=this.m_list.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      if(!this.IsPresentChart(chart.ID()))
        {
         chart=this.m_list.Detach(i);
         if(chart!=NULL)
           {
            if(!this.m_list_del.Add(chart))
               this.m_list.Delete(i);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Aquí, si el objeto de gráfico obtenido de la lista no se encuentra en el terminal de cliente, entonces, con la ayuda del método Detach() de la biblioteca estándar, eliminaremos este objeto de la lista y lo añadiremos a la lista de gráficos eliminados. Si el objeto eliminado de la lista no se ha podido colocar en la nueva lista, lo eliminaremos después para evitar pérdidas de memoria.

Método que crea y envía un evento de gráfico al gráfico del programa de control:

//+------------------------------------------------------------------+
//| Create and send a chart event                                    |
//| to the control program chart                                     |
//+------------------------------------------------------------------+
void CChartObjCollection::SendEvent(ENUM_CHART_OBJ_EVENT event)
  {
   //--- If a chart is added
   if(event==CHART_OBJ_EVENT_CHART_OPEN)
     {
      //--- Get the last chart object added to the list
      CChartObj *chart=this.GetLastAddedChart();
      if(chart==NULL)
         return;
      //--- Send the CHART_OBJ_EVENT_CHART_OPEN event to the control program chart
      //--- pass the chart ID to lparam,
      //--- pass the chart timeframe to dparam,
      //--- pass the chart symbol to sparam
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol());
     }
   //--- If a chart is removed
   else if(event==CHART_OBJ_EVENT_CHART_CLOSE)
     {
      //--- Get the last chart object added to the list of removed charts
      CChartObj *chart=this.GetLastDeletedChart();
      if(chart==NULL)
         return;
      //--- Send the CHART_OBJ_EVENT_CHART_CLOSE event to the control program chart
      //--- pass the chart ID to lparam,
      //--- pass the chart timeframe to dparam,
      //--- pass the chart symbol to sparam
      ::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol());
     }
  }
//+------------------------------------------------------------------+

Ya hemos descrito con detalle la lógica del método en el listado de códigos, así que no requerirá de explicaciones adicionales.


Monitoreando los eventos de los gráficos

Ya hemos creado la funcionalidad necesaria para monitorear algunos eventos de gráfico. Ahora, necesitaremos proporcionar acceso a esta desde el programa de control. Para ello, tenemos la clase de biblioteca principal CEngine. En ella, tendremos que registrar los nuevos métodos que dan acceso a los métodos de la clase de colección de objetos de gráfico que hemos escrito hoy. Como en el último artículo ya escribimos la funcionalidad que actualiza la colección de las listas de objetos de gráfico, disponemos de todo lo necesario para monitorear los eventos en el programa de control; solo necesitamos escribir en el asesor de prueba el procesamiento de los eventos entrantes de la biblioteca, en concreto, de la clase de colección de objetos de gráfico.

Sí, por el momento la biblioteca no puede monitorear los cambios en todas las propiedades de los gráficos, sus ventanas y los indicadores en estas, pero, como ya hemos logrado que todos los objetos sean herederos de la clase extendida del objeto básico de todos los objetos de la biblioteca (y esta ofrece automáticamente a sus herederos la funcionalidad de eventos), ahora solo tenemos que añadir los métodos para controlar las propiedades de los objetos. Este será el material para el próximo artículo. Ahora, vamos a añadir la conexión entre la nueva funcionalidad y el "mundo exterior"; después, pondremos a prueba lo que hemos implementado hoy.

En el archivo \MQL5\Include\DoEasy\Engine.mqh de la clase de objeto principal de la biblioteca, escribimos los métodos de acceso a los nuevos métodos de la colección de objetos de gráfico:

//--- Return the list of chart objects by (1) symbol and (2) timeframe
   CArrayObj           *GetListCharts(const string symbol)                             { return this.m_charts.GetChartsList(symbol);         }
   CArrayObj           *GetListCharts(const ENUM_TIMEFRAMES timeframe)                 { return this.m_charts.GetChartsList(timeframe);      }
//--- Return the list of removed (1) chart objects, (2) chart windows, (3) indicators in the chart window and (4) changed indicators in the chart window
   CArrayObj           *GetListChartsClosed(void)                                      { return this.m_charts.GetListDeletedCharts();        }
   CArrayObj           *GetListChartWindowsDeleted(void)                               { return this.m_charts.GetListDeletedWindows();       }
   CArrayObj           *GetListChartWindowsIndicatorsDeleted(void)                     { return this.m_charts.GetListDeletedIndicators();    }
   CArrayObj           *GetListChartWindowsIndicatorsChanged(void)                     { return this.m_charts.GetListChangedIndicators();    }

//--- Return (1) the specified chart object and (2) the chart object with the program
   CChartObj           *ChartGetChartObj(const long chart_id)                          { return this.m_charts.GetChart(chart_id);            }
   CChartObj           *ChartGetMainChart(void)                                        { return this.m_charts.GetChart(this.m_charts.GetMainChartID());}
//--- Reutrn the chart object of the last (1) open and (2) closed chart
   CChartObj           *ChartGetLastOpenedChart(void)                                  { return this.m_charts.GetLastAddedChart();           }
   CChartObj           *ChartGetLastClosedChart(void)                                  { return this.m_charts.GetLastDeletedChart();         }

//--- Return the object (1) of the last added window of the specified chart and (2) the last removed chart window
   CChartWnd           *ChartGetLastAddedChartWindow(const long chart_id)              { return this.m_charts.GetLastAddedChartWindow(chart_id);}
   CChartWnd           *ChartGetLastDeletedChartWindow(void)                           { return this.m_charts.GetLastDeletedChartWindow();   }
   
//--- Return (1) the last one added to the specified window of the specified chart, (2) the last one removed from the window and (3) the changed indicator
   CWndInd             *ChartGetLastAddedIndicator(const long id,const int win)        { return m_charts.GetLastAddedIndicator(id,win);      }
   CWndInd             *ChartGetLastDeletedIndicator(void)                             { return this.m_charts.GetLastDeletedIndicator();     }
   CWndInd             *ChartGetLastChangedIndicator(void)                             { return this.m_charts.GetLastChangedIndicator();     }
//--- Return the indicator by index from the specified window of the specified chart
   CWndInd             *ChartGetIndicator(const long chart_id,const int win_num,const int ind_index)
                          {
                           return m_charts.GetIndicator(chart_id,win_num,ind_index);
                          }

//--- Return the number of charts in the collection list
   int                  ChartsTotal(void)                                              { return this.m_charts.DataTotal();                   }

Todos los métodos recién añadidos retornan el resultado de la llamada a los métodos correspondientes de la colección de objetos de gráfico.

Simulación

Para las pruebas, tomaremos el asesor del artículo anterior y
lo guardaremos en la carpeta nueva \MQL5\Experts\TestDoEasy\Part71\ con el nuevo nombre TestDoEasyPart71.mq5.

Todo lo que tenemos que hacer es añadir el procesamiento de los nuevos códigos de evento al manejador de eventos de la biblioteca OnDoEasyEvent().
No tiene sentido analizar el código completo de la función: es demasiado voluminoso, y para hacer todo bien, deberíamos dividirlo en manejadores individuales de eventos para los diferentes objetos de biblioteca. Nos ocuparemos de esto mucho más tarde.
Ahora, echaremos un vistazo al bloque de código que debemos escribir en la función del asesor OnDoEasyEvent():

//--- Handling timeseries events
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- "New bar" event
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
        }
     }
     
//--- Handle chart events
   else if(idx>CHART_OBJ_EVENT_NO_EVENT && idx<CHART_OBJ_EVENTS_NEXT_CODE)
     {
      //--- "New chart opening" event
      if(idx==CHART_OBJ_EVENT_CHART_OPEN)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol());
         CChartObj *chart=engine.ChartGetLastOpenedChart();
         if(chart!=NULL)
           {
            string symbol=sparam;
            long chart_id=lparam;
            ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
            string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id;
            Print(DFUN,CMessage::Text(MSG_CHART_COLLECTION_CHART_OPENED),": ",header);
           }
        }
      //--- "Chart closure" event
      if(idx==CHART_OBJ_EVENT_CHART_CLOSE)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol());
         CChartObj *chart=engine.ChartGetLastClosedChart();
         if(chart!=NULL)
           {
            string symbol=sparam;
            long   chart_id=lparam;
            ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
            string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id;
            Print(DFUN,CMessage::Text(MSG_CHART_COLLECTION_CHART_CLOSED),": ",header);
           }
        }
      //--- "Adding a new window on the chart" event
      if(idx==CHART_OBJ_EVENT_CHART_WND_ADD)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol());
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string ind_name="";
         string symbol=sparam;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id+": ";
         
         CChartObj *chart=engine.ChartGetLastOpenedChart();
         if(chart!=NULL)
           {
            timeframe=chart.Timeframe();
            CChartWnd *wnd=engine.ChartGetLastAddedChartWindow(chart.ID());
            if(wnd!=NULL)
              {
               CWndInd *ind=wnd.GetLastAddedIndicator();
               if(ind!=NULL)
                  ind_name=ind.Name();
              }
           }
         Print(DFUN,header,CMessage::Text(MSG_CHART_OBJ_WINDOW_ADDED)," ",(string)win_num," ",ind_name);
        }
      //--- "Removing a window from the chart" event
      if(idx==CHART_OBJ_EVENT_CHART_WND_DEL)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol());
         CChartWnd *wnd=engine.ChartGetLastDeletedChartWindow();
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string symbol=sparam;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id+": ";
         Print(DFUN,header,CMessage::Text(MSG_CHART_OBJ_WINDOW_REMOVED)," ",(string)win_num);
        }
      //--- "Adding a new indicator to the chart window" event
      if(idx==CHART_OBJ_EVENT_CHART_WND_IND_ADD)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string ind_name=sparam;
         string symbol=NULL;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=NULL;
         
         CWndInd *ind=engine.ChartGetLastAddedIndicator(chart_id,win_num);
         if(ind!=NULL)
           {
            CChartObj *chart=engine.ChartGetChartObj(chart_id);
            if(chart!=NULL)
              {
               symbol=chart.Symbol();
               timeframe=chart.Timeframe();
               CChartWnd *wnd=chart.GetWindowByNum(win_num);
               if(wnd!=NULL)
                  header=wnd.Header();
              }
           }
         Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_ADDED)," ",ind_name);
        }
      //--- "Removing an indicator from the chart window" event
      if(idx==CHART_OBJ_EVENT_CHART_WND_IND_DEL)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string ind_name=sparam;
         string symbol=NULL;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=NULL;
         
         CWndInd *ind=engine.ChartGetLastDeletedIndicator();
         if(ind!=NULL)
           {
            CChartObj *chart=engine.ChartGetChartObj(chart_id);
            if(chart!=NULL)
              {
               symbol=chart.Symbol();
               timeframe=chart.Timeframe();
               CChartWnd *wnd=chart.GetWindowByNum(win_num);
               if(wnd!=NULL)
                  header=wnd.Header();
              }
           }
         Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_REMOVED)," ",ind_name);
        }
      //--- "Changing indicator parameters in the chart window" event
      if(idx==CHART_OBJ_EVENT_CHART_WND_IND_CHANGE)
        {
         //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name());
         ENUM_TIMEFRAMES timeframe=WRONG_VALUE;
         string ind_name=sparam;
         string symbol=NULL;
         long   chart_id=lparam;
         int    win_num=(int)dparam;
         string header=NULL;
         
         CWndInd *ind=NULL;
         CWndInd *ind_changed=engine.ChartGetLastChangedIndicator();
         if(ind_changed!=NULL)
           {
            ind=engine.ChartGetIndicator(chart_id,win_num,ind_changed.Index());
            if(ind!=NULL)
              {
               CChartObj *chart=engine.ChartGetChartObj(chart_id);
               if(chart!=NULL)
                 {
                  symbol=chart.Symbol();
                  timeframe=chart.Timeframe();
                  CChartWnd *wnd=chart.GetWindowByNum(win_num);
                  if(wnd!=NULL)
                     header=wnd.Header();
                 }
              }
           }
         Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_CHANGED)," ",ind_name," >>> ",ind.Name());
        }
     }
     
//--- Handling trading events

Para cada uno de los eventos procedentes de la colección de objetos de gráfico, hemos escrito en los comentarios un ejemplo de envío de este evento desde las clases de la biblioteca.
Este ejemplo muestra con claridad en cuál de los tres parámetros del manejador (lparam, dparam y sparam) recibimos los datos y cuáles son estos. Usando estos datos como base, se realiza la búsqueda de los objetos necesarios en la biblioteca y, utilizando estos, se genera un mensaje normal que se muestra en el diario. No implementaremos ningún otro procesamiento de eventos de la colección de objetos de gráfico en el asesor de prueba. Este ejemplo nos bastará para comprender cómo procesar los eventos entrantes para nuestros propios propósitos y necesidades.

Compilamos el asesor y lo iniciamos en el gráfico del símbolo.

Abrimos cualquier gráfico nuevo del instrumento; obtendremos en el diario un mensaje desde el manejador OnDoEasyEvent():

OnDoEasyEvent: Open chart: AUDNZD H4, ID 131733844391938634

Añadimos al gráfico abierto una nueva ventana de cualquier indicador de oscilador; obtendremos en el diario un mensaje desde el manejador OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H1, ID 131733844391938634: Added subwindow 1 Momentum(14)

Añadimos al gráfico abierto cualquier indicador dibujado en la ventana abierta; obtendremos en el diario un mensaje desde el manejador OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Added indicator AMA(14,2,30)

Cambiamos los parámetros del oscilador; obtendremos en el diario un mensaje desde el manejador OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Chart subwindow 1: Changed indicator Momentum(14) >>> Momentum(20)

Cambiamos los parámetros del indicador en la ventana principal; obtendremos en el diario un mensaje desde el manejador OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Changed indicator AMA(14,2,30) >>> AMA(20,2,30)

Eliminamos la ventana del oscilador; obtendremos en el diario dos mensajes desde el manejador OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634: Removed indicator Momentum(20)
OnDoEasyEvent: AUDNZD H1, ID 131733844391938634: Removed subwindow 1

Eliminamos el indicador de la ventana principal; obtendremos en el diario un mensaje desde el manejador OnDoEasyEvent():

OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Removed indicator AMA(20,2,30)

Cerramos la ventana del gráfico previamente abierta; obtendremos en el diario un mensaje desde el manejador OnDoEasyEvent():

OnDoEasyEvent: Closed chart: AUDNZD H4, ID 131733844391938634

Como podemos ver, todos los eventos se procesan correctamente y se envían al programa de control.

¿Qué es lo próximo?

En el próximo artículo, implementaremos el seguimiento automático de los cambios y el control de los cambios de las propiedades de todos los objetos del gráfico.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*Artículos de esta serie:

Otras clases en la biblioteca DoEasy (Parte 67): Clase de objeto de gráfico
Otras clases en la biblioteca DoEasy (Parte 68): Clase de objeto de ventana de gráfico y clases de objetos de indicador en la ventana del gráfico
Otras clases en la biblioteca DoEasy (Parte 69): Clases de colección de objetos de gráfico
Otras clases en la biblioteca DoEasy (Parte 70): Ampliación de la funcionalidad y actualización automática de la colección de objetos de gráfico

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/9360

Archivos adjuntos |
MQL5.zip (3962.05 KB)
Consejos de un programador profesional (parte II): Organizando el almacenamiento y el intercambio de parámetros entre el experto, los scripts y los programas externos Consejos de un programador profesional (parte II): Organizando el almacenamiento y el intercambio de parámetros entre el experto, los scripts y los programas externos
Consejos de un programador profesional sobre métodos, técnicas y herramientas auxiliares para facilitar la programación. En esta ocasión, hablaremos de los parámetros que podemos restaurar tras reiniciar (cerrar) el terminal. Todos los ejemplos son en realidad trozos del código operativo del proyecto Cayman del propio autor.
Otras clases en la biblioteca DoEasy (Parte 70): Ampliación de la funcionalidad y actualización automática de la colección de objetos de gráfico Otras clases en la biblioteca DoEasy (Parte 70): Ampliación de la funcionalidad y actualización automática de la colección de objetos de gráfico
En este artículo, ampliaremos la funcionalidad de los objetos de gráfico, organizaremos la navegación por los gráficos, crearemos capturas de pantalla, y también guardaremos plantillas y las aplicaremos a los gráficos. Asimismo, implementaremos la actualización automática de la colección de objetos de gráfico, sus ventanas y los indicadores en ellas.
Otras clases en la biblioteca DoEasy (Parte 72): Seguimiento y registro de parámetros de los objetos de gráfico en la colección Otras clases en la biblioteca DoEasy (Parte 72): Seguimiento y registro de parámetros de los objetos de gráfico en la colección
En el presente artículo, finalizaremos el trabajo con las clases de los objetos de gráfico y sus colecciones. Implementaremos el seguimiento automático del cambio de las propiedades de los gráficos y sus ventanas, y también el almacenamiento de los parámetros en las propiedades del objeto. Estas mejoras nos permitirán en el futuro crear una funcionalidad de eventos para la colección de gráficos al completo.
Swaps (parte I) : Bloqueo de posiciones y posiciones sintéticas Swaps (parte I) : Bloqueo de posiciones y posiciones sintéticas
En este artículo intentaremos expandir el concepto clásico de los métodos de swap en el comercio, y también hablaremos sobre por qué hemos llegado a la conclusión de que este concepto merece una atención especial y es absolutamente recomendable para el análisis y el estudio.