DoEasy. Controls (Part 26): Finalizing the ToolTip WinForms object and moving on to ProgressBar development
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.
*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
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/11732
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use