DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container
Inhalt
Konzept
Im vorangegangenen Artikel habe ich die Modi für die Anzeige von Registerkartenüberschriften erläutert:
Wenn ein Steuerelement mehr Registerkarten hat, als in die Objektbreite passen (wir gehen davon aus, dass sie sich oben befinden), dann können die Titel, die nicht in das Element passen, abgeschnitten werden und die Bildlaufschaltflächen erhalten. Wenn für das Objekt das Kennzeichen für den Mehrzeilenmodus gesetzt ist, werden die Titel alternativ in mehreren Teilen (je nachdem, wie viele in der Größe des Elements enthalten sind) und in mehreren Zeilen platziert. Es gibt drei Möglichkeiten, die Größe der in Reihen angeordneten Registerkarten festzulegen(SizeMode):
- Normal — die Breite der Tabs wird entsprechend der Breite des Titeltextes festgelegt, der in den Werten PaddingWidth und PaddingHeight des Titels angegebene Platz wird entlang der Kanten des Titels hinzugefügt;
- Fixed — feste Größe, die in den Steuerungseinstellungen angegeben ist. Der Titeltext wird abgeschnitten, wenn er nicht in seine Größe passt;
- FillToRight — Registerkarten, die in die Breite eines Steuerelements passen, werden zur vollen Breite aufgefüllt.
Wenn eine Registerkarte im aktiven mehrzeiligen Modus ausgewählt wird, wird ihr Titel, der nicht an das Registerkartenfeld angrenzt, zusammen mit der gesamten Zeile, in der sie sich befindet, in die Nähe des Registerkartenfelds verschoben, und die Titel, die an das Feld angrenzten, nehmen den Platz der Zeile der ausgewählten Registerkarte ein.
Ich habe auch die Funktionalität der Platzierung von Registerkarten auf der Oberseite des Steuerelements in den Modi Normal und Feststehend implementiert.
In diesem Artikel werde ich Registerkarten im Mehrzeilenmodus auf allen Seiten des Steuerelements platzieren und den Modus zum Einstellen der Größe der Registerkarten FillToRight hinzufügen — die Reihen der Registerkarten entsprechend der Größe des Steuerelements strecken. Wenn Reihen von Registerkartenüberschriften am oberen oder unteren Rand des Containers platziert werden, werden die Überschriften auf die Breite des Steuerelements gedehnt. Wenn die Kopfzeilen der Registerkarten nach links oder rechts verschoben werden, werden die Kopfzeilen an die Höhe des Steuerelements angepasst. In diesem Fall wird der Kopfzeilen-Streckbereich auf jeder Seite dieses Bereichs um zwei Pixel kleiner, da die ausgewählte Registerkarte beim Anklicken um 4 Pixel größer wird. Wenn wir also keine Lücke von zwei Pixeln für die marginale Überschrift lassen, dann geht ihr Rand über die Kontrolle hinaus, wenn sie ausgewählt wird und ihre Größe entsprechend zunimmt.
Für den Modus der Anzeige von Registerkartenüberschriften in einer Zeile und wenn es mehr von ihnen gibt, als durch die Größe des Steuerelements passen, werden alle Überschriften, die über den Container hinausgehen, außerhalb des Containers angeordnet. Wir verfügen noch nicht über ausreichende Funktionen, um grafische Elemente, die über den Container hinausgehen, zu beschneiden, daher wird dieser Modus noch nicht berücksichtigt. Ich werde sie in späteren Artikeln umsetzen.
Verbesserung der Bibliotheksklassen
Wir müssen in den Listen der Registerkartenüberschriften nach allen Überschriften suchen, die sich in derselben Zeile befinden, damit wir nur mit den Überschriften dieser Zeile arbeiten können. Am einfachsten ist es, dem WinForms-Objekt der Bibliothek neue Eigenschaften hinzuzufügen und die seit langem bestehenden Funktionen der Bibliothek zum Suchen und Sortieren von Objektlisten zu nutzen.
In \MQL5\Include\DoEasy\Defines.mqh fügen wir der Liste der Ganzzahl-Eigenschaften des grafischen Elements auf der Leinwand zwei neue Eigenschaften hinzu und erhöhen deren Gesamtzahl von 90 auf 92:
//+------------------------------------------------------------------+ //| 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_SIZE_MODE, // Tab size setting mode CANV_ELEMENT_PROP_TAB_PAGE_NUMBER, // Tab index number CANV_ELEMENT_PROP_TAB_PAGE_ROW, // Tab row index CANV_ELEMENT_PROP_TAB_PAGE_COLUMN, // Tab column index CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (92) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Wir sortieren nach neuer Eigenschaft zur Enumeration möglicher Kriterien zum Sortieren von grafischen Elementen auf der Leinwand hinzufügen:
//+------------------------------------------------------------------+ //| 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_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_TAB_PAGE_ROW, // Sort by tab row index SORT_BY_CANV_ELEMENT_TAB_PAGE_COLUMN, // Sort by tab column index 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 schnell alle Registerkartenüberschriften, die sich in derselben Zeile befinden, finden und zur Liste hinzufügen und ihre Gesamtgröße so berechnen, dass sie sich auf die Größe des Steuerelements ausdehnen und korrekt platziert werden.
Wenn wir dem Objekt neue Eigenschaften hinzufügen, müssen wir auch deren Beschreibung hinzufügen. Alle Eigenschaftsbeschreibungen befinden sich in einem mehrdimensionalen Array, wobei die erste Dimension den Nachrichtenindex enthält und die übrigen Dimensionen Texte in verschiedenen Sprachen enthalten. Im Moment haben wir Nachrichtentexte in Englisch und Russisch, aber andere Sprachen können leicht hinzugefügt werden, indem man die Array-Dimension vergrößert und Texte in den entsprechenden Sprachen in den erforderlichen Dimensionen hinzufügt.
In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:
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_TAB_PAGE_ROW, // Tab row index MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN, // Tab column index MSG_CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control //--- Real properties of graphical elements //--- String properties of graphical elements MSG_CANV_ELEMENT_PROP_NAME_OBJ, // Graphical element object name MSG_CANV_ELEMENT_PROP_NAME_RES, // Graphical resource name MSG_CANV_ELEMENT_PROP_TEXT, // Graphical element text MSG_CANV_ELEMENT_PROP_DESCRIPTION, // Graphical element description }; //+------------------------------------------------------------------+
und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:
{"Режим установки размера вкладок","Tab Size Mode"}, {"Порядковый номер вкладки","Tab ordinal number"}, {"Номер ряда вкладки","Tab row number"}, {"Номер столбца вкладки","Tab column number"}, {"Местоположение объекта внутри элемента управления","Location of the object inside the control"}, //--- String properties of graphical elements {"Имя объекта-графического элемента","The name of the graphic element object"}, {"Имя графического ресурса","Image resource name"}, {"Текст графического элемента","Text of the graphic element"}, {"Описание графического элемента","Description of the graphic element"}, }; //+---------------------------------------------------------------------+
Ich habe die Nachrichtenklasse der Bibliothek und das Konzept der Speicherung ihrer Daten in einem separaten Artikel behandelt.
Bei der Verwendung der Klasse CCanvas aus der MQL5-Standardbibliothek ist es nicht immer möglich, den Code des Fehlers zu erhalten, der uns daran hinderte, ein grafisches Element zu erstellen. Ich füge nach und nach Korrekturen in die Bibliothek ein, damit die Nutzer verstehen, warum ein Element nicht erstellt wurde.
Wir fügen in der Datei \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh für grafische Elemente, und zwar in den Methoden zum Festlegen der Breite und Höhe, eine Beschreibung des Größenänderungsfehlers zusammen mit der Anzeige der Fehlermeldung hinzu:
//+------------------------------------------------------------------+ //| Set a new width | //+------------------------------------------------------------------+ bool CGCnvElement::SetWidth(const int width) { if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width) return true; if(!this.m_canvas.Resize(width,this.m_canvas.Height())) { CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH); return false; } this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width); return true; } //+------------------------------------------------------------------+ //| Set a new height | //+------------------------------------------------------------------+ bool CGCnvElement::SetHeight(const int height) { if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height) return true; if(!this.m_canvas.Resize(this.m_canvas.Width(),height)) { CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT); return false; } this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height); return true; } //+------------------------------------------------------------------+
Hier erhält die Makrosubstitution eine Beschreibung des Elementtyps, dessen Größe nicht geändert werden konnte, und den an die Methode übergebenen Parameterwert. Das macht es einfacher, Fehler bei der Entwicklung von Bibliotheksklassen zu verstehen.
Um die Beschreibungen der beiden neuen Eigenschaften eines grafischen Elements anzeigen zu können, müssen wir der Methode, die die Beschreibung der Ganzzahl-Eigenschaft des Elements zurückgibt, in \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh einen Codeblock hinzufügen:
//+------------------------------------------------------------------+ //| Return the description of the control integer property | //+------------------------------------------------------------------+ string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false) { return ( property==CANV_ELEMENT_PROP_ID ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_TYPE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TYPE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.TypeElementDescription() ) : //---... //---... 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_TAB_PAGE_ROW ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_ROW)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_TAB_PAGE_COLUMN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN)+ (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)) ) : "" ); } //+------------------------------------------------------------------+
Hier erstellen wir je nach der an die Methode übergebenen Eigenschaft eine Textnachricht und lassen diese von der Methode zurückgeben.
Lassen Sie uns nun die Klassen des TabControl WinForms Objekts finalisieren.
Das Objekt besteht aus einem Container, in dem sich die Registerkarten befinden. Der Container wiederum besteht aus zwei Hilfsobjekten — dem Registerfeld und seiner Überschrift. Alle Objekte, die sich auf der Registerkarte befinden sollen, werden auf dem Registerkartenfeld platziert, und wir wählen die Registerkarte, die wir sehen und mit der wir arbeiten wollen, als Registerkartenkopf aus. Registerkartentitel sind in der Klasse TabHeader implementiert, die von dem WinForms-Objekt Button in \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh abgeleitet ist.
Da wir jetzt zwei neue Eigenschaften haben, die die Position der Registerkarte Kopfzeile in der allgemeinen Kopfzeilenliste angeben (Kopfzeile und Spalte, die verwendet werden, um sie auf einem Steuerelement zu platzieren), entfernen wir zwei private Variablen, die diese Eigenschaften gespeichert haben:
//+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { private: int m_width_off; // Object width in the released state int m_height_off; // Object height in the released state int m_width_on; // Object width in the selected state int m_height_on; // Object height in the selected state int m_col; // Header column index int m_row; // Header row index //--- 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:
In den öffentlichen Methoden, die diese beiden Eigenschaften setzen und zurückgeben, werden ihre Werte nun auf die Objekteigenschaften gesetzt (und von diesen zurückgegeben) und nicht mehr auf die Variablen, wie es vorher der Fall war:
//--- 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 Tab row index void SetRow(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_ROW,value); } int Row(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_ROW); } //--- (1) Set and (2) return the Tab column index void SetColumn(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_COLUMN,value); } int Column(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_COLUMN); } //--- Set the tab location void SetTabLocation(const int row,const int col) { this.SetRow(row); this.SetColumn(col); }
Das Objekt für die Registerkartenüberschrift wird im Konstruktor mit Standardwerten für die Größe erstellt und dann an den Größenmodus für die Überschrift angepasst, der in der Objektcontainerklasse festgelegt wurde. Dies liegt daran, dass wir für alle WinForms-Objekte der Bibliothek die gleichen Werte ihrer Parameter verwenden, die allen Objekten gemeinsam sind, während wir zusätzliche Parameter, die zu einem bestimmten Objekt gehören, nach dessen Erstellung einstellen. Dies hat sowohl Vorteile als auch Einschränkungen. Die Bequemlichkeit liegt in der Tatsache, dass wir jedes Objekt mit einer Methode erstellen können, während die Einschränkungen darin liegen, dass es nicht immer möglich ist, ein Objekt sofort mit den gewünschten Werten seiner Eigenschaften zu erstellen, und diese nach der Erstellung des Objekts installiert werden müssen.
In diesem Fall handelt es sich um die Kopfgröße, die vom Größeneinstellungsmodus abhängt. In diesem Fall ändert sich die Größe des ursprünglich in den angegebenen Koordinaten erstellten Objekts weiter, und seine Anfangskoordinate, die sich in der oberen linken Ecke des konstruierten Objekts befindet, ist nicht mehr dort, wo sie ursprünglich geplant war. Daher müssen wir nicht nur die Größe der Kopfzeile ändern, sondern auch ihre Position in der gewünschten Koordinate kontrollieren, da das Objekt in einigen Fällen verschoben wird und sich außerhalb seines Containers befindet.
Für den Modus „Normal“ können wir im Voraus herausfinden, welche Größen die Kopfzeile erhalten wird. In diesem Modus passt sich die Größe des Objekts an den darauf geschriebenen Text an, und dieser Text ist uns bekannt. Wenn sich Kopfzeilen am oberen und unteren Rand des Containers befinden, werden die für das Objekt festgelegten Auffüllungswerte (PaddingLeft und PaddingRight) zu der durch die Textgröße des Objekts berechneten Breite und Höhe addiert, während PaddingTop und PaddingBottom zur Höhe addiert werden. Wenn Kopfzeilen links und rechts vom Container (vertikal) positioniert sind, werden PaddingLeft und PaddingRight zur Höhe des Objekts und PaddingTop und PaddingBottom zur Breite hinzugefügt, da es optisch so aussieht, als sei die Kopfzeile einfach um 90° gedreht und ihr Text vertikal, sodass die tatsächliche Höhe des grafischen Elements die sichtbare Breite des vertikal gedrehten Objekts ist.
Nehmen wir Änderungen an der Methode vor, die alle Titelgrößen festlegt. Im Codeblock für die Einstellung der Kopfzeilengröße auf die Textgröße für den Modus „Normal“ wird die Steuerung der Seite des Containers, auf der sich die Kopfzeilen befinden, implementiert — um die Kopfzeilen oben und unten einzustellen, werden die Padding-Werte in der richtigen Reihenfolge hinzugefügt — Padding wird zur Breite links und rechts hinzugefügt, während es im Falle der Höhe oben und unten hinzugefügt wird. Um Kopfzeilen links und rechts zu platzieren, werden die zu Breite und Höhe hinzugefügten Auffüllungswerte vertauscht — die Auffüllung oben und unten wird zur Breite und die Auffüllung links und rechts wird zur Höhe hinzugefügt:
//--- 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 : switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.TextSize(this.Text(),width,height); width+=this.PaddingLeft()+this.PaddingRight(); height=h+this.PaddingTop()+this.PaddingBottom(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : case CANV_ELEMENT_ALIGNMENT_RIGHT : this.TextSize(this.Text(),height,width); height+=this.PaddingLeft()+this.PaddingRight(); width=w+this.PaddingTop()+this.PaddingBottom(); break; default: break; } break; //---CANV_ELEMENT_TAB_SIZE_MODE_FIXED //---CANV_ELEMENT_TAB_SIZE_MODE_FILL //--- For the Fixed mode, the dimensions remain specified, //--- In case of Fill, they are calculated in the StretchHeaders methods of the TabControl class default: break; } //--- Set the results of changing the width and height to 'res'
Am Ende der Methode werden die Größen für die Kopfzeile im markierten und nicht markierten Zustand auf genau dieselbe Weise festgelegt. Da bei der Auswahl einer Registerkarte durch einen Klick auf die Kopfzeile die Kopfzeile der ausgewählten Registerkarte sich an drei Seiten um zwei Pixel vergrößert, sollte die tatsächliche Größe der horizontal angeordneten Kopfzeile in der Breite um 4 Pixel und in der Höhe um zwei Pixel erhöht werden. Wenn eine Kopfzeile vertikal angeordnet ist, entspricht die tatsächliche Objektbreite der Höhe der vertikal gedrehten Kopfzeile und die tatsächliche Höhe der Breite der Kopfzeile. In diesem Fall wird die Breite um zwei und die Höhe um vier Pixel erhöht:
//--- Set the changed size for different button states switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : case CANV_ELEMENT_ALIGNMENT_RIGHT : this.SetWidthOn(this.Width()+2); this.SetHeightOn(this.Height()+4); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); break; default: break; }
Jetzt sieht die Methode mit allen implementierten Änderungen wie folgt aus:
//+------------------------------------------------------------------+ //| 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 : switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.TextSize(this.Text(),width,height); width+=this.PaddingLeft()+this.PaddingRight(); height=h+this.PaddingTop()+this.PaddingBottom(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : case CANV_ELEMENT_ALIGNMENT_RIGHT : this.TextSize(this.Text(),height,width); height+=this.PaddingLeft()+this.PaddingRight(); width=w+this.PaddingTop()+this.PaddingBottom(); break; default: break; } break; //---CANV_ELEMENT_TAB_SIZE_MODE_FIXED //---CANV_ELEMENT_TAB_SIZE_MODE_FILL //--- For the Fixed mode, the dimensions remain specified, //--- In case of Fill, they are calculated in the StretchHeaders methods of the TabControl class 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 switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : case CANV_ELEMENT_ALIGNMENT_RIGHT : this.SetWidthOn(this.Width()+2); this.SetHeightOn(this.Height()+4); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); break; default: break; } return true; } //+------------------------------------------------------------------+
In der Methode, die die Größe und Position des Elements im „ausgewählten“ Zustand in Abhängigkeit von seiner Position anpasst, habe ich bisher keine Verschiebung der vergrößerten Kopfzeile für Fälle implementiert, in denen sich die Titel links und rechts des Steuerelements befinden. Außerdem habe ich einen kleinen Fehler im Verarbeitungsblock für die untere Kopfzeile gemacht — ich habe den „break“-Operator übersprungen, was keine Fehler verursacht hat, da alle Fälle leer waren und kein Code aufgerufen wurde. Dies führt nun zu einem falschen Verhalten — der Fall, der auf die übersprungene „break“-Anweisung folgt, wird verarbeitet.
Fügen wir die Codeblöcke hinzu, die die vergrößerte Kopfzeile um zwei Punkte in die richtige Richtung verschieben, um Kopfzeilen nach links und rechts zu positionieren:
//+------------------------------------------------------------------+ //| 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 title 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()); } break; 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 if(this.Move(this.CoordX()-2,this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()-2); } 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 if(this.Move(this.CoordX(),this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()); this.SetCoordYRelative(this.CoordYRelative()-2); } break; default: break; } return true; } //+------------------------------------------------------------------+
Fügen wir die Codeblöcke hinzu, die die Kopfzeile mit der wiederhergestellten Größe wieder an ihre ursprüngliche Position verschieben, nachdem die Kopfzeile bei der Vergrößerung in der obigen Methode während ihrer Auswahl verschoben wurde:
//+------------------------------------------------------------------+ //| 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 title 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 if(this.Move(this.CoordX()+2,this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()+2); } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX(),this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()); this.SetCoordYRelative(this.CoordYRelative()+2); } break; default: break; } return true; } //+------------------------------------------------------------------+
Nach diesen Verbesserungen werden die Überschriften der Registerkarten auf der linken oder rechten Seite nun korrekt, wenn sie ausgewählt werden, vergrößert und verkleinert, wenn sie abgewählt werden, wobei sie visuell etwas größer werden und visuell an ihrem ursprünglichen Platz bleiben.
Wenn die Überschriften der Registerkarten in mehreren Reihen angeordnet sind, müssen wir bei der Auswahl einer Registerkarte, deren Überschrift nicht direkt an die Registerkarte selbst angrenzt, sondern irgendwo zwischen den Reihen der anderen Registerkarten liegt, die gesamte Reihe, in der sich die Überschrift der ausgewählten Registerkarte befindet, in die Nähe der Registerkartenfelder verschieben und die Zeile verschieben, die zuvor an die Felder angrenzte, um die Zeile mit der ausgewählten Überschrift zu ersetzen. Eine ähnliche Methode zur Platzierung von Registerkartenüberschriften über dem Steuerelement habe ich bereits in einem früheren Artikel implementiert. Nun müssen wir ähnliche Methoden für die Positionierung von Kopfzeilen am unteren, linken und rechten Rand entwickeln.
Die Methode, die die ausgewählte Registerkartenkopfleiste an die richtige Position am unteren Rand setzt:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position at the bottom | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowBottom(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()+obj.Height(); //--- 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 Methode, die die ausgewählte Registerkarten-Kopfleiste an die richtige linke Position setzt:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position on the left | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowLeft(void) { int row_pressed=this.Row(); // Selected header row int x_pressed=this.CoordX(); // Coordinate where all headers with Row() equal to zero should be moved to int x0=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 X coordinate of the zero line CWinFormBase *obj=this.GetFieldObj(); if(obj==NULL) return; x0=obj.CoordX()-this.Width()+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(x_pressed,header.CoordY())) { 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(x0,header.CoordY())) { 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 Methode, die die die ausgewählte Registerkarten-Kopfleiste an die richtige rechte Position setzt:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position on the right | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowRight(void) { int row_pressed=this.Row(); // Selected header row int x_pressed=this.CoordX(); // Coordinate where all headers with Row() equal to zero should be moved to int x0=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 X coordinate of the zero line CWinFormBase *obj=this.GetFieldObj(); if(obj==NULL) return; x0=obj.RightEdge(); //--- 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(x_pressed,header.CoordY())) { 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(x0,header.CoordY())) { 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); } } //+------------------------------------------------------------------+
Im vorigen Artikel habe ich eine ähnliche Methode erwogen, nämlich das Verschieben einer Reihe von Kopfzeilen, wenn sie sich über dem Steuerelement befinden. Die Logik hinter diesen neuen Methoden ist genau dieselbe, aber für die untere Positionierung verschieben wir die Kopfzeilen entlang der Y-Achse, und für die linke und rechte Positionierung verschieben wir sie entlang der X-Achse. Die gesamte Logik ist in den Code-Kommentaren ausreichend detailliert beschrieben.
Wenn ein Tabulator durch Anklicken der Überschrift ausgewählt wird, wird die Überschrift etwas vergrößert und ggf. aus der Liste der Überschriftenzeilen in die Nähe des Tabulatorfeldes verschoben, und die dadurch entstehende sichtbare Grenze (durch den Feldrahmen) zwischen Überschrift und Tabulatorfeld wird gelöscht, sodass Feld und Überschrift wie ein untrennbares Ganzes wirken. Im vorherigen Artikel habe ich den Rand zwischen dem Feld und der Kopfzeile gelöscht, aber nur für die Position der Kopfzeile oben und unten. Jetzt müssen wir das Löschen des Randes zwischen dem Feld und der Kopfzeile hinzufügen, wenn sich letztere links und rechts befindet.
In \MQL5\Include\DoEasy\Objects\Graph\WForms\TabField.mqh der Tabulatorfeld-Objektklasse in der Methode zum Zeichnen des Kontrollrahmens in Abhängigkeit von der Position der Kopfzeile fügen wir das Zeichnen der Linie unter Verwendung der Hintergrundfarbe an der Position der Kopfzeile links und rechts hinzu:
//+------------------------------------------------------------------+ //| 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 : this.DrawLine(0,header.BottomEdgeRelative()-2,0,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.DrawLine(this.Width()-1,header.BottomEdgeRelative()-2,this.Width()-1,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity()); break; default: break; } } //+------------------------------------------------------------------+
Hier ist alles ganz einfach. Wir holen uns den Zeiger auf das diesem Feld entsprechende Kopfzeilenobjekt und seine Größe und zeichnet eine Linie mit der Hintergrundfarbe in den Feldbereich, an den die Kopfzeile entsprechend der für die Kopfzeile angegebenen Position angrenzt. Dadurch wird die Grenze zwischen dem Feld und der Kopfzeile optisch aufgehoben, und die beiden Objekte erscheinen als ein einziges — die TabControl-Registerkarte, die ich weiter entwickeln werde.
In der TabControl-Objektklassendatei in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh deklarieren wir vier private Methoden zum Dehnen von Kopfzeilen nach Breite und Höhe:
//--- 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); //--- Stretch tab headers by control size void StretchHeaders(void); //--- Stretch tab headers by (1) control width and height when positioned on the (2) left and (3) right void StretchHeadersByWidth(void); void StretchHeadersByHeightLeft(void); void StretchHeadersByHeightRight(void); public:
Wir implementieren diese Methoden außerhalb des Klassenkörpers.
Die Methode, mit der die Registerkartenüberschriften auf die Größe des Steuerelements gebracht werden:
//+------------------------------------------------------------------+ //| Stretch tab headers by control size | //+------------------------------------------------------------------+ void CTabControl::StretchHeaders(void) { //--- Leave if the headers are in one row if(!this.Multiline()) return; //--- Depending on the location of headers switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.StretchHeadersByWidth(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : this.StretchHeadersByHeightLeft(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.StretchHeadersByHeightRight(); break; default: break; } } //+------------------------------------------------------------------+
Die Methode ruft einfach die entsprechenden Methoden auf, je nachdem, wo sich die Registerkartenüberschriften befinden. Für das Ausdehnung in der Breite reicht eine einzige Methode aus, da alle Kopfzeilen immer von links nach rechts angeordnet sind, während es für die Dehnung in der Höhe darauf ankommt, auf welcher Seite die Kopfzeilen angeordnet sind. Wenn sie sich auf der linken Seite befinden, sind sie von unten nach oben angeordnet, und wenn sie sich auf der linken Seite befinden, gehen sie von oben nach unten. Daher gibt es zwei getrennte Methoden zur Streckung nach Höhe für links und rechts positionierte Überschriften.
Die Methode zum Ausdehnen der Tabulatorüberschriften um die Kontrollbreite:
//+------------------------------------------------------------------+ //| Stretch tab headers by control width | //+------------------------------------------------------------------+ void CTabControl::StretchHeadersByWidth(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); if(last==NULL) return; //--- In the loop by the number of header rows for(int i=0;i<last.Row()+1;i++) { //--- Get the list with the row index equal to the loop index CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL); if(list_row==NULL) continue; //--- Get the width of the container, as well as the number of headers in a row, and calculate the width of each header int base_size=this.Width()-4; int num=list_row.Total(); int w=base_size/(num>0 ? num : 1); //--- In the loop by row headers for(int j=0;j<list_row.Total();j++) { //--- Get the current and previous headers from the list by loop index CTabHeader *header=list_row.At(j); CTabHeader *prev=list_row.At(j-1); if(header==NULL) continue; //--- If the header size is changed if(header.Resize(w,header.Height(),false)) { //--- Set new sizes for the header for pressed/unpressed states header.SetWidthOn(w+4); header.SetWidthOff(w); //--- If this is the first header in the row (there is no previous header in the list), //--- then it is not necessary to shift it - move on to the next iteration if(prev==NULL) continue; //--- Shift the header to the coordinate of the right edge of the previous header if(header.Move(prev.RightEdge(),header.CoordY())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } } } } //+------------------------------------------------------------------+
Hier wird zunächst die Anzahl der Kopfzeilen ermittelt. Diese Zahl lässt sich ermitteln, indem man die letzte Kopfzeile der Liste abruft — sie enthält den Index ihrer Zeile in der Eigenschaft Row. Da die Zeilenindizes bei Null beginnen, müssen wir zum resultierenden Wert eine Eins hinzufügen, um die Anzahl der Zeilen anzugeben.
Als Nächstes müssen wir eine Liste der Kopfzeilen in jeder Zeile abrufen und alle Kopfzeilen darin auf die Breite des Objekts strecken. Da wir den Objekteigenschaften Zeilen- und Spaltenwerte hinzugefügt haben, ist es recht einfach,eine Liste der Kopfzeilen einer Zeile zu erhalten — wir filtern die Liste aller Kopfzeilen nach dem Zeilenwert und erhalten eine Liste mit Zeigern auf Objekte mit der angegebenen Zeilennummer. In der Schleife durch die resultierende Liste ändern wir die Breite jeder Kopfzeile auf den zuvor berechneten Wert — die Breite des Containers geteilt durch die Anzahl der Kopfzeilen in der Zeile. Anstatt die gesamte Containerbreite zu verwenden, werden links und rechts zwei Pixel entfernt, sodass extreme Überschriften nicht über den Container hinausragen, wenn sie ausgewählt und vergrößert werden. Da wir die Größe im Voraus durch einen unbekannten Wert teilen, prüfen wir, ob der Divisor mit dem Wert übereinstimmt und teilen im Falle von 0 durch 1, um eine Division durch Null zu vermeiden. Wenn die vorherige Überschrift in der Liste nicht vorhanden ist (der Schleifenindex zeigt auf die allererste Überschrift), muss die Überschrift nirgendwo verschoben werden. Sie bleibt an ihrem Platz, während alle nachfolgenden Kopfzeilen an den rechten Rand der vorherigen Kopfzeile verschoben werden müssen — alle Kopfzeilen haben ihre Breite verändert, sind größer geworden und überlappen sich gegenseitig.
Die Methode, mit der die Registerkartenüberschriften gestreckt werden, damit sie in die Höhe des Steuerelements passen, wenn dieses nach links positioniert ist:
//+------------------------------------------------------------------+ //| Stretch tab headers by control height | //| when placed on the left | //+------------------------------------------------------------------+ void CTabControl::StretchHeadersByHeightLeft(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); if(last==NULL) return; //--- In the loop by the number of header rows for(int i=0;i<last.Row()+1;i++) { //--- Get the list with the row index equal to the loop index CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL); if(list_row==NULL) continue; //--- Get the height of the container, as well as the number of headers in a row, and calculate the height of each header int base_size=this.Height()-4; int num=list_row.Total(); int h=base_size/(num>0 ? num : 1); //--- In the loop by row headers for(int j=0;j<list_row.Total();j++) { //--- Get the current and previous headers from the list by loop index CTabHeader *header=list_row.At(j); CTabHeader *prev=list_row.At(j-1); if(header==NULL) continue; //--- Save the initial header height int h_prev=header.Height(); //--- If the header size is changed if(header.Resize(header.Width(),h,false)) { //--- Set new sizes for the header for pressed/unpressed states header.SetHeightOn(h+4); header.SetHeightOff(h); //--- If this is the first header in the row (there is no previous header in the list) if(prev==NULL) { //--- Calculate the Y offset int y_shift=header.Height()-h_prev; //--- Shift the header by its calculated offset and move on to the next one if(header.Move(header.CoordX(),header.CoordY()-y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } continue; } //--- Move the header by the coordinate of the top edge of the previous header minus the height of the current one and its calculated offset if(header.Move(header.CoordX(),prev.CoordY()-header.Height())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } } } } //+------------------------------------------------------------------+
Die Logik der Methode ist ähnlich wie bei der vorhergehenden, obwohl sie hier etwas komplizierter ist. Da Kopfzeilen von der unteren Kante ihres Containers aus nach links positioniert werden und der Ankerpunkt der Kopfzeile in der oberen linken Ecke liegt, führt eine Größenänderung dazu, dass die untere Kante der Kopfzeile unterhalb der unteren Kante des Containers liegt. Daher müssen wir hier die allererste Überschrift um den berechneten Versatz nach oben verschieben. Dazu müssen wir die Höhe der Kopfzeile speichern, bevor wir sie ändern, und berechnen, um wie viel sich die Größe nach der Größenänderung verändert hat. Wir verwenden den erhaltenen Wert, um die allererste Kopfzeile entlang der Y-Achse zu verschieben, sodass ihr unterer Rand nicht über ihren Container hinausragt.
Die Methode, mit der die Registerkartenüberschriften gestreckt werden, damit sie in die Höhe des Steuerelements passen, wenn dieses nach rechts verschoben wird:
//+------------------------------------------------------------------+ //| Stretch tab headers by control height | //| when placed on the right | //+------------------------------------------------------------------+ void CTabControl::StretchHeadersByHeightRight(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); if(last==NULL) return; //--- In the loop by the number of header rows for(int i=0;i<last.Row()+1;i++) { //--- Get the list with the row index equal to the loop index CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL); if(list_row==NULL) continue; //--- Get the height of the container, as well as the number of headers in a row, and calculate the height of each header int base_size=this.Height()-4; int num=list_row.Total(); int h=base_size/(num>0 ? num : 1); //--- In the loop by row headers for(int j=0;j<list_row.Total();j++) { //--- Get the current and previous headers from the list by loop index CTabHeader *header=list_row.At(j); CTabHeader *prev=list_row.At(j-1); if(header==NULL) continue; //--- If the header size is changed if(header.Resize(header.Width(),h,false)) { //--- Set new sizes for the header for pressed/unpressed states header.SetHeightOn(h+4); header.SetHeightOff(h); //--- If this is the first header in the row (there is no previous header in the list), //--- then it is not necessary to shift it - move on to the next iteration if(prev==NULL) continue; //--- Shift the header to the coordinate of the bottom edge of the previous header if(header.Move(header.CoordX(),prev.BottomEdge())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } } } } //+------------------------------------------------------------------+
Die Methode ist identisch mit der, bei der die Kopfzeilen auf die Breite des Containers gestreckt werden, aber hier werden sie in der Höhe gestreckt. Da sich hier die Kopfzeilen auf der linken Seite befinden und ihr Bericht von oben nach unten verläuft, brauchen wir die Position der ersten Kopfzeile nach der Änderung ihrer Größe nicht anzupassen — ihre Anfangskoordinaten stimmen mit den Koordinaten ihres Platzierungspunkts überein, und das Objekt wird nach unten wachsen, ohne über den Container hinauszuwachsen.
Die Methode, mit der die angegebene Anzahl von Registerkarten erstellt wird, wurde geändert, da wir die Anfangskoordinaten und -größen anhand der Position der Kopfzeilen berechnen müssen. Um die Kopfzeilen links und rechts zu platzieren, weisen wir die der Methode übergebene Kopfzeilenhöhe und -breite der Breite und Höhe entsprechend zu. Wenn sich die Kopfzeile auf der linken Seite befindet, drehen wir die Kopfzeile vertikal um 90°, wenn sie sich auf der rechten Seite befindet — um 270°:
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab title int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs CTabHeader *header=NULL; CTabField *field=NULL; for(int i=0;i<total;i++) { //--- Depending on the location of tab titles, set their initial coordinates int header_x=2; int header_y=0; int header_w=w; int header_h=h; //--- Set the current X and Y coordinate depending on the location of the tab headers switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : header_w=w; header_h=h; header_x=(header==NULL ? 2 : header.RightEdgeRelative()); header_y=0; break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : header_w=w; header_h=h; header_x=(header==NULL ? 2 : header.RightEdgeRelative()); header_y=this.Height()-header_h; break; case CANV_ELEMENT_ALIGNMENT_LEFT : header_w=h; header_h=w; header_x=2; header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : header_w=h; header_h=w; header_x=this.Width()-header_w; header_y=(header==NULL ? 2 : header.BottomEdgeRelative()); break; default: break; } //--- Create the TabHeader object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_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)); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) header.SetFontAngle(90); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) header.SetFontAngle(270); header.SetTabSizeMode(this.TabSizeMode()); //--- Save the initial height of the header and set its size in accordance with the header size setting mode int h_prev=header_h; header.SetSizes(header_w,header_h); //--- Get the Y offset of the header position after changing its height and //--- shift it by the calculated value only for headers on the left int y_shift=header.Height()-h_prev; if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0))) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } //--- 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_w=this.Width(); int field_h=this.Height()-header.Height(); int header_shift=0; switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : field_x=0; field_y=header.BottomEdgeRelative(); field_w=this.Width(); field_h=this.Height()-header.Height(); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : field_x=0; field_y=0; field_w=this.Width(); field_h=this.Height()-header.Height(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : field_x=header.RightEdgeRelative(); field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : field_x=0; field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width(); break; default: break; } //--- Create the TabField object (tab field) if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,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 titles in accordance with the specified display modes and select the specified tab this.ArrangeTabHeaders(); this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
Die Logik der Methode und die Verbesserungen sind in den Kommentaren zum Code beschrieben, und die wichtigsten Merkmale sind vor der Auflistung der Methode angegeben und farblich hervorgehoben. Um die Kopfzeilen auf der linken Seite zu platzieren, müssen wir die Größe der Kopfzeilen speichern, bevor wir sie ändern, den Versatz berechnen und die in der Größe veränderte Kopfzeile an die richtige Position verschieben.
Die im vorigen Artikel beschriebene Methode, die Tabulatorüberschriften oben zu platzieren, wurde ebenfalls geändert:
//+------------------------------------------------------------------+ //| 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()) { //--- 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 minus 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 minus 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()); } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(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 Logik der Methode ist in den Kommentaren zum Code ausführlich beschrieben. Ich werde sie hier nicht wiederholen.
Die Methode, die Registerkartenüberschriften am unteren Rand platziert:
//+------------------------------------------------------------------+ //| Arrange tab headers at the bottom | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersBottom(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()) { //--- 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 minus 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 minus 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()); } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(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())) { 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 Methode ist identisch mit der Methode, bei der die Kopfzeilen oben stehen. Der einzige Unterschied besteht in der Richtung des Versatzes der Kopfzeilen, da sich die Kopfzeilen am unteren Rand befinden und sich im Vergleich zur vorherigen Methode in umgekehrter Richtung bewegen.
Die Methode zur Lokalisierung Registerkartenüberschriften auf der linken Seite:
//+------------------------------------------------------------------+ //| Arrange tab headers on the left | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersLeft(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 y1_base=this.BottomEdgeRelative()-2; // Initial Y coordinate int y2_base=2; // Final Y coordinate int y_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()) { //--- Calculate the value of the upper edge of the header, taking into account that //--- the origin always comes from the bottom edge of TabControl minus 2 pixels int y2=header.CoordYRelative()+y_shift; //--- If the calculated value does not go beyond the upper edge of the TabControl minus 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(y2>=y2_base) col=i-n; //--- If the calculated value goes beyond the upper edge of the TabControl minus 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++; y_shift=this.BottomEdge()-header.BottomEdge()-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()-header.Row()*header.Width(),header.CoordY()+y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(last.Row()>0) { //--- Calculate the offset of the tab field X coordinate int x_shift=last.Row()*last.Width(); //--- 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()+x_shift,header.CoordY())) { 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()+x_shift,field.CoordY())) { 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()-x_shift,field.Height(),false); } } } } } //+------------------------------------------------------------------+
Hier befinden sich die Kopfzeilen auf der linken Seite und die Zeilen sind entlang der X-Achse verschoben. Ansonsten ist die Logik identisch mit den vorherigen Methoden.
Die Methode, die Tabulatorüberschriften nach rechts positioniert:
//+------------------------------------------------------------------+ //| Arrange tab headers to the right | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersRight(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 y1_base=2; // Initial Y coordinate int y2_base=this.BottomEdgeRelative()-2; // Final Y coordinate int y_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()) { //--- Calculate the value of the bottom edge of the header, taking into account that //--- the origin always comes from the upper edge of TabControl + 2 pixels int y2=header.BottomEdgeRelative()-y_shift; //--- If the calculated value does not go beyond the bottom edge of the TabControl minus 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(y2<y2_base) col=i-n; //--- If the calculated value goes beyond the bottom edge of the TabControl minus 2 pixels, else { //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl bottom 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++; y_shift=header.CoordYRelative()-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()+header.Row()*header.Width(),header.CoordY()-y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(last.Row()>0) { //--- Calculate the offset of the tab field X coordinate int x_shift=last.Row()*last.Width(); //--- 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()-x_shift,header.CoordY())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- change the tab field size to the X offset value field.Resize(field.Width()-x_shift,field.Height(),false); } } } } } //+------------------------------------------------------------------+
Die Logik ist ähnlich wie bei der vorherigen Methode, aber die Zeilenversätze sind gespiegelt, weil die Kopfzeilen rechts stehen.
Alle oben genannten Methoden sind im Code ausführlich kommentiert — wir überlassen sie einem unabhängigem Studium. Wenn Sie Fragen haben, können Sie diese gerne im Kommentarteil stellen.
Jetzt können wir alle Änderungen und Verbesserungen testen. Um die Überschriften der Registerkarten in einer Reihe anzuordnen, müssen wir den sichtbaren/unsichtbaren Teil des grafischen Elements beschneiden können. Wenn wir also den Modus für die Platzierung von Kopfzeilen in einer Zeile wählen (der Mehrzeilenmodus ist ausgeschaltet), während es viele Registerkarten gibt, werden alle Kopfzeilen über das Steuerelement hinaus aufgereiht. Ich werde mich mit diesem Thema in späteren Artikeln befassen. Ich habe den „Stopfen“ für diesen Modus in den Methoden der besprochenen Klassen gelassen. Ich werde dort den Code für die Handhabung dieses Modus eingeben.
Test
Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part116\ als TestDoEasy116.mq5.
In den EA-Eingaben fügen wir die Variablen hinzu, um den Mehrzeilenmodus und die Seite, auf der die Tabulatorüberschriften platziert werden, anzugeben:
//--- input parameters sinput bool InpMovable = true; // Panel Movable flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Panel Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Panel Autosize mode sinput ENUM_BORDER_STYLE InpFrameStyle = BORDER_STYLE_SIMPLE; // Label border style sinput ENUM_ANCHOR_POINT InpTextAlign = ANCHOR_CENTER; // Label text align sinput ENUM_INPUT_YES_NO InpTextAutoSize = INPUT_NO; // Label autosize sinput ENUM_ANCHOR_POINT InpCheckAlign = ANCHOR_LEFT; // Check flag align sinput ENUM_ANCHOR_POINT InpCheckTextAlign = ANCHOR_LEFT; // Check label text align sinput ENUM_CHEK_STATE InpCheckState = CHEK_STATE_UNCHECKED; // Check flag state sinput ENUM_INPUT_YES_NO InpCheckAutoSize = INPUT_YES; // CheckBox autosize sinput ENUM_BORDER_STYLE InpCheckFrameStyle = BORDER_STYLE_NONE; // CheckBox border style sinput ENUM_ANCHOR_POINT InpButtonTextAlign = ANCHOR_CENTER; // Button text align sinput ENUM_INPUT_YES_NO InpButtonAutoSize = INPUT_YES; // Button autosize sinput ENUM_AUTO_SIZE_MODE InpButtonAutoSizeMode= AUTO_SIZE_MODE_GROW; // Button Autosize mode sinput ENUM_BORDER_STYLE InpButtonFrameStyle = BORDER_STYLE_NONE; // Button border style sinput bool InpButtonToggle = true ; // Button toggle flag sinput bool InpButtListMSelect = false; // ButtonListBox Button MultiSelect flag sinput bool InpListBoxMColumn = true; // ListBox MultiColumn flag sinput bool InpTabCtrlMultiline = true; // Tab Control Multiline flag sinput ENUM_ELEMENT_ALIGNMENT InpHeaderAlignment = ELEMENT_ALIGNMENT_TOP; // TabHeader Alignment sinput ENUM_ELEMENT_TAB_SIZE_MODE InpTabPageSizeMode = ELEMENT_TAB_SIZE_MODE_NORMAL; // TabHeader Size Mode //--- global variables
Erhöhen wir die Breite des erstellten Bereichs leicht (um 10 Pixel):
//--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) {
und die Breite des zweiten GroupBox-Containers — um 12 Pixel:
//--- 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()+12; 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)) {
All dies geschieht nur, weil wir nicht die Möglichkeit haben, den unsichtbaren Teil der grafischen Elemente zu beschneiden, und alle WinForm-Objekte, die sich auf ihren übergeordneten Objekten befinden und deren Größe größer ist als die des Containers (übergeordnetes Objekt), werden über dessen Grenzen hinausgehen. So wird z. B. eine CheckBox, die auf dem Registerkartenfeld platziert ist, entweder über das Registerkartenfeld hinausragen, wenn sich die Überschriften auf der linken Seite befinden, oder auch über das Registerkartenfeld hinausgehen und die Registerkartenüberschriften auf der rechten Seite von TabControl abdecken. Es gibt zwar noch nicht genug Funktionalität, aber solche Mängel muss ich ausblenden :)
In OnInit() legen wir nach dem Erstellen von TabControl die Position der Registerkartenüberschriften und die Erlaubnis für die Anordnung der Überschriften in mehreren Zeilen fest, die in den EA-Eingaben angegeben sind:
//--- 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 titles 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((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tab_ctrl.SetMultiline(InpTabCtrlMultiline); tab_ctrl.SetHeaderPadding(6,0); tab_ctrl.CreateTabPages(9,0,50,16,TextByLanguage("Вкладка","TabPage"));
Wenn wir das Steuerelement ListBox auf der dritten Registerkarte von TabControl erstellen, setzen wir seine Y-Koordinate näher an den oberen Rand der Registerkarte:
//--- 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,2,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
Bisher befand sich das Objekt an der Koordinate 12, was dazu führt, dass es bei einer mehrzeiligen Anordnung von Tabulatorüberschriften von unten über das Tabulatorfeld hinausgeht (da die Größe des Tabulatorfeldes mit zunehmender Anzahl von Kopfzeilen abnimmt).
Kompilieren Sie den EA und starten Sie ihn auf einem Chart:
Wie wir sehen können, funktioniert das Layout der Registerkartenüberschriften auf der linken und rechten Seite korrekt. Es gibt einige Unzulänglichkeiten, die wir im nächsten Artikel beschreiben und beheben werden, aber bis jetzt ist alles gut.
Was kommt als Nächstes?
Im nächsten Artikel werde ich die Arbeit an TabControl 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
DoEasy. Steuerung (Teil 15): TabControl WinForms Objekt — mehrere Reihen von Registerkartenüberschriften, Methoden zur Behandlung von Registerkarten
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/11356
- 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.