![DoEasy. Steuerung (Teil 15): TabControl WinForms Objekt — mehrere Reihen von Registerkartenüberschriften, Methoden zur Behandlung von Registerkarten](https://c.mql5.com/2/49/doeasy_015_600x314.jpg)
DoEasy. Steuerung (Teil 15): TabControl WinForms Objekt — mehrere Reihen von Registerkartenüberschriften, Methoden zur Behandlung von Registerkarten
Inhalt
- Konzept
- Verbesserung der Bibliotheksklassen
- TabField-Objekt — TabControl-Feld
- Test
- Was kommt als Nächstes?
Konzept
Wenn ein Steuerelement mehr Registerkarten hat, als in die Objektbreite passen (wir gehen davon aus, dass sie sich oben befinden), dann können die Überschriften, die nicht in das Element passen, abgeschnitten werden und deswegen Bildlaufschaltflächen enthalten. Wenn für das Objekt das Kennzeichen für den Mehrzeilenmodus gesetzt ist, werden die Kopfzeilen alternativ in mehreren Stücken (je nachdem, wie viele in der Größe des Elements enthalten sind) und in mehreren Zeilen angeordnet. Es gibt drei Möglichkeiten, die Größe der in Reihen angeordneten Registerkarten festzulegen (SizeMode):
- Normal — die Breite der Tabulatoren wird entsprechend der Breite des Kopfzeilentextes festgelegt, der in den Werten PaddingWidth und PaddingHeight der Kopfzeile angegebene Platz wird entlang der Kanten der Kopfzeile hinzugefügt;
- Fixed — feste Größe, die in den Steuerungseinstellungen angegeben ist. Der Kopfzeilentext wird abgeschnitten, wenn er nicht in seine Größe passt;
- FillToRight — Registerkarten, die in die Breite eines Steuerelements passen, werden auf die volle Breite gestreckt.
Wenn eine Registerkarte im aktiven mehrzeiligen Modus ausgewählt wird, wird ihre Kopfzeile, die nicht an das Registerfeld angrenzt, zusammen mit der gesamten Zeile, in der sie sich befindet, in die Nähe des Registerfelds verschoben, und die Kopfzeilen, die an das Feld angrenzten, nehmen den Platz der Zeile der ausgewählten Registerkarte ein.
Ich werde diesen Modus im aktuellen Artikel implementieren. Ich werde dies jedoch nur für die Registerkarten, die sich oben auf dem Steuerelement befinden, und für die Modi „Normal“ (normal) und „Fixed“ (fest) Registerkartengröße tun. Der Modus FillToRight und die Anordnung der Tabulatoren unten, links und rechts in allen drei Tabulatorgrößenmodi werden in späteren Artikeln implementiert (ebenso wie der Bildlaufmodus für Tabulatoren, die sich in derselben Zeile befinden, wenn der Multiline-Modus deaktiviert ist).
Um mit dem Tabulatorfeld zu interagieren, das zuvor als Container-Objekt der Klasse CContainer implementiert wurde, erstellen wir ein neues TabField-Objekt — den Nachfolger des Container-Objekts mit seinen eigenen Eigenschaften und Methoden für die vollwertige Arbeit mit dem Tabulatorfeld.
Verbesserung der Bibliotheksklassen
In den Bibliotheksdateien \MQL5\Include\DoEasy\Defines.mqh, und zwar in der Liste der grafischen Elementtypen, fügen wir den neuen Typ eines WinForms-Hilfsobjekts hinzu:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base //--- 'Container' object types are to be set below GRAPH_ELEMENT_TYPE_WF_CONTAINER, // Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, // Windows Forms GroupBox GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // Windows Forms TabControl //--- 'Standard control' object types are to be set below GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, // Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, // Base list object of Windows Forms elements GRAPH_ELEMENT_TYPE_WF_LIST_BOX, // Windows Forms ListBox GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // Windows Forms ButtonListBox //--- Auxiliary elements of WinForms objects GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, // Windows Forms ListBoxItem GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Windows Forms TabHeader GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, // Windows Forms TabField }; //+------------------------------------------------------------------+
Da dieses Objekt nicht als eigenständige Einheit funktioniert, ist es ein Hilfsobjekt, das als Teil des TabControls zusammen mit dem gleichen Hilfsobjekt TabHeader, das ich im vorherigen Artikel erstellt habe arbeitet.
Hinzufügen einer neuen Enumeration von Modi zur Einstellung der Tabulatorgröße:
//+------------------------------------------------------------------+ //| Location of an object inside a control | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_ALIGNMENT { CANV_ELEMENT_ALIGNMENT_TOP, // Top CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom CANV_ELEMENT_ALIGNMENT_LEFT, // Left CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; //+------------------------------------------------------------------+ //| Tab size setting mode | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_TAB_SIZE_MODE { CANV_ELEMENT_TAB_SIZE_MODE_NORMAL, // By tab header width CANV_ELEMENT_TAB_SIZE_MODE_FIXED, // Fixed size CANV_ELEMENT_TAB_SIZE_MODE_FILL, // By TabControl width }; //+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+
Ganz am Ende der Liste der ganzzahligen Eigenschaften eines grafischen Elements auf der Leinwand fügen wir zwei neue Eigenschaften hinzu und erhöhen wir die Anzahl der ganzzahligen Eigenschaften von 88 auf 90:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type //---... //---... CANV_ELEMENT_PROP_TAB_MULTILINE, // Several lines of tabs in TabControl CANV_ELEMENT_PROP_TAB_ALIGNMENT, // Location of tabs inside the control CANV_ELEMENT_PROP_TAB_SIZE_MODE, // Tab size setting mode CANV_ELEMENT_PROP_TAB_PAGE_NUMBER, // Tab index number CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (90) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Wir fügen zwei neue Eigenschaften zur Liste der möglicher Sortierkriterien von grafischen Elementen auf der Leinwand hinzu:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_TAB_MULTILINE, // Sort by the flag of several rows of tabs in TabControl SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT, // Sort by the location of tabs inside the control SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE, // Sort by the mode of setting the tab size SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER, // Sort by the tab index number SORT_BY_CANV_ELEMENT_ALIGNMENT, // Sort by the location of the object inside the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text SORT_BY_CANV_ELEMENT_DESCRIPTION, // Sort by graphical element description }; //+------------------------------------------------------------------+
Jetzt können wir Objekte nach diesen neuen Eigenschaften sortieren, auswählen und suchen.
Ich werde weiterhin mehr und mehr neue Objekte hinzufügen. Sie sind jedoch nicht für jedes grafische Objekt gedacht. Dennoch sind alle neu hinzugefügten Eigenschaften bisher in allen Objekten verfügbar. Weiterhin werden wir ihre Verfügbarkeit für Objekte einschränken, in denen solche Eigenschaften nicht vorhanden sein sollen, indem wir einfach Methoden zu den Klassen dieser Methodenobjekte hinzufügen, die das Kennzeichen für die Beibehaltung der einen oder anderen Eigenschaft zurückgeben. Ich habe diese virtuellen Methoden schon vor langer Zeit in den Basisklassen der Bibliothek für jedes Objekt implementiert. Hier fügen wir sie nicht für jedes neue Objekt hinzu, und zwar aus einem einfachen Grund: Wenn wir davon ausgehen, dass alle Objekte erstellt worden sind und es sich lohnt, bei der Verfügbarkeit von Eigenschaften Ordnung zu schaffen, dann werde ich alles auf einmal machen. Ich werde alle Eigenschaften für jedes der Steuerobjekte auf dem Panel ihrer Eigenschaften sichtbar machen, das im Chart erstellt wurde.
Ich habe neue Eigenschaften und Enumerationen hinzugefügt. Fügen wir nun Texte hinzu, um ihre Beschreibungen anzuzeigen.
In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:
MSG_LIB_TEXT_TOP, // Top MSG_LIB_TEXT_BOTTOM, // Bottom MSG_LIB_TEXT_LEFT, // Left MSG_LIB_TEXT_RIGHT, // Right MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL, // By tab header width MSG_LIB_TEXT_TAB_SIZE_MODE_FILL, // By TabControl width MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED, // Fixed size MSG_LIB_TEXT_CORNER_LEFT_UPPER, // Center of coordinates at the upper left corner of the chart MSG_LIB_TEXT_CORNER_LEFT_LOWER, // Center of coordinates at the lower left corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_LOWER, // Center of coordinates at the lower right corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_UPPER, // Center of coordinates at the upper right corner of the chart
...
MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // ButtonListBox control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Tab header MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, // Tab field MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // TabControl MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program
...
//--- CButtonListBox MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON, // Failed to set the group for the button with the index MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON, // Failed to set the Toggle flag to the button with the index //--- CTabControl MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ, // Failed to get TabControl tab //--- Integer properties of graphical elements
...
MSG_CANV_ELEMENT_PROP_TAB_MULTILINE, // Several lines of tabs in the control MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT, // Location of tabs inside the control MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE, // Tab size setting mode MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER, // Tab index number MSG_CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control //--- Real properties of graphical elements
und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:
{"Сверху","Top"}, {"Снизу","Bottom"}, {"Слева","Left"}, {"Справа","Right"}, {"По ширине текста заголовка вкладки","Fit to tab header text width"}, {"По ширине элемента управления TabControl","Fit TabControl Width"}, {"Фиксированный размер","Fixed size"}, {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"}, {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"}, {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"}, {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},
...
{"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""}, {"Заголовок вкладки","Tab header"}, {"Поле вкладки","Tab field"}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"},
...
//--- CButtonListBox {"Не удалось установить группу кнопке с индексом ","Failed to set group for button with index "}, {"Не удалось установить флаг \"Переключатель\" кнопке с индексом ","Failed to set the \"Toggle\" flag on the button with index "}, //--- CTabControl {"Не удалось получить вкладку элемента управления TabControl","Failed to get tab of TabControl"}, //--- Integer properties of graphical elements
...
{"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"}, {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"}, {"Режим установки размера вкладок","Tab Size Mode"}, {"Порядковый номер вкладки","Tab ordinal number"}, {"Местоположение объекта внутри элемента управления","Location of the object inside the control"}, //--- String properties of graphical elements
Da wir einen neuen Modus zum Zeichnen von Registerkartenüberschriften haben, müssen wir die Beschreibung des ausgewählten Modus zurückgeben. Wir implementieren in der Datei \MQL5\Include\DoEasy\Services\DELib.mqh der Bibliotheksdienstfunktionen die Funktion, die die Beschreibung des Modus zur Einstellung der Registerkartengröße zurückgibt:
//+------------------------------------------------------------------+ //| Return the description of the tab size setting mode | //+------------------------------------------------------------------+ string TabSizeModeDescription(ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode) { switch(mode) { case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL : return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL); break; case CANV_ELEMENT_TAB_SIZE_MODE_FIXED : return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED); break; case CANV_ELEMENT_TAB_SIZE_MODE_FILL : return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FILL); break; default : return "Unknown"; break; } } //+------------------------------------------------------------------+
Die Art der Einstellung der Größe der Tabulatoren wird an die Funktion übergeben, und je nachdem wird die entsprechende Textmeldung zurückgegeben.
In der Datei \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh der Basis-Grafikobjektklasse, und zwar in der Methode, die die Beschreibung des Grafikelementtyps zurückgibt, erstellen wir einen Abschnitt für Hilfsobjekte, verschieben die Beschreibung der Registerkartenüberschrift dorthin und fügen die Beschreibung eines neuen Typs hinzu — das Registerkartenfeld:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : //--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : //--- Auxiliary control objects type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD) : "Unknown" ); } //+------------------------------------------------------------------+
Ganz am Ende des letzten Artikels haben wir die Möglichkeit erörtert, eine Methode, die nach der Anzahl der Objekte eines bestimmten Typs sucht, leicht zu optimieren, um ihren Namen zu erzeugen. Wir haben festgestellt, dass in den beiden Methoden, die zusammenarbeiten, die Methode, die einen Objektnamen aus der String-Repräsentation der Enumerationskonstante, die den Objekttyp angibt, erzeugt, zweimal aufgerufen wird.
Um dies zu vermeiden, fügen wir in der Datei \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, in der die Methoden für die Erstellung von grafischen Elementnamen gespeichert sind, nämlich im Abschnitt für geschützte Klassen, eine weitere Methode hinzu — die überladene. Diese Methode dient dazu, den zuvor erstellten und aktuell bekannten Objektnamen zu erhalten, um ihn unter anderen grafischen Objekten im Chart zu suchen:
//--- Return the number of graphical elements (1) by type, (2) by name and type int GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const; int GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const; //--- Create and return the graphical element name by its type string CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type); private:
Außerhalb des Klassenkörper schreiben wir ihre Implementierung:
//+------------------------------------------------------------------+ //| Return the number of graphical elements by type | //+------------------------------------------------------------------+ int CGCnvElement::GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const { //--- Declare a variable with the number of graphical objects and //--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow()); //--- Create the name of a graphical object by its type string name=TypeGraphElementAsString(type); //--- In the loop by all chart and subwindow objects, for(int i=0;i<total;i++) { //--- get the name of the next object string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow()); //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE) continue; //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type, //--- then there is a graphical object of this type - increase the counter of objects of this type if(::StringFind(name_obj,name)>0) n++; } //--- Return the number of found objects by their type return n; } //+------------------------------------------------------------------+ //| Return the number of graphical elements by name and type | //+------------------------------------------------------------------+ int CGCnvElement::GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const { //--- Declare a variable with the number of graphical objects and //--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow()); //--- In the loop by all chart and subwindow objects, for(int i=0;i<total;i++) { //--- get the name of the next object string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow()); //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE) continue; //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type, //--- then there is a graphical object of this type - increase the counter of objects of this type if(::StringFind(name_obj,name)>0) n++; } //--- Return the number of found objects by their type return n; } //+------------------------------------------------------------------+
Im Vergleich zur Methode „pair“, bei der ein Objektname aus seinem Typ gebildet und in einem grafischen Objektnamen gesucht wird, erhalten wir hier diesen Namen in den Methodeneingaben und suchen im Objektnamen nach einer Teilzeichenkette, die den an die Methode übergebenen Namen enthält.
Im Allgemeinen ist nichts zu kompliziert. Ich habe einfach einen Aufruf der Methode zur Namenserstellung entfernt. Wenn der Name noch nicht bekannt ist, rufe ich die erste Methode auf. Wenn sie bekannt ist, wird die zweite verwendet.
Zuvor führte die Methode, die den Namen eines grafischen Elements anhand seines Typs erstellt und zurückgibt, zu einem doppelten Aufruf der Methode zur Erstellung eines Objektnamens anhand seines Typs, das erste Mal innerhalb der Methode und das zweite Mal innerhalb der aufgerufenen Methode, in der ebenfalls die Methode zur Erstellung eines Objektnamens anhand seines Typs aufgerufen wurde:
//+------------------------------------------------------------------+ //| Create and return the graphical element name by its type | //+------------------------------------------------------------------+ string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type) { return this.NamePrefix()+TypeGraphElementAsString(type)+(string)this.GetNumGraphElements(type); } //+------------------------------------------------------------------+
Nehmen wir Änderungen an dieser Methode vor: Wir erstellen einen Objektnamen, verwenden ihn, um den Rückgabestring zu erstellen, und senden ihn an eine neue überladene Methode, die nach der Anzahl solcher Objekte sucht:
//+------------------------------------------------------------------+ //| Create and return the graphical element name by its type | //+------------------------------------------------------------------+ string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type) { string name=TypeGraphElementAsString(type); return this.NamePrefix()+name+(string)this.GetNumGraphElements(name,type); } //+------------------------------------------------------------------+
Die Methode zur Erstellung des grafischen Objektelements wies einen ärgerlichen Fehler auf — wenn beim Erstellen eines grafischen Objekts ein Fehler auftrat, gab die Methode nie einen Fehler zurück (Code 0), aber das Objekt wurde nicht erstellt. Die Fehlerursachen konnten nur durch indirekte Methoden ermittelt werden. Dies gilt eher für die Entwicklung von Klassen grafischer Elemente als für ihre Verwendung durch einen Bibliotheksnutzer, da alle Fehler bei der Erstellung von Objekten bereits in der Phase der Klassenentwicklung beseitigt werden. Dennoch werden wir Änderungen vornehmen, die es uns ermöglichen, die Ursache des Fehlers besser zu verstehen:
//+------------------------------------------------------------------+ //| Create the graphical element object | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Chart ID const int wnd_num, // Chart subwindow const int x, // X coordinate const int y, // Y coordinate const int w, // Width const int h, // Height const bool redraw=false) // Flag indicating the need to redraw { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,this.m_name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num); return true; } int err=::GetLastError(); int code=(err==0 ? (w<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH : h<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT : ERR_OBJECT_ERROR) : err); string subj=(w<1 ? "Width="+(string)w+". " : h<1 ? "Height="+(string)h+". " : ""); CMessage::ToLog(DFUN_ERR_LINE+subj,code,true); return false; } //+------------------------------------------------------------------+
Wir weisen den Code des letzten Fehlers der Variablen zu (bei einem Fehler bei der Erstellung einer grafischen Ressource in der Klasse CCanvas der Standardbibliothek war er immer Null), prüfen dann den Fehlercode, und, wenn er gleich Null ist, dann prüfen wir die Breite und Höhe des erstellten Objekts. Wenn einer dieser Werte kleiner als eins ist, setzen wir den Code der entsprechenden Meldung oder einen allgemeinen Fehler bei der Erstellung eines grafischen Objekts in den Fehlercode. Ist der Fehlercode ungleich Null, weisen wir den Fehlercode der Variablen zu. Als Nächstes erstellen wir eine Zeichenkette mit einer zusätzlichen Beschreibung des Fehlercodes, die auch von der an die Methode übergebenen Breite und Höhe abhängt, und zeigen eine Meldung an, die den Methodennamen mit dem Index der Zeichenkette, der zusätzlichen Meldung und der Beschreibung des Fehlercodes enthält.
Alle Objekte der grafischen Elemente sind von der Klasse der Formularobjekte abgeleitet, die ihrerseits wiederum von anderen Klassen abgeleitet wurden. Aber sie verfügt über die Funktionalität für die Arbeit mit der Maus, sodass alle Objekte der grafischen Oberfläche des Programms irgendwie auf ihr basieren. Jedes Objekt, das an sein Basisobjekt angehängt ist, d.h. das Objekt, das aus dem Basisobjekt erzeugt wurde, erbt die Eigenschaften seines Erzeugers. Zu diesen Eigenschaften gehören auch Aktivität, Sichtbarkeit und Zugänglichkeit. Wenn das Objekt, aus dem ein anderes, ihm untergeordnetes Objekt erstellt wurde, nicht aktiv ist, d.h. nicht auf den Mauszeiger reagiert, dann sollte auch sein untergeordnetes Objekt dieses Verhalten erben (was später geändert werden kann). Wenn das Objekt nicht verfügbar ist (nicht aktiv und grau), dann sollte das untergeordnete Objekt auch so sein. Wenn das Objekt unsichtbar ist, dann sollte auch das untergeordnete Objekt ausgeblendet sein, was ja natürlich ist.
In der Panel-Objektklasse \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, und zwar in der Methode zum Erstellen eines neuen angehängten Elements und zum Hinzufügen zur Liste der angehängten Objekte, legen wir das Vererbte dieser Eigenschaften für alle untergeordneten Objekte fest (Objekte, die aus ihren Eltern erstellt werden):
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- Specify the element index in the list int num=this.m_list_elements.Total(); //--- Create a description of the default graphical element string descript=TypeGraphElementAsString(element_type); //--- Get the screen coordinates of the object relative to the coordinate system of the base object int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,descript,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return NULL; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return NULL; } //--- Set the minimum properties for a bound graphical element obj.SetBackgroundColor(colour,true); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); obj.SetID(this.GetMaxIDAll()+1); obj.SetNumber(num); obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(obj.CoordXRelative()); obj.SetCoordYRelativeInit(obj.CoordYRelative()); obj.SetVisible(this.IsVisible(),false); obj.SetActive(this.Active()); obj.SetEnabled(this.Enabled()); return obj; } //+------------------------------------------------------------------+
Jetzt erbt jedes neu erstellte angehängte Objekt diese Eigenschaften sofort von seinem Basisobjekt. Auf diese Weise werden Situationen vermieden, in denen ein untergeordnetes Objekt eines inaktiven Objekts plötzlich Aktivität zeigt oder ein untergeordnetes Objekt plötzlich auf dem Chart eines verborgenen Objekts erscheint, usw.
In ähnlicher Weise sollten Maus-Ereignis-Handler inaktive oder ausgeblendete Objekte übergehen.
In derselben Datei fügen wir die Zeichenkette, die die Behandlung eines ausgeblendeten oder nicht verfügbaren Objekts deaktivieren, zur letzten Mausereignisbehandlung hinzu:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled()) return; ENUM_MOUSE_FORM_STATE state=this.GetMouseState(); switch(state) {
Wenn dies genau das Objekt dieser Art ist, verlassen wir einfach die Methode.
Ich habe dafür gesorgt, dass ein Objekt, das einen Rahmen hat, nur dann gezeichnet wird, wenn das Flag für seine Neuzeichnung in der Methode zum Löschen und Füllen mit der Hintergrundfarbe gesetzt ist. Dieses Verhalten ist falsch. Es ist nicht immer notwendig, Objekte zu zeichnen, bei denen das gesamte Chart neu gezeichnet werden muss. Wenn das Redraw-Flag in diesem Fall jedoch deaktiviert ist, verschwindet der Objektrahmen, wenn die Methode aufgerufen wird. Außerdem gibt es bereits eine Bedingung für das Zeichnen eines Rahmens — sein Typ ist als nicht fehlend festgelegt. Daher entfernen wir in allen Klassen, in denen es die Methode Erase() gibt, die Prüfung auf das Redraw-Flag, um den Objektrahmen anzuzeigen:
if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
In \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh schreiben wir eine leere virtuelle Methode, um den Objektrahmen zu zeichnen:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { protected: color m_fore_color_init; // Initial color of the control text color m_fore_state_on_color_init; // Initial color of the control text when the control is "ON" private: //--- Return the font flags uint GetFontFlags(void); public: //--- Draw a frame virtual void DrawFrame(void){} //--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list CArrayObj *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type); int ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type); CGCnvElement *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
Die Methode wird in abgeleiteten Klassen, die einen Rahmen benötigen, anstelle der zuvor implementierten Methoden DrawFrameBevel(), DrawFrameFlat(), DrawFrameSimple() und DrawFrameStamp() der Formularobjektklasse verwendet, da diese Methoden meist zum Zeichnen eines bestimmten Formularobjektrahmens verwendet werden. Wenn wir für ein bestimmtes grafisches Element einen eigenen Rahmen zeichnen müssen, definieren wir die hier angegebene Methode um und zeichnen den erforderlichen Rahmen.
Die Methode Erase() prüfen wir nun nicht mehr die Aktualisierungsflags beim Zeichnen eines Rahmens:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
Jetzt wird der Rahmen immer dann gezeichnet, wenn sein Typ für ihn festgelegt ist. Dies geschieht in allen Dateien aller Klassen, die über Löschmethoden verfügen.
Am Ende der Methode, die die Beschreibung der Integer-Eigenschaft des Elements zurückgibt, fügen wir die Codeblöcke hinzu, um die neuen Eigenschaften der grafischen Elemente zurückzugeben:
property==CANV_ELEMENT_PROP_TAB_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property)) ) : property==CANV_ELEMENT_PROP_TAB_SIZE_MODE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TabSizeModeDescription((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(property)) ) : property==CANV_ELEMENT_PROP_TAB_PAGE_NUMBER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property)) ) : "" ); } //+------------------------------------------------------------------+ //| Return the description of the control real property | //+------------------------------------------------------------------+
Jetzt kann jedes grafische Element eine Zeichenkette zurückgeben, der die angegebene neue Eigenschaft und ihren Wert beschreibt.
In einigen geschützten Konstruktoren von grafischen Elementen gibt es jetzt ganz am Ende des Konstruktorcodes eine Zeichenkette, die bewirkt, dass das erstellte Objekt neu gezeichnet wird:
this.Redraw(false);
Dieses Verhalten ist falsch. Das Objekt sollte nur nach seiner endgültigen Erstellung neu gezeichnet werden, nicht bei jedem nächsten Konstruktor der gesamten Vererbungshierarchie des erstellten Objekts.
Stellen wir uns die Hierarchiekette der Objekte vor: Obj0 --> Obj1 --> Obj2 --> Obj3 --> Obj4 --> ... ... --> ObjN, wobei Obj0 das allererste Objekt in der Hierarchie ist, während ObjN das allerletzte ist, dann werden bei seiner Erzeugung alle Konstruktoren der gesamten Vererbungskette nacheinander aufgerufen. Und wenn jede von ihnen die angegebene Zeichenkette mit der Aktualisierung enthält, dann wird das Objekt jedes Mal neu gezeichnet.
Also wir entfernen diese Zeichenfolgen aus allen geschützten Konstruktoren aller Klassen.
Zum Beispiel \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh,
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CCommonBase::CCommonBase(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CWinFormBase(type,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetWidth(w); this.SetHeight(h); this.Initialize(); if(this.AutoSize()) this.AutoSetWH(); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.Redraw(false); } //+------------------------------------------------------------------+
Die gleichen Änderungen wurden bereits in den folgenden Klassen vorgenommen:
CLabel in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh und CButton in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh.
Wir implementieren in derselben Datei der Klasse CCommonBase die oben genannten Änderungen in den Erase()-Methoden, um die Überprüfung des Redraw-Flags zu entfernen:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CCommonBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CCommonBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
Im weiteren Verlauf werden wir diese Änderungen nicht in anderen Dateien anderer Klassen beschreiben.
Da es nicht mehr notwendig ist, das Flag zum Neizeichnen zwangsweise an die Methode zu senden, wird in der Objektklassendatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, und zwar in der Methode zum Neuzeichnen des Objekts, statt der Angabe des vorherigen true und des Neuzeichnens des gesamten Charts, das Redraw-Flag übergeben, das wiederum von außen an die Methode übergeben wird und die Notwendigkeit des Neuzeichnens beeinflusst:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CButton::Redraw(bool redraw) { //--- Fill the object with the background color this.Erase(this.BackgroundColor(),this.Opacity(),redraw); //--- Declare the variables for X and Y coordinates and set their values depending on the text alignment int x=0,y=0; CLabel::SetTextParamsByAlign(x,y); //--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.Update(redraw); } //+------------------------------------------------------------------+
Ganz am Ende der Methode, die den Schaltflächenstatus für alle Schaltflächen derselben Containergruppe auf „freigegeben“ setzt, fügen wir ein Neuzeichnen des Charts hinzu, in dem das Schaltflächenobjekt erstellt wurde, um Änderungen unmittelbar nach der Verarbeitung aller Schaltflächen anzuzeigen:
//+------------------------------------------------------------------+ //| Sets the state of the button to "released" | //| for all Buttons of the same group in the container | //+------------------------------------------------------------------+ void CButton::UnpressOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all objects of a certain type from the base object (Button or its descendant) CArrayObj *list=base.GetListElementsByType(this.TypeGraphElement()); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CButton *obj=list.At(i); if(obj==NULL) continue; //--- set the button status to "released", obj.SetState(false); //--- set the background color to the original one (the cursor is on another button outside this one) obj.SetBackgroundColor(obj.BackgroundColorInit(),false); obj.SetForeColor(obj.ForeColorInit(),false); obj.SetBorderColor(obj.BorderColorInit(),false); //--- Redraw the object to display the changes obj.Redraw(false); } } ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Ganz am Anfang des letzten Mausereignisses fügen wir eine Prüfung auf Unsichtbarkeit oder Unzugänglichkeit des Elements hinzu, wie wir es oben für das Formularobjekt getan haben:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CButton::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled()) return; ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) {
Die gleichen Verbesserungen wurden bereits in den folgenden Klassen vorgenommen:
CCheckBox in \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh und CTabHeader in \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.
Ich werde mich nicht weiter mit diesen Veränderungen befassen.
TabField-Objekt — TabControl-Feld
Im vorangegangenen Artikel habe ich die Hilfsklasse TabHeader für Registerkarten erstellt. Die Klasse wird vom Button-Objekt abgeleitet, da sie fast die gesamte Funktionalität dieses Objekts übernimmt. Eine solche Überschrift steht in direktem Zusammenhang mit dem Feld der Registerkarten, die zusammen eine Registerkarte bilden. Das Steuerelement selbst besteht aus mindestens zwei solcher Registerkarten.
Im vorigen Artikel habe ich ein Containerobjekt verwendet, um das Registerkartenfeld zu implementieren. Dies ist das Basisobjekt für alle Containerobjekte in der Bibliothek. Das Registerkartenfeld sollte untergeordnete Objekte enthalten, die auf diesem Feld erstellt wurden und dementsprechend diesem untergeordnet sind. Die Funktionalität des Basiscontainerobjekts für die Implementierung der Handhabung des Feldes ist natürlich nicht ausreichend. Erstellen wir daher eine neue Klasse des Registerkartenfeld-Objekts auf der Grundlage des Basis-Containerobjekts.
Im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ erstellen wir die neue Datei TabField.mqh der Klasse CTabField. Die Klasse wird nun vom Basis-Containerobjekt abgeleitet: Die Panel-Objektdatei sollte in die erstellte Klassendatei aufgenommen werden. Dadurch erhalten alle Dateien mit grafischen Objekten der Bibliothek Zugang zu ihr:
//+------------------------------------------------------------------+ //| TabField.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Containers\Panel.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabField : public CContainer { }
Im privaten Abschnitt deklarieren wir die Methode, die den Zeiger auf das diesem Feld entsprechende Header-Objekt zurückgibt, und die virtuelle Methode zur Erstellung von grafischen Elementen, die mit der Registerkarte (diesem Feldobjekt) verbunden sind:
//+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabField : public CContainer { private: //--- Find and return a pointer to the header object corresponding to the number of this tab CWinFormBase *GetHeaderObj(void); //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); protected:
Wenn wir den Typ des Header-Objekts in dieser Datei genau durch seinen Typ (CTabHeader) spezifizieren, der in dieser Klasse sichtbar ist, und versuchen, die gesamte Bibliothek zu kompilieren, indem wir die Hauptklasse der CEngine-Bibliothek kompilieren, dann werden wir eine große Anzahl von Fehlern und Warnungen über den unbekannten Typ von CTabHeader erhalten. Anstatt nach dem Fehler zu suchen, deklarieren wir einfach den Rückgabetyp als Basisobjekt für alle WinForms-Bibliotheksobjekte. Es wird genügen, sie hier zu behandeln. Außerhalb der Klasse können wir sie bereits von hier aus mit ihrem korrekten Typ erhalten.
Die virtuelle Methode zur Erstellung von angehängten grafischen Elementen wird benötigt, damit wir beim Zugriff auf das Feld untergeordnete Objekte erstellen können.
Im geschützten Abschnitt der Klasse deklarieren wir einen geschützten Konstruktor:
protected: //--- Protected constructor with object type, chart ID and subwindow CTabField(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public:
Im öffentlichen Abschnitt ‚public‘ deklarieren wir die Methoden für die Arbeit mit der Klasse und einen parametrischen Konstruktor:
public: //--- Draws a field frame depending on the location of the header virtual void DrawFrame(void); //--- (1) Set and (2) return the tab index void SetPageNumber(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value); } int PageNumber(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER); } //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Constructors CTabField(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Alle virtuellen Methoden überschreiben hier die Methoden der gleichnamigen Elternklassen, während die Methoden zum Setzen und Zurückgeben der Nummer der Registerkarte, zu der das Feld gehört, einfach den übergebenen Wert in die Objekteigenschaft setzen und ihn zurückgeben.
Geschützte und parametrische Konstruktoren:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CTabField::CTabField(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(type,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetPaddingAll(3); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTabField::CTabField(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetPaddingAll(3); } //+------------------------------------------------------------------+
Der einzige Unterschied zwischen ihnen besteht darin, dass der geschützte Konstruktor den Typ eines erstellten Objekts erhält (wenn es von ihm geerbt wurde), und dieser Typ wird an das übergeordnete Objekt übergeben. Im öffentlichen parametrischen Konstruktor wird der Typ, der an die übergeordnete Klasse übergeben wird, explizit angegeben — das Feldobjekt.
Im Konstruktorkörper wird das erstellte Objekt standardmäßig auf die gewünschten Werte einiger Eigenschaften gesetzt. Die übrigen Eigenschaften des Objekts werden in den übergeordneten Klassen festgelegt.
Die Methode, die den Zeiger auf die dem Tabulatorindex entsprechende Kopfzeile findet und zurückgibt:
//+------------------------------------------------------------------+ //| Find and return a pointer to the header | //| corresponding to the tab index | //+------------------------------------------------------------------+ CWinFormBase *CTabField::GetHeaderObj(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return NULL; //--- From the base object, get the list of tab header objects CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); //--- Leave only the object whose tab index matches this one in the obtained list list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL); //--- Return the pointer to the found object from the list return(list!=NULL ? list.At(0) : NULL); } //+------------------------------------------------------------------+
Hier wird die Logik der Methode in den Kommentaren zum Code detailliert beschrieben. Kurz gesagt, um dieses Feld mit seiner Kopfzeile abzugleichen, die in der Klasse gespeichert ist, an die das Objekt und die Kopfzeile gebunden sind, müssen wir auf das Basisobjekt zugreifen. Ich habe diese Methode schon vor langer Zeit eingeführt. Ermittelt den Zeiger auf das Basisobjekt und die Liste aller daran gebundenen Objekte mit dem Typ des Registerkarten-Titelobjekts. Die resultierende Liste sortieren wir so, dass nur ein Objekt mit der im Objekt eingestellten Tabulatornummer übrig bleibt. Wir haben also die diesem Feld entsprechende Kopfzeile gefunden, und der Zeiger darauf wird in der resultierenden Liste gespeichert. Geben wir es also zurück. Wenn das Objekt nicht gefunden wird, gibt die Methode NULL zurück.
Die Methode, die den Rand eines Elements in Abhängigkeit von der Position der Kopfzeile zeichnet:
//+------------------------------------------------------------------+ //| Draw the element frame depending on the header position | //+------------------------------------------------------------------+ void CTabField::DrawFrame(void) { //--- Set the initial coordinates int x1=0; int y1=0; int x2=this.Width()-1; int y2=this.Height()-1; //--- Get the tab header corresponding to the field CTabHeader *header=this.GetHeaderObj(); if(header==NULL) return; //--- Draw a rectangle that completely outlines the field this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity()); //--- Depending on the location of the header, draw a line on the edge adjacent to the header. //--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side //--- thus, visually the edge will not be drawn on the adjacent side of the header switch(header.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : // { } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : // { } break; default: break; } } //+------------------------------------------------------------------+
Hier wird die Logik der Methode in den Kommentaren zum Code detailliert beschrieben. Im Moment zeichnet die Methode einen Rahmen, wenn sich die Kopfzeilen oben und unten befinden. Die Standorte auf der rechten und linken Seite werden in späteren Artikeln behandelt. Kurz gesagt, nehmen wir an, dass eine Kopfzeile von oben an das Feld angrenzt. An der Stelle, an der sie sich treffen, sollte es keine Linie geben. Es war möglich, einen Feldrand mit einer gestrichelten Linie zu zeichnen, aber es gibt ein Problem mit der Anzahl der gezeichneten Punkte in Abhängigkeit von der Position der Kopfzeile. Befindet sie sich am linken oder rechten Rand des Feldes, so ist die Anzahl der Linienpunkte um einen weniger, wenn die Kopfzeile nicht am Rand des Feldes liegt.
Daher ist es einfacher, zunächst ein Rechteck zu zeichnen, das das Feld vollständig umreißt, und dann, nachdem man die Koordinaten der Kopfzeile erhalten hat, eine Linie mit der Hintergrundfarbe des Feldes dort zu ziehen, wo die Kopfzeile mit dem Feld in Kontakt ist. Auf diese Weise „löschen“ wir die Linie, in der die Kopfzeile das Feld berührt, um die korrekte Anzeige der Registerkarte zu erhalten.
Die virtuellen Methoden, die ein Element löschen und es mit Farbe und Deckkraft füllen:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CTabField::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CTabField::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
Die Methoden sind identisch mit genau denselben Methoden in anderen Klassen oder in übergeordneten Klassen. Sie wurden hier neu definiert, damit der Rahmen genau nach der oben beschriebenen Methode gezeichnet wird.
Die Methode zum Erstellen eines neuen grafischen Objekts:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabField::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Die virtuelle Methode ist auch identisch mit gleichnamigen Methoden in anderen Klassen. Hier haben wir den neuen Codeblock für die Erstellung des Feldobjekts, dessen Klasse berücksichtigt wird. Ich werde genau die gleichen Blöcke für die Erstellung des Objekts in anderen Klassen in den gleichen Methoden hinzufügen.
Mit dieser Methode werden wir in der Lage sein, Unterobjekte auf dem Registerkartenfeld zu erstellen.
Verbessern wir die Registerkartenkopfklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.
Aus dem privaten Bereich entfernen wir die Methode zur Einstellung der Objektgröße und -verschiebung:
//--- Sets the width, height and shift of the element depending on the state
void SetWH(void);
Auch deklarieren wir die Methoden zum Setzen der Strings der hervorgehobenen Tabulatorüberschrift an die richtige Position:
private: int m_width_off; // Object width in the released state int m_height_off; // Object height in the released state int m_width_on; // Object width in the selected state int m_height_on; // Object height in the selected state int m_col; // Header column index int m_row; // Header row index //--- Adjust the size and location of the element depending on the state bool WHProcessStateOn(void); bool WHProcessStateOff(void); //--- Draws a header frame depending on its position virtual void DrawFrame(void); //--- Set the string of the selected tab header to the correct position, (1) top, (2) bottom, (3) left and (4) right void CorrectSelectedRowTop(void); void CorrectSelectedRowBottom(void); void CorrectSelectedRowLeft(void); void CorrectSelectedRowRight(void); protected:
Im öffentlichen Teil deklarieren/schreiben wir neue Methoden und verbessern die bestehenden:
public: //--- Find and return a pointer to the field object corresponding to the tab index CWinFormBase *GetFieldObj(void); //--- Set the control size in the (1) released and (2) selected state bool SetSizeOff(void) { return(CGCnvElement::SetWidth(this.m_width_off) && CGCnvElement::SetHeight(this.m_height_off) ? true : false); } bool SetSizeOn(void) { return(CGCnvElement::SetWidth(this.m_width_on) && CGCnvElement::SetHeight(this.m_height_on) ? true : false); } //--- Sets the size of the control element void SetWidthOff(const int value) { this.m_width_off=value; } void SetHeightOff(const int value) { this.m_height_off=value; } void SetWidthOn(const int value) { this.m_width_on=value; } void SetHeightOn(const int value) { this.m_height_on=value; } //--- Set all sizes of the control element bool SetSizes(const int w,const int h); //--- Returns the control size int WidthOff(void) const { return this.m_width_off; } int HeightOff(void) const { return this.m_height_off;} int WidthOn(void) const { return this.m_width_on; } int HeightOn(void) const { return this.m_height_on; } //--- (1) Set and (2) return the index of the tab header row void SetRow(const int value) { this.m_row=value; } int Row(void) const { return this.m_row; } //--- (1) Set and (2) return the index of the tab header column void SetColumn(const int value) { this.m_col=value; } int Column(void) const { return this.m_col; } //--- Set the tab location void SetTabLocation(const int row,const int col) { this.SetRow(row); this.SetColumn(col); } //--- (1) Sets and (2) return the location of the object on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); } ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void) const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); } //--- (1) Set and (2) get the mode of setting the tab size void SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode) { this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode); } ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);} //--- (1) Set and (2) return the tab index void SetPageNumber(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value); } int PageNumber(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER); } //--- Sets the state of the control virtual void SetState(const bool flag); //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void); //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control virtual void SetPaddingLeft(const uint value) { this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,(value<1 ? 0 : value)); } virtual void SetPaddingTop(const uint value) { this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,(value<1 ? 0 : value)); } virtual void SetPaddingRight(const uint value) { this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,(value<1 ? 0 : value)); } virtual void SetPaddingBottom(const uint value) { this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,(value<1 ? 0 : value)); } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } virtual void SetPadding(const int left,const int top,const int right,const int bottom) { this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom); } protected: //--- Protected constructor with object type, chart ID and subwindow
Zuvor setzte die Methode SetAlignment() zusätzlich zur Eigenschaft auch die Rahmengröße. Der Rahmen hat hier nur eine Größe — 1 Pixel, also ist hier nichts zu ändern. Entfernen wir den unnötigen Code:
//--- (1) Sets and (2) return the location of the object on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) this.SetBorderSize(1,1,1,0); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) this.SetBorderSize(1,0,1,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) this.SetBorderSize(1,1,0,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) this.SetBorderSize(0,1,1,1); }
Die Konstruktoren der Klasse, geschützt und parametrisch:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetPadding(6,3,6,3); this.SetSizes(w,h); this.SetState(false); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetPadding(6,3,6,3); this.SetSizes(w,h); this.SetState(false); } //+------------------------------------------------------------------+
Anstatt die Größe der Kopfzeile separat einzustellen:
this.SetWidthOff(this.Width());
this.SetHeightOff(this.Height());
this.SetWidthOn(this.Width()+4);
this.SetHeightOn(this.Height()+2);
Wir rufen die Methode zum Einstellen der Größe der Registerkartenüberschriften auf, wobei die Größe in Abhängigkeit vom Einstellungsmodus für die Überschriftengröße festgelegt wird:
//+------------------------------------------------------------------+ //| Set all header sizes | //+------------------------------------------------------------------+ bool CTabHeader::SetSizes(const int w,const int h) { //--- If the passed width or height is less than 4 pixels, //--- make them equal to four pixels int width=(w<4 ? 4 : w); int height=(h<4 ? 4 : h); //--- Depending on the header size setting mode switch(this.TabSizeMode()) { //--- set the width and height for the Normal mode case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL : this.TextSize(this.Text(),width,height); width+=this.PaddingLeft()+this.PaddingRight(); height=h+this.PaddingTop()+this.PaddingBottom(); break; //--- For the Fixed and Fill modes, the size remains //--- passed to the method and adjusted case CANV_ELEMENT_TAB_SIZE_MODE_FIXED : break; //---CANV_ELEMENT_TAB_SIZE_MODE_FILL default: break; } //--- Set the results of changing the width and height to 'res' bool res=true; res &=this.SetWidth(width); res &=this.SetHeight(height); //--- If there is an error in changing the width or height, return 'false' if(!res) return false; //--- Set the changed size for different button states this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); return true; } //+------------------------------------------------------------------+
Die Logik der Methode wird in den Codekommentaren beschrieben. Die Größen werden nur für den Modus angepasst, bei dem die Breite der Kopfzeile mit der Breite des darauf angezeigten Textes übereinstimmt. Für den Modus „Fixed“ sollte die Größe der Kopfzeile festgelegt werden, d. h. sie bleibt diejenige, die der Methode in den Variablen w und h übergeben wurde, wird aber angepasst, wenn die Größen weniger als vier Pixel betragen (in den Variablen width und height). Die Art und Weise, wie die Breite des Containers gestreckt wird, wird in späteren Artikeln behandelt.
Die Methode, mit der der Zustand des Steuerelements festgelegt wird, hat sich grundlegend geändert:
//+------------------------------------------------------------------+ //| Set the state of the control | //+------------------------------------------------------------------+ void CTabHeader::SetState(const bool flag) { //--- Get the button state and set the new one passed to the method bool state=this.State(); CButton::SetState(flag); //--- If the previous state of the button does not match the set if(state!=this.State()) { //--- If the button is pressed if(this.State()) { //--- Call the button resizing method and bring it to the foreground this.WHProcessStateOn(); this.BringToTop(); //--- Get the base object the tab header is attached to (TabControl) CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Set the index of the selected tab to the TabControl object base.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber()); //--- Get the list of tab field objects from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); if(list==NULL) return; //--- In the loop through the received list, hide all fields that do not match the header for(int i=0;i<list.Total();i++) { //--- get the next tab field object CWinFormBase *obj=list.At(i); //--- If the object is not received or corresponds to the selected header, move on if(obj==NULL || obj.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)==this.PageNumber()) continue; //--- Set the ZOrder tab field as the base object and hide the field obj.SetZorder(base.Zorder(),false); obj.Hide(); } //--- Get the field object corresponding to the field header (this object) CWinFormBase *field=this.GetFieldObj(); if(field==NULL) return; //--- Display the field and set its ZOrder higher than other fields of the TabControl object, //--- draw the frame of the field object and bring it to the foreground field.Show(); field.SetZorder(base.Zorder()+1,false); field.DrawFrame(); field.BringToTop(); } //--- If the button is not pressed, call the method to restore the header size else this.WHProcessStateOff(); } } //+------------------------------------------------------------------+
Wenn die Schaltfläche ausgewählt ist (auf die Kopfzeile der Registerkarte geklickt), müssen wir die Schaltfläche (Kopfzeile) vergrößern und die Kopfzeile in den Vordergrund bringen. Dann müssen wir alle Registerkartenfelder ausblenden, die nicht mit der ausgewählten Überschrift übereinstimmen, während das Feld dieser Überschrift im Gegensatz dazu angezeigt und in den Vordergrund gebracht werden soll. Außerdem sollte das angezeigte Registerkartenfeld anklickbar sein, sodass sein ZOrder-Parameter größer ist als der der übrigen Steuerelemente, während für nicht ausgewählte Felder der ZOrder-Parameter niedriger sein sollte als der des ausgewählten Feldes. All dies wird durch die Methode erreicht.
In der Methode, die die Größe und Position eines Elements im Zustand „ausgewählt“ in Abhängigkeit von seiner Position anpasst, müssen wir die Methoden aufrufen, die die ausgewählte Kopfzeile an die Position verschieben, an der die Kopfzeile an ihr Feld angefügt wird. Wenn die Anordnung von Überschriften in mehreren Zeilen erlaubt ist, kann sich die ausgewählte Überschrift in einer Zeile befinden, die das Feld nicht berührt:
//+------------------------------------------------------------------+ //| Adjust the element size and location | //| in the "selected" state depending on its location | //+------------------------------------------------------------------+ bool CTabHeader::WHProcessStateOn(void) { //--- If failed to set a new size, leave if(!this.SetSizeOn()) return false; //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return false; //--- Depending on the header location, switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowTop(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()-2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowBottom(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()); } case CANV_ELEMENT_ALIGNMENT_LEFT : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowLeft(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates // { } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowRight(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates // { } break; default: break; } return true; } //+------------------------------------------------------------------+
Die Position der Registerkartenköpfe auf der linken und rechten Seite kann ich hier nicht beeinflussen. Das werde ich in späteren Artikeln tun.
In der Methode, die die Größe und Position eines Elements im „freigegebenen“ Zustand in Abhängigkeit von seiner Position anpasst, fügen wir verkürzte Codeblöcke hinzu, um Kopfzeilen auf der linken und rechten Seite zu behandeln:
//+------------------------------------------------------------------+ //| Adjust the element size and location | //| in the "released" state depending on its location | //+------------------------------------------------------------------+ bool CTabHeader::WHProcessStateOff(void) { //--- If failed to set a new size, leave if(!this.SetSizeOff()) return false; //--- Depending on the header location, switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX()+2,this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()+2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX()+2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()); } break; case CANV_ELEMENT_ALIGNMENT_LEFT : //--- shift the header to its original position and set the previous relative coordinates // { } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- shift the header to its original position and set the previous relative coordinates // { } break; default: break; } return true; } //+------------------------------------------------------------------+
Dies ist die Grundlage für künftige Verbesserungen.
Die Methode, die die Zeile einer ausgewählten Registerkartenüberschrift an die richtige Position am oberen Rand setzt:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position at the top | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowTop(void) { int row_pressed=this.Row(); // Selected header row int y_pressed=this.CoordY(); // Coordinate where all headers with Row() equal to zero should be moved to int y0=0; // Zero row coordinate (Row == 0) //--- If the zero row is selected, then nothing needs to be done - leave if(row_pressed==0) return; //--- Get the tab field object corresponding to this header and set the Y coordinate of the zero line CWinFormBase *obj=this.GetFieldObj(); if(obj==NULL) return; y0=obj.CoordY()-this.Height()+2; //--- Get the base object (TabControl) CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all tab headers from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); if(list==NULL) return; //--- Swap rows in the loop through all headers - //--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is a zero row if(header.Row()==0) { //--- move the header to the position of the selected row if(header.Move(header.CoordX(),y_pressed)) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one header.SetRow(-1); } } //--- If this is the clicked header line, if(header.Row()==row_pressed) { //--- move the header to the position of the zero row if(header.Move(header.CoordX(),y0)) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one header.SetRow(-2); } } } //--- Set the correct Row and Col for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it if(header.Row()==-1) header.SetRow(row_pressed); //--- If this is the selected row moved to the zero position, set Row of the zero row if(header.Row()==-2) header.SetRow(0); } } //+------------------------------------------------------------------+
Die Methodenlogik wird in den Codekommentaren beschrieben. Die Idee ist folgende: Wenn wir eine Registerkarte ausgewählt haben (auf die Schaltfläche für die Registerkartenüberschrift geklickt haben), die sich in der Nullzeile befindet (die Null ist in Kontakt mit dem Registerkartenfeld, die erste ist über der Null, die zweite ist über der ersten usw.), dann ist es nicht notwendig, die Zeile an eine neue Stelle zu verschieben. Wenn wir eine Registerkarte ausgewählt haben, deren Kopfzeile sich nicht in der Nullzeile befindet, müssen wir alle Kopfzeilen dieser Zeile an die Stelle der Nullzeile verschieben und die Nullzeile an die Stelle der Zeile, deren Kopfzeile angeklickt wurde. Die Nullzeile und die Zeile mit der ausgewählten Tabulatorüberschrift tauschen also immer die Plätze.
Diese Methode behandelt nur eine Situation, in der die Registerkartenüberschriften oben liegen. Sie können sich auch unten, links und rechts befinden. Ich werde die entsprechende Handhabung in späteren Artikeln implementieren. Für den Moment werde ich nur verkürzte Methoden für sie implementieren:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position at the bottom | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowBottom(void) { } //+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position on the left | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowLeft(void) { } //+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position on the right | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowRight(void) { } //+------------------------------------------------------------------+
Die Methode sucht und gibt den Zeiger auf das Feldobjekt zurück, das dem Registerindex entspricht:
//+------------------------------------------------------------------+ //| Find and return a pointer to the field object | //| corresponding to the tab index | //+------------------------------------------------------------------+ CWinFormBase *CTabHeader::GetFieldObj(void) { CWinFormBase *base=this.GetBase(); if(base==NULL) return NULL; CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL); return(list!=NULL ? list.At(0) : NULL); } //+------------------------------------------------------------------+
Die Methode ist identisch mit der Methode GetHeaderObj(), die den Zeiger auf den Tabulator-Header sucht und zurückgibt, den ich oben in der Tabulatorfeld-Objektklasse betrachtet habe. Diese Methode sucht nach dem Tabulatorfeld, das der Überschrift entspricht.
Im Ereignis „Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt“ ergänzen wir den Codeblock hinzu, in dem das entsprechende Registerfeld für die angeklickte Überschrift gesucht und angezeigt wird:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CTabHeader::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- If this is a simple button, set the initial background and text color if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Set the initial frame color this.SetBorderColor(this.BorderColorInit(),false); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); this.Redraw(true); } //--- If this is the toggle button, else { //--- if the button does not work in the group, set its state to the opposite, if(!this.GroupButtonFlag()) this.SetState(!this.State()); //--- if the button is not pressed yet, set it to the pressed state else if(!this.State()) this.SetState(true); //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); //--- Get the field object corresponding to the header CWinFormBase *field=this.GetFieldObj(); if(field!=NULL) { //--- Display the field, bring it to the front and draw a frame field.Show(); field.BringToTop(); field.DrawFrame(); } //--- Redraw an object and a chart this.Redraw(true); } //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } } //+------------------------------------------------------------------+
Wenn wir auf die Überschrift klicken, sollte das Ergebnis die Anzeige des entsprechenden Registerfeldes sein. Der hervorgehobene Codeblock tut genau das. Fügen wir das Neuzeichnen des Charts für eine einfache Schaltfläche hinzu (später werde ich das Erscheinungsbild der Kopfzeile implementieren, einschließlich einer Anzeige in Form von Schaltflächen). Ehrlich gesagt, weiß ich nicht mehr, welches der Experimente zu diesem String geführt hat. Aber lassen wir es erst einmal dabei bewenden — wir sind noch nicht so weit.
Die gesamte Steuerung von Kopfzeilen und Registerfeldern sollte über die Klasse TabControl erfolgen.
Lassen Sie uns die Klasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh verbessern.
Binden wir die Datei der neu geschriebenen Tabulatorfeld-Objektklasse ein und deklarieren neue Variablen und Methoden im privaten Abschnitt:
//+------------------------------------------------------------------+ //| TabControl.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" #include "..\TabHeader.mqh" #include "..\TabField.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabControl : public CContainer { private: int m_item_width; // Fixed width of tab headers int m_item_height; // Fixed height of tab headers int m_header_padding_x; // Additional header width if DrawMode==Fixed int m_header_padding_y; // Additional header height if DrawMode==Fixed int m_field_padding_top; // Padding of top tab fields int m_field_padding_bottom; // Padding of bottom tab fields int m_field_padding_left; // Padding of left tab fields int m_field_padding_right; // Padding of right tab fields //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Return the list of (1) headers and (2) tab fields CArrayObj *GetListHeaders(void) { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); } CArrayObj *GetListFields(void) { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); } //--- Set the tab as selected void SetSelected(const int index); //--- Set the tab as released void SetUnselected(const int index); //--- Set the number of a selected tab void SetSelectedTabPageNum(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value); } //--- Arrange the tab headers according to the set modes void ArrangeTabHeaders(void); //--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right void ArrangeTabHeadersTop(void); void ArrangeTabHeadersBottom(void); void ArrangeTabHeadersLeft(void); void ArrangeTabHeadersRight(void); public:
Im öffentlichen Abschnitt der Klasse deklarieren wir die neuen Methoden:
public: //--- Create the specified number of tabs bool CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text=""); //--- Create a new attached element bool CreateNewElement(const int tab_page, const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Return the number of (1) bound elements and (2) the bound element by the index in the list int TabElementsTotal(const int tab_page); CGCnvElement *GetTabElement(const int tab_page,const int index); //--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list in the specified tab CArrayObj *GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type); int TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type); CGCnvElement *GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index); //--- Return a pointer to the (1) tab header, (2) field and (3) the number of tabs CTabHeader *GetTabHeader(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); } CWinFormBase *GetTabField(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,index); } int TabPages(void) { return(this.GetListHeaders()!=NULL ? this.GetListHeaders().Total() : 0); } //--- (1) Set and (2) return the location of tabs on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; header.SetAlignment(alignment); } } ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void) const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); } //--- (1) Set and (2) get the mode of setting the tab size void SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode) { this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode); CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; header.SetTabSizeMode(mode); } } ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);} //--- Sets all tab headers to the PaddingWidth and PaddingHeight values void SetHeaderPadding(const int w,const int h); //--- Set all tab fields to Padding values void SetFieldPadding(const int top,const int bottom,const int left,const int right); //--- Return the PaddingWidth and PaddingHeight values of the tab headers int HeaderPaddingWidth(void) const { return this.m_header_padding_x; } int HeaderPaddingHeight(void) const { return this.m_header_padding_y; } //--- Return the Padding values of the tab fields int FieldPaddingTop(void) const { return this.m_field_padding_top; } int FieldPaddingBottom(void) const { return this.m_field_padding_bottom; } int FieldPaddingLeft(void) const { return this.m_field_padding_left; } int FieldPaddingRight(void) const { return this.m_field_padding_right; } //--- (1) Set and (2) return the flag allowing multiple rows of tab headers on the control void SetMultiline(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag); } bool Multiline(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); } //--- (1) Set and (2) return the fixed width of tab headers void SetItemWidth(const int value) { this.m_item_width=value; } int ItemWidth(void) const { return this.m_item_width; } //--- (1) Set and (2) return the fixed height of tab headers void SetItemHeight(const int value) { this.m_item_height=value; } int ItemHeight(void) const { return this.m_item_height; } //--- Set a fixed tab size void SetItemSize(const int w,const int h) { if(this.ItemWidth()!=w) this.SetItemWidth(w); if(this.ItemHeight()!=h) this.SetItemHeight(h); } //--- Set the header text (1) of the specified tab and (2) by index void SetHeaderText(CTabHeader *header,const string text); void SetHeaderText(const int index,const string text); //--- Set the tab specified by index to selected/not selected void Select(const int index,const bool flag); //--- Returns the (1) index, (2) the pointer to the selected tab int SelectedTabPageNum(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);} CWinFormBase *SelectedTabPage(void) { return this.GetTabField(this.SelectedTabPageNum()); } //--- Constructor CTabControl(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Im Klassenkonstruktor legen wir die Standardwerte für den Tabulatorgrößenmodus und Auffüllungswerte für die Überschriften und Felder fest:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CTabControl::CTabControl(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetOpacity(0,true); this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetBackgroundColorMouseDown(CLR_CANV_NULL); this.SetBackgroundColorMouseOver(CLR_CANV_NULL); this.SetBorderColor(CLR_CANV_NULL,true); this.SetBorderColorMouseDown(CLR_CANV_NULL); this.SetBorderColorMouseOver(CLR_CANV_NULL); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetItemSize(58,18); this.SetTabSizeMode(CANV_ELEMENT_TAB_SIZE_MODE_NORMAL); this.SetHeaderPadding(6,3); this.SetFieldPadding(3,3,3,3); } //+------------------------------------------------------------------+
In der Methode, die die angegebene Anzahl von Registerkarten erstellt, setzen wir den Zeiger auf ein Basisobjekt, den Registerindex und die Gruppe auf die erstellten Objekte der Überschriften und Felder. Wir legen den Auffüllungswert für Kopfzeilen und Felder fest, fügen den Tabulatortext hinzu, wenn die Methode einen leeren Text für die Tabulator-Kopfzeilen erhalten hat, legen den Modus für die Angabe der Kopfzeilengröße fest und legen deren Größe fest:
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab header int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs CTabHeader *header=NULL; CTabField *field=NULL; for(int i=0;i<total;i++) { //--- Depending on the location of tab headers, set their initial coordinates int header_x=2; int header_y=0; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) header_y=0; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) header_y=this.Height()-h; //--- Set the current X coordinate header_x=(header==NULL ? header_x : header.RightEdgeRelative()); //--- Create the TabHeader object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,w,h,clrNONE,255,this.Active(),false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header.SetBase(this.GetObject()); header.SetPageNumber(i); header.SetGroup(this.Group()+1); header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); header.SetBorderStyle(FRAME_STYLE_SIMPLE); header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); header.SetAlignment(this.Alignment()); header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight()); if(header_text!="" && header_text!=NULL) this.SetHeaderText(header,header_text+string(i+1)); else this.SetHeaderText(header,"TabPage"+string(i+1)); header.SetTabSizeMode(this.TabSizeMode()); header.SetSizes(w,h); //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields int field_x=0; int field_y=0; int field_h=this.Height()-header.Height(); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) field_y=header.BottomEdgeRelative(); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) field_y=0; //--- Create the TabField object (tab field) if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,this.Width(),field_h,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i); if(field==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field.SetBase(this.GetObject()); field.SetPageNumber(i); field.SetGroup(this.Group()+1); field.SetBorderSizeAll(1); field.SetBorderStyle(FRAME_STYLE_SIMPLE); field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); field.SetForeColor(CLR_DEF_FORE_COLOR,true); field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom()); field.Hide(); } //--- Arrange all headers in accordance with the specified display modes and select the specified tab this.ArrangeTabHeaders(); this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
Nachdem die angegebene Anzahl von Registerkarten erstellt wurde, rufen wir die Methode auf, die die Überschriften entsprechend den angegebenen Anzeigemodi anordnet.
Die Methode, die ein neues angehängtes Element erstellt:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CTabControl::CreateNewElement(const int tab_page, const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { CTabField *field=this.GetTabField(tab_page); if(field==NULL) { CMessage::ToLog(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ); ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ)," (Tab",(string)tab_page,")"); return false; } return field.CreateNewElement(element_type,x,y,w,h,colour,opacity,activity,redraw); } //+------------------------------------------------------------------+
Wir rufen das Registerkartenfeldobjekt mit dem angegebenen Registerindex ab und geben das Ergebnis des Aufrufs seiner Methode zur Erstellung eines neu gebundenen Elements zurück.
Die Methode, die die Registerkartenüberschriften entsprechend den angegebenen Modi anordnet:
//+------------------------------------------------------------------+ //| Arrange the tab headers | //| according to the specified modes | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeaders(void) { switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : this.ArrangeTabHeadersTop(); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.ArrangeTabHeadersBottom(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : this.ArrangeTabHeadersLeft(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.ArrangeTabHeadersRight(); break; default: break; } } //+------------------------------------------------------------------+
Je nach dem angegebenen Modus der Registerkartenanordnung rufen wir die entsprechenden Methoden auf.
Die Methode ordnet die Registerkartenüberschriften oben an:
//+------------------------------------------------------------------+ //| Arrange tab headers on top | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersTop(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Declare the variables int col=0; // Column int row=0; // Row int x1_base=2; // Initial X coordinate int x2_base=this.RightEdgeRelative()-2; // Final X coordinate int x_shift=0; // Shift the tab set for calculating their exit beyond the container int n=0; // The variable for calculating the column index relative to the loop index //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next tab header object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If the flag for positioning headers in several rows is set if(this.Multiline()) { //--- CANV_ELEMENT_TAB_SIZE_MODE_FIXED and CANV_ELEMENT_TAB_SIZE_MODE_NORMAL if(this.TabSizeMode()<CANV_ELEMENT_TAB_SIZE_MODE_FILL) { //--- Calculate the value of the right edge of the header, taking into account that //--- the origin always comes from the left edge of TabControl + 2 pixels int x2=header.RightEdgeRelative()-x_shift; //--- If the calculated value does not go beyond the right edge of the TabControl - 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(x2<x2_base) col=i-n; //--- If the calculated value goes beyond the right edge of the TabControl - 2 pixels, else { //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row row++; x_shift=header.CoordXRelative()-2; n=i; col=0; } //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates header.SetTabLocation(row,col); if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- Stretch the headers along the container width //--- CANV_ELEMENT_TAB_SIZE_MODE_FILL else { } } //--- If only one row of headers is allowed else { } } //--- The location of all tab headers is set. Now place them all together with the fields //--- according to the header row and column indices //--- Get the last header in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received and its row value is greater than zero if(last!=NULL && last.Row()>0) { //--- Calculate the offset of the tab field Y coordinate int y_shift=last.Row()*last.Height(); //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- get the tab field corresponding to the received header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- shift the tab header by the calculated row coordinates if(header.Move(header.CoordX(),header.CoordY()+y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } //--- shift the tab field by the calculated shift if(field.Move(field.CoordX(),field.CoordY()+y_shift)) { field.SetCoordXRelative(field.CoordX()-this.CoordX()); field.SetCoordYRelative(field.CoordY()-this.CoordY()); //--- change the size of the shifted field by the value of its shift field.Resize(field.Width(),field.Height()-y_shift,false); } } } } //+------------------------------------------------------------------+
Die Methodenlogik wird in den Codekommentaren beschrieben. Die Methode wurde bisher nur für die Platzierung von Kopfzeilen am oberen Rand des Containers in mehreren Reihen implementiert. Hier wird geprüft, ob die jeweils nächste Überschrift in der Schleife für die nächste Position in der Reihe so platziert wird, dass sie nicht über den Rand des Containers hinausgeht. Wenn dies der Fall ist, beginnen wir eine neue Zeile über der vorherigen. Berechnen wir die Referenzkoordinaten neu, sodass die Reihe, wenn sie sich auf die Koordinaten des Objekts abzüglich des berechneten Versatzes bezieht, wieder am linken Rand des Containers beginnt. Dann berechnen wir, ob die Objekte in den Container passen, und wenn nicht, gehen wir wieder zu einer neuen Reihe über. Nach jeder neuen Zeile werden die erhöhten Werte für Row (Zeile) in die Objekte gesetzt, während die Berechnung der Col-Werte (Spalte) neu beginnt. Am Ende der Schleife haben wir eine Liste von Werten der Zeilen und Spalten, die die Überschriften enthalten sollen.
In einer neuen Schleife durch die Liste der Überschriften werden diese an neuen Koordinaten platziert, die den Werten der im Objekt eingestellten Zeile und Spalte entsprechen, und die entsprechenden Tabulatorfeldobjekte werden um eine vom Maximalwert der Zeile berechnete Entfernung verschoben und in der Höhe um denselben Betrag verringert. Nach Abschluss der Schleife erhalten wir korrekt platzierte Kopfzeilen und die entsprechenden Felder.
In den folgenden Artikeln werden wir die Methode um die Platzierung von Kopfzeilen in anderen Modi ergänzen.
Die übrigen ähnlichen Methoden sind bisher als verkürzte Methoden implementiert:
//+------------------------------------------------------------------+ //| Arrange tab headers at the bottom | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersBottom(void) { } //+------------------------------------------------------------------+ //| Arrange tab headers on the left | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersLeft(void) { } //+------------------------------------------------------------------+ //| Arrange tab headers to the right | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersRight(void) { } //+------------------------------------------------------------------+
Die Methode, die alle Tabulatorüberschriften auf Padding-Werte setzt:
//+------------------------------------------------------------------+ //| Set all tab headers to Padding values | //+------------------------------------------------------------------+ void CTabControl::SetHeaderPadding(const int w,const int h) { this.m_header_padding_x=w; this.m_header_padding_y=h; CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; header.SetPadding(this.m_header_padding_x,this.m_header_padding_y,this.m_header_padding_x,this.m_header_padding_y); } } //+------------------------------------------------------------------+
Die Methode empfängt die Werte, die zusätzlich zur Breite und Höhe der Kopfzeile im Einstellungsmodus Normalgröße addiert werden. Die an die Methode übergebenen Werte werden sofort in die entsprechenden Variablen eingesetzt. Als Nächstes erhalten wir eine Liste aller Kopfzeilen und setzen in einer Schleife durch die resultierende Liste jede Kopfzeile aus der Liste auf die an die Methode übergebenen Auffüllungswerte.
Die Methode setzt die Auffüllungswerte für alle Registerkartenfelder:
//+------------------------------------------------------------------+ //| Set all tab fields to Padding | //+------------------------------------------------------------------+ void CTabControl::SetFieldPadding(const int top,const int bottom,const int left,const int right) { this.m_field_padding_top=top; this.m_field_padding_bottom=bottom; this.m_field_padding_left=left; this.m_field_padding_right=right; CArrayObj *list=this.GetListFields(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabField *field=list.At(i); if(field==NULL) continue; field.SetPadding(left,top,right,bottom); } } //+------------------------------------------------------------------+
Die Methode ist dieselbe wie oben. Aber hier werden die Auffüllungswerte von oben, unten, rechts und links an den Tabulatorrand übergeben. Diese Werte werden auf die entsprechenden Variablen und dann in einer Schleife auf jedes Registerkartenfeldobjekt gesetzt.
Die Methode, mit der die Registerkarte als ausgewählt festgelegt wird, wurde umgestaltet:
//+------------------------------------------------------------------+ //| Set the tab as selected | //+------------------------------------------------------------------+ void CTabControl::SetSelected(const int index) { //--- Get the header by index and CTabHeader *header=this.GetTabHeader(index); if(header==NULL) return; //--- set it to the "selected" state if(!header.State()) header.SetState(true); //--- save the index of the selected tab this.SetSelectedTabPageNum(index); } //+------------------------------------------------------------------+
Nun werden alle Manipulationen mit dem Verschieben des Objekts in den Vordergrund und der Auswahl des entsprechenden Feldes in der oben betrachteten Methode SetState() der Objektklasse Tabulatorüberschrift durchgeführt.
Die Methode, die die Registerkarte als nicht ausgewählt kennzeichnet, wurde auf die gleiche Weise umgestaltet:
//+------------------------------------------------------------------+ //| Select the tab as released | //+------------------------------------------------------------------+ void CTabControl::SetUnselected(const int index) { //--- Get the header by index and CTabHeader *header=this.GetTabHeader(index); if(header==NULL) return; //--- set it to the "released" state if(header.State()) header.SetState(false); } //+------------------------------------------------------------------+
Die Methode, die die Anzahl der gebundenen Elemente auf der angegebenen Registerkarte zurückgibt:
//+------------------------------------------------------------------+ //| Get the number of bound elements in the specified tab | //+------------------------------------------------------------------+ int CTabControl::TabElementsTotal(const int tab_page) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.ElementsTotal() : 0); } //+------------------------------------------------------------------+
Holt das Tabulatorfeldobjekt am angegebenen Index und gibt die Anzahl der daran gebundenen Objekte zurück.
Die Methode ermöglicht es herauszufinden, wie viele Objekte an die Registerkarte mit dem angegebenen Index angehängt sind.
Die Methode, die das gebundene Element nach Index in der Liste auf der angegebenen Registerkarte zurückgibt:
//+------------------------------------------------------------------+ //| Returns the bound element by index in the list | //| in the specified tab | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::GetTabElement(const int tab_page,const int index) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.GetElement(index) : NULL); } //+------------------------------------------------------------------+
Holt das Feldobjekt mit der angegebenen Indexnummer und gibt einen Zeiger auf das angehängte Element der Liste mit dem angegebenen Index zurück.
Die Methode ermöglicht es, einen Zeiger auf das gewünschte Element durch seinen Index auf der angegebenen Registerkarte zu erhalten.
Die Methode liefert die Liste der gebundenen Objekte auf der angegebenen Registerkarte nach einem Objekttyp:
//+------------------------------------------------------------------+ //| Return the list of bound controls by type | //| in the specified tab | //+------------------------------------------------------------------+ CArrayObj *CTabControl::GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.GetListElementsByType(type) : NULL); } //+------------------------------------------------------------------+
Ruft das Feldobjekt mit dem angegebenen Index ab und gibt die Liste der gebundenen Elemente mit dem angegebenen Typ zurück.
Die Methode ermöglicht es, die Liste der Elemente eines einzelnen angegebenen Typs aus der gewünschten Registerkarte zu erhalten.
Die Methode liefert die Anzahl der gebundenen Elemente auf der angegebenen Registerkarte durch einen Objekttyp:
//+------------------------------------------------------------------+ //| Get the list of bound elements by type | //| in the specified tab | //+------------------------------------------------------------------+ int CTabControl::TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.ElementsTotalByType(type) : 0); } //+------------------------------------------------------------------+
Wir holen das Feldobjekt mit der angegebenen Nummer und geben die Anzahl der Elemente des angegebenen Typs zurück, die sich auf der Registerkarte befinden.
Die Methode ermöglicht es herauszufinden, wie viele Elemente des angegebenen Typs auf der angegebenen Registerkarte platziert sind.
Die Methode liefert das gebundene Element nach Index in der Liste auf der angegebenen Registerkarte nach Objekttyp:
//+------------------------------------------------------------------+ //| Get (by type) the bound element by index in the list | //| in the specified tab | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.GetElementByType(type,index) : NULL); } //+------------------------------------------------------------------+
Ruft das Feldobjekt mit der angegebenen Indexnummer ab und gibt das Element des gewünschten Typs mit dem angegebenen Index in der Liste zurück.
Die Methode ermöglicht es, ein Element des gewünschten Typs anhand seiner Nummer aus der angegebenen Registerkarte zu erhalten.
Ganz am Ende der Methode zur Erstellung eines neuen grafischen Objekts ergänzen wir den Codeblock zur Erstellung eines Tabulatorfeldobjekts (extrakt) hinzu:
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Die Panel-Objektklassendatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh erhält die Datei der Tabulatorfeld-Objektklasse:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "..\TabField.mqh" #include "GroupBox.mqh" #include "TabControl.mqh" #include "..\..\WForms\Common Controls\ListBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" #include "..\..\WForms\Common Controls\ButtonListBox.mqh" //+------------------------------------------------------------------+
Am Ende der Methode, die ein neues grafisches Objekt erstellt, platzieren wir den Codeblock für die Erstellung eines Tabulatorfeldobjekts:
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Nun ist es möglich, Objekte dieses Typs im Panel-Objekt und seinen Nachfolgern zu erstellen.
In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh der Container-Basisklasse, und zwar in der Methode, die die Parameter für ein gebundenes Objekt festlegt, fügen wir dem Codeblock zum Festlegen der Parameter des Button-, TabHeader- und ListBoxItem-Objekts (Auszug aus dem Code) Parameter für das Tabulatorfeldobjekt hinzu:
//--- For "Label", "CheckBox" and "RadioButton" WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- set the object text color depending on the one passed to the method: //--- either the container text color, or the one passed to the method. //--- The frame color is set equal to the text color //--- Set the background color to transparent obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- For "Button", "TabHeader", TabField and "ListBoxItem" WinForms objects case GRAPH_ELEMENT_TYPE_WF_BUTTON : case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX :
Nachdem das Objekt in dieser Methode erstellt wurde, werden ihm die hier angegebenen Eigenschaften zugewiesen. Sie können später geändert werden.
In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh der Objektklasse GroupBox schreiben wir die Methode zum Zeichnen eines Rahmens virtuell:
//+------------------------------------------------------------------+ //| GroupBox object class of the WForms controls | //+------------------------------------------------------------------+ class CGroupBox : public CContainer { private: //--- Draw a frame virtual void DrawFrame(void); //--- Create a new graphical object
Da wir eine solche Methode im Basisobjekt aller WinForms-Objekte als virtuell deklariert haben, sollten nun alle gleichnamigen Methoden in den Nachfolgeklassen ebenfalls virtuell gemacht werden, um sie korrekt umzudefinieren und von anderen Klassen darauf zuzugreifen.
In der Methode zum Erstellen eines neuen grafischen Objekts fügen wir auch den Codeblock zum Erstellen eines Tabulatorfeldobjekts hinzu:
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
In der Kollektionsklasse für grafische Objekte in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, und zwar in den Methoden zur Erstellung grafischer Elemente auf der Leinwand, wurden die Variablennamen, die „name“ enthalten, durch „descript“ ersetzt, da der Algorithmus zur Benennung von grafischen Elementen im vorherigen Artikel geändert wurde. Der Variablentyp ist string, sodass keine Fehler zu erwarten waren, aber ich beschloss dennoch, die Namen der formalen Methodenparameter der Klarheit halber zu ersetzen. Betrachten wir folgendes Beispiel.
//--- Create a graphical form object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling int CreateFormHGradientCicle(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.GetMaxID()+1; CForm *obj=new CForm(chart_id,subwindow,descript,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetBackgroundColors(clr,true); obj.SetBorderColor(clr[0],true); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.BorderColor(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,false,true,redraw); return obj.ID(); } //--- Create the 'GroupBox' WinForms graphical object on canvas on the specified chart and subwindow
Auf die übrigen Änderungen werde ich hier nicht eingehen. Sie sind identisch und wurden bereits in die unten beigefügte Bibliotheksdatei implementiert.
In der Methode, die nach Interaktionsobjekten sucht, fügen wir eine Prüfung auf Sichtbarkeit und Zugänglichkeit eines Objekts hinzu. Wenn es unsichtbar oder nicht verfügbar ist, kann ein solches Objekt nicht bearbeitet werden. Sie sollte für die Interaktion mit der Maus nicht verfügbar sein:
//+------------------------------------------------------------------+ //| Search for interaction objects | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam) { //--- If a non-empty pointer is passed if(form!=NULL) { //--- Create the list of interaction objects int total=form.CreateListInteractObj(); //--- In the loop by the created list for(int i=total-1;i>WRONG_VALUE;i--) { //--- get the next form object CForm *obj=form.GetInteractForm(i); //--- If the object is received and the mouse cursor is located above the object, return the pointer to the found object if(obj==NULL) continue; if(!obj.IsVisible()) { continue; } if(!obj.Enabled()) { continue; } if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { CTabControl *tab_ctrl=obj; CForm *elm=tab_ctrl.SelectedTabPage(); if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return elm; } if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return obj; } } //--- Return the same pointer return form; } //+------------------------------------------------------------------+
Wenn das Objekt ausgeblendet oder nicht verfügbar ist, wird es hier übersprungen. Wenn es sich um ein TabControl handelt, holen wir uns sich eine ausgewählte Registerkarte davon.
Wenn sich der Cursor über der ausgewählten Registerkarte befindet, wird der Zeiger auf das Objekt des Registerfelds zurückgegeben.
Bei der Methode zur Nachbearbeitung eines ehemals aktiven Formulars unter dem Cursor werden alle ausgeblendeten und nicht zugänglichen Objekte übersprungen. Sie sollten nicht angefasst werden:
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Get the main object the form is attached to CForm *main=form.GetMain(); if(main==NULL) main=form; //--- Get all the elements attached to the form CArrayObj *list=main.GetListElements(); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CWinFormBase *elm=obj.GetInteractForm(j); if(elm==NULL || !elm.IsVisible() || !elm.Enabled()) continue; //--- determine the location of the cursor relative to the object //--- and call the mouse event handling method for the object elm.MouseFormState(id,lparam,dparam,sparam); elm.OnMouseEventPostProcessing(); } } ::ChartRedraw(main.ChartID()); } //+------------------------------------------------------------------+
In der Datei \MQL5\Include\DoEasy\Engine.mqh des Hauptbibliotheksobjekts wird die Methode GetWFPanel(), die das Objekt nach Namen zurückgibt, in GetWFPanelByName() umbenannt, während die Methode GetWFPanel() das Objekt nach seiner Beschreibung zurückgibt:
//--- Return the WForm Panel object by object name on the current chart CPanel *GetWFPanelByName(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object according to the description of the object on the current chart CPanel *GetWFPanel(const string descript) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_DESCRIPTION,descript,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by chart ID and object name
Da beide Methoden formale Parameter desselben Typs hatten, ist eine Methodenüberladung in dieser Situation nicht möglich. Aus diesem Grund habe ich eine der Methoden umbenannt.
Genau wie in der Kollektionsklasse der grafischen Elemente wurden alle Instanzen von „name“ in den formalen Parametern der Methoden, die WinForms-Objekte erzeugen, in „descript“ umbenannt.
Zum Beispiel:
//--- Create the WinForm Element object CGCnvElement *CreateWFElement(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool v_gradient=true, const bool c_gradient=false, const bool redraw=false) { //--- Get the created object ID int obj_id= ( //--- In case of a vertical gradient: v_gradient ? ( //--- if not a cyclic gradient, create an object with the vertical gradient filling !c_gradient ? this.m_graph_objects.CreateElementVGradient(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) : //--- otherwise, create an object with the cyclic vertical gradient filling this.m_graph_objects.CreateElementVGradientCicle(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) ) : //--- If this is not a vertical gradient: !v_gradient ? ( //--- if not a cyclic gradient, create an object with the horizontal gradient filling !c_gradient ? this.m_graph_objects.CreateElementHGradient(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) : //--- otherwise, create an object with the cyclic horizontal gradient filling this.m_graph_objects.CreateElementHGradientCicle(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) ) : WRONG_VALUE ); //--- return the pointer to an object by its ID return this.GetWFElement(obj_id); } //--- Create the WinForm Element object in the specified subwindow on the current chart
Das sind alle Änderungen und Verbesserungen, die ich für den aktuellen Artikel geplant habe.
Test
Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part115\ als TestDoEasy115.mq5.
Erstellen wir die neuen Enumerationen für die bedingte Kompilierung, um den Modus für die Einstellung der Größe der Header-Tabs wählen zu können:
enum ENUM_ELEMENT_ALIGNMENT { ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP, // Top ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT, // Left ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; enum ENUM_ELEMENT_TAB_SIZE_MODE { ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL, // Fit to tab header text width ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED, // Fixed size ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL, // Fit TabControl Width }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Increase and decrease }; enum ENUM_BORDER_STYLE { BORDER_STYLE_NONE=FRAME_STYLE_NONE, // No frame BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE, // Simple frame BORDER_STYLE_FLAT=FRAME_STYLE_FLAT, // Flat frame BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL, // Embossed (convex) BORDER_STYLE_STAMP=FRAME_STYLE_STAMP, // Embossed (concave) }; enum ENUM_CHEK_STATE { CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Undefined }; enum ENUM_ELEMENT_ALIGNMENT { ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP, // Top ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT, // Left ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; enum ENUM_ELEMENT_TAB_SIZE_MODE { ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL, // By tab header width ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED, // Fixed size ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL, // By TabControl width }; #endif //--- input parameters
In den EA-Eingaben fügen wir eine neue Variable hinzu, die den Modus für die Einstellung der Größe der Registerkartenüberschriften festlegt:
//--- input parameters sinput bool InpMovable = true; // Panel Movable flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Panel Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Panel Autosize mode sinput ENUM_BORDER_STYLE InpFrameStyle = BORDER_STYLE_SIMPLE; // Label border style sinput ENUM_ANCHOR_POINT InpTextAlign = ANCHOR_CENTER; // Label text align sinput ENUM_INPUT_YES_NO InpTextAutoSize = INPUT_NO; // Label autosize sinput ENUM_ANCHOR_POINT InpCheckAlign = ANCHOR_LEFT; // Check flag align sinput ENUM_ANCHOR_POINT InpCheckTextAlign = ANCHOR_LEFT; // Check label text align sinput ENUM_CHEK_STATE InpCheckState = CHEK_STATE_UNCHECKED; // Check flag state sinput ENUM_INPUT_YES_NO InpCheckAutoSize = INPUT_YES; // CheckBox autosize sinput ENUM_BORDER_STYLE InpCheckFrameStyle = BORDER_STYLE_NONE; // CheckBox border style sinput ENUM_ANCHOR_POINT InpButtonTextAlign = ANCHOR_CENTER; // Button text align sinput ENUM_INPUT_YES_NO InpButtonAutoSize = INPUT_YES; // Button autosize sinput ENUM_AUTO_SIZE_MODE InpButtonAutoSizeMode= AUTO_SIZE_MODE_GROW; // Button Autosize mode sinput ENUM_BORDER_STYLE InpButtonFrameStyle = BORDER_STYLE_NONE; // Button border style sinput bool InpButtonToggle = true ; // Button toggle flag sinput bool InpListBoxMColumn = true; // ListBox MultiColumn flag sinput bool InpButtListMSelect = false; // ButtonListBox Button MultiSelect flag //sinput ENUM_ELEMENT_ALIGNMENT InpHeaderAlignment = ELEMENT_ALIGNMENT_TOP; // TabHeader Alignment sinput ENUM_ELEMENT_TAB_SIZE_MODE InpTabPageSizeMode = ELEMENT_TAB_SIZE_MODE_NORMAL; // TabHeader Size Mode //--- global variables
Die Erstellung von WinForms-Objekten in OnInit() des EA sieht nun wie folgt aus:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,400,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 2 bound panel objects CPanel *obj=NULL; for(int i=0;i<2;i++) { //--- create the panel object with calculated coordinates, width of 90 and height of 40 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(prev==NULL ? xb : xb+prev.Width()+20); int y=0; if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false)) { obj=pnl.GetElement(i); if(obj==NULL) continue; obj.SetBorderSizeAll(3); obj.SetBorderStyle(FRAME_STYLE_BEVEL); obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),4*i),true); obj.SetForeColor(clrRed,true); //--- Calculate the width and height of the future text label object int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight(); int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom(); //--- Create a text label object obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,w,h,clrNONE,255,false,false); //--- Get the pointer to a newly created object CLabel *lbl=obj.GetElement(0); if(lbl!=NULL) { //--- If the object has an even or zero index in the list, set the default text color for it if(i % 2==0) lbl.SetForeColor(CLR_DEF_FORE_COLOR,true); //--- If the object index in the list is odd, set the object opacity to 127 else lbl.SetForeColorOpacity(127); //--- Set the font Black width type and //--- specify the text alignment from the EA settings lbl.SetFontBoldType(FW_TYPE_BLACK); lbl.SetTextAlign(InpTextAlign); lbl.SetAutoSize((bool)InpTextAutoSize,false); //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK)); //--- Set the frame width, type and color for a text label and update the modified object lbl.SetBorderSizeAll(1); lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle); lbl.SetBorderColor(CLR_DEF_BORDER_COLOR,true); lbl.Update(true); } } } //--- Create the WinForms GroupBox1 object CGroupBox *gbox1=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox1 int w=pnl.GetUnderlay().Width(); int y=obj.BottomEdgeRelative()+6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0,y,200,150,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox1=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0); if(gbox1!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox1.SetBorderStyle(FRAME_STYLE_STAMP); gbox1.SetBorderColor(pnl.BackgroundColor(),true); gbox1.SetForeColor(gbox1.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox1.SetText("GroupBox1"); //--- Create the CheckBox object gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false); //--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects CCheckBox *cbox=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,0); //--- If CheckBox is created and the pointer to it is received if(cbox!=NULL) { //--- Set the CheckBox parameters from the EA inputs cbox.SetAutoSize((bool)InpCheckAutoSize,false); cbox.SetCheckAlign(InpCheckAlign); cbox.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status cbox.SetText("CheckBox"); cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); cbox.SetBorderColor(CLR_DEF_BORDER_COLOR,true); cbox.SetChecked(true); cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState); } //--- Create 4 RadioButton WinForms objects CRadioButton *rbtn=NULL; for(int i=0;i<4;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? cbox.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,yrb+4,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+1)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(2); } } //--- Create 3 Button WinForms objects CButton *butt=NULL; for(int i=0;i<3;i++) { //--- Create the Button object int ybtn=(butt==NULL ? 12 : butt.BottomEdgeRelative()+4); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+20,ybtn,78,18,clrNONE,255,true,false); //--- get the pointer to the Button object by its index in the list of bound Button type objects butt=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i); //--- If Button is created and the pointer to it is received if(butt!=NULL) { //--- Set the Button parameters from the EA inputs butt.SetAutoSize((bool)InpButtonAutoSize,false); butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false); butt.SetTextAlign(InpButtonTextAlign); //--- Set the text color, as well as frame style and color butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,2),true); butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle); butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-10),true); //--- Set the 'toggle' mode depending on the settings butt.SetToggleFlag(InpButtonToggle); //--- Set the displayed text on the button depending on the 'toggle' flag string txt="Button"+string(i+1); if(butt.Toggle()) butt.SetText("Toggle-"+txt); else butt.SetText(txt); if(i<2) { butt.SetGroup(2); if(butt.Toggle()) { butt.SetBackgroundColorMouseOver(butt.ChangeColorLightness(butt.BackgroundColor(),-5)); butt.SetBackgroundColorMouseDown(butt.ChangeColorLightness(butt.BackgroundColor(),-10)); butt.SetBackgroundStateOnColor(C'0xE2,0xC5,0xB1',true); butt.SetBackgroundStateOnColorMouseOver(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-5)); butt.SetBackgroundStateOnColorMouseDown(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-10)); } } } } //--- Create 2 RadioButton WinForms objects rbtn=NULL; for(int i=0;i<2;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? butt.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,butt.CoordXRelative()-4,yrb+3,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i+4); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+5)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(3); } } } } //--- Create the GroupBox2 WinForms object CGroupBox *gbox2=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2 w=gbox1.Width()-1; int x=gbox1.RightEdgeRelative()+1; int h=gbox1.BottomEdgeRelative()-6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1); if(gbox2!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox2.SetBorderStyle(FRAME_STYLE_STAMP); gbox2.SetBorderColor(pnl.BackgroundColor(),true); gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox2.SetText("GroupBox2"); //--- Create the TabControl object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false); //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type CTabControl *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); //--- If TabControl is created and the pointer to it is received if(tab_ctrl!=NULL) { //--- Set the location of the tab headers on the element and the tab text, as well as create nine tabs tab_ctrl.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tab_ctrl.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP/*(ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment*/); tab_ctrl.SetMultiline(true); tab_ctrl.SetHeaderPadding(6,0); tab_ctrl.CreateTabPages(9,0,50,16,TextByLanguage("Вкладка","TabPage")); //--- Create the CheckedListBox object on the first tab tab_ctrl.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false); //--- get the pointer to the CheckedListBox object from the first tab by its index in the list of bound objects of the CheckBox type CCheckedListBox *check_lbox=tab_ctrl.GetTabElementByType(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0); //--- If CheckedListBox is created and the pointer to it is received if(check_lbox!=NULL) { check_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true); check_lbox.SetMultiColumn(InpListBoxMColumn); check_lbox.SetColumnWidth(0); check_lbox.CreateCheckBox(4,66); } //--- Create the ButtonListBox object on the second tab tab_ctrl.CreateNewElement(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,12,160,30,clrNONE,255,true,false); //--- get the pointer to the ButtonListBox object from the first tab by its index in the list of attached objects of the Button type CButtonListBox *butt_lbox=tab_ctrl.GetTabElementByType(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0); //--- If ButtonListBox is created and the pointer to it is received if(butt_lbox!=NULL) { butt_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true); butt_lbox.SetMultiColumn(true); butt_lbox.SetColumnWidth(0); butt_lbox.CreateButton(4,66,16); butt_lbox.SetMultiSelect(InpButtListMSelect); butt_lbox.SetToggle(InpButtonToggle); for(int i=0;i<butt_lbox.ElementsTotal();i++) { butt_lbox.SetButtonGroup(i,(i % 2==0 ? butt_lbox.Group()+1 : butt_lbox.Group()+2)); butt_lbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false)); } } //--- Create the ListBox object on the third tab int lbw=146; if(!InpListBoxMColumn) lbw=100; tab_ctrl.CreateNewElement(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,4,12,lbw,60,clrNONE,255,true,false); //--- get the pointer to the ListBox object from the third tab by its index in the list of attached objects of the ListBox type CListBox *list_box=tab_ctrl.GetTabElementByType(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0); //--- If ListBox has been created and the pointer to it has been received if(list_box!=NULL) { list_box.SetBackgroundColor(tab_ctrl.BackgroundColor(),true); list_box.SetMultiColumn(InpListBoxMColumn); list_box.CreateList(8,68); } //--- On the remaining tabs (3 - 8), place text labels with the name of the tab for(int i=3;i<9;i++) { CTabField *field=tab_ctrl.GetTabField(i); if(field==NULL) continue; tab_ctrl.CreateNewElement(i,GRAPH_ELEMENT_TYPE_WF_LABEL,1,1,field.Width()-2,field.Height()-2,clrNONE,255,true,false); CLabel *label=tab_ctrl.GetTabElementByType(i,GRAPH_ELEMENT_TYPE_WF_LABEL,0); if(label!=NULL) { label.SetTextAlign(ANCHOR_CENTER); label.SetText(tab_ctrl.GetTabHeader(i).Text()); } } } } } //--- Redraw all objects according to their hierarchy pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Ich hoffe, dass der gesamte Ablauf der Objekterstellung in den Kommentaren zum Code klar beschrieben ist. Hier erstellen wir ein TabControl mit neun Registerkarten auf dem zweiten GroupBox-Container — speziell um zu prüfen, wie sie in Reihen angeordnet werden. Auf den ersten drei Registerkarten legen wir die Objekte an, die wir zuvor im Container GroupBox2 erstellt haben. Diese drei Steuerelemente werden nun jeweils auf einer eigenen Registerkarte platziert. Die übrigen Registerkarten werden mit Textbeschriftungen versehen, die eine Beschreibung der Registerkarte enthalten, die dem Text in den Kopfzeilen entnommen ist.
Kompilieren Sie den EA und starten Sie ihn auf einem Chart:
Die Erstellung der Objekte dauert recht lange. Bald wird es notwendig sein, die Logik der Anzeige von Objekten während ihrer Massenerstellung zu ändern. Wir werden uns später damit befassen. Wenn Sie eine feste Größe für die Registerkartenüberschriften und eine Größe, die sich an die Schriftbreite anpasst, wählen, sehen Sie, dass die Größen der Registerkarten unterschiedlich sind. Die Auswahl der gewünschten Registerkarte und die Neuanordnung der Registerkartenreihen funktionieren korrekt. Die Objekte auf den Registerkarten sind für die Mausinteraktion verfügbar. Bis jetzt ist alles korrekt, sodass wir mit der Entwicklung der Kontrollfunktionen fortfahren können.
Was kommt als Nächstes?
Im nächsten Artikel werde ich meine Arbeit an dem TabControl WinForms Objekt fortsetzen.
*Vorherige Artikel in dieser Reihe:
DoEasy. Steuerung (Teil 10): WinForms-Objekte — Animieren der Nutzeroberfläche
DoEasy. Steuerung (Teil 11): WinForms Objekte — Gruppen, das WinForms-Objekt CheckedListBox
DoEasy. Steuerung (Teil 12): Basislistenobjekt, ListBox und ButtonListBox WinForms-Objekte
DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl
DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/11316
![DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container](https://c.mql5.com/2/48/MQL5-avatar-doeasy-library-2__4.png)
![Einen technischen Indikator selber machen](https://c.mql5.com/2/48/drawing-indicator__1.png)
![Neuronale Netze leicht gemacht (Teil 24): Verbesserung des Instruments für Transfer Learning](https://c.mql5.com/2/48/Neural_networks_made_easy_024.png)
![Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 27): Der Zukunft entgegen (II)](https://c.mql5.com/2/48/development__3.png)
![MQL5 - Sprache von Handelsstrategien, eingebaut ins Kundenterminal MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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.