English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 26): Fertigstellung des WinForms-Objekts ToolTip und Weiterführung der Entwicklung der ProgressBar

DoEasy. Steuerung (Teil 26): Fertigstellung des WinForms-Objekts ToolTip und Weiterführung der Entwicklung der ProgressBar

MetaTrader 5Beispiele | 23 Januar 2023, 15:00
277 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Im letzten Artikel habe ich mit der Entwicklung des Steuerelements SplitContainer begonnen. Der Tooltip sollte nach einer kurzen Pause erscheinen, wenn sich der Mauszeiger über ein Objekt bewegt. Ein zuvor angezeigter Tooltip sollte ausgeblendet werden. Wenn wir den Cursor nach dem Erscheinen des Tooltips an der gleichen Stelle belassen, sollte er nach einer gewissen Zeit verschwinden. Im vorigen Artikel habe ich dafür gesorgt, dass der Tooltip sofort erscheint, wenn man den Mauszeiger über das Objekt bewegt, dem sie zugewiesen wurde. Im aktuellen Artikel werde ich weiter an dem Verhalten der QuickInfo arbeiten und es konsistenter mit dem Verhalten von Programmen machen, die in MS Visual Studio kompiliert wurden. Wenn sich der Mauszeiger über ein Objekt bewegt, sollte erst nach einer Pause der Tooltip ohne Unterbrechung auf dem Bildschirm erscheinen. Wenn sich der Cursor vom Objekt entfernt, wird der Tooltip ausgeblendet. Wenn der Mauszeiger auf dem Objekt stehen bleibt, verschwindet der Tooltip nach einiger Zeit wieder. Um dieses Verhalten zu implementieren, müssen wir den Timer verwenden, der für die grafischen Elemente der Kollektion erstellt wurde. Für jedes Objekt wird eine virtuelle Ereignisbehandlung der Zeit, ein Timer, definiert. Die Implementierung des Timers sollte speziell in den Klassen derjenigen Objekte erfolgen, die in ihm verarbeitet werden sollen.

Damit die Bibliothek versteht, welche Objekte im Timer verarbeitet werden sollen und welche nicht, werde ich eine Liste der aktiven Objekte erstellen. Sie enthält die Zeiger auf die Objekte, die im Timer verarbeitet werden müssen. Dadurch wird es einfacher, den Überblick über die zu verarbeitenden Objekte zu behalten, und wir müssen nicht ständig alle grafischen Elemente in einer Schleife überprüfen. Die Schleife soll sich nur auf die der Liste hinzugefügten Objekte beziehen. Auf diese Weise wird es möglich sein, die grafische Schnittstelle zu animieren. Die Objekte reagieren nicht nur auf die Interaktion mit der Maus, sondern verwenden auch die für sie festgelegte Animationssequenz für visuelle Effekte. Auf diese Weise lassen sich auch gewöhnliche animierte Icons leicht erstellen und verwenden. Es reicht aus, ein Objekt mit Animationsbildern zu erstellen und es in die Liste der aktiven Bibliotheksobjekte aufzunehmen. In der Ereignisbehandlung des Timers eines solchen Objekts werden wir einfach die Animationsbilder, die bei der Erstellung des Objekts erstellt wurden, abwechselnd anzeigen. Heute werde ich einen Timer für das ToolTip-Steuerelement erstellen. Dieser Timer implementiert das oben beschriebene Verhalten des Tooltips. Da dieses Verhalten eine Interaktion mit der Maus impliziert, werden wir sowohl in der Ereignisbehandlung der Kollektion grafischer Elemente als auch im Timer der Kollektion mit ihnen arbeiten.

Nach der Fertigstellung des WinForms-Objekts ToolTip werde ich mit der Entwicklung des ProgressBar-Steuerelements beginnen. Ich werde nur eine statische Version des Objekts erstellen - seine Eigenschaften und sein Aussehen. Seine Funktionsweise wird im nächsten Artikel erläutert.


Verbesserung der Bibliotheksklassen

In \MQL5\Include\DoEasy\Defines.mqh fügen wir die Zeitgeberparameter für grafische Elemente auf der Leinwand hinzu:

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

Jede Kollektion von Bibliotheksobjekten hat ihren eigenen Timer mit einem eigenen Satz von Parametern - Pause, Erhöhung des Timer-Zählers und seiner ID. Ich habe hier genau die gleichen Parameter für den neuen Timer hinzugefügt.

Fügen wir die Standardwerte für das neue ProgressBar-Steuerelement hinzu:

#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


Für einige grafische Kontrollen sind die Elemente der visuellen Effekte erforderlich. Zum Beispiel soll die Fortschrittsanzeige eine Blende haben, die entlang des Balkens verläuft, wie sie in Windows implementiert ist. Das Objekt, das die Blende umsetzt, wird über den Balken gelegt und bewegt sich an ihm entlang. Wir müssen den Objekttyp in der Liste der Bibliotheksobjekttypen festlegen:

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


Fügen wir die neuen Typen von Grafikelementen auf der Leinwand zur Liste der Grafikelementtypen hinzu:

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


Ein Steuerelement (hier ToolTip) kann mehrere Zustände haben, wenn es mit der Maus interagiert. Da das Objekt animiert ist, sollten sein Verhalten und sein Zustand beschrieben werden. Er kann auf den Beginn des Einblendens warten, sich im Einblendzustand befinden, auf den Beginn des Ausblendens warten, sich im Ausblendzustand befinden oder sich im Normalzustand befinden.
Beschreiben wir seine Zustände in der neuen Liste der Kontrollanzeigezustände:

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

Solche Zustände können auf andere Kontrollen angewendet werden. Für sie können später Animationssequenzen erstellt werden. Außerdem ist es nicht notwendig, dass ein Objekt ein- oder ausgeblendet wird. Die Beschreibung eines Objekts, das eingeblendet wird, kann mit der Erweiterung der Dropdown-Liste gleichgesetzt werden, während der Zustand des allmählichen Ausblendens (Dämpfung) - mit dem Zustand zusammengeklappt gleichgesetzt werden kann. Wir können diese Liste jedoch später jederzeit erweitern.

Die ProgressBar kann drei Zeichenstile des Fortschrittsbalkens haben. Wir listen sie in der Enumeration auf:

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

Ich werde hier nur den durchgehenden Balken implementieren.


Wenn für ein Objekt eine animierte Anzeige implementiert ist, sollte sein Zustand gespeichert und über seine Eigenschaften abgerufen werden.
Fügen wir die neuen Eigenschaften des grafischen Elements zur Aufzählung der ganzzahligen Eigenschaften des grafischen Elements auf der Leinwand hinzu und erhöhen wir die Gesamtzahl der ganzzahligen Eigenschaften von 129 auf 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
//+------------------------------------------------------------------+


Wir sortieren nach neuer Eigenschaft zur Enumeration möglicher Kriterien zum Sortieren von grafischen Elementen auf der Leinwand hinzufügen:

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

Jetzt können wir alle grafischen Elemente nach neuen Eigenschaften sortieren und auswählen.


In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:

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

und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:

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


Bei der Erstellung von ObjektKollektionen verwenden wir die Klasse der Liste, die von der Klasse des dynamischen Arrays von Zeigern auf Instanzen der Klasse CObject und ihrer Nachkommen. Die Klasse CArrayObj verfügt über die Methode Detach(), die den Zeiger aus der Liste abruft und den erhaltenen Zeiger zurückgibt. Wir brauchen den Zeiger nicht immer zu verwenden, nachdem wir ihn aus der Liste extrahiert haben.
Daher erstellen wir in der abgeleiteten Klasse in
\MQL5\Include\DoEasy\Collections\ListObj.mqh die Methode, die den Zeiger aus der Liste entfernt, ohne ihn zurückzugeben:

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

Hier wird einfach der Zeiger aus der Liste abgerufen. Wenn dies nicht gelingt, wird false zurückgegeben.
Bei erfolgreichem Entfernen wird der resultierende Zeiger zurückgesetzt und true zurückgegeben.


Lassen Sie uns die Klasse des Pausenobjekts in \MQL5\Include\DoEasy\Services\Pause.mqh leicht abändern.

Wir fügen die öffentliche Methode hinzu, die den Beginn des Pausen-Countdowns in der Anzahl der Millisekunden zurückgibt, die seit dem Start des Systems vergangen sind:

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

Wir benötigen die Methode, wenn wir Steuerelemente im Timer behandeln.


Da ich neue Integer-Eigenschaften des grafischen Elements hinzugefügt habe, müssen wir sie im Basisobjekt des grafischen Elements initialisieren und neue Felder der Objektstruktur hinzufügen.

In \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh fügen wir die neue Eigenschaften zu der Objektstruktur hinzu:

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

Wir benötigen die Struktur des Objekts, um das Objekt korrekt in einer Datei zu speichern und anschließend wiederherzustellen.


Im Methodenblock für den vereinfachten Zugriff auf die Objekteigenschaften legen wir die Methoden für die Behandlung dieser neuen Eigenschaften fest:

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

Die Set-Methoden der Eigenschaften setzen die Werte, die ihnen in den Objekteigenschaften übergeben werden, während die Return-Methoden die zuvor gesetzten Werte aus den Objekteigenschaften zurückgeben.

Legen wir die Standardwerte für die neuen Eigenschaften in beiden Konstruktoren fest:

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


Fügen wir in der Methode, die die Objektstruktur erstellt, das Schreiben der Objekteigenschaften zu den Strukturfeldern hinzu:

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

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

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

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

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

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

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

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


In der Methode, die ein Objekt aus einer Struktur erstellt, implementieren wir das Setzen von Werten in den Objekteigenschaften aus den Strukturfeldern:

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

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

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

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

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

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

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


In \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh der Basisobjektklasse aller grafischen Bibliotheksobjekte, fügen wir die Rückgabe der Beschreibung neuer Objekttypen der Methode hinzu, die die Beschreibung des grafischen Elementtyps zurückgibt:

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

Ich habe alle diese Objekte bereits deklariert, aber ich werde sie etwas später erstellen - nach der Fertigstellung der ToolTip-Objektklasse.


Um mit grafischen Elementen zu arbeiten, müssen wir einen Ereignisbehandlung in Form des Timers erstellen. Da alle grafischen Elemente von der Klasse der Formularobjekte geerbt werden, wird in dieser Klasse der virtuelle Timer deklariert.

Schreiben wir einen virtuellen Timer im öffentlichen Abschnitt in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh:

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

Der Timer tut nichts. Er muss in den Klassen, in denen die Ereignisse des Timers behandelt werden sollen, neu definiert werden.


Wir deklarieren die Methode, die die Anzahl der an das Element angehängten ToolTip-Objekte zurückgibt:

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

Einem grafischen Objekt kann nur ein Tooltip-Objekt zugewiesen werden. Wir können jedoch mehrere ToolTips an ein Objekt anhängen und die erstellten ToolTips anderen Objekten zuweisen, die mit dem Objekt verbunden sind. Dies geschieht, weil nicht alle Bibliotheksobjekte ToolTip-Objekte an sich selbst anhängen können. Wir können jedoch jederzeit Tooltips in dem Container erstellen, an den Objekte angehängt sind, und diesen Objekten dann Tooltips zuweisen. Die Methode ermöglicht es uns, die Anzahl der erstellten und an den Container angehängten ToolTip-Objekte zu ermitteln.

Verfeinern wir nun die Methode, die das angegebene ToolTip-Objekt an ein Objekt bindet.

Wir fügen zusätzlicher Prüfungen auf ungültigen Typ hinzu und auf die Anzahl der angehängten Tooltips, die größer als Null ist:

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

Wenn versehentlich versucht wird, ein Objekt eines anderen Typs anstelle eines Tooltip-Objekts anzuhängen, wird die Methode einen Fehler melden. Wenn ToolTip bereits an das Objekt angehängt ist, wird ebenfalls eine Fehlermeldung angezeigt und die Methode gibt false zurück.


Die Methode gibt die Anzahl der ToolTip-Objekte zurück:

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

Handelt es sich bei diesem Objekt um einen Container, so kann eine beliebige Anzahl von Tooltip-Objekten an ihn angehängt werden. Die Methode gibt ihre Nummer zurück. Ermittelt das nächste Objekt in der Schleife anhand der Gesamtzahl der angehängten Objekte. Wenn es sich bei dem Objekttyp um einen Tooltip handelt, erhöhen wir den Objektzähler (die Variable res). Am Ende der Schleife geben wir das Ergebnis der Berechnung zurück, das in res.


Der Timer für die Kollektion grafischer Elemente wird alle 16 Millisekunden aktualisiert. Um die QuickInfo innerhalb von z. B. einer Sekunde ein- oder auszublenden, müssen wir 1000 Millisekunden durch den Aktualisierungszeitraum des Timers teilen. Folglich müssen wir die Deckkraft des Objekts etwa alle 1000/16=62,5 Millisekunden ändern.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ToolTip.mqh deklarieren wir die Variable zum Speichern des Transparenzänderungsschritts:

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


Im öffentlichen Abschnitt der Klasse deklarieren wir den virtuellen Timer:

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


In jedem der Konstruktoren berechnen wir den Wert der Variablen, die den Transparenzänderungsschritt speichert, auf der Grundlage der Standardwerte für die Ein-/Ausblenddauer und den Schritt des Timerzählers der GrafikelementKollektion:

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


In der Variableninitialisierungsmethode die Standardwerte für die Verzögerungen geben wir beim Ändern der Zustände von Tooltip-Objekten ein:

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


In der Methode, die den Tooltip anzeigt, verschieben wir den Befehl zur Anzeige des Objekts an das Ende und entfernen das komplette Neuzeichnen des gesamten Objekts:

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

Die Methode sieht nun wie folgt aus:

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

Durch diese Verfeinerung wird das unangenehme „Blinken“ des Elements bei der schrittweisen Änderung seiner Deckkraft etwas reduziert.


Fügen wir in der Tooltip-Methode ganz am Ende eine Aktualisierung der geänderten Leinwand hinzu:

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

Beim Ändern der Deckkraft auf Null (das Objekt ist vollständig transparent), bemerkte ich ein seltsames Verhalten von Methoden, die Primitive mit Anti-Aliasing zeichnen - sie reagieren schlecht auf den Transparenzwert. Bei einem sehr niedrigen Deckkraftwert heben sich die mit solchen Methoden gezeichneten Linien zu stark vom Hintergrund eines Objekts ab, das auf genau denselben Deckkraftwert eingestellt ist. Das Hinzufügen einer Zeile zur Aktualisierung der Leinwand ist einer der Versuche, diesen störenden Effekt zu verringern. Es funktioniert jedoch noch nicht...


Ereignisbehandlung durch den Timer:

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

Die Methodenlogik wird in den Codekommentaren beschrieben. Der Handler wird jedes Mal aufgerufen, wenn der Objekttimer abläuft. Bei jeder Iteration des Zeitgebers müssen wir den Zustand des Objekts und seine Warte-/Zustandsänderungszähler überprüfen.

Zunächst wird der normale Zustand des Objekts gespeichert. In diesem Zustand ist der Tooltip vollständig ausgeblendet. Wenn sich das Objekt in diesem Zustand befindet, sollte es zunächst einige Zeit warten, bevor es mit der Einblendung beginnt. Wenn wir also den normalen Zustand des Objekts sehen, schreiben wir ihm den Zustand des Wartens auf die Einblendung vor, starten den Countdown der Wartezeit und verlassen die Methode.

Bei der nächsten Iteration sehen wir einen neuen Zustand (wir warten auf die Einblendung) und überprüfen dementsprechend den Zähler. Wenn die Wartezeit abgelaufen ist, versetzen wir das Objekt in den Einblendungszustand und warten bis zum nächsten Methodenaufruf. Beim nächsten Durchlauf beginnen wir bereits mit dem Prozess der Einblendung des Objekts. Hier müssen wir die Deckkraft des Objekts überprüfen und sie um den Änderungsschritt erhöhen. Dies geschieht bei jeder Iteration der Timer-Operation. Sobald das Objekt vollständig undurchsichtig wird, setzen wir es in einen neuen Zustand - und warten auf den sanften Ausblendungsprozess. Schließlich sollte das Objekt, das nach etwa fünf Sekunden erscheint, auch wieder verschwinden.

Der Prozess des Ausblendens ist identisch mit dem des Einblendens. Am Ende des vollständigen Zyklus wird der Zustand des Änderungszyklusendes auf das Objekt gesetzt. Wenn wir ihm sofort einen normalen Zustand zuweisen, wird der gesamte Zyklus neu gestartet, da der normale Zustand des Objekts als Startbedingung für den Beginn dieses Zyklus dient. Er sollte erst dann in das Objekt geschrieben werden, wenn der Zeiger auf das Objekt aus der Liste der im Zeitgeber zu behandelnden Objekte entfernt wird. Dies geschieht in der Kollektionsklasse der grafischen Elemente. Wenn das Objekt den Status ‚Zyklus vollständig durchlaufen‘ hat, entfernt die Methode den Zeiger auf das Objekt aus der Bearbeitungsliste und versetzt das Objekt selbst in seinen normalen Zustand. Wenn wir das nächste Mal mit dem Mauszeiger über ein Objekt fahren, wird es wieder in die Liste zur Bearbeitung im Timer aufgenommen. Auf die gleiche Weise wird der Objektzeiger aus der Liste entfernt, wenn der Cursor aus dem Bereich des Objekts bewegt wird, der für den Bearbeitungszyklus bereit ist, oder sogar während des Zyklus.


Allmählich nähern wir uns der Erstellung des neuen WinForms-Objekts der ProgressBar . Für dieses Steuerelement müssen wir das Hilfsobjekt Glare (Blende) erstellen. Die Objekte dieses Typs dienen dazu, einige GUI-Elemente visuell zu dekorieren, während im ProgressBar-Objekt die Blende entlang des Fortschrittsbalkens verlaufen soll. Das Konzept sieht folgendermaßen aus: Wir zeichnen einen weißen Fleck in der gewünschten Form und Größe und verwischen ihn von der Mitte zu den Rändern hin. Das Objekt der Blende ist ebenso halbtransparent wie das Schattenobjekt. Hier müssen wir ein solches Objekt vom Schattenobjekt ableiten, um dessen Weichzeichnermethoden nutzen zu können und gleichzeitig das nutzerdefinierte Rendering beizubehalten. Um normalerweise vom Schattenobjekt zu erben, müssen wir ihm einen geschützten Konstruktor hinzufügen, in dem wir den Typ des erstellten Objekts angeben.

Lassen Sie uns Änderungen an der Klasse des Schattenobjekts in \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh vornehmen.

Wir werden die Gaußschen Unschärfemethoden und das Array der Gewichtskoeffizienten vom privaten in den geschützten Bereich der Klasse verschieben. Der geschützte Konstruktor wird dort ebenfalls deklariert. Im öffentlichen Abschnitt der Klasse schreiben wir die virtuelle Methode, die das Flag für die Pflege einer realen Eigenschaft durch ein Objekt zurückgibt:

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

Ich werde nach und nach allen grafischen Elementen Methoden hinzufügen, die die Flags für die Pflege von Eigenschaften durch ein Objekt zurückgeben, da dies Standardmethoden für die Bibliothek sind, die ich in Zukunft benötigen werde. Hier habe ich eine solche Methode hinzugefügt, um zu vermeiden, dass dieses Thema in dieser Klasse wieder auftaucht.


Geschützter Konstruktor:

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

In den formalen Konstruktorparametern übergeben wir den Typ des erstellten Objekts, der im Initialisierungsstring an den Konstruktor der übergeordneten Klasse übergeben wird. Dies geschieht in allen Objekten der Bibliothek. Alles andere wird hier genau so gemacht wie im parametrischen Konstruktor.

Im parametrischen Konstruktor entfernen wir die Zeichenkette am Ende, die bewirkt, dass der Schatten sofort nach seiner Erstellung gezeichnet wird:

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

Die Zeichenfolge verursachte ein falsches Verhalten des Schattenobjekts beim Erstellen von GUI-Elementen. Zunächst erschien ein Schatten auf einem leeren Diagramm, und dann wurde das Erscheinungsbild der grafischen Nutzeroberfläche erstellt. Jetzt wird der Schatten nicht mehr zuerst erscheinen.


Steuerelement ProgressBar

Erstellen wir zunächst ein zusätzliches Objekt der Blende in der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\GlareObj.mqh. Da ich dieses Objekt derzeit nicht benötige, werde ich es nicht berücksichtigen. Sie ist der Klasse der Schattenobjekte völlig ähnlich, mit dem Unterschied, dass die Farbe eines gezeichneten Schattens auf Weiß gesetzt ist:

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

Ich werde alle notwendigen Änderungen in dieser Klasse im nächsten Artikel vornehmen. Lassen wir es vorerst so, wie es ist.


Das ProgressBar-Steuerelement besteht aus zwei Objekten - einem Hintergrund und einem Fortschrittsbalken. Der Hintergrund stellt das eigentliche Steuerelement dar. In Zukunft wird es möglich sein, zusätzliche Elemente darauf zu platzieren, und der Fortschrittsbalken wird durch ein separates Hilfsobjekt dargestellt, dessen Eigenschaften sich vom übergeordneten Objekt unterscheiden.

Erstellen wir in der Bibliotheksdatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ die neue Datei BarProgressBar.mqh für die Klasse CBarProgressBar. Die Klasse sollte vom Basisobjekt aller WinForms-Objekte der Bibliothek abgeleitet sein. Die Panel-Objektdatei sollte in die erstellte Klassendatei aufgenommen werden.

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


Im geschützten Abschnitt der Klasse deklarieren wir die Methode, die die Blende auf dem Fortschrittsbalken anzeigt, sowie den geschützten Konstruktor. In den öffentlichen Abschnitt schreiben wir die Methoden, die Werte von Objekteigenschaften setzen und zurückgeben, virtuelle Methoden zur Verwaltung von Eigenschaften, den parametrischen Konstruktor und den Ereignisbehandler für den Klassentimer:

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

Schauen wir uns die angegebenen Methoden genauer an.


Geschützter Konstruktor:

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

Der Konstruktor erhält den Typ des erstellten grafischen Elements und andere Objektparameter, die in der Initialisierungszeichenfolge auf die übergeordnete Klasse eingestellt sind. Der grafische Objekttyp der Bibliothek ist auf „Hilfsobjekt“ eingestellt, das Objekt hat keinen Rahmen und es sind Standardfarben eingestellt.


Der parametrische Konstruktor:

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

Hier ist alles genau so wie im geschützten Konstruktor, aber der Objekttyp wird nicht in formalen Parametern übergeben. Stattdessen ist sie in der Initialisierungszeichenfolge fest codiert.

Die Methode, die eine Blende zeichnet, wird vorerst leer sein, da die Blende noch nicht fertig ist. Seine Erstellung und Fehlersuche sind für den nächsten Artikel vorgesehen:

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


Im Timer setzen wir vorübergehend den von der Funktion GetTickCount() zurückgegebenen Wert, um sicherzustellen, dass das Objekt in die Liste der aktiven Objekte fällt, die für die Verarbeitung im Bibliotheks-Timer zugewiesen sind:

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


Bevor ich mit der Erstellung des ProgressBar-Steuerelements beginne, muss ich sicherstellen, dass die Objekte der grafischen Elemente unabhängig voneinander im Bibliotheks-Timer verarbeitet werden können.

Das Konzept sieht folgendermaßen aus: Jedes der grafischen Elemente, die unabhängig voneinander im Timer bearbeitet werden, wird als aktives Element betrachtet. Um aktive Elemente zu behandeln, erstellen wir eine Liste, die Zeiger auf solche Objekte enthält. Diese Liste wird immer im Hauptobjekt erstellt und ist dementsprechend in der Sammelklasse der grafischen Elemente sichtbar. Im Timer der Kollektionsklasse werden wir eine Schleife durch alle Hauptformularobjekte ziehen, Zeiger auf aktive Elemente erhalten und deren Ereignisbehandlung im Timer aufrufen. So sind wir nicht gezwungen, jedes grafische Element im Hauptobjekt zu suchen, sondern erstellen sofort eine Liste der Elemente im Hauptobjekt und verarbeiten nur diese.

In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh der Basisobjektklasse aller WinForms-Bibliotheksobjekte fügen wir die Datei der Objektklasse der Blende hinzu und deklarieren den Zeiger auf die Liste der aktiven Elemente im geschützten Abschnitt:

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

Die Einbindung einer Blenden-Objektdatei ermöglicht es uns, diese in vielen grafischen Elementen der Bibliothek zu erstellen und zu verwenden.

Im öffentlichen Teil der Klasse deklarieren wir die Methoden für die Arbeit mit der Liste der aktiven Elemente:

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


Wir fügen den Destruktor der Klasse hinzu :

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

Wenn die Liste der aktiven Elemente erstellt wurde, leeren wir sie und löschen das erstellte Listenobjekt.


In die Konstruktoren der Klasse schreiben wir die Erstellung des Listenobjekts der aktiven Elemente:

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


Schreiben wir in der Methode, die die Beschreibung der Integer-Eigenschaft des Elements zurückgibt, Codeblöcke, um die Beschreibung der neuen Objekteigenschaften zurückzugeben:

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


Die Methode, die die Liste der aktiven Elemente des Hauptobjekts zurückgibt:

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

Hier erhalten wir den Zeiger auf das Hauptobjekt. Wenn NULL zurückgegeben wird, ist dieses Objekt das Hauptobjekt. Wir holen uns die Liste der aktiven Elemente aus dem Hauptobjekt. Wenn es nicht gelingt, den Zeiger zu erhalten, wird dies mitgeteilt und NULL zurückgegeben. Andernfalls wird der Zeiger auf die Liste zurückgegeben.


Die Methode, die die Anzahl der aktiven Elemente des Hauptobjekts zurückgibt:

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

Wenn der Zeiger auf die Liste des Hauptobjekts erhalten wurde, wird die Anzahl der Elemente in der Liste zurückgegeben. Andernfalls wird true zurückgegeben.


Die Methode, die den Index des angegebenen Objekts in der Liste der aktiven Elemente des Hauptobjekts zurückgibt:

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

Die Methode erhält den Zeiger auf das Objekt, dessen Index gesucht werden soll. Holt die Liste der aktiven Elemente aus dem Hauptobjekt. In der Schleife wird anhand der erhaltenen Liste das nächste Objekt ausgewählt. Wenn sein Name gleich dem Namen des an die Methode übergebenen Objekts ist, wird der Schleifenindex zurückgegeben. Am Ende der Schleife wird -1 zurückgegeben, was bedeutet, dass das Objekt nicht gefunden wurde.


Die Methode, die ein Element aus der Liste der aktiven Elemente des Hauptobjekts nach Index zurückgibt:

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

Die Methode erhält den Index eines Objekts, das aus der Liste zurückgegeben werden soll. Wir holen die Liste der aktiven Elemente aus dem Hauptobjekt und geben den Zeiger auf das Objekt nach Index zurück. Wenn die Liste nicht ermittelt werden konnte, gibt die Methode NULL zurück.


Die Methode, die das Flag für das Vorhandensein eines Objekts nach Namen in der Liste der aktiven Elemente des Hauptobjekts zurückgibt:

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

Die Methode empfängt den Namen des Objekts, dessen Vorhandensein in der Liste wir herausfinden müssen. Holt die Liste der aktiven Elemente aus dem Hauptobjekt. In der Schleife durch die erhaltene Liste, das nächste Objekt zu erhalten. Wenn der Name mit dem an die Methode übergebenen Namen übereinstimmt, wird true zurückgegeben. Nach Beendigung der Schleife wird -1 zurückgegeben. Das Objekt ist nicht in der Liste enthalten.


Die Methode, die das angegebene Objekt in die Liste der aktiven Elemente des Hauptobjekts aufnimmt:

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

Ermittelt die Liste der aktiven Elemente des Hauptobjekts. Wenn ein Objekt mit demselben Namen bereits in der Liste enthalten ist, wird false zurückgegeben. Andernfalls wird das Ergebnis des Hinzufügens des Objekts zur Liste zurückgegeben.


Die Methode, die das aktuelle Objekt in die Liste der aktiven Elemente des Hauptobjekts aufnimmt:

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

Gibt das Ergebnis des Aufrufs der obigen Methode zurück. Das aktuelle Objekt wird als ein Objekt angegeben, das der Liste hinzugefügt werden soll.


Die Methode, die das angegebene Objekt aus der Liste der aktiven Elemente des Hauptobjekts entfernt:

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

Ermitteln des Zeigers auf die Liste der aktiven Elemente des Hauptobjekts. Ermitteln des Index des Objekts in der Liste, dessen Zeiger an die Methode übergeben wird. Ermitteln des Zeigers auf das aus der Liste entfernte Objekt. Wenn das Objekt nicht entfernt werden kann, wird false zurückgegeben. Andernfalls wird der Zeiger zurückgesetzt und true zurückgegeben.


Die Methode, die das aktuelle Objekt aus der Liste der aktiven Elemente des Hauptobjekts entfernt:

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

Gibt das Ergebnis des Aufrufs der obigen Methode zurück. Wir geben das aktuelle Objekt als das zu entfernende Objekt an.


Erstellen wir nun das Steuerelement der ProgressBar.

In MQL5\Include\DoEasy\Objects\Graph\WForms\CommonControls\ erstellen wir die neue Datei ProgressBar.mqh mit der Klasse CProgressBar. Um die Funktionsweise des Objekts zu erweitern, soll die Klasse von der Klasse des Container-Objekts abgeleitet werden, damit andere Steuerelemente an das Objekt angehängt werden können. Die Container-Klassendatei sollte zusammen mit der Fortschrittsbalken-Klassendatei in die Datei des erstellten Objekts aufgenommen werden:

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


Deklarieren wir im privaten Abschnitt der Klasse die Methoden zur Erstellung eines neuen grafischen Objekts und des Fortschrittsbalkens sowie die Initialisierungsmethode der Klasse:

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


Im geschützten Abschnitt der Klasse deklarieren wir einen geschützten Konstruktor:

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:


Im öffentlichen Teil der Klasse deklarieren wir die Methoden zum Setzen und Abrufen von Objekteigenschaften, die Methode zum Abrufen des Zeigers auf das Fortschrittsbalkenobjekt, die Methoden, die die Flags für die Pflege der Objekteigenschaften zurückgeben, den parametrischen Konstruktor und den Timer:

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

Nach dem Setzen der Eigenschaft setzen einige Methoden zum Setzen der Objekteigenschaften diese auf die entsprechende Eigenschaft des Fortschrittsbalkenobjekts.

Schauen wir uns die angegebenen Methoden genauer an.


Geschützter Konstruktor:

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

Die Methode empfängt den Typ des erstellten Objekts, der in der Initialisierungszeichenfolge auf die übergeordnete Klasse gesetzt ist. Der Objekttyp der Bibliotheksgrafik ist auf „Standardsteuerung“ eingestellt. Dann werden die Methode zur Initialisierung der Objekteigenschaften und die Methode zur Erstellung des Fortschrittsbalkenobjekts aufgerufen.


Der parametrische Konstruktor:

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

Hier ist der Typ des Steuerelements im Initialisierungsstring fest codiert.


Die Methode zur Initialisierung der Elementeigenschaften:

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

Der Objektrahmen ist auf ein Pixel auf jeder Seite eingestellt, der Rahmentyp ist einfach, die Objektfarben und andere Standardeigenschaften sind ebenfalls eingestellt. Die Länge des Fortschrittsbalkens wird auf die Hälfte der Breite des Objekts festgelegt.


Die Methode, die das Fortschrittsbalkenobjekt erstellt:

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

Die Methodenlogik enthält Kommentare in der Auflistung. Die Höhe des Fortschrittsbalkens wird so eingestellt, dass das Objekt vollständig in den Container passt und gleichzeitig der Containerrahmen nicht von ihm überlappt wird. Nachdem wir den Fortschrittsbalken erstellt haben, fügen wir das gesamte Objekt zur Liste der aktiven Elemente hinzu. In diesem Fall wird es in der >Ereignisbehandlung des Timers der grafischen Bibliothekselemente gelangen und dort verarbeitet werden. Dementsprechend können wir in dem Objekt Timer, die Funktionalität, die unabhängig vom Nutzer funktioniert. Bei diesem Objekt wird dies der visuelle Effekt sein, mit dem ich mich im nächsten Artikel befassen werde.


Die virtuelle Methode zur Erstellung eines neuen grafischen Objekts:

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

Damit das Containerobjekt angehängte Objekte in sich selbst erstellen kann, gibt es eine virtuelle Methode, in der diese Objekte erstellt werden. Für jedes Steuerelement kann die Liste der darin erstellten Objekte unterschiedlich sein. Dieses Objekt bietet die Möglichkeit, das Fortschrittsbalken-Objekt und das Blendenobjekt zu erstellen. Im Moment müssen hier keine weiteren Objekte erstellt werden.


Ereignisbehandlung durch den Timer:

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

Hier erhalten wir den Zeiger auf das Fortschrittsbalkenobjekt und rufen dessen Timer-Ereignisbehandlung auf. Da die Blende genau entlang des Fortschrittsbalkens verlaufen soll, werde ich dieses Verhalten im Timer des Klassenobjekts CBarProgressBar implementieren. Aus diesem Grund wird sein Timer hier aufgerufen, wobei die Ausgabe des GetTickCount()-Werts auf dem Diagramm in Form eines Kommentars geschrieben wird.


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh der Container-Objektklasse, und zwar in ihrer Methode, die die Parameter für das gebundene Objekt festlegt, fügen wir den Codeblock zum Festlegen der Parameter eines neu erstellten Trennobjekts hinzu:

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

Wir verwenden einfach die Standardwerte, die in den Konstruktoren dieser Klassen festgelegt sind.


Nun müssen wir in jedem Containerobjekt die Möglichkeit schaffen, Tooltip-Objekte schnell an das aktuelle und an die angegebenen Elemente anzuhängen. Dadurch wird es einfacher, den gewünschten Objekten, die an Containern angebracht sind, Tooltips zuzuordnen.

Fügen wir in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh die Einbindung der ProgressBar-Steuerungsdatei hinzu:

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


Im öffentlichen Abschnitt deklarieren wir zwei Methoden zum Erstellen und Anhängen von Tooltip-Objekten:

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


In der Methode, die ein neues grafisches Objekt erstellt, erstellen wir eine Zeichenfolge für die Erstellung eines neuen Steuerelements hinzu:

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


Die Methode, die das ToolTip-Objekt erstellt und an das aktuelle Element anhängt:

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

Die Methodenlogik wird in den Codekommentaren beschrieben. Alle erforderlichen Daten werden an die Methode übergeben, um das Tooltip-Objekt zu erstellen. Wenn das Objekt erstellt wird, wird es automatisch mit dem aktuellen Objekt verbunden. Als Nächstes holen wir das neueste ToolTip-Objekt aus der Liste der ToolTip-Objekte und setzen die Parameter, die der Methode übergeben wurden, darauf.


Die Methode, die das ToolTip-Objekt erstellt und an das angegebene Element anhängt:

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

Zusätzlich zu den Parametern, die zum Erstellen eines Tooltip-Objekts erforderlich sind, erhält die Methode das Steuerelement, an das der erstellte ToolTip angehängt werden soll. Mit der obigen Methode wird ein neues Tooltip-Objekt erstellt und an das Steuerelement, das in den Methodenparametern angegeben wurde, angehängt. Wenn der ToolTip erfolgreich angehängt wurde, wird der Zeiger auf das erstellte Objekt zurückgegeben, andernfalls wird NULL ZURÜCKGEGEBEN.


Identische Verbesserungen beim Anhängen von Tooltip-Objekten und beim Erstellen von ProgressBar-Steuerelementen wurden in allen Containerklassendateien vorgenommen:

TabControl.mqh, TabField.mqh, SplitContainer.mqh, SplitContainerPanel.mqh und GroupBox.mqh. Ich werde diese Änderungen hier nicht berücksichtigen.


Jetzt müssen wir die Kollektionsklasse der grafischen Elemente \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh verbessern.

Schauen wir uns an, wie Tooltip-Objekte behandelt werden sollten. Diese Objekte interagieren mit der Maus, und je nach Ergebnis der Interaktion werden sie im Zeitgeber der Kollektion grafischer Elemente verarbeitet. Das ToolTip-Objekt unter dem Cursor wird in die Liste der Interaktionsobjekte aufgenommen. Diese Liste sollte im Timer abgearbeitet werden. In diesem Fall sollte auch das vorherige Objekt, das sich zuvor unter dem Cursor befand, bearbeitet werden. Da das vergangene Objekt jedoch keine Ausblendanimation benötigt, wird die vom Ereignisbehandler der Kollektion aufgerufene Methode für seine Verarbeitung sorgen - das Objekt sollte einfach ausgeblendet und das Nicht-Anzeigen-Flag für es gesetzt werden. Diese Logik unterscheidet sich von der Logik aktiver Objekte, die zunächst in die Liste der aktiven Elemente aufgenommen werden und dort von einem Zeitgeber ständig abgearbeitet werden.

Im privaten Abschnitt der Sammelklasse deklarieren wir die Liste der zu verarbeitenden Objekte:

   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


Deklarieren wir auch die Methoden zur Verarbeitung der aktuellen und vergangenen Objekte unter dem Cursor und die Methoden zur Verarbeitung der Liste der zu behandelnden Objekte:

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


Im öffentlichen Abschnitt schreiben wir die Methode, die eine Liste der zu behandelnden Objekte zurückgibt:

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

und deklarieren die Ereignisbehandlung des Timers der Kollektion:

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


Im Klassenkonstruktor löschen wir die Liste und setzen das Flag für die sortierte Liste auf die Liste:

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


Die Methode, die das grafische Element auf der Leinwand zur Kollektion hinzufügt:

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

Wir holen den Zeiger auf die Liste der zu behandelnden Objekte. Wenn das Objekt, das an die Methode übergeben wurde, nicht übergeben werden konnte, wird dies im Protokoll vermerkt und false zurückgegeben. Bei erfolgreicher Addition wird true zurückgegeben.


Die Methode, die ein Element nach Namen aus der Liste zur Bearbeitung erhält:

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

In der Schleife durch die Liste der zu bearbeitenden Objekte, das nächste Objekt holen. Wenn sein Name mit dem Namen des Objekts übereinstimmt, das an die Methode übergeben wurde, wird der Zeiger auf das gefundene Objekt zurückgegeben. Nach Abschluss der Schleife wird NULL zurückgegeben. Kein Objekt gefunden.


Die Methode, die den Index eines Elements nach Name in der Liste zur Bearbeitung zurückgibt:

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

In der Schleife durch die Liste der zu bearbeitenden Objekte, das nächste Objekt holen. Wenn sein Name mit dem Namen des an die Methode übergebenen Objekts übereinstimmt, wird der Schleifenindex zurückgegeben. Am Ende der Schleife wird -1 zurückgegeben, was bedeutet, dass das Objekt nicht gefunden wurde.


Die Bearbeitungsmethode für das aktuelle ToolTip-Element:

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

Die Methodenlogik wird in den Codekommentaren beschrieben. Die Methode verschiebt das Tooltip-Objekt zu den Cursor-Koordinaten und passt die empfangenen Koordinaten an, wenn das Objekt über den Rand des Bildschirms hinausgeht.


Die Methode zur Behandlung des vorherigen ToolTip-Elements:

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

Auch hier ist alles sehr transparent. Das vorherige Objekt befindet sich natürlich nicht mehr unter dem Cursor. Das bedeutet, dass es sofort ausgeblendet und der Zeiger darauf aus der Liste der zu behandelnden Objekte entfernt werden sollte.

Wenn der im letzten Artikel getestete Expert Advisor auf einem Chart gestartet wird und die Chart-Periode auf eine andere geändert wird, wird das Programm mit einem kritischen Fehler beendet. Dies geschieht, weil der Zeiger auf das Formularobjekt als statisch deklariert ist:

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

Das bedeutet, dass beim Ändern des Zeitrahmens des Charts die Daten darin erhalten bleiben und die Prüfung auf NULL ein negatives Ergebnis liefert, was besagt, dass das Objekt gültig ist:

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

Der Datensatz im Zeiger bezieht sich nicht mehr auf den Speicherbereich von ​diesem Objekt. Dies führt zum Abbruch des Programms, wenn auf die falsche Stelle im Speicher zugegriffen wird. Die Behebung des Fehlers ist einfach: Wir überprüfen die Gültigkeit des Zeigers, nicht seinen Wert:

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


Die Handhabung der vorherigen und aktuellen Tooltips ist nun wie folgt implementiert:

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

Die gesamte Logik ist in den Code-Kommentaren ausreichend detailliert beschrieben. Kurz gesagt, wenn sich das Objekt unter dem Cursor befindet, wird es der Liste zur Bearbeitung hinzugefügt und seine Koordinaten werden auf die Cursor-Koordinaten verschoben. Befindet sich das Objekt nicht unter dem Cursor, so wird es aus der Liste entfernt und ausgeblendet.


Der Timer der Kollektion:

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

Die Logik der Timer ist in den Kommentaren zum Code ausführlich beschrieben. Wir arbeiten mit zwei Schleifen - die Liste der zu bearbeitenden Objekte (ToolTip) und die Liste der aktiven Elemente (Objekte mit visuellen Animationseffekten)


Im Hauptobjekt der CEngine-Bibliothek in \MQL5\Include\DoEasy\Engine.mqh erstellen wir den Timer der grafischen Elemente der Bibliothek und den Timer für Ereignisse.

Im privaten Abschnitt deklarieren wir die Ereignisbehandlung:

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


Im Klassenkonstruktor erstellen wir einen weiteren Timer - den Timer für die Kollektion grafischer Elemente:

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


Im Timer der Ereignisbehandlung ergänzen wir die Codeblöcke für die Handhabung des Timers von grafischen Elementen:

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


Die Ereignisbehandlungsmethode für grafische Elemente auf der Leinwand (canvas):

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

Sobald der Pausentimer der Kollektion grafischer Elemente endet, wird diese Methode aufgerufen, die wiederum den Timer der Kollektionsklasse der grafischen Elemente aufruft. Der Standardaufruf erfolgt alle 16 Millisekunden (Timer-Intervall).

Alles ist bereit für einen Test.


Test

Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part126\ als TestDoEasy126.mq5.

In OnInit() des EAs erstellen wir auf dem zweiten Panel der ersten Registerkarte des TabControls im SplitContainer-Steuerelement das ProgressBar-Steuerelement:

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

Um sicherzugehen, dass das Objekt erstellt wurde, holen wir uns den Zeiger auf das Objekt und senden Sie seine Beschreibung an das Protokoll.

Kompilieren Sie den EA und starten Sie ihn auf einem Chart:


Die für den aktuellen Artikel angegebene Funktionsweisen funktioniert gut. In den Kommentaren zum Chart ist eine sich ständig ändernde Zahl zu sehen. Dies sind die Daten aus dem Timer des Fortschrittsbalkens des ProgressBar-Steuerelements.


Was kommt als Nächstes?

Im nächsten Artikel werde ich die Arbeit am ToolTip-Objekt fortsetzen.

Alle Dateien der aktuellen Bibliotheksversion, des Test-EA und des Chartereignis-Kontrollindikators für MQL5 sind unten angehängt, damit Sie sie testen und herunterladen können.

Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

 
DoEasy. Steuerung (Teil 20): Das WinForms-Objekt SplitContainer
DoEasy. Steuerung (Teil 21): SplitContainer-Steuerung. Paneel-Trennlinie
DoEasy. Steuerung (Teil 22): SplitContainer. Ändern der Eigenschaften des erstellten Objekts
DoEasy. Steuerung (Teil 23): Verbesserung der WinForms-Objekte TabControl und SplitContainer
DoEasy. Steuerung (Teil 24): Hinweis auf WinForms-Hilfsobjekt
DoEasy. Steuerung (Teil 25): Tooltip WinForms-Objekt

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/11732

Beigefügte Dateien |
MQL5.zip (4521.53 KB)
Nicht-lineare Indikatoren Nicht-lineare Indikatoren
In diesem Artikel werde ich versuchen, einige Möglichkeiten zur Erstellung nichtlinearer Indikatoren und deren Verwendung im Handel zu besprechen. In der MetaTrader-Handelsplattform gibt es eine ganze Reihe von Indikatoren, die nicht-lineare Ansätze verwenden.
Neuronale Netze leicht gemacht (Teil 32): Verteiltes Q-Learning Neuronale Netze leicht gemacht (Teil 32): Verteiltes Q-Learning
Wir haben die Q-Learning-Methode in einem der früheren Artikel dieser Serie kennengelernt. Bei dieser Methode werden die Belohnungen für jede Aktion gemittelt. Im Jahr 2017 wurden zwei Arbeiten vorgestellt, die einen größeren Erfolg bei der Untersuchung der Belohnungsverteilungsfunktion zeigen. Wir sollten die Möglichkeit in Betracht ziehen, diese Technologie zur Lösung unserer Probleme einzusetzen.
Algorithmen zur Optimierung mit Populationen Künstliches Bienenvolk (Artificial Bee Colony, ABC) Algorithmen zur Optimierung mit Populationen Künstliches Bienenvolk (Artificial Bee Colony, ABC)
In diesem Artikel werden wir den Algorithmus eines künstlichen Bienenvolkes untersuchen und unser Wissen durch neue Prinzipien zur Untersuchung funktionaler Räume ergänzen. In diesem Artikel werde ich meine Interpretation der klassischen Version des Algorithmus vorstellen.
DoEasy. Steuerung (Teil 25): Das WinForms-Objekt Tooltip DoEasy. Steuerung (Teil 25): Das WinForms-Objekt Tooltip
In diesem Artikel werde ich mit der Entwicklung des Steuerelements Tooltip (Schnellinfo) sowie neuer grafischer Primitive für die Bibliothek beginnen. Natürlich hat nicht jedes Element eine Tooltip, aber jedes grafische Objekt kann ein solches besitzen.