Other classes in DoEasy library (Part 71): Chart object collection events

Artyom Trishkin | 3 June, 2021

Contents


Concept

In the previous article, I introduced the auto update of some chart object properties and related objects — opening a new/closing an existing symbol chart (chart object), adding a new/closing an existing indicator window from the chart object, as well as adding a new/removing/changing an existing indicator in the chart window.

In the current article, I am going to create an event functionality for chart objects, chart window objects and indicator objects in the chart window. This functionality will allow us to send custom events to the control program chart when registering the object events considered above.

Working with charts is implemented with manual chart control in mind, i.e. when users make changes in charts manually or using control elements of their program, operations are performed step by step one object at a time. A programmatic change of several objects at once within a single timer tick may cause an incorrect definition of occurring events. Only the last event will be recorded at best. At worst, changed objects will be defined incorrectly. I will certainly consider such a probability, while writing the code, and insert the code allowing to batch change object parameters wherever possible. However, I am not going to fine tune and test programmatic simultaneous handling of multiple chart elements. I will return to the task if there are requests from the library users.


Improving library classes

Let's add new messages to the library. In \MQL5\Include\DoEasy\Data.mqh, add new message indices:

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

and message texts corresponding to newly added indices:

   {"Шаблон графика сохранён","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"},
   
  };
//+---------------------------------------------------------------------+


In the current article, we will handle some chart events. In order to track them and specify a certain event, create a new enumeration of possible chart events in \MQL5\Include\DoEasy\Defines.mqh:

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

When registering chart events specified in this enumeration, a custom event is sent to the program chart. This custom event is to contain the type of an occurred event using the appropriate constant from the enumeration. Next, the program analyzes the event code and handles it accordingly.

While implementing the event handling, I bumped into an unexpected challenge. It turned out difficult to define an index of an already deleted chart subwindow and the indicator inside it. To define them more conveniently, I decided to implement a new property for the indicator object in the chart window — an index of the window it is located in.

All deleted charts, chart windows and appropriate indicators (objects describing them) are to be stored in special lists allowing us to receive a previously deleted object at any time. This is the object we are able to get an indicator window index from (if the indicator was deleted).

Add a new property constant for an indicator object in the chart window to the enumeration of the chart object integer properties:

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

Since the number of integer properties has increased, increase their number from 66 to 67.

Add sorting by the chart window index to the enumeration of chart object sorting criteria:

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


As mentioned above, I will use special lists to store object copies of the removed indicators, windows and charts. We will need access to these lists in each chart object, its windows and indicators belonging to the windows. To avoid storing custom lists in each object class and arrange access to them from other objects requiring data on a certain deleted object, declare all these lists in the chart object collection class (the class provides access to all chart objects), while pointers to the lists are passed to other objects (chart, chart window, indicator in the chart window). Thus, each object stored in the collection has access to all lists.

This imposes some restrictions on the use of these lists within collection objects, other than the collection object itself. We cannot delete objects in the lists, reset the lists while working inside all collection objects, except for the collection itself, and modify pointers to the lists in any way. However, if we keep in mind this feature of using the pointer to the list (in fact, applying it in read-only mode), arranging the access to the list from different objects is simplified since passing the pointer to the list object is sufficient to read its contents.

Let's improve the indicator object class file in the chart window and the chart window object file (both classes are located in \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh).

In the private class section CWndInd, declare the variable for storing the subwindow index the indicator is located in, while in the public section of the class, write the methods for setting and returning all object properties (previously, we were able to set a single property — the indicator index in the window list):

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

In addition, add passing the chart subwindow index the indicator is located in and assigning the passed value to the appropriate variable to the parametric constructor.

Now when creating such an object, we should additionally specify the index of the subwindow featuring the indicator the object is created for. Thus, we are able to find the window featuring the indicator after removing the indicator and the window. This will make it easier for us to find the index of an already absent window in order to understand which of them contained the indicator removed along with the window, while their objects are located in the lists of removed chart objects.

Let's make changes in the chart window object as well. In the private section of the class, declare the pointers to the lists of indicators changed in the window and removed from it, as well as declare the variable for storing a symbol of the chart the window belongs to. Declare the method returning the flag indicating the presence of the indicator from the window in the chart object list, the method returning the indicator object present in the list but not present on the chart window and the method for checking the changes of parameters of indicators present in the window:

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

Now the class is inherited from the expanded base class of all library objects providing an event functionality that can be easily done for each of such objects.

In the public section of the class, namely, in the method returning the flag indicating that an object supports a specified property, add yet another supported property — chart symbol. Move the implementation of the method returning the string property description beyond the class body (and consider it further):

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

In the public section of the class, declare the method for creating and sending a chart event to the control program chart. The class parametric constructor will now receive the chart symbol name and the pointers to the lists of removed and changed indicators of the window. Also, declare the class destructor:

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

Next, add other necessary methods whose purpose is described in the listing comments:

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


Let's consider the implementation of new and improved methods in detail.

In the initialization list of the parametric class constructor, assign the value passed in the parameters to m_symbol, while in the class body, assign the values of pointers (passed to the method) to the variable pointers to the lists:

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

In the previous article (namely, in the methods providing data on indicators attached to the window), the indicator handles were taken from the indicator list. After reading the indicator data, the handle was immediately released. As a result, a new handle was created each time for the same indicator (due to my incorrect understanding of Help information — the indicator handle should be released only when there is really no need for it anymore, namely when the program operation is completed, and not immediately after receiving data by the handle, while the indicator is to be further used by the program). I will fix this in the current article — handles of all indicators are to be released in the class destructor.

The class destructor:

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

Here in the loop by the list of window indicator objects, get the next object, release the indicator handle set in the object properties and remove the object itself.

The virtual method of comparing two objects by a specified property receives comparing by window index and by chart symbol:

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

This is necessary to sort and search objects in the lists by chart symbol and the newly implemented object property — window index.

Implementation of the method returning the description of the object string property is moved outside the class body:

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

Like in all similar methods of all library objects, here we create a text description of the property passed to the method:
if this is a "Chart symbol" property, return its string description, return an empty string for any other property.
If the property has the "property not supported" flag, the string with the same contents is returned.

In the method creating the list of indicators attached to the window, remove the string, in which the handle of the currently selected indicator is released (the reason was discussed above):

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

the string, in which we create a new indicator object in the chart window, receives the ability to pass the current window index class to the constructor:

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

Now each indicator object in the chart window will "know" the window it is located in.

The same is done to the method adding new indicators to the list:

Remove the string

      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

and add passing the window index when creating a new indicator object in the chart window:

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

In the method returning the flag of the indicator presence from the list in the window, get rid of the string as well:

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

Now we do not free the indicator handle in this method as well:

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

The method checking changes in the parameters of existing indicators:

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

The entire method logic is fully described in its listing. Indicators in the chart window are identified by their short names. If an indicator features a changed parameter, its short name should be changed (this is the case for correctly made custom indicators, while standard indicators take this feature into account). Therefore, the search for a changed indicator here is based on the fact that the index of the changed indicator in the window remains the same, unlike its short name. So, if we detect an indicator present in the window object list but not in the client terminal chart window, we need to check if the indices match — if the indicator and the detected object have the same index (when deleting the indicator, the indices of other indicators in the window are altered), then this is the indicator with changed parameters we were looking for.

Since the indicators removed from the window are to be stored in a special list (for their subsequent search when handling events), we need to improve the method removing indicators not present in the window from the list:

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

The method logic is described in the code listing in detail. I believe, it requires no special explanations. If you have any questions, feel free to ask them in the comments below.

The method returning the flag of the presence of an indicator from the window in the list:

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

The method allows using a short name to define the presence of an appropriate indicator object in the window object list.
Here we create a temporary indicator object and assign a short name passed to the method. Set the flag of sorting by indicator name to the list of indicator objects and get the indicator index with such a name in the list. Make sure to delete the temporary object and return the flag indicating that the index of a detected indicator in the list exceeds -1 (if the indicator with such a name is found, its index exceeds -1).

The method returning an indicator object present in the list but not in the chart window:

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

Here, in the loop by all indicator objects in the list, get the next indicator object. If there is no such indicator on the chart, return the pointer to the detected indicator object. Otherwise, return NULL.

The method returning the indicator object from the object list by the indicator index in the chart window list:

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

Here, we create a temporary indicator object and assign the index passed to the method. Set the flag of sorting by indicator index on a chart to the list of indicator objects and get the indicator index in the window object list with such an index in the chart window list. Make sure to delete the temporary object and return the pointer to the object by a specified index in the list of indicator objects.
If the object is not found, the Search() method returns -1, while the At() method returns NULL in case of such index value. Thus, the method returns either a pointer to the detected object in the list, or NULL, if the indicator object with the specified index in the chart window is not present in the list.

The method returning the window indicator object from the list by handle:

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

The method is identical to the one considered above, except that it receives a value of a handle of a necessary indicator object. Thus, the list is sorted by the "indicator handle" property, so that the search in the list is performed by this object property.

Improve the method updating data by indicators attached to the window for sending custom events to the control program chart in case of any changes in the number of their parameters or in the parameters themselves:

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

The method logic is described in the code listing. I should note that the loops by newly added indicator objects in the indicator (or removed indicator) list are not needed in the current implementation of event handling. Now it is possible to immediately call the method of sending events. But the loop of searching for indicator objects newly added to the lists may be necessary in case we need to re-arrange the class for a correct tracking of changes of multiple indicators at once within a single timer tick. It is much easier to implement that programmatically but not manually. Currently, I am implementing handling manual changes on the chart. Therefore, I do not need these loops for now, although they may turn out to be of use during the future development.

The method creating and sending a chart window event to the control program chart:

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

The entire method logic is fully described in its listing. If you have any questions, feel free to ask them in the comments below.

Now let's improve the chart object class in \MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqh.

Similar to the chart window object class, make this class a descendant of the class of the extended object of all library objects, while in the private section of the class, declare the pointers to removed chart windows, removed and changed indicators, as well as the method for re-creating chart windows:

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

In the public section of the class, write and declare new methods for working with class events.
Pass the pointers to new lists to the parametric constructor:

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

Let's consider new and improved class methods.

In the parametric constructor, let the variables storing the pointers to list objects receive the pointers to them passed to the method:

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

At the end of the constructor listing, set the flag of sorting by chart window index to the list of removed chart object windows:

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

The method returning the last indicator added to the window:

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

The method receives the window index of the chart, from which we want to get the last added indicator. Use the GetWindowByNum() method to get the required chart window. In turn, get the last added indicator from that chart window. If failed to get the chart window object, return NULL. Keep in mind that the GetLastAddedIndicator() chart window object method can also return NULL.

The method returning the indicator by index from the specified chart window:

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

The method receives the chart window index, from which we want to get the last added indicator, and the indicator index in the window list.
Use the GetWindowByNum() method to get the required chart window and get the indicator by its index in the chart window. If failed to get the chart window object, return NULL. Keep in mind that the GetIndicatorByIndex() chart window object method can also return NULL.

In the method updating the chart object and the list of its windows, replace the CreateWindowsList() method with the new method 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);
  }
//+------------------------------------------------------------------+

The method for creating the list of chart windows CreateWindowsList() is to be used only for building windows when launching the program. The method for re-constructing changed windows (considered below) is to be used when updating the chart object.

Now, when creating a new chart window object, we need to pass the chart symbol name and the pointers to the lists to it. So, let's implement passing them to the chart window object class constructor when creating a new chart window object in the method of creating the chart window list:

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

The method for checking the changes of chart windows and re-creating their list:

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

The method creating and sending a chart event to the control program chart:

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

The entire logic of the last two methods is completely described in their listings and requires no explanations.

If you have any questions related to the methods, feel free to ask them in the comments.

Improve the chart object collection class in \MQL5\Include\DoEasy\Collections\ChartObjCollection.mqh.

In the private section of the class, declare the list objects, the pointers to which were passed to the chart window and indicator objects in the chart windows:

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

Instead of the pointers to the lists, here we declare the CArrayObj objects, in which we will store all deleted chart objects.

In the public section of the class, declare new methods required for working with chart object events:

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

Let's consider the implementation of new methods and improvement of the exisiting ones.

In the class constructor, clear the new lists and set the sorted list flags to them:

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

In the method declaring the chart object collection list, add calling the method of sending events to the control program chart:

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

The code blocks, in which the SendEvent() method is called, are identical to the ones previously considered for the chart window object class and are also implemented with their possible future improvement in mind. The loops are not required to handle manual changes in terminal charts — we can call the method of sending events immediately.

In the method creating a new chart object and adding it to the list, it is now possible to pass the pointers to new lists when creating a new chart object. Let's add these changes:

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

The method returning the last added window to the chart by chart ID :

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

The method receives the ID of the chart whose last added window we want to get.
Get the chart object by the ID passed to the method and retrieve the list of its windows.
The last added window is always located at the end of the listreturn the object at the very end of the list of chart windows or NULL if failed to do that.

The method returning the last indicator added to the specified window of a specified chart:

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

The method receives the chart ID and the number of the subwindow whose last added indicator we want to get.
Get the chart object by the ID passed to the method and use the GetLastAddedIndicator() chart object method to return the pointer to the last indicator added to the specified chart window. If failed, return NULL.

The method returning the indicator by index from the specified window of the specified chart:

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

The method receives the chart ID, subwindow index and indicator index we want to get.
Get the chart object by the ID passed to the method
and use the GetIndicator() chart object method to return the pointer to the indicator from the specified chart window. If failed, return NULL.

In the method, which finds and removes a chart object not present in the terminal from the list, add the code block placing the detected chart object to the list of removed charts:

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

Here, if the chart object obtained from the list is not present in the client terminal, use the Detach() standard library method to remove the object from the list and add it to the list of removed charts. If failed to place the removed object to the new list, delete it to avoid a memory leak.

The method creating and sending a chart event to the control program chart:

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

The method logic is described in the listing in detail. I believe, it requires no explanations.


Track chart events

I have created the functionality for tracking some chart events. Now we need to provide the control program with access to it. To achieve this, I will use the CEngine library main class. It should feature the new methods providing access to the chart object collection class methods I have implemented in the current article. Since we already have the functionality updating the chart object collection list described in the previous article, all is ready for tracking events in the control program. We only need to implement handling incoming events (namely, events arriving from the chart object collection class) from the library in the test EA.

The library is not yet able to track changes in all chart properties, their windows and indicators. However, since all objects are already descendants of the extended class of the base object of all library objects (which automatically endows its descendants with event functionality), then all we have to do is add the methods for managing object properties. I will leave it for the next article. Now, let's connect the new functionality with the "outside world" and test all I have implemented in the current article.

In the \MQL5\Include\DoEasy\Engine.mqh file of the library main object class, add the methods of accessing the new methods of the chart object collection:

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

All newly added methods return the result of calling the appropriate chart object collection methods.

Test

To perform the test, I will use the EA from the previous article and save it in \MQL5\Experts\TestDoEasy\Part71\ as TestDoEasyPart71.mq5.

All we need to do is add handling new event codes to the OnDoEasyEvent() library event handler.
There is no point in considering the full function code. It is large and requires splitting into separate handlers of events from different library objects. I will deal with this much later.
Now let's consider the code block to be added to the OnDoEasyEvent() EA function:

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

Each of the events arriving from the chart object collection features an example of sending that event from the library classes in the comments.
This example clearly shows what kind of data we get in each of the three handler parameters (lparam, dparam and sparam). This data is used to search for the necessary objects in the library. It is also used as a basis when creating a common message to be shown in the journal. I will not implement another handling of events from the chart object collection in the test EA. This example is quite sufficient to understand how to handle incoming events for your own needs.

Compile the EA and launch it on a symbol chart.

Open a new symbol chart — get the following journal message from the OnDoEasyEvent() handler:

OnDoEasyEvent: Open chart: AUDNZD H4, ID 131733844391938634

Add a new window of any oscillator to the open chart — get the following journal message from the OnDoEasyEvent() handler:

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

Add any indicator drawn in the main window to the open chart — get the following journal message from the OnDoEasyEvent() handler:

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

Change the oscillator parameters — get the following journal message from the OnDoEasyEvent() handler:

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

Change the indicator parameters in the main window — get the following journal message from the OnDoEasyEvent() handler:

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

Remove the oscillator window — get the following two journal messages from the OnDoEasyEvent() handler:

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

Remove the indicator from the main window — get the following journal message from the OnDoEasyEvent() handler:

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

Close the previously opened chart window — get the following journal message from the OnDoEasyEvent() handler:

OnDoEasyEvent: Closed chart: AUDNZD H4, ID 131733844391938634

As we can see, all events are handled correctly and sent to the control program.

What's next?

In the next article, I will implement auto tracking of changes and managing the properties of all chart objects.

All files of the current version of the library are attached below together with the test EA file for MQL5 for you to test and download.
Leave your questions and suggestions in the comments.

Back to contents

*Previous articles within the series:

Other classes in DoEasy library (Part 67): Chart object class
Other classes in DoEasy library (Part 68): Chart window object class and indicator object classes in the chart window
Other classes in DoEasy library (Part 69): Chart object collection class
Other classes in DoEasy library (Part 70): Expanding functionality and auto updating the chart object collection