English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 2): Arbeiten an der Klasse CPanel

DoEasy. Steuerung (Teil 2): Arbeiten an der Klasse CPanel

MetaTrader 5Beispiele | 14 Juni 2022, 10:55
145 0
Artyom Trishkin
Artyom Trishkin

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:

  1. Objekt erfolgreich zur Liste hinzugefügt,
  2. Objekt bereits in der Kollektionsliste vorhanden ist,
  3. 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.

Alle Dateien der aktuellen Bibliotheksversion, des Test-EA und des Chartereignis-Kontrollindikators für MQL5 sind unten angehängt, damit Sie sie testen und herunterladen können. Hinterlassen Sie Ihre Fragen, Kommentare und Vorschläge in den Kommentaren.

Zurück zum Inhalt

*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

Beigefügte Dateien |
MQL5.zip (4384.12 KB)
Datenwissenschaft und maschinelles Lernen (Teil 04): Vorhersage des aktuellen Börsenkrachs Datenwissenschaft und maschinelles Lernen (Teil 04): Vorhersage des aktuellen Börsenkrachs
In diesem Artikel werde ich versuchen, unser logistisches Modell zu verwenden, um den Börsencrash auf der Grundlage der Fundamentaldaten der US-Wirtschaft vorherzusagen. NETFLIX und APPLE sind die Aktien, auf die wir uns konzentrieren werden, wobei wir die früheren Börsencrashs von 2019 und 2020 nutzen werden, um zu sehen, wie unser Modell in der aktuellen Krise abschneiden wird.
Erfahren Sie, wie Sie ein Handelssystem durch Accumulation/Distribution (AD) entwerfen Erfahren Sie, wie Sie ein Handelssystem durch Accumulation/Distribution (AD) entwerfen
Willkommen zu einem neuen Artikel aus unserer Serie über das Erlernen des Entwerfens von Handelssystemen auf der Grundlage der beliebtesten technischen Indikatoren. In diesem Artikel erfahren Sie mehr über einen neuen technischen Indikator, den Accumulation/Distribution Indikator, und darüber, wie Sie ein Handelssystem mit MQL5 entwerfen basierend auf einfachen AD-Handelsstrategien, um sie im MetaTrader 5 verwenden zu können.
Lernen Sie, wie man ein Handelssystem mit dem OBV entwickelt Lernen Sie, wie man ein Handelssystem mit dem OBV entwickelt
Der neue Artikel aus unserer Serie über die Gestaltung eines Handelssystems auf der Grundlage der beliebtesten technischen Indikatoren betrachtet einen neuen technischen Indikator - den Money Flow Index (Geldflussindikator, MFI). Wir werden ihn im Detail kennenlernen und ein einfaches Handelssystem mit Hilfe von MQL5 entwickeln, um es in MetaTrader 5 auszuführen.
Video: Wie man MetaTrader 5 und MQL5 für den einfachen automatisierten Handel einrichtet Video: Wie man MetaTrader 5 und MQL5 für den einfachen automatisierten Handel einrichtet
In diesem kleinen Videokurs lernen Sie, wie Sie den MetaTrader 5 für den automatisierten Handel herunterladen, installieren und einrichten. Sie erfahren auch, wie Sie die Chart-Einstellungen und die Optionen für den automatischen Handel anpassen können. Sie werden Ihren ersten Backtest durchführen und am Ende dieses Kurses werden Sie wissen, wie Sie einen Expert Advisor importieren, der automatisch 24/7 handeln kann, ohne dass Sie vor Ihrem Bildschirm sitzen müssen.