English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt

DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt

MetaTrader 5Beispiele | 21 Oktober 2022, 09:26
242 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Im letzten Artikel sind wir bei der Entwicklung des TabControl WinForms-Objekts auf eine Beschränkung der Länge des Namens eines grafischen Elements gestoßen, die uns daran hinderte, das Objekt vollständig zu erstellen. Der Name jedes untergeordneten, grafischen Elements, das im übergeordneten Element enthalten ist, enthält einen Verweis auf sein übergeordnetes Element mit der gesamten Hierarchie aller zugehörigen grafischen Steuerelemente. Der Name jedes nachfolgenden Objekts in dieser Kette war länger als der Name des vorherigen Objekts. Infolgedessen stieß ich auf eine Beschränkung der Länge des Namens einer grafischen Ressource auf 63 Zeichen. Hier werde ich einen anderen Algorithmus für die Benennung von grafischen Elementen implementieren, um diesen Nachteil zu beseitigen: Jedes neue Objekt desselben Typs wird in seinem Namen den Namen des Programms, den Namen des grafischen Elementtyps und die Anzahl der bereits vorhandenen Elemente dieses Typs enthalten, die im Programm bei der Erstellung von GUI-Elementen erstellt wurden.

Bei der Erstellung von GUI-Elementen für das Testprogramm dieses Artikels erhielten wir beispielsweise die folgende Liste von grafischen Elementen (nur der erste Teil aller konstruierten Elemente ist sichtbar, aber das reicht aus, um das akzeptierte Konzept zu verstehen):


Somit gibt es jetzt keine Einschränkungen mehr für die Verschachtelung von Objekten bei der Erstellung von Steuerelementen. Anstatt eine Hierarchie im Namen eines grafischen Elements anzuzeigen, werden wir einfach den Elementindex mit dem Programmnamen und dem Elementtyp verwenden.

Wir sind nicht in der Lage, die ungefähre Position eines grafischen Elements in der Hierarchie der Ketten verwandter Objekte anhand seines Namens zu erkennen. Aber wir haben derzeit keine Beschränkung für die Länge des Namens. Um irgendwie verstehen zu können, um welche Art von Objekt es sich handelt, werden wir eine neue Eigenschaft zu den String-Eigenschaften von grafischen Elementen hinzufügen — die grafische Elementbeschreibung. Dadurch wird die Frage geklärt, welchen Zweck das grafische Element hat und wie es in Ihrem Programm genutzt werden kann. Wenn wir zum Beispiel ein grafisches Element einer Umschalttaste erstellen, geben wir in die Beschreibung etwas wie „eine Taste zum Umschalten der Handelsrichtung“ ein. Diese Beschreibung kann verwendet werden, um direkt auf dieses Steuerelement im Programm zu verweisen, was viel besser ist als die Verwendung eines „vagen“ Namens wie „MeinProgramm_Elm00_Elm01_Elm00“, wie es vorher der Fall war...

Neben der Erstellung eines neuen Algorithmus für die Benennung von grafischen Elementen werde ich auch die Entwicklung des TabControls fortsetzen. Ich werde nämlich das TabHeader-Objekt erstellen, das eine Registerkartenüberschrift beschreibt. Dieses Objekt muss in der Lage sein, in einer Gruppe mit anderen ähnlichen Objekten — den Kopfzeilen anderer Registerkartenobjekte — zu arbeiten. Wenn dieses Element ausgewählt wird, sollte es leicht in der Größe wachsen können, und gleichzeitig sollte es die Position des Satzes von Titeln aller Registerkarten auf dem TabControl berücksichtigen — oben, unten, links oder rechts, und je nach ihrer Position, zeichnen wir einen Rahmen nur an der richtigen Stelle des Objekts. Befindet sich beispielsweise der Registerkartenkopf oben auf dem TabControl, dann sollte der Rahmen, der den Registerkartentitel umreißt, nur auf drei Seiten gezeichnet werden — links, oben und rechts. Die Unterseite des Registerkartenkopfes berührt das Registerkartenfeld, auf dem die Objekte dieser Registerkarte platziert werden sollen. Die Kontaktstelle sollte keine gezeichnete Umrandung haben, so dass der Tab-Titel und das Tab-Feld eine Einheit ohne sichtbare Trennung bilden.

Heute werde ich die beschriebene Handhabung des Abgrenzens der Ränder in Abhängigkeit von der Position der Registerkartenköpfe nur für die Registerkartenkopfobjekte implementieren. Im nächsten Artikel werde ich mich mit dem Zeichnen der Ränder des Registerkartenfelds und dem Platzieren anderer Steuerelemente befassen.


Verbesserung der Bibliotheksklassen

Einige Steuerelemente verwenden vorhandene Steuerelemente für ihre Arbeit, z. B. verwendet ListBox eine verbesserte Schaltfläche zum Zeichnen ihrer Kollektion (Elemente). Um sie zu implementieren, müssen wir ein neues Objekt erstellen, das vom Element Button abgeleitet ist, und die erforderliche Funktionalität hinzufügen. Es wäre besser, dieses und einige andere ähnliche Objekte in eine separate Kategorie von Hilfsobjekten aufzunehmen, die im Stammverzeichnis der Kontrollkategorien und nicht in deren Ordnern untergebracht werden.

In \MQL5\Include\DoEasy\Defines.mqh, und zwar in der Enumeration der grafischen Elementtypen, fügen wir einen neuen Typ des Containerobjekts TabControl sowie zwei zusätzliche Steuerelemente ListBoxItem und TabHeader in der neuen Kategorie 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
  };
//+------------------------------------------------------------------+


Hinzufügen einer neuen Eigenschaft zur Enumeration der String-Eigenschaften von Canvas-basierten grafischen Elementen — Beschreibung eines grafischen Elements, und die Erhöhung der Gesamtzahl der String-Eigenschaften von 3 auf 4:

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


Hinzufügen des Sortierens nach der neuen Eigenschaft ganz am Ende der Liste der möglichen Kriterien für die Sortierung von grafischen Elementen auf der Leinwand:

//+------------------------------------------------------------------+
//| 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_ALIGNMENT,                // Sort by the location of tabs inside the control
   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 grafische Elemente nach einer neuen Eigenschaft auswählen und sortieren.


In \MQL5\Include\DoEasy\DataData.mqh fügen wir die neuen Nachrichtenindizes hinzu und entfernen den unnötigen Nachrichtenindex:

//--- WinForms standard
   MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,             // WinForms base standard control
   MSG_GRAPH_ELEMENT_TYPE_WF_LABEL,                   // Label control
   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                // CheckBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,             // RadioButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON,                  // Button control
   MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,       // Base list object of Windows Forms elements
   MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                // ListBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,           // ListBox control collection object
   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,        // CheckedListBox control
   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_PAGE,                // TabPage control
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // TabControl
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Graphical object does not belong to a program

...

//--- String properties of graphical elements
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Graphical element object name
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Graphical resource name
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Graphical element text
   MSG_CANV_ELEMENT_PROP_DESCRIPTION,                 // Graphical element description
  };
//+------------------------------------------------------------------+


und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen: Löschen auch des entfernten Indextextes:

//--- WinForms standard
   {"Базовый стандартный элемент управления WinForms","Basic Standard WinForms Control"},
   {"Элемент управления \"Label\"","Control element \"Label\""},
   {"Элемент управления \"CheckBox\"","Control element \"CheckBox\""},
   {"Элемент управления \"RadioButton\"","Control element \"RadioButton\""},
   {"Элемент управления \"Button\"","Control element \"Button\""},
   {"Базовый объект-список Windows Forms элементов","Basic Windows Forms List Object"},
   {"Элемент управления \"ListBox\"","Control element \"ListBox\""},
   {"Объект коллекции элемента управления ListBox","Collection object of the ListBox control"},
   {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""},
   {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""},
   {"Заголовок вкладки","Tab header"},
                                                                                             
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

...

//--- String properties of graphical elements
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},
   {"Описание графического элемента","Description of the graphic element"},
  };
//+---------------------------------------------------------------------+


Implementieren wir in der Dienstfunktionsdatei \MQL5\Include\DoEasy\Services\DELibDELib.mqh die Funktion zum Erstellen und Zurückgeben der Beschreibung des grafischen Elementtyps für seine spätere Verwendung in der Bibliothek:

//+------------------------------------------------------------------+
//| Return the graphical object type as string                       |
//+------------------------------------------------------------------+
string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   ushort array[];
   int total=StringToShortArray(StringSubstr(::EnumToString(type),18),array);
   for(int i=0;i<total-1;i++)
     {
      if(array[i]==95)
        {
         i+=1;
         continue;
        }
      else
         array[i]+=0x20;
     }
   string txt=ShortArrayToString(array);
   StringReplace(txt,"_Wf_Base","WFBase");
   StringReplace(txt,"_Wf_","");
   StringReplace(txt,"_Obj","");
   StringReplace(txt,"_","");
   StringReplace(txt,"Groupbox","GroupBox");
   return txt;
  }
//+------------------------------------------------------------------+

Der Algorithmus sieht folgendermaßen aus: Wir übergeben der Funktion den Typ des grafischen Elements, der für die Beschreibung erforderlich ist. Als Nächstes wird in der Zeile

int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);

die Anzahl der Teilzeichenfolgen abgerufen, die aus dem Namen der Enumerationskonstante extrahiert wurden.

Sehen wir uns dies am Beispiel der Konstante GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX an.

Wir konvertieren die Enumerationskonstante „GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX“ in einen Text:

EnumToString(type)

Aus dem erhaltenen Text „GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX“ extrahieren wir die Zeichenkette „_WF_CHECKED_LIST_BOX „, die bei dem Zeichen 18 beginnt:

StringSubstr(EnumToString(type),18)

Die erhaltene Zeichenkette „_WF_CHECKED_LIST_BOXwird Zeichen für Zeichen in das ushort-Array kopiert , wobei die Anzahl der kopierten Zeichen erhalten wird:

int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);

Als Ergebnis enthält array[] die Codes jedes Stringsymbols „_WF_CHECKED_LIST_BOX“.


Jetzt müssen wir nach jedem „_“-Zeichen einen Großbuchstaben stehen lassen, während alle anderen Zeichen klein geschrieben werden.

Wir erledigen das in der Schleife durch das Zeichenarray:

   for(int i=0;i<total-1;i++)
     {
      if(array[i]==95)
        {
         i+=1;
         continue;
        }
      else
         array[i]+=0x20;
     }

Das erste Zeichen der Zeichenkette und des Arrays ist „_“. Sobald wir den Zeichencode (95) im Array gefunden haben, setzen wir den Schleifenindex auf das nächstfolgende Symbol.

In der Zeichenfolge „_WF_CHECKED_LIST_BOX“ sind diese Zeichen farblich gekennzeichnet.
Nachdem der Schleifenindex für den nächsten Zeichencode im Array gesetzt wurde, gehen wir zur nächsten Iteration über und überspringen dabei das Zeichen, das unverändert bleiben soll. Beim else-Operator addieren wir 32 zum Zeichencode im Array, wodurch das Zeichen klein geschrieben wird.

Nach der gesamten Schleife enthält das Array also die Zeichencodes der Zeichenkette „_Wf_Checked_List_Box“, die wir in die Zeichenkette umwandeln:

string txt=ShortArrayToString(array);

Als Nächstes ersetzen wir einfach die angegebenen Vorkommen von Zeichenketten durch die, die wir in der erhaltenen Zeichenkette benötigen, und geben die letzte Zeile zurück:

   StringReplace(txt,"_Wf_Base","WFBase");
   StringReplace(txt,"_Wf_","");
   StringReplace(txt,"_Obj","");
   StringReplace(txt,"_","");
   StringReplace(txt,"Groupbox","GroupBox");
   return txt;

Wir werden diese neue Funktion verwenden, um den Dateinamen aus dem grafischen Elementtyp zu erhalten.


In der Datei \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh des grafischen Basisobjekts, und zwar in der Methode, die die Beschreibung des grafischen Elementtyps zurückgibt, fügen wir einen neuen Typ hinzu und entfernen den überflüssigen:

//+------------------------------------------------------------------+
//| 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_HEADER          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)         :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_PAGE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE)           :
      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)    :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+


Um den Namen des Objekts zu erhalten, benötigen wir die obige Funktion, die den Namen des erstellten grafischen Elements entsprechend seinem Typ zurückgibt. Dies reicht jedoch nicht aus, um einen vollwertigen Objektnamen zu erzeugen. Wir müssen die Anzahl der bereits vorhandenen grafischen Elemente desselben Typs (die in einem Symbolchart und seinem Unterfenster vorhanden sind) zu der von der Funktion erhaltenen Zeichenfolge hinzufügen.

In der Objektklasse \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh für grafische Elemente, und zwar in ihrem geschützten Abschnitt, sind zwei Methoden zu deklarieren — eine, die die Anzahl der grafischen Elemente nach Typ zurückgibt, und eine, die den Namen des grafischen Elements nach Typ erstellt und zurückgibt:

//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
//--- Copy the color array to the specified background color array
   void              CopyArraysColors(color &array_dst[],const color &array_src[],const string source);
   
//--- Return the number of graphical elements by type
   int               GetNumGraphElements(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:

Auf die Implementierung von Methoden werde ich später noch eingehen.

Im privaten Bereich fügen wir neue Felder in die Objektstruktur ein:

private:
   int               m_shift_coord_x;                          // Offset of the X coordinate relative to the base object
   int               m_shift_coord_y;                          // Offset of the Y coordinate relative to the base object
   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type
      //---...
      //---...
      bool           button_toggle;                            // Toggle flag of the control featuring a button
      bool           button_state;                             // Status of the Toggle control featuring a button
      bool           button_group_flag;                        // Button group flag
      bool           multicolumn;                              // Horizontal display of columns in the ListBox control
      int            column_width;                             // Width of each ListBox control column
      bool           tab_multiline;                            // Several lines of tabs in TabControl
      int            tab_alignment;                            // Location of tabs inside the control
      int            alignment;                                // Location of the object inside the control
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
      uchar          text[256];                                // Graphical element text
      uchar          descript[256];                            // Graphical element description
     };
   SData             m_struct_obj;                             // Object structure
   uchar             m_uchar_array[];                          // uchar array of the object structure

Wir brauchen sie, um das Objekt korrekt in einer Datei zu speichern und aus ihr zu lesen. Dies sind die neuen Eigenschaften des grafischen Elements, die ich im aktuellen Artikel oder früher hinzugefügt habe, aber vergessen habe, hier zu schreiben.

Aus der Deklaration der Methode, die ein neues grafisches Element erstellt, entfernen wir den formalen Parameter, in dem der Name des erstellten Objekts an die Methode übergeben wurde:

//--- Create the element
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const bool redraw=false);

Jetzt wird der Name des Objekts nicht mehr an die Methode übergeben, sondern anhand seines Typs in der Methode erstellt.

Die Variable „name“ wurden bereits in allen bisher implementierten grafischen Elementobjektklassen durch „descript“ ersetzt, und zwar in den formalen Parametern aller ihrer Konstruktoren. In der aktuellen Datei ist das zum Beispiel so:

protected:
//--- Protected constructor
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const long    chart_id,
                                  const int     wnd_num,
                                  const string  descript,
                                  const int     x,
                                  const int     y,
                                  const int     w,
                                  const int     h);
public:
//--- (1) Set and (2) return the X coordinate shift relative to the base object
   void              SetCoordXRelative(const int value)                                { this.m_shift_coord_x=value;                }
   int               CoordXRelative(void)                                        const { return this.m_shift_coord_x;               }
//--- (1) Set and (2) return the Y coordinate shift relative to the base object
   void              SetCoordYRelative(const int value)                                { this.m_shift_coord_y=value;                }
   int               CoordYRelative(void)                                        const { return this.m_shift_coord_y;               }
   
//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Parametric constructor
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const int     element_id,
                                  const int     element_num,
                                  const long    chart_id,
                                  const int     wnd_num,
                                  const string  descript,
                                  const int     x,
                                  const int     y,
                                  const int     w,
                                  const int     h,
                                  const color   colour,
                                  const uchar   opacity,
                                  const bool    movable=true,
                                  const bool    activity=true,
                                  const bool    redraw=false);

Jetzt übergeben wir den Namen des erstellten Objekts nicht an den Klassenkonstruktor. Die Bibliothek erstellt einen neuen Namen für ein neues Objekt auf der Grundlage seines Typs. Daher übergeben wir dem Konstruktor nicht den Namen des Objekts, sondern seine Beschreibung, die wir selbst zuweisen können, um anhand dieser Beschreibung auf das erstellte Objekt zu verweisen. Alle diese Änderungen wurden bereits an allen Klassen aller WinForms-Objekte vorgenommen, und wir werden sie hier nicht weiter berücksichtigen — sie können in den Dateien im Anhang des Artikels gefunden werden.

Die Einstellung eines grafischen Elementtyps wurde ebenfalls geändert. Zuvor habe ich den Elementtyp zweimal in jedem Konstruktor jeder WinForms-Objektklasse festgelegt. Zunächst wurde der Typ im Basisobjekt der grafischen Bibliothekselemente (in dessen Variable m_type_element) festgelegt:

void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type;   }

während die zweite Zeichenfolge denselben Typ in den Objekteigenschaften festlegt.

Vereinfachen wir dies, indem wir die öffentliche Methode zum Setzen (und Zurückgeben) des Objekttyps in beide Werte auf einmal erstellen — Variable und Eigenschaft:

//--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element,
//--- (5) all shifts of the active area edges relative to the element, (6) opacity
   void              SetActiveAreaLeftShift(const int value)   { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value));       }
   void              SetActiveAreaRightShift(const int value)  { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value));      }
   void              SetActiveAreaTopShift(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value));        }
   void              SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value));     }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetOpacity(const uchar value,const bool redraw=false);
   
//--- (1) Set and (2) return the graphical element type
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type)
                       {
                        CGBaseObj::SetTypeElement(type);
                        this.SetProperty(CANV_ELEMENT_PROP_TYPE,type);
                       }
   ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void)  const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(CANV_ELEMENT_PROP_TYPE);   }
   
//--- Set the main background color

Jetzt setzen wir eine einzelne Zeichenkette, indem wir die Methode zum Setzen der Eigenschaft in jedem Konstruktor jeder WinForms-Objektklasse aufrufen. Die Methode setzt die Eigenschaft auf beide übergeordneten Klassen.


Fügen wir zwei Methoden für die Rückgabe und das Setzen der Beschreibung eines grafischen Elements zu seinen Eigenschaften hinzu:

//--- Graphical object group
   virtual int       Group(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP);                }
   virtual void      SetGroup(const int value)
                       {
                        CGBaseObj::SetGroup(value);
                        this.SetProperty(CANV_ELEMENT_PROP_GROUP,value);
                       }
//--- Graphical element description
   string            Description(void)                   const { return this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION);               }
   void              SetDescription(const string descr)        { this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descr);                }
   
//+------------------------------------------------------------------+
//| The methods of receiving raster data                             |
//+------------------------------------------------------------------+


In beiden Klassenkonstruktoren fügen wir die Methode zum Einstellen des Typs des grafischen Elements hinzu, rufen die Methode zum Erstellen des Elementnamens nach seinem Typ auf und fügen die Einstellung der neuen Eigenschaften eines grafischen Elements hinzu:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   descript,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.SetTypeElement(element_type);
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(colour,true);
   this.SetOpacity(opacity);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID

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

      this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,CANV_ELEMENT_CHEK_STATE_UNCHECKED);  // Status of a control having a checkbox
      this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,true);                        // Auto change flag status when it is selected
      
      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,false);                                        // Toggle flag of the control featuring a button
      this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,false);                                         // Status of the Toggle control featuring a button
      this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,false);                                         // Button group flag
      this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,false);                                // Horizontal display of columns in the ListBox control
      this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,0);                                    // Width of each ListBox control column
      this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,false);                                        // Several lines of tabs in TabControl
      this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                   // Location of tabs inside the control
      this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                       // Location of an object inside the control
      this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                                                    // Graphical element text
      this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                                       // Graphical element description
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+


In der Methode zum Erstellen einer Objektstruktur fügen wir das Speichern neuer Eigenschaften in Strukturfelder hinzu:

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

   this.m_struct_obj.button_toggle=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE);                             // Toggle flag of the control featuring a button
   this.m_struct_obj.button_state=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE);                               // Status of the Toggle control featuring a button
   
   this.m_struct_obj.button_group_flag=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP);                          // Button group flag
   this.m_struct_obj.multicolumn=(bool)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN);                       // Horizontal display of columns in the ListBox control
   this.m_struct_obj.column_width=(int)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH);                       // Width of each ListBox control column
   this.m_struct_obj.tab_multiline=(bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE);                             // Several lines of tabs in TabControl
   this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);                              // Location of tabs inside the control
   this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT);                                      // Location of an object inside the control
//--- Save real properties

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


In der Methode, die ein Objekt aus der Struktur erstellt, fügen wir die Werte aus den neuen Strukturfeldern zu den neuen Objekteigenschaften hinzu:

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

   this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,this.m_struct_obj.button_toggle);                             // Toggle flag of the control featuring a button
   this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,this.m_struct_obj.button_state);                               // Status of the Toggle control featuring a button

   this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,this.m_struct_obj.button_group_flag);                          // Button group flag
   this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,this.m_struct_obj.multicolumn);                       // Horizontal display of columns in the ListBox control
   this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,this.m_struct_obj.column_width);                      // Width of each ListBox control column
   this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,this.m_struct_obj.tab_multiline);                             // Several lines of tabs in TabControl
   this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment);                             // Location of tabs inside the control
   this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment);                                     // Location of an object inside the control
//--- Save real properties

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


Jetzt übergeben wir den Namen eines erstellten Objekts nicht als Parameter der Methode, die eine grafische Ressource der Klasse CCanvas erstellt, die an das Chartobjekt gebunden ist. Stattdessen übergeben wir den zuvor festgelegten Objektnamen;

//+------------------------------------------------------------------+
//| 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;
     }
   CMessage::ToLog(DFUN_ERR_LINE,::GetLastError(),true);
   return false;
  }
//+------------------------------------------------------------------+


Die Methode, die die Anzahl der grafischen Elemente nach Typ zurückgibt:

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

Jeder Methodenstring ist ausführlich kommentiert, sodass die Logik der Methode meiner Meinung nach klar sein sollte. Kurz gesagt, wir müssen herausfinden, wie viele grafische Objekte des angegebenen Typs sich in dem Chart und dem Unterfenster befinden, in dem wir ein weiteres Element dieses Typs erstellen müssen. Wir können bereits einen Namen nach Typ erstellen, da die entsprechende Funktion bereits hinzugefügt wurde. Wir erstellen den Namen des Objekts nach seinem Typ, und suchen dann in einer Schleife durch alle grafischen Objekte im Chart und seinem Unterfenster nach einem Objekt, dessen Name eine Teilzeichenkette mit dem erstellten Namen des grafischen Objekts nach seinem Typ enthält. Wenn ein solcher Name gefunden wird, dann existiert das Objekt dieses Typs bereits und wir müssen den Zähler erhöhen. Am Ende der Schleife steht dann die Anzahl der gefundenen Objekte mit dem gewünschten Typ. Bringen wir es also zurück.


Die Methode, die den Namen eines grafischen Elements auf der Grundlage seines Typs erstellt und zurückgibt:

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

Die Methode empfängt den Objekttyp, für den der Name erstellt werden soll.
Dann erhält das Präfix der grafischen Bibliotheksobjekte ("Programmname "+"_") den Objektnamen nach seinem Typ und die Anzahl der Objekte dieses Typs.
Das Ergebnis wird von der Methode zurückgegeben.

Beide Methoden verwenden einen Methodenaufruf, um den Namen des grafischen Objekts nach seinem Typ zu ermitteln. Dies bedeutet, dass sie optimiert werden können, indem ein Aufruf der Methode, die zweimal aufgerufen wird, entfernt wird. Ich werde dies im nächsten Artikel tun (unter Berücksichtigung des Prinzips „vom Einfachen zum Komplexen“).


Damit wir den Typ eines erstellten Objekts korrekt angeben können, müssen wir verstehen, wie dieser Typ die grafische Elementklasse CGCnvElement „erreicht“, die eine der übergeordneten Klassen für WinForms-Objekte ist und in der diese Objekte erstellt werden. Wir müssen den Typ des erstellten grafischen Elements an diese Klasse „übermitteln“. Jetzt wird in allen Konstruktoren der von ihr geerbten Klassen der Typ, zu dem die Nachfolgeklasse gehört, explizit angegeben. Es wird also immer nur der Typ erstellt, der in der Klasse angegeben ist, die in der Vererbungshierarchie auf die Klasse CGCnvElement folgt. Dies ist die Klasse der Formularobjekte.

Wie können wir den Typ des erstellten Objekts senden, das in der Vererbungshierarchie weit von der CForm-Klasse entfernt ist? Die Antwort liegt auf der Hand: Jede dieser Klassen sollte einen weiteren Konstruktor haben, der nicht eindeutig den Typ des zu erstellenden Objekts angibt (wie es jetzt der Fall ist), sondern über die Konstruktorvariable in der Initialisierungsliste an die Elternklasse übergeben wird. Ein solcher Konstruktor sollte geschützt sein, damit er nur in geerbten Klassen funktionieren kann. Der Zugang von außen ist verboten. Solche Konstruktoren sind bereits für jedes WinForms-Objekt erstellt worden. Bei der gegenseitigen Vererbung wird der Typ der abgeleiteten Klasse auf die Elternklasse übertragen. Dies geschieht entlang der gesamten Kette der Objekthierarchie bis hin zum CGCnvElement-Objekt, in dem das gewünschte grafische Element mit dem Typ erstellt wird, der seinen Elternteil entlang der gesamten Vererbungskette „erreicht“ hat.

Vergessen wir nicht, dass die formalen Variablen „name“ in den Konstruktoren und Methoden zur Erstellung grafischer Elemente in allen Dateien aller WinForms-Objektklassen bereits in „descript“ umbenannt wurden. Ich erinnere noch einmal daran, um nicht mehr auf dieses Thema zurückzukommen und zu vermeiden, dass die gleichen Änderungen, die bereits für jedes WinForms-Objekt vorgenommen wurden, beschrieben werden.

In der Formularobjektdatei \MQL5\Include\DoEasy\Objects\Graph\Form.mqh wurde die Deklaration der unnötigen Methode entfernt, die den Namen eines abhängigen Objekts zurückgibt, (alle Namen werden jetzt automatisch in der Klasse CGCnvElement erstellt):

//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
//--- Return the name of the dependent object
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
//--- Update coordinates of bound objects
   virtual bool      MoveDependentObj(const int x,const int y,const bool redraw=false);

Entfernen wir auch den Implementierungscode für diese Methode aus dem Klassenverzeichnis.


Wir deklarieren den neuen geschützten Konstruktor oberhalb aller Klassenkonstruktoren:

//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CForm(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:                     
//--- Constructors

Im Gegensatz zu anderen Konstruktoren hat er einen formalen Parameter, über den der Typ des erzeugten Objekts angegeben wird.

Schreiben wir nun die Implementierung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CForm::CForm(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) : CGCnvElement(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+

Im Gegensatz zu öffentlichen Konstruktoren erhält die Initialisierungsliste des Konstruktors, d. h. der Konstruktor der übergeordneten Klasse, den Typ des erzeugten Objekts, der wiederum von der geerbten Klasse an den Konstruktor übergeben wird.

Indem wir also einen ähnlichen geschützten Konstruktor für jedes WinForms-Objekt erstellen, schaffen wir eine Kette, entlang derer der Typ jeder Nachfolgeklasse an die CGCnvElement-Elternklasse übergeben wird, in der ein Objekt mit einem Typ erstellt wird, der entlang der gesamten Kette der Hierarchie der geerbten Objekte erhalten wird.

Aus der Methode, die ein neues grafisches Objekt erstellt, entfernen wir eine Zeile, die einen abhängigen Objektnamen erzeugt, und übergeben bei der Erstellung neuer Objekte anstelle des Namens den Parameter „descript“ (Beschreibung), der in den formalen Parametern der Methode übergeben wird:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int obj_num,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   //--- Depending on the created object type,
   switch(type)
     {
      //--- create a graphical element object
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity);
        break;
      //--- create a form object
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(type,this.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));
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+

Solche Änderungen wurden an allen ähnlichen Methoden anderer Klassen von WinForms-Objekten vorgenommen. Wir werden hier nicht weiter darauf eingehen — alles ist in den Dateien im Anhang zu diesem Artikel zu finden.

In der Methode, die ein neues angehängtes Element erstellt und es zur Liste der angehängten Objekte hinzufügt, anstelle der Zeichenketten zur Erstellung des Namens des grafischen Objekts,

//--- Create a graphical element name
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;

fügen wir die Erstellung des Standardobjektbeschreibungstextes hinzu und übergeben ihn an die Methode zur Erstellung eines neuen grafischen Objekts:

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

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

   return obj;
  }
//+------------------------------------------------------------------+


In der Methode, die ein Schattenobjekt erstellt, fügen wir die Standardbeschreibung des Objekts anstelle seines Namens ein:

//+------------------------------------------------------------------+
//| Create the shadow object                                         |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- If the shadow flag is disabled or the shadow object already exists, exit
   if(!this.m_shadow || this.m_shadow_obj!=NULL)
      return;

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

//--- Create a new shadow object and set the pointer to it in the variable
   this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.NameObj()+"Shadow",x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
   this.m_list_tmp.Add(this.m_shadow_obj);
//--- Set the properties for the created shadow object
   
   //---...
   //---...
   //---...

//--- Move the form object to the foreground
   this.BringToTop();
  }
//+------------------------------------------------------------------+

Bei allen Methoden, bei denen wir automatisch eine Standardobjektbeschreibung erstellen, können wir diese Beschreibung jederzeit von unserem Programm aus mit der Methode SetDescription() ändern.


In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh der Basisklasse des WinForms-Objekts deklarieren wir den geschützten Konstruktor und entfernen einen der öffentlichen, da er hier nicht benötigt wird:

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CWinFormBase(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:
//--- Constructors
                     CWinFormBase(const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
                     CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
                       }

//--- (1) Set and (2) return the default text color of all panel objects


Die Implementierung des geschützten Konstruktors wiederholt fast vollständig die Implementierung des öffentlichen parametrischen Konstruktors:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(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) : CForm(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_BASE; 
//--- Initialize all variables
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+

Der einzige Unterschied besteht darin, dass wir hier den im Konstruktor festgelegten Typ als formalen Parameter an den Konstruktor der übergeordneten Klasse übergeben. Die Einstellung des Objekttyps in den Eigenschaften erfolgt nun durch den Aufruf der neuen Methode. Die gleiche Zeichenkette wird nun in den öffentlichen Konstruktor geschrieben:

//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables


anstelle der beiden vorherigen, die die gleiche Funktion hatten:

//--- Set the graphical element and library object types as a base WinForms object
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables

Ähnliche Verbesserungen wurden an allen Klassen aller WinForms-Objekte vorgenommen. Sie werden sie nicht weiter besprochen.


In der Methode, die die Beschreibung der Text-Eigenschaft des Elements zurückgibt, implementieren wir die Rückgabe der Beschreibung der neuen Eigenschaft:

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


Alle oben genannten Verbesserungen werden in den Klassen der WinForms-Objekte in den Dateien vorgenommen:

\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh,

Abgesehen von den ähnlichen Verbesserungen, die für alle WinForms-Objekte gelten, implementieren wir in der Datei des Schaltflächenobjekts \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh die Methode, mit der der Status virtuell gemacht wird, da wir ihn in den abgeleiteten Klassen neu definieren müssen:

   bool              Toggle(void)                        const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE);                               }
//--- (1) Set and (2) return the Toggle control status
   virtual void      SetState(const bool flag)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag);
                        if(this.State())
                          {
                           this.SetBackgroundColor(this.BackgroundStateOnColor(),false);
                           this.SetForeColor(this.ForeStateOnColor(),false);
                           this.UnpressOtherAll();
                          }
                        else
                          {
                           this.SetBackgroundColor(this.BackgroundColorInit(),false);
                           this.SetForeColor(this.ForeColorInit(),false);
                           this.SetBorderColor(this.BorderColorInit(),false);
                          }
                       }
   bool              State(void)                         const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE);                                }

 

Wir verwenden Schaltflächenobjekte zur Anzeige von Zeichenketten in der ListBox-Objektklasse . Um aber den Text auf Schaltflächen so darzustellen, dass der Text immer an den linken Rand der Schaltfläche gedrückt wird, wenn diese linksbündig ist, müssen wir einen Parameter einführen, der die Anzahl der Zeichen angibt, um die der Text nach rechts verschoben werden soll.
Das Button-Objekt hat keine solche Eigenschaft. Erstellen wir daher das Hilfsobjekt ListBoxItem.

Erstellen wir im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ die neue Datei ListBoxItem.mqh der Klasse CListBoxItem. Die Klasse sollte von der Basisklasse der Listenobjekte abgeleitet sein und ihre Datei sollte in die erstellte Klassendatei aufgenommen werden:

//+------------------------------------------------------------------+
//|                                                  ListBoxItem.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 "Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| Label object class of WForms controls                            |
//+------------------------------------------------------------------+
class CListBoxItem : public CButton
  {
  }


Im privaten Abschnitt der Klasse deklarieren wir die Variable, in der die Anzahl der Zeichen gespeichert wird, um die der Text verschoben werden soll, und die String-Variable, in der der Shift-String enthalten sein soll. Wir deklarieren einen geschützten Konstruktor im geschützten Abschnitt der Klasse, während die Methoden für die Arbeit mit Klassenvariablen und der parametrische Konstruktor im öffentlichen Abschnitt deklariert werden:

//+------------------------------------------------------------------+
//| Label object class of WForms controls                            |
//+------------------------------------------------------------------+
class CListBoxItem : public CButton
  {
private:
   uchar             m_text_shift;                          // Element text shift to the right from the left edge in characters
   string            m_shift_space;                         // Shift string
protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CListBoxItem(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:
//--- Return the element text shift to the right from the left edge in characters
   uchar             TextShift(void)                        const { return this.m_text_shift;   }
//--- (1) Create and (2) return the string consisting of the number of shift characters
   void              SetTextShiftSpace(const uchar value);
   string            GetTextShiftSpace(void)                const { return this.m_shift_space;  }
//--- Set the element text
   virtual void      SetText(const string text);
//--- Constructor
                     CListBoxItem(const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
  };
//+------------------------------------------------------------------+


Konstrukteure der Klasse:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CListBoxItem::CListBoxItem(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)
  {
//--- 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.SetTextAlign(ANCHOR_LEFT);
   this.SetTextShiftSpace(1);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListBoxItem::CListBoxItem(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_LIST_BOX_ITEM,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetTextAlign(ANCHOR_LEFT);
   this.SetTextShiftSpace(1);
  }
//+------------------------------------------------------------------+

Beide Konstruktoren sind fast identisch, außer dass der geschützte Konstruktor einen formalen Parameter hat, an den der Objekttyp übergeben wird, und dass dieser Typ an den Konstruktor der übergeordneten Klasse übergeben wird, während im parametrischen Konstruktor der Objekttyp genau als ListBoxItem festgelegt wird. Jetzt haben wir alle WinForms-Objekte der Bibliothek auf diese Weise angeordnet. Die Verschiebung des Textes nach rechts um ein Zeichen wird in jedem Konstruktor festgelegt.


Die Methode erzeugt eine Zeichenkette, die aus der Anzahl der Großbuchstaben (Shift-Zeichen) besteht:

//+------------------------------------------------------------------+
//| Create a string consisting of the number of shift characters     |
//+------------------------------------------------------------------+
void CListBoxItem::SetTextShiftSpace(const uchar value)
  {
   this.m_text_shift=value;
   this.m_shift_space="";
   switch(this.TextAlign())
     {
      case ANCHOR_LEFT        :
      case ANCHOR_LEFT_LOWER  :
      case ANCHOR_LEFT_UPPER  :
        for(int i=0;i<(int)this.m_text_shift;i++)
           this.m_shift_space+=" ";
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Die Methode erhält die Anzahl der Zeichen, um die die Zeichenkette verschoben werden soll. Der Anfangswert der Verschiebungszeichenfolge wird anschließend festgelegt. Abhängig von der Textausrichtung und unter der Voraussetzung, dass der Text am linken Rand beginnt, fügen wir bei jeder Iteration der Schleife ein Leerzeichen zu der versetzten Zeile hinzu. Am Ende der Schleife enthält die Zeichenfolge die erforderliche Anzahl von Leerzeichen.


Die Methode, die den Text des Elements festlegt:

//+------------------------------------------------------------------+
//| Set the element text                                             |
//+------------------------------------------------------------------+
void CListBoxItem::SetText(const string text)
  {
   this.SetProperty(CANV_ELEMENT_PROP_TEXT,this.GetTextShiftSpace()+text);
  }
//+------------------------------------------------------------------+

Hier erhält die Objekteigenschaft den Text des Objekts mit der Anzahl der Leerzeichen, die mit der oben genannten Methode SetTextShiftSpace() festgelegt wurde.


In der ListBox-Objektklasse der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ListBox.mqh, und zwar in ihrem privaten Abschnitt, fügen wir die Variable hinzu, die die Verschiebung des Textes der Kollektion der Elementobjekte speichert (ListBoxItem-Objektklasse, siehe oben). Wir deklarieren im öffentlichen Abschnitt die Methoden zur Behandlung einer neuen Variablen und im geschützten Abschnitt den geschützten Konstruktor:

//+------------------------------------------------------------------+
//| ListBox object class of the WForms controls                      |
//+------------------------------------------------------------------+
class CListBox : public CElementsListBox
  {
private:
   uchar             m_text_shift;                       // ListBoxItem elements text shift to the right from the left edge in characters
//--- 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);
public:
//--- (1) Set and (2) return the element text shift to the right from the left edge in characters
   void              SetTextShift(const uchar value);
   uchar             TextShift(void)                  const { return this.m_text_shift;   }
//--- Create a list from the specified number of rows (Label objects)
   void              CreateList(const int line_count,const int new_column_width=0,const bool autosize=true);
   
protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CListBox(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:
//--- Constructor
                     CListBox(const long chart_id,
                              const int subwindow,
                              const string descript,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
  };
//+------------------------------------------------------------------+

Die Konstruktoren werden identisch zu den bereits betrachteten Objekten anderer Klassen erstellt und finalisiert.

Verfeinern wir die Methode, die die Liste mit der angegebenen Anzahl von Zeichenfolgen erstellt (ListBoxItem).

Anstelle des Objekts der Klasse CButton werde ich nun mit der neuen Klasse CListBoxItem arbeiten und das Objekt dieser neuen Klasse erstellen. Nach dem Anlegen des Objekts erstellen wir gleich dessen Textverschiebung und setzen den Standardtext des angelegten Elements der Kollektion:

//+------------------------------------------------------------------+
//| Create the list from the specified number of rows (ListBoxItem)  |
//+------------------------------------------------------------------+
void CListBox::CreateList(const int count,const int new_column_width=0,const bool autosize=true)
  {
//--- Create the pointer to the CListBoxItem object
   CListBoxItem *obj=NULL;
//--- Calculate the width of the created object depending on the specified column width
   int width=(new_column_width>0 ? new_column_width : this.Width()-this.BorderSizeLeft()-this.BorderSizeRight());
//--- Create the specified number of ListBoxItem objects
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,count,0,0,width,15,new_column_width,autosize);
//--- In the loop by the created number of objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Get the created object from the list by the loop index
      obj=this.GetElement(i);
      //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM));
         continue;
        }
      //--- Set left center text alignment
      obj.SetTextAlign(ANCHOR_LEFT);
      obj.SetTextShiftSpace(this.TextShift());
      //--- Set the object text
      obj.SetFontSize(8);
      obj.SetText(TypeGraphElementAsString(obj.TypeGraphElement())+string(i+1));
      //--- Set the background, text and frame color
      obj.SetBackgroundStateOnColor(clrDodgerBlue,true);
      obj.SetBackgroundStateOnColorMouseOver(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-5));
      obj.SetBackgroundStateOnColorMouseDown(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-10));
      obj.SetForeStateOnColor(this.BackgroundColor(),true);
      obj.SetForeStateOnColorMouseOver(obj.ChangeColorLightness(obj.ForeStateOnColor(),-5));
      obj.SetForeStateOnColorMouseDown(obj.ChangeColorLightness(obj.ForeStateOnColor(),-10));
      obj.SetBorderColor(obj.BackgroundColor(),true);
      obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown());
      obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver());
      //--- Set the flags of the toggle and group buttons
      obj.SetToggleFlag(true);
      obj.SetGroupButtonFlag(true);
     }
//--- If the flag of auto resizing the base object is passed to the method,
//--- set the auto resize mode to "increase and decrease"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+


Die Methode zur Einstellung der Textverschiebung des Elements vom linken Rand nach rechts in Zeichen:

//+------------------------------------------------------------------+
//| Set the element text shift                                       |
//| to the right from the left edge in characters                    |
//+------------------------------------------------------------------+
void CListBox::SetTextShift(const uchar value)
  {
   this.m_text_shift=value;
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CListBoxItem *obj=this.GetElement(i);
      if(obj==NULL || obj.TextShift()==value)
         continue;
      obj.SetTextShiftSpace(value);
      obj.SetText(obj.Text());
      obj.Update(false);
     }
  }
//+------------------------------------------------------------------+

Hier wird die Anzahl der Shift-Zeichen, die an die Methode übergeben wird, in eine Variable gesetzt. Dann wird in einer Schleife durch die Liste der gebundenen Objekte das nächste Objekt geholt und der Text mit einer Verschiebung für dieses Objekt mit der oben erwähnten Objektmethode SetText() gesetzt.

In der Klasse des Basis-Containerobjekts in MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh entfernen wir den redundanten Konstruktor:

                     CContainer(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; 
                        this.SetForeColor(CLR_DEF_FORE_COLOR,true);
                        this.SetFontBoldType(FW_TYPE_NORMAL);
                        this.SetMarginAll(3);
                        this.SetPaddingAll(0);
                        this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
                        this.SetBorderStyle(FRAME_STYLE_NONE);
                        this.SetAutoScroll(false,false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                       }
//--- Destructor
                    ~CContainer();
  };
//+------------------------------------------------------------------+


Wir deklarieren einen geschützten Konstruktor im geschützten Abschnitt:

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CContainer(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:
//--- Constructor
                     CContainer(const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h);


Die Implementierung des geschützten Konstruktors ist identisch mit allen zuvor hinzugefügten geschützten Konstruktoren in anderen Klassen.


In der Methode, die die Parameter für das angehängte Objekt festlegt, fügen wir eine Behandlung des ListBoxItem-Objekts hinzu, die derjenigen für dieObjekte Button und TabHeader ähnelt:

      //--- 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 the Button, TabHeader and ListBoxItem WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
      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   :
        //--- 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.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;


Wir entfernen die Behandlung des nun fehlenden TabPage-Objekts aus der Methode:

        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE          :
        //--- 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.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR : colour,true);
        obj.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
        obj.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
        obj.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
        obj.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY);
        obj.SetBorderSizeAll(1);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
  


In der Klasse \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ElementsListBox.mqh, der Basisklasse für Steuerlistenobjekte, entfernen wir die Klassendatei CListBoxItem ein:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\Containers\Container.mqh"
#include "..\ListBoxItem.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the WForms control list              |
//+------------------------------------------------------------------+

Nun wird die Klasse der Kollektionslinien der Steuerungen in anderen Objekten der Bibliothek verfügbar sein.

Wie bei anderen Objekten ist der geschützte Konstruktor zu deklarieren:

protected:
//--- Create the specified number of specified WinForms objects
   void              CreateElements(ENUM_GRAPH_ELEMENT_TYPE element_type,
                                    const int count,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h,
                                    uint new_column_width=0,
                                    const bool autosize=true);
                                    
//--- Protected constructor with object type, chart ID and subwindow
                     CElementsListBox(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:
//--- Constructor

Die Implementierung des geschützten Konstruktors und die Verfeinerung des parametrischen Konstruktors sind identisch mit anderen WinForms-Objekten.


Die Klasse TabHeader — Das TabControl-Objekt des Kopfes der Registerkarte

Der Titel der TabControl-Registerkarte wird auf der Grundlage des Schaltflächenobjekts erstellt. Der Titel der Registerkarte sollte, wie die Umschalttaste, entweder aktiviert (die Registerkarte ist ausgewählt) oder deaktiviert (eine andere Registerkarte ist ausgewählt) sein. Alle Titel sollten in einer Gruppe arbeiten. Genau wie bei den Gruppenschaltflächen gilt auch hier, dass, wenn eine Registerkarte ausgewählt ist, die übrigen freigegeben werden sollten. Es sollte jedoch nicht zu einer Situation kommen, in der alle Registerkarten freigegeben sind. Eine Registerkarte sollte ebenfalls ausgewählt bleiben.

Wir können dies mit Hilfe der Schaltflächen erreichen. Allerdings können die Schaltflächen ihre Größe nicht je nach Zustand ändern, während die Registerkartenüberschriften dies tun sollten. Eine ausgewählte Registerkarte hat einen etwas größeren Titel als eine freigegebene Registerkarte. Registerkartenüberschriften können von vier Seiten in das Steuerelement eingefügt werden — oben, unten, links und rechts. Dementsprechend sollte der Rahmen nicht von der Seite aus gezeichnet werden, die mit dem Registerkartenfeld in Kontakt ist. Das bedeutet, dass wir eine neue Methode für die Schaltfläche erstellen müssen, um sie neu zu zeichnen. Bei dieser Methode wird der Rahmen in Übereinstimmung mit der Position des Titels auf dem Steuerelement gezeichnet. Außerdem müssen wir die neue Methode für die Behandlung einer gedrückten Schaltfläche implementieren, sodass die Schaltfläche größer wird und sich zu neuen Koordinaten verschiebt, sodass sie immer auf das Tabulatorfeld gedrückt bleibt.

Im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ erstellen wir eine neue Datei TabHeader.mqh mit der Klasse TabHeader. Die Klasse sollte von der Klasse CCheckBox abgeleitet sein, und ihre Datei sollte in die eingebunden Klassendatei aufgenommen werden:

//+------------------------------------------------------------------+
//|                                                    TabHeader.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 "Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
  }


Wir deklarieren die Variablen und Methoden für den Klassenbetrieb in den Klassenabschnitten privat, geschützte und öffentlich (private, protected und public):

//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
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
//--- Sets the width, height and shift of the element depending on the state
   void              SetWH(void);
//--- Adjust the size and location of the element depending on the state
   void              WHProcessStateOn(void);
   void              WHProcessStateOff(void);
//--- Draw the element frame depending on the location
   void              DrawFrame(void);
   
protected:
//--- 'The cursor is inside the active area, the left mouse button is clicked' event handler
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

public:
//--- 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;  }
//--- 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 title 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 title 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 index,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);
                        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);
                       }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }
   

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

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     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);
public:
//--- Constructor
                     CTabHeader(const long chart_id,
                             const int subwindow,
                             const string descript,
                             const int x,
                             const int y,
                             const int w,
                             const int h);
  };
//+------------------------------------------------------------------+

Die Beschreibungen der Variablen und Methoden sind ziemlich selbsterklärend. Betrachten wir das genauer.

Klassenkonstruktoren — geschützt und parametrisch. Die Implementierung ist fast identisch, und die Logik ist genau dieselbe wie bei den übrigen Bibliotheksobjekten: Der geschützte Konstruktor übergibt den angegebenen Typ an den Konstruktor der übergeordneten Klasse, während der parametrische Konstruktor seinen Objekttyp an den der übergeordneten Klasse übergibt:

//+------------------------------------------------------------------+
//| 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.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);
   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.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);
   this.SetState(false);
  }
//+------------------------------------------------------------------+

Im Hauptteil des Konstruktors werden Standardwerte für Objektfarben in verschiedenen Zuständen festgelegt. Die Flags der Umschalttaste und der in der Gruppe arbeitenden Taste werden sofort gesetzt. Im gedrückten Zustand wird das Objekt in jeder Richtung um zwei Pixel vergrößert, mit Ausnahme der an das Feld angrenzenden Registerkarte.

Die Methode, die den Zustand des Steuerelements festlegt:

//+------------------------------------------------------------------+
//| Set the state of the control                                     |
//+------------------------------------------------------------------+
void CTabHeader::SetState(const bool flag)
  {
   bool state=this.State();
   CButton::SetState(flag);
   if(state!=flag)
      this.SetWH();
  }
//+------------------------------------------------------------------+

Zuerst wird der aktuelle Zustand gespeichert, dann wird die Methode der übergeordneten Klasse zum Setzen des Zustands aufgerufen. Wenn sich der vorherige Zustand vom aktuellen unterscheidet, rufen wir die Methode zum Ändern der Größe der Registerkartenüberschrift auf.


Die Methode zur Einstellung die Elementbreite, -höhe und -verschiebung in Abhängigkeit von ihrem Zustand:

//+------------------------------------------------------------------+
//| Set the element width, height and shift                          |
//| depending on its state                                           |
//+------------------------------------------------------------------+
void CTabHeader::SetWH(void)
  {
   if(this.State())
      this.WHProcessStateOn();
   else
      this.WHProcessStateOff();
  }
//+------------------------------------------------------------------+

Wenn der Zustand „aktiviert“ ist, rufen wir die Methode zur Änderung der Größe und Position für den Zustand „aktiviert“ auf, andernfalls rufen wir die Methode zur Änderung der Größe und Position für den Zustand „deaktiviert“ auf.


Die Methode, die die Größe und Position eines Elements im Zustand „ausgewählt“ in Abhängigkeit von seiner Position anpasst:

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "selected" state depending on its location                |
//+------------------------------------------------------------------+
void CTabHeader::WHProcessStateOn(void)
  {
//--- If failed to get a new size, leave
   if(!this.SetSizeOn())
      return;
//--- Depending on the title location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP :
        //--- move it where necessary and set 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 :
        //--- move it where necessary and set new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      default:
        break;
     }
   this.Update(false);
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist im Code kommentiert. Je nachdem, wo sich die Überschrift befindet (bisher werden nur zwei Positionen bearbeitet - oben und unten), wird die Größe der Überschrift geändert und auf neue Koordinaten verschoben, sodass ihr Rand, der mit dem Tabulatorfeld in Kontakt ist, an Ort und Stelle bleibt. Optisch wird das Objekt um zwei Pixel in jede Richtung erweitert, außer an der Seite, die an das Feld angrenzt.


Die Methode, die die Größe und Position eines Elements im Zustand „freigegeben“ in Abhängigkeit von seiner Position anpasst:

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "released" state depending on its location                |
//+------------------------------------------------------------------+
void CTabHeader::WHProcessStateOff(void)
  {
//--- If failed to get a new size, leave
   if(!this.SetSizeOff())
      return;
//--- Depending on the title location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP :
        //--- move it where necessary and set 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 :
        //--- move it where necessary and set new relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      default:
        break;
     }
   this.Update(false);
  }
//+------------------------------------------------------------------+

Je nachdem, wo sich die Überschrift befindet (bisher werden nur zwei Positionen bearbeitet - oben und unten), wird die Größe der Überschrift geändert und auf neue Koordinaten verschoben, sodass ihr Rand, der mit dem Tabulatorfeld in Kontakt ist, an Ort und Stelle bleibt. Optisch wird das Objekt in jeder Richtung um zwei Pixel verkleinert, mit Ausnahme der an das Feld angrenzenden Seite.


Die Methode zeichnet den Elementrahmen je nach Standort:

//+------------------------------------------------------------------+
//| Draw the element frame depending on the location                 |
//+------------------------------------------------------------------+
void CTabHeader::DrawFrame(void)
  {
//--- Set initial coordinates
   int x1=0;
   int x2=this.Width()-1;
   int y1=0;
   int y2=this.Height()-1;
//--- Depending on the position of the header, draw a frame
//--- so that the edge of the drawn frame adjacent to the field goes beyond the object
//--- thus, visually the edge will not be drawn on the adjacent side
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        this.DrawRectangle(x1,y1,x2,y2+1,this.BorderColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        this.DrawRectangle(x1,y1-1,x2,y2,this.BorderColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        this.DrawRectangle(x1,y1,x2+1,y2,this.BorderColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        this.DrawRectangle(x1-1,y1,x2,y2,this.BorderColor(),this.Opacity());
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist im Code kommentiert. Wenn wir ein Rechteck auf einem Objekt so zeichnen, dass die Koordinaten einer der Seiten über das Objekt hinausgehen, dann wird auf dieser Seite nichts gezeichnet. Das ist es, was wir hier verwenden. An der Stelle, an der der Titel an das Registerfeld angrenzen soll, geben wir absichtlich die Koordinaten an, die über das Objekt hinausgehen, und der Rahmen wird nicht auf dieser Seite gezeichnet.


Die Methode löscht das Element und füllt es mit Farbe und Deckkraft:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CTabHeader::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 && redraw)
      this.DrawFrame();
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Die virtuelle Methode hat Vorrang vor der Methode des übergeordneten Objekts, bei der ein Rahmen innerhalb des Objekts gezeichnet wird. Hier rufen wir die obige Methode auf, um einen Rahmen auf nur drei Seiten des Objekts zu zeichnen.


Die Methode, die ein Element mit einer Farbverlaufsfüllung löscht:

//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CTabHeader::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 && redraw)
      this.DrawFrame();
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Die Methode ist identisch mit der oben beschriebenen, füllt aber den Hintergrund mit einem Farbverlauf aus dem Array von Farben, die der Methode übergeben werden.


Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt:

//+------------------------------------------------------------------+
//| '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);
        }
      //--- 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);
        }
      //--- 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);
     }
//--- Redraw the object
   this.Redraw(false);
  }
//+------------------------------------------------------------------+


Behandlung des letzten Mausereignisses:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CTabHeader::OnMouseEventPostProcessing(void)
  {
   ENUM_MOUSE_FORM_STATE state=GetMouseState();
   switch(state)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED     :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL       :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED)
          {
           this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false);
           this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
           this.Redraw(false);
          }
        break;

      //--- The cursor is inside the form, the mouse buttons are not clicked
      //--- The cursor is inside the form, any mouse button is clicked
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      //--- The cursor is inside the active area, any mouse button is clicked
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, left mouse button is released
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Beide Methoden sind identisch mit den Methoden der übergeordneten Klasse.
Sie wurden hierher verschoben, damit sie bei der weiteren Entwicklung des TabControl-Objekts möglicherweise neu definiert werden können.


TabControl-Klasse — Wiederaufnahme der Entwicklung

Im letzten Artikel haben wir mit der Entwicklung von TabControl begonnen, sind aber auf eine Beschränkung bei der Länge der Namen der erstellten grafischen Objekte gestoßen. Nachdem wir den neuen Algorithmus für die Benennung von grafischen Bibliothekselementen erstellt haben, setzen wir unsere Arbeit in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh fort.

Binden wir die Objektklassendatei für die Registerkartenüberschrift in die Kontrolldatei ein:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\TabHeader.mqh"
//+------------------------------------------------------------------+


Im privaten Abschnitt der Klasse deklarieren wir zwei Methoden, um den Status der durch den Index angegebenen Registerkarte zu setzen:

private:
   int                  m_item_width;                 // Fixed width of tab titles
   int                  m_item_height;                // Fixed height of tab titles
//--- 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);
//--- Set the tab as selected
   void              SetSelected(const int index);
//--- Set the tab as released
   void              SetUnselected(const int index);
   
public:


Wir benennen CreateTabPage() in CreateTabPages() um, versehen sie mit dem Rückgabetyp bool , ändern die Menge der formalen Parameter und fügen zwei Methoden hinzu, die die Zeiger auf die Kopfzeile und das Registerfeld nach Index zurückgeben:

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="");
   
//--- Return a pointer to the (1) title and (2) tab field
   CTabHeader       *GetHeader(const int index)          { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); }
   CContainer       *GetField(const int index)           { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index);  }


Implementieren wir die Methode SetAlignment() so, dass sie nicht nur den Wert für die Objekteigenschaft festlegt, sondern auch für alle im Steuerelement erstellten Registerkartentitel:

//--- (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.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
                        if(list==NULL)
                           return;
                        for(int i=0;i<list.Total();i++)
                          {
                           CTabHeader *header=list.At(i);
                           if(header==NULL)
                              continue;
                           header.SetAlignment(alignment);
                          }
                       }

Zuerst setzen wir den Wert in den Eigenschaften des Objekts, dann erhalten wir eine Liste aller erstellten Registerkarten. In der Schleife setzen wir durch die Ergebnisliste für jedes Objekt denselben Wert.


Wir deklarieren drei neue öffentliche Methoden:

//--- 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 tab as selected/released
   void              Select(const int index,const bool flag);

//--- Set the title 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);

//--- Constructor


Im Klassenkonstruktor setzen wir alle Farben auf transparent außer der Textfarbe und führen die Änderungen an allen WinForms-Objekten durch:

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

Dieses Objekt sollte als Container für die darin erstellten Registerkarten dienen. Außerdem sollte es diese Registerkarten verwalten. Daher ist es völlig transparent, aber die Möglichkeit, Text darauf anzuzeigen, bleibt bestehen.


Die Methode, die die angegebene Anzahl von Registerkarten erstellt:

//+------------------------------------------------------------------+
//| 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 title
   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;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab titles, 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.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());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      
      //--- 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 Container object (tab field)
      CContainer *field=NULL;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER,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_CONTAINER),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER),string(i+1));
         return false;
        }
      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.Hide();
   //---
     }
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

Die Registerkarten bestehen aus zwei Objekten — dem Registertitel und dem Registerfeld. Der Titel (Schaltfläche) dient zur Auswahl der aktiven Registerkarte, und das Feld wird verwendet, um andere Steuerelemente darauf zu platzieren. Die Schaltflächen sollten immer angezeigt werden, während Registerkartenfelder immer ausgeblendet bleiben sollten, mit Ausnahme des aktiven Feldes.

Hier, in der Schleife, erstellen wir zunächst Schaltflächen als Registerkartentitel und setzen sie auf ihre Standardwerte. In der gleichen Iteration der Schleife nach der Erstellung der Schaltfläche erstellen wir einen Container für das Registerkartenfeld und legen auch dessen Standardwerte fest. Die Koordinaten und die Größe der Registerkartenfelder hängen von der Position der Kopfzeilen im Steuerelement ab. Nach Beendigung der Schleife zur Erstellung des Registerkartenobjekts geben wir das Registerkartenset in den Eingaben als ausgewählt an.


Die Methode, die die Registerkarte als ausgewählt festlegt:

//+------------------------------------------------------------------+
//| Set the tab as selected                                          |
//+------------------------------------------------------------------+
void CTabControl::SetSelected(const int index)
  {
   CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);
   CContainer *field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index);
   if(header==NULL || field==NULL)
      return;
   field.Show();
   field.BringToTop();
   header.SetState(true);
   header.BringToTop();
  }
//+------------------------------------------------------------------+

Wir ermitteln den Zeiger auf den Titel und das Registerfeld nach Index.
Wir zeigen das Feld und bringen es in den Vordergrund
.
Wir setzen den ausgewählten Titel und bringen ihn in den Vordergrund.


Die Methode, die die Registerkarte als freigegeben setzt:

//+------------------------------------------------------------------+
//| Select the tab as released                                       |
//+------------------------------------------------------------------+
void CTabControl::SetUnselected(const int index)
  {
   CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);
   CContainer *field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index);
   if(header==NULL || field==NULL)
      return;
   field.Hide();
   header.SetState(false);
  }
//+------------------------------------------------------------------+

Ermitteln der Zeiger auf den Titel und das Registerfeld nach Index.
Ausblenden des Felds und
Freigabe der Kopfzeile.


Die Methode, die die Registerkarte als ausgewählt/freigegeben festlegt:

//+------------------------------------------------------------------+
//| Set the tab as selected/released                                 |
//+------------------------------------------------------------------+
void CTabControl::Select(const int index,const bool flag)
  {
   if(flag)
      this.SetSelected(index);
   else
      this.SetUnselected(index);
  }
//+------------------------------------------------------------------+

Der Tabulatorindex und das Flag werden an die Methode übergeben, und je nach dem Wert des Flags wird eine der beiden oben beschriebenen Methoden aufgerufen.


Die Methode setzt den Titeltext der angegebenen Registerkarte:

//+------------------------------------------------------------------+
//| Set the title text of the specified tab                          |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderText(CTabHeader *header,const string text)
  {
   if(header==NULL)
      return;
   header.SetText(text);
  }
//+------------------------------------------------------------------+

Die Methode erhält den Zeiger auf das Objekt, in das der an die Methode übergebene Text gesetzt wird.


Die Methode, die den Text der Registerkartenüberschrift nach Index festlegt:

//+------------------------------------------------------------------+
//| Set the tab title text by index                                  |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderText(const int index,const string text)
  {
   CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);
   this.SetHeaderText(header,text);
  }
//+------------------------------------------------------------------+

Die Methode erhält den Index der Registerkarte, in die der Text gesetzt werden soll.
Wir holen uns per Index das Objekt mit dem Typ des Tabellentitels und rufen die obige Methode auf, um den Text auf das angegebene Objekt zu setzen.


In der Methode zur Erstellung eines neuen grafischen Objekts fügen wir die Erstellung des ListBoxItem-Objekts hinzu und entfernen den Block zur Erstellung des jetzt fehlenden TabPage-Objekts:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                         const int obj_num,
                                         const string descript,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const color colour,
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity)
  {

   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_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_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;
  }
//+------------------------------------------------------------------+

Jetzt übergeben wir die Objektbeschreibung (statt des Namens) an die Methode in jedem Block zur Objekterstellung.


Die Objektklasse Panel in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh und das Objekt GroupBox in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh weisen dieselben Verbesserungen auf wie die WinForms-Objekte: Es wurde ein neuer geschützter Konstruktor hinzugefügt, ein unnötiger parametrischer Konstruktor entfernt und die virtuelle Methode CreateNewGObject() so verbessert, wie ich es oben beschrieben habe.

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


Test

Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn in \MQL5\Experts\TestDoEasy\Part114\ als TstDE114.mq5.

Um die Beschreibungen der Enumerationskonstanten in zwei Sprachen in den EA-Einstellungen anzuzeigen, erstellen wir eine zusätzliche Enumeration für die Kompiliersprachen Englisch und Russisch. Wir fügen in den Einstellungen eine neue Variable mit dem Typ der Enumeration hinzu, die angibt, wo — oben oder unten (rechts-links ist noch nicht implementiert) — sich die Registerkartenüberschriften befinden sollen:

//--- enumerations by compilation language
#ifdef COMPILE_EN
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Grow
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Grow and Shrink
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // None
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (bevel)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (stamp)
  };
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,      // Indeterminate
  };
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
  };
#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
  };
#endif 
//--- 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      =  false;                  // Button toggle flag
//sinput   bool                          InpListBoxMColumn    =  false;                  // ListBox MultiColumn flag
sinput   bool                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
sinput   ENUM_ELEMENT_ALIGNMENT        InpHeaderAlignment   =  ELEMENT_ALIGNMENT_TOP;  // TabHeader Alignment
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+


In OnInit(), d. h. im Block für die Erstellung des grafischen Objekts, kommentieren wir den Block für die Erstellung des TabControl-Objekts aus dem vorherigen Artikel aus (ich werde ihn später dort platzieren) und den Code für die Erstellung des ListBox-Objekts (ich werde ihn auf den Registerkarten des zukünftigen TabControl-Objekts platzieren). Nach dem Codeblock für die Erstellung des ButtonListBox-Objekts platzieren wir den Codeblock für die Erstellung des TabControl-Objekts mit drei Registerkarten, von denen die erste zunächst ausgewählt werden soll:

      //--- 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 *tctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
//            if(tctrl!=NULL)
//              {
//               //--- get the pointer to the Container object by its index in the list of bound objects of the Container type
//               CContainer *page=tctrl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,0);
//               if(page!=NULL)
//                 {
//                  // Here we will create objects attached to the specified tab of the TabControl object
//                  // Unfortunately, in the current state of creating the names of graphical objects of the library,
//                  // their further creation is limited by the number of characters in the resource name in the CCanvas class
//                 }
//               
//              }
            ///*
            //--- Create the CheckedListBox object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false);
            //--- get the pointer to the CheckedListBox object by its index in the list of bound objects of the CheckBox type
            CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0);
            //--- If CheckedListBox is created and the pointer to it is received
            if(clbox!=NULL)
              {
               clbox.SetMultiColumn(true);
               clbox.SetColumnWidth(0);
               clbox.CreateCheckBox(4,66);
              }
            //--- Create the ButtonListBox object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,clbox.BottomEdgeRelative()+6,160,30,clrNONE,255,true,false);
            //--- get the pointer to the ButtonListBox object by its index in the list of attached objects of the Button type
            CButtonListBox *blbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0);
            //--- If ButtonListBox is created and the pointer to it is received
            if(blbox!=NULL)
              {
               blbox.SetMultiColumn(true);
               blbox.SetColumnWidth(0);
               blbox.CreateButton(4,66,16);
               blbox.SetMultiSelect(InpButtListMSelect);
               blbox.SetToggle(InpButtonToggle);
               for(int i=0;i<blbox.ElementsTotal();i++)
                 {
                  blbox.SetButtonGroup(i,(i % 2==0 ? blbox.Group()+1 : blbox.Group()+2));
                  blbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false));
                 }
              }
            
            int lbx=6;
            int lby=blbox.BottomEdgeRelative()+6;
            int lbw=180;
            //--- Create the TabControl object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,lbx,lby,lbw,78,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 titles on the element and the tab text
               tab_ctrl.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
               tab_ctrl.CreateTabPages(3,0,56,16,TextByLanguage("Вкладка","TabPage"));
              }
                        
            //--- Create the ListBox object
            //int lbx=4;
            //int lby=blbox.BottomEdgeRelative()+6;
            //int lbw=146;
            //if(!InpListBoxMColumn)
            //  {
            //   lbx=blbox.RightEdgeRelative()+6;
            //   lby=14;
            //   lbw=100;
            //  }
            //gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,lbx,lby,lbw,60,clrNONE,255,true,false);
            ////--- get the pointer to the ListBox object by its index in the list of attached objects of ListBox type
            //CListBox *lbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0);
            ////--- If ListBox has been created and the pointer to it has been received
            //if(lbox!=NULL)
            //  {
            //   lbox.SetMultiColumn(true);
            //   lbox.CreateList(8,68);
            //  }
            //*/
           }
        }

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


Jetzt sind die Tabs im Vergleich zum EA aus dem vorherigen Artikel lebendiger geworden. Die Registerkartentitel können sowohl oberhalb als auch unterhalb des Containers platziert werden, aber es gibt auch auffällige Schwächen: Wenn sich andere Elemente bei der Interaktion mit der Maus gut verhalten, dann „blinken“ die Registerkartentitel merklich. Wir werden uns später damit befassen. Schließlich fuhr ich mit dem Cursor über die Linie zwischen dem Titel und dem Tabulatorfeld. Diese Zeile sollte nicht vorhanden sein. Ich habe dies bereits in dem Artikel erwähnt. Im nächsten Artikel werde ich dafür sorgen, dass die Registerkarte (die Registerkarte selbst und ihr Titel) ein einheitliches Ganzes bilden.


Was kommt als Nächstes?

Im nächsten Artikel werde ich die Entwicklung von TabControl 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 1): Erste Schritte
DoEasy. Steuerung (Teil 2): Arbeiten an der Klasse CPanel
DoEasy. Steuerung (Teil 3): Erstellen gebundener Steuerelemente
DoEasy. Steuerung (Teil 4): Paneel-Steuerung, Parameter für Padding und Dock
DoEasy. Steuerung (Teil 5): Basisobjekt von WinForms, Paneel-Steuerelement, Parameter AutoSize
DoEasy. Steuerung (Teil 6): Paneel-Steuerung, automatische Größenanpassung des Containers an den inneren Inhalt
DoEasy. Steuerung (Teil 7): Steuerung der Text Label
DoEasy. Steuerung (Teil 8): Objektkategorien von Basis-WinForms zur Steuerung von GroupBox- und CheckBox
DoEasy. Steuerung (Teil 9): Neuanordnung von WinForms-Objektmethoden, RadioButton und Button-Steuerungen
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



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

Beigefügte Dateien |
MQL5.zip (4422.74 KB)
Risiko- und Kapitalmanagement durch Expert Advisor Risiko- und Kapitalmanagement durch Expert Advisor
In diesem Artikel geht es darum, was Sie in einem Backtest-Bericht nicht sehen können, was Sie erwarten sollten, wenn Sie automatisierte Handelssoftware verwenden, wie Sie Ihr Geld verwalten, wenn Sie Expert Advisors verwenden, und wie Sie einen erheblichen Verlust ausgleichen können, um in der Handelsaktivität zu bleiben, wenn Sie automatisierte Verfahren verwenden.
Neuronale Netze leicht gemacht (Teil 23): Aufbau eines Tools für Transfer Learning Neuronale Netze leicht gemacht (Teil 23): Aufbau eines Tools für Transfer Learning
In dieser Artikelserie haben wir bereits mehr als einmal über Transfer Learning berichtet. In diesem Artikel schlage ich vor, diese Lücke zu schließen und einen genaueren Blick auf Transfer Learning zu werfen.
Algorithmen zur Populationsoptimierung Algorithmen zur Populationsoptimierung
Dies ist ein einführender Artikel über die Klassifizierung von Optimierungsalgorithmen (OA). In dem Artikel wird versucht, einen Prüfstand (eine Reihe von Funktionen) zu erstellen, der zum Vergleich von OAs und vielleicht zur Ermittlung des universellsten Algorithmus unter allen bekannten Algorithmen verwendet werden soll.
DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl
In diesem Artikel werde ich den Umgang mit dem Aussehen von WinForms-Objekte nach dem Bewegen des Mauszeigers weg von dem Objekt, sowie die Entwicklung der TabControl WinForms-Objekt korrigieren und optimieren.