English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 15): TabControl WinForms Objekt — mehrere Reihen von Registerkartenüberschriften, Methoden zur Behandlung von Registerkarten

DoEasy. Steuerung (Teil 15): TabControl WinForms Objekt — mehrere Reihen von Registerkartenüberschriften, Methoden zur Behandlung von Registerkarten

MetaTrader 5Beispiele | 3 November 2022, 11:38
161 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Wenn ein Steuerelement mehr Registerkarten hat, als in die Objektbreite passen (wir gehen davon aus, dass sie sich oben befinden), dann können die Überschriften, die nicht in das Element passen, abgeschnitten werden und deswegen Bildlaufschaltflächen enthalten. Wenn für das Objekt das Kennzeichen für den Mehrzeilenmodus gesetzt ist, werden die Kopfzeilen alternativ in mehreren Stücken (je nachdem, wie viele in der Größe des Elements enthalten sind) und in mehreren Zeilen angeordnet. Es gibt drei Möglichkeiten, die Größe der in Reihen angeordneten Registerkarten festzulegen (SizeMode):

  • Normal — die Breite der Tabulatoren wird entsprechend der Breite des Kopfzeilentextes festgelegt, der in den Werten PaddingWidth und PaddingHeight der Kopfzeile angegebene Platz wird entlang der Kanten der Kopfzeile hinzugefügt;
  • Fixed — feste Größe, die in den Steuerungseinstellungen angegeben ist. Der Kopfzeilentext wird abgeschnitten, wenn er nicht in seine Größe passt;
  • FillToRight — Registerkarten, die in die Breite eines Steuerelements passen, werden auf die volle Breite gestreckt.

Wenn eine Registerkarte im aktiven mehrzeiligen Modus ausgewählt wird, wird ihre Kopfzeile, die nicht an das Registerfeld angrenzt, zusammen mit der gesamten Zeile, in der sie sich befindet, in die Nähe des Registerfelds verschoben, und die Kopfzeilen, die an das Feld angrenzten, nehmen den Platz der Zeile der ausgewählten Registerkarte ein.

Ich werde diesen Modus im aktuellen Artikel implementieren. Ich werde dies jedoch nur für die Registerkarten, die sich oben auf dem Steuerelement befinden, und für die Modi „Normal“ (normal) und „Fixed“ (fest) Registerkartengröße tun. Der Modus FillToRight und die Anordnung der Tabulatoren unten, links und rechts in allen drei Tabulatorgrößenmodi werden in späteren Artikeln implementiert (ebenso wie der Bildlaufmodus für Tabulatoren, die sich in derselben Zeile befinden, wenn der Multiline-Modus deaktiviert ist).

Um mit dem Tabulatorfeld zu interagieren, das zuvor als Container-Objekt der Klasse CContainer implementiert wurde, erstellen wir ein neues TabField-Objekt — den Nachfolger des Container-Objekts mit seinen eigenen Eigenschaften und Methoden für die vollwertige Arbeit mit dem Tabulatorfeld.


Verbesserung der Bibliotheksklassen

In den Bibliotheksdateien \MQL5\Include\DoEasy\Defines.mqh, und zwar in der Liste der grafischen Elementtypen, fügen wir den neuen Typ eines WinForms-Hilfsobjekts 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
   //--- '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
   //--- 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
  };
//+------------------------------------------------------------------+

Da dieses Objekt nicht als eigenständige Einheit funktioniert, ist es ein Hilfsobjekt, das als Teil des TabControls zusammen mit dem gleichen Hilfsobjekt TabHeader, das ich im vorherigen Artikel erstellt habe arbeitet.

Hinzufügen einer neuen Enumeration von Modi zur Einstellung der Tabulatorgröße:

//+------------------------------------------------------------------+
//| Location of an object inside a control                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_ALIGNMENT
  {
   CANV_ELEMENT_ALIGNMENT_TOP,                        // Top
   CANV_ELEMENT_ALIGNMENT_BOTTOM,                     // Bottom
   CANV_ELEMENT_ALIGNMENT_LEFT,                       // Left
   CANV_ELEMENT_ALIGNMENT_RIGHT,                      // Right
  };
//+------------------------------------------------------------------+
//| Tab size setting mode                                            |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_TAB_SIZE_MODE
  {
   CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,                 // By tab header width
   CANV_ELEMENT_TAB_SIZE_MODE_FIXED,                  // Fixed size
   CANV_ELEMENT_TAB_SIZE_MODE_FILL,                   // By TabControl width
  };
//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+


Ganz am Ende der Liste der ganzzahligen Eigenschaften eines grafischen Elements auf der Leinwand fügen wir zwei neue Eigenschaften hinzu und erhöhen wir die Anzahl der ganzzahligen Eigenschaften von 88 auf 90:

//+------------------------------------------------------------------+
//| 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_TAB_MULTILINE,                   // Several lines of tabs in TabControl
   CANV_ELEMENT_PROP_TAB_ALIGNMENT,                   // Location of tabs inside the control
   CANV_ELEMENT_PROP_TAB_SIZE_MODE,                   // Tab size setting mode
   CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,                 // Tab index number
   CANV_ELEMENT_PROP_ALIGNMENT,                       // Location of an object inside the control
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (90)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Wir fügen zwei neue Eigenschaften zur Liste der möglicher Sortierkriterien 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_TAB_MULTILINE,                // Sort by the flag of several rows of tabs in TabControl
   SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT,                // Sort by the location of tabs inside the control
   SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE,                // Sort by the mode of setting the tab size
   SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER,              // Sort by the tab index number
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Sort by the location of the object inside the control
//--- 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
  };
//+------------------------------------------------------------------+

Jetzt können wir Objekte nach diesen neuen Eigenschaften sortieren, auswählen und suchen.

Ich werde weiterhin mehr und mehr neue Objekte hinzufügen. Sie sind jedoch nicht für jedes grafische Objekt gedacht. Dennoch sind alle neu hinzugefügten Eigenschaften bisher in allen Objekten verfügbar. Weiterhin werden wir ihre Verfügbarkeit für Objekte einschränken, in denen solche Eigenschaften nicht vorhanden sein sollen, indem wir einfach Methoden zu den Klassen dieser Methodenobjekte hinzufügen, die das Kennzeichen für die Beibehaltung der einen oder anderen Eigenschaft zurückgeben. Ich habe diese virtuellen Methoden schon vor langer Zeit in den Basisklassen der Bibliothek für jedes Objekt implementiert. Hier fügen wir sie nicht für jedes neue Objekt hinzu, und zwar aus einem einfachen Grund: Wenn wir davon ausgehen, dass alle Objekte erstellt worden sind und es sich lohnt, bei der Verfügbarkeit von Eigenschaften Ordnung zu schaffen, dann werde ich alles auf einmal machen. Ich werde alle Eigenschaften für jedes der Steuerobjekte auf dem Panel ihrer Eigenschaften sichtbar machen, das im Chart erstellt wurde.


Ich habe neue Eigenschaften und Enumerationen hinzugefügt. Fügen wir nun Texte hinzu, um ihre Beschreibungen anzuzeigen.

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

   MSG_LIB_TEXT_TOP,                                  // Top
   MSG_LIB_TEXT_BOTTOM,                               // Bottom
   MSG_LIB_TEXT_LEFT,                                 // Left
   MSG_LIB_TEXT_RIGHT,                                // Right
   
   MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL,                 // By tab header width
   MSG_LIB_TEXT_TAB_SIZE_MODE_FILL,                   // By TabControl width
   MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED,                  // Fixed size
   
   MSG_LIB_TEXT_CORNER_LEFT_UPPER,                    // Center of coordinates at the upper left corner of the chart
   MSG_LIB_TEXT_CORNER_LEFT_LOWER,                    // Center of coordinates at the lower left corner of the chart
   MSG_LIB_TEXT_CORNER_RIGHT_LOWER,                   // Center of coordinates at the lower right corner of the chart
   MSG_LIB_TEXT_CORNER_RIGHT_UPPER,                   // Center of coordinates at the upper right corner of the chart

...

   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,         // ButtonListBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,              // Tab header
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,               // Tab field
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // TabControl
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program

...

//--- CButtonListBox
   MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON,         // Failed to set the group for the button with the index 
   MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON,        // Failed to set the Toggle flag to the button with the index 

//--- CTabControl
   MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ,               // Failed to get TabControl tab

//--- Integer properties of graphical elements

...

   MSG_CANV_ELEMENT_PROP_TAB_MULTILINE,               // Several lines of tabs in the control
   MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT,               // Location of tabs inside the control
   MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE,               // Tab size setting mode
   MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,             // Tab index number
   MSG_CANV_ELEMENT_PROP_ALIGNMENT,                   // Location of an object inside the control
//--- Real properties of graphical elements

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

   {"Сверху","Top"},
   {"Снизу","Bottom"},
   {"Слева","Left"},
   {"Справа","Right"},
   
   {"По ширине текста заголовка вкладки","Fit to tab header text width"},
   {"По ширине элемента управления TabControl","Fit TabControl Width"},
   {"Фиксированный размер","Fixed size"},
   
   {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"},
   {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"},
   {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"},
   {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},

...

   {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""},
   {"Заголовок вкладки","Tab header"},
   {"Поле вкладки","Tab field"},
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},

...

//--- CButtonListBox
   {"Не удалось установить группу кнопке с индексом ","Failed to set group for button with index "},
   {"Не удалось установить флаг \"Переключатель\" кнопке с индексом ","Failed to set the \"Toggle\" flag on the button with index "},
   
//--- CTabControl
   {"Не удалось получить вкладку элемента управления TabControl","Failed to get tab of TabControl"},
   
//--- Integer properties of graphical elements

...

   {"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"},
   {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"},
   {"Режим установки размера вкладок","Tab Size Mode"},
   {"Порядковый номер вкладки","Tab ordinal number"},
   {"Местоположение объекта внутри элемента управления","Location of the object inside the control"},
   
//--- String properties of graphical elements


Da wir einen neuen Modus zum Zeichnen von Registerkartenüberschriften haben, müssen wir die Beschreibung des ausgewählten Modus zurückgeben. Wir implementieren in der Datei \MQL5\Include\DoEasy\Services\DELib.mqh der Bibliotheksdienstfunktionen die Funktion, die die Beschreibung des Modus zur Einstellung der Registerkartengröße zurückgibt:

//+------------------------------------------------------------------+
//| Return the description of the tab size setting mode              |
//+------------------------------------------------------------------+
string TabSizeModeDescription(ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
  {
   switch(mode)
     {
      case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL);   break;
      case CANV_ELEMENT_TAB_SIZE_MODE_FIXED  :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED);    break;
      case CANV_ELEMENT_TAB_SIZE_MODE_FILL   :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FILL);     break;
      default                                :  return "Unknown"; break;
     }
  }
//+------------------------------------------------------------------+

Die Art der Einstellung der Größe der Tabulatoren wird an die Funktion übergeben, und je nachdem wird die entsprechende Textmeldung zurückgegeben.


In der Datei \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh der Basis-Grafikobjektklasse, und zwar in der Methode, die die Beschreibung des Grafikelementtyps zurückgibt, erstellen wir einen Abschnitt für Hilfsobjekte, verschieben die Beschreibung der Registerkartenüberschrift dorthin und fügen die Beschreibung eines neuen Typs hinzu — das Registerkartenfeld:

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


Ganz am Ende des letzten Artikels haben wir die Möglichkeit erörtert, eine Methode, die nach der Anzahl der Objekte eines bestimmten Typs sucht, leicht zu optimieren, um ihren Namen zu erzeugen. Wir haben festgestellt, dass in den beiden Methoden, die zusammenarbeiten, die Methode, die einen Objektnamen aus der String-Repräsentation der Enumerationskonstante, die den Objekttyp angibt, erzeugt, zweimal aufgerufen wird.

Um dies zu vermeiden, fügen wir in der Datei \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, in der die Methoden für die Erstellung von grafischen Elementnamen gespeichert sind, nämlich im Abschnitt für geschützte Klassen, eine weitere Methode hinzu — die überladene. Diese Methode dient dazu, den zuvor erstellten und aktuell bekannten Objektnamen zu erhalten, um ihn unter anderen grafischen Objekten im Chart zu suchen:

//--- Return the number of graphical elements (1) by type, (2) by name and type
   int               GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const;
   int               GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const;
//--- Create and return the graphical element name by its type
   string            CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type);
   
private:


Außerhalb des Klassenkörper schreiben wir ihre Implementierung:

//+------------------------------------------------------------------+
//| Return the number of graphical elements by type                  |
//+------------------------------------------------------------------+
int CGCnvElement::GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const
  {
//--- Declare a variable with the number of graphical objects and
//--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created
   int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow());
//--- Create the name of a graphical object by its type
   string name=TypeGraphElementAsString(type);
//--- In the loop by all chart and subwindow objects,
   for(int i=0;i<total;i++)
     {
      //--- get the name of the next object
      string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow());
      //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for
      if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE)
         continue;
      //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type,
      //--- then there is a graphical object of this type - increase the counter of objects of this type
      if(::StringFind(name_obj,name)>0)
         n++;
     }
//--- Return the number of found objects by their type
   return n;
  }
//+------------------------------------------------------------------+
//| Return the number of graphical elements by name and type         |
//+------------------------------------------------------------------+
int CGCnvElement::GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const
  {
//--- Declare a variable with the number of graphical objects and
//--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created
   int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow());
//--- In the loop by all chart and subwindow objects,
   for(int i=0;i<total;i++)
     {
      //--- get the name of the next object
      string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow());
      //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for
      if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE)
         continue;
      //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type,
      //--- then there is a graphical object of this type - increase the counter of objects of this type
      if(::StringFind(name_obj,name)>0)
         n++;
     }
//--- Return the number of found objects by their type
   return n;
  }
//+------------------------------------------------------------------+

Im Vergleich zur Methode „pair“, bei der ein Objektname aus seinem Typ gebildet und in einem grafischen Objektnamen gesucht wird, erhalten wir hier diesen Namen in den Methodeneingaben und suchen im Objektnamen nach einer Teilzeichenkette, die den an die Methode übergebenen Namen enthält.

Im Allgemeinen ist nichts zu kompliziert. Ich habe einfach einen Aufruf der Methode zur Namenserstellung entfernt. Wenn der Name noch nicht bekannt ist, rufe ich die erste Methode auf. Wenn sie bekannt ist, wird die zweite verwendet.

Zuvor führte die Methode, die den Namen eines grafischen Elements anhand seines Typs erstellt und zurückgibt, zu einem doppelten Aufruf der Methode zur Erstellung eines Objektnamens anhand seines Typs, das erste Mal innerhalb der Methode und das zweite Mal innerhalb der aufgerufenen Methode, in der ebenfalls die Methode zur Erstellung eines Objektnamens anhand seines Typs aufgerufen wurde:

//+------------------------------------------------------------------+
//| Create and return the graphical element name by its type         |
//+------------------------------------------------------------------+
string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return this.NamePrefix()+TypeGraphElementAsString(type)+(string)this.GetNumGraphElements(type);

  }
//+------------------------------------------------------------------+

Nehmen wir Änderungen an dieser Methode vor: Wir erstellen einen Objektnamen, verwenden ihn, um den Rückgabestring zu erstellen, und senden ihn an eine neue überladene Methode, die nach der Anzahl solcher Objekte sucht:

//+------------------------------------------------------------------+
//| Create and return the graphical element name by its type         |
//+------------------------------------------------------------------+
string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   string name=TypeGraphElementAsString(type);
   return this.NamePrefix()+name+(string)this.GetNumGraphElements(name,type);
  }
//+------------------------------------------------------------------+


Die Methode zur Erstellung des grafischen Objektelements wies einen ärgerlichen Fehler auf — wenn beim Erstellen eines grafischen Objekts ein Fehler auftrat, gab die Methode nie einen Fehler zurück (Code 0), aber das Objekt wurde nicht erstellt. Die Fehlerursachen konnten nur durch indirekte Methoden ermittelt werden. Dies gilt eher für die Entwicklung von Klassen grafischer Elemente als für ihre Verwendung durch einen Bibliotheksnutzer, da alle Fehler bei der Erstellung von Objekten bereits in der Phase der Klassenentwicklung beseitigt werden. Dennoch werden wir Änderungen vornehmen, die es uns ermöglichen, die Ursache des Fehlers besser zu verstehen:

//+------------------------------------------------------------------+
//| Create the graphical element object                              |
//+------------------------------------------------------------------+
bool CGCnvElement::Create(const long chart_id,     // Chart ID
                          const int wnd_num,       // Chart subwindow
                          const int x,             // X coordinate
                          const int y,             // Y coordinate
                          const int w,             // Width
                          const int h,             // Height
                          const bool redraw=false) // Flag indicating the need to redraw
                         
  {
   ::ResetLastError();
   if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,this.m_name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.Erase(CLR_CANV_NULL);
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   int err=::GetLastError();
   int code=(err==0 ? (w<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH : h<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT : ERR_OBJECT_ERROR) : err);
   string subj=(w<1 ? "Width="+(string)w+". " : h<1 ? "Height="+(string)h+". " : "");
   CMessage::ToLog(DFUN_ERR_LINE+subj,code,true);
   
   return false;
  }
//+------------------------------------------------------------------+

Wir weisen den Code des letzten Fehlers der Variablen zu (bei einem Fehler bei der Erstellung einer grafischen Ressource in der Klasse CCanvas der Standardbibliothek war er immer Null), prüfen dann den Fehlercode, und, wenn er gleich Null ist, dann prüfen wir die Breite und Höhe des erstellten Objekts. Wenn einer dieser Werte kleiner als eins ist, setzen wir den Code der entsprechenden Meldung oder einen allgemeinen Fehler bei der Erstellung eines grafischen Objekts in den Fehlercode. Ist der Fehlercode ungleich Null, weisen wir den Fehlercode der Variablen zu. Als Nächstes erstellen wir eine Zeichenkette mit einer zusätzlichen Beschreibung des Fehlercodes, die auch von der an die Methode übergebenen Breite und Höhe abhängt, und zeigen eine Meldung an, die den Methodennamen mit dem Index der Zeichenkette, der zusätzlichen Meldung und der Beschreibung des Fehlercodes enthält.


Alle Objekte der grafischen Elemente sind von der Klasse der Formularobjekte abgeleitet, die ihrerseits wiederum von anderen Klassen abgeleitet wurden. Aber sie verfügt über die Funktionalität für die Arbeit mit der Maus, sodass alle Objekte der grafischen Oberfläche des Programms irgendwie auf ihr basieren. Jedes Objekt, das an sein Basisobjekt angehängt ist, d.h. das Objekt, das aus dem Basisobjekt erzeugt wurde, erbt die Eigenschaften seines Erzeugers. Zu diesen Eigenschaften gehören auch Aktivität, Sichtbarkeit und Zugänglichkeit. Wenn das Objekt, aus dem ein anderes, ihm untergeordnetes Objekt erstellt wurde, nicht aktiv ist, d.h. nicht auf den Mauszeiger reagiert, dann sollte auch sein untergeordnetes Objekt dieses Verhalten erben (was später geändert werden kann). Wenn das Objekt nicht verfügbar ist (nicht aktiv und grau), dann sollte das untergeordnete Objekt auch so sein. Wenn das Objekt unsichtbar ist, dann sollte auch das untergeordnete Objekt ausgeblendet sein, was ja natürlich ist.

In der Panel-Objektklasse \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, und zwar in der Methode zum Erstellen eines neuen angehängten Elements und zum Hinzufügen zur Liste der angehängten Objekte, legen wir das Vererbte dieser Eigenschaften für alle untergeordneten Objekte fest (Objekte, die aus ihren Eltern erstellt werden):

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//| and add it to the list of bound objects                          |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity)
  {
//--- If the type of a created graphical element is less than the "element", inform of that and return 'false'
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return NULL;
     }
//--- Specify the element index in the list
   int num=this.m_list_elements.Total();
//--- Create a description of the default graphical element
   string descript=TypeGraphElementAsString(element_type);
//--- Get the screen coordinates of the object relative to the coordinate system of the base object
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,descript,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return NULL;
//--- and add it to the list of bound graphical elements
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return NULL;
     }
//--- Set the minimum properties for a bound graphical element
   obj.SetBackgroundColor(colour,true);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
   obj.SetID(this.GetMaxIDAll()+1);
   obj.SetNumber(num);
   obj.SetCoordXRelative(obj.CoordX()-this.CoordX());
   obj.SetCoordYRelative(obj.CoordY()-this.CoordY());
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(obj.CoordXRelative());
   obj.SetCoordYRelativeInit(obj.CoordYRelative());
   obj.SetVisible(this.IsVisible(),false);
   obj.SetActive(this.Active());
   obj.SetEnabled(this.Enabled());
   return obj;
  }
//+------------------------------------------------------------------+

Jetzt erbt jedes neu erstellte angehängte Objekt diese Eigenschaften sofort von seinem Basisobjekt. Auf diese Weise werden Situationen vermieden, in denen ein untergeordnetes Objekt eines inaktiven Objekts plötzlich Aktivität zeigt oder ein untergeordnetes Objekt plötzlich auf dem Chart eines verborgenen Objekts erscheint, usw.

In ähnlicher Weise sollten Maus-Ereignis-Handler inaktive oder ausgeblendete Objekte übergehen.

In derselben Datei fügen wir die Zeichenkette, die die Behandlung eines ausgeblendeten oder nicht verfügbaren Objekts deaktivieren, zur letzten Mausereignisbehandlung hinzu:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {

Wenn dies genau das Objekt dieser Art ist, verlassen wir einfach die Methode.


Ich habe dafür gesorgt, dass ein Objekt, das einen Rahmen hat, nur dann gezeichnet wird, wenn das Flag für seine Neuzeichnung in der Methode zum Löschen und Füllen mit der Hintergrundfarbe gesetzt ist. Dieses Verhalten ist falsch. Es ist nicht immer notwendig, Objekte zu zeichnen, bei denen das gesamte Chart neu gezeichnet werden muss. Wenn das Redraw-Flag in diesem Fall jedoch deaktiviert ist, verschwindet der Objektrahmen, wenn die Methode aufgerufen wird. Außerdem gibt es bereits eine Bedingung für das Zeichnen eines Rahmens — sein Typ ist als nicht fehlend festgelegt. Daher entfernen wir in allen Klassen, in denen es die Methode Erase() gibt, die Prüfung auf das Redraw-Flag, um den Objektrahmen anzuzeigen:

if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)

In \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh schreiben wir eine leere virtuelle Methode, um den Objektrahmen zu zeichnen:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color_init;                        // Initial color of the control text
   color             m_fore_state_on_color_init;               // Initial color of the control text when the control is "ON"
private:
//--- Return the font flags
   uint              GetFontFlags(void);
public:
//--- Draw a frame
   virtual void      DrawFrame(void){}
//--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list
   CArrayObj        *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   int               ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);

Die Methode wird in abgeleiteten Klassen, die einen Rahmen benötigen, anstelle der zuvor implementierten Methoden DrawFrameBevel(), DrawFrameFlat(), DrawFrameSimple() und DrawFrameStamp() der Formularobjektklasse verwendet, da diese Methoden meist zum Zeichnen eines bestimmten Formularobjektrahmens verwendet werden. Wenn wir für ein bestimmtes grafisches Element einen eigenen Rahmen zeichnen müssen, definieren wir die hier angegebene Methode um und zeichnen den erforderlichen Rahmen.


Die Methode Erase() prüfen wir nun nicht mehr die Aktualisierungsflags beim Zeichnen eines Rahmens:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::Erase(colour,opacity,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CWinFormBase::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::Erase(colors,opacity,vgradient,cycle,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Jetzt wird der Rahmen immer dann gezeichnet, wenn sein Typ für ihn festgelegt ist. Dies geschieht in allen Dateien aller Klassen, die über Löschmethoden verfügen.


Am Ende der Methode, die die Beschreibung der Integer-Eigenschaft des Elements zurückgibt, fügen wir die Codeblöcke hinzu, um die neuen Eigenschaften der grafischen Elemente zurückzugeben:

      property==CANV_ELEMENT_PROP_TAB_ALIGNMENT                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_TAB_SIZE_MODE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TabSizeModeDescription((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_TAB_PAGE_NUMBER              ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ALIGNMENT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the control real property              |
//+------------------------------------------------------------------+

Jetzt kann jedes grafische Element eine Zeichenkette zurückgeben, der die angegebene neue Eigenschaft und ihren Wert beschreibt.


In einigen geschützten Konstruktoren von grafischen Elementen gibt es jetzt ganz am Ende des Konstruktorcodes eine Zeichenkette, die bewirkt, dass das erstellte Objekt neu gezeichnet wird:

this.Redraw(false);

Dieses Verhalten ist falsch. Das Objekt sollte nur nach seiner endgültigen Erstellung neu gezeichnet werden, nicht bei jedem nächsten Konstruktor der gesamten Vererbungshierarchie des erstellten Objekts.

Stellen wir uns die Hierarchiekette der Objekte vor: Obj0 --> Obj1 --> Obj2 --> Obj3 --> Obj4 --> ... ... --> ObjN, wobei Obj0 das allererste Objekt in der Hierarchie ist, während ObjN das allerletzte ist, dann werden bei seiner Erzeugung alle Konstruktoren der gesamten Vererbungskette nacheinander aufgerufen. Und wenn jede von ihnen die angegebene Zeichenkette mit der Aktualisierung enthält, dann wird das Objekt jedes Mal neu gezeichnet.

Also wir entfernen diese Zeichenfolgen aus allen geschützten Konstruktoren aller Klassen.

Zum Beispiel \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh,

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CCommonBase::CCommonBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                         const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CWinFormBase(type,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.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   if(this.AutoSize())
      this.AutoSetWH();
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

Die gleichen Änderungen wurden bereits in den folgenden Klassen vorgenommen:
CLabel
in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh und CButton in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh.

Wir implementieren in derselben Datei der Klasse CCommonBase die oben genannten Änderungen in den Erase()-Methoden, um die Überprüfung des Redraw-Flags zu entfernen:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CCommonBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::Erase(colour,opacity,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CCommonBase::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::Erase(colors,opacity,vgradient,cycle,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Im weiteren Verlauf werden wir diese Änderungen nicht in anderen Dateien anderer Klassen beschreiben.


Da es nicht mehr notwendig ist, das Flag zum Neizeichnen zwangsweise an die Methode zu senden, wird in der Objektklassendatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, und zwar in der Methode zum Neuzeichnen des Objekts, statt der Angabe des vorherigen true und des Neuzeichnens des gesamten Charts, das Redraw-Flag übergeben, das wiederum von außen an die Methode übergeben wird und die Notwendigkeit des Neuzeichnens beeinflusst:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CButton::Redraw(bool redraw)
  {
//--- Fill the object with the background color
   this.Erase(this.BackgroundColor(),this.Opacity(),redraw);
//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment
   int x=0,y=0;
   CLabel::SetTextParamsByAlign(x,y);
//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


Ganz am Ende der Methode, die den Schaltflächenstatus für alle Schaltflächen derselben Containergruppe auf „freigegeben“ setzt, fügen wir ein Neuzeichnen des Charts hinzu, in dem das Schaltflächenobjekt erstellt wurde, um Änderungen unmittelbar nach der Verarbeitung aller Schaltflächen anzuzeigen:

//+------------------------------------------------------------------+
//| Sets the state of the button to "released"                       |
//| for all Buttons of the same group in the container               |
//+------------------------------------------------------------------+
void CButton::UnpressOtherAll(void)
  {
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all objects of a certain type from the base object (Button or its descendant)
   CArrayObj *list=base.GetListElementsByType(this.TypeGraphElement());
//--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one)
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL);
//--- From the received list, select only those objects whose group index matches the group of the current one
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL);
//--- If the list of objects is received,
   if(list!=NULL)
     {
      //--- in the loop through all objects in the list
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next object,
         CButton *obj=list.At(i);
         if(obj==NULL)
            continue;
         //--- set the button status to "released",
         obj.SetState(false);
         //--- set the background color to the original one (the cursor is on another button outside this one)
         obj.SetBackgroundColor(obj.BackgroundColorInit(),false);
         obj.SetForeColor(obj.ForeColorInit(),false);
         obj.SetBorderColor(obj.BorderColorInit(),false);
         //--- Redraw the object to display the changes
         obj.Redraw(false);
        }
     }
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+


Ganz am Anfang des letzten Mausereignisses fügen wir eine Prüfung auf Unsichtbarkeit oder Unzugänglichkeit des Elements hinzu, wie wir es oben für das Formularobjekt getan haben:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CButton::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=GetMouseState();
   switch(state)
     {

Die gleichen Verbesserungen wurden bereits in den folgenden Klassen vorgenommen:
CCheckBox in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh und CTabHeader in \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.

Ich werde mich nicht weiter mit diesen Veränderungen befassen.


TabField-Objekt — TabControl-Feld

Im vorangegangenen Artikel habe ich die Hilfsklasse TabHeader für Registerkarten erstellt. Die Klasse wird vom Button-Objekt abgeleitet, da sie fast die gesamte Funktionalität dieses Objekts übernimmt. Eine solche Überschrift steht in direktem Zusammenhang mit dem Feld der Registerkarten, die zusammen eine Registerkarte bilden. Das Steuerelement selbst besteht aus mindestens zwei solcher Registerkarten.

Im vorigen Artikel habe ich ein Containerobjekt verwendet, um das Registerkartenfeld zu implementieren. Dies ist das Basisobjekt für alle Containerobjekte in der Bibliothek. Das Registerkartenfeld sollte untergeordnete Objekte enthalten, die auf diesem Feld erstellt wurden und dementsprechend diesem untergeordnet sind. Die Funktionalität des Basiscontainerobjekts für die Implementierung der Handhabung des Feldes ist natürlich nicht ausreichend. Erstellen wir daher eine neue Klasse des Registerkartenfeld-Objekts auf der Grundlage des Basis-Containerobjekts.

Im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ erstellen wir die neue Datei TabField.mqh der Klasse CTabField. Die Klasse wird nun vom Basis-Containerobjekt abgeleitet: Die Panel-Objektdatei sollte in die erstellte Klassendatei aufgenommen werden. Dadurch erhalten alle Dateien mit grafischen Objekten der Bibliothek Zugang zu ihr:

//+------------------------------------------------------------------+
//|                                                     TabField.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Containers\Panel.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabField : public CContainer
  {
  }


Im privaten Abschnitt deklarieren wir die Methode, die den Zeiger auf das diesem Feld entsprechende Header-Objekt zurückgibt, und die virtuelle Methode zur Erstellung von grafischen Elementen, die mit der Registerkarte (diesem Feldobjekt) verbunden sind:

//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabField : public CContainer
  {
private:
//--- Find and return a pointer to the header object corresponding to the number of this tab
   CWinFormBase     *GetHeaderObj(void);
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
protected:

Wenn wir den Typ des Header-Objekts in dieser Datei genau durch seinen Typ (CTabHeader) spezifizieren, der in dieser Klasse sichtbar ist, und versuchen, die gesamte Bibliothek zu kompilieren, indem wir die Hauptklasse der CEngine-Bibliothek kompilieren, dann werden wir eine große Anzahl von Fehlern und Warnungen über den unbekannten Typ von CTabHeader erhalten. Anstatt nach dem Fehler zu suchen, deklarieren wir einfach den Rückgabetyp als Basisobjekt für alle WinForms-Bibliotheksobjekte. Es wird genügen, sie hier zu behandeln. Außerhalb der Klasse können wir sie bereits von hier aus mit ihrem korrekten Typ erhalten.

Die virtuelle Methode zur Erstellung von angehängten grafischen Elementen wird benötigt, damit wir beim Zugriff auf das Feld untergeordnete Objekte erstellen können.

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

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


Im öffentlichen Abschnitt ‚public‘ deklarieren wir die Methoden für die Arbeit mit der Klasse und einen parametrischen Konstruktor:

public:
//--- Draws a field frame depending on the location of the header
   virtual void      DrawFrame(void);
//--- (1) Set and (2) return the tab index
   void              SetPageNumber(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
   int               PageNumber(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);   }
//--- 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);
//--- Constructors
                     CTabField(const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
  };
//+------------------------------------------------------------------+

Alle virtuellen Methoden überschreiben hier die Methoden der gleichnamigen Elternklassen, während die Methoden zum Setzen und Zurückgeben der Nummer der Registerkarte, zu der das Feld gehört, einfach den übergebenen Wert in die Objekteigenschaft setzen und ihn zurückgeben.


Geschützte und parametrische Konstruktoren:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CTabField::CTabField(const ENUM_GRAPH_ELEMENT_TYPE type,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CContainer(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTabField::CTabField(const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+

Der einzige Unterschied zwischen ihnen besteht darin, dass der geschützte Konstruktor den Typ eines erstellten Objekts erhält (wenn es von ihm geerbt wurde), und dieser Typ wird an das übergeordnete Objekt übergeben. Im öffentlichen parametrischen Konstruktor wird der Typ, der an die übergeordnete Klasse übergeben wird, explizit angegeben — das Feldobjekt.

Im Konstruktorkörper wird das erstellte Objekt standardmäßig auf die gewünschten Werte einiger Eigenschaften gesetzt. Die übrigen Eigenschaften des Objekts werden in den übergeordneten Klassen festgelegt.

Die Methode, die den Zeiger auf die dem Tabulatorindex entsprechende Kopfzeile findet und zurückgibt:

//+------------------------------------------------------------------+
//| Find and return a pointer to the header                          |
//| corresponding to the tab index                                   |
//+------------------------------------------------------------------+
CWinFormBase *CTabField::GetHeaderObj(void)
  {
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return NULL;
//--- From the base object, get the list of tab header objects
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
//--- Leave only the object whose tab index matches this one in the obtained list
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL);
//--- Return the pointer to the found object from the list
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Hier wird die Logik der Methode in den Kommentaren zum Code detailliert beschrieben. Kurz gesagt, um dieses Feld mit seiner Kopfzeile abzugleichen, die in der Klasse gespeichert ist, an die das Objekt und die Kopfzeile gebunden sind, müssen wir auf das Basisobjekt zugreifen. Ich habe diese Methode schon vor langer Zeit eingeführt. Ermittelt den Zeiger auf das Basisobjekt und die Liste aller daran gebundenen Objekte mit dem Typ des Registerkarten-Titelobjekts. Die resultierende Liste sortieren wir so, dass nur ein Objekt mit der im Objekt eingestellten Tabulatornummer übrig bleibt. Wir haben also die diesem Feld entsprechende Kopfzeile gefunden, und der Zeiger darauf wird in der resultierenden Liste gespeichert. Geben wir es also zurück. Wenn das Objekt nicht gefunden wird, gibt die Methode NULL zurück.


Die Methode, die den Rand eines Elements in Abhängigkeit von der Position der Kopfzeile zeichnet:

//+------------------------------------------------------------------+
//| Draw the element frame depending on the header position          |
//+------------------------------------------------------------------+
void CTabField::DrawFrame(void)
  {
//--- Set the initial coordinates
   int x1=0;
   int y1=0;
   int x2=this.Width()-1;
   int y2=this.Height()-1;
//--- Get the tab header corresponding to the field
   CTabHeader *header=this.GetHeaderObj();
   if(header==NULL)
      return;
//--- Draw a rectangle that completely outlines the field
   this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity());
//--- Depending on the location of the header, draw a line on the edge adjacent to the header.
//--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side
//--- thus, visually the edge will not be drawn on the adjacent side of the header
   switch(header.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        // { }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        // { }
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Hier wird die Logik der Methode in den Kommentaren zum Code detailliert beschrieben. Im Moment zeichnet die Methode einen Rahmen, wenn sich die Kopfzeilen oben und unten befinden. Die Standorte auf der rechten und linken Seite werden in späteren Artikeln behandelt. Kurz gesagt, nehmen wir an, dass eine Kopfzeile von oben an das Feld angrenzt. An der Stelle, an der sie sich treffen, sollte es keine Linie geben. Es war möglich, einen Feldrand mit einer gestrichelten Linie zu zeichnen, aber es gibt ein Problem mit der Anzahl der gezeichneten Punkte in Abhängigkeit von der Position der Kopfzeile. Befindet sie sich am linken oder rechten Rand des Feldes, so ist die Anzahl der Linienpunkte um einen weniger, wenn die Kopfzeile nicht am Rand des Feldes liegt.

Daher ist es einfacher, zunächst ein Rechteck zu zeichnen, das das Feld vollständig umreißt, und dann, nachdem man die Koordinaten der Kopfzeile erhalten hat, eine Linie mit der Hintergrundfarbe des Feldes dort zu ziehen, wo die Kopfzeile mit dem Feld in Kontakt ist. Auf diese Weise „löschen“ wir die Linie, in der die Kopfzeile das Feld berührt, um die korrekte Anzeige der Registerkarte zu erhalten.

Die virtuellen Methoden, die ein Element löschen und es mit Farbe und Deckkraft füllen:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CTabField::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::Erase(colour,opacity,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CTabField::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::Erase(colors,opacity,vgradient,cycle,redraw);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Die Methoden sind identisch mit genau denselben Methoden in anderen Klassen oder in übergeordneten Klassen. Sie wurden hier neu definiert, damit der Rahmen genau nach der oben beschriebenen Methode gezeichnet wird.

Die Methode zum Erstellen eines neuen grafischen Objekts:

//+------------------------------------------------------------------+
//| 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.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.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
         element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM     :
         element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(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 virtuelle Methode ist auch identisch mit gleichnamigen Methoden in anderen Klassen. Hier haben wir den neuen Codeblock für die Erstellung des Feldobjekts, dessen Klasse berücksichtigt wird. Ich werde genau die gleichen Blöcke für die Erstellung des Objekts in anderen Klassen in den gleichen Methoden hinzufügen.

Mit dieser Methode werden wir in der Lage sein, Unterobjekte auf dem Registerkartenfeld zu erstellen.


Verbessern wir die Registerkartenkopfklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.

Aus dem privaten Bereich entfernen wir die Methode zur Einstellung der Objektgröße und -verschiebung:

//--- Sets the width, height and shift of the element depending on the state
   void              SetWH(void);


Auch deklarieren wir die Methoden zum Setzen der Strings der hervorgehobenen Tabulatorüberschrift an die richtige Position:

private:
   int               m_width_off;                        // Object width in the released state
   int               m_height_off;                       // Object height in the released state
   int               m_width_on;                         // Object width in the selected state
   int               m_height_on;                        // Object height in the selected state
   int               m_col;                              // Header column index
   int               m_row;                              // Header row index
//--- Adjust the size and location of the element depending on the state
   bool              WHProcessStateOn(void);
   bool              WHProcessStateOff(void);
//--- Draws a header frame depending on its position
   virtual void      DrawFrame(void);
//--- Set the string of the selected tab header to the correct position, (1) top, (2) bottom, (3) left and (4) right
   void              CorrectSelectedRowTop(void);
   void              CorrectSelectedRowBottom(void);
   void              CorrectSelectedRowLeft(void);
   void              CorrectSelectedRowRight(void);
   
protected:


Im öffentlichen Teil deklarieren/schreiben wir neue Methoden und verbessern die bestehenden:

public:
//--- Find and return a pointer to the field object corresponding to the tab index
   CWinFormBase     *GetFieldObj(void);
//--- Set the control size in the (1) released and (2) selected state
   bool              SetSizeOff(void)  { return(CGCnvElement::SetWidth(this.m_width_off) && CGCnvElement::SetHeight(this.m_height_off) ? true : false);  }
   bool              SetSizeOn(void)   { return(CGCnvElement::SetWidth(this.m_width_on) && CGCnvElement::SetHeight(this.m_height_on)   ? true : false);  }
//--- Sets the size of the control element
   void              SetWidthOff(const int value)                                            { this.m_width_off=value;  }
   void              SetHeightOff(const int value)                                           { this.m_height_off=value; }
   void              SetWidthOn(const int value)                                             { this.m_width_on=value;   }
   void              SetHeightOn(const int value)                                            { this.m_height_on=value;  }
//--- Set all sizes of the control element
   bool              SetSizes(const int w,const int h);
//--- Returns the control size
   int               WidthOff(void)                                                    const { return this.m_width_off; }
   int               HeightOff(void)                                                   const { return this.m_height_off;}
   int               WidthOn(void)                                                     const { return this.m_width_on;  }
   int               HeightOn(void)                                                    const { return this.m_height_on; }
//--- (1) Set and (2) return the index of the tab header row
   void              SetRow(const int value)                                                 { this.m_row=value;        }
   int               Row(void)                                                         const { return this.m_row;       }
//--- (1) Set and (2) return the index of the tab header column
   void              SetColumn(const int value)                                              { this.m_col=value;        }
   int               Column(void)                                                      const { return this.m_col;       }
//--- Set the tab location
   void              SetTabLocation(const int row,const int col)
                       {
                        this.SetRow(row);
                        this.SetColumn(col);
                       }
//--- (1) Sets and (2) return the location of the object on the control
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment);
                       }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }
   
//--- (1) Set and (2) get the mode of setting the tab size
   void              SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode);
                       }
   ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);}

//--- (1) Set and (2) return the tab index
   void              SetPageNumber(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
   int               PageNumber(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);   }

//--- Sets the state of the control
   virtual void      SetState(const bool flag);

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

//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);
   
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   virtual void      SetPaddingLeft(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   virtual void      SetPadding(const int left,const int top,const int right,const int bottom)
                       {
                        this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom);
                       }
protected:
//--- Protected constructor with object type, chart ID and subwindow

Zuvor setzte die Methode SetAlignment() zusätzlich zur Eigenschaft auch die Rahmengröße. Der Rahmen hat hier nur eine Größe — 1 Pixel, also ist hier nichts zu ändern. Entfernen wir den unnötigen Code:

//--- (1) Sets and (2) return the location of the object on the control
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
                           this.SetBorderSize(1,1,1,0);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
                           this.SetBorderSize(1,0,1,1);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
                           this.SetBorderSize(1,1,0,1);
                        if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
                           this.SetBorderSize(0,1,1,1);
                       }


Die Konstruktoren der Klasse, geschützt und parametrisch:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);
   this.SetGroupButtonFlag(true);
   this.SetText(TypeGraphElementAsString(this.TypeGraphElement()));
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
   this.SetPadding(6,3,6,3);
   this.SetSizes(w,h);
   this.SetState(false);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);
   this.SetGroupButtonFlag(true);
   this.SetText(TypeGraphElementAsString(this.TypeGraphElement()));
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
   this.SetPadding(6,3,6,3);
   this.SetSizes(w,h);
   this.SetState(false);
  }
//+------------------------------------------------------------------+

Anstatt die Größe der Kopfzeile separat einzustellen:

   this.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);

Wir rufen die Methode zum Einstellen der Größe der Registerkartenüberschriften auf, wobei die Größe in Abhängigkeit vom Einstellungsmodus für die Überschriftengröße festgelegt wird:

//+------------------------------------------------------------------+
//| Set all header sizes                                             |
//+------------------------------------------------------------------+
bool CTabHeader::SetSizes(const int w,const int h)
  {
//--- If the passed width or height is less than 4 pixels, 
//--- make them equal to four pixels
   int width=(w<4 ? 4 : w);
   int height=(h<4 ? 4 : h);
//--- Depending on the header size setting mode
   switch(this.TabSizeMode())
     {
      //--- set the width and height for the Normal mode
      case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL :
        this.TextSize(this.Text(),width,height);
        width+=this.PaddingLeft()+this.PaddingRight();
        height=h+this.PaddingTop()+this.PaddingBottom();
        break;
      //--- For the Fixed and Fill modes, the size remains
      //--- passed to the method and adjusted
      case CANV_ELEMENT_TAB_SIZE_MODE_FIXED  : break;
      //---CANV_ELEMENT_TAB_SIZE_MODE_FILL
      default: break;
     }
//--- Set the results of changing the width and height to 'res'
   bool res=true;
   res &=this.SetWidth(width);
   res &=this.SetHeight(height);
//--- If there is an error in changing the width or height, return 'false'
   if(!res)
      return false;
//--- Set the changed size for different button states
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);
   this.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   return true;
  }
//+------------------------------------------------------------------+

Die Logik der Methode wird in den Codekommentaren beschrieben. Die Größen werden nur für den Modus angepasst, bei dem die Breite der Kopfzeile mit der Breite des darauf angezeigten Textes übereinstimmt. Für den Modus „Fixed“ sollte die Größe der Kopfzeile festgelegt werden, d. h. sie bleibt diejenige, die der Methode in den Variablen w und h übergeben wurde, wird aber angepasst, wenn die Größen weniger als vier Pixel betragen (in den Variablen width und height). Die Art und Weise, wie die Breite des Containers gestreckt wird, wird in späteren Artikeln behandelt.

Die Methode, mit der der Zustand des Steuerelements festgelegt wird, hat sich grundlegend geändert:

//+------------------------------------------------------------------+
//| Set the state of the control                                     |
//+------------------------------------------------------------------+
void CTabHeader::SetState(const bool flag)
  {
//--- Get the button state and set the new one passed to the method
   bool state=this.State();
   CButton::SetState(flag);
//--- If the previous state of the button does not match the set
   if(state!=this.State())
     {
      //--- If the button is pressed
      if(this.State())
        {
         //--- Call the button resizing method and bring it to the foreground
         this.WHProcessStateOn();
         this.BringToTop();
         //--- Get the base object the tab header is attached to (TabControl)
         CWinFormBase *base=this.GetBase();
         if(base==NULL)
            return;
         //--- Set the index of the selected tab to the TabControl object
         base.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber());
         //--- Get the list of tab field objects from the base object
         CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
         if(list==NULL)
            return;
         //--- In the loop through the received list, hide all fields that do not match the header
         for(int i=0;i<list.Total();i++)
           {
            //--- get the next tab field object
            CWinFormBase *obj=list.At(i);
            //--- If the object is not received or corresponds to the selected header, move on
            if(obj==NULL || obj.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)==this.PageNumber())
               continue;
            //--- Set the ZOrder tab field as the base object and hide the field
            obj.SetZorder(base.Zorder(),false);
            obj.Hide();
           }
         //--- Get the field object corresponding to the field header (this object)
         CWinFormBase *field=this.GetFieldObj();
         if(field==NULL)
            return;
         //--- Display the field and set its ZOrder higher than other fields of the TabControl object,
         //--- draw the frame of the field object and bring it to the foreground
         field.Show();
         field.SetZorder(base.Zorder()+1,false);
         field.DrawFrame();
         field.BringToTop();
        }
      //--- If the button is not pressed, call the method to restore the header size
      else
         this.WHProcessStateOff();
     }
  }
//+------------------------------------------------------------------+

Wenn die Schaltfläche ausgewählt ist (auf die Kopfzeile der Registerkarte geklickt), müssen wir die Schaltfläche (Kopfzeile) vergrößern und die Kopfzeile in den Vordergrund bringen. Dann müssen wir alle Registerkartenfelder ausblenden, die nicht mit der ausgewählten Überschrift übereinstimmen, während das Feld dieser Überschrift im Gegensatz dazu angezeigt und in den Vordergrund gebracht werden soll. Außerdem sollte das angezeigte Registerkartenfeld anklickbar sein, sodass sein ZOrder-Parameter größer ist als der der übrigen Steuerelemente, während für nicht ausgewählte Felder der ZOrder-Parameter niedriger sein sollte als der des ausgewählten Feldes. All dies wird durch die Methode erreicht.


In der Methode, die die Größe und Position eines Elements im Zustand „ausgewählt“ in Abhängigkeit von seiner Position anpasst, müssen wir die Methoden aufrufen, die die ausgewählte Kopfzeile an die Position verschieben, an der die Kopfzeile an ihr Feld angefügt wird. Wenn die Anordnung von Überschriften in mehreren Zeilen erlaubt ist, kann sich die ausgewählte Überschrift in einer Zeile befinden, die das Feld nicht berührt:

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "selected" state depending on its location                |
//+------------------------------------------------------------------+
bool CTabHeader::WHProcessStateOn(void)
  {
//--- If failed to set a new size, leave
   if(!this.SetSizeOn())
      return false;
//--- Get the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return false;
//--- Depending on the header location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowTop();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()-2))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative()-2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowBottom();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowLeft();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        // {   }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowRight();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        // {   }
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+

Die Position der Registerkartenköpfe auf der linken und rechten Seite kann ich hier nicht beeinflussen. Das werde ich in späteren Artikeln tun.


In der Methode, die die Größe und Position eines Elements im „freigegebenen“ Zustand in Abhängigkeit von seiner Position anpasst, fügen wir verkürzte Codeblöcke hinzu, um Kopfzeilen auf der linken und rechten Seite zu behandeln:

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "released" state depending on its location                |
//+------------------------------------------------------------------+
bool CTabHeader::WHProcessStateOff(void)
  {
//--- If failed to set a new size, leave
   if(!this.SetSizeOff())
      return false;
//--- Depending on the header location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()+2))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative()+2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        //--- shift the header to its original position and set the previous relative coordinates
        // {   }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        //--- shift the header to its original position and set the previous relative coordinates
        // {   }
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+

Dies ist die Grundlage für künftige Verbesserungen.


Die Methode, die die Zeile einer ausgewählten Registerkartenüberschrift an die richtige Position am oberen Rand setzt:

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position at the top                               |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowTop(void)
  {
   int row_pressed=this.Row();      // Selected header row
   int y_pressed=this.CoordY();     // Coordinate where all headers with Row() equal to zero should be moved to
   int y0=0;                        // Zero row coordinate (Row == 0)
//--- If the zero row is selected, then nothing needs to be done - leave
   if(row_pressed==0)
      return;
      
//--- Get the tab field object corresponding to this header and set the Y coordinate of the zero line
   CWinFormBase *obj=this.GetFieldObj();
   if(obj==NULL)
      return;
   y0=obj.CoordY()-this.Height()+2;
   
//--- Get the base object (TabControl)
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all tab headers from the base object
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   if(list==NULL)
      return;
//--- Swap rows in the loop through all headers -
//--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is a zero row
      if(header.Row()==0)
        {
         //--- move the header to the position of the selected row
         if(header.Move(header.CoordX(),y_pressed))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one
            header.SetRow(-1);
           }
        }
      //--- If this is the clicked header line,
      if(header.Row()==row_pressed)
        {
         //--- move the header to the position of the zero row
         if(header.Move(header.CoordX(),y0))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one
            header.SetRow(-2);
           }
        }
     }
//--- Set the correct Row and Col
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it
      if(header.Row()==-1)
         header.SetRow(row_pressed);
      //--- If this is the selected row moved to the zero position, set Row of the zero row
      if(header.Row()==-2)
         header.SetRow(0);
     }
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben. Die Idee ist folgende: Wenn wir eine Registerkarte ausgewählt haben (auf die Schaltfläche für die Registerkartenüberschrift geklickt haben), die sich in der Nullzeile befindet (die Null ist in Kontakt mit dem Registerkartenfeld, die erste ist über der Null, die zweite ist über der ersten usw.), dann ist es nicht notwendig, die Zeile an eine neue Stelle zu verschieben. Wenn wir eine Registerkarte ausgewählt haben, deren Kopfzeile sich nicht in der Nullzeile befindet, müssen wir alle Kopfzeilen dieser Zeile an die Stelle der Nullzeile verschieben und die Nullzeile an die Stelle der Zeile, deren Kopfzeile angeklickt wurde. Die Nullzeile und die Zeile mit der ausgewählten Tabulatorüberschrift tauschen also immer die Plätze.

Diese Methode behandelt nur eine Situation, in der die Registerkartenüberschriften oben liegen. Sie können sich auch unten, links und rechts befinden. Ich werde die entsprechende Handhabung in späteren Artikeln implementieren. Für den Moment werde ich nur verkürzte Methoden für sie implementieren:

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position at the bottom                            |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowBottom(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position on the left                              |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowLeft(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position on the right                             |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowRight(void)
  {
  
  }
//+------------------------------------------------------------------+


Die Methode sucht und gibt den Zeiger auf das Feldobjekt zurück, das dem Registerindex entspricht:

//+------------------------------------------------------------------+
//| Find and return a pointer to the field object                    |
//| corresponding to the tab index                                   |
//+------------------------------------------------------------------+
CWinFormBase *CTabHeader::GetFieldObj(void)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return NULL;
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Die Methode ist identisch mit der Methode GetHeaderObj(), die den Zeiger auf den Tabulator-Header sucht und zurückgibt, den ich oben in der Tabulatorfeld-Objektklasse betrachtet habe. Diese Methode sucht nach dem Tabulatorfeld, das der Überschrift entspricht.


Im Ereignis „Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt“ ergänzen wir den Codeblock hinzu, in dem das entsprechende Registerfeld für die angeklickte Überschrift gesucht und angezeigt wird:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CTabHeader::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- The mouse button released outside the element means refusal to interact with the element
   if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge())
     {
      //--- If this is a simple button, set the initial background and text color
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorInit(),false);
         this.SetForeColor(this.ForeColorInit(),false);
        }
      //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not
      else
        {
         this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false);
         this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false);
        }
      //--- Set the initial frame color
      this.SetBorderColor(this.BorderColorInit(),false);
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- The mouse button released within the element means a  click on the control
   else
     {
      //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.ForeColorMouseOver(),false);
         this.Redraw(true);
        }
      //--- If this is the toggle button,
      else
        {
         //--- if the button does not work in the group, set its state to the opposite,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- if the button is not pressed yet, set it to the pressed state
         else if(!this.State())
            this.SetState(true);
         //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not
         this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false);
         
         //--- Get the field object corresponding to the header
         CWinFormBase *field=this.GetFieldObj();
         if(field!=NULL)
           {
            //--- Display the field, bring it to the front and draw a frame
            field.Show();
            field.BringToTop();
            field.DrawFrame();
           }
         //--- Redraw an object and a chart
         this.Redraw(true);
        }
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Set the frame color for "The cursor is over the active area" status
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
  }
//+------------------------------------------------------------------+

Wenn wir auf die Überschrift klicken, sollte das Ergebnis die Anzeige des entsprechenden Registerfeldes sein. Der hervorgehobene Codeblock tut genau das. Fügen wir das Neuzeichnen des Charts für eine einfache Schaltfläche hinzu (später werde ich das Erscheinungsbild der Kopfzeile implementieren, einschließlich einer Anzeige in Form von Schaltflächen). Ehrlich gesagt, weiß ich nicht mehr, welches der Experimente zu diesem String geführt hat. Aber lassen wir es erst einmal dabei bewenden — wir sind noch nicht so weit.


Die gesamte Steuerung von Kopfzeilen und Registerfeldern sollte über die Klasse TabControl erfolgen.

Lassen Sie uns die Klasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh verbessern.

Binden wir die Datei der neu geschriebenen Tabulatorfeld-Objektklasse ein und deklarieren neue Variablen und Methoden im privaten Abschnitt:

//+------------------------------------------------------------------+
//|                                                   TabControl.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\TabHeader.mqh"
#include "..\TabField.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabControl : public CContainer
  {
private:
   int               m_item_width;                    // Fixed width of tab headers
   int               m_item_height;                   // Fixed height of tab headers
   int               m_header_padding_x;              // Additional header width if DrawMode==Fixed
   int               m_header_padding_y;              // Additional header height if DrawMode==Fixed
   int               m_field_padding_top;             // Padding of top tab fields
   int               m_field_padding_bottom;          // Padding of bottom tab fields
   int               m_field_padding_left;            // Padding of left tab fields
   int               m_field_padding_right;           // Padding of right tab fields
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);

//--- Return the list of (1) headers and (2) tab fields
   CArrayObj        *GetListHeaders(void)                { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);  }
   CArrayObj        *GetListFields(void)                 { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);   }
//--- Set the tab as selected
   void              SetSelected(const int index);
//--- Set the tab as released
   void              SetUnselected(const int index);
//--- Set the number of a selected tab
   void              SetSelectedTabPageNum(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
//--- Arrange the tab headers according to the set modes
   void              ArrangeTabHeaders(void);
//--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right
   void              ArrangeTabHeadersTop(void);
   void              ArrangeTabHeadersBottom(void);
   void              ArrangeTabHeadersLeft(void);
   void              ArrangeTabHeadersRight(void);
public:


Im öffentlichen Abschnitt der Klasse deklarieren wir die neuen Methoden:

public:
//--- Create the specified number of tabs
   bool              CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="");
   
//--- Create a new attached element
   bool              CreateNewElement(const int tab_page,
                                      const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
 
//--- Return the number of (1) bound elements and (2) the bound element by the index in the list
   int               TabElementsTotal(const int tab_page);
   CGCnvElement     *GetTabElement(const int tab_page,const int index);
   
//--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list in the specified tab
   CArrayObj        *GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type);
   int               TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Return a pointer to the (1) tab header, (2) field and (3) the number of tabs
   CTabHeader       *GetTabHeader(const int index)       { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);    }
   CWinFormBase     *GetTabField(const int index)        { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,index);     }
   int               TabPages(void)                      { return(this.GetListHeaders()!=NULL ? this.GetListHeaders().Total() : 0); }
//--- (1) Set and (2) return the location of tabs on the control
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment);
                        CArrayObj *list=this.GetListHeaders();
                        if(list==NULL)
                           return;
                        for(int i=0;i<list.Total();i++)
                          {
                           CTabHeader *header=list.At(i);
                           if(header==NULL)
                              continue;
                           header.SetAlignment(alignment);
                          }
                       }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }

//--- (1) Set and (2) get the mode of setting the tab size
   void              SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode);
                        CArrayObj *list=this.GetListHeaders();
                        if(list==NULL)
                           return;
                        for(int i=0;i<list.Total();i++)
                          {
                           CTabHeader *header=list.At(i);
                           if(header==NULL)
                              continue;
                           header.SetTabSizeMode(mode);
                          }
                       }
   ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);}
   
//--- Sets all tab headers to the PaddingWidth and PaddingHeight values
   void              SetHeaderPadding(const int w,const int h);
//--- Set all tab fields to Padding values
   void              SetFieldPadding(const int top,const int bottom,const int left,const int right);
//--- Return the PaddingWidth and PaddingHeight values of the tab headers
   int               HeaderPaddingWidth(void)      const { return this.m_header_padding_x;      }
   int               HeaderPaddingHeight(void)     const { return this.m_header_padding_y;      }
//--- Return the Padding values of the tab fields
   int               FieldPaddingTop(void)         const { return this.m_field_padding_top;     }
   int               FieldPaddingBottom(void)      const { return this.m_field_padding_bottom;  }
   int               FieldPaddingLeft(void)        const { return this.m_field_padding_left;    }
   int               FieldPaddingRight(void)       const { return this.m_field_padding_right;   }
   
//--- (1) Set and (2) return the flag allowing multiple rows of tab headers on the control
   void              SetMultiline(const bool flag)       { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag);         }
   bool              Multiline(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); }
//--- (1) Set and (2) return the fixed width of tab headers
   void              SetItemWidth(const int value)       { this.m_item_width=value;             }
   int               ItemWidth(void)               const { return this.m_item_width;            }
//--- (1) Set and (2) return the fixed height of tab headers
   void              SetItemHeight(const int value)      { this.m_item_height=value;            }
   int               ItemHeight(void)              const { return this.m_item_height;           }
//--- Set a fixed tab size
   void              SetItemSize(const int w,const int h)
                       {
                        if(this.ItemWidth()!=w)
                           this.SetItemWidth(w);
                        if(this.ItemHeight()!=h)
                           this.SetItemHeight(h);
                       }
//--- Set the header text (1) of the specified tab and (2) by index
   void              SetHeaderText(CTabHeader *header,const string text);
   void              SetHeaderText(const int index,const string text);
   
//--- 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());             }
   
//--- Constructor
                     CTabControl(const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+


Im Klassenkonstruktor legen wir die Standardwerte für den Tabulatorgrößenmodus und Auffüllungswerte für die Überschriften und Felder fest:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CTabControl::CTabControl(const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetOpacity(0,true);
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetBackgroundColorMouseDown(CLR_CANV_NULL);
   this.SetBackgroundColorMouseOver(CLR_CANV_NULL);
   this.SetBorderColor(CLR_CANV_NULL,true);
   this.SetBorderColorMouseDown(CLR_CANV_NULL);
   this.SetBorderColorMouseOver(CLR_CANV_NULL);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetItemSize(58,18);
   this.SetTabSizeMode(CANV_ELEMENT_TAB_SIZE_MODE_NORMAL);
   this.SetHeaderPadding(6,3);
   this.SetFieldPadding(3,3,3,3);
  }
//+------------------------------------------------------------------+


In der Methode, die die angegebene Anzahl von Registerkarten erstellt, setzen wir den Zeiger auf ein Basisobjekt, den Registerindex und die Gruppe auf die erstellten Objekte der Überschriften und Felder. Wir legen den Auffüllungswert für Kopfzeilen und Felder fest, fügen den Tabulatortext hinzu, wenn die Methode einen leeren Text für die Tabulator-Kopfzeilen erhalten hat, legen den Modus für die Angabe der Kopfzeilengröße fest und legen deren Größe fest:

//+------------------------------------------------------------------+
//| Create the specified number of tabs                              |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Calculate the size and initial coordinates of the tab header
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);
   
//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab headers, set their initial coordinates
      int header_x=2;
      int header_y=0;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
         header_y=0;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
         header_y=this.Height()-h;
      //--- Set the current X coordinate
      header_x=(header==NULL ? header_x : header.RightEdgeRelative());
      //--- Create the TabHeader object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,w,h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      header.SetTabSizeMode(this.TabSizeMode());
      header.SetSizes(w,h);
      
      //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields
      int field_x=0;
      int field_y=0;
      int field_h=this.Height()-header.Height();
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
         field_y=header.BottomEdgeRelative();
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
         field_y=0;
      //--- Create the TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,this.Width(),field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Arrange all headers in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

Nachdem die angegebene Anzahl von Registerkarten erstellt wurde, rufen wir die Methode auf, die die Überschriften entsprechend den angegebenen Anzeigemodi anordnet.


Die Methode, die ein neues angehängtes Element erstellt:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CTabControl::CreateNewElement(const int tab_page,
                                   const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h,
                                   const color colour,
                                   const uchar opacity,
                                   const bool activity,
                                   const bool redraw)
  {
   CTabField *field=this.GetTabField(tab_page);
   if(field==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ);
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ)," (Tab",(string)tab_page,")");
      return false;
     }
   return field.CreateNewElement(element_type,x,y,w,h,colour,opacity,activity,redraw);
  }
//+------------------------------------------------------------------+

Wir rufen das Registerkartenfeldobjekt mit dem angegebenen Registerindex ab und geben das Ergebnis des Aufrufs seiner Methode zur Erstellung eines neu gebundenen Elements zurück.


Die Methode, die die Registerkartenüberschriften entsprechend den angegebenen Modi anordnet:

//+------------------------------------------------------------------+
//| Arrange the tab headers                                          |
//| according to the specified modes                                 |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeaders(void)
  {
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :  this.ArrangeTabHeadersTop();     break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :  this.ArrangeTabHeadersBottom();  break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :  this.ArrangeTabHeadersLeft();    break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :  this.ArrangeTabHeadersRight();   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+

Je nach dem angegebenen Modus der Registerkartenanordnung rufen wir die entsprechenden Methoden auf.


Die Methode ordnet die Registerkartenüberschriften oben an:

//+------------------------------------------------------------------+
//| Arrange tab headers on top                                       |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersTop(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Declare the variables
   int col=0;                                // Column
   int row=0;                                // Row
   int x1_base=2;                            // Initial X coordinate
   int x2_base=this.RightEdgeRelative()-2;   // Final X coordinate
   int x_shift=0;                            // Shift the tab set for calculating their exit beyond the container
   int n=0;                                  // The variable for calculating the column index relative to the loop index
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next tab header object
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If the flag for positioning headers in several rows is set
      if(this.Multiline())
        {
         //--- CANV_ELEMENT_TAB_SIZE_MODE_FIXED and CANV_ELEMENT_TAB_SIZE_MODE_NORMAL
         if(this.TabSizeMode()<CANV_ELEMENT_TAB_SIZE_MODE_FILL)
           {
            //--- Calculate the value of the right edge of the header, taking into account that
            //---  the origin always comes from the left edge of TabControl + 2 pixels
            int x2=header.RightEdgeRelative()-x_shift;
            //--- If the calculated value does not go beyond the right edge of the TabControl - 2 pixels, 
            //--- set the column number equal to the loop index minus the value in the n variable
            if(x2<x2_base)
               col=i-n;
            //--- If the calculated value goes beyond the right edge of the TabControl - 2 pixels,
            else
              {
               //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels),
               //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row
               row++;
               x_shift=header.CoordXRelative()-2;
               n=i;
               col=0;
              }
            //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates
            header.SetTabLocation(row,col);
            if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height()))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
           }
         //--- Stretch the headers along the container width
         //--- CANV_ELEMENT_TAB_SIZE_MODE_FILL
         else
           {
            
           }
        }
      //--- If only one row of headers is allowed
      else
        {
         
        }
     }
//--- The location of all tab headers is set. Now place them all together with the fields
//--- according to the header row and column indices
//--- Get the last header in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
//--- If the object is received and its row value is greater than zero
   if(last!=NULL && last.Row()>0)
     {
      //--- Calculate the offset of the tab field Y coordinate
      int y_shift=last.Row()*last.Height();
      //--- In a loop by the list of headers,
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next object
         CTabHeader *header=list.At(i);
         if(header==NULL)
            continue;
         //--- get the tab field corresponding to the received header
         CTabField  *field=header.GetFieldObj();
         if(field==NULL)
            continue;
         //--- shift the tab header by the calculated row coordinates
         if(header.Move(header.CoordX(),header.CoordY()+y_shift))
           {
            header.SetCoordXRelative(header.CoordX()-this.CoordX());
            header.SetCoordYRelative(header.CoordY()-this.CoordY());
           }
         //--- shift the tab field by the calculated shift
         if(field.Move(field.CoordX(),field.CoordY()+y_shift))
           {
            field.SetCoordXRelative(field.CoordX()-this.CoordX());
            field.SetCoordYRelative(field.CoordY()-this.CoordY());
            //--- change the size of the shifted field by the value of its shift
            field.Resize(field.Width(),field.Height()-y_shift,false);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben. Die Methode wurde bisher nur für die Platzierung von Kopfzeilen am oberen Rand des Containers in mehreren Reihen implementiert. Hier wird geprüft, ob die jeweils nächste Überschrift in der Schleife für die nächste Position in der Reihe so platziert wird, dass sie nicht über den Rand des Containers hinausgeht. Wenn dies der Fall ist, beginnen wir eine neue Zeile über der vorherigen. Berechnen wir die Referenzkoordinaten neu, sodass die Reihe, wenn sie sich auf die Koordinaten des Objekts abzüglich des berechneten Versatzes bezieht, wieder am linken Rand des Containers beginnt. Dann berechnen wir, ob die Objekte in den Container passen, und wenn nicht, gehen wir wieder zu einer neuen Reihe über. Nach jeder neuen Zeile werden die erhöhten Werte für Row (Zeile) in die Objekte gesetzt, während die Berechnung der Col-Werte (Spalte) neu beginnt. Am Ende der Schleife haben wir eine Liste von Werten der Zeilen und Spalten, die die Überschriften enthalten sollen.

In einer neuen Schleife durch die Liste der Überschriften werden diese an neuen Koordinaten platziert, die den Werten der im Objekt eingestellten Zeile und Spalte entsprechen, und die entsprechenden Tabulatorfeldobjekte werden um eine vom Maximalwert der Zeile berechnete Entfernung verschoben und in der Höhe um denselben Betrag verringert. Nach Abschluss der Schleife erhalten wir korrekt platzierte Kopfzeilen und die entsprechenden Felder.

In den folgenden Artikeln werden wir die Methode um die Platzierung von Kopfzeilen in anderen Modi ergänzen.

Die übrigen ähnlichen Methoden sind bisher als verkürzte Methoden implementiert:

//+------------------------------------------------------------------+
//| Arrange tab headers at the bottom                                |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersBottom(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Arrange tab headers on the left                                  |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersLeft(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Arrange tab headers to the right                                 |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersRight(void)
  {
  
  }
//+------------------------------------------------------------------+


Die Methode, die alle Tabulatorüberschriften auf Padding-Werte setzt:

//+------------------------------------------------------------------+
//| Set all tab headers to Padding values                            |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderPadding(const int w,const int h)
  {
   this.m_header_padding_x=w;
   this.m_header_padding_y=h;
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      header.SetPadding(this.m_header_padding_x,this.m_header_padding_y,this.m_header_padding_x,this.m_header_padding_y);
     }
  }
//+------------------------------------------------------------------+

Die Methode empfängt die Werte, die zusätzlich zur Breite und Höhe der Kopfzeile im Einstellungsmodus Normalgröße addiert werden. Die an die Methode übergebenen Werte werden sofort in die entsprechenden Variablen eingesetzt. Als Nächstes erhalten wir eine Liste aller Kopfzeilen und setzen in einer Schleife durch die resultierende Liste jede Kopfzeile aus der Liste auf die an die Methode übergebenen Auffüllungswerte.


Die Methode setzt die Auffüllungswerte für alle Registerkartenfelder:

//+------------------------------------------------------------------+
//| Set all tab fields to Padding                                    |
//+------------------------------------------------------------------+
void CTabControl::SetFieldPadding(const int top,const int bottom,const int left,const int right)
  {
   this.m_field_padding_top=top;
   this.m_field_padding_bottom=bottom;
   this.m_field_padding_left=left;
   this.m_field_padding_right=right;
   CArrayObj *list=this.GetListFields();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabField *field=list.At(i);
      if(field==NULL)
         continue;
      field.SetPadding(left,top,right,bottom);
     }
  }
//+------------------------------------------------------------------+

Die Methode ist dieselbe wie oben. Aber hier werden die Auffüllungswerte von oben, unten, rechts und links an den Tabulatorrand übergeben. Diese Werte werden auf die entsprechenden Variablen und dann in einer Schleife auf jedes Registerkartenfeldobjekt gesetzt.


Die Methode, mit der die Registerkarte als ausgewählt festgelegt wird, wurde umgestaltet:

//+------------------------------------------------------------------+
//| Set the tab as selected                                          |
//+------------------------------------------------------------------+
void CTabControl::SetSelected(const int index)
  {
//--- Get the header by index and
   CTabHeader *header=this.GetTabHeader(index);
   if(header==NULL)
      return;
//--- set it to the "selected" state
   if(!header.State())
      header.SetState(true);
//--- save the index of the selected tab
   this.SetSelectedTabPageNum(index);
  }
//+------------------------------------------------------------------+

Nun werden alle Manipulationen mit dem Verschieben des Objekts in den Vordergrund und der Auswahl des entsprechenden Feldes in der oben betrachteten Methode SetState() der Objektklasse Tabulatorüberschrift durchgeführt.


Die Methode, die die Registerkarte als nicht ausgewählt kennzeichnet, wurde auf die gleiche Weise umgestaltet:

//+------------------------------------------------------------------+
//| Select the tab as released                                       |
//+------------------------------------------------------------------+
void CTabControl::SetUnselected(const int index)
  {
//--- Get the header by index and
   CTabHeader *header=this.GetTabHeader(index);
   if(header==NULL)
      return;
//--- set it to the "released" state
   if(header.State())
      header.SetState(false);
  }
//+------------------------------------------------------------------+


Die Methode, die die Anzahl der gebundenen Elemente auf der angegebenen Registerkarte zurückgibt:

//+------------------------------------------------------------------+
//| Get the number of bound elements in the specified tab            |
//+------------------------------------------------------------------+
int CTabControl::TabElementsTotal(const int tab_page)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.ElementsTotal() : 0);
  }
//+------------------------------------------------------------------+

Holt das Tabulatorfeldobjekt am angegebenen Index und gibt die Anzahl der daran gebundenen Objekte zurück.
Die Methode ermöglicht es herauszufinden, wie viele Objekte an die Registerkarte mit dem angegebenen Index angehängt sind.


Die Methode, die das gebundene Element nach Index in der Liste auf der angegebenen Registerkarte zurückgibt:

//+------------------------------------------------------------------+
//| Returns the bound element by index in the list                   |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::GetTabElement(const int tab_page,const int index)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetElement(index) : NULL);
  }
//+------------------------------------------------------------------+

Holt das Feldobjekt mit der angegebenen Indexnummer und gibt einen Zeiger auf das angehängte Element der Liste mit dem angegebenen Index zurück.
Die Methode ermöglicht es, einen Zeiger auf das gewünschte Element durch seinen Index auf der angegebenen Registerkarte zu erhalten.


Die Methode liefert die Liste der gebundenen Objekte auf der angegebenen Registerkarte nach einem Objekttyp:

//+------------------------------------------------------------------+
//| Return the list of bound controls by type                        |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CArrayObj *CTabControl::GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetListElementsByType(type) : NULL);
  }
//+------------------------------------------------------------------+

Ruft das Feldobjekt mit dem angegebenen Index ab und gibt die Liste der gebundenen Elemente mit dem angegebenen Typ zurück.
Die Methode ermöglicht es, die Liste der Elemente eines einzelnen angegebenen Typs aus der gewünschten Registerkarte zu erhalten.


Die Methode liefert die Anzahl der gebundenen Elemente auf der angegebenen Registerkarte durch einen Objekttyp:

//+------------------------------------------------------------------+
//| Get the list of bound elements by type                           |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
int CTabControl::TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.ElementsTotalByType(type) : 0);
  }
//+------------------------------------------------------------------+

Wir holen das Feldobjekt mit der angegebenen Nummer und geben die Anzahl der Elemente des angegebenen Typs zurück, die sich auf der Registerkarte befinden.
Die Methode ermöglicht es herauszufinden, wie viele Elemente des angegebenen Typs auf der angegebenen Registerkarte platziert sind.


Die Methode liefert das gebundene Element nach Index in der Liste auf der angegebenen Registerkarte nach Objekttyp:

//+------------------------------------------------------------------+
//| Get (by type) the bound element by index in the list             |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetElementByType(type,index) : NULL);
  }
//+------------------------------------------------------------------+

Ruft das Feldobjekt mit der angegebenen Indexnummer ab und gibt das Element des gewünschten Typs mit dem angegebenen Index in der Liste zurück.
Die Methode ermöglicht es, ein Element des gewünschten Typs anhand seiner Nummer aus der angegebenen Registerkarte zu erhalten.


Ganz am Ende der Methode zur Erstellung eines neuen grafischen Objekts ergänzen wir den Codeblock zur Erstellung eines Tabulatorfeldobjekts (extrakt) hinzu:

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(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 Panel-Objektklassendatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh erhält die Datei der Tabulatorfeld-Objektklasse:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "..\TabField.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+


Am Ende der Methode, die ein neues grafisches Objekt erstellt, platzieren wir den Codeblock für die Erstellung eines Tabulatorfeldobjekts:

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(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;
  }
//+------------------------------------------------------------------+

Nun ist es möglich, Objekte dieses Typs im Panel-Objekt und seinen Nachfolgern zu erstellen.


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh der Container-Basisklasse, und zwar in der Methode, die die Parameter für ein gebundenes Objekt festlegt, fügen wir dem Codeblock zum Festlegen der Parameter des Button-, TabHeader- und ListBoxItem-Objekts (Auszug aus dem Code) Parameter für das Tabulatorfeldobjekt hinzu:

      //--- For "Label", "CheckBox" and "RadioButton" WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
        //--- set the object text color depending on the one passed to the method:
        //--- either the container text color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        //--- Set the background color to transparent
        obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetOpacity(0,false);
        break;
      //--- For "Button", "TabHeader", TabField and "ListBoxItem" WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM     :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        obj.SetForeColor(this.ForeColor(),true);
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      //--- For "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :

Nachdem das Objekt in dieser Methode erstellt wurde, werden ihm die hier angegebenen Eigenschaften zugewiesen. Sie können später geändert werden.


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh der Objektklasse GroupBox schreiben wir die Methode zum Zeichnen eines Rahmens virtuell:

//+------------------------------------------------------------------+
//| GroupBox object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CGroupBox : public CContainer
  {
private:
//--- Draw a frame
   virtual void      DrawFrame(void);
//--- Create a new graphical object

Da wir eine solche Methode im Basisobjekt aller WinForms-Objekte als virtuell deklariert haben, sollten nun alle gleichnamigen Methoden in den Nachfolgeklassen ebenfalls virtuell gemacht werden, um sie korrekt umzudefinieren und von anderen Klassen darauf zuzugreifen.


In der Methode zum Erstellen eines neuen grafischen Objekts fügen wir auch den Codeblock zum Erstellen eines Tabulatorfeldobjekts hinzu:

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(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 Kollektionsklasse für grafische Objekte in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, und zwar in den Methoden zur Erstellung grafischer Elemente auf der Leinwand, wurden die Variablennamen, die „name“ enthalten, durch „descript“ ersetzt, da der Algorithmus zur Benennung von grafischen Elementen im vorherigen Artikel geändert wurde. Der Variablentyp ist string, sodass keine Fehler zu erwarten waren, aber ich beschloss dennoch, die Namen der formalen Methodenparameter der Klarheit halber zu ersetzen. Betrachten wir folgendes Beispiel.

//--- Create a graphical form object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling
   int               CreateFormHGradientCicle(const long chart_id,
                                              const int subwindow,
                                              const string descript,
                                              const int x,
                                              const int y,
                                              const int w,
                                              const int h,
                                              color &clr[],
                                              const uchar opacity,
                                              const bool movable,
                                              const bool activity,
                                              const bool shadow=false,
                                              const bool redraw=false)
                       {
                        int id=this.GetMaxID()+1;
                        CForm *obj=new CForm(chart_id,subwindow,descript,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetBackgroundColors(clr,true);
                        obj.SetBorderColor(clr[0],true);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.BorderColor(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,false,true,redraw);
                        return obj.ID();
                       }
 
//--- Create the 'GroupBox' WinForms graphical object on canvas on the specified chart and subwindow

Auf die übrigen Änderungen werde ich hier nicht eingehen. Sie sind identisch und wurden bereits in die unten beigefügte Bibliotheksdatei implementiert.

In der Methode, die nach Interaktionsobjekten sucht, fügen wir eine Prüfung auf Sichtbarkeit und Zugänglichkeit eines Objekts hinzu. Wenn es unsichtbar oder nicht verfügbar ist, kann ein solches Objekt nicht bearbeitet werden. Sie sollte für die Interaktion mit der Maus nicht verfügbar sein:

//+------------------------------------------------------------------+
//| 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 and the mouse cursor is located above the object, return the pointer to the found object
         if(obj==NULL)
            continue;
         if(!obj.IsVisible())
           {
            continue;
           }
         if(!obj.Enabled())
           {
            continue;
           }
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            CTabControl *tab_ctrl=obj;
            CForm *elm=tab_ctrl.SelectedTabPage();
            if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
               return elm;
           }
         if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return obj;
        }
     }
//--- Return the same pointer
   return form;
  }
//+------------------------------------------------------------------+

Wenn das Objekt ausgeblendet oder nicht verfügbar ist, wird es hier übersprungen. Wenn es sich um ein TabControl handelt, holen wir uns sich eine ausgewählte Registerkarte davon.
Wenn sich der Cursor über der ausgewählten Registerkarte befindet, wird der Zeiger auf das Objekt des Registerfelds zurückgegeben.


Bei der Methode zur Nachbearbeitung eines ehemals aktiven Formulars unter dem Cursor werden alle ausgeblendeten und nicht zugänglichen Objekte übersprungen. Sie sollten nicht angefasst werden:

//+------------------------------------------------------------------+
//| Post-processing of the former active form under the cursor       |
//+------------------------------------------------------------------+
void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- Get the main object the form is attached to
   CForm *main=form.GetMain();
   if(main==NULL)
      main=form;
//--- Get all the elements attached to the form
   CArrayObj *list=main.GetListElements();
   if(list==NULL)
      return;
   //--- In the loop by the list of received elements
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the pointer to the object
      CForm *obj=list.At(i);
      //--- if failed to get the pointer, move on to the next one in the list
      if(obj==NULL)
         continue;
      obj.OnMouseEventPostProcessing();
      //--- Create the list of interaction objects and get their number
      int count=obj.CreateListInteractObj();
      //--- In the loop by the obtained list
      for(int j=0;j<count;j++)
        {
         //--- get the next object
         CWinFormBase *elm=obj.GetInteractForm(j);
         if(elm==NULL || !elm.IsVisible() || !elm.Enabled())
            continue;
         //--- determine the location of the cursor relative to the object 
         //--- and call the mouse event handling method for the object
         elm.MouseFormState(id,lparam,dparam,sparam);
         elm.OnMouseEventPostProcessing();
        }
     }
   ::ChartRedraw(main.ChartID());
  }
//+------------------------------------------------------------------+


In der Datei \MQL5\Include\DoEasy\Engine.mqh des Hauptbibliotheksobjekts wird die Methode GetWFPanel(), die das Objekt nach Namen zurückgibt, in GetWFPanelByName() umbenannt, während die Methode GetWFPanel() das Objekt nach seiner Beschreibung zurückgibt:

//--- Return the WForm Panel object by object name on the current chart
   CPanel              *GetWFPanelByName(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object according to the description of the object on the current chart
   CPanel              *GetWFPanel(const string descript)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_DESCRIPTION,descript,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by chart ID and object name

Da beide Methoden formale Parameter desselben Typs hatten, ist eine Methodenüberladung in dieser Situation nicht möglich. Aus diesem Grund habe ich eine der Methoden umbenannt.

Genau wie in der Kollektionsklasse der grafischen Elemente wurden alle Instanzen von „name“ in den formalen Parametern der Methoden, die WinForms-Objekte erzeugen, in „descript“ umbenannt.

Zum Beispiel:

//--- Create the WinForm Element object
   CGCnvElement        *CreateWFElement(const long chart_id,
                                        const int subwindow,
                                        const string descript,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h,
                                        color &clr[],
                                        const uchar opacity,
                                        const bool v_gradient=true,
                                        const bool c_gradient=false,
                                        const bool redraw=false)
                          {
                           //--- Get the created object ID
                           int obj_id=
                             (
                              //--- In case of a vertical gradient:
                              v_gradient ?
                                (
                                 //--- if not a cyclic gradient, create an object with the vertical gradient filling
                                 !c_gradient ? this.m_graph_objects.CreateElementVGradient(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) :
                                 //--- otherwise, create an object with the cyclic vertical gradient filling
                                 this.m_graph_objects.CreateElementVGradientCicle(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw)
                                ) :
                              //--- If this is not a vertical gradient:
                              !v_gradient ?
                                (
                                 //--- if not a cyclic gradient, create an object with the horizontal gradient filling
                                 !c_gradient ? this.m_graph_objects.CreateElementHGradient(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) :
                                 //--- otherwise, create an object with the cyclic horizontal gradient filling
                                 this.m_graph_objects.CreateElementHGradientCicle(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw)
                                ) :
                              WRONG_VALUE
                             );
                           //--- return the pointer to an object by its ID
                           return this.GetWFElement(obj_id);
                          }
//--- Create the WinForm Element object in the specified subwindow on the current chart

Das sind alle Änderungen und Verbesserungen, die ich für den aktuellen Artikel geplant habe.


Test

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

Erstellen wir die neuen Enumerationen für die bedingte Kompilierung, um den Modus für die Einstellung der Größe der Header-Tabs wählen zu können:

enum ENUM_ELEMENT_ALIGNMENT
  {
   ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP,                    // Top
   ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM,              // Bottom
   ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT,                  // Left
   ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT,                // Right
  };
enum ENUM_ELEMENT_TAB_SIZE_MODE
  {
   ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,      // Fit to tab header text width
   ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED,        // Fixed size
   ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL,          // Fit TabControl Width
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Increase only
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Increase and decrease
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // No frame
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple frame
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat frame
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (convex)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (concave)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Unchecked
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Checked
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Undefined
  };
enum ENUM_ELEMENT_ALIGNMENT
  {
   ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP,                    // Top
   ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM,              // Bottom
   ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT,                  // Left
   ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT,                // Right
  };
enum ENUM_ELEMENT_TAB_SIZE_MODE
  {
   ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,      // By tab header width
   ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED,        // Fixed size
   ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL,          // By TabControl width
  };
#endif 
//--- input parameters


In den EA-Eingaben fügen wir eine neue Variable hinzu, die den Modus für die Einstellung der Größe der Registerkartenüberschriften festlegt:

//--- 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                          InpListBoxMColumn    =  true;                   // ListBox MultiColumn flag
sinput   bool                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
//sinput   ENUM_ELEMENT_ALIGNMENT        InpHeaderAlignment   =  ELEMENT_ALIGNMENT_TOP;  // TabHeader Alignment
sinput   ENUM_ELEMENT_TAB_SIZE_MODE    InpTabPageSizeMode   =  ELEMENT_TAB_SIZE_MODE_NORMAL; // TabHeader Size Mode
//--- global variables


Die Erstellung von WinForms-Objekten in OnInit() des EA sieht nun wie folgt aus:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions

//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,400,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
   if(pnl!=NULL)
     {
      //--- Set Padding to 4
      pnl.SetPaddingAll(4);
      //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
      pnl.SetMovable(InpMovable);
      pnl.SetAutoSize(InpAutoSize,false);
      pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);

      //--- In the loop, create 2 bound panel objects
      CPanel *obj=NULL;
      for(int i=0;i<2;i++)
        {
         //--- create the panel object with calculated coordinates, width of 90 and height of 40
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(prev==NULL ? xb : xb+prev.Width()+20);
         int y=0;
         if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false))
           {
            obj=pnl.GetElement(i);
            if(obj==NULL)
               continue;
            obj.SetBorderSizeAll(3);
            obj.SetBorderStyle(FRAME_STYLE_BEVEL);
            obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),4*i),true);
            obj.SetForeColor(clrRed,true);
            //--- Calculate the width and height of the future text label object
            int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight();
            int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom();
            //--- Create a text label object
            obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,w,h,clrNONE,255,false,false);
            //--- Get the pointer to a newly created object
            CLabel *lbl=obj.GetElement(0);
            if(lbl!=NULL)
              {
               //--- If the object has an even or zero index in the list, set the default text color for it
               if(i % 2==0)
                  lbl.SetForeColor(CLR_DEF_FORE_COLOR,true);
               //--- If the object index in the list is odd, set the object opacity to 127
               else
                  lbl.SetForeColorOpacity(127);
               //--- Set the font Black width type and
               //--- specify the text alignment from the EA settings
               lbl.SetFontBoldType(FW_TYPE_BLACK);
               lbl.SetTextAlign(InpTextAlign);
               lbl.SetAutoSize((bool)InpTextAutoSize,false);
               //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol 
               lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK));
               //--- Set the frame width, type and color for a text label and update the modified object
               lbl.SetBorderSizeAll(1);
               lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle);
               lbl.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
               lbl.Update(true);
              }
           }
        }
      //--- Create the WinForms GroupBox1 object
      CGroupBox *gbox1=NULL;
      //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox1
      int w=pnl.GetUnderlay().Width();
      int y=obj.BottomEdgeRelative()+6;
      //--- If the attached GroupBox object is created
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0,y,200,150,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects
         gbox1=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0);
         if(gbox1!=NULL)
           {
            //--- set the "indented frame" type, the frame color matches the main panel background color,
            //--- while the text color is the background color of the last attached panel darkened by 1
            gbox1.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox1.SetBorderColor(pnl.BackgroundColor(),true);
            gbox1.SetForeColor(gbox1.ChangeColorLightness(obj.BackgroundColor(),-1),true);
            gbox1.SetText("GroupBox1");
            //--- Create the CheckBox object
            gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false);
            //--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects
            CCheckBox *cbox=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,0);
            //--- If CheckBox is created and the pointer to it is received
            if(cbox!=NULL)
              {
               //--- Set the CheckBox parameters from the EA inputs
               cbox.SetAutoSize((bool)InpCheckAutoSize,false);
               cbox.SetCheckAlign(InpCheckAlign);
               cbox.SetTextAlign(InpCheckTextAlign);
               //--- Set the displayed text, frame style and color, as well as checkbox status
               cbox.SetText("CheckBox");
               cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
               cbox.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
               cbox.SetChecked(true);
               cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState);
              }
            //--- Create 4 RadioButton WinForms objects
            CRadioButton *rbtn=NULL;
            for(int i=0;i<4;i++)
              {
               //--- Create the RadioButton object
               int yrb=(rbtn==NULL ? cbox.BottomEdgeRelative() : rbtn.BottomEdgeRelative());
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,yrb+4,50,20,clrNONE,255,true,false);
               //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects
               rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i);
               //--- If RadioButton1 is created and the pointer to it is received
               if(rbtn!=NULL)
                 {
                  //--- Set the RadioButton parameters from the EA inputs
                  rbtn.SetAutoSize((bool)InpCheckAutoSize,false);
                  rbtn.SetCheckAlign(InpCheckAlign);
                  rbtn.SetTextAlign(InpCheckTextAlign);
                  //--- Set the displayed text, frame style and color, as well as checkbox status
                  rbtn.SetText("RadioButton"+string(i+1));
                  rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
                  rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
                  rbtn.SetChecked(!i);
                  rbtn.SetGroup(2);
                 }
              }
            //--- Create 3 Button WinForms objects
            CButton *butt=NULL;
            for(int i=0;i<3;i++)
              {
               //--- Create the Button object
               int ybtn=(butt==NULL ? 12 : butt.BottomEdgeRelative()+4);
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+20,ybtn,78,18,clrNONE,255,true,false);
               //--- get the pointer to the Button object by its index in the list of bound Button type objects
               butt=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i);
               //--- If Button is created and the pointer to it is received
               if(butt!=NULL)
                 {
                  //--- Set the Button parameters from the EA inputs
                  butt.SetAutoSize((bool)InpButtonAutoSize,false);
                  butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false);
                  butt.SetTextAlign(InpButtonTextAlign);
                  //--- Set the text color, as well as frame style and color
                  butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,2),true);
                  butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle);
                  butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-10),true);
                  //--- Set the 'toggle' mode depending on the settings
                  butt.SetToggleFlag(InpButtonToggle);
                  //--- Set the displayed text on the button depending on the 'toggle' flag
                  string txt="Button"+string(i+1);
                  if(butt.Toggle())
                     butt.SetText("Toggle-"+txt);
                  else
                     butt.SetText(txt);
                  if(i<2)
                    {
                     butt.SetGroup(2);
                     if(butt.Toggle())
                       {
                        butt.SetBackgroundColorMouseOver(butt.ChangeColorLightness(butt.BackgroundColor(),-5));
                        butt.SetBackgroundColorMouseDown(butt.ChangeColorLightness(butt.BackgroundColor(),-10));
                        butt.SetBackgroundStateOnColor(C'0xE2,0xC5,0xB1',true);
                        butt.SetBackgroundStateOnColorMouseOver(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-5));
                        butt.SetBackgroundStateOnColorMouseDown(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-10));
                       }
                    }
                 }
              }
            //--- Create 2 RadioButton WinForms objects
            rbtn=NULL;
            for(int i=0;i<2;i++)
              {
               //--- Create the RadioButton object
               int yrb=(rbtn==NULL ? butt.BottomEdgeRelative() : rbtn.BottomEdgeRelative());
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,butt.CoordXRelative()-4,yrb+3,50,20,clrNONE,255,true,false);
               //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects
               rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i+4);
               //--- If RadioButton1 is created and the pointer to it is received
               if(rbtn!=NULL)
                 {
                  //--- Set the RadioButton parameters from the EA inputs
                  rbtn.SetAutoSize((bool)InpCheckAutoSize,false);
                  rbtn.SetCheckAlign(InpCheckAlign);
                  rbtn.SetTextAlign(InpCheckTextAlign);
                  //--- Set the displayed text, frame style and color, as well as checkbox status
                  rbtn.SetText("RadioButton"+string(i+5));
                  rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
                  rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
                  rbtn.SetChecked(!i);
                  rbtn.SetGroup(3);
                 }
              }
           }
        }
      
      //--- Create the GroupBox2 WinForms object
      CGroupBox *gbox2=NULL;
      //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2
      w=gbox1.Width()-1;
      int x=gbox1.RightEdgeRelative()+1;
      int h=gbox1.BottomEdgeRelative()-6;
      //--- If the attached GroupBox object is created
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects
         gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1);
         if(gbox2!=NULL)
           {
            //--- set the "indented frame" type, the frame color matches the main panel background color,
            //--- while the text color is the background color of the last attached panel darkened by 1
            gbox2.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox2.SetBorderColor(pnl.BackgroundColor(),true);
            gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true);
            gbox2.SetText("GroupBox2");
            
            //--- Create the TabControl object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false);
            //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type
            CTabControl *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
            //--- If TabControl is created and the pointer to it is received
            if(tab_ctrl!=NULL)
              {
               //--- Set the location of the tab headers on the element and the tab text, as well as create nine tabs
               tab_ctrl.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
               tab_ctrl.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP/*(ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment*/);
               tab_ctrl.SetMultiline(true);
               tab_ctrl.SetHeaderPadding(6,0);
               tab_ctrl.CreateTabPages(9,0,50,16,TextByLanguage("Вкладка","TabPage"));

               //--- Create the CheckedListBox object on the first tab
               tab_ctrl.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false);
               //--- get the pointer to the CheckedListBox object from the first tab by its index in the list of bound objects of the CheckBox type
               CCheckedListBox *check_lbox=tab_ctrl.GetTabElementByType(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0);
               //--- If CheckedListBox is created and the pointer to it is received
               if(check_lbox!=NULL)
                 {
                  check_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  check_lbox.SetMultiColumn(InpListBoxMColumn);
                  check_lbox.SetColumnWidth(0);
                  check_lbox.CreateCheckBox(4,66);
                 }
               
               //--- Create the ButtonListBox object on the second tab
               tab_ctrl.CreateNewElement(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,12,160,30,clrNONE,255,true,false);
               //--- get the pointer to the ButtonListBox object from the first tab by its index in the list of attached objects of the Button type
               CButtonListBox *butt_lbox=tab_ctrl.GetTabElementByType(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0);
               //--- If ButtonListBox is created and the pointer to it is received
               if(butt_lbox!=NULL)
                 {
                  butt_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  butt_lbox.SetMultiColumn(true);
                  butt_lbox.SetColumnWidth(0);
                  butt_lbox.CreateButton(4,66,16);
                  butt_lbox.SetMultiSelect(InpButtListMSelect);
                  butt_lbox.SetToggle(InpButtonToggle);
                  for(int i=0;i<butt_lbox.ElementsTotal();i++)
                    {
                     butt_lbox.SetButtonGroup(i,(i % 2==0 ? butt_lbox.Group()+1 : butt_lbox.Group()+2));
                     butt_lbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false));
                    }
                 }
               
               //--- Create the ListBox object on the third tab
               int lbw=146;
               if(!InpListBoxMColumn)
                  lbw=100;
               tab_ctrl.CreateNewElement(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,4,12,lbw,60,clrNONE,255,true,false);
               //--- get the pointer to the ListBox object from the third tab by its index in the list of attached objects of the ListBox type
               CListBox *list_box=tab_ctrl.GetTabElementByType(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0);
               //--- If ListBox has been created and the pointer to it has been received
               if(list_box!=NULL)
                 {
                  list_box.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  list_box.SetMultiColumn(InpListBoxMColumn);
                  list_box.CreateList(8,68);
                 }
               
               //--- On the remaining tabs (3 - 8), place text labels with the name of the tab
               for(int i=3;i<9;i++)
                 {
                  CTabField *field=tab_ctrl.GetTabField(i);
                  if(field==NULL)
                     continue;
                  tab_ctrl.CreateNewElement(i,GRAPH_ELEMENT_TYPE_WF_LABEL,1,1,field.Width()-2,field.Height()-2,clrNONE,255,true,false);
                  CLabel *label=tab_ctrl.GetTabElementByType(i,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                  if(label!=NULL)
                    {
                     label.SetTextAlign(ANCHOR_CENTER);
                     label.SetText(tab_ctrl.GetTabHeader(i).Text());
                    }
                 }
              }
           }
        }
      //--- Redraw all objects according to their hierarchy
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Ich hoffe, dass der gesamte Ablauf der Objekterstellung in den Kommentaren zum Code klar beschrieben ist. Hier erstellen wir ein TabControl mit neun Registerkarten auf dem zweiten GroupBox-Container — speziell um zu prüfen, wie sie in Reihen angeordnet werden. Auf den ersten drei Registerkarten legen wir die Objekte an, die wir zuvor im Container GroupBox2 erstellt haben. Diese drei Steuerelemente werden nun jeweils auf einer eigenen Registerkarte platziert. Die übrigen Registerkarten werden mit Textbeschriftungen versehen, die eine Beschreibung der Registerkarte enthalten, die dem Text in den Kopfzeilen entnommen ist.

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


Die Erstellung der Objekte dauert recht lange. Bald wird es notwendig sein, die Logik der Anzeige von Objekten während ihrer Massenerstellung zu ändern. Wir werden uns später damit befassen. Wenn Sie eine feste Größe für die Registerkartenüberschriften und eine Größe, die sich an die Schriftbreite anpasst, wählen, sehen Sie, dass die Größen der Registerkarten unterschiedlich sind. Die Auswahl der gewünschten Registerkarte und die Neuanordnung der Registerkartenreihen funktionieren korrekt. Die Objekte auf den Registerkarten sind für die Mausinteraktion verfügbar. Bis jetzt ist alles korrekt, sodass wir mit der Entwicklung der Kontrollfunktionen fortfahren können.


Was kommt als Nächstes?

Im nächsten Artikel werde ich meine Arbeit an dem TabControl WinForms 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. Schreiben Sie Ihre Fragen, Kommentare und Vorschläge im Kommentarteil.

Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

DoEasy. Steuerung (Teil 10): WinForms-Objekte — Animieren der Nutzeroberfläche
DoEasy. Steuerung (Teil 11): WinForms Objekte — Gruppen, das WinForms-Objekt CheckedListBox
DoEasy. Steuerung (Teil 12): Basislistenobjekt, ListBox und ButtonListBox WinForms-Objekte
DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl
DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt



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

Beigefügte Dateien |
MQL5.zip (4432.19 KB)
DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container
In diesem Artikel werde ich die Entwicklung von TabControl fortsetzen und die Anordnung von Tabulatorüberschriften auf allen vier Seiten des Steuerelements für alle Modi der Einstellung der Größe der Überschriften implementieren: Normal, Fixed und Fill To Right (rechts auffüllend).
Einen technischen Indikator selber machen Einen technischen Indikator selber machen
In diesem Artikel gehe ich auf die Algorithmen ein, mit denen Sie Ihren eigenen technischen Indikator erstellen können. Sie werden lernen, wie man mit sehr einfachen Ausgangsannahmen ziemlich komplexe und interessante Ergebnisse erzielen kann.
Neuronale Netze leicht gemacht (Teil 24): Verbesserung des Instruments für Transfer Learning Neuronale Netze leicht gemacht (Teil 24): Verbesserung des Instruments für Transfer Learning
Im vorigen Artikel haben wir ein Tool zum Erstellen und Bearbeiten der Architektur neuronaler Netze entwickelt. Heute werden wir die Arbeit an diesem Instrument fortsetzen. Wir werden versuchen, sie nutzerfreundlicher zu gestalten. Dies mag ein Schritt weg von unserem Thema sein. Aber ist es nicht so, dass ein gut organisierter Arbeitsplatz eine wichtige Rolle bei der Erreichung dieses Ziels spielt?
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 27): Der Zukunft entgegen (II) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 27): Der Zukunft entgegen (II)
Gehen wir nun zu einem vollständigeren Auftragssystem direkt auf dem Chart über. In diesem Artikel zeige ich einen Weg, das Auftragssystem zu reparieren, oder besser gesagt, es intuitiver zu gestalten.