English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 26): Apurando o objeto WinForms "ToolTip" e desenvolvendo o "ProgressBar".

DoEasy. Controles (Parte 26): Apurando o objeto WinForms "ToolTip" e desenvolvendo o "ProgressBar".

MetaTrader 5Exemplos | 13 fevereiro 2023, 16:20
240 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

Neste artigo, prosseguiremos a construção do controle ToolTip. Quando o cursor é movido sobre um objeto, a ponta da ferramenta deve surgir após uma breve pausa e qualquer outra dica anterior deve ser escondida. Se o cursor permanecer sobre o objeto, a dica deve sumir após um certo período de tempo. No artigo anterior, alcançamos o objetivo de fazer com que a ponta da ferramenta aparecesse instantaneamente ao posicionar o cursor sobre o objeto designado. Agora, vamos aperfeiçoar o comportamento da ponta da ferramenta para ficar mais alinhado com o comportamento padrão dos binários compilados do MS Visual Studio. Quando o cursor é colocado sobre um objeto, haverá uma pausa antes da ponta da ferramenta surgir suavemente na tela. Se o cursor é retirado do objeto, a ponta da ferramenta será escondida. Se o cursor é deixado sobre o objeto, a ponta da ferramenta desaparecerá após um tempo. Para implementar este comportamento, precisamos usar o temporizador já criado para os elementos gráficos da coleção. Cada objeto terá seu manipulador virtual para eventos do temporizador. Este temporizador deve ser implementado diretamente nas classes de objetos que precisam ser processados.

Para aprimorar a eficiência da biblioteca, criaremos uma lista de objetos ativos que indicarão quais objetos devem ser processados no temporizador. Isso facilita o rastreamento dos objetos desejados e evita a varredura constante de todos os elementos gráficos. O loop será executado apenas para objetos adicionados à lista, possibilitando que a GUI se torne mais dinâmica. Além de reagirem à interação do mouse, os objetos também utilizarão as animações definidas para efeitos visuais. Até mesmo ícones animados simples podem ser facilmente criados, basta criar um objeto com quadros de animação e adicioná-los à lista de objetos ativos. No manipulador de eventos temporizados deste objeto, basta alternar os quadros de animação previamente criados. Hoje, teremos o foco em criar o manipulador de eventos temporizados para o controle ToolTip. Este manipulador implementará o comportamento de ponta de ferramenta descrito anteriormente, envolvendo a interação com o mouse. Portanto, tanto o manipulador de eventos do elemento gráfico quanto o temporizador serão tratados.

Uma vez que o objeto ToolTip WinForms tenha sido finalizado, procedermos à criação do controle ProgressBar (controle "barra de progresso" do Windows). Vamos criar apenas uma versão estática do objeto - suas propriedades e aparência. Chegaremos ao fundo da questão no próximo artigo.


Modificando as classes da biblioteca

No arquivo \MQL5\Include\DoEasy\Defines.mqh adicionamos parâmetros de temporizador para elementos gráficos na tela:

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

Cada coleção de objetos de biblioteca tem seu próprio manipulador de temporizador, com seus respetivos parâmetros, como pausa, incremento do contador e identificador. Aqui adicionamos os mesmos parâmetros para o novo temporizador.

Para o novo controle ProgressBar, acrescentamos valores padrão:

#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


Antecipando o que virá, alguns controles gráficos terão efeitos visuais. Por exemplo, para a linha de progresso, faremos um brilho ao longo da mesma, conforme implementado no Windows. O objeto que aplica o brilho será colocado no topo da linha e se moverá ao longo dela. Para tal objeto, é necessário especificar seu tipo na lista de tipos de objetos da biblioteca:

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


E vamos adicionar novos tipos de elementos gráficos à lista de tipos de elementos gráficos na tela:

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


Neste artigo o controle ToolTip pode ter vários estados ao interagir com o mouse. Como o objeto é animado, seu comportamento e estado devem ser descritos de alguma forma. Pode esperar antes de aparecer, aparecer, aguardar antes de desaparecer, estar em estado de atenuação ou estar em seu estado normal.
Vamos descrever seus estados em uma nova lista de estados de exibição de controle:

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

Este tipo de estados também pode ser aplicado a outros controles para os quais é possível criar posteriormente sequências de animação. Não necessariamente o objeto tem que aparecer ou desaparecer gradualmente. O aparecimento gradual de um objeto pode ser equiparado ao desdobramento de uma lista suspensa, e o estado de desaparecimento gradual (fading out) pode ser equiparado à minimização de um objeto. No entanto, podemos sempre acrescentar a esta lista mais tarde.

A barra de progresso pode ter três estilos de linhas. Vamos descrevê-las em uma enumeração:

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

Hoje faremos apenas um estilo, que será a uma faixa contínua.


O estado do objeto que tem exibição animada deve ser armazenado e recuperado das propriedades do objeto.
Adicionamos as novas propriedades do elemento gráfico à enumeração de propriedades inteiras do mesmo na tela e aumentamos o número total de propriedades inteiras de 129 para 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
//+------------------------------------------------------------------+


Adicionaremos novas propriedades à lista de critérios para ordenar os elementos gráficos na tela:

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

Agora poderemos selecionar, filtrar e ordenar elementos gráficos por novas propriedades.


No arquivo \MQL5\Include\DoEasy\Data.mqh, adicionamos os índices das novas mensagens:

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

e as mensagens de texto correspondentes aos índices recém-adicionados:

   {"Не установлен","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"},
  };
//+---------------------------------------------------------------------+


Ao criar coleções de objetos, usamos uma classe de lista derivada de uma classe de matriz dinâmica de ponteiros para instâncias da classe CObject e seus descendentes. A classe CArrayObj tem um método Detach() que extrai um ponteiro para um objeto de uma lista e retorna o ponteiro resultante. Nem sempre precisamos usar mais um ponteiro quando o extraímos de uma lista.
Assim, na classe derivada no arquivo
\MQL5\Include\DoEasy\Collections\ListObj.mqh criaremos um método que extrai um ponteiro da lista sem devolvê-lo:

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

Isto é simples: extraímos o ponteiro da lista e, se isso falhar, retornamos false.
Se a extração for bem sucedida , zeramos o ponteiro resultante e retornamos true.


Vamos modificar ligeiramente a classe do objeto de pausa no arquivo \MQL5\Include\DoEasy\Services\Pause.mqh.

Vamos adicionar um método público que retorna o início da contagem regressiva da pausa em milissegundos desde a inicialização do sistema:

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

Precisamos desse método ao fazer os controles no temporizador.


Como adicionamos novas propriedades inteiras ao elemento gráfico, precisamos inicializá-las no objeto base do elemento gráfico e adicionar novos campos de estrutura de objeto.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, adicionamos novos campos à estrutura:

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

Precisamos da estrutura do objeto a fim de salvar e recuperar corretamente o objeto.


No bloco de métodos simplificados de acesso de propriedade de objeto , escreveremos métodos para lidar com estas novas propriedades:

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

Os métodos de definição de propriedade escrevem os valores recebidos por eles nas propriedades do objeto, e os métodos de retorno devolvem valores previamente definidos a partir das propriedades do objeto.

Em ambos os construtores, definimos os valores padrão para as novas propriedades:

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


No método que cria a estrutura do objeto, escrevemos os valores das propriedades do objeto nos campos da estrutura:

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


No método que cria um objeto a partir de uma estrutura, escrevemos valores para as propriedades do objeto a partir dos campos da estrutura:

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


No arquivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh da classe do objeto base de todos os objetos gráficos da biblioteca, escrevemos o retorno da descrição dos novos tipos de objetos no método que retorna a descrição do tipo de elemento gráfico:

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

Todos esses objetos já foram declarados, mas iremos criá-los mais tarde, após finalizarmos a classe do objeto ToolTip.


Para trabalhar com gráficos, precisamos criar um manipulador de eventos com temporizador. Como todos os elementos gráficos são herdados da classe objeto-forma, é nesta classe que declararemos um manipulador virtual de temporizador.

Na seção pública do arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, escrevemos novamente o seguinte método:

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

Este manipulador não faz nada aqui, ele deve ser restaurado nas classes em que os eventos do temporizador devem ser tratados.


Declaramos um método que devolve o número de objetos ToolTip anexados ao elemento:

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

Apenas um objeto dica de ferramenta pode ser atribuído a um objeto gráfico. Mas vários objetos ToolTip podem ser anexados a um objeto, e outros objetos anexados a ele podem ser atribuídos às dicas de ferramenta criadas. Isto se deve ao fato de que nem todos os objetos da biblioteca podem ser anexados aos objetos ToolTip. Mas podemos sempre criar dicas de ferramenta no contêiner ao qual os objetos são anexados, e depois atribui-las a esses objetos. Este método servirá para saber o número de objetos ToolTip criados e anexados ao contêiner.

Vamos modificar o método que atribui o ToolTip ao objeto.

Adicionamos verificações adicionais de o tipo incorreto e de número de dicas anexas maior que zero:

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

Se erroneamente tentarmos anexar um tipo diferente de objeto em vez de um objeto de dica, o método irá relatar um erro. Se um ToolTip já estiver anexado ao objeto, uma mensagem de erro também será exibida e o método retornará false.


Método que devolve o número de objetos ToolTip:

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

Se este objeto for um contêiner, qualquer número de objetos dica de ferramenta pode ser anexado a ele. O método retorna a quantidade de objetos existente. No loop, usamos número total de objetos anexados para, assim, obter o próximo objeto. Se o tipo de objeto for uma dica de ferramenta, aumentamos o contador de objetos (variável res). No final do loop, retornamos o resultado da contagem registrado na variável res.


O temporizador da coleção de elementos gráficos é atualizado uma vez a cada 16 milissegundos, e para fazer com que a dica de ferramenta apareça ou se desvaneça por um segundo, precisamos de 1000 milissegundos divididos pelo período de atualização do temporizador. Como resultado, teremos que mudar a opacidade do objeto aproximadamente a cada 1000/16=62,5 milissegundos.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ToolTip.mqh declaramos a variável para armazenar o incremento de transparência:

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


Na seção pública da classe, declaramos um manipulador de eventos de temporizador virtual:

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


E em cada um dos construtores, vamos calcular o valor da variável que armazena o incremento de transparência, com base nos valores padrão para a duração da surgimiento/desvanecimento e o incremento do contador para o temporizador da coleção de elementos gráficos:

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


No método de inicialização de variáveis, escrevemos os valores padrão para os atrasos de mudança de estado dos objetos dica da ferramenta:

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


No método que exibe a dica da ferramenta, movemos o comando de exibição do objeto até o fim e removemos o redesenho completo do objeto inteiro:

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

O método fica assim:

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

Este ajuste reduz ligeiramente o 'piscar' desagradável do elemento à medida que sua opacidade muda gradualmente.


No método que desenha a dica de ferramenta, no final adicionamos uma atualização da tela modificada:

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

Quando mudo a opacidade para zero (o objeto é completamente transparente), noto um comportamento estranho nos métodos que desenham as primitivas com suavização - eles não reagem bem ao valor da transparência. Quando o valor da opacidade é muito baixo, as linhas traçadas com estes métodos se destacam muito contra um objeto que tem o mesmo valor de opacidade. A adição de uma linha de atualização de tela é uma tentativa de reduzir este efeito desagradável. Entretanto, enquanto não estiver funcionando...


Manipulador de eventos do temporizador:

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

A lógica do método é descrita nos comentários ao código. Cada vez que o temporizador do objeto é acionado, este manipulador é chamado. A cada iteração do temporizador, precisamos verificar o estado do objeto e seus contadores de espera ou de mudança de estado.

Primeiramente, o estado normal do objeto é registrado. Nesse estado, a dica de ferramenta está completamente oculta. Se o objeto se encontra neste estado, ele deve esperar um determinado tempo antes de iniciar seu processo de exibição suave. Portanto, quando o objeto está no estado normal, é estabelecido um período de espera para que a dica de ferramenta surja gradualmente e, após esse tempo, a contagem regressiva do temporizador é iniciada e o método é encerrado.

Na próxima etapa, o objeto está no estado de espera pelo processo de surgimento suave e verificamos o contador. Se o tempo de espera terminar, mudamos o estado do objeto para surgimento suave e prosseguimos. Durante o processo de surgimento suave, aumentamos gradualmente a opacidade do objeto em cada iteração do temporizador até que ele se torne completamente opaco. Em seguida, mudamos o estado do objeto para espera pelo processo de desvanecimento suave, já que é importante que o objeto desvaneça suavemente após cerca de cinco segundos.

O processo de desvanecimento suave é idêntico ao processo de surgimento suave. Ao final do laço, o estado de término de laço de mudanças é registrado no objeto. Se o estado normal for registrado imediatamente, o laço inteiro será reiniciado, pois o estado normal do objeto é justamente a condição de partida para o início do laço. E isso só deve ser registrado no objeto quando o ponteiro para ele for removido da lista de objetos que precisam ser processados no temporizador. Isso é feito na classe coleção de elementos gráficos. Quando o objeto apresenta o estado de término de laço completo, o método remove o ponteiro para o objeto da lista de processamento e estabelece seu estado normal. Na próxima vez que o cursor apontar para o objeto, ele será colocado de volta na lista de processamento no temporizador. Da mesma forma, o ponteiro para o objeto é removido da lista se o cursor for retirado da área do objeto, já pronto para o laço de processamento, ou mesmo durante este ciclo.


Gradualmente, estamos chegando à criação do novo objeto WinForms ProgressBar. Para esse controle, precisaremos criar um objeto auxiliar chamado "Brilho". Objetos deste tipo serão usados para a aparência visual de alguns elementos da GUI, e no objeto ProgressBar, o brilho deve percorrer a linha de progresso. A concepção será a seguinte: desenhamos uma mancha branca na forma e tamanho necessários e a espalhamos do centro para as bordas. O objeto-brilho será tão semi-transparente quanto o objeto-sombra. E aqui chegamos ao fato de que precisamos herdar esse objeto da sombra para usar seus métodos de desvanecimento e fazer a renderização por conta própria. Para herdar corretamente da sombra, precisamos adicionar um construtor protegido a ele, no qual especificaremos o tipo de objeto a ser criado.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh fazemos alterações na classe do objeto sombra.

Movemos os métodos de borrão gaussiano e a matriz de coeficientes de ponderação da seção privada para a seção protegida da classe, e declaramos um construtor protegido na mesma seção. Na seção pública da classe, escrevemos um método virtual que devolva o sinalizador para manter o objeto como uma propriedade real:

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

Aos poucos adicionaremos métodos que retornam sinalizadores que indicam o objeto suporta as propriedades a todos os elementos gráficos, já que estes são métodos padrão de biblioteca que precisaremos mais tarde. Acrescentamos aqui tal método simplesmente para garantir que não voltaremos a esta questão nesta classe novamente.


Construtor protegido:

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

Nos parâmetros formais do construtor, é passado o tipo do objeto criado, que é passado na linha de inicialização para o construtor da classe pai. Isto é feito em todos os objetos da biblioteca. Tudo o resto aqui é feito exatamente da mesma forma que no construtor paramétrico.

No construtor paramétrico, apagamos a linha no final que faz com que a sombra seja desenhada assim que ela é criada:

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

A presença desta linha causou um comportamento incorreto do objeto sombra durante a criação de elementos GUI - primeiro a sombra aparecia em um gráfico em branco e depois a aparência da GUI era construída. Agora a sombra não aparecerá primeiro.


Controle ProgressBar

Primeiro, criamos um objeto auxiliar brilho na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\ no arquivo GlareObj.mqh. Não precisaremos deste objeto hoje, ele será feito inteiramente à imagem da classe do objeto sombra. Apenas a cor do brilho que está sendo desenhado será branca:

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

No próximo artigo, faremos todas as mudanças necessárias nesta classe. Por enquanto, vamos deixar as coisas como estão.


O controle da barra de progresso consistirá em dois objetos, a base e a linha de progresso. A base será o próprio controle. A barra de progresso será um objeto auxiliar separado com propriedades próprias que podem ser alteradas em relação ao objeto principal.

Na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\, criamos um novo arquivo Splitter.mqh da classe CSplitter. A classe deve ser herdada do objeto base de todos os objetos WinForms da biblioteca, cujo arquivo deve ser anexado ao arquivo da classe que está sendo criada:

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


Na seção protegida da classe, vamos declarar um método que exibe o objeto brilho na barra de progresso e um construtor protegido. Na seção pública, escrevemos métodos que definem e retornam valores das propriedades do objeto, métodos virtuais para manter propriedades, construtor paramétrico e manipulador de eventos do temporizador de classe:

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

Vamos dar uma olhada nos métodos declarados.


Construtor protegido:

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

O tipo de elemento gráfico a ser criado é passado para o construtor, que é passado para a classe pai na linha de inicialização. O tipo de objeto gráfico da biblioteca é definido como "auxiliar", o objeto não tem moldura, e são definidas as cores padrão.


Construtor paramétrico:

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

Isto é exatamente o mesmo que o construtor protegido, mas o tipo do objeto não é passado em parâmetros formais, mas é codificado de forma forçada na linha de inicialização.

O método que põe o brilho estará vazio por enquanto, já que o objeto brilho ainda não está pronto, e sua criação e depuração está programada para o próximo artigo:

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


No gerenciador de eventos do temporizador, simplesmente mostraremos o valor retornado a partir de GetTickCount() por enquanto, apenas para garantir que este objeto seja incluído na lista de objetos ativos designados ao gerenciador de eventos do temporizador da biblioteca:

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


Antes de começarmos a criar o controle ProgressBar, precisamos ter certeza de que os objetos elementos gráficos têm a capacidade de se processarem a si mesmos no temporizador da biblioteca.

A ideia é a seguinte: cada um dos elementos gráficos que têm processamento independente planejado no temporizador será considerado um elemento ativo. Para gerenciar esses elementos ativos, vamos criar uma lista que armazenará as referências a esses objetos. Essa lista será sempre criada no objeto principal e, portanto, será acessível na classe coleção de elementos gráficos. Na classe coleção de temporizadores, percorreremos todos os objetos forma principais, obteremos as referências aos elementos ativos e chamaremos seus manipuladores de eventos de temporizador. Dessa forma, não precisaremos procurar cada elemento gráfico no objeto principal, mas criaremos imediatamente uma lista com esses elementos no objeto principal e os processaremos.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh do objeto base de todos os objetos WinForms da biblioteca, escreveremos o arquivo de inclusão da classe de objetos brilho e na seção protegida declararemos um ponteiro para a lista de elementos ativos:

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

Aqui a inclusão do arquivo de objeto brilho nos dará a capacidade de criá-lo e usá-lo em muitos dos elementos gráficos da biblioteca.

Na seção pública da classe, declaramos métodos para trabalhar com a lista de elementos ativos:

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


Vamos adicionar o destruidor da classe:

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

Se a lista de elementos ativos tiver sido criada, limpamos a lista e excluímos o objeto lista criado.


Nos construtores da classes, vamos escrever a criação do objetos lista de elementos ativos:

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


No método que retorna a descrição da propriedade inteira do elemento, escrevemos blocos de código para retornar as descrições das novas propriedades do objeto WInForms:

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


Método que devolve uma lista de elementos ativos do objeto principal:

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

Aqui: obtemos um ponteiro para o objeto principal. Se NULL for devolvido é porque é o principal. Obtemos a lista de elementos ativos a partir do objeto principal. Se o painel não puder ser obtido, notificamos isso e retornamos false. Caso contrário, retornamos um ponteiro para lista.


Método que devolve uma lista de elementos ativos do objeto principal:

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

Se o ponteiro para a lista do objeto principal for recuperado, o número de elementos da lista será devolvido, caso contrário, zero.


Método que retorna o índice do objeto especificado na lista de elementos ativos do objeto principal:

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

O método recebe o ponteiro para o objeto cujo índice devemos encontrar. Obtemos a lista de elementos ativos a partir do objeto principal. Com um laço, obtemos o próximo objeto, e se seu nome for igual ao nome do objeto passado para o método, o índice do laço é retornado. No final do laço ciclo retornamos -1, o que significa objeto não encontrado.


Método que retorna um elemento da lista de elementos ativos do objeto principal por índice:

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

O índice do objeto a ser devolvido da lista é passado para o método. Usamos o índice para obter a lista de elementos ativos do objeto principal e retornamos um ponteiro para o objeto. Se a lista não puder ser obtida, o método retornará NULL.


Método que retorna o sinalizador de presença de objeto segundo o nome na lista de elementos ativos do objeto principal:

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

O método recebe o nome do objeto cuja presença na lista deve ser conhecida. Obtemos a lista de elementos ativos a partir do objeto principal. Com um laço, obtemos o próximo objeto, e se seu nome for igual ao nome do objeto passado para o método, o índice do laço é retornado (true). Ao final do laço, retornamos false, o que indica que o objeto não está na lista.


Método que adiciona o objeto especificado à lista de elementos ativos do objeto principal:

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

Obtemos a lista de elementos ativos a partir do objeto principal. Se um objeto com este nome já existe na lista, devolvemos false. Caso contrário, devolvemos o resultado da adição do objeto à lista.


Método que adiciona o objeto especificado à lista de elementos ativos do objeto principal:

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

Retornamos o resultado da chamada do método acima. Especificamos o objeto atual como o objeto a ser adicionado à lista.


Método que remove o objeto especificado da lista de elementos ativos do objeto principal:

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

Recebemos um ponteiro para a lista de elementos ativos no objeto principal. Obtemos o índice do objeto na lista, cujo ponteiro foi passado para o método. Obtemos um ponteiro para o objeto removido da lista. Se o objeto não pôde ser removido, devolvemos false. Caso contrário, zeramos o ponteiro e retornamos true.


Método que remove o objeto especificado a partir da lista de elementos ativos do objeto principal:

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

Retornamos o resultado da chamada do método acima. O objeto a ser retirado é o objeto atual.


Criando o controle ProgressBar.

No diretório da biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, criamos um novo arquivo ProgressBar.mqh da classe CProgressBar. Como planejamos tornar possível anexar outros controles ao objeto para ampliar sua funcionalidade, a classe deve ser herdada da classe do objeto contêiner. O arquivo da classe contêiner, juntamente com o arquivo de classe da barra de progresso, deve ser anexado ao arquivo objeto que está sendo criado:

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


Na seção privada da classe, vamos declarar métodos para a criação de um novo objeto gráfico e barra de progresso, bem como um método para a inicialização da classe:

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


Na seção protegida da classe, declaramos um construtor protegido:

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:


Na seção pública da classe, vamos declarar métodos para definir e recuperar propriedades do objeto, um método para obter um ponteiro para o objeto barra de progresso, métodos que retornam sinalizadores de suporte de propriedades, um construtor paramétrico e um manipulador de eventos do temporizador:

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

Alguns métodos de configuração de propriedades do objeto, após a definição de uma propriedade, definem a mesma propriedade no objeto barra de progresso correspondente.

Vamos dar uma olhada nos métodos declarados.


Construtor protegido:

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

O tipo de objeto a ser criado é passado para o método, que é definido para a classe pai na linha de inicialização. O tipo de objeto gráfico da biblioteca é definido como "controle padrão". O método de inicialização da propriedade do objeto e o método que cria o objeto barra de progresso são então chamados.


Construtor paramétrico:

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

Aqui, o tipo de controle é codificado de forma rígida na linha de inicialização.


Método de inicialização das propriedades do elemento:

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

O objeto tem uma borda de 1 pixel de largura em cada lado, com uma aparência lisa. As cores e outras características são definidas como padrão. O comprimento da barra de progresso é ajustado para a metade da largura do objeto.


Método que cria o objeto barra de progresso:

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

A lógica do método é comentada em sua listagem. A altura da barra de progresso é estabelecida de forma que ela se ajuste completamente no contêiner sem ser ocultada pelos contornos do mesmo. Depois de criarmos a barra de progresso, adicionamos todo o objeto à lista de elementos ativos. Dessa forma, ele será incluído no gerenciador de eventos do temporizador para elementos gráficos da biblioteca e será processado ali. Desta maneira, no temporizador desse objeto, podemos criar funcionalidades que não dependam do usuário. Nesse objeto, será um efeito visual, que será abordado na próxima seção.


Método virtual que cria um novo objeto gráfico:

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

Para que um objeto contêiner possa criar objetos anexos dentro de si, existe um método virtual no qual esses objetos são criados. Para cada controle, a lista de objetos criados nele pode ser diferente. Este objeto permite criar um objeto barra de progresso e um objeto brilho. Por enquanto, não é necessário criar mais objetos aqui.


Manipulador de eventos do temporizador:

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

Aqui: obtemos um ponteiro para o objeto barra de progresso e chamamos seu manipulador de eventos do temporizador. Como o brilho deve correr exatamente ao longo da barra de progresso, vamos implementá-lo no temporizador do objeto da classe CBarProgressBar. E é por isso que seu temporizador é chamado aqui, seu valor GetTickCount() é escrito como um comentário sobre o gráfico por enquanto.


No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh, no método que define os parâmetros do objeto anexado, escreveremos um bloco de código para definir os parâmetros do objeto dica de ferramenta recém-criado:

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

Basta definir os valores padrão definidos nos construtores dessas classes.


Agora precisamos tornar possível que cada objeto contêiner possa rapidamente anexar objetos dica de ferramentas ao elemento atual e ao elemento especificado. Isto facilitará a anexação de dicas de ferramentas aos objetos desejados anexados aos contêineres.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh, adicionamos a inclusão do arquivo do controle ProgressBar:

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


Na seção pública, declaramos dois métodos para criar e anexar objetos dica de ferramenta:

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


No método que cria um novo objeto gráfico, vamos adicionar uma linha de código para criar um novo controle ProgressBar:

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


Método que cria e anexa um objeto ToolTip ao elemento atual:

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

A lógica do método é descrita nos comentários ao código. Todos os dados necessários para criar um objeto dica de ferramenta são passados para o método. Quando um objeto é criado, ele é automaticamente anexado ao objeto atual. Em seguida, recuperamos o objeto ToolTip mais recente da lista de objetos dica de ferramenta e configuramos os parâmetros passados para o método.


Método que cria e anexa um objeto ToolTip ao elemento atual:

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

Além dos parâmetros necessários para criar o objeto dica de ferramenta, o controle ao qual o ToolTip criado deve ser anexado é passado para o método. O método acima cria um novo objeto dica de ferramenta e o anexa ao controle especificado nos parâmetros do método. Se o ToolTip for anexado com sucesso, o ponteiro é devolvido ao objeto criado. Caso contrário, retornamos NULL.


Refinamentos idênticos para anexar objetos dica de ferramenta e criar controles da barra de progresso são feitos em todos os arquivos de classes contêineres:

TabControl.mqh, TabField.mqh, SplitContainer.mqh, SplitContainerPanel.mqh e GroupBox.mqh. Não vamos considerar estas mudanças aqui.


Vamos alterar a classe-coleção de objetos gráficos no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

Vejamos como os objetos dica de ferramenta devem ser manuseados. Estes objetos interagem com o mouse e, dependendo da interação, são manipulados no temporizador da coleção de elementos gráficos. O objeto ToolTip que está abaixo do cursor é adicionado à lista de objetos de interação. Esta lista deve ser processada em um temporizador. Qualquer objeto passado que estivesse sob o cursor também deve ser processado. Mas como o objeto passado não precisa de uma animação desbotada, ele será manipulado por um método chamado pelo manipulador de eventos da coleção, porque o objeto deve ser simplesmente escondido e o sinalizador de não exibição deve ser marcado. Esta lógica é diferente daquela dos objetos ativos, que são inicialmente colocados na lista de elementos ativos e permanentemente processados pelo temporizador da lista.

Na seção privada da classe coleção, declaramos a lista de objetos a serem processados:

   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


Lá também declararemos métodos para lidar com os objetos atuais e passados sob o cursor, e métodos para lidar com a lista de objetos a serem manuseados:

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


Na seção pública, escrevemos um método que devolva a lista de objetos a serem processados:

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

e declaramos um manipulador de eventos com temporizador de coleção:

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


No construtor da classe, limpamos a lista e definimos um sinalizador de lista ordenada:

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


Método que adiciona um elemento gráfico na tela à coleção:

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

Obtemos um ponteiro para a lista de objetos a serem processados. Se ele não acrescentar um objeto passado ao método, nós reportamos isso ao log e retornamos false. Se adicionado com sucesso, retornamos true.


Método que usa o nome para recuperar o elemento de uma lista de processamento:

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

Em um laço sobre a lista de objetos a serem processados, obtemos o próximo objeto e se seu nome corresponder ao nome do objeto passado para o método, devolvemos o ponteiro para o objeto encontrado. No final do laço, retornamos NULL - objeto não encontrado.


Método que retorna o índice de elemento por nome na lista a ser processada:

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

Em um laço sobre a lista de objetos a serem processados, obtemos o próximo objeto e se seu nome corresponder ao nome do objeto passado para o método, devolvemos o ponteiro para o objeto encontrado. No final do laço retornamos -1, o que significa objeto não encontrado.


Método para processar o elemento ToolTip atual:

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

A lógica do método é descrita nos comentários ao código. O método desloca o objeto dica da ferramenta pelas coordenadas do cursor e corrige as coordenadas resultantes se o objeto se estender para além da borda da tela.


Método para processar o elemento ToolTip atual:

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

Aqui também é bastante transparente. O objeto passado não está mais sob o cursor. Isto significa que ele deve ser escondido imediatamente e um ponteiro para ele removido da lista de objetos a serem processados.

Se você executar o EA testado no último artigo em um gráfico e mudar o período gráfico para outro, o programa será encerrado com um erro crítico. Isso acontece porque o ponteiro para objeto forma é declarado como estático:

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

Isto significa que quando o período gráfico é alterado, os dados permanecerão nele e a verificação de NULL dará um resultado negativo, dizendo que o objeto é válido:

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

Os dados registrados no ponteiro não se irão referir à área de memória daquele objeto. É por isso que o programa encerra ao acessar o local errado da memória. O erro é fácil de corrigir: vamos verificar a validade do ponteiro, não o seu valor:

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


O manuseio das dicas de ferramentas agora é feito assim:

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

A lógica é explicada em detalhes nos comentários do código. Em resumo: se um objeto estiver sob o cursor, ele é adicionado à lista a ser processada e suas coordenadas são deslocadas com base nas coordenadas do cursor. Se o objeto não estiver sob o cursor, ele é removido da lista e escondido.


Manipulador de eventos do temporizador da coleção:

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

A lógica do manipulador é totalmente explicada nos comentários do código. Trabalhamos em dois laços, um sobre a lista de objetos a serem processados (ToolTip) e outro sobre a lista de elementos ativos (objetos com efeitos de animação)


No objeto principal da biblioteca CEngine, no arquivo \MQL5\Include\DoEasy\Engine.mqh é necessário criar um temporizador para os elementos gráficos da biblioteca e um manipulador de eventos do temporizador.

Na seção privada, declaramos um manipulador:

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


No construtor da classe, criamos outro temporizador, que é o temporizador da coleção de elementos gráficos:

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


No manipulador de eventos do temporizador, escrevemos os blocos de código do manipulador do temporizador do elemento gráfico:

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


Método para processar eventos de elementos gráficos na tela:

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

Assim que a pausa do temporizador da coleção de elementos gráficos terminar, este método será chamado, o qual, por sua vez, chama o manipulador do temporizador da classe coleção de elementos gráficos. A chamada padrão ocorrerá a cada 16 milissegundos (pausa do temporizador).

Agora estamos prontos para o teste.


Teste

Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part126\ com o novo nome TestDoEasy126.mq5.

No manipulador OnInit(), na primeira aba do TabControl no controle SplitContainer, criamos um controle barra de progresso em seu segundo painel:

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

Para verificar se o objeto foi criado, obtemos um ponteiro para ele e exibimos sua descrição no log.

Compilamos o Expert Advisor e o iniciamos no gráfico:


Bem, a funcionalidade anunciada até agora está funcionando satisfatoriamente. O gráfico nos comentários mostra um número em constante mudança, esta é a saída do temporizador da barra de progresso do controle ProgressBar.


O que virá a seguir?

No próximo artigo, continuaremos trabalhando no objeto ProgressBar.

Todos os arquivos da versão atual da biblioteca, os arquivos do EA de teste e o indicador do controle de eventos dos gráficos para MQL5 estão anexados abaixo.

Voltar ao conteúdo

*Artigos desta série:

 
DoEasy. Controles (Parte 20): Objeto WinForms SplitContainer
DoEasy. Controles (Parte 21): Controle SplitContainer. Separador de painéis
DoEasy. Controles (Parte 22): SplitContainer. Alterando as propriedades do objeto criado
DoEasy. Controles (Parte 23): Apurando os objetos WinForms TabControl e SplitContainer
DoEasy. Controles (Parte 24): Objeto WinForms dica
DoEasy. Controles (Parte 25): Objeto WinForms Tooltip

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

Arquivos anexados |
MQL5.zip (4521.53 KB)
Algoritmos de otimização populacionais: Colônia artificial de abelhas (Artificial Bee Colony, ABC) Algoritmos de otimização populacionais: Colônia artificial de abelhas (Artificial Bee Colony, ABC)
Hoje estudaremos o algoritmo de colônia artificial de abelhas. Complementaremos nosso conhecimento com novos princípios para estudar espaços funcionais. E neste artigo falarei sobre minha interpretação da versão clássica do algoritmo.
Redes neurais de maneira fácil (Parte 32): Aprendizado Q distribuído Redes neurais de maneira fácil (Parte 32): Aprendizado Q distribuído
Em um dos artigos desta série, já nos iniciamos no método aprendizado Q, que calcula a média da recompensa para cada ação. Em 2017, foram apresentados 2 trabalhos simultâneos, que tiveram sucesso quanto ao estudo da função de distribuição de recompensas. Vamos considerar a possibilidade de usar essa tecnologia para resolver nossos problemas.
Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 15): Automação (VII) Aprendendo a construindo um Expert Advisor que opera de forma automática (Parte 15): Automação (VII)
Para coroar esta sequencia de automação. Vamos complementar o que foi visto no artigo anterior. Este definitivamente mostra como tudo irá se encaixar, fazendo o Expert Advisor, funcionar como um relógio.
Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 04): Análise discriminante linear Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 04): Análise discriminante linear
O trader moderno está quase sempre à procura de novas ideias. Para isso, tenta novas estratégias, modifica e descarta aquelas que não funcionam. Nesta série de artigos, tentarei provar que o assistente MQL5 é a verdadeira espinha dorsal de um trader moderno.