DoEasy. Controls (Part 26): Finalizing the ToolTip WinForms object and moving on to ProgressBar development

Artyom Trishkin | 12 January, 2023

Contents


Concept

In the previous article, I started the development of the ToolTip control. The tooltip should appear after a short pause when we hover over an object. The previously displayed tooltip should be hidden. If we leave the cursor in the same position after the tooltip appears, then it should disappear after a certain time. In the previous article, I made the tooltip appear immediately after hovering over the object it was assigned to. In the current article, I will continue to work on the behavior of the tooltip and make it more consistent with the behavior of programs compiled in MS Visual Studio. When hovering over an object, there should be a pause, after which the tooltip appears smoothly on the screen. If we remove the cursor from the object, the tooltip is hidden. When leaving the cursor on the object, the tooltip disappears smoothly after some time. To implement this behavior, we will need to use the timer created for the graphical elements of the collection. Each object will have a virtual timer event handler defined. The implementation of the timer should be carried out specifically in the classes of those objects that are to be processed in it.

In order for the library to understand which objects should be processed in the timer and which should not, I will create a list of active objects. It will contain the pointers to those objects that need to be processed in the timer. This will make it easier to keep track of the objects we need to process and save us from constantly scanning all the graphical elements in a loop. The loop is to be only on the objects added to the list. This approach will make it possible to animate the graphical interface. The objects will not only respond to interaction with the mouse, but also use the animation sequence specified for them for visual effects. We can easily create and use even ordinary animated icons in this way. It will be sufficient just to create an object with animation frames and place it in the list of active library objects, while in the timer event handler of such an object, we will simply alternate the animation frames created during the object construction. Today I will create a timer event handler for the ToolTip control. This handler will implement the above behavior of the tooltip. Since this behavior implies interaction with the mouse, we will work with them both in the event handler of the collection of graphical elements and in the collection timer.

After finalizing the ToolTip WinForms object, I will start the development of the ProgressBar control. I will create only a static version of the object - its properties and appearance. Its operation will be implemented in the next article.


Improving library classes

In \MQL5\Include\DoEasy\Defines.mqh, add timer parameters for graphical elements on canvas:

//--- Parameters of the graphical objects collection timer
#define COLLECTION_GRAPH_OBJ_PAUSE        (250)                   // Graphical objects collection timer pause in milliseconds
#define COLLECTION_GRAPH_OBJ_COUNTER_STEP (16)                    // Graphical objects timer counter increment
#define COLLECTION_GRAPH_OBJ_COUNTER_ID   (10)                    // Graphical objects timer counter ID
//--- Parameters of the timer for the collection of graphical elements on canvas
#define COLLECTION_GRAPH_ELM_PAUSE        (16)                    // Graphical elements collection timer pause in milliseconds
#define COLLECTION_GRAPH_ELM_COUNTER_STEP (16)                    // Graphical elements timer counter increment
#define COLLECTION_GRAPH_ELM_COUNTER_ID   (11)                    // Graphical elements timer counter ID

//--- Collection list IDs

Each collection of library objects has its own timer handler with its own set of parameters - pause, timer counter increment and its ID. I have added exactly the same parameters here for the new timer.

Add default values for the new ProgressBar control:

#define CLR_DEF_CONTROL_HINT_BACK_COLOR               (C'0xFF,0xFF,0xE1')  // Hint control background color
#define CLR_DEF_CONTROL_HINT_BORDER_COLOR             (C'0x76,0x76,0x76')  // Hint control frame color
#define CLR_DEF_CONTROL_HINT_FORE_COLOR               (C'0x5A,0x5A,0x5A')  // Hint control text color

#define CLR_DEF_CONTROL_PROGRESS_BAR_BACK_COLOR       (C'0xF0,0xF0,0xF0')  // ProgressBar control background color
#define CLR_DEF_CONTROL_PROGRESS_BAR_BORDER_COLOR     (C'0xBC,0xBC,0xBC')  // ProgressBar control frame color
#define CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR       (C'0x00,0x78,0xD7')  // ProgressBar control text color
#define CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR        (C'0x06,0xB0,0x25')  // ProgressBar control progress line color


Some graphical control will require the elements of their visual effects. For example, the progress line is to have a glare that runs along the bar, as it is implemented in Windows. The object that implements the glare will be superimposed on top of the bar and move along it. We need to set the object type in the list of the library object types:

//+------------------------------------------------+
//| List of library object types                   |
//+------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Graphics
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                       // "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                          // Form object type
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // "Form for managing pivot points of graphical object" object type
   OBJECT_DE_TYPE_GSHADOW,                                        // Shadow object type
   OBJECT_DE_TYPE_GGLARE,                                         // Glare object type
   //--- WinForms
   OBJECT_DE_TYPE_GWF_BASE,                                       // WinForms Base object type (base abstract WinForms object)
   OBJECT_DE_TYPE_GWF_CONTAINER,                                  // WinForms container object type
   OBJECT_DE_TYPE_GWF_COMMON,                                     // WinForms standard control object type
   OBJECT_DE_TYPE_GWF_HELPER,                                     // WinForms auxiliary control object type
//--- Animation


Add new types of graphical elements on the canvas to the list of graphic element types:

//+------------------------------------------------+
//| The list of graphical element types            |
//+------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_GLARE_OBJ,                      // Glare object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                    // Panel object underlay
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms Base
   //--- 'Container' object types are to be set below
   GRAPH_ELEMENT_TYPE_WF_CONTAINER,                   // Windows Forms container base object
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
   GRAPH_ELEMENT_TYPE_WF_GROUPBOX,                    // Windows Forms GroupBox
   GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                 // Windows Forms TabControl
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,             // Windows Forms SplitContainer
   //--- 'Standard control' object types are to be set below
   GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,                 // Windows Forms base standard control
   GRAPH_ELEMENT_TYPE_WF_LABEL,                       // Windows Forms Label
   GRAPH_ELEMENT_TYPE_WF_BUTTON,                      // Windows Forms Button
   GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                    // Windows Forms CheckBox
   GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,                 // Windows Forms RadioButton
   GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,           // Base list object of Windows Forms elements
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                    // Windows Forms ListBox
   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             // Windows Forms ButtonListBox
   GRAPH_ELEMENT_TYPE_WF_TOOLTIP,                     // Windows Forms ToolTip
   GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,                // Windows Forms ProgressBar
   //--- Auxiliary elements of WinForms objects
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,               // Windows Forms ListBoxItem
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                   // Windows Forms TabField
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,       // Windows Forms SplitContainerPanel
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,                // Windows Forms ArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,             // Windows Forms UpArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,           // Windows Forms DownArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,           // Windows Forms LeftArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,          // Windows Forms RightArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,        // Windows Forms UpDownArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,        // Windows Forms LeftRightArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_SPLITTER,                    // Windows Forms Splitter
   GRAPH_ELEMENT_TYPE_WF_HINT_BASE,                   // Windows Forms HintBase
   GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,              // Windows Forms HintMoveLeft
   GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,             // Windows Forms HintMoveRight
   GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,                // Windows Forms HintMoveUp
   GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,              // Windows Forms HintMoveDown
   GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,            // Windows Forms BarProgressBar
  };
//+------------------------------------------------------------------+


A control (here it is ToolTip) can have multiple states when interacting with the mouse. Since the object is animated, its behavior and state should be described. It may wait for the fading in to start, be in the fading in state, wait for the fading out to begin, be in the fading out state, or it may be in its normal state.
Let's describe its states in the new control display state list:

//+------------------------------------------------+
//| The list of predefined icons                   |
//+------------------------------------------------+
enum ENUM_CANV_ELEMENT_TOOLTIP_ICON
  {
   CANV_ELEMENT_TOOLTIP_ICON_NONE,                    // None
   CANV_ELEMENT_TOOLTIP_ICON_INFO,                    // Info
   CANV_ELEMENT_TOOLTIP_ICON_WARNING,                 // Warning
   CANV_ELEMENT_TOOLTIP_ICON_ERROR,                   // Error
   CANV_ELEMENT_TOOLTIP_ICON_USER,                    // User
  };
//+------------------------------------------------+
//| List of control display states                 |
//+------------------------------------------------+
enum ENUM_CANV_ELEMENT_DISPLAY_STATE
  {
   CANV_ELEMENT_DISPLAY_STATE_NORMAL,                 // Normal
   CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN,        // Wait for fading in
   CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN,        // Fading in
   CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN,      // Fading in end
   CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT,       // Wait for fading out
   CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT,       // Fading out
   CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT,     // Fading out end
   CANV_ELEMENT_DISPLAY_STATE_COMPLETED,              // Complete processing
  };
//+------------------------------------------------------------------+

Such states can be applied to other controls. Animation sequences can be created for them later. Besides, it is not necessary for an object to fade in or out. The description of an object fading in can be equated to expanding the drop-down list, while the state of gradual fading out (attenuation) - to the state of collapse. However, we can always expand this list later.

ProgressBar can have three progress bar drawing styles. Let's list them in the enumeration:

//+------------------------------------------------+
//| List of ProgressBar element styles             |
//+------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE
  {
   CANV_ELEMENT_PROGRESS_BAR_STYLE_BLOCKS,            // Segmented blocks
   CANV_ELEMENT_PROGRESS_BAR_STYLE_CONTINUOUS,        // Continuous bar
   CANV_ELEMENT_PROGRESS_BAR_STYLE_MARQUEE,           // Continuous scrolling
  };
//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+

I will implement only the continuous bar here.


If animated display is implemented for an object, its state should be saved and obtained from its properties.
Let's add the new graphical element properties to the enumeration of integer properties of the graphical element on canvas and increase the total number of integer properties from 129 to 138:

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Element ID
   CANV_ELEMENT_PROP_TYPE,                            // Graphical element type

   //---...
   //---...

   CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,           // Upper edge area width
   CANV_ELEMENT_PROP_DISPLAYED,                       // Non-hidden control display flag
   CANV_ELEMENT_PROP_DISPLAY_STATE,                   // Control display state
   CANV_ELEMENT_PROP_DISPLAY_DURATION,                // Control display duration
   CANV_ELEMENT_PROP_GROUP,                           // Group the graphical element belongs to
   CANV_ELEMENT_PROP_ZORDER,                          // Priority of a graphical object for receiving the event of clicking on a chart

   //---...
   //---...

   CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,              // Tooltip in the form of a "cloud"
   CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,              // Fade when showing/hiding a tooltip
   CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM,            // The upper bound of the range ProgressBar operates in
   CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM,            // The lower bound of the range ProgressBar operates in
   CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,               // ProgressBar increment needed to redraw it
   CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE,              // ProgressBar style
   CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,              // Current ProgressBar value from Min to Max
   CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED, // Progress bar animation speed in case of Marquee style
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (138)         // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Add new properties to the enumeration of possible criteria of sorting graphical elements on canvas:

//+------------------------------------------------------------------+
//| Possible sorting criteria of graphical elements on the canvas    |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Sort by integer properties
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Sort by element ID
   SORT_BY_CANV_ELEMENT_TYPE,                         // Sort by graphical element type

   //---...
   //---...

   SORT_BY_CANV_ELEMENT_BORDER_TOP_AREA_WIDTH,        // Sort by upper edge area width
   SORT_BY_CANV_ELEMENT_DISPLAYED,                    // Sort by non-hidden control display flag
   SORT_BY_CANV_ELEMENT_DISPLAY_STATE,                // Sort by control display state
   SORT_BY_CANV_ELEMENT_DISPLAY_DURATION,             // Sort by control display duration
   SORT_BY_CANV_ELEMENT_GROUP,                        // Sort by a group the graphical element belongs to
   SORT_BY_CANV_ELEMENT_ZORDER,                       // Sort by the priority of a graphical object for receiving the event of clicking on a chart

   //---...
   //---...

   SORT_BY_CANV_ELEMENT_TOOLTIP_IS_BALLOON,           // Sort by a cloud tooltip flag
   SORT_BY_CANV_ELEMENT_TOOLTIP_USE_FADING,           // Sort by the flag of fading when showing/hiding a tooltip
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_MAXIMUM,         // Sort by the upper bound of the range ProgressBar operates in
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_MINIMUM,         // Sort by the lower bound of the range ProgressBar operates in
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_STEP,            // Sort by ProgressBar increment needed to redraw it
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_STYLE,           // Sort by ProgressBar style
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_VALUE,           // Sort by the current ProgressBar value from Min to Max
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_MARQUEE_ANIM_SPEED, // Sort by progress bar animation speed in case of Marquee style
//--- Sort by real properties

//--- Sort by string properties
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Sort by the graphical resource name
   SORT_BY_CANV_ELEMENT_TEXT,                         // Sort by graphical element text
   SORT_BY_CANV_ELEMENT_DESCRIPTION,                  // Sort by graphical element description
   SORT_BY_CANV_ELEMENT_TOOLTIP_HEADER,               // Sort by Tooltip element header
   SORT_BY_CANV_ELEMENT_TOOLTIP_TEXT,                 // Sort by Tooltip element text
  };
//+------------------------------------------------------------------+

Now we are able to sort and select all graphical elements by new properties.


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

   MSG_LIB_TEXT_CHEK_STATE_UNCHECKED,                 // Unchecked
   MSG_LIB_TEXT_CHEK_STATE_CHECKED,                   // Checked
   MSG_LIB_TEXT_CHEK_STATE_INDETERMINATE,             // Undefined
   
   MSG_LIB_TEXT_ICON_NONE,                            // None
   MSG_LIB_TEXT_ICON_INFO,                            // Info
   MSG_LIB_TEXT_ICON_WARNING,                         // Warning
   MSG_LIB_TEXT_ICON_ERROR,                           // Error
   MSG_LIB_TEXT_ICON_USER,                            // User
   MSG_LIB_TEXT_STYLE_BLOCKS,                         // Segmented blocks
   MSG_LIB_TEXT_STYLE_CONTINUOUS,                     // Continuous bar
   MSG_LIB_TEXT_STYLE_MARQUEE,                        // Continuous scrolling
   
   MSG_LIB_TEXT_SUNDAY,                               // Sunday
   MSG_LIB_TEXT_MONDAY,                               // Monday

...

   MSG_GRAPH_ELEMENT_TYPE_ELEMENT,                    // Element
   MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                 // Shadow object
   MSG_GRAPH_ELEMENT_TYPE_GLARE_OBJ,                  // Glare object
   MSG_GRAPH_ELEMENT_TYPE_FORM,                       // Form
   MSG_GRAPH_ELEMENT_TYPE_WINDOW,                     // Window

...

   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,          // HintMoveLeft control
   MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP,                 // ToolTip control
   MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,        // BarProgressBar control
   MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,            // ProgressBar control
//---
   MSG_GRAPH_ELEMENT_TYPE_WF_WRONG_TYPE_PASSED,       // Incorrect control type
   MSG_GRAPH_ELEMENT_TYPE_WF_CANT_ADD_2_TOOLTIP,      // Unable to add two or more ToolTips to the same control
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Graphical object does not belong to a program

...

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Failed to get the list of newly added objects
   MSG_GRAPH_OBJ_FAILED_GET_ACTIVE_OBJ_LIST,          // Failed to get the list of active elements
   MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES,             // Failed to get object names
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list

...

   MSG_CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,       // Upper edge area width
   MSG_CANV_ELEMENT_PROP_DISPLAYED,                   // Non-hidden control display flag
   MSG_CANV_ELEMENT_PROP_DISPLAY_STATE,               // Control display state
   MSG_CANV_ELEMENT_PROP_DISPLAY_DURATION,            // Control display duration
   MSG_CANV_ELEMENT_PROP_ENABLED,                     // Element availability flag
   MSG_CANV_ELEMENT_PROP_FORE_COLOR,                  // Default text color for all control objects

...

   MSG_CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,          // Tooltip in the form of a "cloud"
   MSG_CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,          // Fade when showing/hiding a tooltip
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM,        // The upper bound of the range ProgressBar operates in
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM,        // The lower bound of the range ProgressBar operates in
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,           // ProgressBar increment needed to redraw it
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE,          // ProgressBar style
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,          // Current ProgressBar value from Min to Max
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED,// Progress bar animation speed in case of Marquee style
   
//--- Real properties of graphical elements

//--- String properties of graphical elements
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Graphical element object name
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Graphical resource name
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Graphical element text
   MSG_CANV_ELEMENT_PROP_DESCRIPTION,                 // Graphical element description
   MSG_CANV_ELEMENT_PROP_TOOLTIP_TITLE,               // Element tooltip title
   MSG_CANV_ELEMENT_PROP_TOOLTIP_TEXT,                // Element tooltip text
  };
//+------------------------------------------------------------------+

and text messages corresponding to the newly added indices:

   {"Не установлен","Unchecked"},
   {"Установлен","Checked"},
   {"Неопределённый","Indeterminate"},
   
   {"None","None"},
   {"Info","Info"},
   {"Warning","Warning"},
   {"Error","Error"},
   {"Пользовательский","User"},
   {"Сегментированные блоки","Blocks"},
   {"Непрерывная полоса","Continuous"},
   {"Непрерывная прокрутка","Marquee"},
   
   {"Воскресение","Sunday"},
   {"Понедельник","Monday"},

...

   {"Элемент","Element"},
   {"Объект тени","Shadow object"},
   {"Объект блика","Glare object"},
   {"Форма","Form"},
   {"Окно","Window"},

...

   {"Элемент управления \"HintMoveDown\"","Control element \"HintMoveDown\""},
   {"Элемент управления \"ToolTip\"","Control element \"ToolTip\""},
   {"Элемент управления BarProgressBar","Control element \"BarProgressBar\""},
   {"Элемент управления ProgressBar","Control element \"ProgressBar\""},
   
   {"Передан не правильный тип элемента управления","Wrong control type passed"},
   {"Нельзя к одному элементу управления добавить два и более ToolTip","Can't add two or more ToolTips to one control"},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

...

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось получить список активных элементов","Failed to get list of active elements"},
   {"Не удалось получить имена объектов","Failed to get object names"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},

...

   {"Ширина области верхней грани","Width of the top border area"},
   {"Флаг отображения не скрытого элемента управления","Flag that sets the display of a non-hidden control"},
   {"Состояние отображения элемента управления","Display state of the control"},
   {"Продолжительность процесса отображения элемента управления","Duration of the process of displaying the control"},
   {"Флаг доступности элемента","Element Availability Flag"},
   {"Цвет текста по умолчанию для всех объектов элемента управления","Default text color for all objects in the control"},

...

   {"Подсказка в форме \"облачка\"","Tooltip as \"Balloon\""},
   {"Угасание при отображении и скрытии подсказки","Tooltip uses fading"},
   {"Верхняя граница диапазона, в котором действует ProgressBar","Upper bound of the range in which the ProgressBar operates"},
   {"Нижняя граница диапазона, в котором действует ProgressBar","Lower bound of the range in which the ProgressBar operates"},
   {"Величина приращения значения ProgressBar для его перерисовки","Increment value of the ProgressBar to redraw it"},
   {"Стиль элемента ProgressBar","Progress Bar element style"},
   {"Текущее начение элемента ProgressBar в диапазоне от Min до Max","Current value of the ProgressBar in the range from Min to Max"},
   {"Скорость анимации полосы прогресса при стиле Marquee","Marquee style progress bar animation speed"},
   
//--- String properties of graphical elements
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},
   {"Описание графического элемента","Description of the graphic element"},
   {"Заголовок подсказки элемента","Element tooltip header"},
   {"Текст подсказки элемента","Element tooltip title"},
  };
//+---------------------------------------------------------------------+


When creating object collections, we use the class of the list derived from the class of the dynamic array of pointers to CObject class instances and its descendants. The CArrayObj class features the Detach() method that retrieves the pointer from the list and returns the obtained pointer. We do not always need to use the pointer after extracting it from the list.
Therefore, in the derived class in
\MQL5\Include\DoEasy\Collections\ListObj.mqh, create the method removing the pointer from the list without returning it:

//+------------------------------------------------------------------+
//|                                                      ListObj.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------+
//| Include files                                  |
//+------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------+
//| Class of collection lists                      |
//+------------------------------------------------+
class CListObj : public CArrayObj
  {
private:
   int               m_type;                    // List type
public:
   bool              DetachElement(const int index)
                       {
                        CObject *obj=CArrayObj::Detach(index);
                        if(obj==NULL)
                           return false;
                        obj=NULL;
                        return true;
                       }
   void              Type(const int type)       { this.m_type=type;     }
   virtual int       Type(void)           const { return(this.m_type);  }
                     CListObj()                 { this.m_type=0x7778;   }
  };
//+------------------------------------------------------------------+

Here we simply retrieve the pointer from the list. If failed to do that, return false.
Upon successful extraction, reset the resulting pointer and return true.


Let's slightly modify the pause object class in \MQL5\Include\DoEasy\Services\Pause.mqh.

Add the public method returning the start of the pause countdown in the number of milliseconds that have passed since the system started:

//--- Return (1) the time passed from the countdown start in milliseconds, (2) waiting completion flag,
//--- (3) pause countdown start time, (4) pause in milliseconds, (5) countdown start
   ulong             Passed(void)                     const { return this.TickCount()-this.m_start;                     }
   bool              IsCompleted(void)                const { return this.Passed()>this.m_wait_msc;                     }
   ulong             TimeBegin(void)                  const { return this.m_time_begin;                                 }
   ulong             TimeWait(void)                   const { return this.m_wait_msc;                                   }
   ulong             CountdownStart(void)             const { return this.m_start;                                      }

We will need the method when handling controls in the timer.


Since I have added new integer properties of the graphical element, we need to initialize them in the base object of the graphical element and add new fields of the object structure.

In \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, add new fileds to the structure:

private:
   int               m_shift_coord_x;                          // Offset of the X coordinate relative to the base object
   int               m_shift_coord_y;                          // Offset of the Y coordinate relative to the base object
   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type

      //---...
      //---...

      int            visible_area_h;                           // Visibility scope height
      bool           displayed;                                // Non-hidden control display flag
      int            display_state;                            // Control display state
      long           display_duration;                         // Control display duration
      int            split_container_fixed_panel;              // Panel that retains its size when the container is resized
      bool           split_container_splitter_fixed;           // Separator moveability flag

      //---...
      //---...

      //---...
      //---...

      int            border_right_area_width;                  // Right edge area width
      int            border_top_area_width;                    // Upper edge area width
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
      uchar          text[256];                                // Graphical element text
      uchar          descript[256];                            // Graphical element description
     };
   SData             m_struct_obj;                             // Object structure
   uchar             m_uchar_array[];                          // uchar array of the object structure

We need the structure of the object to correctly save the object to a file and subsequently restore it.


In the block of methods for simplified access to object properties, set the methods for handling these new properties:

//--- (1) Set and (2) return the flag for displaying a non-hidden control
   virtual void      SetDisplayed(const bool flag)             { this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,flag);                   }
   bool              Displayed(void)                     const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED);           }
   
//--- (1) Set and (2) return the control display status
   void              SetDisplayState(const ENUM_CANV_ELEMENT_DISPLAY_STATE state)
                       { this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE,state);                                                      }
   ENUM_CANV_ELEMENT_DISPLAY_STATE DisplayState(void)    const
                       { return (ENUM_CANV_ELEMENT_DISPLAY_STATE)this.GetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE);                    }
   
//--- (1) Set and (2) return the control display duration
   void              SetDisplayDuration(const long value)      { this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION,value);           }
   long              DisplayDuration(void)               const { return this.GetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION);          }
   
//--- (1) Set and (2) return the graphical element type
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type)
                       {
                        CGBaseObj::SetTypeElement(type);
                        this.SetProperty(CANV_ELEMENT_PROP_TYPE,type);
                       }
   ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void)  const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(CANV_ELEMENT_PROP_TYPE);   }

The property setter methods set the values passed to them into the object properties, while the return methods return previously set values from the object properties.

Set the default values for the new properties in both constructors:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   descript,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.SetTypeElement(element_type);
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=main_obj;
   this.m_element_base=base_obj;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(colour,true);
   this.SetOpacity(opacity);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Visibility scope height
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Non-hidden control display flag
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE,CANV_ELEMENT_DISPLAY_STATE_NORMAL);// Control display state
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION,DEF_CONTROL_PROCESS_DURATION);  // Control display duration
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // Control area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Control area Y coordinate

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Control area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // Right scroll area X coordinate

      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TEXT,"");                                            // Tooltip text for the element
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------+
//| Protected constructor                          |
//+------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  descript,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=main_obj;
   this.m_element_base=base_obj;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetOpacity(0);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,false))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Visibility scope height
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Non-hidden control display flag
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE,CANV_ELEMENT_DISPLAY_STATE_NORMAL);// Control display state
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION,DEF_CONTROL_PROCESS_DURATION);  // Control display duration
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // Control area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Control area Y coordinate

      //---...
      //---...
 
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Control area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // Right scroll area X coordinate

      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TITLE,"");                                           // Tooltip title for the element
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TEXT,"");                                            // Tooltip text for the element
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+


In the method creating the object structure, add writing object property values to the structure fields:

//+------------------------------------------------+
//| Create the object structure                    |
//+------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Save integer properties
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                               // Element ID
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                           // Graphical element type

   //---...
   //---...

   this.m_struct_obj.visible_area_h=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT);  // Visibility scope height
   this.m_struct_obj.displayed=(bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED);                // Flag for displaying a non-hidden control
   this.m_struct_obj.display_state=(int)this.GetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE);         // Control display state
   this.m_struct_obj.display_duration=this.GetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION);        // Control display duration
   this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER);                            // Priority of a graphical object for receiving the on-chart mouse click event
   this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED);                    // Element availability flag

   //---...
   //---...

   this.m_struct_obj.fore_color=(color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR);             // Default text color for all control objects
   this.m_struct_obj.fore_color_opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY); // Opacity of the default text color for all control objects

   //---...
   //---...

   this.m_struct_obj.border_right_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH);    // Right edge area width
   this.m_struct_obj.border_top_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH);        // Top edge area width
//--- Save real properties

//--- Save string properties
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);   // Graphical element object name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);   // Graphical resource name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text);           // Graphical element text
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description
   //--- Save the structure to the uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+


In the method that creates an object from a structure, implement setting values in the object properties from the structure fields:

//+------------------------------------------------+
//| Create the object from the structure           |
//+------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Save integer properties
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                    // Element ID
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                                // Graphical element type

   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,this.m_struct_obj.visible_area_h);       // Visibility scope height
   this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,this.m_struct_obj.displayed);                      // Non-hidden control display flag
   this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE,this.m_struct_obj.display_state);              // Control display state
   this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION,this.m_struct_obj.display_duration);        // Control display duration
   this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder);                            // Priority of a graphical object for receiving the event of clicking on a chart
   this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled);                          // Element availability flag

   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY,this.m_struct_obj.background_color_opacity);       // Element opacity
   this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.m_struct_obj.background_color_mouse_down); // Control background color when clicking on the control
   
   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,this.m_struct_obj.border_bottom_area_width);    // Bottom edge area width
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,this.m_struct_obj.border_right_area_width);      // Right edge area width
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,this.m_struct_obj.border_top_area_width);          // Top edge area width
//--- Save real properties

//--- Save string properties
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));   // Graphical element object name
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));   // Graphical resource name
   this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text));           // Graphical element text
   this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description
  }
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh of the base object class of all library graphical objects, add returning the description of new object types to the method returning the description of the graphical element type:

//+------------------------------------------------------------------+
//| Return the description of the graphical element type             |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return
     (
      type==GRAPH_ELEMENT_TYPE_STANDARD                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)                 :
      type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)        :
      type==GRAPH_ELEMENT_TYPE_ELEMENT                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)                  :
      type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)               :
      type==GRAPH_ELEMENT_TYPE_GLARE_OBJ                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_GLARE_OBJ)                :
      type==GRAPH_ELEMENT_TYPE_FORM                      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                     :
      type==GRAPH_ELEMENT_TYPE_WINDOW                    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                   :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)              :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)                  :
      //--- Containers
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)             :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)           :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)       :
      //--- Standard controls
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)           :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)           :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)                :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)     :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)         :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)      :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)       :
      type==GRAPH_ELEMENT_TYPE_WF_TOOLTIP                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP)               :
      type==GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR)          :
      //--- Auxiliary control objects
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)            :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)             :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) :
      type==GRAPH_ELEMENT_TYPE_WF_SPLITTER               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER)              :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE)             :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT)        :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT)       :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP)          :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN)        :
      type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR)      :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

I have already declared all these objects, but I will create them a little later - after finalizing the ToolTip object class.


To work with graphical elements, we need to create a timer event handler. Since all graphical elements are inherited from the form object class, it is in this class that I will declare the virtual timer handler.

In the public section in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, write a virtual timer event handler:

//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Mouse event handler
   virtual void      OnMouseEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);

//--- Timer
   virtual void      OnTimer(void)  { return; }

The handler does nothing. It needs to be redefined in the classes the timer events are to be handled in.


Declare the method returning the number of ToolTip objects attached to the element:

//--- Add a new attached element
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

//--- (1) Bind the ToolTip object to the element
   bool              AddTooltip(CForm *tooltip);
//--- Return (1) the number of ToolTip objects, (2) bound ToolTip object, (3) by description
   int               ToolTipTotal(void);
   CForm            *GetTooltip(void);
   CForm            *GetTooltipByDescription(const string descript);
//--- Set the text for Tooltip
   virtual void      SetTooltipText(const string text);

Only one tooltip object can be assigned to one graphical object. But we can attach several ToolTips to an object, and assign the created tooltips to other objects attached to it. This is done because not all library objects can attach ToolTip objects to themselves. But we can always create tooltips in the container to which objects are attached, and then assign tooltips to these objects. The method allows us to find out the number of ToolTip objects created and attached to the container.

Let's refine the method that binds the specified ToolTip object to an object.

Add additional invalid type and the number of attached tooltips being greater than zero checks:

//+------------------------------------------------------------------+
//| Assign the specified ToolTip object to an object                 |
//+------------------------------------------------------------------+
bool CForm::AddTooltip(CForm *tooltip)
  {
//--- If the pointer to an empty object is passed or the object type is not equal to Tooltip, report an error and return 'false'
   if(tooltip==NULL)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT);
      return false;
     }
//--- If a pointer to an object whose type is not equal to Tooltip is passed, report an error and return 'false'
   if(tooltip.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_ELEMENT_TYPE_WF_WRONG_TYPE_PASSED);
      return false;
     }
//--- If the list of attached objects already contains the Tooltip object with the same description as the object passed to the method - 
//--- inform of that in the journal and return 'false'
   if(this.GetTooltipByDescription(tooltip.Description())!=NULL)
     {
      ::Print(DFUN,this.TypeElementDescription()+": ",CMessage::Text(MSG_FORM_TOOLTIP_OBJ_ALREADY_EXISTS),": ",tooltip.Name(),", Description: \"",tooltip.Description(),"\"");
      return false;
     }
//--- If the list of attached objects already contains the Tooltip object, report this to the log and return 'false'
   if(this.ToolTipTotal()>0)
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CANT_ADD_2_TOOLTIP));
      return false;
     }
//--- If it was not possible to add the Tooltip object to the list of attached objects, report an error and return 'false'
   if(!this.m_list_elements.Add(tooltip))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",tooltip.NameObj());
      return false;
     }
//--- If the coordinates of the object added to the list are changed, set the Tooltip relative coordinates
   if(tooltip.Move(this.CoordX(),this.CoordY()))
     {
      tooltip.SetCoordXRelative(tooltip.CoordX()-this.CoordX());
      tooltip.SetCoordYRelative(tooltip.CoordY()-this.CoordY());
     }
//--- Set this object as the base object for the Tooltip object and return 'true'
   tooltip.SetBase(this.GetObject());
   return true;
  }
//+------------------------------------------------------------------+

If we mistakenly try to attach an object of a different type instead of a tooltip object, the method will report an error. If ToolTip is already attached to the object, then an error message will also be displayed and the method will return false.


The method returning the number of ToolTip objects:

//+------------------------------------------------+
//| Return the number of ToolTip objects           |
//+------------------------------------------------+
int CForm::ToolTipTotal(void)
  {
   int res=0;
   for(int i=this.ElementsTotal()-1;i>WRONG_VALUE;i--)
     {
      CGCnvElement *obj=this.GetElement(i);
      if(obj!=NULL && obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
         res++;
     }
   return res;
  }
//+------------------------------------------------------------------+

If this object is a container, then any number of tooltip objects can be attached to it. The method returns their number. Get the next object in the loop by the total number of attached objects. If the object type is a tooltip, increase the object counter (res variable). At the end of the loop, return the result of the calculation set in res.


The graphical element collection timer is updated every 16 milliseconds. To make the tooltip fade in or out within, say, one second, we need to divide 1000 milliseconds by the timer update period. As a result, we need to change the opacity of the object approximately every 1000/16=62.5 milliseconds.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ToolTip.mqh, declare the variable to store the transparency change step:

//+------------------------------------------------------------------+
//| Class of the base Hint object of the WForms controls             |
//+------------------------------------------------------------------+
class CToolTip : public CHintBase
  {
private:
   int               m_step;                             // Transparency change step
//--- Adjust a tooltip size according to a text size
   void              CorrectSizeByTexts(void);
protected:


In the public section of the class, declare the virtual timer event handler:

//--- Display the element
   virtual void      Show(void);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Draw the hint frame
   virtual void      DrawFrame(void);
//--- Initialize the variables
   virtual void      Initialize(void);
//--- Timer
   virtual void      OnTimer(void);
  };
//+------------------------------------------------------------------+


In each of the constructors, calculate the value of the variable that stores the transparency change step based on the default values for fade in/out duration and the step of the graphical element collection timer counter:

//+------------------------------------------------+
//| Protected constructor with an object type,     |
//| chart ID and subwindow                         |
//+------------------------------------------------+
CToolTip::CToolTip(const ENUM_GRAPH_ELEMENT_TYPE type,
                   CGCnvElement *main_obj,CGCnvElement *base_obj,
                   const long chart_id,
                   const int subwindow,
                   const string descript,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.m_step=(int)::floor(DEF_CONTROL_PROCESS_DURATION/COLLECTION_GRAPH_ELM_COUNTER_STEP);
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CToolTip::CToolTip(CGCnvElement *main_obj,CGCnvElement *base_obj,
                   const long chart_id,
                   const int subwindow,
                   const string descript,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.m_step=(int)::floor(DEF_CONTROL_PROCESS_DURATION/COLLECTION_GRAPH_ELM_COUNTER_STEP);
  }
//+------------------------------------------------------------------+


In the variable initialization method, enter the default values for the delays in changing the states of tooltip objects:

//+------------------------------------------------+
//| Initialize the variables                       |
//+------------------------------------------------+
void CToolTip::Initialize(void)
  {
   this.SetBackgroundColor(CLR_DEF_CONTROL_HINT_BACK_COLOR,true);
   this.SetBorderColor(CLR_DEF_CONTROL_HINT_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_CONTROL_HINT_FORE_COLOR,true);
   this.SetDisplayed(false);
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetShadow(true);
   this.DrawShadow(2,2,CLR_DEF_SHADOW_COLOR,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
   this.SetOpacity(255,false);
   this.SetTitle("");
   this.SetTooltipText("");
   this.SetInitialDelay(DEF_CONTROL_TOOLTIP_INITIAL_DELAY);
   this.SetAutoPopDelay(DEF_CONTROL_TOOLTIP_AUTO_POP_DELAY);
   this.SetReshowDelay(DEF_CONTROL_TOOLTIP_RESHOW_DELAY);
   this.SetShowAlways(false);
   this.SetIcon(CANV_ELEMENT_TOOLTIP_ICON_NONE);
   this.SetBalloon(false);
   this.SetUseFading(true);
   this.SetTextAlign(ANCHOR_LEFT_UPPER);
   this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
   this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_NORMAL);
   this.SetDisplayed(false);
   this.Hide();
  }
//+------------------------------------------------------------------+


In the method that displays the tooltip, move the object display command to the very end and remove the complete redrawing of the entire object:

//+------------------------------------------------+
//| Show the element                               |
//+------------------------------------------------+
void CToolTip::Show(void)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed() || this.TooltipText()=="")
      return;
//--- Display the object
   CGCnvElement::Show();
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object exists, display the shadow
   if(this.IsShadow() && shadow!=NULL)
     {
      shadow.Show();
      this.BringToTop();
     }
//--- Redraw the object
   this.Redraw(true);
  }
//+------------------------------------------------------------------+

Now the method looks as follows:

//+------------------------------------------------+
//| Show the element                               |
//+------------------------------------------------+
void CToolTip::Show(void)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed() || this.TooltipText()=="")
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object exists, display the shadow
   if(this.IsShadow() && shadow!=NULL)
     {
      shadow.Show();
      this.BringToTop();
     }
//--- Display the object
   CGCnvElement::Show();
  }
//+------------------------------------------------------------------+

This refinement slightly reduces the unpleasant "blinking" of the element when gradually changing its opacity.


In the tooltip method, add an update of the modified canvas at the very end:

//+------------------------------------------------+
//| Draw a hint                                    |
//+------------------------------------------------+
void CToolTip::DrawHint(const int shift)
  {
   int y=3;
   int x=6+(this.Icon()>CANV_ELEMENT_TOOLTIP_ICON_NONE ? 16 : 0);
   this.DrawIcon();
   if(this.Title()!="" && this.Title()!=NULL)
     {
      this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_BLACK);
      this.Text(x,y,this.Title(),this.ForeColor(),this.Opacity(),this.TextAnchor());
      this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_NORMAL);
      y+=this.TextHeight(this.Title())+4;
     }
   this.Text(x,y,this.Text(),this.ForeColor(),this.Opacity(),this.TextAnchor());
   this.Update();
  }
//+------------------------------------------------------------------+

When changing the opacity to zero (the object is completely transparent), I noticed a strange behavior of methods that draw primitives with anti-aliasing - they react poorly to the transparency value. With a very low opacity value, the lines drawn with such methods stand out too much against the background of an object that is set to exactly the same opacity value. Adding a canvas update line is one of the attempts to reduce this annoying effect. However, it is not yet working...


Timer event handler:

//+------------------------------------------------+
//| Timer                                          |
//+------------------------------------------------+
void CToolTip::OnTimer(void)
  {
//--- If the object is in the normal state (hidden)
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_NORMAL)
     {
      //--- set the state of waiting for the object to fade in
      //--- set the waiting duration and set the countdown time
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN);
      this.m_pause.SetWaitingMSC(this.InitialDelay());
      this.m_pause.SetTimeBegin();
      return;
     }
//--- If the object is in the state of waiting for fading in
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN)
     {
      //--- If the waiting time has not yet passed, leave
      if(this.m_pause.Passed()<this.InitialDelay())
         return;
      //--- Set the state of the object fading in and
      //---  the process start countdown time
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN);
      this.m_pause.SetTimeBegin();
      return;
     }
//--- If the object is in the state of fading in
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN)
     {
      //--- If the object is not completely opaque yet
      if(this.Opacity()<255)
        {
         //--- set the display flag and show the object,
         //--- calculate the required opacity value at this timer step,
         //--- set the calculated opacity to the properties and redraw the object
         this.SetDisplayed(true);
         if(this.Opacity()==0)
            this.Show();
         uchar value=(this.Opacity()+(uchar)this.m_step<255 ? this.Opacity()+(uchar)this.m_step : 255);
         this.SetOpacity(value);
         this.Redraw(true);
         return;
        }
      //--- Set the end state of fading in
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN);
     }
//--- If the object is in the state of completion of fading in
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN)
     {
      //--- set the state of waiting for object fading out,
      //--- set the waiting duration and set the countdown time
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT);
      this.m_pause.SetWaitingMSC(this.AutoPopDelay());
      this.m_pause.SetTimeBegin();
      return;
     }
//--- If the object is in the state of waiting for fading out
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT)
     {
      //--- If the waiting time has not yet passed, leave
      if(this.m_pause.Passed()<this.AutoPopDelay())
         return;
      //--- Set the state of the object fading out and
      //---  the process start countdown time
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT);
      this.m_pause.SetTimeBegin();
      return;
     }
//--- If the object is in the state of fading out
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT)
     {
      //--- If the object is not completely transparent yet
      if(this.Opacity()>0)
        {
         //--- set the display flag,
         //--- calculate the required opacity value at this timer step,
         //--- set the calculated opacity to the properties and redraw the object
         //this.SetDisplayed(true);
         uchar value=(this.Opacity()-(uchar)this.m_step>0 ? this.Opacity()-(uchar)this.m_step : 0);
         this.SetOpacity(value);
         this.Redraw(true);
         return;
        }
      //--- Set the end state of fading out,
      //--- set the properties to full transparency and redraw the object
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT);
      this.SetOpacity(0);
      this.Redraw(true);
     }
//--- If the object is in the state of completion of fading out
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT)
     {
      //--- Set the flag for not displaying, set the full transparency,
      //--- hide the object and set the state of the completion of the entire cycle of changes
      this.SetDisplayed(false);
      this.SetOpacity(0);
      this.Hide();
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED);
     }
  }
//+------------------------------------------------------------------+

The method logic is fully described in the code comments. The handler is called each time the object timer expires. At each iteration of the timer, we need to check the state of the object and its wait/state change counters.

First, the object has its usual state saved. In this state, the tooltip is completely hidden. If the object is in this state, then first of all it should wait some time before starting fading in. Therefore, seeing the normal state of the object, we prescribe to it the state of waiting for fading in, start the countdown of the waiting time and leave the method.

At the next iteration, we see a new state (waiting for fading in) and, accordingly, check the counter. If the waiting time is up, set the object to the fading in state and leave until the next method call. On the next run, we already get into the process of the object fading in. Here we need to check the opacity of the object and increase it by the change step. This happens on each iteration of the timer operation. As soon as the object becomes completely opaque, we set a new state for it - waiting for the smooth fading process. After all, the object that appears after about five seconds should also disappear smoothly.

The process of its fading out is identical to fading in. At the end of the full cycle, the state of the change cycle end is set to the object. If we immediately set a normal state to it, then the whole cycle will be started anew since the normal state of the object serves as the starting condition for launching this cycle. It should be written to the object only when the pointer to it is removed from the list of objects to be handled in the timer. This is done in the collection class of graphical elements. Seeing the object full cycle completion status, the method removes the pointer to the object from the handling list and sets the object itself to its normal state. The next time we hover the cursor over an object, it will again be placed in the list for processing in the timer. In the same way, the object pointer is removed from the list if the cursor is moved from the area of the object that is ready for the handling cycle, or even during the cycle.


Gradually, we are approaching the creation of a new ProgressBar WinForms object. For this control, we will need to create the Glare auxiliary object. The objects of this type will serve to visually decorate some GUI elements, while in the ProgressBar object, the glare should run along the progress bar. The concept will be as follows: draw a white spot of the required shape and size and blur it from the center to the edges. The glare object will be as semi-transparent as the shadow object. Here we come to the fact that we need to inherit such an object from the shadow object in order to use its blur methods, while keeping custom rendering. In order to normally inherit from the shadow object, we need to add a protected constructor to it, in which we will indicate the type of the created object.

Let's make changes to the class of the shadow object in \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh.

We will move the Gaussian blur methods and the array of weight coefficients from the private to the protected section of the class. The protected constructor is declared there as well. In the public section of the class, write the virtual method returning the flag for maintaining a real property by an object:

//+------------------------------------------------+
//| Shadows object class                           |
//+------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color;                         // Shadow color
   uchar             m_opacity;                       // Shadow opacity
   uchar             m_blur;                          // Blur
//--- Draw the object shadow form
   void              DrawShadowFigureRect(const int w,const int h);
protected:
//--- Gaussian blur
   bool              GaussianBlur(const uint radius);
//--- Return the array of weight ratios
   bool              GetQuadratureWeights(const double mu0,const int n,double &weights[]);
//--- Protected constructor with object type, chart ID and subwindow
                     CShadowObj(const ENUM_GRAPH_ELEMENT_TYPE type,
                                CGCnvElement *main_obj,CGCnvElement *base_obj,
                                const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h);
public:
//--- Constructor indicating the main and base objects, chart ID and subwindow
                     CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

//--- Supported object properties (1) integer, (2) real and (3) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Draw an object shadow

I will gradually add methods that return the flags for maintaining properties by an object to all graphical elements, since these are standard methods for the library, which I will need in the future. Here I have added such a method simply to avoid returning to this matter in this class.


Protected constructor:

//+------------------------------------------------+
//| Protected constructor with an object type,     |
//| chart ID and subwindow                         |
//+------------------------------------------------+
CShadowObj::CShadowObj(const ENUM_GRAPH_ELEMENT_TYPE type,
                       CGCnvElement *main_obj,CGCnvElement *base_obj,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.SetOpacityDraw(CLR_DEF_SHADOW_OPACITY);
   this.SetBlur(DEF_SHADOW_BLUR);
   color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
  }
//+------------------------------------------------------------------+

In the formal constructor parameters, the type of the created object is passed to the parent class in the initialization string. This is done in all objects of the library. Everything else here is done exactly the same as in the parametric constructor.

In the parametric constructor, remove the string at the end that causes the shadow to be drawn immediately after it is created:

//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.SetOpacityDraw(CLR_DEF_SHADOW_OPACITY);
   this.SetBlur(DEF_SHADOW_BLUR);
   color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+

The string caused incorrect behavior of the shadow object when creating GUI elements. First, a shadow appeared on an empty chart, and then the appearance of the GUI was built. Now the shadow will not appear first.


ProgressBar control

First, let's create an auxiliary glare object in \MQL5\Include\DoEasy\Objects\Graph\WForms\ of the GlareObj.mqh file. Since I currently do not need this object, I will not consider it. It is completely similar to the shadow object class except that the color of a drawn glare is set to white:

//+------------------------------------------------------------------+
//|                                                     GlareObj.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------+
//| Include files                                  |
//+------------------------------------------------+
#include "..\ShadowObj.mqh"
//+------------------------------------------------+
//| Glare object class                             |
//+------------------------------------------------+
class CGlareObj : public CShadowObj
  {
private:
   color             m_color;                         // Glare color
   uchar             m_opacity;                       // Glare opacity
   uchar             m_blur;                          // Blur
//--- Draw the object glare form
   void              DrawGlareFigure(const int w,const int h);
   void              DrawGlareFigureRect(const int w,const int h);
protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CGlareObj(const ENUM_GRAPH_ELEMENT_TYPE type,
                               CGCnvElement *main_obj,CGCnvElement *base_obj,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
public:
//--- Constructor indicating the main and base objects, chart ID and subwindow
                     CGlareObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);

//--- Supported object properties (1) integer, (2) real and (3) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Draw the object glare
   void              Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw);
  };
//+------------------------------------------------+
//| Protected constructor with an object type,     |
//| chart ID and subwindow                         |
//+------------------------------------------------+
CGlareObj::CGlareObj(const ENUM_GRAPH_ELEMENT_TYPE type,
                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CShadowObj(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GGLARE; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.SetOpacityDraw(CLR_DEF_SHADOW_OPACITY);
   this.SetBlur(DEF_SHADOW_BLUR);
   this.m_color=clrWhite;
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CGlareObj::CGlareObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CShadowObj(GRAPH_ELEMENT_TYPE_GLARE_OBJ,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GGLARE; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.SetOpacityDraw(CLR_DEF_SHADOW_OPACITY);
   this.SetBlur(DEF_SHADOW_BLUR);
   this.m_color=clrWhite;
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
  }
//+------------------------------------------------+
//| Draw the object glare                          |
//+------------------------------------------------+
void CGlareObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw)
  {
   if(!this.IsVisible())
      return;
//--- Set the glare offset values along the X and Y axes to variables
   this.SetCoordXRelative(shift_x);
   this.SetCoordYRelative(shift_y);
//--- Calculate the height and width of the drawn rectangle
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Draw a filled rectangle with calculated dimensions
   this.DrawGlareFigure(w,h);
//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant
   this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal)
   if(!this.GaussianBlur(this.m_blur))
      return;
//--- Shift the glare object by X/Y offsets specified in the method arguments and update the canvas
   CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),false);
   CGCnvElement::Update(redraw);
  }
//+------------------------------------------------+
//| Draw the object glare form                     |
//+------------------------------------------------+
void CGlareObj::DrawGlareFigure(const int w,const int h)
  {
   this.DrawGlareFigureRect(w,h);
  }
//+------------------------------------------------+
//| Draw the rectangle object glare form           |
//+------------------------------------------------+
void CGlareObj::DrawGlareFigureRect(const int w,const int h)
  {
   CGCnvElement::DrawRectangleFill(OUTER_AREA_SIZE,OUTER_AREA_SIZE,OUTER_AREA_SIZE+w-1,OUTER_AREA_SIZE+h-1,this.m_color,this.OpacityDraw());
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

I will make all the necessary changes in this class in the next article. For now, let's leave it as it is.


The ProgressBar control will consist of two objects - a watermark and a progress bar. The underlay will represent the control element itself. It will be possible to place additional elements on it in the future, and the progress bar will be represented by a separate auxiliary object with its properties changed from the parent object.

In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ library file, create the new BarProgressBar.mqh file of the CBarProgressBar class. The class should be derived from the base object of all library WinForms objects. The base object file should be included into the created class file:

//+------------------------------------------------------------------+
//|                                               BarProgressBar.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------+
//| Include files                                  |
//+------------------------------------------------+
#include "..\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| BarProgressBar object class of the ProgressBar control           |
//+------------------------------------------------------------------+
class CBarProgressBar : public CWinFormBase
  {
  }


In the protected section of the class, declare the method that displays the glare object on the progress bar, as well as the protected constructor. In the public section, write the methods that set and return values from object properties, virtual methods for maintaining properties, the parametric constructor and the class timer event handler:

//+------------------------------------------------+
//| Include files                                  |
//+------------------------------------------------+
#include "..\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| BarProgressBar object class of the ProgressBar control           |
//+------------------------------------------------------------------+
class CBarProgressBar : public CWinFormBase
  {
private:

protected:
   //--- Display the glare
   virtual void      DrawGlare(void);
//--- Protected constructor with object type, chart ID and subwindow
                     CBarProgressBar(const ENUM_GRAPH_ELEMENT_TYPE type,
                                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
public:
//--- Set the (1) animation speed in case of Marquee style, (2) style, (3) increment value, (4) current value of the ProgressBar control
   void              SetMarqueeAnimationSpeed(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED,value);  }
   void              SetStyle(const ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE style) { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE,style); }
   void              SetStep(const int value)                     { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,value);                }
   void              SetValue(const int value)                    { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,value);               }

//--- Supported object properties (1) integer, (2) real and (3) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Constructor
                     CBarProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
//--- Timer
   virtual void      OnTimer(void);
  };
//+------------------------------------------------------------------+

Let's take a closer look at the declared methods.


Protected constructor:

//+------------------------------------------------+
//| Protected constructor with an object type,     |
//| chart ID and subwindow                         |
//+------------------------------------------------+
CBarProgressBar::CBarProgressBar(const ENUM_GRAPH_ELEMENT_TYPE type,
                                 CGCnvElement *main_obj,CGCnvElement *base_obj,
                                 const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h) : CWinFormBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
   this.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
   this.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
   this.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
  }
//+------------------------------------------------------------------+

The constructor receives the type of the created graphical element passed to the parent class in the initialization string. The library graphical object type is set to "auxiliary", the object has no border and default colors are set.


Parametric constructor:

//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CBarProgressBar::CBarProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                 const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
   this.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
   this.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
   this.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
  }
//+------------------------------------------------------------------+

Everything here is exactly the same as in the protected constructor, but the object type is not passed in formal parameters. It is hard-coded in the initialization string instead.

The method drawing a glare will be empty for now since the glare object since the glare object is not yet ready. Its creation and debugging are scheduled for the next article:

//+------------------------------------------------+
//| Draw a glare                                   |
//+------------------------------------------------+
void CBarProgressBar::DrawGlare(void)
  {
   
  }
//+------------------------------------------------------------------+


In the timer event handler temporarily set the value returned from the GetTickCount() function to make sure that the object falls into the list of active objects assigned for processing in the library timer:

//+------------------------------------------------+
//| Timer                                          |
//+------------------------------------------------+
void CBarProgressBar::OnTimer(void)
  {
   Comment(DFUN,GetTickCount());
  }
//+------------------------------------------------------------------+


Before I start creating the ProgressBar control, I need to make sure that the graphical element objects can be processed independently in the library timer.

The concept will be as follows: each of the graphical elements to be independently processed in the timer will be considered an active element. To handle active elements, let's create a list that will contain pointers to such objects. This list will always be created in the main object and, accordingly, is visible in the collection class of graphical elements. In the collection class timer, we will loop through all the main form objects, get pointers to active elements and call their timer event handlers. Thus, we will not be forced to look for each graphical element in the main object, but immediately create a list of the elements in the main object and process such elements only.

In the \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh file of the base object class of all WinForms library objects, add including the file of the glare object class and declare the pointer to the list of active elements in the protected section:

//+------------------------------------------------------------------+
//|                                                  WinFormBase.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------+
//| Include files                                  |
//+------------------------------------------------+
#include "GlareObj.mqh"
#include "..\Form.mqh"
#include "..\..\..\Services\Select.mqh"
//+------------------------------------------------+
//| Form object class                              |
//+------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   CArrayObj        *m_list_active_elements;                   // Pointer to the list of active elements
   color             m_fore_color_init;                        // Initial color of the control text
   color             m_fore_state_on_color_init;               // Initial color of the control text when the control is "ON"
private:
//--- Return the font flags
   uint              GetFontFlags(void);
public:

Including a glare object file here will allow us to create and use it in many graphical elements of the library.

In the public section of the class, declare the methods for working with the list of active elements:

public:
//--- Draw a frame
   virtual void      DrawFrame(void){}
//--- Return by type the (1) list, (2) the number of bound controls, the bound control (3) by index in the list, (4) by name
   CArrayObj        *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   int               ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   CGCnvElement     *GetElementByName(const string name);
//--- Return the list of active elements of (1) the current, (2) main object
   CArrayObj        *GetListActiveElements(void)      { return this.m_list_active_elements; }
   CArrayObj        *GetListMainActiveElements(void);
//--- Return the number of active elements of the main object
   int               ListMainActiveElementsTotal(void);
//--- Return the element from the list of active elements of the main object by index
   CWinFormBase     *GetActiveElement(const int index);
//--- Return the index of the specified object in the list of active elements of the main object
   int               IndexActiveElements(CWinFormBase *obj);
//--- Return the flag of the object presence by name in the list of active elements
   bool              IsPresentObjInListActiveElements(string name_obj);
//--- Add (1) the specified and (2) the current object to the list of active elements
   bool              AddObjToListActiveElements(CWinFormBase *obj);
   bool              AddObjToListActiveElements(void);
//--- Remove (1) the specified and (2) the current object from the list of active elements
   bool              DetachObjFromListActiveElements(CWinFormBase *obj);
   bool              DetachObjFromListActiveElements(void);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);


Add the class destructor:

public:
//--- Constructor
                     CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
//--- Destructor
                    ~CWinFormBase(void)
                      {
                       if(this.m_list_active_elements!=NULL)
                         {
                           this.m_list_active_elements.Clear();
                           delete this.m_list_active_elements;
                         }
                      }
//--- (1) Set and (2) return the default text color of all panel objects

If the list of active elements is created, clear it and delete the created list object.


In the class constructors, write the creation of the list object of active elements:

//+------------------------------------------------+
//| Protected constructor with an object type,     |
//| chart ID and subwindow                         |
//+------------------------------------------------+
CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_list_active_elements=new CArrayObj();
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(GRAPH_ELEMENT_TYPE_WF_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the graphical element and library object types as a base WinForms object
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_list_active_elements=new CArrayObj();
  }
//+------------------------------------------------------------------+


In the method that returns the description of the element integer property, write code blocks to return the description of new object properties:

//+------------------------------------------------------------------+
//| Return the description of the control integer property           |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_ID                           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

      //---...
      //---...

      property==CANV_ELEMENT_PROP_DISPLAYED                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DISPLAYED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_DISPLAY_STATE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DISPLAY_STATE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_DISPLAY_DURATION             ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DISPLAY_DURATION)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_GROUP                        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

      //---...
      //---...


      property==CANV_ELEMENT_PROP_TOOLTIP_USE_FADING           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_USE_FADING)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
         
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_STEP            ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_STEP)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+


The method that returns the list of active elements of the main object:

//+------------------------------------------------------------------+
//| Return the list of active elements of the main object            |
//+------------------------------------------------------------------+
CArrayObj *CWinFormBase::GetListMainActiveElements(void)
  {
   CWinFormBase *main=this.GetMain();
   if(main==NULL)
      main=this.GetObject();
   CArrayObj *list=main.GetListActiveElements();
   if(list==NULL)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_GET_ACTIVE_OBJ_LIST);
      .return NULL;
     }
   return list;
  }
//+------------------------------------------------------------------+

Here we get the pointer to the main object. If NULL is returned, this object is the main one. Get the list of active elements from the main object. If failed to get the list, inform of that and return NULL. Otherwise, return the pointer to the list.


The method that returns the number of active elements of the main object:

//+------------------------------------------------------------------+
//| Return the number of active elements of the main object          |
//+------------------------------------------------------------------+
int CWinFormBase::ListMainActiveElementsTotal(void)
  {
   return(this.GetListMainActiveElements()!=NULL ? this.GetListMainActiveElements().Total() : 0);
  }
//+------------------------------------------------------------------+

If the pointer to the list of the main object has been obtained, return the number of elements in the list. Otherwise, return zero.


The method that returns the index of the specified object in the list of active elements of the main object:

//+------------------------------------------------------------------+
//| Return the specified object index                                |
//| in the list of active elements of the main object                |
//+------------------------------------------------------------------+
int CWinFormBase::IndexActiveElements(CWinFormBase *obj)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   if(list==NULL)
      return WRONG_VALUE;
   for(int i=0;i<this.ListMainActiveElementsTotal();i++)
     {
      CWinFormBase *elm=list.At(i);
      if(elm!=NULL && elm.Name()==obj.Name())
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

The method receives the pointer to the object whose index is to be found. Get the list of active elements from the main object. In the loop, according to the received list, get the next object. If its name is equal to the name of the object passed to the method, return the loop index. At the end of the loop, return -1, which means that the object was not found.


The method that returns an element from the list of active elements of the main object by index:

//+------------------------------------------------+
//| Return an element from the list of active      |
//| elements of the main object by index           |
//+------------------------------------------------+
CWinFormBase *CWinFormBase::GetActiveElement(const int index)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   return(list!=NULL ? list.At(index) : NULL);
  }
//+------------------------------------------------------------------+

The method receives the index of an object to be returned from the list. We get the list of active elements from the main object and return the pointer to the object by index. If the list could not be obtained, the method returns NULL.


The method that returns the flag of the presence of an object by name in the list of active elements of the main object:

//+------------------------------------------------------------------+
//|  Return the flag of an object presence by name                   |
//| in the list of active elements of the main object                |
//+------------------------------------------------------------------+
bool CWinFormBase::IsPresentObjInListActiveElements(string name_obj)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   if(list==NULL)
      return false;
   for(int i=list.Total()-1;i>WRONG_VALUE;i--)
     {
      CWinFormBase *obj=list.At(i);
      if(obj!=NULL && obj.Name()==name_obj)
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

The method receives the name of the object whose presence in the list we need to find out. Get the list of active elements from the main object. In the loop by the obtained list, get the next object. If its name matches the one passed to the method, returntrue. Upon the loop completion, return false. The object is not in the list.


The method that adds the specified object to the list of active elements of the main object:

//+------------------------------------------------------------------+
//| Add the specified object to the list                             |
//| of active elements of the main object                            |
//+------------------------------------------------------------------+
bool CWinFormBase::AddObjToListActiveElements(CWinFormBase *obj)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   if(list==NULL)
      return false;
   if(obj==NULL || this.IsPresentObjInListActiveElements(obj.Name()))
      return false;
   return list.Add(obj);
  }
//+------------------------------------------------------------------+

Get the list of active elements of the main object. If an object with the same name is already in the list, return false. Otherwise, return the result of adding the object to the list.


The method that adds the current object to the list of active elements of the main object:

//+------------------------------------------------+
//| Add the current object to the list of          |
//| active elements of the main object             |
//+------------------------------------------------+
bool CWinFormBase::AddObjToListActiveElements(void)
  {
   return this.AddObjToListActiveElements(this.GetObject());
  }
//+------------------------------------------------------------------+

Return the result of calling the above method. Specify the current object as an object to be added to the list.


The method that removes the specified object from the list of active elements of the main object:

//+------------------------------------------------+
//| Remove the specified object from the list of   |
//| active elements of the main object             |
//+------------------------------------------------+
bool CWinFormBase::DetachObjFromListActiveElements(CWinFormBase *obj)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   if(list==NULL)
      return false;
   int index=this.IndexActiveElements(obj);
   if(index==WRONG_VALUE)
      return false;
   CWinFormBase *elm=list.Detach(index);
   if(elm==NULL)
      return false;
   elm=NULL;
   return true;
  }
//+------------------------------------------------------------------+

Get the pointer to the list of active elements of the main object. Get the index of the object in the list, the pointer to which is passed to the method. Get the pointer to the object removed from the list. If failed to remove the object, return false. Otherwise, reset the pointer and return true.


The method that removes the current object from the list of active elements of the main object:

//+------------------------------------------------+
//| Remove the current object from the list of     |
//| active elements of the main object             |
//+------------------------------------------------+
bool CWinFormBase::DetachObjFromListActiveElements(void)
  {
   return this.DetachObjFromListActiveElements(this.GetObject());
  }
//+------------------------------------------------------------------+

Return the result of calling the above method. Specify the current object as the object to be removed.


Let's create the ProgressBar control.

In MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, create a new file ProgressBar.mqh of the CProgressBar class. Since, in order to extend the functionality of the object, we plan to make it possible to attach other controls to it, the class should be derived from the container object class. The container class file should be included in the file of the created object together with the progress bar class file:

//+------------------------------------------------------------------+
//|                                                  ProgressBar.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------+
//| Include files                                  |
//+------------------------------------------------+
#include "..\Containers\Container.mqh"
#include "..\Helpers\BarProgressBar.mqh"
//+------------------------------------------------------------------+
//| ArrowLeftRightBox object class of WForms controls                |
//+------------------------------------------------------------------+
class CProgressBar : public CContainer
  {
  }


In the private section of the class, declare the methods for creating a new graphical object and the progress bar, as well as the class initialization method:

//+------------------------------------------------------------------+
//| ArrowLeftRightBox object class of WForms controls                |
//+------------------------------------------------------------------+
class CProgressBar : public CContainer
  {
private:
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Create the progress bar object
   void              CreateProgressBar(void);

//--- Initialize the element properties
   void              Initialize(void);

protected:


In the protected section of the class, declare a protected constructor:

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CProgressBar(const ENUM_GRAPH_ELEMENT_TYPE type,
                                  CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
public:


In the public section of the class, declare the methods for setting and getting object properties, the method for getting the pointer to the progress bar object, the methods that return the flags for maintaining object properties, the parametric constructor and the timer event handler:

public:
//--- (1) Set and (2) return the animation speed of the progress bar in case of the Marquee style
   void              SetMarqueeAnimationSpeed(const int value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED,value);
                        CBarProgressBar *bar=this.GetProgressBar();
                        if(bar!=NULL)
                           bar.SetMarqueeAnimationSpeed(value);
                       }
   int               MarqueeAnimationSpeed(void)   const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED);  }

//--- (1) Set and (2) return the progress bar style
   void              SetStyle(const ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE style)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE,style);
                        CBarProgressBar *bar=this.GetProgressBar();
                        if(bar!=NULL)
                           bar.SetStyle(style);
                       }
   ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE Style(void) const
                       { return (ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE);          }

//--- (1) Set and (2) return the progress bar increment to redraw it
   void              SetStep(const int value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,value);
                        CBarProgressBar *bar=this.GetProgressBar();
                        if(bar!=NULL)
                           bar.SetStep(value);
                       }
   int               Step(void)                          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STEP);    }
   
//--- (1) Set and (2) return the current value of the progress bar in the range from Min to Max
   void              SetValue(const int value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,value);
                        CBarProgressBar *bar=this.GetProgressBar();
                        if(bar!=NULL)
                           bar.SetValue(value);
                       }
   int               Value(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE);   }
   
//--- (1) Set and (2) return the upper bound of the ProgressBar operating range
   void              SetMaximum(const int value)               { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM,value);       }
   int               Maximum(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM); }
   
//--- (1) Set and (2) return the lower bound of the ProgressBar operating range
   void              SetMinimum(const int value)               { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM,value);       }
   int               Minimum(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM); }
   
//--- Return the pointer to the progress bar object
   CBarProgressBar  *GetProgressBar(void)                { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,0);     }

//--- Supported object properties (1) integer, (2) real and (3) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Constructor
                     CProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
//--- Timer
   virtual void      OnTimer(void);
  };
//+------------------------------------------------------------------+

After setting the property, some methods for setting the object properties set it to the corresponding property of the progress bar object.

Let's take a closer look at the declared methods.


Protected constructor:

//+------------------------------------------------+
//| Protected constructor with an object type,     |
//| chart ID and subwindow                         |
//+------------------------------------------------+
CProgressBar::CProgressBar(const ENUM_GRAPH_ELEMENT_TYPE type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CContainer(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.CreateProgressBar();
  }
//+------------------------------------------------------------------+

The method receives the type of the created object, which is set to the parent class in the initialization string. The library graphic object type is set to "standard control". Then the object properties initialization method and the method that creates the progress bar object are called.


Parametric constructor:

//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CProgressBar::CProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.CreateProgressBar();
  }
//+------------------------------------------------------------------+

Here the type of the control is hard-coded in the initialization string.


The element properties initialization method:

//+------------------------------------------------+
//| Initialize the element properties              |
//+------------------------------------------------+
void CProgressBar::Initialize(void)
  {
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BACK_COLOR,true);
   this.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
   this.SetMarqueeAnimationSpeed(10);
   this.SetMaximum(100);
   this.SetMinimum(0);
   this.SetStep(10);
   this.SetStyle(CANV_ELEMENT_PROGRESS_BAR_STYLE_CONTINUOUS);
   this.SetValue(this.Width()/2);
  }
//+------------------------------------------------------------------+

The object frame is set to one pixel on each side, the frame type is simple, the object colors and other default properties are set as well. The length of the progress bar is set to half the width of the object.


The method that creates the progress bar object:

//+------------------------------------------------+
//| Create the progress bar object                 |
//+------------------------------------------------+
void CProgressBar::CreateProgressBar(void)
  {
//--- Set the length of the progress bar equal to the object Value()
//--- The height of the progress bar is equal to the height of the object minus the top and bottom frame sizes
   int w=this.Value();
   int h=this.Height()-this.BorderSizeTop()-this.BorderSizeBottom();
//--- Create the progress bar object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,0,0,w,h,clrNONE,255,false,false);
//--- Add the created CProgressBar object to the list of active elements of the collection
   if(this.AddObjToListActiveElements())
     {
      //--- To perform the check, get this element from the list, display its description and the number of active elements in the list
      CProgressBar *progress_bar=this.GetActiveElement(this.IndexActiveElements(this.GetObject()));
      if(progress_bar!=NULL)
         Print(DFUN_ERR_LINE,"CProgressBar: ",progress_bar.TypeElementDescription(),", ListMainActiveElementsTotal=",this.ListMainActiveElementsTotal());
     }
  }
//+------------------------------------------------------------------+

The method logic features comments in the listing. The height of the progress bar is set so that the object fits completely into the container, and at the same time the container frame is not overlapped by it. After creating the progress bar, add the entire object to the list of active elements. In this case, it will get into the event handler of the timer of the library graphical elements and will be processed in it. Accordingly, in the object timer, we can make the functionality that works independently of the user. In this object, this will be the visual effect I am going to deal with in the next article.


The virtual method creating a new graphical object:

//+------------------------------------------------+
//| Create a new graphical object                  |
//+------------------------------------------------+
CGCnvElement *CProgressBar::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                             const int obj_num,
                                             const string descript,
                                             const int x,
                                             const int y,
                                             const int w,
                                             const int h,
                                             const color colour,
                                             const uchar opacity,
                                             const bool movable,
                                             const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR  :
         element=new CBarProgressBar(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_GLARE_OBJ            :
         element=new CGlareObj(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

In order for the container object to create attached objects inside itself, there is a virtual method in which these objects are created. For each control, the list of objects created in it can be different. This object provides the ability to create the progress bar object and the glare object. For now, no more objects need to be created here.


Timer event handler:

//+------------------------------------------------+
//| Timer                                          |
//+------------------------------------------------+
void CProgressBar::OnTimer(void)
  {
   CBarProgressBar *bar=this.GetProgressBar();
   if(bar!=NULL)
      bar.OnTimer();
  }
//+------------------------------------------------------------------+

Here we get the pointer to the progress bar object and call its timer event handler. Since the glare should run exactly along the progress bar, I will implement that behavior in the timer of the CBarProgressBar class object. For this reason, its timer is called here featuring the output of the GetTickCount() value on the chart is written in the form of a comment.


In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh file of the base container object, namely, in the method setting the parameters for the bound object, add the code block for setting the parameters to the BarProgressBar and ProgressBar created objects:

//+------------------------------------------------+
//| Set parameters for the attached object         |
//+------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
//--- Set the text color of the object to be the same as that of the base container
   obj.SetForeColor(this.ForeColor(),true);
//--- If the created object is not a container, set the same group for it as the one for its base object
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)
      obj.SetGroup(this.Group());
//--- Depending on the object type
   switch(obj.TypeGraphElement())
     {
      //--- For the Container, Panel and GroupBox WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            :
      case GRAPH_ELEMENT_TYPE_WF_PANEL                :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             :
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- For "Label", "CheckBox" and "RadioButton" WinForms objects

      //---...
      //---...

      //--- For ToolTip WinForms object
      case GRAPH_ELEMENT_TYPE_WF_TOOLTIP              :
        obj.SetBackgroundColor(CLR_DEF_CONTROL_HINT_BACK_COLOR,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_HINT_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_CONTROL_HINT_FORE_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        obj.SetOpacity(0,false);
        obj.SetDisplayed(false);
        obj.Hide();
        break;
      //--- For BarProgressBar WinForms object
      case GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR     :
        obj.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
        obj.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      //--- For ProgressBar WinForms object
      case GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR         :
        obj.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BACK_COLOR,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+

Simply set the default values that are set in the constructors of these classes.


Now we need to make in each container object the ability to quickly attach tooltip objects to the current and to the specified elements. This will make it easier to attach tooltips to the desired objects attached to containers.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh, add inclusion of the ProgressBar control file:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------+
//| Include files                                  |
//+------------------------------------------------+
#include "Container.mqh"
#include "..\Helpers\TabField.mqh"
#include "..\Helpers\ArrowUpButton.mqh"
#include "..\Helpers\ArrowDownButton.mqh"
#include "..\Helpers\ArrowLeftButton.mqh"
#include "..\Helpers\ArrowRightButton.mqh"
#include "..\Helpers\ArrowUpDownBox.mqh"
#include "..\Helpers\ArrowLeftRightBox.mqh"
#include "..\Helpers\HintMoveLeft.mqh"
#include "..\Helpers\HintMoveRight.mqh"
#include "..\Helpers\HintMoveUp.mqh"
#include "..\Helpers\HintMoveDown.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "SplitContainer.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
#include "..\..\WForms\Common Controls\ToolTip.mqh"
#include "..\..\WForms\Common Controls\ProgressBar.mqh"
//+------------------------------------------------------------------+


In the public section, declare two methods for creating and attaching tooltip objects:

   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
//--- Create and attach the ToolTip object (1) to the current and (2) to the specified control
   CToolTip         *SetToolTip(const string tooltip_description,const string tooltip_title,const string tooltip_text,ENUM_CANV_ELEMENT_TOOLTIP_ICON tooltip_ico);
   CToolTip         *SetToolTipTo(CForm *element,const string tooltip_description,const string tooltip_title,const string tooltip_text,ENUM_CANV_ELEMENT_TOOLTIP_ICON tooltip_ico);


In the method that creates a new graphical object, add a string for creating the ProgressBar control:

//+------------------------------------------------+
//| Create a new graphical object                  |
//+------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.GetMain(),this.GetObject(),this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);               break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             : element=new CSplitter(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_BASE            : element=new CHintBase(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT       : element=new CHintMoveLeft(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT      : element=new CHintMoveRight(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);      break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP         : element=new CHintMoveUp(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN       : element=new CHintMoveDown(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_TOOLTIP              : element=new CToolTip(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR         : element=new CProgressBar(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


The method that creates and attaches the ToolTip object to the current element:

//+------------------------------------------------------------------+
//| Create and attach the ToolTip object to the current element      |
//+------------------------------------------------------------------+
CToolTip *CPanel::SetToolTip(const string tooltip_description,const string tooltip_title,const string tooltip_text,ENUM_CANV_ELEMENT_TOOLTIP_ICON tooltip_ico)
  {
//--- Create a new tooltip object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,0,0,10,10,clrNONE,0,false,false);
//--- Get the list of all created ToolTip objects
   CArrayObj *list=this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TOOLTIP);
   if(list==NULL || list.Total()==0)
      .return NULL;
//--- Get the last element in the list of ToolTip objects (the last one created)
   CToolTip *tooltip=list.At(list.Total()-1);
//--- If the object is received
   if(tooltip!=NULL)
     {
      //--- set the description, icon, title and tooltip text for the object
      tooltip.SetDescription(tooltip_description);
      tooltip.SetIcon(tooltip_ico);
      tooltip.SetTitle(tooltip_title);
      tooltip.SetTooltipText(tooltip_text);
     }
//--- Return the ToolTip object
   return tooltip;
  }
//+------------------------------------------------------------------+

The method logic is fully described in the code comments. All necessary data is passed to the method to create the tooltip object. When the object is created, it automatically becomes attached to the current object. Next, we get the most recent ToolTip object from the list of tooltip objects and set the parameters, passed to the method, to it.


The method that creates and attaches the ToolTip object to the specified element:

//+------------------------------------------------------------------+
//| Create and attach the ToolTip object to the specified element    |
//+------------------------------------------------------------------+
CToolTip *CPanel::SetToolTipTo(CForm *element,const string tooltip_description,const string tooltip_title,const string tooltip_text,ENUM_CANV_ELEMENT_TOOLTIP_ICON tooltip_ico)
  {
   CToolTip *tooltip=this.SetToolTip(tooltip_description,tooltip_title,tooltip_text,tooltip_ico);
   if(tooltip==NULL)
      .return NULL;
   if(element.AddTooltip(tooltip))
      return tooltip;
   .return NULL;
  }
//+------------------------------------------------------------------+

In addition to the parameters required to create a tooltip object, the method receives the control the created ToolTip is to be attached to. Using the above method, a new tooltip object is created and attached to the control specified in the method parameters. If the ToolTip is successfully attached, return the pointer to the created object. Otherwise, return NULL.


Identical improvements on attaching tooltip objects and creating ProgressBar controls have been made in all container class files:

TabControl.mqh, TabField.mqh, SplitContainer.mqh, SplitContainerPanel.mqh and GroupBox.mqh. I will not consider these changes here.


Let's improve the collection class of graphical elements in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

Let's look at how tooltip objects should be handled. These objects interact with the mouse, and depending on the result of the interaction, are processed in the timer of the collection of graphical elements. The ToolTip object under the cursor is added to the list of interaction objects. This list should be processed in the timer. In this case, the previous object, which was previously under the cursor, should also be processed. But, since the past object does not need a fade out animation, the method called from the collection event handler will deal with its processing - the object should simply be hidden and the non-display flag should be set for it. Such logic differs from the logic of active objects, which are initially placed in the list of active elements and are constantly processed by a timer in it.

In the private section of the collection class, declare the list of objects to be processed:

   SDataPivotPoint   m_data_pivot_point[];      // Pivot point data structure array
   CArrayObj         m_list_charts_control;     // List of chart management objects
   CListObj          m_list_all_canv_elm_obj;   // List of all graphical elements on canvas
   CListObj          m_list_elm_to_process;     // List of graphical elements on canvas for processing
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   CArrayObj         m_list_deleted_obj;        // List of removed graphical objects
   CMouseState       m_mouse;                   // "Mouse status" class object


Also, declare the methods for processing the current and past objects under the cursor and the methods for processing the list of objects for handling:

//--- Reset all interaction flags for all forms except the specified one
   void              ResetAllInteractionExeptOne(CGCnvElement *form);
//--- Post-processing of the former active form under the cursor
   void              FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam);
//--- Processing (1) the current and (2) previous ToolTip element
   void              TooltipCurrProcessing(CForm *tooltip,CForm *base);
   void              TooltipPrevProcessing(CForm *tooltip);
//--- Add the element (1) to the collection list and (2) to the list for processing
   bool              AddCanvElmToCollection(CGCnvElement *element);
   bool              AddCanvElmToProcessList(CForm *element);
//--- Get an element by name from the list for handling
   CForm            *GetCanvElementFromProcessList(CForm *element);
//--- Return the element index by name in the list for handling
   int               CanvElementIndexInProcessList(CForm *element) const;
//--- Add the element to the collectionl ist or return the existing one
   ENUM_ADD_OBJ_RET_CODE AddOrGetCanvElmToCollection(CGCnvElement *element,int &id);
//--- Return the graphical elemnt index in the collection list
   int               GetIndexGraphElement(const long chart_id,const string name);


In the public section, write the method that returns a list of objects to handle:

public:
//--- Return itself
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
//--- Return the full collection list of standard graphical objects "as is"
   CArrayObj        *GetListGraphObj(void)                                                               { return &this.m_list_all_graph_obj;   }
//--- Return (1) the complete collection list of graphical elements on canvas "as is", (2) the list of elements to be handled
   CArrayObj        *GetListCanvElm(void)                                                                { return &this.m_list_all_canv_elm_obj;}
   CArrayObj        *GetListCanvElmToProcess(void)                                                       { return &this.m_list_elm_to_process;  }
//--- Return the list of graphical elements by a selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)             { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);           }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)            { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);           }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)            { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);           }

and declare the collection timer event handler:

//--- (1) Event handlers, (2) timer, (3) deinitialization
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   void              OnTimer();
   void              OnDeinit(void);


In the class constructor, clear the list and set the sorted list flag to it:

//+------------------------------------------------+
//| Constructor                                    |
//+------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   this.m_type=COLLECTION_GRAPH_OBJ_ID;
   this.m_name_prefix=this.m_name_program+"_";
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_charts_control.Sort();
   this.m_list_charts_control.Clear();
   this.m_total_objects=0;
   this.m_is_graph_obj_event=false;
   this.m_list_deleted_obj.Clear();
   this.m_list_deleted_obj.Sort();
   this.m_list_all_canv_elm_obj.Clear();
   this.m_list_all_canv_elm_obj.Sort();
   this.m_list_elm_to_process.Clear();
   this.m_list_elm_to_process.Sort();
  }
//+------------------------------------------------------------------+


The method adding the graphical element on canvas to the list for handling:

//+------------------------------------------------------------------+
//| Add a graphical element on canvas to the list for handling       |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::AddCanvElmToProcessList(CForm *element)
  {
   if(this.GetCanvElementFromProcessList(element)!=NULL)
      return false;
   if(!this.m_list_elm_to_process.Add(element))
     {
      CMessage::ToLog(DFUN+element.TypeElementDescription()+element.Name()+": ",MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Get the pointer to the list of objects for handling. If failed to pass the object, passed to the method, to it, inform of that in the log and return false. Upon successful addition, return true.


The method that receives an element by name from the list for handling:

//+------------------------------------------------------------------+
//| Get an element by name from the list for handling                |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetCanvElementFromProcessList(CForm *element)
  {
   for(int i=this.m_list_elm_to_process.Total()-1;i>WRONG_VALUE;i--)
     {
      CForm *obj=this.m_list_elm_to_process.At(i);
      if(obj==NULL)
         continue;
      if(obj.Name()==element.Name())
         return obj;
     }
   .return NULL;
  }
//+------------------------------------------------------------------+

In the loop by the list of objects for handling, get the next object. If its name matches the name of the object passed to the method, return the pointer to the found object. Upon the loop completion, return NULL. No object found.


The method that returns the index of an element by name in the list for handling:

//+------------------------------------------------------------------+
//| Return the index of an element by name in the list for handling  |
//+------------------------------------------------------------------+
int CGraphElementsCollection::CanvElementIndexInProcessList(CForm *element) const
  {
   for(int i=this.m_list_elm_to_process.Total()-1;i>WRONG_VALUE;i--)
     {
      CForm *obj=this.m_list_elm_to_process.At(i);
      if(obj==NULL)
         continue;
      if(obj.Name()==element.Name())
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

In the loop by the list of objects for handling, get the next object. If its name matches the name of the object passed to the method, return the loop index. At the end of the loop, return -1, which means that the object was not found.


The handling method for the current ToolTip element:

//+------------------------------------------------+
//| Handle the current ToolTip element             |
//+------------------------------------------------+
void CGraphElementsCollection::TooltipCurrProcessing(CForm *tooltip,CForm *base)
  {
//--- If at least one empty object is passed, leave
   if(tooltip==NULL || base==NULL)
      return;
//--- Get the chart width and height
   int w=(int)::ChartGetInteger(tooltip.ChartID(),CHART_WIDTH_IN_PIXELS,tooltip.SubWindow());
   int h=(int)::ChartGetInteger(tooltip.ChartID(),CHART_HEIGHT_IN_PIXELS,tooltip.SubWindow());
//--- Get cursor coordinates
   int x=this.m_mouse.CoordX();
   int y=this.m_mouse.CoordY();
//--- If at the current X coordinate (cursor) the tooltip goes beyond the right edge of the chart, adjust the X coordinate
   if(x+tooltip.Width()>w)
      x=w-tooltip.Width();
//--- If at the current Y coordinate (cursor) the tooltip goes beyond the bottom edge of the chart, adjust the Y coordinate
   if(y+tooltip.Height()>h)
      y=h-tooltip.Height();
//--- If the tooltip object is shifted to the received cursor coordinates
   if(tooltip.Move(x,y))
     {
      //--- Set new relative tooltip coordinates
      tooltip.SetCoordXRelative(tooltip.CoordX()-base.CoordX());
      tooltip.SetCoordYRelative(tooltip.CoordY()-base.CoordY());
     }
  }
//+------------------------------------------------------------------+

The method logic is fully described in the code comments. The method shifts the tooltip object to the cursor coordinates and adjusts the received coordinates if the object goes beyond the edge of the screen.


The method for handling the previous ToolTip element:

//+------------------------------------------------+
//| Handle the previous ToolTip element            |
//+------------------------------------------------+
void CGraphElementsCollection::TooltipPrevProcessing(CForm *tooltip)
  {
//--- If an empty object is passed, leave
   if(tooltip==NULL)
      return;
//--- Set the object non-display flag, make it completely transparent and hide it
   tooltip.SetDisplayed(false);
   tooltip.SetOpacity(0,false);
   tooltip.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_NORMAL);
   tooltip.Hide();
//--- Get the index of the object in the list
   int index=this.CanvElementIndexInProcessList(tooltip);
//--- If the index is received, remove the object from the list
   if(index>WRONG_VALUE)
      this.m_list_elm_to_process.DetachElement(index);
  }
//+------------------------------------------------------------------+

Here, too, everything is quite transparent. The previous object, of course, is no longer under the cursor. This means it should be immediately hidden and the pointer to it should be removed from the list of objects to be handled.

If the Expert Advisor tested in the last article is launched on a chart and the chart period is changed to another one, the program will end with a critical error. This happens because the pointer to the form object is declared as static:

      //--- Declare static variables for the active form and status flags
      static CForm *form=NULL;

This means that when changing the period of the chart, the data will remain in it and the NULL check will yield a negative result, saying the object is valid:

      //--- In case of the mouse movement event
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         //--- If the cursor is above the form
         if(form!=NULL)
           {
            //--- If the move flag is set

The data set in the pointer will no longer refer to the memory area of ​this object. Hence the termination of the program when accessing the wrong place in memory. Fixing the error is easy: check the validity of the pointer, not its value:

      //--- In case of the mouse movement event
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         //--- If the cursor is above the form
         if(::CheckPointer(form)!=POINTER_INVALID)
           {
            //--- If the move flag is set
            if(move)
              {


The handling of the previous and current tooltips is now implemented as follows:

            //--- If the move flag is disabled
            else
              {
               //--- Get the ToolTip object assigned to the form and declare the previous ToolTip
               CForm *tooltip=form.GetTooltip();
               static CForm *tooltip_prev=NULL;
               //--- If the ToolTip object is received
               if(tooltip!=NULL && tooltip.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                 {
                  //--- If the previous ToolTip object exists
                  if(::CheckPointer(tooltip_prev)!=POINTER_INVALID && tooltip_prev.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                    {
                     //--- If the name of the previous ToolTip is not equal to the name of the current one, hide the previous ToolTip object
                     if(tooltip_prev.Name()!=tooltip.Name())
                        this.TooltipPrevProcessing(tooltip_prev);
                    }
                    
                  //--- Get the base object set in ToolTip
                  CForm *base=tooltip.GetBase();
                  //--- If the base object is received
                  //--- and if the name of the base object is the same as the name of the current form (the ToolTip is bound to the form)
                  if(base!=NULL && base.Name()==form.Name())
                    {
                     //--- Add ToolTip to the handling list, shift it to the cursor coordinates
                     //--- and write the current ToolTip to the variable as the previous one
                     if(this.AddCanvElmToProcessList(tooltip))
                       {
                        this.TooltipCurrProcessing(tooltip,base);
                        tooltip_prev=tooltip;
                       }
                    }
                 }
               //--- If there is no current ToolTip object, but the previous ToolTip exists, hide the previous ToolTip object
               else if(::CheckPointer(tooltip_prev)!=POINTER_INVALID && tooltip_prev.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                  this.TooltipPrevProcessing(tooltip_prev);
               
               //--- The undefined mouse status in mouse_state means releasing the left button
               //--- Assign the new mouse status to the variable
               if(mouse_state==MOUSE_FORM_STATE_NONE)
                  mouse_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED;
               //--- Handle moving the cursor mouse away from the graphical element
               this.FormPostProcessing(form,id,lparam,dparam,sparam);
              }

All logic is described in sufficient detail in the comments to the code. In short, if the object is under the cursor, then it is added to the list for handling and its coordinates are shifted to the cursor coordinates. If the object is not under the cursor, then it is removed from the list and hidden.


The collection timer event handler:

//+------------------------------------------------+
//| Timer                                          |
//+------------------------------------------------+
void CGraphElementsCollection::OnTimer(void)
  {
//--- Handle the elements under the cursor
//--- In the loop by the list of objects for handling
   for(int i=this.m_list_elm_to_process.Total()-1;i>WRONG_VALUE;i--)
     {
      //--- get the next object from the list
      CForm *obj=this.m_list_elm_to_process.At(i);
      if(obj==NULL)
         continue;
      //--- If the state of the object is the full loop is completed
      if(obj.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED)
        {
         //--- remove the pointer to the object from the list and go to the next iteration
         this.m_list_elm_to_process.DetachElement(i);
         continue;
        }
      //--- Call the object timer event handler
      obj.OnTimer();
     }

//--- Work with active elements of the collection
//--- In the loop by the collection list of graphical elements
   for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++)
     {
      //--- Get the next object from the list
      CWinFormBase *obj=this.m_list_all_canv_elm_obj.At(i);
      if(obj==NULL)
         continue;
      //--- Get the list of active elements from the object
      CArrayObj *list=obj.GetListMainActiveElements();
      if(list==NULL || list.Total()==0)
         continue;
      //--- In the loop by the list of active elements
      for(int j=0;j<list.Total();j++)
        {
         //--- get the next object and
         CWinFormBase *elm=list.At(j);
         if(elm==NULL)
            continue;
         //--- call the object timer event handler
         elm.OnTimer();
        }
     }
  }
//+------------------------------------------------------------------+

The handler logic is fully described in the comments to the code. Work in the two loops — by the list of objects for handling (ToolTip) and by the list of active elements (objects with visual animation effects)


In the main object of the CEngine library in \MQL5\Include\DoEasy\Engine.mqh, create the timer of the graphical elements of the library and the timer event handler.

In the private section, declare the handler:

//--- Handling events of (1) orders, deals and positions, (2) accounts, (3) graphical objects and (4) canvas elements
   void                 TradeEventsControl(void);
   void                 AccountEventsControl(void);
   void                 GraphObjEventsControl(void);
   void                 GraphElmEventsControl(void);
//--- (1) Working with a symbol collection and (2) symbol list events in the market watch window


In the class constructor, create yet another timer — the graphical elements collection timer:

//+------------------------------------------------+
//| CEngine constructor                            |
//+------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program_type=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name_program=::MQLInfoString(MQL_PROGRAM_NAME);
   this.m_name_prefix=this.m_name_program+"_";
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE);
   this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE);
   this.CreateCounter(COLLECTION_TICKS_COUNTER_ID,COLLECTION_TICKS_COUNTER_STEP,COLLECTION_TICKS_PAUSE);
   this.CreateCounter(COLLECTION_CHARTS_COUNTER_ID,COLLECTION_CHARTS_COUNTER_STEP,COLLECTION_CHARTS_PAUSE);
   this.CreateCounter(COLLECTION_GRAPH_OBJ_COUNTER_ID,COLLECTION_GRAPH_OBJ_COUNTER_STEP,COLLECTION_GRAPH_OBJ_PAUSE);
   this.CreateCounter(COLLECTION_GRAPH_ELM_COUNTER_ID,COLLECTION_GRAPH_ELM_COUNTER_STEP,COLLECTION_GRAPH_ELM_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+


In the timer event handler, add code blocks for handling the timer of graphical elements:

//+------------------------------------------------+
//| CEngine timer                                  |
//+------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//--- If this is not a tester, work with collection events by timer
   if(!this.IsTester())
     {
   //--- Timer of the collections of historical orders and deals, as well as of market orders and positions
      int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
      CTimerCounter* cnt1=this.m_list_counters.At(index);
      if(cnt1!=NULL)
        {
         //--- If unpaused, work with the order, deal and position collections events
         if(cnt1.IsTimeDone())
            this.TradeEventsControl();
        }
   //--- Account collection timer
      index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
      CTimerCounter* cnt2=this.m_list_counters.At(index);
      if(cnt2!=NULL)
        {
         //--- If unpaused, work with the account collection events
         if(cnt2.IsTimeDone())
            this.AccountEventsControl();
        }
   //--- Timer 1 of the symbol collection (updating symbol quote data in the collection)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
      CTimerCounter* cnt3=this.m_list_counters.At(index);
      if(cnt3!=NULL)
        {
         //--- If the pause is over, update quote data of all symbols in the collection
         if(cnt3.IsTimeDone())
            this.m_symbols.RefreshRates();
        }
   //--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
      CTimerCounter* cnt4=this.m_list_counters.At(index);
      if(cnt4!=NULL)
        {
         //--- If the pause is over
         if(cnt4.IsTimeDone())
           {
            //--- update data and work with events of all symbols in the collection
            this.SymbolEventsControl();
            //--- When working with the market watch list, check the market watch window events
            if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
               this.MarketWatchEventsControl();
           }
        }
   //--- Trading class timer
      index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
      CTimerCounter* cnt5=this.m_list_counters.At(index);
      if(cnt5!=NULL)
        {
         //--- If unpaused, work with the list of pending requests
         if(cnt5.IsTimeDone())
            this.m_trading.OnTimer();
        }
   //--- Timeseries collection timer
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- If the pause is over, work with the timeseries list (update all except the current one)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
        
   //--- Timer of timeseries collection of indicator buffer data
      index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID);
      CTimerCounter* cnt7=this.m_list_counters.At(index);
      if(cnt7!=NULL)
        {
         //--- If the pause is over, work with the timeseries list of indicator data (update all except for the current one)
         if(cnt7.IsTimeDone()) 
            this.IndicatorSeriesRefreshAll();
        }
   //--- Tick series collection timer
      index=this.CounterIndex(COLLECTION_TICKS_COUNTER_ID);
      CTimerCounter* cnt8=this.m_list_counters.At(index);
      if(cnt8!=NULL)
        {
         //--- If the pause is over, work with the tick series list (update all except the current one)
         if(cnt8.IsTimeDone())
            this.TickSeriesRefreshAllExceptCurrent();
        }
   //--- Chart collection timer
      index=this.CounterIndex(COLLECTION_CHARTS_COUNTER_ID);
      CTimerCounter* cnt9=this.m_list_counters.At(index);
      if(cnt9!=NULL)
        {
         //--- If unpaused, work with the chart list
         if(cnt9.IsTimeDone())
            this.ChartsRefreshAll();
        }
        
   //--- Graphical objects collection timer
      index=this.CounterIndex(COLLECTION_GRAPH_OBJ_COUNTER_ID);
      CTimerCounter* cnt10=this.m_list_counters.At(index);
      if(cnt10!=NULL)
        {
         //--- If unpaused, work with the list of graphical objects
         if(cnt10.IsTimeDone())
            this.GraphObjEventsControl();
        }
        
   //--- The timer for the collection of graphical elements on the canvas
      index=this.CounterIndex(COLLECTION_GRAPH_ELM_COUNTER_ID);
      CTimerCounter* cnt11=this.m_list_counters.At(index);
      if(cnt11!=NULL)
        {
         //--- If the pause is over, work with the list of graphical elements on the canvas
         if(cnt11.IsTimeDone())
            this.GraphElmEventsControl();
        }
        
     }
//--- If this is a tester, work with collection events by tick
   else
     {
      //--- work with events of collections of orders, deals and positions by tick
      this.TradeEventsControl();
      //--- work with events of collections of accounts by tick
      this.AccountEventsControl();
      //--- update quote data of all collection symbols by tick
      this.m_symbols.RefreshRates();
      //--- work with events of all symbols in the collection by tick
      this.SymbolEventsControl();
      //--- work with the list of pending orders by tick
      this.m_trading.OnTimer();
      //--- work with the timeseries list by tick
      this.SeriesRefresh(data_calculate);
      //--- work with the timeseries list of indicator buffers by tick
      this.IndicatorSeriesRefreshAll();
      //--- work with the list of tick series by tick
      this.TickSeriesRefreshAll();
      //--- work with the list of graphical objects by tick
      this.GraphObjEventsControl();
      //--- work with the list of graphical elements on canvas by tick
      this.GraphElmEventsControl();
     }
  }
//+------------------------------------------------------------------+


The event handling method for graphical elements on canvas:

//+------------------------------------------------------------------+
//| Event handling method for graphical elements on canvas           |
//+------------------------------------------------------------------+
void CEngine::GraphElmEventsControl(void)
  {
   this.m_graph_objects.OnTimer();
  }
//+------------------------------------------------------------------+

As soon as the pause timer of the collection of graphical elements ends, this method is called, which in turn calls the timer handler of the collection class of graphical elements. The default call will occur every 16 milliseconds (timer pause).

Everything is ready for a test.


Test

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

In the OnInit() EA handler, on the second panel of the first tab of the TabControl in the SplitContainer control, create the ProgressBar control:

                  //--- On each of the control panels...
                  for(int j=0;j<2;j++)
                    {
                     //--- Get the panel by loop index
                     CSplitContainerPanel *panel=split_container.GetPanel(j);
                     if(panel==NULL)
                        continue;
                     //--- set its description for the panel
                     panel.SetDescription(TextByLanguage("Панель","Panel")+string(j+1));
                     
                     //--- If this is the first tab and the second panel
                     if(n==0 && j==1)
                       {
                        //--- Create the ProgressBar control on it
                        if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,4,4,100,12,clrNONE,255,false,false))
                          {
                           CProgressBar *progress_bar=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,0);
                           if(progress_bar!=NULL)
                             {
                              Print(DFUN,progress_bar.TypeElementDescription()," ",progress_bar.Name());
                             }
                          }
                       }
                     
                     //--- ...create a text label with the panel name
                     if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,4,4,panel.Width()-8,panel.Height()-8,clrDodgerBlue,255,true,false))
                       {
                        CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                        if(label==NULL)
                           continue;
                        label.SetTextAlign(ANCHOR_CENTER);
                        label.SetText(panel.Description());
                       }
                    }

To make sure the object is created, get the pointer to it and send its description to the log.

Compile the EA and launch it on the chart:


The functionality declared for the current article works well. We can see a constantly changing number in the chart comments. This is the data from the progress bar timer of the ProgressBar control.


What's next?

In the next article, I will continue working on the ProgressBar object.

All files of the current library version, test EA and chart event control indicator for MQL5 are attached below.

Back to contents

*Previous articles within the series:

 
DoEasy. Controls (Part 20): SplitContainer WinForms object
DoEasy. Controls (Part 21): SplitContainer control. Panel separator
DoEasy. Controls (Part 22): SplitContainer. Changing the properties of the created object
DoEasy. Controls (Part 23): Improving TabControl and SplitContainer WinForms objects
DoEasy. Controls (Part 24): Hint auxiliary WinForms object
DoEasy. Controls (Part 25): Tooltip WinForms object