
DoEasy. Steuerung (Teil 17): Beschneiden unsichtbarer Objektteile, Hilfspfeiltasten WinForms-Objekte
Inhalt
- Konzept
- Verbesserung der Bibliotheksklassen
- Klassen von Hilfsobjekten für Pfeiltasten
- Test
- Was kommt als Nächstes?
Konzept
Jedes grafische Element, das mit seinem Container verbunden ist, kann nur innerhalb seines Containers gesehen werden. Wenn ein Teil des Elements über die Begrenzungen des Containers hinausragt, sollte dieser überstehende Teil ausgeblendet werden. Statt des gesamten Objekts sollte nur der Teil ausgeblendet werden, der außerhalb der Sichtbarkeitsgrenzen des übergeordneten Objekts liegt, an das es angehängt ist. In der Regel werden die Kanten des Containers als Sichtbarkeitsgrenzen verwendet. Wenn das Objekt jedoch einen Rahmen hat, sollte dieser Rahmen nicht von dem angehängten Objekt überlappt werden, und die Sichtbarkeitsgrenze für das hervorstehende Objekt ist in diesem Fall die Innenkante des Containerrahmens.
MQL verfügt über spezielle grafische Objekteigenschaften zum Beschneiden grafischer Elemente auf der Grundlage eines BMP-Bildes(OBJ_BITMAP_LABEL und OBJ_BITMAP). Diese Eigenschaften ermöglichen es, nur einen Teil des Bildes anzuzeigen, der durch den rechteckigen Sichtbarkeitsbereich begrenzt wird:
Leider funktioniert das Zuschneiden und Positionieren des sichtbaren Teils eines Objekts innerhalb des rechteckigen Bereichs nicht, wenn Canvas verwendet wird. Obwohl die Ressource für die Leinwand als Bitmap-Bild im Speicher erstellt wird, „verhindert etwas“ mit dieser Methode für Objekte auf der Grundlage von physischen bmp-Dateien mit Bildern im Speicher als Ressource gebaut gemeint.
Deshalb werde ich meinen eigenen Weg gehen — ich werde einen solchen Geltungsbereich selbst erstellen und Teile des Objekts abschneiden, die über den Geltungsbereich ihrer Container hinausgehen. Zu Beginn entspricht der rechteckige Bereich der Breite und Höhe des Containers oder befindet sich innerhalb des Objektrahmens (falls dieser einen Rahmen hat). Jedes grafische Element verfügt über eine Methode, die seine eigene Position im Verhältnis zu seinem Container ausliest und seinen überflüssigen sichtbaren Teil abschneidet (indem es einfach seinen Hintergrund mit einer transparenten Farbe bei voller Transparenz malt).
Neben der Implementierung der erwähnten Funktionsweise der grafischen Elemente werde ich auch mehrere Klassen von grafischen Hilfselementen erstellen. Es handelt sich dabei um Schaltflächenobjekte mit Pfeilen. Solche Schaltflächen werden zur Implementierung von Steuerelementen wie Bildlaufleisten, Dropdown-Listen und anderen ähnlichen Steuerelementen benötigt.
Wenn wir über das in der Entwicklung befindliche TabControl sprechen, dann braucht es solche Schaltflächen, um die in einer Zeile befindlichen Registerkartenköpfe zu verschieben, falls es zu viele davon gibt, um alle in das Steuerelement zu passen. Die Registerkarten, die über das Steuerelement hinausgehen, sollten ausgeblendet werden (was ich heute implementiere), und nur zwei Schaltflächen mit Links-Rechts-Pfeilen werden verwendet, um die Kopfleiste zu scrollen und die gewünschte Registerkartenüberschrift zu finden und anzuzeigen. Daher werde ich nach der Erstellung solcher Schaltflächenobjekte mit Pfeilen zwei weitere Objekte erstellen — mit zwei Schaltflächen „links-rechts“ und „oben-unten“. Ich werde diese Objekte im nächsten Artikel verwenden, um die versteckte Registerkarte im TabControl-Objekt zu finden.
Verbesserung der Bibliotheksklassen
In \MQL5\Include\DoEasy\Defines.mqh fügen wir eine Makrosubstitution hinzu, die die Standardgröße der Seiten der Pfeiltasten angibt:
#define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define DEF_CHECK_SIZE (12) // Verification flag default size #define DEF_ARROW_BUTTON_SIZE (15) // Default arrow button size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace #define DEF_FRAME_WIDTH_SIZE (3) // Default form/panel/window frame width
Bei der Erstellung eines solchen Schaltflächenobjekts können wir jederzeit eine andere Größe für die Schaltflächenseiten angeben, aber die Standardgröße ist 15 Pixel.
Hinzufügen eines neuen Typs — ein Hilfsobjekt zur Liste der Bibliotheksobjekttypen, und zwar zum Abschnitt WinForms-Objekt:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_BASE, // WinForms Base object type (base abstract WinForms object) OBJECT_DE_TYPE_GWF_CONTAINER, // WinForms container object type OBJECT_DE_TYPE_GWF_COMMON, // WinForms standard control object type OBJECT_DE_TYPE_GWF_HELPER, // WinForms auxiliary control object type //--- Animation //---... //---... }
Anschließend können wir nur Hilfsobjekte dieser Art von grafischen Objekten auswählen, um beliebige Aktionen mit ihnen auszuführen.
Fügen wir der Liste der grafischen Elementtypen die neuen Typen hinzu, deren Objektklassen ich in diesem Artikel erstellen werde:
//+------------------------------------------------------------------+ //| 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 GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON, // Windows Forms ArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP, // Windows Forms UpArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN, // Windows Forms DownArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT, // Windows Forms LeftArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT, // Windows Forms RightArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX, // Windows Forms UpDownArrowButtonsBox GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX, // Windows Forms LeftRightArrowButtonsBox }; //+------------------------------------------------------------------+
In der Liste der ganzzahligen Eigenschaften eines grafischen Elements auf der Leinwand fügen wir vier neue Eigenschaften hinzu, um die Koordinaten und Abmessungen des Sichtbarkeitsbereichs des grafischen Elements anzugeben, und erhöhen die Gesamtzahl der ganzzahligen Eigenschaften von 92 auf 96:
//+------------------------------------------------------------------+ //| 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_ACT_RIGHT, // Right border of the element active area CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_VISIBLE_AREA_X, // Visibility scope X coordinate CANV_ELEMENT_PROP_VISIBLE_AREA_Y, // Visibility scope Y coordinate CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, // Visibility scope width CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, // Visibility scope height CANV_ELEMENT_PROP_GROUP, // Group the graphical element belongs to CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (96) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Wir fügen neue Eigenschaften zur Liste der möglichen Kriterien für die Sortierung 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_ACT_RIGHT, // Sort by the right border of the element active area SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_VISIBLE_AREA_X, // Sort by visibility scope X coordinate SORT_BY_CANV_ELEMENT_VISIBLE_AREA_Y, // Sort by visibility scope Y coordinate SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH, // Sort by visibility scope width SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT, // Sort by visibility scope height SORT_BY_CANV_ELEMENT_GROUP, // Sort by a group the graphical element belongs to SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //---... //---... 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 grafische Elemente nach neu hinzugefügten Eigenschaften auswählen und sortieren.
In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:
MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, // TabControl tab field MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // TabControl MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON, // ArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP, // UpArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN, // DownArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT, // LeftArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT, // RightArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX, // UpDownArrowBox control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX, // LeftRightArrowBox control MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Graphical object does not belong to a program
...
MSG_CANV_ELEMENT_PROP_ACT_RIGHT, // Right border of the element active area MSG_CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X, // Visibility scope X coordinate MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y, // Visibility scope Y coordinate MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, // Visibility scope width MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, // Visibility scope height MSG_CANV_ELEMENT_PROP_ENABLED, // Element availability flag MSG_CANV_ELEMENT_PROP_FORE_COLOR, // Default text color for all control objects
und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:
{"Поле вкладки элемента управления \"TabControl\"","Tab field of the Control element \"TabControl\""}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""}, {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""}, {"Элемент управления \"DownArrowButton\"","Control element \"DownArrowButton\""}, {"Элемент управления \"LeftArrowButton\"","Control element \"LeftArrowButton\""}, {"Элемент управления \"RightArrowButton\"","Control element \"RightArrowButton\""}, {"Элемент управления \"UpDownArrowBox\"","Control element \"UpDownArrowBox\""}, {"Элемент управления \"LeftRightArrowBox\"","Control element \"LeftRightArrowBox\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
...
{"Правая граница активной зоны элемента","Right border of the element's active area"}, {"Нижняя граница активной зоны элемента","Bottom border of the element's active area"}, {"Координата X области видимости","X-coordinate of object visibility area"}, {"Координата Y области видимости","Y-coordinate of object visibility area"}, {"Ширина области видимости","Width of object visibility area"}, {"Высота области видимости","Height of object visibility area"}, {"Флаг доступности элемента","Element Availability Flag"}, {"Цвет текста по умолчанию для всех объектов элемента управления","Default text color for all objects in the control"},
Lassen Sie uns einige kleinere Verbesserungen in der Bibliotheksdatei \MQL5\Include\DoEasy\Services\DELib.mqh einführen, und zwar in der Funktion, die den Typ des grafischen Objekts als 'string' zurückgibt:
//+------------------------------------------------------------------+ //| Return the graphical object type as string | //+------------------------------------------------------------------+ string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type) { ushort array[]; int total=StringToShortArray(StringSubstr(EnumToString(type),18),array); for(int i=0;i<total-1;i++) { if(array[i]==95) { i+=1; continue; } else array[i]+=0x20; } string txt=ShortArrayToString(array); StringReplace(txt,"_Wf_Base","WFBase"); StringReplace(txt,"_Wf_",""); StringReplace(txt,"_Obj",""); StringReplace(txt,"_",""); StringReplace(txt,"Groupbox","GroupBox"); StringReplace(txt,"ButtonsUdBox","ButtonsUDBox"); StringReplace(txt,"ButtonsLrBox","ButtonsLRBox"); return txt; } //+------------------------------------------------------------------+
Da ich neue grafische Elemente erstellen werde, muss ich den automatisch generierten Namen eines grafischen Objekts leicht ändern, um es zu erstellen. Bei der Erstellung einer Namenskette lässt die Funktion zunächst keine zwei oder mehr aufeinanderfolgende Großbuchstaben zu. Gleichzeitig sollte der Objektname drei solcher Zeichen enthalten. Daher ersetzen wir einfach die automatisch generierte Zeichenkette durch die von uns benötigte.
Jetzt werden die Namen der heute neu erstellten Objekte korrekt sein.
In den Klassenkonstruktoren des ListBoxItem-Hilfsobjekts, nämlich in \MQL5\Include\DoEasy\Objects\Graph\WForms\ListBoxItem.mqh geben wir den grafischen Objekttyp der Bibliothek als WinForms-Hilfsobjekt ein:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CListBoxItem::CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CListBoxItem::CListBoxItem(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } //+------------------------------------------------------------------+
Bei der Arbeit mit Canvas kann es Probleme geben, wenn nur der sichtbare Teil eines grafischen Objekts angezeigt wird, der von einem rechteckigen Rahmen umgeben ist. Also werde ich es selbst tun. Grafische Objekte haben jedoch immer noch solche Eigenschaften, und deshalb müssen wir eine Funktionsweise zum Setzen und Abrufen dieser Eigenschaften in einem grafischen Objekt schaffen. Diese Funktion wird auch verwendet, um einen Sichtbarkeitsbereich für grafische Elemente auf der Leinwand zu erstellen.
In der Datei \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh des grafischen Basisobjekts der Bibliothek fügen wir virtuelle Methoden zum Setzen und Abrufen neuer Eigenschaften von grafischen Objekten hinzu, während die Methode zum Setzen des Sichtbarkeitsflags umbenannt wird , damit klar ist, dass das Flag durch die Methode gesetzt wird:
//--- Set the priority of a graphical object for receiving the event of clicking on a chart virtual bool SetZorder(const long value,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop) { this.m_zorder=value; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- (1) Set and (2) return the X coordinate of the upper left corner of the rectangle visibility scope of the OBJ_BITMAP_LABEL and OBJ_BITMAP graphical object virtual bool SetXOffset(const long value) { ::ResetLastError(); if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET,value)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } virtual int XOffset(void) const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET); } //--- (1) Set and (2) return the Y coordinate of the upper left corner of the rectangle visibility scope of the OBJ_BITMAP_LABEL and OBJ_BITMAP graphical object virtual bool SetYOffset(const long value) { ::ResetLastError(); if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET,value)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } virtual int YOffset(void) const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET); } //--- (1) Set and (2) return the width of OBJ_LABEL (read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT and OBJ_RECTANGLE_LABEL objects virtual bool SetXSize(const long value) { ::ResetLastError(); if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE,value)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } virtual int XSize(void) const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE); } //--- (1) Set and (2) return the height of OBJ_LABEL (read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT, OBJ_RECTANGLE_LABEL objects virtual bool SetYSize(const long value) { ::ResetLastError(); if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE,value)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } virtual int YSize(void) const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE); } //--- Set object visibility on all timeframes bool SetVisibleFlag(const bool flag,const bool only_prop) { long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop) { this.m_visible=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; }
Die Methoden zum Setzen von Eigenschaften geben das Flag für die erfolgreiche Platzierung eines bestimmten Eigenschaftswerts des grafischen Objekts in der Warteschlange des Charts zurück. Mit anderen Worten: Die Funktion ist asynchron und gibt nur das Flag für das erfolgreiche Setzen des Befehls in der Warteschlange zurück, nicht aber das erfolgreiche Setzen der gewünschten Eigenschaft. Um eine Prüfung durchzuführen, müssen wir die geänderte Eigenschaft aus dem Objekt lesen und ihren Wert überprüfen. Dies wurde für alle ObjectSetXXX-Funktionen durchgeführt, aber bei der Verwendung der Bibliothek wurden keine Verzögerungen bei der Ausführung der Befehlswarteschlange festgestellt, sodass ich diese Konstruktion vorerst für die Einstellung der Eigenschaften grafischer Objekte verwenden werde.
In der Methode, die die Beschreibung des grafischen Elementtyps zurückgibt, fügen wir die Pfeiltasten, die ich in diesem Artikel implementieren werde:
//+------------------------------------------------------------------+ //| 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) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : "Unknown" ); } //+------------------------------------------------------------------+
Jetzt können wir Beschreibungen neuer Arten von Hilfsobjekten erhalten, wenn sie bereit sind, und wir können sie erstellen.
In der Basisobjektdatei der Bibliothek für grafische Standardobjekte \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh wurde der Aufruf der Methode zum Setzen der Sichtbarkeitsflag korrigiert, da diese Methode jetzt umbenannt wurde:
//--- Object visibility on timeframes bool Visible(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0); } bool SetFlagVisible(const bool flag,const bool only_prop) { if(!CGBaseObj::SetVisibleFlag(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,flag); return true; } //--- Background object
In der Datei MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh des grafischen Elementobjekts auf der Leinwand fügen wir, und zwar in der privaten Struktur des Objekts, neue ganzzahlige Eigenschaften hinzu:
private: int m_shift_coord_x; // Offset of the X coordinate relative to the base object int m_shift_coord_y; // Offset of the Y coordinate relative to the base object struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type //---... //---... int tab_alignment; // Location of tabs inside the control int alignment; // Location of the object inside the control int visible_area_x; // Visibility scope X coordinate int visible_area_y; // Visibility scope Y coordinate int visible_area_w; // Visibility scope width int visible_area_h; // Visibility scope height //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name uchar text[256]; // Graphical element text uchar descript[256]; // Graphical element description }; SData m_struct_obj; // Object structure
Wir brauchen die Struktur der Objekteigenschaften, um die Eigenschaften eines Objekts korrekt zu speichern und aus der Datei zu lesen.
Alle Methoden, die auf die zuvor umbenannte Methode in der grafischen Basisobjektdatei zugreifen, sollten nun auf diese Methode unter ihrem neuen Namen verweisen:
//--- Set the object above all virtual void BringToTop(void) { CGBaseObj::SetVisibleFlag(false,false); CGBaseObj::SetVisibleFlag(true,false);} //--- (1) Show and (2) hide the element virtual void Show(void) { CGBaseObj::SetVisibleFlag(true,false); } virtual void Hide(void) { CGBaseObj::SetVisibleFlag(false,false); } //--- Priority of a graphical object for receiving the event of clicking on a chart
Implementieren wir virtuelle Methoden für die Arbeit mit neuen grafischen Elementeigenschaften:
//--- Graphical object group virtual int Group(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP); } virtual void SetGroup(const int value) { CGBaseObj::SetGroup(value); this.SetProperty(CANV_ELEMENT_PROP_GROUP,value); } //--- Visibility scope X coordinate virtual int XOffset(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X); } virtual bool SetXOffset(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetXOffset(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope Y coordinate virtual int YOffset(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y); } virtual bool SetYOffset(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYOffset(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope width virtual int XSize(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH); } virtual bool SetXSize(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetXSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope height virtual int YSize(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT); } virtual bool SetYSize(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope X coordinate virtual int VisibleAreaX(void) const { return this.XOffset(); } virtual bool SetVisibleAreaX(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetXOffset(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope Y coordinate virtual int VisibleAreaY(void) const { return this.YOffset(); } virtual bool SetVisibleAreaY(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYOffset(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope width virtual int VisibleAreaWidth(void) const { return this.XSize(); } virtual bool SetVisibleAreaWidth(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetXSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope height virtual int VisibleAreaHeight(void) const { return this.YSize(); } virtual bool SetVisibleAreaHeight(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Return the (1) X coordinate, (2) right border, (3) Y coordinate, (4) bottom border of the visible area int CoordXVisibleArea(void) const { return this.CoordX()+this.VisibleAreaX(); } int RightEdgeVisibleArea(void) const { return this.CoordXVisibleArea()+this.VisibleAreaWidth(); } int RightEdgeVisibleAreaRelative(void) const { return this.VisibleAreaX()+this.VisibleAreaWidth(); } int CoordYVisibleArea(void) const { return this.CoordY()+this.VisibleAreaY(); } int BottomEdgeVisibleArea(void) const { return this.CoordYVisibleArea()+this.VisibleAreaHeight(); } int BottomEdgeVisibleAreaRelative(void) const { return this.VisibleAreaY()+this.VisibleAreaHeight(); } //--- Graphical element description
Die Methoden zum Setzen eines Eigenschaftswertes setzen die Eigenschaft zunächst direkt im grafischen Objekt selbst. Ist der Vorgang erfolgreich, setzen sie den Wert auf die Eigenschaft der Objektklasse. Die Werte werden von den Objekteigenschaften zurückgegeben, die dort zuvor durch Methoden zum Setzen von Eigenschaften festgelegt wurden.
Die Hilfsmethoden geben den berechneten Wert der gewünschten Kante oder die Koordinate der oberen linken Ecke des Sichtbarkeitsbereichs des Objekts zurück und vereinfachen den Zugang zum Lesen der erforderlichen Eigenschaften der Sichtbarkeitsbereichsgrenzen, da sie in Übereinstimmung mit ähnlichen Methoden grafischer Elemente benannt sind und die gewünschten Werte zurückgeben, ohne dass sie unabhängig berechnet werden müssen.
Wenn ein Grafikelement-Objekt erstellt wird, sollte es mit der dafür eingestellten Farbe gefüllt werden, die notwendigen Beschriftungen oder Bilder sollten darauf gezeichnet werden, und dann sollte das Objekt seine Position innerhalb des Containers berechnen und seine überstehenden Teile außerhalb des Containers abschneiden. Beim Beschneiden werden die Bereiche, die ausgeblendet werden sollen, mit einer transparenten Farbe gefüllt. Wir müssen also zuerst die Methode Erase() aufrufen, die den Hintergrund mit einer Farbe füllt und in der etwas anderes auf das Objekt gezeichnet wird, und dann die unsichtbaren Teile des Bildes löschen. All dies sollte wieder in der Methode Erase() untergebracht werden. Das bedeutet, dass die erste Füllung mit Farbe erfolgen sollte, ohne dass die verdeckten Bereiche abgeschnitten werden. Rufen wir diese Methode EraseNoCrop() auf. Erstellen wir außerdem die Methode Crop(), um verdeckte Bereiche zu beschneiden. Wir rufen diese Methoden nacheinander mit der bestehenden Methode Erase() auf.
Lassen Sie uns neue Methoden in den geschützten und öffentlichen Abschnitten der Klasse deklarieren:
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- 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); //--- Clear the element completely virtual void Erase(const bool redraw=false); protected: //--- Clear the element filling it with color and opacity without cropping and updating virtual void EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false); //--- Clears the element with a gradient fill without cropping and updating virtual void EraseNoCrop(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); public: //--- Crops the image outlined by (1) the specified and (2) previously set rectangular visibility scope void Crop(const uint coord_x,const uint coord_y,const uint width,const uint height); virtual void Crop(void); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); } //+------------------------------------------------------------------+
In beiden Klassenkonstruktoren werden die die Standardwerte für die Koordinaten und die Größe des Sichtbarkeitsbereichs des Objekts so festgelegt, dass der rechteckige Sichtbarkeitsbereich der Größe des gesamten Objekts entspricht. Nachdem wir alle Eigenschaften eingestellt haben, setzen wir das Flag für die Sichtbarkeit des Objekts auf „hidden“ (ausgeblendet). Dies befreit uns von der Notwendigkeit zu beobachten, wie alle GUI-Element-Objekte des Programms nach und nach aufgebaut werden (zusammen mit dem Ausblenden des Hauptformularobjekts und dessen Anzeige nach dem Aufbau aller grafischen Objekte im Programm selbst beim Aufbau seiner grafischen Komponente):
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.SetTypeElement(element_type); this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(colour,true); this.SetOpacity(opacity); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight()); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom()); // Bottom border of the element active area this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,0); // Visibility scope X coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,0); // Visibility scope Y coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h); // Visibility scope height //---... //---... this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0); // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Location of an object inside the control this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description this.SetVisibleFlag(false,false); } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetOpacity(0); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,false)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight()); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom()); // Bottom border of the element active area this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,0); // Visibility scope X coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,0); // Visibility scope Y coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h); // Visibility scope height //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Location of tabs inside the control this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Location of an object inside the control //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description this.SetVisibleFlag(false,false); } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+
In der Methode, die die Struktur des Objekts erstellt, fügen wir die Einstellung der Sichtbarkeitseigenschaften des Bereichs hinzu:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type //---... //---... this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); // Right border of the element active area this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area this.m_struct_obj.visible_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X); // Visibility scope X coordinate this.m_struct_obj.visible_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y); // Visibility scope Y coordinate this.m_struct_obj.visible_area_w=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH); // Visibility scope width this.m_struct_obj.visible_area_h=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT); // Visibility scope height this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER); // Priority of a graphical object for receiving the on-chart mouse click event this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); // Element availability flag //---... //---... this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); // Location of tabs inside the control this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT); // Location of an object inside the control //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj); // Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res); // Graphical resource name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text); // Graphical element text ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
In der Methode, die ein Objekt aus einer Struktur erstellt, implementieren wir die Einstellung der Sichtbarkeitseigenschaften des Bereichs auf die Objekteigenschaften:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,this.m_struct_obj.visible_area_x); // Visibility scope X coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,this.m_struct_obj.visible_area_y); // Visibility scope Y coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,this.m_struct_obj.visible_area_w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,this.m_struct_obj.visible_area_h); // Visibility scope height this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder); // Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled); // Element availability flag //---... //---... this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,this.m_struct_obj.fore_color); // Default text color for all control objects this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,this.m_struct_obj.fore_color_opacity); // Opacity of the default text color for all control objects //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment); // Location of tabs inside the control this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment); // Location of an object inside the control //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj)); // Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res)); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text)); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description } //+------------------------------------------------------------------+
Die Einstellung der Sichtbarkeitseigenschaften des rechteckigen Bereichs fügen wir zu den Methoden zur Einstellung der Breite und Höhe des Steuerelements 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); this.SetVisibleAreaX(0,true); this.SetVisibleAreaWidth(width,true); 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); this.SetVisibleAreaY(0,true); this.SetVisibleAreaHeight(height,true); return true; } //+------------------------------------------------------------------+
Wenn sich die Größe des grafischen Elements ändert, sollte sich der Sichtbarkeitsbereich entsprechend ändern, damit er das gesamte Objekt abdeckt, was wir hier tun. Nach jeder Änderung der Größe des Objekts wird eine neue entsprechende Größe des Sichtbarkeitsbereichs festgelegt, dessen Startkoordinate in der linken oberen Ecke des grafischen Objekts gleich Null ist.
Jetzt sehen die Erase()-Methoden wie folgt aus:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CGCnvElement::Erase(const color colour,const uchar opacity,const bool redraw=false) { this.EraseNoCrop(colour,opacity,false); this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CGCnvElement::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { this.EraseNoCrop(colors,opacity,vgradient,cycle,false); this.Crop(); //--- If specified, update the canvas this.Update(redraw); } //+------------------------------------------------------------------+
Zunächst wird die Methode EraseNoCrop() aufgerufen, die das Element mit einer bestimmten Farbe löscht , wobei die Aktualisierung deaktiviert ist. Als Nächstes rufen wir die Methode Crop() auf, die verdeckte Bereiche beschneidet, und die Leinwand mit dem angegebenen Chartaktualisierungsflag wird aktualisiert.
Die Methoden zum Füllen der Leinwand mit Farbe, ohne verdeckte Bereiche abzuschneiden:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //| without cropping and with the chart update by flag | //+------------------------------------------------------------------+ void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false) { color arr[1]; arr[0]=colour; this.SaveColorsBG(arr); this.m_canvas.Erase(::ColorToARGB(colour,opacity)); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill without cropping | //| but with updating the chart by flag | //+------------------------------------------------------------------+ void CGCnvElement::EraseNoCrop(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Set the vertical and cyclic gradient filling flags this.m_gradient_v=vgradient; this.m_gradient_c=cycle; //--- Check the size of the color array int size=::ArraySize(colors); //--- If there are less than two colors in the array if(size<2) { //--- if the array is empty, erase the background completely and leave if(size==0) { this.Erase(redraw); return; } //--- in case of one color, fill the background with this color and opacity, and leave this.EraseNoCrop(colors[0],opacity,redraw); return; } //--- Declare the receiver array color out[]; //--- Set the gradient size depending on the filling direction (vertical/horizontal) int total=(vgradient ? this.Height() : this.Width()); //--- and get the set of colors in the receiver array CColors::Gradient(colors,out,total,cycle); total=::ArraySize(out); //--- In the loop by the number of colors in the array for(int i=0;i<total;i++) { //--- depending on the filling direction switch(vgradient) { //--- Horizontal gradient - draw vertical segments from left to right with the color from the array case false : DrawLineVertical(i,0,this.Height()-1,out[i],opacity); break; //--- Vertical gradient - draw horizontal segments downwards with the color from the array default: DrawLineHorizontal(0,this.Width()-1,i,out[i],opacity); break; } } //--- Save the background color array this.SaveColorsBG(colors); this.Update(redraw); } //+------------------------------------------------------------------+
Dabei handelt es sich um die bisherigen Erase()-Methoden, die nun durch eine Methode zum Schneiden verdeckter Bereiche ergänzt werden, während die neuen Erase()-Methoden nun den Aufruf dieser Methoden und das Beschneiden der Bereiche, die über den Container hinausgehen, beinhalten.
Die Methode, die das von einem bestimmten rechteckigen Sichtbarkeitsbereich umrissene Bild beschneidet:
//+--------------------------------------------------------------------+ //| Crop the image outlined by a specified rectangular visibility scope| //+--------------------------------------------------------------------+ void CGCnvElement::Crop(const uint coord_x,const uint coord_y,const uint width,const uint height) { //--- If the passed coordinates and the size of the visibility scope match the size of the object, leave if(coord_x==0 && coord_y==0 && width==this.Width() && height==this.Height()) return; //--- Set the coordinates and size of the visibility scope in the object properties this.SetVisibleAreaX(coord_x,true); this.SetVisibleAreaY(coord_y,true); this.SetVisibleAreaWidth(width,true); this.SetVisibleAreaHeight(height,true); //--- If the object in the current state has not yet been saved, //--- save its bitmap to the array for subsequent restoration if(::ArraySize(this.m_duplicate_res)==0) this.ResourceStamp(DFUN); //--- In the loop through the image lines of the graphical object for(int y=0;y<this.Height();y++) { //--- go through each pixel of the current line for(int x=0;x<this.Width();x++) { //--- If the string and its pixel are in the visibility scope, skip the pixel if(y>=this.VisibleAreaY() && y<=this.BottomEdgeVisibleAreaRelative() && x>=this.VisibleAreaX() && x<=this.RightEdgeVisibleAreaRelative()) continue; //--- If the line pixel is outside the visibility scope, set a transparent color for it this.SetPixel(x,y,CLR_CANV_NULL,0); } } } //+------------------------------------------------------------------+
Die Anfangskoordinaten des sichtbaren Bereichs des Objekts relativ zu seinem Container und die Größe dieses Bereichs werden an die Methode übergeben. Die übergebenen Werte werden auf die Eigenschaften des Objekts gesetzt, und dann erfolgt in zwei Schleifen das Löschen (Füllen mit einer transparenten Farbe) derjenigen Bildpixel, die über den festgelegten sichtbaren Bereich hinausgehen.
Die Methode, die das durch den berechneten rechteckigen Sichtbarkeitsbereich umrissene Bild beschneidet:
//+------------------------------------------------------------------+ //| Crop the image outlined by the calculated | //| rectangular visibility scope | //+------------------------------------------------------------------+ void CGCnvElement::Crop(void) { //--- Get the pointer to the base object CGCnvElement *base=this.GetBase(); //--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave if(base==NULL) return; //--- Set the initial coordinates and size of the visibility scope to the entire object int vis_x=0; int vis_y=0; int vis_w=this.Width(); int vis_h=this.Height(); //--- Set the size of the top, bottom, left and right areas that go beyond the container int crop_top=0; int crop_bottom=0; int crop_left=0; int crop_right=0; //--- Calculate the boundaries of the container area, inside which the object is fully visible int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea()); int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1); int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea()); int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1); //--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond //--- the boundaries of the container area, inside which the object is fully visible crop_top=this.CoordY()-top; if(crop_top<0) vis_y=-crop_top; crop_bottom=bottom-this.BottomEdge()-1; if(crop_bottom<0) vis_h=this.Height()+crop_bottom-vis_y; crop_left=this.CoordX()-left; if(crop_left<0) vis_x=-crop_left; crop_right=right-this.RightEdge()-1; if(crop_right<0) vis_w=this.Width()+crop_right-vis_x; //--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0) this.Crop(vis_x,vis_y,vis_w,vis_h); } //+------------------------------------------------------------------+
Die Methodenlogik wird in den Codekommentaren beschrieben. Zunächst erhalten wir einen Zeiger auf das Containerobjekt, an das dieses grafische Element angehängt ist. Abhängig von der Größe des Containers und den Rändern des Bereichs, in dem die angehängten Objekte sichtbar sind, wird berechnet, wie weit das am Container angehängte Objekt über die Grenzen dieses Containerbereichs hinausgeht. Wenn das Objekt auf beiden Seiten über die Grenzen hinausgeht, rufen wir die Methode zum Beschneiden der Bereiche des Bildes auf, die ausgeblendet werden sollen.
Lassen Sie uns einige Verbesserungen in der Datei \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh der Schattenobjektklasse vornehmen.
Im Klassenkonstruktor setzen wir das Sichtbarkeitsflag für das erstellte Objekt auf „hidden“ (ausgeblendet):
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CShadowObj::CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetBackgroundColor(clrNONE,true); CGCnvElement::SetOpacity(0); CGCnvElement::SetActive(false); this.m_opacity=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.SetVisibleFlag(false,false); CGCnvElement::Erase(); } //+------------------------------------------------------------------+
Bei der Methode des Schattenzeichnens prüft zunächst die Sichtbarkeit des Objekts. Wenn das Objekt ausgeblendet ist, dann gibt es nichts zu zeichnen — die Methode wird verlassen:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw) { if(!this.IsVisible()) return; //--- Set the shadow shift values to the variables by X and Y axes this.SetCoordXRelative(shift_x); this.SetCoordYRelative(shift_y); //--- Calculate the height and width of the drawn rectangle int w=this.Width()-OUTER_AREA_SIZE*2; int h=this.Height()-OUTER_AREA_SIZE*2; //--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); //--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value); //--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(this.m_blur)) return; //--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw); CGCnvElement::Update(redraw); } //+------------------------------------------------------------------+
In der Formularobjekt-Klassendatei \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, und zwar in der Methode, die ein neues gebundenes Element erstellt und es zur Liste der gebundenen Objekte hinzufügt, wird der Name der zuvor umbenannten Methode festgelegt:
//+------------------------------------------------------------------+ //| 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.SetVisibleFlag(this.IsVisible(),false); obj.SetActive(this.Active()); obj.SetEnabled(this.Enabled()); return obj; } //+------------------------------------------------------------------+
Da der Schatten jetzt nur gezeichnet wird, wenn das Sichtbarkeitsflag aktiviert ist, tauschen wir das Schattenrendering und die Aktivierung des Sichtbarkeitsflags in der Methode zum Zeichnen des Schattens:
//+------------------------------------------------------------------+ //| Draw the shadow | //+------------------------------------------------------------------+ void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR) { //--- If the shadow flag is disabled, exit if(!this.m_shadow) return; //--- If there is no shadow object, create it if(this.m_shadow_obj==NULL) this.CreateShadowObj(colour,opacity); //--- If the shadow object exists, draw the shadow on it, //--- set the shadow object visibility flag and //--- move the form object to the foreground if(this.m_shadow_obj!=NULL) { this.m_shadow_obj.SetVisibleFlag(true,false); this.m_shadow_obj.Draw(shift_x,shift_y,blur,true); this.BringToTop(); } } //+------------------------------------------------------------------+
Zuvor wurden diese Methoden in umgekehrter Reihenfolge aufgerufen, und der Schatten wurde nicht gezeichnet.
Wir verbessern die Erase()-Methoden in der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh der Klasse des Basisobjekts für alle WinForms-Objekte:
//+------------------------------------------------------------------+ //| 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::EraseNoCrop(colour,opacity,false); //--- 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.Crop(); 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::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Nun rufen wir zunächst die Methode EraseNoCrop() des grafischen Elements auf, zeichnen dann den Rahmen und schneiden verdeckte Bereiche.
In der Methode, die die Beschreibung der Integer-Eigenschaft des Elements zurückgibt, fügen wir einen Codeblock hinzu, um die Beschreibung der neuen Eigenschaften des Objekts zurückzugeben — die Koordinaten und die Größe seines Sichtbarkeitsbereichs:
//+------------------------------------------------------------------+ //| 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_ACT_RIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_RIGHT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ACT_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_VISIBLE_AREA_X ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_VISIBLE_AREA_Y ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_GROUP ? CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ZORDER ? CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+ (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)) ) : "" ); } //+------------------------------------------------------------------+
Das Objekt ist nun in der Lage, den Namen der neuen Eigenschaften anzuzeigen, die im aktuellen Artikel erstellt wurden.
Verbessern wir die Erase()-Methoden entsprechend dem neuen Konzept ihrer Konstruktion in der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh, der Standardobjektklasse der Steuerung:
//+------------------------------------------------------------------+ //| 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::EraseNoCrop(colour,opacity,false); //--- 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.Crop(); 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::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Hier rufen wir zunächst die Methode EraseNoCrop() des grafischen Elementes auf, zeichnen dann den Rahmen und schneiden ausgeblendeten Bereiche ab.
Verbessern wir die Methode Redraw() in CheckBox \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh der WinForms-Objektklassendatei:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CCheckBox::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),true); //--- Set corrected text coordinates relative to the checkbox this.SetCorrectTextCoords(); //--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.ShowControlFlag(this.CheckState()); this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Hier löschen wir zuerst das Bild mit der Objektfarbe, ohne versteckte Bereiche zu beschneiden, dann zeichnen wir alles, was wir brauchen, auf die Leinwand (wie zuvor) und rufen die Methode zum Beschneiden versteckter Bereiche des Bildes auf.
In der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh der Objektklasse WinForms, und zwar in der Methode zum Einstellen des Elementtextes, rufen wir nach dem Einstellen die Methode zum Beschneiden verdeckter Bereiche auf, damit der gezeichnete Text entlang der Grenzen des sichtbaren Bereichs beschnitten wird:
//--- Set the element text virtual void SetText(const string text) { CWinFormBase::SetText(text); if(this.AutoSize()) this.AutoSetWH(); this.Crop(); }
In der Methode, die das Objekt neu zeichnet, ersetzen wir den Aufruf der Methode Erase() durch den Aufruf der Methode EraseNoCrop(). Nach all den Manipulationen mit dem Aufbau des Erscheinungsbildes des Objekts, rufen wir die Methode zum Schneiden der verdeckten Bereiche des Bildes:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CLabel::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.EraseNoCrop(this.BackgroundColor(),0,redraw); //--- Declare the variables for X and Y coordinates and set their values depending on the text alignment int x=0,y=0; this.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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Verbessern wir die Objekt-Redraw-Methode auf die gleiche Weise in der Objektdatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh der Schaltfläche von WinForms:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CButton::Redraw(bool redraw) { //--- Fill the object with the background color this.EraseNoCrop(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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Klassen von Hilfsobjekten für Pfeiltasten
Wenn das WinForms-Objekt über ein einzeiliges Layout von Registerkarten verfügt und es mehr Registerkarten gibt, als in die Breite oder Höhe des Objekts passen, dann werden die Registerkarten, die über ihre Container hinausgehen, ausgeblendet.um die Kopfleiste zu verschieben, müssen wir Pfeiltasten erstellen. Wenn Sie darauf klicken, wird die Kopfleiste nach links/rechts oder oben/unten verschoben. Wir werden solche Schaltflächen in anderen Steuerelementen benötigen, daher werden sie in der Liste der WinForms-Hilfsobjekte aufgeführt — sie sind keine eigenständigen Steuerelemente, sondern werden zur Erstellung anderer verwendet.
Solche Schaltflächenobjekte mit Pfeilen werden wie folgt angeordnet: Wir werden ein Basisobjekt für alle diese Schaltflächen erstellen, das Methoden zum Einstellen ihrer Eigenschaften enthalten wird. Abgeleitete Objekte erzeugen eine bestimmte Schaltfläche: mit einem Pfeil nach links, rechts, oben oder unten.
Darüber hinaus werden wir auf der Grundlage der erstellten Objekte zwei weitere erstellen — sie werden verwendet, um das WinForms TabControl-Objekt zu erstellen, und zwar werden dies Objekte mit zwei Schaltflächen sein: eine wird zwei Schaltflächen haben, die horizontal mit Links-Rechts-Pfeilen angeordnet sind, und die zweite wird zwei Schaltflächen haben, die vertikal mit Auf- und Ab-Pfeilen angeordnet sind. Diese Objekte dienen zum horizontalen und vertikalen Scrollen der Registerkarten-Kopfleiste.
Im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ erstellen wir die neue Datei ArrowButton.mqh mit der Klasse CArrowButton.
Die Klasse sollte von der Klasse CCheckBox abgeleitet sein, und ihre Datei sollte in die eingebunden Klassendatei aufgenommen werden:
//+------------------------------------------------------------------+ //| ArrowButton.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowButton : public CButton { }
Im privaten Abschnitt wird eine Variable deklariert, in der die Farbe des Pfeils gespeichert wird. Im geschützten Abschnitt werden wir eine virtuelle Methode zum Zeichnen des Pfeils und einen geschützten Konstruktor deklarieren. Im öffentlichen Abschnitt Methoden deklarieren wir zum Setzen und Zurückgeben der Farbe des Pfeils einen parametrischen Konstruktor und Methoden zum Neuzeichnen des Objekts und zum Zeichnen seines Rahmens:
//+------------------------------------------------------------------+ //| Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowButton : public CButton { private: color m_arrow_color; // Arrow color protected: //--- Draw the arrow virtual void DrawArrow(void){return;} //--- Protected constructor with object type, chart ID and subwindow CArrowButton(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: //--- (1) Set and (2) return the arrow color void SetArrowColor(const color clr) { this.m_arrow_color=clr; } color ArrowColor(void) const { return this.m_arrow_color; } //--- Constructor CArrowButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- Redraw the object virtual void Redraw(bool redraw); //--- 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); //--- Draw the button frame virtual void DrawFrame(void); }; //+------------------------------------------------------------------+
Die virtuelle Methode DrawArrow() zeichnet nichts in der Klasse. Da sie virtuell ist, wird sie in abgeleiteten Klassen neu definiert, von denen jede ihre eigene Methode zum Zeichnen von Pfeilen — links, rechts, oben und unten — erstellen wird.
Ich denke, der Zweck der anderen Methoden ist klar. Sie befinden sich alle in anderen Bibliotheksobjekten, und wir haben sie schon oft berücksichtigt.
Der geschützte Konstruktor, der den Objekttyp, die Chart-ID und das Unterfenster angibt:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowButton::CArrowButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetPaddingAll(0); this.SetMarginAll(0); this.SetBorderSizeAll(1); this.SetArrowColor(CLR_DEF_FORE_COLOR); } //+------------------------------------------------------------------+
Der Typ des erstellten Objekts wird an den Konstruktor weitergegeben, der wiederum die Kette an die übrigen übergeordneten Objekte weitergibt. Im Hauptteil des Konstruktors werden der Typ des grafischen Elements, der Typ des grafischen Objekts der Bibliothek, Nullwerte für Padding und Margin, die Größe des Rahmens von einem Pixel und die Farbe des Pfeils als Standardtextfarbe der Steuerelemente festgelegt.
Nach der Erstellung eines Objekts können alle diese Parameter (mit Ausnahme der Objekttypen) geändert werden.
Im parametrischen Konstruktor gehen wir genauso vor, nur dass der Typ des zu erstellenden Objekts nicht übergeben wird, während im Initialisierungsstring der Typ „Arrow button“ (Pfeiltaste) an den Konstruktor des übergeordneten Objekts übergeben wird:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowButton::CArrowButton(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_ARROW_BUTTON,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetPaddingAll(0); this.SetMarginAll(0); this.SetBorderSizeAll(1); this.SetArrowColor(CLR_DEF_FORE_COLOR); } //+------------------------------------------------------------------+
Die Methode zum Neuzeichnen eines Objekts:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CArrowButton::Redraw(bool redraw) { //--- Fill the object with background color having transparency this.Erase(this.BackgroundColor(),this.Opacity(),true); } //+------------------------------------------------------------------+
Hier wird die Methode Erase() aufgerufen, mit der wir die Neuzeichnung vornehmen:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CArrowButton::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); this.DrawArrow(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Hier ist alles genauso wie bei allen anderen Objekten, entsprechend dem neuen Konzept des Beschneidens versteckter Bereiche des Bildes: Die Methode EraseNoCrop() (bei der das Objekt mit der Hintergrundfarbe gefüllt wird) wird zuerst aufgerufen, dann werden der Rahmen und der Pfeil gezeichnet sowie versteckte Bereiche beschnitten.
Die Methode, die ein Element mit einer Farbverlaufsfüllung löscht:
//+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CArrowButton::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::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); this.DrawArrow(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Alles ist genau gleich wie bei der oben beschriebenen Methode. Hier wird die überladene Methode EraeNoCrop() aufgerufen. Sie füllt den Hintergrund mit einem Farbverlauf.
Die Methode, die den Rand eines Elements zeichnet:
//+------------------------------------------------------------------+ //| Draw the element border | //+------------------------------------------------------------------+ void CArrowButton::DrawFrame(void) { this.DrawRectangle(0,0,this.Width()-1,this.Height()-1,this.BorderColor(),this.Opacity()); } //+------------------------------------------------------------------+
Hier zeichnen wir einfach ein Rechteck um die Objektgrenzen mit der angegebenen Hintergrundfarbe und Deckkraft.
Wenn wir dieses Objekt erstellen, wird einfach eine normale Schaltfläche ohne Beschriftung und Pfeile gezeichnet. Die Pfeile werden in den abgeleiteten Objekten der Klasse gezeichnet.
Objekt mit linker Pfeiltaste.
Wir erstellen im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ die neue Datei ArrowLeftButton.mqh für die Klasse CArrowLeftButton. Die Klasse sollte von der neu erstellten Pfeilschaltflächen-Basisklasse abgeleitet werden und ihre Datei sollte in die erstellte Klassendatei aufgenommen werden:
//+------------------------------------------------------------------+ //| ArrowLeftButton.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 "ArrowButton.mqh" //+------------------------------------------------------------------+ //| Left Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowLeftButton : public CArrowButton { }
Im geschützten Bereich der Klasse deklarieren wir die Methode zum Zeichnen des Pfeils und den geschützten Konstruktor, während wir im öffentlichen Bereich den parametrischen Konstruktor deklarieren:
//+------------------------------------------------------------------+ //| Left Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowLeftButton : public CArrowButton { private: protected: //--- Draw the arrow virtual void DrawArrow(void); //--- Protected constructor with object type, chart ID and subwindow CArrowLeftButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CArrowLeftButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Im geschützten Konstruktor legen wir den Typ des an die Methode übergebenen grafischen Elements fest, während wir im parametrischen Konstruktor den Objekttyp als „linke Pfeiltaste“ in der Initialisierungszeichenfolge an den Konstruktor der übergeordneten Klasse übergeben und denselben Typ für das Objekt festlegen:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowLeftButton::CArrowLeftButton(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) : CArrowButton(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); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowLeftButton::CArrowLeftButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT); } //+------------------------------------------------------------------+
Die Methode zum Zeichnen eines Pfeils:
//+------------------------------------------------------------------+ //| Draw the arrow | //+------------------------------------------------------------------+ void CArrowLeftButton::DrawArrow(void) { //--- Create X and Y coordinate arrays for drawing a triangle double x=(double)this.Width()/2; double y=(double)this.Height()/2; double w=(double)this.Width(); double h=(double)this.Height(); //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(w*0.7), int(w*0.7), int(w*0.3)}; int array_y[]={int(h*0.3), int(h*0.7), int(h*0.5)}; //--- Draw a filled triangle followed by a smoothed one on top of it this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); } //+------------------------------------------------------------------+
Diese virtuelle Methode wird für jedes der Objekte, die Pfeile in verschiedene Richtungen zeichnen, unterschiedlich sein. Sie unterscheiden sich jedoch nur in den Koordinaten der gezeichneten Dreieckspunkte. Der Unterschied zwischen dieser Klasse und anderen, die Pfeile auf Schaltflächen in andere Richtungen zeichnen, liegt nur in der Art des grafischen Elements und der virtuellen Methode, die einen Pfeil entsprechend seiner individuellen Koordinaten zeichnet.
Betrachten wir sie in ihrer Gesamtheit, ohne sie zu erklären, da die obige Klasse mit den anderen völlig identisch ist und sich alle diese Klassen im gleichen Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ befinden.
Die Objektklasse der rechten Pfeiltaste in der Datei ArrowRightButton.mqh:
//+------------------------------------------------------------------+ //| ArrowRightButton.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 "ArrowButton.mqh" //+------------------------------------------------------------------+ //| Right Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowRightButton : public CArrowButton { private: protected: //--- Draw the arrow virtual void DrawArrow(void); //--- Protected constructor with object type, chart ID and subwindow CArrowRightButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CArrowRightButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowRightButton::CArrowRightButton(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) : CArrowButton(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); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowRightButton::CArrowRightButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT); } //+------------------------------------------------------------------+ //| Draw the arrow | //+------------------------------------------------------------------+ void CArrowRightButton::DrawArrow(void) { //--- Create X and Y coordinate arrays for drawing a triangle double x=(double)this.Width()/2; double y=(double)this.Height()/2; double w=(double)this.Width(); double h=(double)this.Height(); //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(w*0.3), int(w*0.7), int(w*0.3)}; int array_y[]={int(h*0.3), int(h*0.5), int(h*0.7)}; //--- Draw a filled triangle followed by a smoothed one on top of it this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); } //+------------------------------------------------------------------+
Objektklasse der Pfeil-nach-oben-Schaltfläche in der Datei ArrowUpButton.mqh:
//+------------------------------------------------------------------+ //| ArrowUpButton.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 "ArrowButton.mqh" //+------------------------------------------------------------------+ //| Up Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowUpButton : public CArrowButton { private: protected: //--- Draw the arrow virtual void DrawArrow(void); //--- Protected constructor with object type, chart ID and subwindow CArrowUpButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CArrowUpButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowUpButton::CArrowUpButton(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) : CArrowButton(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); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowUpButton::CArrowUpButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP); } //+------------------------------------------------------------------+ //| Draw the arrow | //+------------------------------------------------------------------+ void CArrowUpButton::DrawArrow(void) { //--- Create X and Y coordinate arrays for drawing a triangle double x=(double)this.Width()/2; double y=(double)this.Height()/2; double w=(double)this.Width(); double h=(double)this.Height(); //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(w*0.3), int(w*0.5), int(w*0.7)}; int array_y[]={int(h*0.7), int(h*0.3), int(h*0.7)}; //--- Draw a filled triangle followed by a smoothed one on top of it this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); } //+------------------------------------------------------------------+
Objektklasse der Pfeil-nach-unten-Taste in der Datei ArrowDownButton.mqh:
//+------------------------------------------------------------------+ //| ArrowDownButton.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 "ArrowButton.mqh" //+------------------------------------------------------------------+ //| Down Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowDownButton : public CArrowButton { private: protected: //--- Draw the arrow virtual void DrawArrow(void); //--- Protected constructor with object type, chart ID and subwindow CArrowDownButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CArrowDownButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowDownButton::CArrowDownButton(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) : CArrowButton(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); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowDownButton::CArrowDownButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN); } //+------------------------------------------------------------------+ //| Draw the arrow | //+------------------------------------------------------------------+ void CArrowDownButton::DrawArrow(void) { //--- Create X and Y coordinate arrays for drawing a triangle double x=(double)this.Width()/2; double y=(double)this.Height()/2; double w=(double)this.Width(); double h=(double)this.Height(); //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(w*0.3), int(w*0.5), int(w*0.7)}; int array_y[]={int(h*0.3), int(h*0.7), int(h*0.3)}; //--- Draw a filled triangle followed by a smoothed one on top of it this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); } //+------------------------------------------------------------------+
Alle diese Klassen sind identisch. Der Unterschied besteht nur in den Objekttypen, die in den Klassenkonstruktoren festgelegt werden, und in den unterschiedlichen Koordinatenwerten der Eckpunkte in den Methoden DrawArrow().
Zwei weitere Hilfsklassen, die auf den erstellten Klassen der Schaltflächenobjekte mit Pfeilen basieren. Jeder von ihnen wird einen Container haben, an dem zwei Knöpfe befestigt werden. Bei der ersten Klasse sind die linke und rechte Taste horizontal angeordnet, bei der zweiten Klasse sind die Auf- und Ab-Tasten vertikal angeordnet.
Im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ erstellen wir die neue Datei ArrowLeftRightBox.mqh für CArrowLeftRightBox
Die Klasse sollte vom Containerobjekt WinForms-Klasse abgeleitet werden, während die CPanel-Klassendatei in die Datei der erstellten Klasse aufgenommen werden sollte:
//+------------------------------------------------------------------+ //| ArrowLeftRightBox.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" //+------------------------------------------------------------------+ //| ArrowLeftRightBox object class of WForms controls | //+------------------------------------------------------------------+ class CArrowLeftRightBox : public CContainer { }
Im privaten Abschnitt der Klasse deklarieren wir eine virtuelle Methode zur Erstellung eines grafischen Objekts und eine Methode zur Erstellung zweier Pfeilschaltflächen. Im geschützten Bereich der Klasse deklarieren wir einen geschützten Konstruktor, während wir im öffentlichen Bereich zwei Methoden schreiben, um Zeiger auf Schaltflächenobjekte mit Pfeilen zu erhalten, und einen parametrischen Konstruktor deklarieren:
//+------------------------------------------------------------------+ //| ArrowLeftRightBox object class of WForms controls | //+------------------------------------------------------------------+ class CArrowLeftRightBox : public CContainer { private: //--- 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); //--- Create ArrowButton Up and Down objects void CreateArrowButtons(const int width,const int height); protected: //--- Protected constructor with object type, chart ID and subwindow CArrowLeftRightBox(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Return the pointer to the (1) up and (2) down arrow button CArrowLeftButton *GetArrowUpButton(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0); } CArrowRightButton*GetArrowDownButton(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,0); } //--- Constructor CArrowLeftRightBox(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Betrachten wir nun die Implementierung der angegebenen Methoden.
Der geschützte Konstruktor, der den Objekttyp, die Chart-ID und das Unterfenster angibt:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowLeftRightBox::CArrowLeftRightBox(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) { //--- 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_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE)); } //+------------------------------------------------------------------+
Hier stellen wir den Typ des grafischen Elements ein, das an den Konstruktor übergeben wird, stellen den Typ des grafischen Objekts der Bibliothek als „Hilfsobjekt“ ein, setzen die Größe des Objektrahmens auf ein Pixel, den Rahmentyp auf flach, die Rahmenfarbe auf Standard, die Standardfarbe der Pfeile auf die Standardtextfarbe von WinForms-Objekten und rufen die Methode zur Erstellung von zwei Pfeilschaltflächen auf. Wenn in diesem Fall die Breite und Höhe, die dem Konstruktor übergeben werden, kleiner sind als die angegebene Größe des Schaltflächenobjekts mit Standardpfeilen, wird das Objekt mit den angegebenen Abmessungen erstellt, andernfalls mit den Standardabmessungen. Die standardmäßig eingestellte Schaltflächengröße ist also der maximal mögliche Wert für Pfeilschaltflächenobjekte.
Der parametrische Konstruktor mit Chart- und Unterfenster-ID:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CArrowLeftRightBox::CArrowLeftRightBox(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_ARROW_BUTTONS_LR_BOX,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE)); } //+------------------------------------------------------------------+
Hier ist alles gleich wie im geschützten Konstruktor, nur dass hier der Typ des grafischen Elements nicht an den Konstruktor übergeben wird, sondern fest als ArrowLeftRightButtonBox codiert ist.
Die Methode, die die Objekte ArrowButton Left und Right erstellt:
//+------------------------------------------------------------------+ //| Create ArrowButton Left and Right objects | //+------------------------------------------------------------------+ void CArrowLeftRightBox::CreateArrowButtons(const int width,const int height) { //--- Calculate the width of the object from the width of two buttons plus the size of the frame on the left and right //--- and the height of the object from the height of the button plus the top and bottom frame sizes int w=width*2+this.BorderSizeLeft()+this.BorderSizeRight(); int h=height+this.BorderSizeTop()+this.BorderSizeBottom(); //--- If the received width or height is greater than the width or height of the object, resize it if(w>this.Width() || h>this.Height()) this.Resize((w>this.Width() ? w : this.Width()),(h>this.Height() ? h : this.Height()),false); //--- Create two buttons next to each other starting from the 0 : 0 coordinate of the container this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0,0,width,height,clrNONE,255,true,false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,width,0,width,height,clrNONE,255,true,false); } //+------------------------------------------------------------------+
Die Größen der zu erstellenden Schaltflächen werden an die Methode übergeben. Ausgehend von der Größe der Schaltflächen wird die Breite des Containers als Größe der Breite von zwei Schaltflächen berechnet, wobei die Größe des Objektrahmens berücksichtigt wird. Die Höhe des Containers ergibt sich aus der Höhe des Knopfes unter Berücksichtigung der Größe des Containerrahmens. Wenn die berechnete Größe der Schaltflächen größer ist als die Größe des Containers, wird dessen Größe auf die berechnete Größe erhöht, während die Methoden aufgerufen werden, um horizontal nebeneinander angeordnete Schaltflächen zu erstellen.
Die Methode zum Erstellen eines neuen grafischen Objekts:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CArrowLeftRightBox::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_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(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; } //+------------------------------------------------------------------+
Mit der virtuellen Methode können nur zwei Objekte erstellt werden — die linke und die rechte Pfeilschaltfläche.
Die Klasse zur Erstellung eines Containers mit zwei vertikal angeordneten Schaltflächen ist identisch mit der oben beschriebenen.
ImBibliotheksordner \MQL5\Include\DoEasy\Objects\Graph\WForms\ erstellen wir die neue Datei ArrowUpDownBox.mqh für die Klasse CArrowUpDownBox.
Die Klasse sollte vom Containerobjekt WinForms-Klasse abgeleitet werden, während die CPanel-Klassendatei in die Datei der erstellten Klasse aufgenommen werden sollte:
//+------------------------------------------------------------------+ //| ArrowUpDownBox.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" //+------------------------------------------------------------------+ //| ArrowUpDownBox object class of the WForms controls | //+------------------------------------------------------------------+ class CArrowUpDownBox : public CContainer { private: //--- 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); //--- Create ArrowButton Up and Down objects void CreateArrowButtons(const int width,const int height); protected: //--- Protected constructor with object type, chart ID and subwindow CArrowUpDownBox(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Return the pointer to the (1) up and (2) down arrow button CArrowUpButton *GetArrowUpButton(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0); } CArrowDownButton *GetArrowDownButton(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0); } //--- Constructor CArrowUpDownBox(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowUpDownBox::CArrowUpDownBox(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) { //--- 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_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE)); } //+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CArrowUpDownBox::CArrowUpDownBox(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_ARROW_BUTTONS_UD_BOX,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE)); } //+------------------------------------------------------------------+ //| Create ArrowButton Up and Down objects | //+------------------------------------------------------------------+ void CArrowUpDownBox::CreateArrowButtons(const int width,const int height) { //--- Calculate the width of the object from the width of the button plus the size of the frame on the left and right //--- and the height of the object from the height of two buttons plus the top and bottom frame sizes int w=width+this.BorderSizeLeft()+this.BorderSizeRight(); int h=height*2+this.BorderSizeTop()+this.BorderSizeBottom(); //--- If the received width or height is greater than the width or height of the object, resize it if(w>this.Width() || h>this.Height()) this.Resize((w>this.Width() ? w : this.Width()),(h>this.Height() ? h : this.Height()),false); //--- Create two buttons one above the other starting from the 0 : 0 coordinate of the container this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0,0,width,height,clrNONE,255,true,false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0,height,width,height,clrNONE,255,true,false); } //+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CArrowUpDownBox::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_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(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; } //+------------------------------------------------------------------+
Der Unterschied zwischen den beiden oben genannten Klassen besteht nur in den Typen der grafischen Elemente, die in den Klassenkonstruktoren festgelegt werden, und in den Methoden zur Erstellung von zwei Schaltflächen. In der zweiten Klasse erstellt die Methode vertikal positionierte Schaltflächen und passt die Größe des Containers an die Höhe der beiden Schaltflächen an. Beide Methoden sind in der Auflistung ausführlich kommentiert und bedürfen hoffentlich keiner weiteren Erläuterung.
Diese beiden Objekte werden im nächsten Artikel verwendet, um die Kopfleiste der Registerkarten zu verschieben, wenn sie horizontal und vertikal in einer Zeile angeordnet sind, über das Steuerelement hinausgehen und dementsprechend ausgeblendet sind.
Nachdem wir nun neue grafische Elemente erstellt haben, müssen wir die Container-Objekte verfeinern, damit sie von den neuen Objekten wissen und sie erstellen können.
In der Container-Objektklassendatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh benennen wir die Methoden um, die die Größe und die Koordinaten des Arbeitsbereichs zurückgeben, sodass ihre Namen den Namen ähnlicher Methoden entsprechen — wir entfernen das Präfix „Get“:
public: //--- Return the size and coordinates of the working area int WidthWorkspace(void) const { return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int HeightWorkspace(void) const { return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } int CoordXWorkspace(void) const { return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft()); } int CoordYWorkspace(void) const { return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop()); } int RightEdgeWorkspace(void) const { return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int BottomEdgeWorkspace(void) const { return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } //--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)
Wir deklarieren die virtuelle Methode zum Beschneiden ausgeblendeter Objektbereiche:
virtual void SetBorderSizeBottom(const uint value) { CForm::SetBorderSizeBottom(value); if(this.PaddingBottom()<this.BorderSizeBottom()) this.SetPaddingBottom(this.BorderSizeBottom()); } //--- Crop the image outlined by the specified rectangular visibility area virtual void Crop(void); protected:
Solche Methoden sollten in allen wichtigen WinForms-Objekten der Bibliothek vorhanden sein.
In der Methode, die ein neues gebundenes Element erstellt, fügen wir den Aufruf der Methode Crop() für ein neu erstelltes Objekt hinzu:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CContainer::CreateNewElement(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) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set parameters for the created object this.SetObjParams(obj,colour); //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Crop along the edges of the visible part obj.Crop(); //--- return 'true' return true; } //+------------------------------------------------------------------+
Nach der Erstellung eines neuen Objekts und der Größenanpassung des Containers an die darin erstellten Objekte (bei gesetztem Flag für die automatische Größenanpassung des Containers) sollte geprüft werden, ob das neu erstellte Objekt über den Containerbereich hinausgeht, in dem die Objekte sichtbar sein sollten. Außerdem sollten wir die Teile des Bildes des neuen Objekts beschneiden, die außerhalb dieses Bereichs liegen.
In der Methode, die Parameter für das gebundene Objekt festlegt, fügen wir die Implementierung der Zeiger auf die Basis- und Hauptobjekte hinzu und schreiben den Codeblock zum Festlegen der Parameter für die Pfeilschaltflächenobjekte, die erstellt und an den Container gebunden wurden:
//+------------------------------------------------------------------+ //| Set parameters for the attached object | //+------------------------------------------------------------------+ void CContainer::SetObjParams(CWinFormBase *obj,const color colour) { obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); //--- Set the text color of the object to be the same as that of the base container obj.SetForeColor(this.ForeColor(),true); //--- If the created object is not a container, set the same group for it as the one for its base object if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX) obj.SetGroup(this.Group()); //--- Depending on the object type switch(obj.TypeGraphElement()) { //--- For the Container, Panel and GroupBox WinForms objects case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : //--- set the frame color equal to the background color obj.SetBorderColor(obj.BackgroundColor(),true); break; //--- 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 : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break; //--- For the "TabControl", "ArrowButton" WinForms object case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY); break; //--- For the "ArrowButton" WinForms object case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : //--- 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.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; default: break; } obj.Crop(); } //+------------------------------------------------------------------+
Am Ende der Methode schneiden wir die ausgeblendeten Bereiche des erstellten Objekts zu.
Es ist wahrscheinlich, dass der Aufruf der Methode Crop() überflüssig ist, wenn das angehängte Element in der vorhergehenden Methode erstellt wurde und nachdem in dieser Methode Standardeigenschaften festgelegt wurden. Weitere Tests werden die Methode zeigen, aus der der Aufruf der Methode Crop() entfernt werden kann.
Schreiben wir eine Implementierung der Methode, die das durch den berechneten rechteckigen Sichtbarkeitsbereich umrissene Bild beschneidet:
//+------------------------------------------------------------------+ //| Crop the image outlined by the calculated | //| rectangular visibility scope | //+------------------------------------------------------------------+ void CContainer::Crop(void) { //--- Get the pointer to the base object CContainer *base=this.GetBase(); //--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave if(base==NULL) return; //--- Set the initial coordinates and size of the visibility scope to the entire object int vis_x=0; int vis_y=0; int vis_w=this.Width(); int vis_h=this.Height(); //--- Set the size of the top, bottom, left and right areas that go beyond the container int crop_top=0; int crop_bottom=0; int crop_left=0; int crop_right=0; //--- Calculate the boundaries of the container area, inside which the object is fully visible int top=fmax(base.CoordYWorkspace(),base.CoordYVisibleArea()); int bottom=fmin(base.BottomEdgeWorkspace(),base.BottomEdgeVisibleArea()+1); int left=fmax(base.CoordXWorkspace(),base.CoordXVisibleArea()); int right=fmin(base.RightEdgeWorkspace(),base.RightEdgeVisibleArea()+1); //--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond //--- the boundaries of the container area, inside which the object is fully visible crop_top=this.CoordY()-top; if(crop_top<0) vis_y=-crop_top; crop_bottom=bottom-this.BottomEdge()-1; if(crop_bottom<0) vis_h=this.Height()+crop_bottom-vis_y; crop_left=this.CoordX()-left; if(crop_left<0) vis_x=-crop_left; crop_right=right-this.RightEdge()-1; if(crop_right<0) vis_w=this.Width()+crop_right-vis_x; //--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0) this.Crop(vis_x,vis_y,vis_w,vis_h); } //+------------------------------------------------------------------+
Die Methode ist identisch mit dem, was ich in der Objektklasse CGCnvElement für grafische Elemente geschrieben habe, aber anstatt den Basisobjekttyp als CGCnvElement zu erhalten, erhalten wir das Basisobjekt mit dem CContainer-Containerobjekttyp. Um die Grenzen des Containerbereichs, in dem das Objekt vollständig sichtbar ist, zu berechnen, verwenden wir die Methoden, die die Grenzen des Container-Arbeitsbereichs zurückgeben, die nicht im grafischen Basiselement vorhanden sind.
In der Methode ArrangeObjects() wurden die Namen der zuvor umbenannten Methoden, bei denen das Präfix „Get“ bereits entfernt wurde, schon durch die aktuellen Namen ersetzt.
Zum Beispiel:
//+------------------------------------------------------------------+ //| Place bound objects in the order of their Dock binding | //+------------------------------------------------------------------+ bool CContainer::ArrangeObjects(const bool redraw) { //--- Get the list of bound objects with WinForms type basic and higher CArrayObj *list=this.GetListWinFormsObj(); CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL; //--- In the loop by all bound objects, for(int i=0;i<list.Total();i++) { //--- Get the current and previous elements from the list obj=list.At(i); prev=list.At(i-1); //--- If the object is not received, move on if(obj==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire working area width and by the initial object height), move on to the next one if(!obj.Resize(this.WidthWorkspace(),obj.GetHeightInit(),false)) continue; //--- Get the object binding coordinates x=this.CoordXWorkspace(); y=(prev!=NULL ? prev.BottomEdge()+1 : this.CoordYWorkspace()); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Bottom
Es gibt viele solcher und ähnlicher Ersetzungen in der Methode, sie sind alle schon gemacht worden, und es macht keinen Sinn, sie hier zu beschreiben. Dies sind nur Verbesserungen aus der Kategorie der Intellisense-Nutzerfreundlichkeit beim Schreiben von Code, und sie haben keinen Einfluss auf seine Logik.
Verbessern wir die Panel-Objektklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.
Fügen wir ihr die Dateien aller heute neu erstellten Klassen hinzu:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "..\TabField.mqh" #include "..\ArrowButton.mqh" #include "..\ArrowUpButton.mqh" #include "..\ArrowDownButton.mqh" #include "..\ArrowLeftButton.mqh" #include "..\ArrowRightButton.mqh" #include "..\ArrowUpDownBox.mqh" #include "..\ArrowLeftRightBox.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" //+------------------------------------------------------------------+
Nun sind diese Klassen in allen grafischen Objekten der Bibliothek sichtbar, in denen sie erstellt werden können.
Wir fügen die Codeblöcke für die Erstellung aller neu erstellten Objekte in die Methode zur Erstellung eines neuen grafischen Objekts ein:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::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; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(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 Methode, die das Underlay-Objekt erstellt, ändern wir die Namen der zuvor umbenannten Methoden:
//+------------------------------------------------------------------+ //| Create the underlay object | //+------------------------------------------------------------------+ bool CPanel::CreateUnderlayObj(void) { this.m_underlay=new CGCnvElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY,this.ID(),this.Number(),this.ChartID(),this.SubWindow(),this.NameObj()+"Underlay", this.CoordXWorkspace(),this.CoordYWorkspace(),this.WidthWorkspace(),this.HeightWorkspace(), CLR_CANV_NULL,0,false,false); if(m_underlay==NULL) { CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ); return false; } if(!this.m_list_tmp.Add(this.m_underlay)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); delete this.m_underlay; return false; } this.SetUnderlayParams(); return true; } //+------------------------------------------------------------------+
In der Datei der Steuerklasse GroupBox, \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh, und zwar in der Methode zum Erstellen eines neuen grafischen Objekts, fügen wir die Codeblöcke für die Erstellung aller neuen Objekte hinzu, die ich hier genauso wie in der vorherigen Klasse erstellt habe:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::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; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(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 Methoden zum Löschen eines Elements wurden in Übereinstimmung mit den gleichen, bereits abgeschlossenen Methoden anderer Klassen verbessert:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CGroupBox::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- Draw a frame encasing a group of objects this.DrawFrame(); //--- Draw a header above the frame CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity()); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CGroupBox::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::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- Draw a frame encasing a group of objects this.DrawFrame(); //--- Draw a header above the frame CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity()); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Wie üblich füllen wir mit der Hintergrundfarbe, die benötigten Gestaltungselemente und schneiden überflüssige Elemente außerhalb des sichtbaren Bereichs ab.
Wir verbessern die Header-Objektklasse der Registerkarte TabControl in \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.
Im öffentlichen Abschnitt der Klasse deklarieren wir die Methode zum Neuzeichnen des Objekts:
//--- Sets the state of the control virtual void SetState(const bool flag); //--- Redraw the object virtual void Redraw(bool redraw); //--- Clear the element filling it with color and opacity
Wir legen in den Klassenkonstruktoren den Typ des grafischen Objekts der Bibliothek für das Element als „WinForms-Hilfsobjekt“ fest:
//+------------------------------------------------------------------+ //| 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_HELPER; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); //---... //---... 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_HELPER; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); //---... //---... this.SetSizes(w,h); this.SetState(false); } //+------------------------------------------------------------------+
In der Methode, die den Zustand des Steuerelements festlegt, wird die Kopfzeile nur dann in den Vordergrund gestellt, wenn das Objekt sichtbar ist. Behandeln wir das Tabulatorfeld-Objekt auf die gleiche Weise — wir zeigen es an, bringen es in den Vordergrund, zeichnen Gestaltungselemente darauf und schneiden es nur, wenn das Objekt sichtbar ist:
//+------------------------------------------------------------------+ //| 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(); if(this.IsVisible()) this.BringToTop(); //--- Get the base object the tab title 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.SetZorder(base.Zorder()+1,false); if(this.IsVisible()) { field.Show(); field.DrawFrame(); field.Crop(); field.BringToTop(); } } //--- If the button is not pressed, call the method to restore the title size else { this.WHProcessStateOff(); CWinFormBase *field=this.GetFieldObj(); field.Hide(); } } } //+------------------------------------------------------------------+
Wenn die Schaltfläche nicht gedrückt wird, wird das Registerkartenfeldobjekt ausgeblendet.
Die Methoden zur Reinigung der Elemente wurden in Übereinstimmung mit dem neuen Konzept für alle grafischen Elemente verbessert:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CTabHeader::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CTabHeader::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Füllen mit Farbe, Zeichnen der Designelemente und Abschneiden des Überstehenden.
Die Methode zum Neuzeichnen eines Objekts:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CTabHeader::Redraw(bool redraw) { //--- Fill the object with the background color this.Erase(this.BackgroundColor(),this.Opacity(),false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Hier rufen wir die Methode zum Füllen des Objekts mit Farbe auf, legen die Parameter für die Textausgabe fest, zeichnen den Text und schneiden dann die Bereiche des Bildes ab, die über den Container hinausgehen.
In der Ereignisbehandlung „Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt“ (die Methode MouseActiveAreaReleasedHandler()),
fügen wir das Abschneiden des Registerkartenfeldes bei der Anzeige auf dem Element nach dem Klicken auf die Registerkartenüberschrift hinzu:
//--- 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(); field.Crop(); } //--- 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); } } //+------------------------------------------------------------------+
Verbessern wir die Tabulatorfeld-Objektklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\TabField.mqh.
In den Klassenkonstruktoren legen wir den Typ des grafischen Objekts der Bibliothek als „WinForms-Hilfsobjekt“ fest:
//+------------------------------------------------------------------+ //| 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_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); //---... //---... 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_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); //---... //---... this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetPaddingAll(3); } //+------------------------------------------------------------------+
Die Methoden zum Löschen von Elementen wurden in der gleichen Weise verbessert wie in allen früheren Klassen von WinForms-Objekten:
//+------------------------------------------------------------------+ //| 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::EraseNoCrop(colour,opacity,false); //--- 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.Crop(); 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::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Fügen wir der Methode zum Erstellen eines neuen grafischen Objekts die Codeblöcke für die Konstruktion aller hier erstellten neuen Objekte hinzu:
//+------------------------------------------------------------------+ //| 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; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(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; } //+------------------------------------------------------------------+
Verbessern wir das WinForms-Objekt TabControl in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh.
Für dieses Objekt müssen wir eigene Methoden erstellen, um es in den Vordergrund zu bringen und anzuzeigen. Solche Methoden übergeordneter Klassen bewegen sich in einer Schleife durch alle Objekte, die mit dem Container verbunden sind, bringen sie alle in den Vordergrund und zeigen sie alle an. Die Logik ist für das TabControl nicht geeignet — es enthält verborgene Felder, die von den Methoden der übergeordneten Klassen angezeigt und in den Vordergrund gebracht werden, was nicht akzeptabel ist. Hier müssen wir zunächst prüfen, ob das Feld zur ausgewählten Registerkarte gehört und nur dieses Feld angezeigt und in den Vordergrund gebracht werden soll, während alle anderen ausgeblendet bleiben.
Deklarieren wir diese beiden virtuellen Methoden im öffentlichen Abschnitt der Klasse:
//--- 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()); } //--- Set the object above all virtual void BringToTop(void); //--- Show the control virtual void Show(void); //--- Constructor
Im Klassenkonstruktor müssen wir sicherstellen, dass Padding auf Null gesetzt wurde, da sonst die Größe des Bereichs, in dem die gebundenen Objekte sichtbar sein sollen, um drei Pixel auf jeder Seite beschnitten wird (standardmäßig war Padding gleich drei Pixel) und die Registerkartenfelder an den falschen Stellen beschnitten werden — entlang der Kontur des Containers, drei Pixel groß:
//+------------------------------------------------------------------+ //| 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.SetPaddingAll(0); this.SetHeaderPadding(6,3); this.SetFieldPadding(3,3,3,3); } //+------------------------------------------------------------------+
In der Methode, die die angegebene Anzahl von Registerkarten erstellt, passen wir die Koordinaten der Kopfzeile und die Größe der Registerkartenfelder um zwei Pixel an. Außerdem sollte das Sichtbarkeitsflag der Registerkartenüberschrift mit dem des Steuerelements übereinstimmen. In diesem Fall werden die Registerkartenüberschriften für ein ausgeblendetes Objekt nicht angezeigt:
//+------------------------------------------------------------------+ //| 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=2; 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=2; 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-2; 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()); } header.SetVisibleFlag(this.IsVisible(),false); //--- 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()-2; 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()-2; 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()-2; break; case CANV_ELEMENT_ALIGNMENT_RIGHT : field_x=0; field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width()-2; 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; } //+------------------------------------------------------------------+
Durch die Verschiebung der Kopfzeilen um zwei Pixel werden diese innerhalb des Containers platziert, während die ausgewählte Kopfzeile um zwei Pixel vergrößert wird und somit außerhalb des Containers liegt. Dann wird es sicher beschnitten, da sein um zwei Pixel vergrößerter Teil über seinen Container hinausgeht und unsichtbar werden sollte. Das Verschieben der Koordinaten der Kopfzeile löst dieses Problem, aber gleichzeitig sollte auch das Tabulatorfeld um zwei Pixel verkleinert werden, damit es in den Container passt, da das Tabulatorfeld seine Koordinaten von den Koordinaten der nun um zwei Pixel verschobenen Kopfzeile übernimmt.
In der Methode, die die Registerkartenüberschriften an die Breite des Steuerelements anpasst, ändern wir (StretchHeadersByWidth()), die Berechnung der Überschriftenbreite. Jetzt wird ihre Breite durch Abrundung auf die nächste ganze Zahl berechnet, was ihre Ausrichtung in der Reihe ein wenig angenehmer macht:
//--- 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=(int)round((double)base_size/double(num>0 ? num : 1)); //--- In the loop by row headers for(int j=0;j<list_row.Total();j++) {
Bei der Methode, bei der die Registerkarte als ausgewählt festgelegt wird, werden die übrigen Registerkarten zwangsweise als freigegeben festgelegt:
//+------------------------------------------------------------------+ //| 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()) { CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { if(i==index) continue; this.SetUnselected(i); } header.SetState(true); } //--- save the index of the selected tab this.SetSelectedTabPageNum(index); } //+------------------------------------------------------------------+
Der Index der ausgewählten Registerkarte wird an die Methode übergeben. In der Schleife durch die Liste aller Tabulatorüberschriften, überprüfen wir den Schleifenindex. Wenn er gleich dem ausgewählten Registerindex ist, wird die Schleife zur nächsten Iteration geschickt. Wenn der Schleifenindex nicht gleich dem Index des ausgewählten Tabs ist, dann rufen wir die Methode auf, um den durch den Schleifenindex angegebenen Tab in den freigegebenen Zustand zu versetzen.
Nach dem Setzen des Textes rufen wir die Methode zum Beschneiden der verdeckten Bereiche der Kopfzeile in der Methode auf, die den Kopftext der angegebenen Registerkarte setzt:
//+------------------------------------------------------------------+ //| Set the title text of the specified tab | //+------------------------------------------------------------------+ void CTabControl::SetHeaderText(CTabHeader *header,const string text) { if(header==NULL) return; header.SetText(text); header.Crop(); } //+------------------------------------------------------------------+
Damit wird der Text in der Kopfzeile abgeschnitten, wenn er teilweise außerhalb des Bereichs liegt.
Fügen wir der Methode zum Erstellen eines neuen grafischen Objekts die Codeblöcke für die Konstruktion aller hier erstellten neuen Objekte hinzu:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_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; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(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 Methode, die das Steuerelement, control, anzeigt:
//+------------------------------------------------------------------+ //| Show the control | //+------------------------------------------------------------------+ void CTabControl::Show(void) { //--- Get the list of all tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- If the object has a shadow, display it if(this.m_shadow_obj!=NULL) this.m_shadow_obj.Show(); //--- Display the container CGCnvElement::Show(); //--- Move all elements of the object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
Zunächst wird der Container des Objekts angezeigt (im Normalzustand hat er eine transparente Farbe, kann aber jede vom Nutzer angegebene Farbe haben), dann wird die Methode aufgerufen, die alle Elemente des Objekts in den Vordergrund bringt. Auf diese Methode werde ich weiter unten eingehen.
Die Methode, die das Objekt über alles (in den Vordergrund) stellt:
//+------------------------------------------------------------------+ //| Set the object above all the rest | //+------------------------------------------------------------------+ void CTabControl::BringToTop(void) { //--- Move all elements of the object to the foreground CForm::BringToTop(); //--- Get the index of the selected tab int selected=this.SelectedTabPageNum(); //--- Declare the pointers to tab header objects and tab fields CTabHeader *header=NULL; CTabField *field=NULL; //--- Get the list of all tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- In a loop by the list of tab headers, for(int i=0;i<list.Total();i++) { //--- get the next header, and if failed to get the object, //--- or this is the header of the selected tab, skip it header=list.At(i); if(header==NULL || header.PageNumber()==selected) continue; //--- bring the header to the foreground header.BringToTop(); //--- get the tab field corresponding to the current header field=header.GetFieldObj(); if(field==NULL) continue; //--- Hide the tab field field.Hide(); } //--- Get the pointer to the title of the selected tab header=this.GetTabHeader(selected); if(header!=NULL) { //--- bring the header to the front header.BringToTop(); //--- get the tab field corresponding to the selected tab header field=header.GetFieldObj(); //--- Display the tab field on the foreground if(field!=NULL) field.BringToTop(); } } //+------------------------------------------------------------------+
Die Methodenlogik wurde in den Codekommentaren ausführlich beschrieben. Kurz gesagt, wir müssen alle Registerkartenüberschriften und das Feld der aktuell ausgewählten Registerkarte in den Vordergrund bringen. Dafür bringen wir in einer Schleife alle Kopfzeilen in den Vordergrund, mit Ausnahme der Kopfzeile der ausgewählten Registerkarte, und blenden die Felder dieser Registerkarten aus.
Am Ende bringen wir die Kopfzeile der ausgewählten Registerkarte und ihr Feld in den Vordergrund.
Mit diesen Änderungen wird das TabControl funktionieren, ohne dass die Felder nicht ausgewählter Registerkarten fälschlicherweise in den Vordergrund geschaltet werden, und es wird korrekt angezeigt, wenn von ausgeblendet zu sichtbar gewechselt wird.
In der Kollektionsklasse für grafische Elemente in der Datei \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, und zwar in der Methode, die den Zeiger auf das Formular unter dem Cursor zurückgibt, fügen wir die Überprüfung des Objektsichtbarkeitsflags hinzu, um die Auswahl verborgener Objekte zu vermeiden:
//+------------------------------------------------------------------+ //| Return the pointer to the form located under the cursor | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam, ENUM_MOUSE_FORM_STATE &mouse_state, long &obj_ext_id, int &form_index) { //--- Set the ID of the extended standard graphical object to -1 //--- and the index of the anchor point managed by the form to -1 obj_ext_id=WRONG_VALUE; form_index=WRONG_VALUE; //--- Initialize the mouse status relative to the form mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare the pointers to graphical element collection class objects CGCnvElement *elm=NULL; CForm *form=NULL; //--- Get the list of objects the interaction flag is set for (there should be only one object) CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL); //--- If managed to obtain the list and it is not empty, if(list!=NULL && list.Total()>0) { //--- Get the only graphical element there elm=list.At(0); //--- If the element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE && elm.IsVisible()) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is inside the form, if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { //--- Find the interaction object. //--- This will be either the found object or the same form form=this.SearchInteractObj(form,id,lparam,dparam,sparam); //--- Return the form object //Comment(form.TypeElementDescription()," ",form.Name()); return form; } } } //--- If there is no a single form object with a specified interaction flag, //--- in the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the next element elm=this.m_list_all_canv_elm_obj.At(i); if(elm==NULL || !elm.IsVisible() || !elm.Enabled()) continue; //--- if the obtained element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { //--- Find the interaction object. //--- This will be either the found object or the same form form=this.SearchInteractObj(form,id,lparam,dparam,sparam); //--- Return the form object //Comment(form.TypeElementDescription()," ",form.Name()); return form; } } } //--- ... //--- ... //---... //--- Nothing is found - return NULL return NULL; } //+------------------------------------------------------------------+
In der Methode der Nachbearbeitung des ehemals aktiven Formulars unter dem Cursor werden die Überprüfung des Sichtbarkeitsflags und die Behandlung des TabControl-Objekts hinzugefügt:
//+------------------------------------------------------------------+ //| 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 || !obj.IsVisible() || !obj.Enabled()) 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; if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { CTabControl *tab_ctrl=elm; CForm *selected=tab_ctrl.SelectedTabPage(); if(selected!=NULL) elm=selected; } //--- 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()); } //+------------------------------------------------------------------+
Wenn das aktuelle Objekt in der Schleife TabControl ist, müssen wir nur die ausgewählte Registerkarte für die Verarbeitung finden. Nachdem die Registerkarte gefunden und der Mauszeiger zugewiesen wurde, wird nur die ausgewählte Registerkarte vom Mausereignishandler verarbeitet.
Derzeit sind dies alle Verbesserungen und Veränderungen der Bibliotheksklassen.
Test
Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part117\ als TestDoEasy117.mq5.
Erstellen wir ein TabControl auf dem Hauptpanel. Auf der ersten Registerkarte platzieren wir alle Objekte der hier erstellten Pfeilschaltflächen-Objektklassen. Die horizontalen Links-Rechts-Doppelpfeile werden im Vergleich zur Standardbreite verkleinert, um zu sehen, ob sie in der Größe angepasst werden können. Wir fügen zwei Eingabevariablen für die Standortkoordinaten von TabControl hinzu. Dann können wir die Anfangskoordinaten so festlegen, dass das erstellte Objekt außerhalb seines Containers gehen kann, um Methoden zum Zuschneiden eines Bildes zu testen, das außerhalb des Sichtbarkeitsbereichs liegt. Wir können dies jedoch bereits aus den Registerkartenüberschriften ersehen. Es wird 11 davon geben, und sie passen nicht in die Größe des Steuerelements, wenn sie in einer Reihe angeordnet sind.
Wir deklarieren zwei neue Variablen im Eingabe-Block:
//--- 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 sinput int InpTabControlX = 10; // TabControl X coord sinput int InpTabControlY = 20; // TabControl Y coord //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
Wir implementieren in OnInit() den folgenden Code zum Erstellen des Panels und zum Anhängen von TabControl an dieses:
//+------------------------------------------------------------------+ //| 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,410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { pnl.Hide(); Print(DFUN,"Panel visibility: ",pnl.IsVisible(),": ",pnl.TypeElementDescription()," ",pnl.Name()); //--- Set Padding to 4 pnl.SetPaddingAll(3); //--- 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); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false); CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tc!=NULL) { tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tc.SetMultiline(InpTabCtrlMultiline); tc.SetHeaderPadding(6,0); tc.CreateTabPages(11,0,56,16,TextByLanguage("Вкладка","TabPage")); //--- tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,10,10,15,15,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,30,10,15,15,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,50,10,15,15,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,70,10,15,15,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,10,30,13,13,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,40,30,9,13,clrNONE,255,true,false); } //--- Redraw all objects according to their hierarchy pnl.Show(); pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Unmittelbar nach der Erstellung des Panels blenden wir es aus. Alle anderen Konstruktionen finden im ausgeblendeten Modus statt: das Erstellen von TabControl auf dem Panel, das Erstellen von 11 Registerkarten sowie das Erstellen aller Pfeilschaltflächenobjekte, deren Klassen ich hier auf der ersten Registerkarte hinzugefügt habe. Nachdem wir alle erforderlichen Elemente hinzugefügt haben, zeigen wir das Panel an und zeichnen es neu.
Kompilieren Sie den EA und starten Sie ihn auf einem Chart:
Das Beschneiden von Bereichen, die über den Sichtbarkeitsbereich hinausgehen, funktioniert korrekt, Kopfzeilen, die über den Container hinausgehen, werden entlang seines Randes beschnitten, und wenn wir die Koordinaten des TabControls so einstellen, dass das Element links über den Container hinausgeht, wird auch hier alles korrekt beschnitten — das Element selbst wird entlang der Ränder des Panels beschnitten, und Schaltflächen, die sich auf dem Steuerelement befinden, werden ebenfalls durch den Rand des Sichtbarkeitsbereichs des Panels und nicht durch ihre Container beschnitten. Hier funktioniert alles korrekt. Horizontale Links-Rechts-Schaltflächen haben eine geringere Breite als die Standardbreite (9 Pixel). Trotzdem werden sie korrekt angezeigt.
Was muss repariert werden? Das Schattenobjekt erscheint vor dem Feld, das es wirft. Wir werden uns später damit befassen.
Was kommt als Nächstes?
Im nächsten Artikel werde ich die Arbeit an TabControl fortsetzen. Außerdem werde ich das Scrollen der Registerkartenüberschriften, die über das Steuerelement hinausgehen, implementieren.
*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
DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/11408





- 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.