
DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt
Inhalt
- Konzept
- Verbesserung der Bibliotheksklassen
- Die Klasse TabHeader — Das TabControl-Objekt des Kopfes der Registerkarte
- TabControl-Klasse — Wiederaufnahme der Entwicklung
- Test
- Was kommt als Nächstes?
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_BOX“ wird 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.
*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





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.