DoEasy. Steuerung (Teil 25): Das WinForms-Objekt Tooltip

Artyom Trishkin | 13 Januar, 2023

Inhalt


Konzept

Wenn Sie mit der Maus über ein Steuerelement fahren, erscheint nach einiger Zeit ein Tooltip mit einer Beschreibung. Natürlich hat nicht jedes Element eine Tooltip, aber jedes grafische Objekt kann ein solches besitzen. Erstellen wir eine solche Möglichkeit für die grafischen Elemente der Bibliothek.

Nicht jedes grafische Element in der Bibliothek „weiß“ von der Existenz anderer grafischer Elemente, aber alle diese Objekte können verwaltete Elemente mit sich selbst verbinden. Objekte, die an ein grafisches Element angehängt sind, können Objekte enthalten, von denen das übergeordnete Element nichts weiß. Da die Steuerung der angehängten Elemente in der Kollektionsklasse der grafischen Elemente erfolgt, gibt es keine Probleme mit der Sichtbarkeit der verschiedenen Arten von Steuerelementen — die Sammelklasse weiß alles über sie.

Wenn wir uns als Beispiel MS Visual Studio zuwenden, ist das erstellte ToolTip-Objekt im Formularobjekt sichtbar und kann Objekten zugewiesen werden, die an dieses Formular gebunden sind. Wir werden es folgendermaßen machen: die Objekte, die die Klasse des Tooltip-Objekts sehen, werden es erstellen können, und alle anderen Objekte werden das zuvor erstellte Objekt dieser Art an sich selbst anhängen können. Um dies zu tun, müssen sie es nicht „wissen“, da die ArrayObj-Liste jedes Objekt akzeptiert, das von der Basisklasse der Standardbibliothek abgeleitet ist.

Mit anderen Worten: In Container-Objekten (die die Fähigkeit haben, andere grafische Objekte zu erstellen und an sich selbst anzuhängen) können wir Tooltip-Steuerelemente erstellen und angeben, welchem Element dieser Tooltip zugewiesen werden soll. Das Element enthält den Zeiger auf das ToolTip-Objekt, während das Objekt, für das der Tooltip bestimmt ist, im zugewiesenen Tooltip-Objekt festgelegt wird.

In der Regel erscheint der Tooltip, nachdem der Mauszeiger eine Zeit lang über dem Steuerelement verweilt hat. Wenn einem Objekt mehrere Hinweise zugeordnet sind und der erste Hinweis bereits erschienen ist, dann erscheinen beim Überfahren solcher Objekte mit dem Mauszeiger die übrigen Tooltips fast ohne Verzögerung. Dieses Verhalten ist ein Thema für zukünftige Artikel. Hier werde ich nur das ToolTip-Steuerelement erstellen und es ermöglichen, es grafischen Elementen zuzuweisen.

Zusätzlich zur Entwicklung des neuen Steuerelements werde ich hier die Fähigkeit hinzufügen, neue grafische Primitive zu zeichnen, einschließlich Standard-Symbole sowie Rechts-Links- und Auf-Ab-Pfeile... Mit anderen Worten, ich werde langsam damit beginnen, die Bibliothek um die Möglichkeit zu erweitern, vordefinierte Bilder zu zeichnen und sie „wie sie sind“ zu verwenden. Solche Symbole können in den ToolTip-Steuerelementen und in anderen Elementen verwendet werden, die ich bereits früher erstellt habe. Später werde ich die Möglichkeit hinzufügen, neue grafische Primitive in allen grafischen Elementen der Bibliothek zu verwenden.


Verbesserung der Bibliotheksklassen

In \MQL5\Include\DoEasy\Defines.mqh fügen wir den neuen Grafikelementtyp 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_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
   //--- 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
  };
//+------------------------------------------------------------------+


Um einige Standardbilder wie Info-, Warn-, Fehler- und andere Symbole zeichnen zu können, erstellen wir eine Enumeration mit den entsprechenden Symboltypen:

//+-------------------------------------------------+
//| Separator location in Split Container           |
//+-------------------------------------------------+
enum ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION
  {
   CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL,        // Vertical
   CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,      // Horizontal
  };
//+-------------------------------------------------+
//| 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
  };
//+-----------------------------------------------------------+
//| Integer properties of the graphical element on the canvas |
//+-----------------------------------------------------------+

Hier gebe ich die Namen der neuen Symbole ein, die ich in der Bibliothek erstellen werde. Dann können wir einfach die Art des Symbols, das wir auf dem Steuerelement zeichnen möchten, aus der Liste auswählen. Die Methoden zum Zeichnen der einzelnen Arten von Symbolen aus der Liste werden später implementiert.


Das neue Steuerelement erfordert neue Eigenschaften.

Wir fügen die neuen Eigenschaften zur Liste der ganzzahligen grafischen Elementeigenschaften hinzu und erhöhen wir deren Gesamtzahl von 122 auf 129:

//+------------------------------------------------------------------+
//| 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_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Flag for collapsed panel 2
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Panel 2 minimum size
   CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,           // Tooltip display delay
   CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,          // Tooltip display duration
   CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,            // One element new tooltip display delay
   CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,             // Display a tooltip in inactive window
   CANV_ELEMENT_PROP_TOOLTIP_ICON,                    // Icon displayed in tooltip
   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
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (129)         // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Der Liste der Zeichenketteneigenschaften fügen wir zwei neue Eigenschaften hinzu und erhöhen deren Gesamtzahl auf 6:

//+------------------------------------------------------------------+
//| String properties of the graphical element on the canvas         |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_STRING
  {
   CANV_ELEMENT_PROP_NAME_OBJ = (CANV_ELEMENT_PROP_INTEGER_TOTAL+CANV_ELEMENT_PROP_DOUBLE_TOTAL), // Graphical element object name
   CANV_ELEMENT_PROP_NAME_RES,                        // Graphical resource name
   CANV_ELEMENT_PROP_TEXT,                            // Graphical element text
   CANV_ELEMENT_PROP_DESCRIPTION,                     // Graphical element description
   CANV_ELEMENT_PROP_TOOLTIP_TITLE,                   // Element tooltip title
   CANV_ELEMENT_PROP_TOOLTIP_TEXT,                    // Element tooltip text
  };
#define CANV_ELEMENT_PROP_STRING_TOTAL  (6)           // Total number of string properties
//+------------------------------------------------------------------+


Wir fügen neue Eigenschaften zur Liste der möglichen Kriterien für die Sortierung von grafischen Elementen auf der Leinwand hinzu:

//+------------------------------------------------------------------+
//| 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_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Sort by flag for collapsed panel 2
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Sort by panel 2 minimum size
   SORT_BY_CANV_ELEMENT_TOOLTIP_INITIAL_DELAY,        // Sort by tooltip display delay
   SORT_BY_CANV_ELEMENT_TOOLTIP_AUTO_POP_DELAY,       // Sort by tooltip display duration
   SORT_BY_CANV_ELEMENT_TOOLTIP_RESHOW_DELAY,         // Sort by one element new tooltip display delay
   SORT_BY_CANV_ELEMENT_TOOLTIP_SHOW_ALWAYS,          // Sort by a tooltip in inactive window
   SORT_BY_CANV_ELEMENT_TOOLTIP_ICON,                 // Sort by icon displayed in a tooltip
   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 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 header for an element
   SORT_BY_CANV_ELEMENT_TOOLTIP_TEXT,                 // Sort by ToolTip text for an element
  };
//+------------------------------------------------------------------+

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


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

//--- CForm
   MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// No shadow object. Create it using the CreateShadowObj() method
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ,      // Failed to create new shadow object
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ,          // Failed to create new pixel copier object
   MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST,            // Pixel copier object with ID already present in the list 
   MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST,             // No pixel copier object with ID in the list 
   MSG_FORM_OBJECT_ERR_NOT_INTENDED,                  // The method is not meant for creating such an object: 
   MSG_FORM_TOOLTIP_OBJ_ALREADY_EXISTS,               // ToolTip object already exists

//--- CFrame

...

   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,            // HintMoveLeft control
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,          // HintMoveLeft control
   MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP,                 // ToolTip 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

...

   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,  // Flag for collapsed panel 1
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,   // Panel 2 minimum size
   MSG_CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,       // Tooltip display delay
   MSG_CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,      // Tooltip display duration
   MSG_CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,        // One element new tooltip display delay
   MSG_CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,         // Display a tooltip in inactive window
   MSG_CANV_ELEMENT_PROP_TOOLTIP_ICON,                // Icon displayed in a tooltip
   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
   
//--- 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
  };
//+------------------------------------------------------------------+

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

//--- CForm
   {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"},
   {"Не удалось создать новый объект для тени","Failed to create new object for shadow"},
   {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"},
   {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "},
   {"В списке нет объекта-копировщика пикселей с идентификатором ","No pixel copier object with ID "},
   {"Метод не предназначен для создания такого объекта: ","The method is not intended to create such an object: "},
   {"Объект ToolTip уже существует","ToolTip object already exists"},
   
//--- CFrame

...

   {"Элемент управления \"HintMoveUp\"","Control element \"HintMoveUp\""},
   {"Элемент управления \"HintMoveDown\"","Control element \"HintMoveDown\""},
   {"Элемент управления \"ToolTip\"","Control element \"ToolTip\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

...

   {"Флаг свёрнутости панели 2","Flag to indicate that panel 2 is collapsed"},
   {"Минимальный размер панели 2","Min size of Panel 2"},
   {"Задержка отображения подсказки","Tooltip initial delay"},
   {"Длительность отображения подсказки","Tooltip autopoop delay"},
   {"Задержка отображения новой подсказки одного элемента","Tooltip reshow delay"},
   {"Отображать подсказку в неактивном окне","Tooltip show always"},
   {"Значок, отображаемый в подсказке","Tooltip icon"},
   {"Подсказка в форме \"облачка\"","Tooltip as \"Balloon\""},
   {"Угасание при отображении и скрытии подсказки","Tooltip uses fading"},

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


Um eine Beschreibung eines grafischen Elements zu erhalten, enthält die Datei \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh des Basisobjekts der grafischen Bibliothek die Methode TypeElementDescription(). Fügen wir eine neue Beschreibung der Steuerelemente hinzu:

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

Die Methode erhält den Typ des Objekts, dessen Beschreibung ermittelt werden soll. Je nach übergebenem Typ wird eine Textbeschreibung angezeigt, die dem Index der Nachricht entspricht, die wir oben in die Datei Data.mqh eingefügt haben.

Bisher wurde das Basisobjekt für ein grafisches Element manuell festgelegt, wenn ein angefügtes grafisches Element erstellt wurde.

Außerdem habe ich im vorherigen Artikel die Notwendigkeit, dies zu tun, beseitigt und die Methoden zum Schreiben der Zeiger auf die Haupt- und Basisobjekte in das Objekt entfernt. Die Praxis hat jedoch gezeigt, dass wir solche Methoden weiterhin brauchen. Um das Element, für das das Tooltip erstellt wurde, in ein neues Tooltip-Objekt zu schreiben, benötigen wir eine Methode zum Schreiben des Zeigers auf das Basisobjekt.

Im öffentlichen Abschnitt in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh schreiben wir erneut eine solche Methode:

public:
//--- (1) Set and (2) return the X coordinate shift relative to the base object
   void              SetCoordXRelative(const int value)                                { this.m_shift_coord_x=value;                }
   int               CoordXRelative(void)                                        const { return this.m_shift_coord_x;               }
//--- (1) Set and (2) return the Y coordinate shift relative to the base object
   void              SetCoordYRelative(const int value)                                { this.m_shift_coord_y=value;                }
   int               CoordYRelative(void)                                        const { return this.m_shift_coord_y;               }
//--- Set the pointer to the parent element within related objects of the current group
   void              SetBase(CGCnvElement *base)                                       { this.m_element_base=base;                  }
   
//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

Die Methode erhält den Zeiger auf das Basisobjekt, das der Variablen m_element_base zugewiesen ist. Um anzuzeigen, für welches Objekt ein Tooltip erstellt wurde, schreiben wir den Zeiger auf das Element, das einen Tooltip auslöst, wenn der Mauszeiger darüber schwebt.

Die Methoden zum Einstellen der Element-Opazität und zum Festlegen des Element-Display-Flags sind virtuell, da wir sie in abgeleiteten Klassen neu definieren müssen. Außerdem fügen wir die Methoden zum Setzen und Zurückgeben eines Tooltip-Textes hinzu:

//--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element,
//--- (5) all shifts of the active area edges relative to the element, (6) opacity
   void              SetActiveAreaLeftShift(const int value)   { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value));       }
   void              SetActiveAreaRightShift(const int value)  { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value));      }
   void              SetActiveAreaTopShift(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value));        }
   void              SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value));     }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   virtual void      SetOpacity(const uchar value,const bool redraw=false);
   
//--- (1) Set and (2) return the Tooltip text
   virtual void      SetTooltipText(const string text)         { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TEXT,text);                }
   virtual string    TooltipText(void)                         { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_TEXT);              }
   
//--- (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);           }


Der Konstruktorkörper enthält die Liste der Methoden zum Zeichnen verschiedener grafischer Primitive. Am Ende der Liste deklarieren wir neue Methoden zum Zeichnen vordefinierter „Standard“-Bilder:

//--- Return coordinate offsets relative to the rectangle anchor point by size
   void              GetShiftXYbySize(const int width,               // Rectangle size by width
                                      const int height,              //Rectangle size by height
                                      const ENUM_FRAME_ANCHOR anchor,// Rectangle anchor point, relative to which the offsets are calculated
                                      int &shift_x,                  // X coordinate of the rectangle upper left corner
                                      int &shift_y);                 // Y coordinate of the rectangle upper left corner
                                      
//+-------------------------------------------------+
//| Methods for drawing predefined standard images  |
//+-------------------------------------------------+
//--- Draw the Info icon
   void              DrawIconInfo(const int coord_x,const int coord_y,const uchar opacity);
//--- Draw the Warning icon
   void              DrawIconWarning(const int coord_x,const int coord_y,const uchar opacity);
//--- Draw the Error icon
   void              DrawIconError(const int coord_x,const int coord_y,const uchar opacity);
//--- Draw the left arrow
   void              DrawArrowLeft(const int coord_x,const int coord_y,const color clr,const uchar opacity);
//--- Draw the right arrow
   void              DrawArrowRight(const int coord_x,const int coord_y,const color clr,const uchar opacity);
//--- Draw the up arrow
   void              DrawArrowUp(const int coord_x,const int coord_y,const color clr,const uchar opacity);
//--- Draw the down arrow
   void              DrawArrowDown(const int coord_x,const int coord_y,const color clr,const uchar opacity);
  };
//+------------------------------------------------------------------+


In beiden Klassenkonstruktoren werden die Standardwerte für neue Eigenschaften des grafischen Elements festgelegt:

//+-------------------------------------------------+
//| 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_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Panel 2 minimum size
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,500);                                  // Tooltip display delay
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,5000);                                // Tooltip display duration
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,100);                                   // One element new tooltip display delay
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,false);                                  // Display a tooltip in inactive window
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON,CANV_ELEMENT_TOOLTIP_ICON_NONE);                // Icon displayed in a tooltip
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,false);                                   // Tooltip in the form of a "cloud"
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,true);                                    // Fade when showing/hiding a tooltip
      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());
     }
  }
//+------------------------------------------------------------------+
//| 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_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25);                         // Panel 1 minimum size
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Panel 2 minimum size
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,500);                                  // Tooltip display delay
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,5000);                                // Tooltip display duration
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,100);                                   // One element new tooltip display delay
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,false);                                  // Display a tooltip in inactive window
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON,CANV_ELEMENT_TOOLTIP_ICON_NONE);                // Icon displayed in a tooltip
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,false);                                   // Tooltip in the form of a "cloud"
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,true);                                    // Fade when showing/hiding a tooltip
      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());
     }
  }
//+------------------------------------------------------------------+


Außerhalb des Klassenkörpers schreiben wir die Methoden zum Zeichnen vordefinierter Bilder.

Die Methode, die das Info-Symbol zeichnet:

//+-------------------------------------------------+
//| Draw the Info icon                              |
//+-------------------------------------------------+
void CGCnvElement::DrawIconInfo(const int coord_x,const int coord_y,const uchar opacity)
  {
   int x=coord_x+8;
   int y=coord_y+8;
   this.DrawCircleFill(x,y,7,C'0x00,0x77,0xD7',opacity);
   this.DrawCircleWu(x,y,7.5,C'0x00,0x3D,0x8C',opacity);
   this.DrawRectangle(x,y-5,x+1,y-4, C'0xFF,0xFF,0xFF',opacity);
   this.DrawRectangle(x,y-2,x+1,y+4,C'0xFF,0xFF,0xFF',opacity);
  }
//+------------------------------------------------------------------+

Die Koordinaten der oberen linken Ecke des Rechtecks, das das Bild umreißt, werden an die Methode übergeben. Anschließend werden die Koordinaten des Mittelpunkts des Kreises berechnet und ein gefüllter Kreis gezeichnet, während darüber ein Kreis mit Glättung nach der Wu-Methode gezeichnet wird. Nachdem die Kreise gezeichnet wurden, wird das i-Symbol gezeichnet. Es besteht aus zwei Rechtecken, wobei das erste einen 2x2 Punkt und das zweite eine 2x6 vertikale Linie zeichnet. Leider können wir aufgrund des seltsamen Anti-Aliasing-Algorithmus, der beim Zeichnen einer solchen Linie angewendet wird, keine geglätteten Linien mit einer bestimmten Breite (LineThick) zeichnen. Daher zeichnen wir einfach ein Rechteck mit zwei Pixeln Breite und der gewünschten Höhe, um zwei Pixel-Linien zu zeichnen. In der Methode sind alle Farben vordefiniert, sodass das Bild standardmäßig wie in MS Visual Studio aussieht.


Die Methode, die das Warnsymbol zeichnet:

//+-------------------------------------------------+
//| Draw the Warning icon                           |
//+-------------------------------------------------+
void CGCnvElement::DrawIconWarning(const int coord_x,const int coord_y,const uchar opacity)
  {
   int x=coord_x+8;
   int y=coord_y+1;
   this.DrawTriangleFill(x,y,x+8,y+14,x-8,y+14,C'0xFC,0xE1,0x00',opacity);
   this.DrawTriangleWu(x,y,x+8,y+14,x-7,y+14,C'0xFF,0xB9,0x00',opacity);
   this.DrawRectangle(x,y+5,x+1,y+9,  C'0x00,0x00,0x00',opacity);
   this.DrawRectangle(x,y+11,x+1,y+12,C'0x00,0x00,0x00',opacity);
  }
//+------------------------------------------------------------------+

Die Koordinaten der oberen linken Ecke des Rechtecks, das das Bild umreißt, werden an die Methode übergeben. Anschließend werden die Koordinaten des oberen Punktes des Dreiecks berechnet, ein ausgefülltes Dreieck gezeichnet und ein Dreieck mit Glättung nach der Wu-Methode darüber gezeichnet. Bei dem zweiten Dreieck ist die X-Koordinate seines Scheitelpunkts im Vergleich zum ersten ausgefüllten Dreieck um ein Pixel nach rechts verschoben. Dies geschieht, um den Scheitelpunkt optisch dicker zu machen, da das Symbol innerhalb der Dreiecke zwei Pixel breit sein sollte und nicht genau in der Mitte des Dreiecks positioniert werden kann. Deshalb haben wir die Spitze des Dreiecks um zwei Pixel „gespreizt“, wodurch die Position des Symbols im Inneren visuell zentriert wird. Nach dem Zeichnen der Dreiecke wird das i-Symbol gezeichnet. Es besteht aus zwei Rechtecken, wobei das erste eine vertikale 2x6-Linie und das zweite einen 2x2-Punkt zeichnet.

Die Methode, die das Fehlersymbol zeichnet:

//+-------------------------------------------------+
//| Draw the Error icon                             |
//+-------------------------------------------------+
void CGCnvElement::DrawIconError(const int coord_x,const int coord_y,const uchar opacity)
  {
   int x=coord_x+8;
   int y=coord_y+8;
   this.DrawCircleFill(x,y,7,C'0xF0,0x39,0x16',opacity);
   this.DrawCircleWu(x,y,7.5,C'0xA5,0x25,0x12',opacity);
   this.DrawLineWu(x-3,y-3,x+3,y+3,C'0xFF,0xFF,0xFF',opacity);
   this.DrawLineWu(x+3,y-3,x-3,y+3,C'0xFF,0xFF,0xFF',opacity);
  }
//+------------------------------------------------------------------+

Bei dieser Methode werden zwei Kreise gezeichnet — einer, mit Farbe gefüllt, und ein geglätteter, der ihn umhüllt.. In der Mitte befinden sich zwei geglättete Linien, die das X-Symbol bilden. Wie bei anderen Symbolen sind die Farben vordefiniert und passen zu MS Visual Studio.


Die Methoden, die Pfeile nach links, rechts, oben und unten zeichnen:

//+-------------------------------------------------+
//| Draw the left arrow                             |
//+-------------------------------------------------+
void CGCnvElement::DrawArrowLeft(const int coord_x,const int coord_y,const color clr,const uchar opacity)
  {
   int x=coord_x;
   int y=coord_y+5;
   this.DrawTriangleFill(x,y,x+3,y-3,x+3,y+3,clr,opacity);
   this.DrawTriangleWu(x,y,x+3,y-3,x+3,y+3,clr,opacity);
  }
//+-------------------------------------------------+
//| Draw the right arrow                            |
//+-------------------------------------------------+
void CGCnvElement::DrawArrowRight(const int coord_x,const int coord_y,const color clr,const uchar opacity)
  {
   int x=coord_x;
   int y=coord_y+5;
   this.DrawTriangleFill(x+3,y,x,y+3,x,y-3,clr,opacity);
   this.DrawTriangleWu(x+3,y,x,y+3,x,y-3,clr,opacity);
  }
//+-------------------------------------------------+
//| Draw the up arrow                               |
//+-------------------------------------------------+
void CGCnvElement::DrawArrowUp(const int coord_x,const int coord_y,const color clr,const uchar opacity)
  {
   int x=coord_x+5;
   int y=coord_y;
   this.DrawTriangleFill(x,y,x+3,y+3,x-3,y+3,clr,opacity);
   this.DrawTriangleWu(x,y,x+3,y+3,x-3,y+3,clr,opacity);
  }
//+-------------------------------------------------+
//| Draw the down arrow                             |
//+-------------------------------------------------+
void CGCnvElement::DrawArrowDown(const int coord_x,const int coord_y,const color clr,const uchar opacity)
  {
   int x=coord_x+5;
   int y=coord_y+3;
   this.DrawTriangleFill(x,y,x+3,y-3,x-3,y-3,clr,opacity);
   this.DrawTriangleWu(x,y,x+3,y-3,x-3,y-3,clr,opacity);
  }
//+------------------------------------------------------------------+

Die Koordinaten der linken oberen Ecke des Umrissrechtecks sowie die Farbe und Deckkraft der gezeichneten Pfeile werden an alle Methoden übergeben. Anschließend werden die Koordinaten des ursprünglichen Scheitelpunkts des Dreiecks berechnet und ein ausgefülltes Dreieck gezeichnet, während das nach der Wu-Methode geglättete Dreieck darüber gezeichnet wird. Für jeden weiteren Scheitelpunkt des gezeichneten Dreiecks werden die Koordinaten mit einem Versatz zum ersten Scheitelpunkt berechnet. Farbe und Deckkraft werden so festgelegt, dass das Aussehen der gezeichneten Dreiecke je nach Zustand des grafischen Elements, auf dem der Pfeil gezeichnet wird, verändert werden kann. Im Falle eines inaktiven Elements sollte der Pfeil beispielsweise grau sein. Im Allgemeinen gibt es hier etwas mehr Möglichkeiten als bei den Symbolen.

Ein Schattenobjekt existiert immer in Verbindung mit einem anderen Objekt. Die Deckkraft des Hintergrunds, auf dem der Schatten gezeichnet wird, sollte immer Null sein — das Objekt sollte immer transparent sein. Gleichzeitig sollte der auf diesem Untergrund gezeichnete Schatten auch einen gewissen Grad an Transparenz aufweisen, da der Schatten seltsam aussehen würde, wenn er die Objekte, auf die er geworfen wird, vollständig verdeckt. Führen wir also das Konzept der Deckkraft des gezeichneten Schattens für das Schattenobjekt ein. Dieser Wert sollte sich ändern, wenn die Methode SetOpacity() aufgerufen wird, sodass der Hintergrund jederzeit transparent ist. Dieselbe Methode funktioniert auch für die Unterlage, wobei genau ihr Deckkraftwert festgelegt wird. Fügen wir also die neue Methode SetOpacityDraw (Deckkraft des gezeichneten Schattens) hinzu und kombinieren diese beiden Methoden. Wenn sie aufgerufen werden, wird die Deckkraft des Hintergrunds immer auf Null gesetzt, und für die gezeichnete Schattenfarbe wird der in der Methode angegebene Wert verwendet.

In \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh benennen wir die Methoden SetOpacity() und Opacity() um und deklarieren die virtuelle Methode SetOpacity():

//--- Draw an object shadow
   void              Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw);
//--- Set the element opacity
   virtual void      SetOpacity(const uchar value,const bool redraw=false);

//+------------------------------------------------------------------+
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- (1) Set and (2) return the shadow color
   void              SetColor(const color colour)                             { this.m_color=colour;     }
   color             Color(void)                                        const { return this.m_color;     }
//--- (1) Set and (2) return the drawn shadow opacity
   void              SetOpacityDraw(const uchar opacity)                      { this.m_opacity=opacity;  }
   uchar             OpacityDraw(void)                                  const { return this.m_opacity;   }
//--- (1) Set and (2) return the shadow blur
   void              SetBlur(const uchar blur)                                { this.m_blur=blur;        }
   uchar             Blur(void)                                         const { return this.m_blur;      }
  };
//+------------------------------------------------------------------+


Im Klassenkonstruktor legen wir die Standardwerte für die Deckkraft des gezeichneten Schattens und seine Unschärfe fest:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                       const long chart_id,
                       const int subwindow,
                       const string name,
                       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,name,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();
  }
//+------------------------------------------------------------------+


Um die Deckkraft in der Methode festzulegen, die die Form des Objektschattens zeichnet, verwenden wir den Wert, der für die Deckkraft des gezeichneten Schattens festgelegt wurde:

//+------------------------------------------------------------------+
//| Draw the object shadow form                                      |
//+------------------------------------------------------------------+
void CShadowObj::DrawShadowFigureRect(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();
  }
//+------------------------------------------------------------------+


Schreiben wir eine neue virtuelle Methode, die die Deckkraft eines Elements festlegt:

//+------------------------------------------------------------------+
//| Set the element opacity                                          |
//+------------------------------------------------------------------+
void CShadowObj::SetOpacity(const uchar value,const bool redraw=false)
  {
   CGCnvElement::SetOpacity(0,false);
   this.SetOpacityDraw(value>(uchar)CLR_DEF_SHADOW_OPACITY ? (uchar)CLR_DEF_SHADOW_OPACITY : value);
   this.m_canvas.Update(redraw);
  }
//+------------------------------------------------------------------+

Beim Aufruf der Methode wird zunächst die vollständige Transparenz des Schattenuntergrunds eingestellt. Anschließend wird der an die Methode übergebene Deckkraftwert für den gezeichneten Schatten festgelegt. In diesem Fall wird der Wert verwendet, wenn er den für die Bibliothek festgelegten Standardwert für die Schattentrübung überschreitet. Dadurch wird verhindert, dass undurchsichtige Schatten entstehen, die die darunter liegenden Objekte vollständig verdecken.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh ändern wir die Logik der Methode, mit der ein Objekt neu gezeichnet wird. Bisher wurde der Schatten eines Objekts immer dann gezeichnet, wenn er vorhanden war. Wenn das Flag zum Neuzeichnen zurückgesetzt wurde, wurde der Schatten einfach gelöscht. Dies führte dazu, dass die Schatten von Gegenständen verschwinden konnten. Machen wir das alles innerhalb der Überprüfung des Neuzeichnen-Flags:

//+-------------------------------------------------+
//| Redraw the object                               |
//+-------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- If the object type is less than the "Base WinForms object" or the object is not to be displayed, exit
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || !this.Displayed())
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the redraw flag is set,
   if(redraw)
     {
      //--- completely redraw the object and save its new initial look
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw);
      this.Done();
      //--- If the object has a shadow and the "Shadow" object exists, redraw it
      if(this.IsShadow() && shadow!=NULL)
        {
         //--- remove the previously drawn shadow,
         shadow.Erase();
         //--- save the relative shadow coordinates,
         int x=shadow.CoordXRelative();
         int y=shadow.CoordYRelative();
         //--- redraw the shadow,
         shadow.Draw(0,0,shadow.Blur(),redraw);
         //--- restore relative shadow coordinates
         shadow.SetCoordXRelative(x);
         shadow.SetCoordYRelative(y);
        }
     }
//--- otherwise, remove the object
   else
      this.Erase();
//--- Redraw all bound objects with the redraw flag
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(redraw)
         element.Redraw(redraw);
     }
//--- If the redraw flag is set and if this is the main object the rest are bound to,
//--- redraw the chart to display changes immediately
   if(this.IsMain() && redraw)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+


In der Methode, die die Beschreibung der Ganzzahl-Eigenschaft des Elements zurückgibt, fügen wir den Codeblock für die Rückgabe der Beschreibungen neuer Eigenschaften hinzu:

//+------------------------------------------------------------------+
//| 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_SPLIT_CONTAINER_PANEL2_MIN_SIZE ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_ICON ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_ICON)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON)+
         (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)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+


In der Methode, die die Beschreibung der String-Eigenschaft des Elements zurückgibt, ergänzen wir auch die Rückgabe der Beschreibungen neuer String-Eigenschaften hinzu:

//+------------------------------------------------------------------+
//| Return the description of the control string property            |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_STRING property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_NAME_OBJ         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_OBJ)+": \""+this.GetProperty(property)+"\""        :
      property==CANV_ELEMENT_PROP_NAME_RES         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_RES)+": \""+this.GetProperty(property)+"\""        :
      property==CANV_ELEMENT_PROP_TEXT             ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TEXT)+": \""+this.GetProperty(property)+"\""            :
      property==CANV_ELEMENT_PROP_DESCRIPTION      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DESCRIPTION)+": \""+this.GetProperty(property)+"\""     :
      property==CANV_ELEMENT_PROP_TOOLTIP_TITLE    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_TITLE)+": \""+this.GetProperty(property)+"\""   :
      property==CANV_ELEMENT_PROP_TOOLTIP_TEXT     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_TEXT)+": \""+this.GetProperty(property)+"\""    :
      ""
     );
  }
//+------------------------------------------------------------------+


Da wir nun Methoden zum Zeichnen von Standardpfeilen haben, fügen wir deren Aufruf zu den DrawArrow()-Methoden zum Zeichnen von Pfeilen in den Pfeilschaltflächen-Objektklassen hinzu.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowLeftButton.mqh:

//+-------------------------------------------------+
//| Draw the arrow                                  |
//+-------------------------------------------------+
void CArrowLeftButton::DrawArrow(void)
  {
   CGCnvElement::DrawArrowLeft(5,2,this.ArrowColor(),this.Opacity());
  }
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowRightButton.mqh:

//+-------------------------------------------------+
//| Draw the arrow                                  |
//+-------------------------------------------------+
void CArrowRightButton::DrawArrow(void)
  {
   CGCnvElement::DrawArrowRight(6,2,this.ArrowColor(),this.Opacity());
  }
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowUpButton.mqh:

//+-------------------------------------------------+
//| Draw the arrow                                  |
//+-------------------------------------------------+
void CArrowUpButton::DrawArrow(void)
  {
   CGCnvElement::DrawArrowUp(2,5,this.ArrowColor(),this.Opacity());
  }
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowDownButton.mqh:

//+-------------------------------------------------+
//| Draw the arrow                                  |
//+-------------------------------------------------+
void CArrowDownButton::DrawArrow(void)
  {
   CGCnvElement::DrawArrowDown(2,6,this.ArrowColor(),this.Opacity());
  }
//+------------------------------------------------------------------+

Wie man sehen kann, rufen wir in jeder Klasse einfach die Methode zum Zeichnen des gewünschten Pfeils aus der Klasse des grafischen Basiselements auf, die dem Zweck des Klassenobjekts entspricht. Wenn die Klasse eine Schaltfläche mit einem linken Pfeil erstellt, wird die Methode zum Zeichnen des linken Pfeils aufgerufen, für die Schaltfläche mit dem rechten Pfeil wird die Methode zum Zeichnen des rechten Pfeils aufgerufen usw.

Beginnen wir mit der Entwicklung des Tooltip-Objekts.


Die WinForms Objektklasse ToolTip

Im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ erstellen wir die neue Datei ToolTip.mqh für die Klasse CToolTip. Die Klasse sollte sollte von der Basisklasse des Hinweisobjekts abgeleitet, und ihre Datei sollte in die erstellte Klassendatei eingebunden werden:

//+------------------------------------------------------------------+
//|                                                      ToolTip.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 "..\Helpers\HintBase.mqh"
//+------------------------------------------------------------------+
//| Class of the base Hint object of the WForms controls             |
//+------------------------------------------------------------------+
class CToolTip : public CHintBase
  {
  }


Im privaten Abschnitt der Klasse deklarieren wir die Methode, mit der die Größe des Tooltips entsprechend den eingestellten Texten angepasst wird. Im geschützten Abschnitt der Klasse schreiben wir die Methoden, mit denen die Symbole der verschiedenen Typen gezeichnet werden, und deklarieren den Konstruktor der geschützten Klasse:

class CToolTip : public CHintBase
  {
private:
//--- Adjust a tooltip size according to a text size
   void              CorrectSizeByTexts(void);
protected:
   //--- Draw (1) tooltip, (2) icon, (3) Info, (4) Warning, (5) Error and (6) User
   virtual void      DrawHint(const int shift);
   void              DrawIcon(void);
   void              DrawIconInfo(void)                  { CGCnvElement::DrawIconInfo(3,1,this.Opacity());                       }
   void              DrawIconWarning(void)               { CGCnvElement::DrawIconWarning(3,1,this.Opacity());                    }
   void              DrawIconError(void)                 { CGCnvElement::DrawIconError(3,1,this.Opacity());                      }
   virtual void      DrawIconUser(void)                  { return; }
//--- Protected constructor with object type, chart ID and subwindow
                     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);
public:

Die Methoden zum Zeichnen der Info-, Warn- und Fehlersymbole rufen einfach die entsprechenden Methoden der oben implementierten Basisklasse für grafische Elemente auf. Die virtuelle Methode, die das nutzerdefinierte Symbol zeichnet, tut nichts. Sie müsste in abgeleiteten Klassen neu definiert werden, falls wir die Funktionsweise dieses Objekts eigenständig ändern wollten.

Im öffentlichen Abschnitt der Klasse implementieren wir die Methoden zum Setzen und Zurückgeben der Eigenschaften, die ich heute für dieses Objekt hinzugefügt habe. Wir deklarieren den Konstruktor der parametrischen Klasse und die Methoden zum Anzeigen, Neuzeichnen, Löschen, Zeichnen eines Rahmens und Initialisieren eines Objekts:

public:
//--- (1) Set and (2) return the tooltip display delay
   void              SetInitialDelay(const long delay)   { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,delay);      }
   long              InitialDelay(void)                  { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY);     }
   
//--- (1) Set and (2) return the tooltip display duration
   void              SetAutoPopDelay(const long delay)   { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,delay);     }
   long              AutoPopDelay(void)                  { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY);    }
   
//--- (1) Set and (2) return the delay in a display of a new tooltip of one element
   void              SetReshowDelay(const long delay)    { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,delay);       }
   long              ReshowDelay(void)                   { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY);      }
   
//--- (1) Set and (2) return the flag for displaying a tooltip in an inactive window
   void              SetShowAlways(const bool flag)      { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,flag);         }
   bool              ShowAlways(void)                    { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS); }
   
//--- (1) Set and (2) return the type of the icon displayed in a tooltip
   void              SetIcon(const ENUM_CANV_ELEMENT_TOOLTIP_ICON ico)
                       { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON,ico);   }
   ENUM_CANV_ELEMENT_TOOLTIP_ICON Icon(void)
                       { return (ENUM_CANV_ELEMENT_TOOLTIP_ICON)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON); }
   
//--- (1) Set and (2) return the cloud tooltip flag
   void              SetBalloon(const bool flag)         { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,flag);          }
   bool              Balloon(void)                       { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON);  }
   
//--- (1) Set and (2) return the flag of fading when showing/hiding a tooltip
   void              SetUseFading(const bool flag)       { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,flag);          }
   bool              UseFading(void)                     { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING);  }
   
//--- (1) Set and (2) return the Tooltip title
   void              SetTitle(const string header);
   string            Title(void)                         { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_TITLE);             }
   
//--- (1,2) Set the Tooltip text
   virtual void      SetTooltipText(const string text);
   virtual void      SetText(const string text)          { this.SetTooltipText(text);                                            }
   
//--- Constructor
                     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);
//--- 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);
  };
//+------------------------------------------------------------------+

Schauen wir uns die deklarierten Methoden der Klasse genauer an.

Der geschützte Konstruktor, der den Objekttyp, die Chart-ID und das Unterfenster angibt:

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

Der Konstruktor erhält den Typ des erstellten grafischen Elements und andere Objektparameter, die in der Initialisierungszeichenfolge auf die übergeordnete Klasse eingestellt sind. Im Klassenkörper bestimmen wir den Typ des grafischen Elements, das an die Methode übergeben wird, und legen den Typ des grafischen Objekts der Bibliothek fest. Rufen wir die Methode zum Setzen aller Standardobjektparameter auf.

Der parametrische Konstruktor:

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

Hier ist alles ähnlich wie beim geschützten Konstruktor, aber der Typ des grafischen Elements ist fest als ToolTip kodiert.


Initialisierungsmethode der Variablen

//+-------------------------------------------------+
//| 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(500);
   this.SetAutoPopDelay(5000);
   this.SetReshowDelay(100);
   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();
  }
//+------------------------------------------------------------------+

Hier werden alle Eigenschaften des Tooltip-Objekts angegeben, die es unmittelbar nach seiner Erstellung haben soll.


Die Methode zum Neuzeichnen eines Objekts:

//+-------------------------------------------------+
//| Redraw the object                               |
//+-------------------------------------------------+
void CToolTip::Redraw(bool redraw)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed() || this.Text()=="")
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the redraw flag is set,
   if(redraw)
     {
      //--- completely redraw the object and save its new initial look
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,true);
      this.Done();
      //--- If the object has a shadow and the "Shadow" object exists, redraw it
      if(this.IsShadow() && shadow!=NULL)
        {
         //--- remove the previously drawn shadow,
         shadow.Erase();
         //--- save the relative shadow coordinates,
         int x=shadow.CoordXRelative();
         int y=shadow.CoordYRelative();
         //--- redraw the shadow,
         shadow.Draw(0,0,shadow.Blur(),true);
         //--- restore relative shadow coordinates
         shadow.SetCoordXRelative(x);
         shadow.SetCoordYRelative(y);
        }
     }
//--- otherwise, erase the object and its shadow
   else
     {
      CGCnvElement::Erase();
      if(this.IsShadow() && shadow!=NULL)
         shadow.Erase();
     }
  }
//+------------------------------------------------------------------+

Die Logik der Methode wird in den Codekommentaren beschrieben. Hier ist fast alles genau dasselbe wie in der gleichnamigen Methode in der Klasse des WinForms-Basisobjekts CWinFormBase, außer dass die gebundenen Objekte und das Chart neu gezeichnet werden.


Die Methoden, um ein Element zu löschen, indem man es mit einer Hintergrundfarbe zeichnet:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CToolTip::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Draw a hint
   this.DrawHint(0);
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+-------------------------------------------------+
//| Clear the element with a gradient fill          |
//+-------------------------------------------------+
void CToolTip::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Draw a hint
   this.DrawHint(0);
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Die Methoden sind identisch mit den gleichnamigen Methoden anderer grafischer Elemente der Bibliothek, insbesondere mit den Methoden des übergeordneten Objekts. Wenn hier keine weiteren Änderungen vorgenommen werden müssen, werden sie aus der Klasse entfernt.

Die Methode, die den Rand eines Elements zeichnet:

//+-------------------------------------------------+
//| Draw the element border                         |
//+-------------------------------------------------+
void CToolTip::DrawFrame(void)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed() || this.Text()=="")
      return;
//--- Draw a rectangle along the object edges
   this.DrawRectangle(0,0,this.Width()-1,this.Height()-1,this.BorderColor(),this.Opacity());
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist im Code kommentiert. Der Unterschied zu der Methode der übergeordneten Klasse besteht hier nur in der Prüfung der Notwendigkeit, das Element anzuzeigen.


Die Methode, die das Element anzeigt:

//+-------------------------------------------------+
//| 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 prüft, ob das Objekt einen Schatten hat, und wenn ja, dann muss dieser auch angezeigt werden. Gleichzeitig sollte nach der Anzeige des Schattens das Objekt selbst in den Vordergrund gerückt werden, da sich das Objekt nach der Anzeige des Schattens unter diesem befindet.


Die Methode, die den ToolTip-Text festlegt:

//+-------------------------------------------------+
//| Set the Tooltip text                            |
//+-------------------------------------------------+
void CToolTip::SetTooltipText(const string text)
  {
   CGCnvElement::SetTooltipText(text);
   CWinFormBase::SetText(text);
   this.CorrectSizeByTexts();
  }
//+------------------------------------------------------------------+

Hier wird der Tooltip-Text zunächst auf eine neue Eigenschaft des Objekts gesetzt, und dann wird derselbe Text auch auf die Eigenschaft „Objekttext“ gesetzt, da diese Eigenschaften für das Tooltip-Objekt als identisch angesehen werden können. Anschließend wird die Methode zur Größenanpassung des Objekts entsprechend den eingestellten Titel- und Tooltip-Texten aufgerufen.


Die Methode, die den Titeltext für ToolTip festlegt:

//+-------------------------------------------------+
//| Set the title text for Tooltip                  |
//+-------------------------------------------------+
void CToolTip::SetTitle(const string header)
  {
   this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TITLE,header);
   this.CorrectSizeByTexts();
  }
//+------------------------------------------------------------------+

Hier setzen wir zunächst den an die Methode übergebenen Text in eine neue String-Eigenschaft des Objekts, und dann rufen wir die Methode zur Größenanpassung des Objekts auf.


Die Methode, die die Größe des Tooltips an die Größe der Texte anpasst:

//+-------------------------------------------------+
//| Adjust tooltip size to fit texts                |
//+-------------------------------------------------+
void CToolTip::CorrectSizeByTexts(void)
  {
//--- If the tooltip text is not set for the object, leave
   if(this.TooltipText()=="" || this.TooltipText()==NULL)
      return;
//--- Declare variables for the width and height of the object
   int w=this.Width();
   int h=this.Height();
//--- Declare variables for the width and height of the title and tooltip texts
   int w1=0;
   int h1=0;
   int w2=w;
   int h2=h;
//--- If the header text is set, get and adjust its size
   if(this.Title()!="" && this.Title()!=NULL)
     {
      this.TextSize(this.Title(),w1,h1);
      if(w1<6)
         w1=6;
      if(h1<19)
         h1=19;
     }
//--- If the tooltip text is set, get and adjust its size
   this.TextSize(this.Text(),w2,h2);
   if(w2<6)
      w2=6;
   if(h2<19)
      h2=19;
//--- Calculate the total size of the tooltip
   w=fmax(w1,w2);
   h=h1+h2;
//--- Set the size of the object in accordance with the calculated ones
   this.Resize(w+12+(this.Icon()>CANV_ELEMENT_TOOLTIP_ICON_NONE && this.Icon()<=CANV_ELEMENT_TOOLTIP_ICON_USER ? 16 : 0),h,false);
  }
//+------------------------------------------------------------------+

Die Logik der Methode wird in den Codekommentaren beschrieben. Kurz gesagt, wenn für ein Objekt kein QuickInfo-Text festgelegt ist, wird keine QuickInfo angezeigt, unabhängig davon, ob für das Objekt ein Titeltext festgelegt wurde oder nicht. Wenn die QuickInfo nicht angezeigt wird, muss ihre Größe nicht geändert werden. Deshalb verlassen wir in einer solchen Situation einfach die Methode. Wenn der Tooltip-Text festgelegt ist, müssen wir die Größen des Titels und des Tooltip-Textes ermitteln. Wenn die Breite des resultierenden Textes kleiner als 6 ist, dann ist die Breite des Objekts gleich 6, wenn die Höhe kleiner als 19 ist, dann sollte die Höhe des Objekts 19 sein. Als Nächstes legen wir die größte Breite der beiden Texte fest und verwenden sie als Breite des Objekts. Die Höhe des Objekts ist gleich der Summe der Höhen der beiden Texte. Bei der Größenänderung wird der Texteinzug vom linken Rand von sechs Pixeln berücksichtigt. Dementsprechend sollte der rechte Einzug ebenfalls sechs Pixel betragen. Daher addieren wir 12 Pixel zu der berechneten Breite und prüfen die Bedingung für das Vorhandensein des Symbols. Wenn das Symbol gezeichnet werden soll, dann addieren wir die Breite des Symbols zur Breite des Objekts um 16 Pixel. Als Ergebnis erhalten wir die richtige Größe des Objekts, bei der beide Texte — der Titel, die Beschreibung und das Symbol (falls vorhanden) — harmonisch aussehen werden.


Die Methode, die einen Hinweis zeichnet:

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

Den vertikalen Einzug des Textes setzen wir auf drei Pixel und den horizontalen Einzug auf sechs Pixel. Wenn ein Symbol gezeichnet werden soll, dann addieren wir dessen Breite von 16 Pixeln zum horizontalen Einzug. Wenn das Tooltip einen Titel haben soll, setzen wir die Schriftart auf fett, zeigen den Überschriftentext an, setzen die Schriftbreite auf normal zurück und addieren die Höhe des Titeltextes plus 4 Pixel zur vertikalen Koordinate. Wenn wir fertig sind, zeigen wir den Tooltip-Text an. Wenn es keinen Titel gibt, wird der Text des Tooltips an der anfänglichen Y-Koordinate angezeigt, und wenn es einen Titel gibt, wird er an der berechneten Y-Koordinate angezeigt.


Die Methode, die ein Symbol zeichnet:

//+-------------------------------------------------+
//| Draw an icon                                    |
//+-------------------------------------------------+
void CToolTip::DrawIcon(void)
  {
   switch(this.Icon())
     {
      case CANV_ELEMENT_TOOLTIP_ICON_INFO    :  this.DrawIconInfo();    break;
      case CANV_ELEMENT_TOOLTIP_ICON_WARNING :  this.DrawIconWarning(); break;
      case CANV_ELEMENT_TOOLTIP_ICON_ERROR   :  this.DrawIconError();   break;
      case CANV_ELEMENT_TOOLTIP_ICON_USER    :  this.DrawIconUser();    break;
      //--- Icon None
      default: break;
     }
  }
//+------------------------------------------------------------------+

Je nach dem für das Objekt eingestellten Symboltyp rufen wir die Methode zum Zeichnen des entsprechenden Bildes auf.

Im Moment ist dies alles, was für das Funktionieren dieser Klasse erforderlich ist. Außerdem werden wir die Klasse modifizieren, um die gewünschten Arbeitsergebnisse zu erzielen.

Verfeinern wir noch den Rest der Bibliotheksklassen, damit sie mit dem neuen Objekt arbeiten können.


In der Formularobjektklasse \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, und zwar im öffentlichen Abschnitt, deklarieren wir die virtuellen Methoden zum Setzen des Flags für die Anzeige eines nicht ausgeblendeten Steuerelements und zum Einstellen der Deckkraft des Elements:

//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);
//--- Set the priority of a graphical object for receiving the event of clicking on a chart
   virtual bool      SetZorder(const long value,const bool only_prop);
//--- Set the object above all
   virtual void      BringToTop(void);
//--- Set the flag for displaying a non-hidden control
   virtual void      SetDisplayed(const bool flag);
//--- Set the element opacity
   virtual void      SetOpacity(const uchar value,const bool redraw=false);

//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);


Dort deklarieren wir im öffentlichen Abschnitt die Methoden für die Arbeit mit dem angehängten Tooltip-Objekt und für das Setzen seines Textes:

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

//--- (1) Attach, return (2) the attached ToolTip object, (3) by description
   bool              AddTooltip(CForm *tooltip);
   CForm            *GetTooltip(void);
   CForm            *GetTooltipByDescription(const string descript);
//--- Set the text for Tooltip
   virtual void      SetTooltipText(const string text);

//--- Draw an object shadow
   void              DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR);


Reparieren wir noch die Methode, die ein neues grafisches Objekt erstellt:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CForm::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;
   //--- Depending on the created object type,
   switch(type)
     {
      //--- create a graphical element object
      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;
      //--- create a form object
      case GRAPH_ELEMENT_TYPE_FORM     :
         element=new CForm(type,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));
   else
     {
      element.SetMovable(movable);
      element.SetCoordXRelative(element.CoordX()-this.CoordX());
      element.SetCoordYRelative(element.CoordY()-this.CoordY());
     }
   return element;
  }
//+------------------------------------------------------------------+

Wenn das Objekt nicht erstellt wird, wird ein Protokolleintrag angezeigt. Dann gab es einen Aufruf an den Zeiger eines nicht erstellten Objekts, was zu einem kritischen Fehler führen konnte. Aus diesem Grund wird der Block für die Einstellung der Parameter des erstellten Objekts in else {} eingeschlossen.

Jedes WinForms-Objekt sollte in der Lage sein, an sich selbst den Zeiger auf ein zuvor erstelltes Tooltip-Objekt anzuhängen. Damit ein Objekt „versteht“, dass ein solcher Tooltip bereits an ihm angebracht wurde, sollte es zwischen ihnen durch eine im Tooltip-Objekt festgelegte Beschreibung unterscheiden. Auf diese Weise können wir mehrere Tooltip-Objekte im Container-Objekt erstellen und für jedes von ihnen eine eindeutige Beschreibung festlegen. Dann können wir den Zeiger auf ein beliebiges anderes Objekt erhalten, das mit dem Objekt verbunden ist, in dem die QuickInfos erstellt wurden, und diesem Objekt die gewünschte QuickInfo anhand ihrer Beschreibung zuweisen. Zu diesem Zweck sollte jedes WinForms-Objekt über Methoden verfügen, die es uns ermöglichen, ein Objekt vom Typ CForm und seine Nachkommen von außen anzuhängen. Ich habe solche Methoden bereits in der Formularobjektklasse deklariert. Schauen wir uns ihre Umsetzung an.


Die Methode, die ein angehängtes ToolTip-Objekt erstellt:

//+-------------------------------------------------+
//| Create the attached ToolTip 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 || tooltip.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT);
      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 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()+1,this.CoordY()+1))
     {
      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;
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben. Der Zeiger auf das Tooltip-Objekt wird an die Methode übergeben. Wenn ein Objekt dieses Typs mit genau der gleichen Beschreibung bereits an das Objekt angehängt ist, dann gehen wir davon aus, dass es sich um die gleiche QuickInfo handelt. Einen Fehler melden und false zurückgeben. Als Nächstes fügen wir den Zeiger auf das Tooltip-Objekt, das an die Methode übergeben wurde, der Liste der angehängten Objekte hinzu. In diesem Fall kann das Objekt selbst nichts über den Typ CToolTip wissen, aber da dieser Typ ein Erbe der Klasse CForm ist, sollte er an die Methode mit dem Typ CForm übergeben werden. In der Kollektionsklasse, in der die Popup-Objekte verarbeitet werden, können wir dann dieses Objekt aus der Liste holen und mit ihm wie mit dem CToolTip-Objekt arbeiten. Am Ende der Methode wird der Zeiger auf das aktuelle Objekt in das Tooltip-Objekt geschrieben, sodass es die Basis für das Tooltip-Objekt wird. Da Tooltip-Objekte in anderen Objekten erstellt und dann an dritte Objekte angehängt werden können, hilft uns der Eintrag über das Basisobjekt zu bestimmen, welchem Objekt es als Tooltip zugewiesen wird. Das Basisobjekt ist das Objekt, dem die QuickInfo zugeordnet ist.


Die Methode, die das angehängte ToolTip-Objekt zurückgibt:

//+-------------------------------------------------+
//| Return the attached ToolTip object              |
//+-------------------------------------------------+
CForm *CForm::GetTooltip(void)
  {
   CForm *obj=NULL;
   for(int i=this.ElementsTotal()-1;i>WRONG_VALUE;i--)
     {
      obj=this.GetElement(i);
      if(obj!=NULL && obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
         break;
     }
   return obj;
  }
//+------------------------------------------------------------------+

Hier in der Schleife über die angehängten Objekte, erhalten wir das nächste Objekt durch den Schleifenindex. Wenn dieses Objekt nicht leer ist und den Typ CToolTip hat, verlassen wir die Schleife. Am Ende der Schleife geben wir den in der Variablen obj gespeicherten Zeiger zurück.

Einem Objekt können mehrere Tooltips mit unterschiedlichen Beschreibungen zugewiesen werden. Die Methode gibt das angehängte ToolTip-Objekt per Beschreibung zurück, um den Zeiger auf das gewünschte Tooltip-Objekt zu erhalten:

//+------------------------------------------------------------------+
//| Return the attached ToolTip object by description                |
//+------------------------------------------------------------------+
CForm *CForm::GetTooltipByDescription(const string descript)
  {
   CForm *obj=NULL;
   for(int i=this.ElementsTotal()-1;i>WRONG_VALUE;i--)
     {
      obj=this.GetElement(i);
      if(obj!=NULL && obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP && obj.Description()==descript)
         break;
     }
   return obj;
  }
//+------------------------------------------------------------------+

Die Methode erhält den Beschreibungstext, anhand dessen das Tooltip-Objekt gefunden werden muss. In der Schleife durch die Liste der angehängten Objekte erhalten wir das nächste Objekt. Wenn sie nicht leer ist, ihr Typ CToolTip ist und die Beschreibung mit der gewünschten übereinstimmt, wird die Schleife unterbrochen. Am Ende der Schleife wird der Zeiger auf das gefundene (oder leere) Objekt zurückgegeben.

Die Methode, die den ToolTip-Text festlegt:

//+-------------------------------------------------+
//| Set the Tooltip text                            |
//+-------------------------------------------------+
void CForm::SetTooltipText(const string text)
  {
   CGCnvElement::SetTooltipText(text);
   CForm *tooltip=this.GetTooltip();
   if(tooltip!=NULL)
      tooltip.SetTooltipText(text);
  }
//+------------------------------------------------------------------+

Zunächst legen wir den Text für die QuickInfo auf dieses Objekt fest. Dann erhalten wir den Zeiger auf das angehängte Tooltip-Objekt und setzen den an die Methode übergebenen Tooltip-Text. Wenn das Objekt nur eine QuickInfo hat, setzt die Methode denselben Text für dieses Objekt und die ihm zugewiesene QuickInfo.

Die virtuelle Methode, die das Flag für die Anzeige eines nicht ausgeblendeten Steuerelements setzt:

//+------------------------------------------------------------------+
//| Set the flag for displaying a non-hidden control                 |
//+------------------------------------------------------------------+
void CForm::SetDisplayed(const bool flag)
  {
//--- Set the display flag for the object
   CGCnvElement::SetDisplayed(flag);
//--- If the shadow use flag is set and the shadow object has been created,
//--- set the display flag for the shadow object
   if(this.m_shadow && this.m_shadow_obj!=NULL)
      this.m_shadow_obj.SetDisplayed(flag);
  }
//+------------------------------------------------------------------+

Im Falle eines Objekts, das einen Schatten auf andere Objekte werfen kann, ist es notwendig, das Anzeige-Flag nicht nur für das Objekt selbst, sondern auch für das Schattenobjekt zu setzen. Andernfalls wird der Schatten unabhängig vom Objekt „sein eigenes Leben führen“. Deshalb setzen wir hier zunächst das Anzeige-Flag für das Objekt selbst. Danach wird das Flag für seinen Schatten gesetzt, falls er einen hat.


Die virtuelle Methode, die die Deckkraft des Elements festlegt:

//+------------------------------------------------------------------+
//| Set the element opacity                                          |
//+------------------------------------------------------------------+
void CForm::SetOpacity(const uchar value,const bool redraw=false)
  {
//--- Set the object opacity value
   CGCnvElement::SetOpacity(value,redraw);
//--- If the shadow use flag is set and the shadow object has been created,
//--- set the opacity value for the shadow object
   if(this.m_shadow && this.m_shadow_obj!=NULL)
      this.m_shadow_obj.SetOpacity(value,redraw);
  }
//+------------------------------------------------------------------+

Hier ist alles gleich geblieben. Zunächst legen wir den Deckkraftwert für das Objekt selbst fest. Wenn das Schattenobjekt vorhanden ist, wird der Deckkraftwert auch für dieses Objekt festgelegt.


Um mit dem neuen Steuerelement zu arbeiten, müssen wir seine Datei mit der Klassendatei des Formularobjekts verbinden.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh legen wir die Einbeziehung der CToolTip-Klassendatei fest:

//+------------------------------------------------------------------+
//| 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"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+


In der Methode zum Erstellen eines neuen grafischen Objekts fügen wir die Zeichenfolge zum Erstellen eines neuen Tooltip-Objekts 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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


In \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh, und zwar in der Methode, die die Parameter für das angehängte Objekt erstellt, fügen wir den Codeblock für die Einstellung der Parameter eines neu erstellten Tooltip-Objekts 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

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

      //--- 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;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+


Wir fügen in der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh des GroupBox-Objekts, und zwar in der Methode zur Erstellung eines neuen grafischen Objekts, auch die Zeichenfolge zur Erstellung eines Tooltip-Objekts hinzu:

//+-------------------------------------------------+
//| Create a new graphical object                   |
//+-------------------------------------------------+
CGCnvElement *CGroupBox::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_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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh der Objektklasse TabControl müssen wir die Methoden für den Empfang von Schaltflächenobjekten mit Pfeilen hinzufügen, die zum Scrollen der Titelleiste verwendet werden. Ohne Zeiger auf diese Hilfsobjekte können wir ihnen keine Tooltips zuweisen.

Im privaten Abschnitt der Klasse deklarieren wir die Methoden für den Empfang von Zeigern auf Schaltflächenobjekte:

//--- Return the list of (1) headers, (2) tab fields, the pointer to the (3) up-down and (4) left-right button objects
   CArrayObj        *GetListHeaders(void)          { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);        }
   CArrayObj        *GetListFields(void)           { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);         }
   CArrowUpDownBox  *GetArrUpDownBox(void)         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0); }
   CArrowLeftRightBox *GetArrLeftRightBox(void)    { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,0); }

//--- Return the pointer to the (1) up, (2) down, (3) left and (4) right arrow button
   CArrowUpButton   *GetArrowUpButton(void);
   CArrowDownButton *GetArrowDownButton(void);
   CArrowLeftButton *GetArrowLeftButton(void);
   CArrowRightButton*GetArrowRightButton(void);
   
//--- Return the pointer to the (1) last and (2) first visible tab header
   CTabHeader       *GetLastHeader(void)           { return this.GetTabHeader(this.TabPages()-1);                                }
   CTabHeader       *GetFirstVisibleHeader(void);
//--- Set the tab as selected


Im öffentlichen Abschnitt deklarieren wir die Methoden zum Setzen von Tooltips für Schaltflächenobjekte mit Pfeilen:

//--- Set the tab specified by index to selected/not selected
   void              Select(const int index,const bool flag);
//--- Returns the (1) index, (2) the pointer to the selected tab
   int               SelectedTabPageNum(void)      const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);}
   CWinFormBase     *SelectedTabPage(void)               { return this.GetTabField(this.SelectedTabPageNum());             }
   
//--- Add Tooltip to the (1) up, (2) down, (3) left and (4) right arrow button
   bool              AddTooltipToArrowUpButton(CForm *tooltip);
   bool              AddTooltipToArrowDownButton(CForm *tooltip);
   bool              AddTooltipToArrowLeftButton(CForm *tooltip);
   bool              AddTooltipToArrowRightButton(CForm *tooltip);
   
//--- Set the object above all
   virtual void      BringToTop(void);
//--- Show the control
   virtual void      Show(void);
//--- Event handler


In der Methode, die ein neues grafisches Objekt erstellt, fügen wir eine Zeichenfolge zur Erstellung eines Tooltip-Objekts hinzu:

//+-------------------------------------------------+
//| Create a new graphical object                   |
//+-------------------------------------------------+
CGCnvElement *CTabControl::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_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_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_TOOLTIP              : element=new CToolTip(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 Methoden, die die Zeiger auf die Pfeiltasten zurückgeben:

//+-------------------------------------------------+
//| Return the pointer to the up arrow button       |
//+-------------------------------------------------+
CArrowUpButton *CTabControl::GetArrowUpButton(void)
  {
   CArrowUpDownBox *box=this.GetArrUpDownBox();
   return(box!=NULL ? box.GetArrowUpButton() : NULL);
  }
//+-------------------------------------------------+
//| Return the pointer to the down arrow button     |
//+-------------------------------------------------+
CArrowDownButton *CTabControl::GetArrowDownButton(void)
  {
   CArrowUpDownBox *box=this.GetArrUpDownBox();
   return(box!=NULL ? box.GetArrowDownButton() : NULL);
  }
//+-------------------------------------------------+
//| Return the pointer to the left arrow button     |
//+-------------------------------------------------+
CArrowLeftButton *CTabControl::GetArrowLeftButton(void)
  {
   CArrowLeftRightBox *box=this.GetArrLeftRightBox();
   return(box!=NULL ? box.GetArrowLeftButton() : NULL);
  }
//+-------------------------------------------------+
//| Return the pointer to the right arrow button    |
//+-------------------------------------------------+
CArrowRightButton *CTabControl::GetArrowRightButton(void)
  {
   CArrowLeftRightBox *box=this.GetArrLeftRightBox();
   return(box!=NULL ? box.GetArrowRightButton() : NULL);
  }
//+------------------------------------------------------------------+

Alle Methoden sind miteinander identisch. Zunächst erhalten wir den Zeiger auf das entsprechende Objekt, das ein Paar von Links-Rechts- oder Auf-Ab-Tasten enthält. Von diesen Objekten erhalten wir den Zeiger auf die gewünschte Taste — oben, unten, links oder rechts — und geben ihn zurück.


Die Methoden, die das ToolTip-Objekt zu den Pfeiltasten nach oben, unten, links und rechts hinzufügen:

//+-------------------------------------------------+
//| Add ToolTip to the up arrow button              |
//+-------------------------------------------------+
bool CTabControl::AddTooltipToArrowUpButton(CForm *tooltip)
  {
   CArrowUpButton *butt=this.GetArrowUpButton();
   return(butt!=NULL ? butt.AddTooltip(tooltip) : false);
  }
//+-------------------------------------------------+
//| Add ToolTip to the down arrow button            |
//+-------------------------------------------------+
bool CTabControl::AddTooltipToArrowDownButton(CForm *tooltip)
  {
   CArrowDownButton *butt=this.GetArrowDownButton();
   return(butt!=NULL ? butt.AddTooltip(tooltip) : false);
  }
//+-------------------------------------------------+
//| Add ToolTip to the left arrow button            |
//+-------------------------------------------------+
bool CTabControl::AddTooltipToArrowLeftButton(CForm *tooltip)
  {
   CArrowLeftButton *butt=this.GetArrowLeftButton();
   return(butt!=NULL ? butt.AddTooltip(tooltip) : false);
  }
//+-------------------------------------------------+
//| Add ToolTip to the right arrow button           |
//+-------------------------------------------------+
bool CTabControl::AddTooltipToArrowRightButton(CForm *tooltip)
  {
   CArrowRightButton *butt=this.GetArrowRightButton();
   return(butt!=NULL ? butt.AddTooltip(tooltip) : false);
  }
//+------------------------------------------------------------------+

Die Methoden sind miteinander identisch. Zunächst holen wir den Zeiger auf die entsprechende Schaltfläche mit Hilfe der oben genannten Methoden, und setzen dann den Zeiger auf das Tooltip-Objekt, das an die Methode übergeben wurde, auf das erhaltene Objekt. Bei erfolgreichem Empfang des Zeigers auf das Button-Objekt und Hinzufügen des Zeigers zum Tooltip-Objekt gibt die Methode true zurück — andernfalls false.


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabField.mqh der Objektklasse TabField fügen wir die Zeichenfolge für die Erstellung eines neuen Tooltip-Objekts in die Methode zur Erstellung eines neuen grafischen Objekts hinzu:

//+-------------------------------------------------+
//| Create a new graphical object                   |
//+-------------------------------------------------+
CGCnvElement *CTabField::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_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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh der Objektklasse SplitContainer deklarieren wir die Methode zum Setzen einer QuickInfo für das Separator-Objekt:

//--- Return the pointer to the separator
   CSplitter        *GetSplitter(void)                         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SPLITTER,0);                      }
//--- Return a pointer to the (1) "Left shift", (1) "Right shift", (1) "Up shift" and (1) "Down shift" hint objects
   CHintMoveLeft    *GetHintMoveLeft(void)                     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);                }
   CHintMoveRight   *GetHintMoveRight(void)                    { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);               }
   CHintMoveUp      *GetHintMoveUp(void)                       { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);                  }
   CHintMoveDown    *GetHintMoveDown(void)                     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);                }
//--- Add Tooltip to the separator
   bool              AddTooltipToSplitter(CForm *tooltip);

//--- (1) set and (2) return the minimum possible size of the panel 1 and 2


In der Methode zum Erstellen eines neuen grafischen Objekts fügen wir die Zeichenfolge zum Erstellen eines neuen Tooltip-Objekts hinzu:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainer::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_SPLIT_CONTAINER_PANEL: element=new CSplitContainerPanel(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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


Die Methode, die ein ToolTip zur Trennlinie hinzufügt:

//+-------------------------------------------------+
//| Add ToolTip to the separator                    |
//+-------------------------------------------------+
bool CSplitContainer::AddTooltipToSplitter(CForm *tooltip)
  {
   CSplitter *obj=this.GetSplitter();
   return(obj!=NULL ? obj.AddTooltip(tooltip) : false);
  }
//+------------------------------------------------------------------+

Hier erhalten wir den Zeiger auf das Trennlinien-Objekt und geben das Ergebnis des Hinzufügens des Zeigers auf das Tooltip-Objekt zum Trennlinien-Objekt zurück.


Fügen wir in der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh des Klassenobjekts SplitContainerPanel, und zwar in der Methode zum Erstellen eines neuen grafischen Objekts, die Zeichenfolge zum Erstellen eines neuen Tooltip-Objekts hinzu:

//+-------------------------------------------------+
//| Create a new graphical object                   |
//+-------------------------------------------------+
CGCnvElement *CSplitContainerPanel::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_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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Jetzt können wir in jedem Containerobjekt ein neues Tooltip-Objekt erstellen und es einem der vorhandenen grafischen Elemente zuweisen.

Um Tooltip-Objekte zu behandeln, müssen wir Änderungen an \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh der Kollektionsklasse der grafischen Elemente vornehmen.

Im privaten Abschnitt der Klasse deklarieren wir die Methode zur Behandlung von Tooltip-Objekten:

//--- 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);
//--- Handle Tooltip elements
   void              TooltipProcessing(CForm *form,CForm *tooltip,CForm *tooltip_prev);
//--- Add the element to the collection list
   bool AddCanvElmToCollection(CGCnvElement *element);


In der Methode, die nach Interaktionsobjekten sucht, fügen wir das Überspringen des Tooltip-Objekts hinzu:

//+-------------------------------------------------+
//| Search for interaction objects                  |
//+-------------------------------------------------+
CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If a non-empty pointer is passed
   if(form!=NULL)
     {
      //--- Create the list of interaction objects
      int total=form.CreateListInteractObj();
      //--- In the loop by the created list
      for(int i=total-1;i>WRONG_VALUE;i--)
        {
         //--- get the next form object
         CForm *obj=form.GetInteractForm(i);
         //--- If the object is received, but is not visible, or not active, or should not be displayed, skip it
         if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed())
            continue;
         
         //--- If this is Tooltip, skip such an object for now
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
           {
            continue;
           }
         
         //--- If the form object is TabControl, return the selected tab under the cursor
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            //---...
            //---...

Wenn wir nach einem grafischen Element unter dem Cursor suchen und auf ein Tooltip-Objekt stoßen, müssen wir es hier ignorieren, damit es nicht behandelt wird. Wenn aktive Tooltip-Objekte erstellt werden, die eine Interaktion mit der Maus ermöglichen, dann wird statt des Überspringens geprüft, ob ein solches Objekt für die Interaktion mit der Maus verfügbar ist. In der Zwischenzeit lassen wir solche Objekte hier aus.


Die Methode, die ToolTip-Elemente behandelt:

//+-------------------------------------------------+
//| Handle ToolTip elements                         |
//+-------------------------------------------------+
void CGraphElementsCollection::TooltipProcessing(CForm *form,CForm *tooltip,CForm *tooltip_prev)
  {
//--- 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 hidden
   if(!tooltip.IsVisible())
     {
      //--- If the tooltip object is shifted to the received cursor coordinates
      if(tooltip.Move(x,y))
        {
         //--- Get the object the tooltip object is assigned to
         CForm *base=tooltip.GetBase();
         //--- If the object is received, set new relative tooltip coordinates
         if(base!=NULL)
           {
            tooltip.SetCoordXRelative(tooltip.CoordX()-base.CoordX());
            tooltip.SetCoordYRelative(tooltip.CoordY()-base.CoordY());
           }
        }
     }
//--- Set the tooltip drawing flag, as well as full opacity, and display the object
   tooltip.SetDisplayed(true);
   tooltip.SetOpacity(255);
   tooltip.Show();
//--- If the previous tooltip object exists and its name is not equal to the name of the current object (it is the same object)
   if(tooltip_prev!=NULL && tooltip_prev.Name()!=tooltip.Name())
     {
      //--- Set the flag disabling the display, make the object completely transparent and hide it
      tooltip_prev.SetDisplayed(false);
      tooltip.SetOpacity(0);
      tooltip_prev.Hide();
     }
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben. Wenn wir das Objekt unter dem Cursor erhalten, prüfen wir, ob ein Tooltip-Objekt für dieses Objekt festgelegt ist. Wenn dies der Fall ist, rufen wir diese Methode auf, um das Tooltip-Objekt zu behandeln. Wir übergeben das Objekt selbst an die Methode, wenn ein Tooltip gefunden wurde, den Zeiger auf das Objekt Tooltip sowie den Zeiger auf das zuvor behandelte Tooltip-Objekt.


Fügen wir den folgenden Codeblock in den in die Ereignisbehandlung von OnChartEvent() der Klasse ein, und zwar in das Segment, das die Situation des Entfernens der Objektbewegungsfahne behandelt (der Cursor befindet sich über dem Objekt, aber keine der Schaltflächen wurde bisher gedrückt):

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

            //--- 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)
                 {
                  //--- and if its type is indeed ToolTip (to avoid incorrect casting, which we sometimes get if we do not check the type)
                  if(tooltip.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                    {
                     //--- Get the base object set in ToolTip
                     CForm *base=tooltip.GetBase();
                     //--- If the base object is received
                     if(base!=NULL)
                       {
                        //--- 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.Name()==form.Name())
                          {
                           //--- Handle the received and previous ToolTip and write the current ToolTip to the variable as the previous one
                           this.TooltipProcessing(form,tooltip,tooltip_prev);
                           tooltip_prev=tooltip;
                          }
                       }
                    }
                 }
               //--- If there is no ToolTip attached to the object
               else
                 {
                  //--- If the previous ToolTip exists
                  if(tooltip_prev!=NULL)
                    {
                     //--- and if its type is indeed ToolTip (to avoid incorrect casting, which we sometimes get if we do not check the type)
                     if(tooltip_prev.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                       {
                        //--- Set the object non-display flag, make it completely transparent and hide it
                        tooltip_prev.SetDisplayed(false);
                        tooltip_prev.SetOpacity(0,false);
                        tooltip_prev.Hide();
                       }
                    }
                 }
               //--- 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);
              }
           }
        }
      
      //--- If the cursor is not above the form
      if(form==NULL)
        {


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

Die Logik dieses Codeblocks ist in den Kommentaren beschrieben. Interessanterweise werden nach der Aktualisierung des Terminals die logischen Bedingungen, d. h. die gleichzeitige Prüfung auf Nicht-NULL und einige Objekteigenschaften, nicht immer korrekt gehandhabt. Daher werden hier die Bedingungen, die in eine Zeichenkette geschrieben werden können, mit Klammern { } in Blöcke unterteilt. Ich kann mich irren, aber auf jeden Fall wird dieser Codeblock bei der Weiterentwicklung der Tooltip-Objektklasse überarbeitet werden. In diesem Fall erlaubt uns der Code nur, das Objekt zu finden, für das die QuickInfo eingestellt ist, und diese QuickInfo anzuzeigen, während die vorherige ausgeblendet wird. Mit anderen Worten, es kommt weder zu Verzögerungen bei der Anzeige der QuickInfo noch zu einem reibungslosen Erscheinungsbild der QuickInfo. All dies werde ich in späteren Artikeln umsetzen.

Das ist alles, was im Moment zu tun war. Testen wir die Ergebnisse.


Test

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

Fügen wir zwei neue Variablen zu den EA-Einstellungen hinzu, um den Icon-Typ, der in einem Tooltip verwendet wird, und den Tooltip-Titeltext festzulegen:

//--- input parameters
sinput   bool                          InpMovable           =  true;                   // Panel Movable flag
sinput   ENUM_INPUT_YES_NO             InpAutoSize          =  INPUT_YES;              // Panel Autosize
sinput   ENUM_AUTO_SIZE_MODE           InpAutoSizeMode      =  AUTO_SIZE_MODE_GROW;    // Panel Autosize mode
sinput   ENUM_BORDER_STYLE             InpFrameStyle        =  BORDER_STYLE_SIMPLE;    // Label border style
sinput   ENUM_ANCHOR_POINT             InpTextAlign         =  ANCHOR_CENTER;          // Label text align
sinput   ENUM_INPUT_YES_NO             InpTextAutoSize      =  INPUT_NO;               // Label autosize
sinput   ENUM_ANCHOR_POINT             InpCheckAlign        =  ANCHOR_LEFT;            // Check flag align
sinput   ENUM_ANCHOR_POINT             InpCheckTextAlign    =  ANCHOR_LEFT;            // Check label text align
sinput   ENUM_CHEK_STATE               InpCheckState        =  CHEK_STATE_UNCHECKED;   // Check flag state
sinput   ENUM_INPUT_YES_NO             InpCheckAutoSize     =  INPUT_YES;              // CheckBox autosize
sinput   ENUM_BORDER_STYLE             InpCheckFrameStyle   =  BORDER_STYLE_NONE;      // CheckBox border style
sinput   ENUM_ANCHOR_POINT             InpButtonTextAlign   =  ANCHOR_CENTER;          // Button text align
sinput   ENUM_INPUT_YES_NO             InpButtonAutoSize    =  INPUT_YES;              // Button autosize
sinput   ENUM_AUTO_SIZE_MODE           InpButtonAutoSizeMode=  AUTO_SIZE_MODE_GROW;    // Button Autosize mode
sinput   ENUM_BORDER_STYLE             InpButtonFrameStyle  =  BORDER_STYLE_NONE;      // Button border style
sinput   bool                          InpButtonToggle      =  true ;                  // Button toggle flag
sinput   bool                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
sinput   bool                          InpListBoxMColumn    =  true;                   // ListBox MultiColumn flag
sinput   bool                          InpTabCtrlMultiline  =  false;                   // Tab Control Multiline flag
sinput   ENUM_ELEMENT_ALIGNMENT        InpHeaderAlignment   =  ELEMENT_ALIGNMENT_TOP;  // TabHeader Alignment
sinput   ENUM_ELEMENT_TAB_SIZE_MODE    InpTabPageSizeMode   =  ELEMENT_TAB_SIZE_MODE_FILL; // TabHeader Size Mode
sinput   int                           InpTabControlX       =  10;                     // TabControl X coord
sinput   int                           InpTabControlY       =  20;                     // TabControl Y coord
sinput   ENUM_CANV_ELEMENT_TOOLTIP_ICON InpTooltipIcon      =  CANV_ELEMENT_TOOLTIP_ICON_NONE;  // Tooltip Icon
sinput   string                        InpTooltipTitle      =  "";                     // Tooltip Title
//--- global variables


Nachdem TabControl erstellt wurde, implementieren wir zwei Tooltip-Objekte und weisen sie den beiden Schaltflächen zum Scrollen der Titelleiste nach links und rechts zu:

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

         //--- Create TabControl
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
         CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
         if(tc!=NULL)
           {
            tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
            tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
            tc.SetMultiline(InpTabCtrlMultiline);
            tc.SetHeaderPadding(6,0);
            tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage"));
            
            //--- Create Tooltip for the Left button
            tc.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,0,0,10,10,clrNONE,0,false,false);
            CToolTip *tooltip=tc.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,0);
            if(tooltip!=NULL)
              {
               tooltip.SetDescription("Left Button Tooltip");
               tooltip.SetIcon(InpTooltipIcon);
               tooltip.SetTitle(InpTooltipTitle);
               tooltip.SetTooltipText(TextByLanguage("Нажмите для прокрутки заголовков вправо","Click to scroll headings to the right"));
               tc.AddTooltipToArrowLeftButton(tooltip);
              }
            //--- Create Tooltip for the Right button
            tc.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,0,0,10,10,clrNONE,0,false,false);
            tooltip=tc.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,1);
            if(tooltip!=NULL)
              {
               tooltip.SetDescription("Right Button Tooltip");
               tooltip.SetIcon(ENUM_CANV_ELEMENT_TOOLTIP_ICON(InpTooltipIcon+1));
               tooltip.SetTitle(InpTooltipTitle);
               tooltip.SetTooltipText(TextByLanguage("Нажмите для прокрутки заголовков влево","Click to scroll headings to the left"));
               tc.AddTooltipToArrowRightButton(tooltip);
              }
              
            //--- Create a text label with a tab description on each tab
            for(int j=0;j<tc.TabPages();j++)
              {
                //---...
                //---...

Wie man sieht, weisen wir dem ToolTip-Objekt nach der Erstellung sofort eine Beschreibung, ein angezeigtes Symbol, Titeltexte und Tooltips zu und fügen es der entsprechenden Schaltfläche hinzu. Für das zweite Tooltip-Objekt geben wir einen Wert als Symbol an, der um eins höher ist als in den Einstellungen angegeben, sodass zwei Symbole gleichzeitig angezeigt werden können.

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


Wenn Sie mit dem Mauszeiger über die Pfeilschaltflächen fahren, erscheinen sofort die Tooltips. Jede von ihnen hat ihr eigenes Symbol. Tooltips verschwinden nicht mehr vom Bildschirm — ihre Koordinaten werden korrekt angepasst. Jetzt sehen wir neue Pfeile, die als standardmäßige grafische Primitive auf den Pfeiltasten gezeichnet werden. Natürlich ist das Verhalten von Tooltip-Objekten nicht korrekt. Ihr korrektes Verhalten soll später implementiert werden. Der Test diente lediglich dazu, das Aussehen der Objekte zu beurteilen.


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