
DoEasy. Steuerung (Teil 2): Arbeiten an der Klasse CPanel
Inhalt
Konzept
Im letzten Artikel habe ich ein ausführliches Thema über die Erstellung von Steuerelementen im Stil von Windows Forms begonnen. Aber ich habe einige Fehler und Unzulänglichkeiten, die auf die Anfänge des Umgangs mit grafischen Objekten zurückgehen, immer noch nicht behoben. Ich habe zum Beispiel noch nie getestet, wie sich grafische Objekte verhalten, wenn man den Zeitrahmen des Charts wechselt. Sie werden einfach nicht im Chart angezeigt, während im Journal Meldungen erscheinen, dass solche Objekte bereits erstellt wurden. Sie werden also nicht geschaffen und nicht erbracht. Es gibt auch einige Probleme. Zum Beispiel können nur Formularobjekte mit der Maus interagieren. Das grafische Element, das als übergeordnetes Objekt für das Formular dient, hat keine Mausfunktionalität. Diese Lösung ist sinnvoll, da es sich um das minimale grafische Objekt der Bibliothek handelt, das für alle grafischen Konstruktionen verwendet werden kann. Wenn wir jedoch Interaktion benötigen, sollte ein Formularobjekt als minimales grafisches Objekt dienen. Aber sein Nachkomme ist ein Panel-Steuerelement, mit dessen Entwicklung ich im vorherigen Artikel begonnen habe. Es reagiert nicht so gut auf die Maus. Obwohl es das sollte. Dies sind die Kosten für das ständige Hinzufügen von Objekten und ihrer Funktionalität zur Bibliothek.
Im aktuellen Artikel werde ich einige Unzulänglichkeiten und Fehler beheben sowie die Funktionalität des Panel-Steuerungsobjekts weiter ausbauen. Insbesondere werde ich die Methoden zur Einstellung der Parameter der Schriftart implementieren, die standardmäßig für alle Textobjekte des Panels verwendet wird. Wir haben zum Beispiel ein bestimmtes Objekt der Klasse CPanel. Sie ermöglicht es, andere Steuerelemente daran zu befestigen. Wenn ein angefügtes Steuerelement einen Text hat, werden die Standard-Schriftartparameter von dem Panel geerbt, an das es angefügt ist. Im Gegenzug kann das Panel (als separates grafisches Element) wie alle anderen grafischen Elemente der Bibliothek auch beliebige Texte in sich selbst zeichnen. Auch für diese Texte werden standardmäßig die Schriftartparameter verwendet. Natürlich können wir einen neuen Text auf ein grafisches Element zeichnen, indem wir andere Schriftparameter unmittelbar vor der Anzeige festlegen. Der Text wird mit diesen explizit angegebenen Parametern angezeigt.
Außerdem sollte jedes Bedienfeld als Steuerelement die Möglichkeit haben, die Rahmen des Bedienfelds anzuzeigen und die Rahmenparameter zu steuern. Implementieren wir die Möglichkeit, einen Rahmen mit den Standardwerten und mit explizit angegebenen Parametern oder ohne sie zu zeichnen.
Verbesserung des Bibliotheksunterrichts
Ich habe die Datei \MQL5\Include\DoEasy\GraphINI.mqh erstellt, um schnell verschiedene Anzeigestile von grafischen Elementen einstellen zu können.
Sie enthält Parameter für verschiedene Farbschemata sowie Typen und Anzeigestile für verschiedene grafische Elemente. Später wird es möglich sein, benutzerdefinierte Parameter hinzuzufügen, wobei die vorhandenen Parameter als Beispiele dienen.
Um die Parameter des Formularstils visueller darzustellen, müssen wir die Reihenfolge der Indizes und der entsprechenden Stilwerte leicht ändern.
Verschieben wir einfach die Parameter, die in der Liste an erster Stelle stehen, nach ganz unten und legen deren Eigentümerschaft fest:
//+------------------------------------------------------------------+ //| List of form style parameter indices | //+------------------------------------------------------------------+ enum ENUM_FORM_STYLE_PARAMS { //--- CForm FORM_STYLE_FRAME_SHADOW_OPACITY, // Shadow opacity FORM_STYLE_FRAME_SHADOW_BLUR, // Shadow blur FORM_STYLE_DARKENING_COLOR_FOR_SHADOW, // Form shadow color darkening FORM_STYLE_FRAME_SHADOW_X_SHIFT, // Shadow X axis shift FORM_STYLE_FRAME_SHADOW_Y_SHIFT, // Shadow Y axis shift //--- CPanel FORM_STYLE_FRAME_WIDTH_LEFT, // Panel frame width to the left FORM_STYLE_FRAME_WIDTH_RIGHT, // Panel frame width to the right FORM_STYLE_FRAME_WIDTH_TOP, // Panel frame width on top FORM_STYLE_FRAME_WIDTH_BOTTOM, // Panel frame width below }; #define TOTAL_FORM_STYLE_PARAMS (9) // Number of form style parameters //+------------------------------------------------------------------+ //| Array containing form style parameters | //+------------------------------------------------------------------+ int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]= { //--- "Flat form" style parameters { //--- CForm 80, // Shadow opacity 4, // Shadow blur 80, // Form shadow color darkening 2, // Shadow X axis shift 2, // Shadow Y axis shift //--- CPanel 3, // Panel frame width to the left 3, // Panel frame width to the right 3, // Panel frame width on top 3, // Panel frame width below }, //--- "Embossed form" style parameters { //--- CForm 80, // Shadow opacity 4, // Shadow blur 80, // Form shadow color darkening 2, // Shadow X axis shift 2, // Shadow Y axis shift //--- CPanel 3, // Panel frame width to the left 3, // Panel frame width to the right 3, // Panel frame width on top 3, // Panel frame width below }, }; //+------------------------------------------------------------------+
Dies ist keineswegs eine kritische Verbesserung, aber die richtige Strukturierung der Parameter wird später die Lesbarkeit erleichtern.
In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:
//--- CGraphElementsCollection MSG_GRAPH_ELM_COLLECTION_ERR_OBJ_ALREADY_EXISTS, // Error. A chart control object already exists with chart id MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ,// Failed to create chart control object with chart ID MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ, // Failed to get chart control object with chart ID MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS,// Such graphical object already exists: MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT, // Error! Empty object MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT, // Failed to get a graphical element from the list
und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:
//--- CGraphElementsCollection {"Ошибка. Уже существует объект управления чартами с идентификатором чарта ","Error. A chart control object already exists with chart id "}, {"Не удалось создать объект управления чартами с идентификатором чарта ","Failed to create chart control object with chart id "}, {"Не удалось получить объект управления чартами с идентификатором чарта ","Failed to get chart control object with chart id "}, {"Такой графический объект уже существует: ","Such a graphic object already exists: "}, {"Ошибка! Пустой объект","Error! Empty object"}, {"Не удалось получить графический элемент из списка","Failed to get graphic element from list"},
Die Flags ermöglichen die Verwaltung des Schriftstils. Wir sollten jedoch eine Enumeration verwenden, um die erforderlichen Schriftarten und -breiten aus der Liste auswählen zu können. Wenn wir eine Enumeration als Schriftart oder -breite an eine Methode übergeben, können wir eine Art und einen Typ aus der Dropdown-Liste auswählen, was viel bequemer ist, als sich die Namen der Flags zu merken.
Außerdem kann das Panel in den meisten Fällen mit einem Rahmen um ein Objekt angezeigt werden. Daher benötigen wir eine Makrosubstitution, die die Standardrahmenbreite des Panels angibt.
Erstellen wir in \MQL5\Include\DoEasy\Defines.mqh die Makrosubstitution, die die Standardbreite aller Rahmenseiten des Paneels angibt:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel #define CLR_FORE_COLOR (C'0x2D,0x43,0x48') // Default color for texts of objects on canvas #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font 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
sowie die Enumerationen von Schriftstilen und Breitentypen ganz am Ende der Dateiliste:
//+------------------------------------------------------------------+ //| Font style list | //+------------------------------------------------------------------+ enum ENUM_FONT_STYLE { FONT_STYLE_NORMAL=0, // Normal FONT_STYLE_ITALIC=FONT_ITALIC, // Italic FONT_STYLE_UNDERLINE=FONT_UNDERLINE, // Underline FONT_STYLE_STRIKEOUT=FONT_STRIKEOUT // Strikeout }; //+------------------------------------------------------------------+ //| FOnt width type list | //+------------------------------------------------------------------+ enum ENUM_FW_TYPE { FW_TYPE_DONTCARE=FW_DONTCARE, FW_TYPE_THIN=FW_THIN, FW_TYPE_EXTRALIGHT=FW_EXTRALIGHT, FW_TYPE_ULTRALIGHT=FW_ULTRALIGHT, FW_TYPE_LIGHT=FW_LIGHT, FW_TYPE_NORMAL=FW_NORMAL, FW_TYPE_REGULAR=FW_REGULAR, FW_TYPE_MEDIUM=FW_MEDIUM, FW_TYPE_SEMIBOLD=FW_SEMIBOLD, FW_TYPE_DEMIBOLD=FW_DEMIBOLD, FW_TYPE_BOLD=FW_BOLD, FW_TYPE_EXTRABOLD=FW_EXTRABOLD, FW_TYPE_ULTRABOLD=FW_ULTRABOLD, FW_TYPE_HEAVY=FW_HEAVY, FW_TYPE_BLACK=FW_BLACK }; //+------------------------------------------------------------------+
Wie Sie sehen können, habe ich für jede Enumerationskonstante einfach den entsprechenden Flag-Wert gesetzt. Aber für den Schriftstil habe ich eine neue Konstante für die "normale" Schrift eingeführt - weder kursiv, noch unterstrichen, noch durchgestrichen. Der Wert ist gleich Null und setzt zuvor aktivierte zusätzliche Schriftstil-Flags zurück.
Die Klasse der Formularobjekte enthält die Animationsklasse für grafische Elemente auf Leinwandbasis und das Schattenobjekt für Formulare. Beide Objekte werden mit dem Operator 'new' erstellt. Nach der Fertigstellung werden sie im Destruktor der Klasse entfernt. So werden diese Objekte immer rechtzeitig aufgespürt und entfernt.
Das Problem trat jedoch auf, als ich von der Formularobjektklasse erbte. Das Panel-Objekt wird von der Form-Objektklasse abgeleitet. Wie sich herausstellt, werden die oben genannten Objekte nicht entfernt, was zu einem Speicherleck führt. Sie können den EA über vorherigen Artikel starten, um sich selbst davon zu überzeugen. Beim Entfernen aus dem Chart zeigt das Journal die Meldung über den Verlust von 4 Objekten und das Auslaufen von 512 Byte Speicher an:
4 undeleted objects left 1 object of type CAnimations left 3 objects of type CArrayObj left 512 bytes of leaked memory
Ich habe lange Zeit vergeblich nach dem Grund gesucht, warum die Objekte nicht gelöscht werden. Daher werde ich alle diese Aufgaben dem Terminal-Subsystem zuweisen.
Dazu erstellen wir einfach das Klassenobjekt und fügen ihm die in der Klasse CForm erstellten Objekte hinzu. Nach Abschluss des Vorgangs löscht das Terminal den Speicher aller in der Liste enthaltenen Objekte.
Wir öffnen die Enumeration \MQL5\Include\DoEasy\Objects\Graph\Form.mqh und deklarieren eine solche Liste.
Verschieben wir auch die Variablen für die Speicherung der Breite jeder Seite des Formularrahmens in den Bereich der geschützten Klasse:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_tmp; CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Create a new graphical object CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, 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 a shadow object void CreateShadowObj(const color colour,const uchar opacity); protected: int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom //--- Initialize the variables void Initialize(void); void Deinitialize(void); public:
Wir werden auf die Variablen von den abgeleiteten Klassen aus zugreifen, daher sollten die Variablen im geschützten Bereich gespeichert werden, der sowohl in dieser Klasse als auch in den abgeleiteten Klassen sichtbar bleibt.
In der Initialisierungsmethode löschen wir die neue Liste und setzen das Flag für sortierte Listen. Wir legen für die Breite jeder Seite des Formularrahmens den neuen Makro-Ersatzwert fest und fügen das Animationsobjekt zur neuen Liste hinzu:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CForm::Initialize(void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE; this.m_mouse_state_flags=0; this.m_offset_x=0; this.m_offset_y=0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(m_animations); } //+------------------------------------------------------------------+
In der Methode, die ein Schattenobjekt erstellt, fügen wir das Objekt der neuen Liste hinzu, nachdem es erfolgreich erstellt worden ist:
//+------------------------------------------------------------------+ //| Create the shadow object | //+------------------------------------------------------------------+ void CForm::CreateShadowObj(const color colour,const uchar opacity) { //--- ... //--- ... //--- Create a new shadow object and set the pointer to it in the variable this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); return; } this.m_list_tmp.Add(m_shadow_obj); //--- ... //--- Move the form object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
Diese Verbesserung bewahrt uns vor unkontrollierten und schwer zu findenden Speicherlecks.
Ich werde meine Arbeit an der Klasse des WinForms CPanel-Steuerungsobjekts fortsetzen.
Implementieren wir den Umgang mit den Parametern der Panel-Schriftart und ihrem Rahmen.
Deklarieren wir im privaten Abschnitt der Klasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh die Variable zum Speichern des Schriftbreitentyps und die Methode, die die für die Schriftart gesetzten Flags zurückgibt:
//+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { private: color m_fore_color; // Default text color for all panel objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Panel frame style bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding element borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls //--- Return the font flags uint GetFontFlags(void); public:
Wenn wir den Wert für die Schriftbreite in der Klasse festlegen, wird der Wert in der Variablen m_bold_type angegeben.
Die Methode, die die Flags der Schrifttypen zurückgibt, gibt alle Parameter zurück, die für sie festgelegt wurden: Name, Größe, Flags und Winkel. Da wir hier nur mit den Flags arbeiten, rufen wir die Methode auf, die nur die Flags aus den Schrifteigenschaften der CCanvas-Klasse zurückgibt, um zu vermeiden, dass in jeder Methode lokale Variablen deklariert werden, die schriftspezifische Werte enthalten.
Deklarieren wir im öffentlichen Abschnitt der Klasse die Methoden für die Behandlung der Schriftstilflags und Schriftbreitentypen:
public: //--- (1) Set and (2) return the default text color of all panel objects void ForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the Bold font flag void Bold(const bool flag); bool Bold(void); //--- (1) Set and (2) return the Italic font flag void Italic(const bool flag); bool Italic(void); //--- (1) Set and (2) return the Strikeout font flag void Strikeout(const bool flag); bool Strikeout(void); //--- (1) Set and (2) return the Underline font flag void Underline(const bool flag); bool Underline(void); //--- (1) Set and (2) return the font style void FontDrawStyle(ENUM_FONT_STYLE style); ENUM_FONT_STYLE FontDrawStyle(void); //--- (1) Set and (2) return the font width type void FontBoldType(ENUM_FW_TYPE type); ENUM_FW_TYPE FontBoldType(void) const { return this.m_bold_type; } //--- (1) Set and (2) return the frame style
...
und schreiben die Methoden zum Setzen und Zurückgeben der Eigenschaften des Panelrahmens:
//--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } //--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control void FrameWidthLeft(const int value) { this.m_frame_width_left=value; } void FrameWidthTop(const int value) { this.m_frame_width_top=value; } void FrameWidthRight(const int value) { this.m_frame_width_right=value; } void FrameWidthBottom(const int value) { this.m_frame_width_bottom=value; } void FrameWidthAll(const int value) { this.FrameWidthLeft(value); this.FrameWidthTop(value); this.FrameWidthRight(value); this.FrameWidthBottom(value); } //--- Return the width of the form frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom int FrameWidthLeft(void) const { return this.m_frame_width_left; } int FrameWidthTop(void) const { return this.m_frame_width_top; } int FrameWidthRight(void) const { return this.m_frame_width_right; } int FrameWidthBottom(void) const { return this.m_frame_width_bottom; } //--- Constructors
Wir fügen in jedem Konstruktor die Einstellung der Standard-Schriftbreite hinzu:
CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CPanel::CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CPanel::CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CPanel::CPanel(const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),0,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+
Der Typ ist standardmäßig für die Schrifttypen Bedienfeld eingestellt.
Die private Methode, die die Schriftart-Flags zurückgibt:
//+------------------------------------------------------------------+ //| Return the font flags | //+------------------------------------------------------------------+ uint CPanel::GetFontFlags(void) { string name; int size; uint flags; uint angle; CGCnvElement::GetFont(name,size,flags,angle); return flags; } //+------------------------------------------------------------------+
Da die Methode GetFont() der Grafikelementklasse die Variablen über eine Verknüpfung erhalten soll, während die übergebenen Variablen die aus den Schriftparametern in der Klasse CCanvas gewonnenen Werte enthalten sollen, deklarieren wir hier alle erforderlichen Variablen, holen ihre Werte durch Aufruf der Methode GetFont() ab und geben nur die erhaltenen Flags zurück.
Die Methode, die das Flag für die Schriftart fett (Bold) setzt:
//+------------------------------------------------------------------+ //| Set the Bold font flag | //+------------------------------------------------------------------+ void CPanel::Bold(const bool flag) { uint flags=this.GetFontFlags(); if(flag) { this.m_bold_type=FW_TYPE_BOLD; CGCnvElement::SetFontFlags(flags | FW_BOLD); } else this.m_bold_type=FW_TYPE_NORMAL; } //+------------------------------------------------------------------+
Hier erhalten wir die Flags mit der oben beschriebenen Methode GetFontFlags(). Wenn das in den Methodenargumenten übergebene Flag gesetzt ist, schreiben wir den Wert Bold in die Variable m_bold_type, in der der Schriftbreitentyp gespeichert ist, und setzen Sie ein weiteres Flag FW_BOLD für die Schriftflags.
Wenn das in den Methodenargumenten übergebene Flag nicht gesetzt ist, erhält die Variable m_bold_type den Standardwert.
Die Methode, die das Flag für die Schriftart Bold zurückgibt:
//+------------------------------------------------------------------+ //| Return the Bold font flag | //+------------------------------------------------------------------+ bool CPanel::Bold(void) { uint flags=this.GetFontFlags(); return(flags &FW_BOLD)==FW_BOLD; } //+------------------------------------------------------------------+
Hier werden die Flags mit derMethodeGetFontFlags() abgerufen und das Ergebnis der Prüfung, ob das FW_BOLD-Flag in der Variablen vorhanden ist, zurückgegeben.
Die Methoden zum Setzen und Zurückgeben der übrigen Schriftartenflags unterscheiden sich geringfügig, da sie kein Setzen der Variablenwert des Flags erfordern.
Im Übrigen sind sie mit den oben genannten identisch:
//+------------------------------------------------------------------+ //| Set the Italic font flag | //+------------------------------------------------------------------+ void CPanel::Italic(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_ITALIC); } //+------------------------------------------------------------------+ //| Return the Italic font flag | //+------------------------------------------------------------------+ bool CPanel::Italic(void) { uint flags=this.GetFontFlags(); return(flags &FONT_ITALIC)==FONT_ITALIC; } //+------------------------------------------------------------------+ //| Set the Strikeout font flag | //+------------------------------------------------------------------+ void CPanel::Strikeout(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT); } //+------------------------------------------------------------------+ //| Return the Strikeout font flag | //+------------------------------------------------------------------+ bool CPanel::Strikeout(void) { uint flags=this.GetFontFlags(); return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT; } //+------------------------------------------------------------------+ //| Set the Underline font flag | //+------------------------------------------------------------------+ void CPanel::Underline(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE); } //+------------------------------------------------------------------+ //| Return the Underline font flag | //+------------------------------------------------------------------+ bool CPanel::Underline(void) { uint flags=this.GetFontFlags(); return(flags &FONT_UNDERLINE)==FONT_UNDERLINE; } //+------------------------------------------------------------------+
Ich glaube, diese Methoden sind klar und bedürfen keiner Erklärung.
Die Methode zur Einstellung des Schriftstils:
//+------------------------------------------------------------------+ //| Set the font style | //+------------------------------------------------------------------+ void CPanel::FontDrawStyle(ENUM_FONT_STYLE style) { switch(style) { case FONT_STYLE_ITALIC : this.Italic(true); break; case FONT_STYLE_UNDERLINE : this.Underline(true); break; case FONT_STYLE_STRIKEOUT : this.Strikeout(true); break; default: break; } } //+------------------------------------------------------------------+
Je nach dem an die Methode übergebenenSchriftstil (kursiv, unterstrichen, durchgestrichen) wird die dem Stil entsprechende Installationsmethode aufgerufen.
Die Methode, die den Schriftstil zurückgibt:
//+------------------------------------------------------------------+ //| Return the font style | //+------------------------------------------------------------------+ ENUM_FONT_STYLE CPanel::FontDrawStyle(void) { return ( this.Italic() ? FONT_STYLE_ITALIC : this.Underline() ? FONT_STYLE_UNDERLINE : this.Strikeout() ? FONT_STYLE_UNDERLINE : FONT_STYLE_NORMAL ); } //+------------------------------------------------------------------+
Je nach Schriftart, die von den entsprechenden Methoden zurückgegeben wird, wird dieselbe Schriftart zurückgegeben.
Wenn keiner der drei Stile eingestellt ist, wird Normal zurückgegeben.
Die Methode, die den Typ der Schriftbreite zurückgibt:
//+------------------------------------------------------------------+ //| Set the font width type | //+------------------------------------------------------------------+ void CPanel::FontBoldType(ENUM_FW_TYPE type) { this.m_bold_type=type; uint flags=this.GetFontFlags(); switch(type) { case FW_TYPE_DONTCARE : CGCnvElement::SetFontFlags(flags | FW_DONTCARE); break; case FW_TYPE_THIN : CGCnvElement::SetFontFlags(flags | FW_THIN); break; case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT); break; case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT); break; case FW_TYPE_LIGHT : CGCnvElement::SetFontFlags(flags | FW_LIGHT); break; case FW_TYPE_REGULAR : CGCnvElement::SetFontFlags(flags | FW_REGULAR); break; case FW_TYPE_MEDIUM : CGCnvElement::SetFontFlags(flags | FW_MEDIUM); break; case FW_TYPE_SEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD); break; case FW_TYPE_DEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD); break; case FW_TYPE_BOLD : CGCnvElement::SetFontFlags(flags | FW_BOLD); break; case FW_TYPE_EXTRABOLD : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD); break; case FW_TYPE_ULTRABOLD : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD); break; case FW_TYPE_HEAVY : CGCnvElement::SetFontFlags(flags | FW_HEAVY); break; case FW_TYPE_BLACK : CGCnvElement::SetFontFlags(flags | FW_BLACK); break; default : CGCnvElement::SetFontFlags(flags | FW_NORMAL); break; } } //+------------------------------------------------------------------+
Legen wir hier in der Variablen m_bold_type den Wert fest, der an die Methode übergeben wird. Holen Sie sich die Font-Flags mit der Methode GetFontFlags().
Je nach dem an die Methode übergebenen Schriftbreiten-Flag fügen wir ein weiteres Flag, das dem angegebenen Typ entspricht, zu der erhaltenen Variablen mit den Schriftbreiten-Flags hinzu.
Folglich enthält m_bold_type den an die Methode übergebenen Enumerationswert, während die Flags der Schrifttypen das Flag mit dem Schriftbreitentyp enthält, der dem Enumerationswert ENUM_FW_TYPE entspricht.
Beim Hinzufügen eines grafischen Elements zur Kollektionsliste wird zunächst geprüft, ob es in der Liste vorhanden ist. Wenn ein solches Objekt bereits vorhanden ist, fügen wir es entweder nicht hinzu, informieren darüber im Journal und geben den Fehler beim Hinzufügen zurück, oder wir tun dasselbe und geben den Zeiger auf das vorhandene Objekt anstelle des Fehlers zurück. Dies kann im Falle der dynamischen Objekterzeugung nützlich sein, um das dynamisch erzeugte, aber ausgeblendete Objekt aus der Methode zurückzugeben und es im Chart anzuzeigen, wenn versucht wird, genau dasselbe Objekt zu erzeugen.
Erstellen wir die Enumeration, um verschiedene Rückgabecodes von der Methode zum Hinzufügen eines grafischen Elements zur Liste zurückzugeben.
Wir schreiben in die Datei der grafischen Kollektionsklasse der grafischen Elemente \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh die folgende Enumeration, bevor wir die Klasse deklarieren:
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ #resource "\\"+PATH_TO_EVENT_CTRL_IND; // Indicator for controlling graphical object events packed into the program resources enum ENUM_ADD_OBJ_RET_CODE // Enumerate the codes of returning the method for adding an object to the list { ADD_OBJ_RET_CODE_SUCCESS, // Successful ADD_OBJ_RET_CODE_EXIST, // Object exists in the collection list ADD_OBJ_RET_CODE_ERROR, // Failed to add to the collection list }; class CGraphElementsCollection : public CBaseObj
Hier gibt es drei Rückgabecodes:
- Objekt erfolgreich zur Liste hinzugefügt,
- Objekt bereits in der Kollektionsliste vorhanden ist,
- das Objekt konnte nicht in die Kollektionsliste aufgenommen werden.
Jetzt können wir flexibel das gewünschte Ergebnis zurückgeben. Der einzige mögliche Fehler ist, dass das Objekt nicht in die Liste aufgenommen werden kann. Andere Ergebnisse zeigen die Möglichkeit an, die Arbeit fortzusetzen - entweder wurde das Objekt erstellt und der Kollektion hinzugefügt oder ein solches Objekt ist bereits vorhanden und wir können es bearbeiten.
Um grafische Elemente direkt aus der Kollektionsklasse zu erstellen, müssen wir ihrem Namen ein Präfix hinzufügen - den Programmnamen mit einem Unterstrich am Ende. Deklarieren wir im privaten Abschnitt der Klasse die Variable zum Speichern des Präfixes des grafischen Objekts:
class CGraphElementsCollection : public CBaseObj { private: //--- ... bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check string m_name_prefix; // Object name prefix //--- Return the flag indicating the graphical element class object presence in the collection list of graphical elements
Fügen wir außerdem zwei private Methoden hinzu: die Methode, die entweder ein neues grafisches Element erstellt oder die ID des vorhandenen Elements zurückgibt,
und die Methode, die den Index des angegebenen grafischen Elements in der Kollektionsliste zurückgibt:
//--- Reset all interaction flags for all forms except the specified one void ResetAllInteractionExeptOne(CGCnvElement *form); //--- Add the element to the collection list bool AddCanvElmToCollection(CGCnvElement *element); //--- Add the element to the collectionl ist or return the existing one ENUM_ADD_OBJ_RET_CODE AddOrGetCanvElmToCollection(CGCnvElement *element,int &id); //--- Return the graphical elemnt index in the collection list int GetIndexGraphElement(const long chart_id,const string name);
Die Methode, die die grafische Elementliste nach Chart-ID und Objektname zurückgibt, muss korrigiert werden. Das Problem ist, dass die Namen aller grafischen Bibliotheksobjekte mit dem Programmnamen beginnen. Der Name wird in das Präfix von grafischen Objektnamen gesetzt. Wenn wir der Suchmethode einfach den Objektnamen übergeben, wird kein solches Objekt gefunden. Dies liegt daran, dass nach dem Namen gesucht wird, der der Methode übergeben wird, während grafische Objekte auch ein Präfix haben. Daher müssen wir das Vorhandensein eines Namenspräfixes im Suchnamen prüfen und, falls kein Präfix vorhanden ist, dieses dem Namen des gesuchten Objekts hinzufügen. In diesem Fall wird die Suche immer korrekt funktionieren - wenn der gesuchte Name bereits ein Präfix enthält, wird dem Namen nichts hinzugefügt und der an die Methode übergebene Wert wird gesucht. Fehlt das Präfix, wird es an den Namen angehängt, und die Suche wird durchgeführt.
Suchen wir im öffentlichen Abschnitt der Klasse die Methode, die die Liste der grafischen Elemente nach Chart-ID und Objektname zurückgibt, und fügen ihr die oben beschriebenen Verbesserungen hinzu:
//--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); }
Hier wird ein Name für die Suche angegeben, geprüft, ob der Name ein Präfix enthält, und wenn nicht, wird das Präfix dem Namen hinzugefügt. Andernfalls sollten Sie nichts hinzufügen.
Als Nächstes wird die Liste der grafischen Elemente nach Chart-ID abgerufen und die Liste nach dem gesuchten Objektnamen sortiert zurückgegeben. Wenn das Objekt nicht gefunden wird, gibt die Methode NULL zurück.
Lassen Sie uns eine weitere Methode schreiben, die das grafische Element nach Chart- und Namens-ID zurückgibt:
//--- Return the graphical element by chart ID and name CGCnvElement *GetCanvElement(const long chart_id,const string name) { CArrayObj *list=this.GetListCanvElementByName(chart_id,name); return(list!=NULL ? list.At(0) : NULL); } //--- Constructor
Hier erhalten Sie die Liste der grafischen Elemente nach Chart-ID und Objektname. Wenn die Liste erhalten wird, geben wir den Zeiger auf das einzige darin enthaltene Objekt zurück, andernfalls wird NULL ZURÜCKGEGEBEN.
Wir müssen die Liste der grafischen Elemente löschen, um alle GUI-Elemente beim Wechsel des Zeitrahmens neu zu erstellen. Da der Destruktor der Klasse, der das Löschen aller Listen vorsieht, nur beim Entfernen des Programms aus dem Chart aufgerufen wird, müssen wir das Löschen im Handler OnDeinit() implementieren. Deklarieren wir es:
//--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag void Refresh(void); void Refresh(const long chart_id); //--- Event handlers void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); void OnDeinit(void); private: //--- Move all objects on canvas to the foreground void BringToTopAllCanvElm(void);
Wir ersetzen diesen Codeblock in jeder Methode, die ein grafisches Element erstellt:
if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; }
mit dem folgenden:
//--- Create a graphical element object on canvas on a specified chart and subwindow int CreateElement(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; if(res==ADD_OBJ_RET_CODE_EXIST) obj.SetID(id); obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling
Hier erhalten wir zunächst den Rückgabewert der Methode, die ein neues grafisches Element erstellt oder die ID des vorhandenen Elements zurückgibt. Wenn die Methode einen Fehler liefert, wird -1 zurückgegeben. Wenn der Rückgabewert anzeigt, dass das Objekt existiert, setzen Sie die von der Methode AddOrGetCanvElmToCollection() erhaltene ID auf die neu erstellten Objekteigenschaften. Der Punkt ist, dass wir zunächst den maximalen Wert für eine neue Objekt-ID festlegen - die Anzahl der Objekte in der Liste, während die ID für das bereits vorhandene Objekt anders sein sollte. In diesem Fall wird die ID auf die Variable gesetzt, die per Link an die Methode übergeben wird, die das Objekt zur Liste hinzufügt oder das vorhandene Objekt zurückgibt.
Wir fügen also entweder ein neues Objekt zur Liste hinzu oder holen uns den Zeiger auf das vorhandene Objekt und setzen seine ID darin.
Für die Methoden zur Erstellung von Formularobjekten und Panels wird der Codeblock etwas anders aussehen:
//--- Create a graphical object form object on canvas on a specified chart and subwindow int CreateForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling
Dieser Fall ist einfacher. Wenn ein Fehler auftritt, wird -1 zurückgegeben. Die ID sollte nicht wiederhergestellt werden, da sie im Klassenkonstruktor für ein neues Objekt zugewiesen und nicht in seiner Erstellungsmethode angegeben wird.
Fügen wir in der Methode zur Erstellung des Panelobjekts die Breite und den Typ des Panelrahmens hinzu:
//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow int CreatePanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const int frame_width=-1, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Erase(clr,opacity,redraw); if(frame_width>0) obj.FrameWidthAll(frame_width); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style); obj.Done(); return obj.ID(); }
Füllen wir das Feld im Methodenrumpf mit Farbe. Wenn die Breite des Rahmens größer als Null ist, stellen wir die Breite aller Rahmenseiten auf die Panel-Eigenschaften ein, legen den aktiven Panel-Bereich innerhalb des Rahmens fest, zeichnen den Rahmen und speichern das Panel-Aussehen.
Legen wir im Klassenkonstruktor den Wert für das Präfix des grafischen Objektnamens festdie Liste aller grafischen Elemente und setzen das Flag für die sortierte Liste :
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CGraphElementsCollection::CGraphElementsCollection() { this.m_type=COLLECTION_GRAPH_OBJ_ID; this.m_name_prefix=this.m_name_program+"_"; ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true); ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true); this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID); this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID); this.m_list_all_graph_obj.Clear(); this.m_list_charts_control.Sort(); this.m_list_charts_control.Clear(); this.m_total_objects=0; this.m_is_graph_obj_event=false; this.m_list_deleted_obj.Clear(); this.m_list_deleted_obj.Sort(); this.m_list_all_canv_elm_obj.Clear(); this.m_list_all_canv_elm_obj.Sort(); } //+------------------------------------------------------------------+
Die Methode, die das grafische Element auf der Leinwand zur Kollektion hinzufügt:
//+------------------------------------------------------------------+ //| Add the graphical element on canvas to the collection | //+------------------------------------------------------------------+ bool CGraphElementsCollection::AddCanvElmToCollection(CGCnvElement *element) { if(!this.m_list_all_canv_elm_obj.Add(element)) { CMessage::ToLog(DFUN+element.Name()+": ",MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); return false; } return true; } //+------------------------------------------------------------------+
Wenn das Element nicht zur Liste hinzugefügt werden kann, wird dies im Journal mitgeteilt und false zurückgegeben. Andernfalls wird true zurückgegeben.
Die Methode, die das Element der Kollektionsliste hinzufügt oder das vorhandene Element zurückgibt:
//+------------------------------------------------------------------+ //| Add the element to the collectionl ist or return the existing one| //+------------------------------------------------------------------+ ENUM_ADD_OBJ_RET_CODE CGraphElementsCollection::AddOrGetCanvElmToCollection(CGCnvElement *element,int &id) { //--- If an invalid pointer is passed, notify of that and return the error code if(element==NULL) { CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT); return ADD_OBJ_RET_CODE_ERROR; } //--- If the graphical element with a specified chart ID and name is already present, if(this.IsPresentCanvElmInList(element.ChartID(),element.Name())) { //--- inform of the object existence, CMessage::ToLog(DFUN+element.Name()+": ",MSG_LIB_SYS_OBJ_ALREADY_IN_LIST); //--- get the element from the collection list. element=this.GetCanvElement(element.ChartID(),element.Name()); //--- If failed to get the object, inform of that and return the error code if(element==NULL) { CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT); return ADD_OBJ_RET_CODE_ERROR; } //--- set the ID of the object obtained from the list to the value returned by the link //--- and return the object existence code in the list id=element.ID(); return ADD_OBJ_RET_CODE_EXIST; } //--- If failed to add the object to the list, remove it and return the error code if(!this.AddCanvElmToCollection(element)) { delete element; return ADD_OBJ_RET_CODE_ERROR; } //--- All is successful return ADD_OBJ_RET_CODE_SUCCESS; } //+------------------------------------------------------------------+
Jeder Methodenstring ist in den Kommentaren ausführlich beschrieben, sodass ich hoffe, dass hier alles klar ist. Kurz gesagt, die Methode erhält den Zeiger auf ein neu erstelltes Objekt. Wenn ein solches Objekt in der Liste vorhanden ist, weisen wir den Zeiger (auf das vorhandene Listenobjekt) dem Zeiger zu, der an die Methode übergeben wird. Die vorhandene Objekt-ID wird auf die Variable gesetzt, die in der Methode über den Link übergeben wird. Nach außen hin wird die Variable als Objekt-ID zugewiesen. Wenn es kein solches Objekt in der Liste gibt, fügen wir es hinzu und geben das Flag "Operation erfolgreich" zurück.
Die Methode, die den Index des grafischen Elements in der Kollektionsliste zurückgibt:
//+------------------------------------------------------------------+ //| Return the graphical elemnt index in the collection list | //+------------------------------------------------------------------+ int CGraphElementsCollection::GetIndexGraphElement(const long chart_id,const string name) { for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++) { CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i); if(obj==NULL) continue; if(obj.ChartID()==chart_id && obj.Name()==name) return i; } return WRONG_VALUE; } //+------------------------------------------------------------------+
Hier erhalten wir das nächste Objekt in der Schleife durch alle grafischen Elemente. Gibt den Schleifenindex zurück, wenn die Chart-ID und der Name mit den an die Methode übergebenen Werten übereinstimmen. Nach Beendigung der Schleife wird -1 zurückgegeben.
Hier sollte ich klarstellen, warum ich die Schnellsuche nicht mit der Bibliotheksklasse CSelect anwende. Tatsache ist, dass ich den Objektindex in der vollständigen Liste der gesamten Kollektion suche, während das Sortieren der Liste nach Eigenschaften neue Listen erzeugt und wir den Objektindex aus der sortierten Liste und nicht aus der Kollektionsliste erhalten. Natürlich werden ihre Indizes in den meisten Fällen nicht übereinstimmen.
Aus demselben Grund werde ich den Fehler in der Methode beheben, die ein Objekt findet, das in der Kollektion vorhanden ist, aber nicht in der Tabelle. Die Methode gibt auch den Zeiger auf das Objekt und den Objektindex in der Liste zurück:
//+------------------------------------------------------------------+ //|Find an object present in the collection but not on a chart | //| Return the pointer to the object and its index in the list. | //+------------------------------------------------------------------+ CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id,int &index) { index=WRONG_VALUE; for(int i=0;i<this.m_list_all_graph_obj.Total();i++) { CGStdGraphObj *obj=this.m_list_all_graph_obj.At(i); if(obj==NULL) continue; if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name())) { index=i; return obj; } } return NULL; } //+------------------------------------------------------------------+
Zuvor haben wir die Objektliste nach Chart-ID erhalten und eine Schleife über die resultierende Liste gezogen. Dies war falsch.
Jetzt implementiere ich die Schleife über die gesamte Kollektionsliste und erhalte dementsprechend den richtigen Objektindex in der Liste.
In dem EA aus dem vorherigen Artikel konnten nur Formularobjekte mit der Maus interagieren.
Grafische Elementobjekte sollten keine automatische Interaktion mit der Maus haben, aber alle vom Formularobjekt abgeleiteten Objekte sollten die Behandlung von Mausereignissen von ihm erben. Der Fehler lag in der Tatsache, dass ich den Typ des grafischen Elements im Event-Handler streng überprüft habe. Die Ereignisse wurden nicht behandelt, wenn es sich nicht um ein Formular handelte.
Jetzt werden wir diese Prüfung ändern. Handelt es sich bei dem grafischen Elementtyp um ein Formular oder ein anderes Element in der Vererbungshierarchie, dann sollten solche Objekte im Eventhandler verarbeitet werden.
Beheben wir das:
Wir nehmen in der Methode GetFormUnderCursor() die folgende Änderung vor:
//--- 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_FORM) { //--- 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) 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) continue; //--- if the obtained element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_FORM) { //--- 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) return form; } } //--- If there is no a single form object from the collection list //--- Get the list of extended standard graphical objects
Der Gleichheitsvergleich ("==") war vorher hier. Jetzt heißt es "größer als oder gleich". Da alle Werte der Enumerationskonstanten für grafische Elementtypen in aufsteigender Reihenfolge stehen, haben alle nachfolgenden Typen einen konstanten Wert, der größer als der Wert der Konstante GRAPH_ELEMENT_TYPE_FORM ist.
Außerdem sollten wir die Änderung in der Methode implementieren, die Interaktionsflags für alle Formulare außer dem angegebenen zurücksetzt:
//+--------------------------------------------------------------------+ //| Reset all interaction flags for all forms except the specified one | //+--------------------------------------------------------------------+ void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept) { //--- 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 pointer to the object CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i); //--- if failed to receive the pointer, or it is not a form or its descendants, or it is not a form whose pointer has been passed to the method, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID())) continue; //--- Reset the interaction flag for the current form in the loop obj.SetInteraction(false); } } //+------------------------------------------------------------------+
Früher gab es hier einen Vergleich für Ungleichheit ("!=") und alle Objekte außer Formularobjekten wurden übersprungen. Nun werden alle Objekte, die sich in der Vererbungshierarchie unterhalb des Formularobjekts befinden, übersprungen.
In der Methode SetZOrderMAX() ändern wir den auf dem grafischen Objekt angezeigten Testtext leicht ab (da die Texte auch im Test-EA geändert werden) und beheben den Fehler, der verhindert, dass Objekte mit der Maus interagieren können:
//+------------------------------------------------------------------+ //| Set ZOrde to the specified element | //| and adjust it in other elements | //+------------------------------------------------------------------+ bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj) { //--- ... //--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder CForm *form=obj; //--- and draw a text specifying ZOrder on the form form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Sort the list of graphical elements by an element ID this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID); //--- ... //--- ... //--- In the loop by the obtained list of remaining graphical element objects for(int i=0;i<list.Total();i++) { //--- get the next object CGCnvElement *elm=list.At(i); //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0) continue; //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value if(!elm.SetZorder(elm.Zorder()-1,false)) res &=false; //--- Temporarily (for the test purpose), if the element is a form, if(elm.Type()>=OBJECT_DE_TYPE_GFORM) { //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form form=elm; form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); } } //--- Upon the loop completion, return the result set in 'res' return res; } //+------------------------------------------------------------------+
Schreiben wir die Ereignisbehandlung der Deinitialisierung:
//+------------------------------------------------------------------+ //| Deinitialization event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnDeinit(void) { this.m_list_all_canv_elm_obj.Clear(); } //+------------------------------------------------------------------+
Hier ist alles ganz einfach. Wir löschen die Kollektionsliste der grafischen Elemente.
Dementsprechend sollte die Methode aus dem Hauptobjekt der CEngine-Bibliothek in \MQL5\Include\DoEasy\Engine.mqh aufgerufen werden.
Wir öffnen die Klassendatei und lassen ihre Methode OnDeinit() die Methode aus der Klasse der grafischen Elementkollektion aufrufen:
//+------------------------------------------------------------------+ //| Deinitialize library | //+------------------------------------------------------------------+ void CEngine::OnDeinit(void) { this.m_indicators.GetList().Clear(); this.m_graph_objects.OnDeinit(); } //+------------------------------------------------------------------+
Dies sind derzeit alle Verbesserungen in der Bibliothek. Testen wir die Ergebnisse.
Test
Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn unter \MQL5\Experts\TestDoEasy\Part102\ als TestDoEasyPart102.mq5.
Ich werde keine wesentlichen Änderungen vornehmen. Stattdessen ändere ich nur die Koordinaten der grafischen Objekte, sodass der Abstand zwischen ihnen etwas kürzer ist, und vergrößere das Feld. Um die Panelgröße zu erstellen, verwenden wir den folgenden Code im OnInit()-Handler:
//--- Create WinForms Panel object CPanel *pnl=NULL; obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+20,50,230,150,array_clr[0],200,true,true); list=engine.GetListCanvElementByID(ChartID(),obj_id); pnl=list.At(0); if(pnl!=NULL) { pnl.FontDrawStyle(FONT_STYLE_NORMAL); pnl.Bold(true); Print(DFUN,EnumToString(pnl.FontDrawStyle())); Print(DFUN,EnumToString(pnl.FontBoldType())); pnl.SetFontSize(10); pnl.TextOnBG(0,pnl.TypeElementDescription()+": ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',pnl.Opacity()); pnl.Update(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Wenn das Panel erfolgreich erstellt wurde, stellen Sie die Schriftart für die Texte auf Normal und die Schriftart "fett" ein und in das Log Journal wird die Beschreibung des Schriftstils und der Schriftbreite geschrieben.
Alle Texte, die auf allen grafischen Elementobjekten angezeigt werden, zeigen nun automatisch den Objekttyp, die ID und die ZOrder an, wenn auch in einem etwas anderen Format als im letzten Artikel.
Sie können alle Änderungen in den angehängten Dateien einsehen.
Kompilieren Sie den EA und starten Sie ihn auf dem Chart:
Wie wir sehen können, interagieren alle notwendigen Objekte mit der Maus, das Panel hat jetzt den Rahmen, während die Schrift darauf wie vorgesehen fett dargestellt wird. Beim Umschalten von Chart verschwinden keine Objekte mehr, aber sie werden auch nicht an ihrem neuen Ort gespeichert. Um dies zu beheben, müssen wir die Objektdaten in eine Datei schreiben und sie bei Bedarf lesen. Ich werde dies tun, sobald ich alle Objekte mit ihrer geplanten Vererbungshierarchie habe.
Was kommt als Nächstes?
Im nächsten Artikel werde ich die Entwicklung der Objektklasse des Panel WinForms fortsetzen.
*Vorherige Artikel in dieser Reihe:
DoEasy. Steuerung (Teil 1): Erste Schritte
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/10697





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