DoEasy. Steuerung (Teil 3): Erstellen gebundener Steuerelemente

Artyom Trishkin | 21 Juni, 2022

Inhalt


Konzept

Im aktuellen Artikel werde ich das Erstellen von grafischen Steuerelementen entwickeln, die an ein anderes grafisches Element gebunden sind. Angenommen, wir haben das Panel-Steuerelement. Das Element selbst ist nur ein Container, der andere Steuerelemente aufnehmen kann. Beim Verschieben des Panels werden alle daran gebundenen Control-Objekte mit verschoben. Das Panel ist ein Basisobjekt, um GUI-Elemente gruppiert zu platzieren. Da das Terminal keinen visuellen GUI-Konstruktor hat, wird die Konstruktion solcher Elemente einem Programmierer zugewiesen. Die Bibliothek ermöglicht eine einfache Entwicklung solcher GUI-Elemente, da wir nur die Erstellungsreihenfolge der erforderlichen grafischen Elemente angeben müssen, um sie auf dem Panel zu platzieren. Darüber hinaus besteht die Möglichkeit, Elemente programmgesteuert zu erstellen und dem Panel hinzuzufügen.

Im aktuellen Artikel werde ich die notwendigen Methoden weiter entwickeln, da ich bereits ein Werkstück der Methode zum Erstellen von Elementen in einem anderen Element habe. Die Methoden ermöglichen es uns, ein neues gebundenes grafisches Element direkt aus dem Panel zu erstellen und es als unabhängigen Teil des GUI-Programms zu behandeln. Jedes solche Element, das erstellt und an das Panel angehängt wird, kann seinerseits auch andere Elemente in sich selbst erstellen. Die kleinste Einheit mit einer solchen Funktionsweise soll ein Formularklassenobjekt sein.

Außerdem werde ich ein wenig am Schattenobjekt des grafischen Elements basteln, da es immer noch unter einigen Logikfehlern leidet, wenn es auf eines der Objekte angewendet wird, die einen Schatten haben können. Beispielsweise wird ein Schatten nur auf die Oberseite des Charts gezeichnet, während er ein Objekt überlagern sollte, über dem sich ein ihn entworfenes Objekt befindet.


Verbesserung des Bibliotheksklassen

\MQL5\Include\DoEasy\Defines.mqh bietet Makroersetzungen zum Festlegen von Standardwerten für einige Eigenschaften von Bibliotheksobjekten.

Ändern wir im Canvas-Parameterblock den Makro-Substitutionsnamen von CLR_FORE_COLOR in CLR_DEF_FORE_COLOR, fügen den Standardwert für die Intransparenz von grafischen Elementobjekten und einige andere Standardwerte für die Schattenobjekteigenschaftenhinzu:

//--- 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_DEF_FORE_COLOR             (C'0x2D,0x43,0x48')        // Default color for texts of objects on canvas
#define CLR_DEF_OPACITY                (200)                      // Default color non-transparency for canvas objects
#define CLR_DEF_SHADOW_COLOR           (C'0x6B,0x6B,0x6B')        // Default color for canvas object shadows
#define CLR_DEF_SHADOW_OPACITY         (127)                      // Default color non-transparency for canvas objects
#define DEF_SHADOW_BLUR                (4)                        // Default blur for canvas object shadows
#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
//--- Graphical object parameters

Diese Werte werden von der Bibliothek in den Methoden zum Erstellen grafischer Elemente verwendet. Einmal erstellt, können die Standardwerte jederzeit geändert werden.

Die Konstanten sind nicht ganz logisch in der Enumeration der Grafikelementtypen angeordnet:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_PANEL,                          // Windows Forms Panel
  };
//+------------------------------------------------------------------+

Die Schattenobjektkonstante befindet sich nach der Objektkonstante des grafischen Elements. Dies ist im Hinblick auf die Einschränkung von Objekttypen in einigen Methoden nicht sehr praktisch. Wenn wir beispielsweise alle GUI-Elementobjekte behandeln müssen, können wir festlegen, dass nur Objekte des Elementtyps oder höher behandelt werden. Auch das Schattenobjekt wird in diesem Fall von der Methode behandelt. Um dies zu vermeiden und die Möglichkeit zu erhalten, Objektgruppen nach dem Wert der Aufzählungskonstante auszuwählen, tauschen wir die angegebenen Konstanten so aus, dass GUI-Elementobjekte gemäß ihrer Vererbungshierarchie angeordnet werden:

//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Standard graphical object
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Extended standard graphical object
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Shadow object
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Element
   GRAPH_ELEMENT_TYPE_FORM,                           // Form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
   //--- WinForms
   GRAPH_ELEMENT_TYPE_PANEL,                          // Windows Forms Panel
  };
//+------------------------------------------------------------------+

Jetzt können wir schnell die notwendigen Objekte auswählen, die in den Bibliotheksmethoden behandelt werden sollen.


In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes hinzugefügt:

//--- CForm
   MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// No shadow object. Create it using the CreateShadowObj() method
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ,      // Failed to create new shadow object
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ,          // Failed to create new pixel copier object
   MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST,            // Pixel copier object with ID already present in the list 
   MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST,             // No pixel copier object with ID in the list 
   MSG_FORM_OBJECT_ERR_NOT_INTENDED,                  // The method is not meant for creating such an object: 

und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:

//--- CForm
   {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"},
   {"Не удалось создать новый объект для тени","Failed to create new object for shadow"},
   {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"},
   {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "},
   {"В списке нет объекта-копировщика пикселей с идентификатором ","No pixel copier object with ID "},
   {"Метод не предназначен для создания такого объекта: ","The method is not intended to create such an object: "},


Alle GUI-Elemente werden aus verschiedenen Bibliotheksobjekten erstellt. Das kleinste ist ein grafisches Elementobjekt. Eine neue Funktionsweise, die in übergeordneten Objekten nicht vorhanden ist, wird jedem nachfolgenden Objekt in ihrer Vererbungshierarchie hinzugefügt. Daher reicht die Funktionsweise einiger Methoden des übergeordneten Objekts nicht aus, um sie vollständig in den untergeordneten Objekten zu implementieren. Wir müssen solche Methoden virtuell machen und die erforderliche Funktionalität in den untergeordneten Objekten hinzufügen.

Jedes Objekt, aus dem die GUI-Elemente bestehen, sollte das Element, an das es angehängt ist, "kennen", damit es beispielsweise die Eigenschaften des Basisobjekts für seine Positionierung verwenden kann. Dazu führen wir einen Zeiger auf das Basisobjekt in der Elementobjektklasse ein (dieses Objekt ist das übergeordnete Objekt für alle GUI-Elemente, daher ist es logisch, den Zeiger darin zu platzieren).

In diesem Stadium sind alle Objekte, aus denen die grafische Nutzeroberfläche besteht, an die Koordinaten des Chartbildschirms gebunden — ihre Koordinaten werden von der oberen linken Ecke des Charts aus gezählt. Aber wenn wir ein Objekt innerhalb eines anderen positionieren müssen, dann ist es logisch, sein Koordinatensystem zu seinem Basisobjekt zu verschieben, wo der Ursprung die obere linke Ecke des grafischen Basiselements und nicht des Charts ist. Dazu führen wir das Konzept der relativen Koordinaten ein und fügen neue Methoden hinzu, die die Koordinaten eines Objekts relativ zum Basisobjekt zurückgeben, dem, an das es angehängt ist. Um das Objekt zu positionieren, können wir dann einfach eine Verschiebung relativ zur oberen linken Ecke des Basisobjekts angeben, anstatt ständig eine neue Positionierungskoordinate zu berechnen, wenn das grafische Basiselement entlang des Charts verschoben wird.

Deklarieren wir im geschützten Abschnitt von \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh den Zeiger auf das Basisobjekt, an das das grafische Element angehängt ist, während wir im privaten Abschnitt die Variablen zum Speichern der relativen Koordinatenverschiebung deklarieren das Basisobjekt:

//+------------------------------------------------------------------+
//| Class of the graphical element object                            |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CGCnvElement     *m_element_base;                           // Pointer to the parent element
   CCanvas           m_canvas;                                 // CCanvas class object
   CPause            m_pause;                                  // Pause class object
   bool              m_shadow;                                 // Shadow presence
   color             m_chart_color_bg;                         // Chart background color
   uint              m_duplicate_res[];                        // Array for storing resource data copy

//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
private:
   int               m_shift_coord_x;                          // Offset of the X coordinate relative to the base object
   int               m_shift_coord_y;                          // Offset of the Y coordinate relative to the base object
   struct SData

Im öffentlichen Abschnitt geben wir die Methoden zum Setzen und Zurückgeben des Zeigers auf das Basisobjekt an:

//--- Create the element
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const color colour,
                            const uchar opacity,
                            const bool redraw=false);

//--- (1) Set and (2) return the pointer to the parent control
   void              SetBase(CGCnvElement *element)                                    { this.m_element_base=element;               }
   CGCnvElement     *GetBase(void)                                                     { return this.m_element_base;                }
//--- Return the pointer to a canvas object
   CCanvas          *GetCanvasObj(void)                                                { return &this.m_canvas;                     }

Die Methode Move() wird virtuell gemacht:

//--- Return the size of the graphical resource copy array
   uint              DuplicateResArraySize(void)                                       { return ::ArraySize(this.m_duplicate_res);  }
   
//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);

//--- Save an image to the array
   bool              ImageCopy(const string source,uint &array[]);

Da das Grafikelementobjekt an ein anderes Objekt angehängt werden kann, während es selbst keine angehängten Objekte haben kann, erfordert seine von der Move()-Methode gehandhabte Verschiebung keine Modifikationen. Gleichzeitig können an seine untergeordneten Objekte bereits andere grafische Elemente angehängt sein, während die Zeiger auf diese Elemente in der Liste platziert sind, was bedeutet, dass seine Methode Move() auch die gesamte Liste der daran gebundenen Objekte verarbeiten sollte. wobei für jedes Objekt eine eigene Move()-Methode aufgerufen wird. Aus diesem Grund ist das Verfahren virtuell und wird für jedes der geerbten Objekte separat implementiert.

Im öffentlichen Abschnitt geben wir die Methoden zum Festlegen und Zurückgeben relativer Objektkoordinaten an:

protected:
//--- Protected constructor
                     CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const long    chart_id,
                                  const int     wnd_num,
                                  const string  name,
                                  const int     x,
                                  const int     y,
                                  const int     w,
                                  const int     h);
public:
//--- (1) Set and (2) return the X coordinate shift relative to the base object
   void              SetCoordXRelative(const int value)                                { this.m_shift_coord_x=value;                }
   int               CoordXRelative(void)                                        const { return this.m_shift_coord_x;               }
//--- (1) Set and (2) return the Y coordinate shift relative to the base object
   void              SetCoordYRelative(const int value)                                { this.m_shift_coord_y=value;                }
   int               CoordYRelative(void)                                        const { return this.m_shift_coord_y;               }
   
//--- Event handler


Legen wir im Standardkonstruktor die Initialisierung des Zeigers auf das Basisobjekt und die Verschiebung der relativen Objektkoordinaten fest:

//--- Default constructor/Destructor
                     CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND))
                        { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_base=NULL; this.m_shift_coord_x=0; this.m_shift_coord_y=0; }
                    ~CGCnvElement()
                        { this.m_canvas.Destroy();             }

Wenn der Zeiger auf das Basisobjekt NULL ist, bedeutet dies, dass das Objekt keinem anderen grafischen Element zugeordnet ist.

Im Methodenblock für einen vereinfachten Zugriff auf die Objekteigenschaften legen wir die Methoden zur Rückgabe der relativen Koordinaten der rechten und unteren Objektkante fest:

//--- Return (1) the background color, (2) the opacity, coordinate (3) of the right and (4) bottom element edge
   color             ColorBackground(void)               const { return this.m_color_bg;                                               }
   uchar             Opacity(void)                       const { return this.m_opacity;                                                }
   int               RightEdge(void)                     const { return this.CoordX()+this.m_canvas.Width();                           }
   int               BottomEdge(void)                    const { return this.CoordY()+this.m_canvas.Height();                          }
//--- Return the relative coordinate of the (1) right and (2) bottom element edge
   int               RightEdgeRelative(void)             const { return this.CoordXRelative()+this.m_canvas.Width();                   }
   int               BottomEdgeRelative(void)            const { return this.CoordYRelative()+this.m_canvas.Height();                  }
//--- Return the (1) X, (2) Y coordinates, (3) element width and (4) height,

Die Methode BringToTop() wird virtuell gemacht:

//--- Set the object above all
   virtual void      BringToTop(void)                          { CGBaseObj::SetVisible(false,false); CGBaseObj::SetVisible(true,false);}
//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true,false);                                    }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false,false);                                   }

Der Grund, warum die Methode auch virtuell gemacht wird, ist derselbe wie oben erwähnt — alle grafischen Elemente, die an das Objekt gebunden sind, sollten auch behandelt werden, wenn vor allem das Basisobjekt gesetzt wird. Andernfalls werden alle Elemente, die an das im Vordergrund angezeigte Objekt gebunden sind, einfach visuell daraus verschwinden. Sie sollten auch in der Reihenfolge, in der sie daran gebunden wurden, über das Basisobjekt verschoben werden. Dies wird in den virtuellen Methode BringToTop() der abgeleiteten Objekten gehandhabt, die Listen von angehängten Objekten haben.

Initialisieren wir in den parametrischen und geschützten Konstruktoren den Zeiger auf das Basisobjekt und die relative Koordinatenverschiebung:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   name,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_color_bg=colour;
   this.m_opacity=opacity;
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {

Die Initialisierung des Zeigers und der Variablen im geschützten Konstruktor wird auf die gleiche Weise implementiert.


Ein Formularobjekt ist die kleinste Einheit, die ein Schattenobjekt aufweisen kann. Wir haben es schon einmal verwendet, allerdings für stationäre Objekte. Weisen wir einem beliebigen beweglichen Interface-Objekt die Verwendung eines Schattens zu, dann macht sich beim Verschieben eines grafischen Objekts sofort ein Nachteil bemerkbar — der Schatten bleibt bestehen. Daher müssen wir erstens Variablen hinzufügen, um die Schattenverschiebung relativ zu dem Objekt zu speichern, das sie wirft, und zweitens, damit der Schatten dem bewegten Objekt folgt. Da sich das Schattenobjekt jedoch unter dem grafischen Element befindet, müssen wir zuerst das Schattenobjekt verschieben, dann das grafische Element verschieben und schließlich das Chart aktualisieren. In diesem Fall wird die Objektverschiebung sichtbar. Der von ihm geworfene Schatten folgt dem Objekt.

Wenn wir auf das Objekt klicken, wird es außerdem in den Vordergrund gebracht und sein Schatten sollte auch höher werden als alle anderen Objekte auf dem Chart, sodass es über anderen grafischen Elementen gezeichnet wird, die sich auf dem Chart unter dem verschobenen Objekt befinden.

Im privaten Abschnitt von \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh deklarieren wir die Variablen zum Speichern von Schattenverschiebungen relativ zu dem grafischen Objekt, das diesen Schatten wirft:

//+------------------------------------------------------------------+
//| Shadows object class                                             |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color_shadow;                  // Shadow color
   uchar             m_opacity_shadow;                // Shadow opacity
   int               m_offset_x;                      // Shadow X axis shift
   int               m_offset_y;                      // Shadow Y axis shift

Schreiben wir im öffentlichen Abschnitt die Methoden, die die Schattenverschiebungswerte relativ zum Objekt zurückgeben :

//--- Supported object properties (1) integer and (2) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Return the shadow shift by (1) X and (2) Y
   int               OffsetX(void)                                      const { return this.m_offset_x;        }
   int               OffsetY(void)                                      const { return this.m_offset_y;        }

//--- Draw an object shadow

Im Klassenkonstruktor werden die Variablenwerte initialisiert:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetColorBackground(clrNONE);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.m_opacity_shadow=127;
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   this.m_color_shadow=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.m_visible=true;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+


Fügen wir beim Zeichnen des Schattenobjekts die in den Methodenargumenten übergebenen Verschiebungswerte zu den neuen Variablen hinzu:

//+------------------------------------------------------------------+
//| Draw the object shadow                                           |
//+------------------------------------------------------------------+
void CShadowObj::DrawShadow(const int shift_x,const int shift_y,const uchar blur_value)
  {
//--- Set the shadow shift values to the variables by X and Y axes
   this.m_offset_x=shift_x;
   this.m_offset_y=shift_y;
//--- Calculate the height and width of the drawn rectangle
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Draw a filled rectangle with calculated dimensions
   this.DrawShadowFigureRect(w,h);
//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant
   int radius=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal)
   if(!this.GaussianBlur(radius))
      return;
//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas
   CGCnvElement::Move(this.CoordX()+this.m_offset_x,this.CoordY()+this.m_offset_y);
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

Nachdem der Schatten gezeichnet wurde, verschieben wir ihn um die angegebenen Werte. Das Verfahren bleibt größtenteils unverändert, mit Ausnahme des Speicherns der X- und Y-Verschiebungswerte in den neuen Variablen. Zuvor wurde das Schattenobjekt auf die in den Methodenargumenten übergebenen Werte verschoben, während es jetzt auf die in den neuen Variablen festgelegten Werte verschoben wird, was dasselbe ist. Aber jetzt haben wir den Wert der Verschiebungen, die beim Erstellen und Zeichnen des Schattenobjekts angegeben wurden. Wir werden sie beim Verschieben des Schattenobjekts benötigen.


Verschieben wir in der Formularobjektdatei \MQL5\Include\DoEasy\Objects\Graph\Form.mqhdie Methoden CreateNameDependentObject() und CreateShadowObj() aus dem privaten Abschnitt der Klasse in den geschützten:

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);
//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
//--- 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;   }
   
public:

Diese Methoden sollten in den untergeordneten Klassenobjekten verfügbar sein, daher ist ihr Platz im geschützten Abschnitt der Klasse.

Die private Methode zum Erstellen eines neuen grafischen Objekts wird virtuell gemacht, da sie eine gewisse Verfeinerung in den untergeordneten Objekten erfordert. Deklaration der virtuelle Methode, die die Koordinaten eines gebundenen Objekts zurückgibt , wenn Sie es erstellen:

//--- Create a new graphical object
   virtual 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);
//--- Return the initial coordinates of a bound object
   virtual void      GetCoords(int &x,int &y);

Die Methode, die ein neues gebundenes Objekt erstellt, befindet sich innerhalb der Formularobjektklasse. Das Formularobjekt wird von der Objektklasse des grafischen Elements geerbt. Dementsprechend sind diese beiden Klassen (Element und Formular) im Formularklassenobjekt bekannt und sichtbar und können hier erstellt werden. Aber wir müssen in der Lage sein, andere Steuerelemente zu erstellen — ein Fenster, ein Bedienfeld und andere Klassenobjekte, die wir im Prozess der Entwicklung von WinForms-Objekten erstellen werden. Sie sind jedoch in dieser Klasse nicht sichtbar. Dementsprechend verfügt jede von ihnen über die virtuelle Methode CreateNewGObject(), die den Code zum Erstellen von Steuerelementen enthält, die in den Klassen sichtbar sind.

Im öffentlichen Abschnitt der Klasse deklarieren wir die virtuellen Methoden, die wir in der Objektklasse des grafischen Elements virtuell gemacht haben:

public:
//--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
   int               MouseCursorX(void)            const { return this.m_mouse.CoordX();     }
   int               MouseCursorY(void)            const { return this.m_mouse.CoordY();     }
//--- Set the flags of mouse scrolling, context menu and the crosshairs tool for the chart
   void              SetChartTools(const bool flag);
//--- (1) Set and (2) return the shift of X and Y coordinates relative to the cursor
   void              SetOffsetX(const int value)         { this.m_offset_x=value;            }
   void              SetOffsetY(const int value)         { this.m_offset_y=value;            }
   int               OffsetX(void)                 const { return this.m_offset_x;           }
   int               OffsetY(void)                 const { return this.m_offset_y;           }

//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);
//--- Set the priority of a graphical object for receiving the event of clicking on a chart
   virtual bool      SetZorder(const long value,const bool only_prop);
//--- Set the object above all
   virtual void      BringToTop(void);
   
//--- Event handler

Die Implementierung der Methoden mit den erforderlichen Handlern für die Formularobjektklasse (Handhabung der Verschiebung anderer an das Objekt gebundener Steuerelemente) wird unten hinzugefügt.

Die Methode, die das Schattenobjekt zurückgibt, hat zuvor aufgrund eines Fehlers den Objekttyp des grafischen Elements CGCnvElement zurückgegeben. Es ist jedoch nicht kritisch, außer dass die in der Schattenobjektklasse angegebenen Methoden nicht verfügbar bleiben.
Also reparieren wir es, sodass die Methode den Schattenobjekttyp zurückgibt, und fügen zwei Methoden hinzu, um die Anzahl der gebundenen Steuerelemente und das Steuerelement nach seinem Index in der Liste der gebundenen Objekte zurückzugeben:

//--- Return (1) the list of attached objects and (2) the shadow object
   CForm            *GetObject(void)                                          { return &this;                  }
   CArrayObj        *GetListElements(void)                                    { return &this.m_list_elements;  }
   CShadowObj       *GetShadowObj(void)                                       { return this.m_shadow_obj;      }
//--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames
   CAnimations      *GetAnimationsObj(void)                                   { return this.m_animations;      }
   CArrayObj        *GetListFramesText(void)
                       { return(this.m_animations!=NULL ? this.m_animations.GetListFramesText() : NULL);       }
   CArrayObj        *GetListFramesQuad(void)
                       { return(this.m_animations!=NULL ? this.m_animations.GetListFramesQuad() : NULL);       }

//--- Return the (1) number of bound elements and (2) the bound element by the index in the list
   int               ElementsTotal(void)                       const { return this.m_list_elements.Total();    }
   CGCnvElement     *GetElement(const int index)                     { return this.m_list_elements.At(index);  }
   
//--- Set the form (1) color scheme and (2) style


Ändern wir die Methodenargumente in der Methode, die ein neues gebundenes Element erstellt. Der Index und das Flag der Verschiebbarkeit eines erstellten Elements wurden bisher neben anderen Eigenschaften übergeben. Da der Index automatisch ausgewählt wird, während die Verschiebbarkeit für gebundene Elemente nicht benötigt wird (da die Objekte alle Verschiebungen des Basisobjekts erben, an das sie angehängt sind), übergeben wir den Typ eines erstellten Objekts anstelle des Index, während das Flag der Verschiebbarkeit einfach aus den Methodenargumenten entfernt wird. Nun sieht die Methodendeklaration wie folgt aus:

//--- Create a new attached element
   bool              CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity);
//--- Add a new attached element


Fügen wir in der Methode zum Zeichnen des Objektschattens anstelle des zuvor übergebenen Werts 4 den Standardwert für die Schattenunschärfe hinzu:

//--- Add a new attached element
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

//--- Draw an object shadow
   void              DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR);

//--- Draw the form frame


Bisher hat die Methode zum Erstellen eines neuen Grafikobjekts nur ein Objekt des Grafikelementtyps CGCnvElement erstellt.
Jetzt wird die Methode so verbessert, dass das erstellte Objekt denselben Typ hat, der in den Methodenargumenten übergeben wurde:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int obj_num,
                                      const string obj_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)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   //--- Depending on the created object type,
   switch(type)
     {
      //--- create a graphical element object
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      //--- create a form object
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+


Die Methode zum Erstellen eines neuen gebundenen Elements wurde erheblich geändert:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity)
  {
//--- If the type of a created graphical element is less than the "element", inform of that and return 'false'
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return false;
     }
//--- Specify the element index in the list
   int num=this.m_list_elements.Total()+1;
//--- Create a graphical element name
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;
//--- Get the initial coordinates of the object relative to the coordinate system of the base object
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return false;
//--- and add it to the list of bound graphical elements
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return false;
     }
//--- Set the minimum properties for a bound graphical element
   obj.SetBase(this.GetObject());
   obj.SetCoordXRelative(x);
   obj.SetCoordYRelative(y);
   obj.SetZorder(this.Zorder(),false);
//--- Draw an added object and return 'true'
   obj.Erase(colour,opacity,true);
   return true;
  }
//+------------------------------------------------------------------+

Die gesamte Logik ist in den Codekommentaren beschrieben. Beim Erstellen eines grafischen Objektnamens verwenden wir den Basisobjektnamen + "_Elm"-Zeichenfolge + Objektnummer in der Liste. Ich werde die folgende Logik für die Objektnummer verwenden: Wenn die Objektnummer im Intervall von 1 bis einschließlich 9 liegt, wird eine Null hinzugefügt: 01, 02, 03,.., .., 08, 09. Für 10 und darüber hinaus füge ich nichts hinzu. Die Methode erstellt nur den Objektnamen, während der Name des Basisobjekts in der Methode CreateNewGObject() hinzugefügt wird, nämlich in seinem ersten String, wenn die Methode CreateNameDependentObject() aufgerufen wird.

Die Methode zum Hinzufügen eines neuen angehängten Elements zur Liste wurde ebenfalls leicht geändert. Im Moment wendet die Methode die Schleife an, die zum Suchen eines Objekts in der Liste gedacht ist, das denselben Namen wie das hinzugefügte hat:

//+------------------------------------------------------------------+
//| Add a new attached element                                       |
//+------------------------------------------------------------------+
bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y)
  {
   if(obj==NULL)
      return false;
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      CGCnvElement *elm=this.m_list_elements.At(i);
      if(elm==NULL)
         continue;
      if(elm.Name()==obj.Name())
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj());
         return false;
        }
     }
   if(!this.m_list_elements.Add(obj))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Die vorherige Suche

   this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ);
   int index=this.m_list_elements.Search(obj);
   if(index>WRONG_VALUE)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj());
      return false;
     }

funktioniert aus einem mir noch unklaren Grund nicht. Es gibt immer das Vorhandensein eines Objekts mit dem Namen zurück, der an die Methode in der Liste der gebundenen Objekte übergeben wurde. Dieses Verhalten ist falsch. Wir werden uns später damit befassen. Die Suche wurde durch die Schleife ersetzt.

Die Methode, die die Anfangskoordinaten eines gebundenen Objekts zurückgibt:

//+------------------------------------------------------------------+
//| Return the initial coordinates of a bound object                 |
//+------------------------------------------------------------------+
void CForm::GetCoords(int &x,int &y)
  {
   x=this.CoordX()+this.m_frame_width_left+x;
   y=this.CoordY()+this.m_frame_width_top+y;
  }
//+------------------------------------------------------------------+

Die Methode gibt die Werte ihrer relativen Koordinaten, die vom Link übergeben werden, an die Werte zurück, die für die Breite des Rahmens links für die X-Koordinate und oben für die Y-Koordinate angepasst sind, einfach weil, wenn ein Objekt einen Rahmen hat, dann der hinzugefügte Gegenstände sollten sich nicht darauf befinden. Wird also eine Koordinate übergeben, zB 0, so wird diese um die Breite des Rahmens verschoben. Mit anderen Worten, wenn der Rahmen eine Breite von 3 hat, werden alle Koordinaten um diesen Betrag verschoben.

Die virtuelle Methode zum Aktualisieren der Elementkoordinaten:

//+------------------------------------------------------------------+
//| Update the coordinate elements                                   |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
   CGCnvElement *base=this.GetBase();
   bool res=true;
//--- If the element is not movable and is a base object, leave
   if(!this.Movable() && base==NULL)
      return false;
//--- If the object has a shadow and we failed to set new coordinate values to the properties of the shadow object, return 'false'
   if(this.m_shadow)
     {
      if(this.m_shadow_obj==NULL || !this.m_shadow_obj.Move(x-OUTER_AREA_SIZE+this.m_shadow_obj.OffsetX(),y-OUTER_AREA_SIZE+this.m_shadow_obj.OffsetY(),false))
         return false;
     }
//--- If failed to set new values into graphical object properties, return 'false'
   if(!this.SetCoordX(x) || !this.SetCoordY(y))
      return false;
//--- In the loop by all bound objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next object and shift it
      CGCnvElement *obj=m_list_elements.At(i);
      if(obj==NULL)
         continue;
      if(!obj.Move(x+this.m_frame_width_left+obj.CoordXRelative(),y+this.m_frame_width_top+obj.CoordYRelative(),false))
         return false;
     }
   //--- If the update flag is set and this is a base object, redraw the chart.
   if(redraw && base==NULL)
      ::ChartRedraw(this.ChartID());
   //--- Return 'true'
   return true;
  }
//+------------------------------------------------------------------+

Die Methode wird der übergeordneten Objektklasse CGCnvElement entnommen und die Schleife durch die Liste der gebundenen Objekte hinzugefügt. Dieselbe Methode wird auch für jedes der Objekte aufgerufen. Somit werden alle Objekte in Kettenreihenfolge hinter ihr Basisobjekt verschoben.
Um zu vermeiden, dass das Chart für jedes Objekt aktualisiert wird, in dem die Methode aufgerufen wird, habe ich die Prüfung für das Basisobjekt hinzugefügt (da es an kein anderes Basisobjekt gebunden ist, ist sein Zeiger auf das Basisobjekt NULL). Das bedeutet, dass das Chart einmal aktualisiert wird — nur nach Abschluss der Schleife.

Die Methode, die die Priorität eines grafischen Objekts für den Empfang des Ereignisses beim Klicken auf ein Chart festlegt:

//+------------------------------------------------------------------+
//| Set the priority of a graphical object                           |
//| for receiving the event of clicking on a chart                   |
//+------------------------------------------------------------------+
bool CForm::SetZorder(const long value,const bool only_prop)
  {
   if(!CGCnvElement::SetZorder(value,only_prop))
      return false;
   if(this.m_shadow)
     {
      if(this.m_shadow_obj==NULL || !this.m_shadow_obj.SetZorder(value,only_prop))
         return false;
     }
   int total=this.m_list_elements.Total();
   for(int i=0;i<total;i++)
     {
      CGCnvElement *obj=this.m_list_elements.At(i);
      if(obj==NULL)
         continue;
      if(!obj.SetZorder(value,only_prop))
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Wie in der oben betrachteten Methode habe ich hier die Schleife hinzugefügt, indem für alle angehängten Objekte die gleiche Methode aufgerufen wird.

Die Methode, die das Objekt über alle anderen stellt:

//+------------------------------------------------------------------+
//| Set the object above all the rest                                |
//+------------------------------------------------------------------+
void CForm::BringToTop(void)
  {
//--- If the shadow usage flag is set
   if(this.m_shadow)
     {
      //--- If the shadow object is created, move it to the foreground
      if(this.m_shadow_obj!=NULL)
         this.m_shadow_obj.BringToTop();
     }
//--- Move the object to the foreground (the object is located above the shadow)
   CGCnvElement::BringToTop();
//--- In the loop by all bound objects,
   int total=this.m_list_elements.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next object from the list
      CGCnvElement *obj=this.m_list_elements.At(i);
      if(obj==NULL)
         continue;
      //--- and move it to the foreground
      obj.BringToTop();
     }
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben. Wenn ein Objekt einen Schatten hat, sollte dieser Schatten in den Vordergrund, vor allen Chartobjekten, befindet. Als Nächstes wird das Objekt selbst in den Vordergrund gerückt, sodass es sich über dem Schatten befindet. Verschieben wir sie als Nächstes in der Schleife von allen gebundenen Objekten über das Objekt, an das sie gebunden sind.


Verbessern wirr die Panel-Objektklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.

Im privaten Abschnitt der Klasse deklarieren wir zwei virtuelle Methoden , die in der übergeordneten Klasse implementiert wurden:

//+------------------------------------------------------------------+
//| 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);
//--- Create a new graphical object
   virtual 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);
//--- Return the initial coordinates of a bound object
   virtual void      GetCoords(int &x,int &y);

public:

In der Methode zum Erstellen eines neuen grafischen Objekts in der Klasse können wir die Erstellung des CPanel-Objekts hinzufügen, da es in der Klasse desselben Typs sichtbar ist. Die Methode GetCoords() hat vorübergehend die gleiche Implementierung wie die übergeordnete Klasse. Höchstwahrscheinlich wird das geändert, wenn die Klasse weiter verbessert wird.

Verfeinern wir die Methodeneinstellung Padding -Werte für jede Platz des Panels:

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   void              PaddingLeft(const uint value)
                       {
                        this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value);
                       }
   void              PaddingTop(const uint value)
                       {
                        this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value);
                       }
   void              PaddingRight(const uint value)
                       {
                        this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                       }
   void              PaddingBottom(const uint value)
                       {
                        this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                       }
   void              PaddingAll(const uint value)
                       {
                        this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value);
                       }
//--- 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

Wenn nun der Padding-Wert kleiner als die Rahmenbreite des Panels ist, entspricht der Wert der Breite der entsprechenden Seite des Rahmens.


Fügen wir im Standardkonstruktor die Einstellung des grafischen Objekttyps als "Panel" zu den Objekteigenschaften hinzu:

                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.m_fore_color=CLR_DEF_FORE_COLOR;
                        this.m_bold_type=FW_TYPE_NORMAL;
                        this.MarginAll(3);
                        this.PaddingAll(0);
                        this.Initialize();
                       }
//--- Destructor

Dieselbe Zeichenfolge wird allen verbleibenden Klassenkonstruktoren hinzugefügt. Ohne die Zeichenfolge wird der vom übergeordneten Klassenkonstruktor registrierte Objekttyp auf "Formular" gesetzt. Daher habe ich in diesen Konstruktoren die erzwungene Einstellung der Eigenschaft als "Panel" hinzugefügt.
Das ist eigentlich ein Fehler in der Klassenlogik. Mal sehen wie das später behoben werden kann...

Die Namen der zuvor umbenannten Makroersetzungen CLR_FORE_COLOR wurden in allen Klassenkonstruktoren durch CLR_DEF_FORE_COLOR ersetzt.

Die Methode zum Erstellen eines neuen grafischen Objekts:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string obj_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)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_PANEL :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Das Verfahren wiederholt die oben betrachtete Logik des gleichnamigen Elternobjektverfahrens. Hier habe ich jedoch die Erstellung eines weiteren grafischen Steuerobjekts "Panel" hinzugefügt, da ein solcher Objekttyp in der Klasse bekannt ist, da es sich um eine Klasse desselben Typs handelt.
In anderen Klassen, die später hinzugefügt werden sollen, wird das virtuelle Verfahren die Zeichenketten enthalten, die graphische Elemente erzeugen, die ihren Typen entsprechen.

Die Methode, die die Anfangskoordinaten eines gebundenen Objekts zurückgibt:

//+------------------------------------------------------------------+
//| Return the initial coordinates of a bound object                 |
//+------------------------------------------------------------------+
void CPanel::GetCoords(int &x,int &y)
  {
   x=this.CoordX()+this.FrameWidthLeft()+x;
   y=this.CoordY()+this.FrameWidthTop()+y;
  }
//+------------------------------------------------------------------+

In der Methode ist die Logik dieselbe wie in der gleichnamigen Methode des übergeordneten Objekts. Der Unterschied besteht darin, dass wir hier die öffentlichen Methoden der übergeordneten Klasse verwenden, um auf den Rahmenbreitenwert zuzugreifen, und nicht die Werte der Variablen, die im privaten Abschnitt verborgen und in den untergeordneten Klassen nicht verfügbar sind.


Fügen wir in der Kollektionsklasse der grafischen Elemente \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh die Methoden für den schnellen Zugriff auf grafische Steuerelemente hinzu:

//--- Return the list of graphical elements by element type
   CArrayObj        *GetListCanvElementByType(const ENUM_GRAPH_ELEMENT_TYPE type)
                       {
                        return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_TYPE,type,EQUAL);
                       }

//--- ...
                
//--- 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);
                       }
//--- Return the graphical element by chart and object IDs
   CGCnvElement     *GetCanvElement(const long chart_id,const int element_id)
                       {
                        CArrayObj *list=this.GetListCanvElementByID(chart_id,element_id);
                        return(list!=NULL ? list.At(0) : NULL);
                       }

//--- Constructor
                     CGraphElementsCollection();

Ich habe schon oft über die Logik hinter solchen Methoden nachgedacht. Es ist eine übliche Sortierung nach angegebener Eigenschaft.

In der Methode zum Erstellen eines grafischen Objekts WinForms Panel-Objekt auf der Leinwand in einem bestimmten Chart und Unterfenster fügen wir das Erstellen und Zeichnen eines Schattenobjekts hinzu
falls das Vorhandensein eines Schattens in den Methodenargumenten angegeben ist:

//--- 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);
                        //--- Draw the shadow drawing flag
                        obj.SetShadow(shadow);
                        if(shadow)
                          {
                           //--- Calculate the shadow color as the chart background color converted to the monochrome one
                           //--- and darken the monochrome color by 20 units
                           color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20);
                           //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
                           //--- Set the shadow opacity to the default value, while the blur radius is equal to 4
                           obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
                          }
                        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ügen wir die Methoden zum Erstellen von Panel-Objekten mit unterschiedlichen Füllungstypen (vertikaler und horizontaler Farbverlauf und zyklischer vertikaler und horizontaler Farbverlauf) hinzu. Hier werde ich eine dieser Methoden betrachten, da sich die verbleibenden nur in der Kombination der Flags unterscheiden, die an die Methode Erase() der Klasse CGCnvElement übergeben werden:

//--- Create a WinForms Panel object graphical object on canvas on a specified chart and subwindow with the vertical gradient filling
   int               CreatePanelVGradient(const long chart_id,
                                          const int subwindow,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          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[0]);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        //--- Draw the shadow drawing flag
                        obj.SetShadow(shadow);
                        if(shadow)
                          {
                           //--- Calculate the shadow color as the chart background color converted to the monochrome one
                           //--- and darken the monochrome color by 20 units
                           color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20);
                           //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
                           //--- Set the shadow opacity to the default value, while the blur radius is equal to 4
                           obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
                          }
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Erase(clr,opacity,true,false,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();
                       }
//--- ...

  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+

Die Methoden sind fast identisch, abgesehen von den Flags, die an die Erase()-Methoden übergeben werden. Im Gegensatz zur allerersten Methode erhalten diese Methoden das Array von Farben, die für die Farbverlaufsfüllung verwendet werden, und nicht die Variable, die die Füllfarbe des Felds angibt.

Die Methoden Erase() der Elternklasse CGCnvElement, die verwendet werden, um über einen rechteckigen Bereich eines Objekts zu malen, ermöglichen es uns, entweder mit einer einzigen Farbe zu malen oder verschiedene Verlaufsfüllmethoden zu verwenden. Ich habe sie bereits beim Erstellen einer Objektklasse für grafische Elemente berücksichtigt.

Sie können sich mit all diesen Methoden in den Dateien vertraut machen, die dem Artikel beigefügt sind.

Um Steuerelemente aus nutzerdefinierten Programmen einfach erstellen und handhaben zu können, legen wir in der Hauptklasse der CEngine-Bibliothek, nämlich im öffentlichen Abschnitt von \MQL5\Include\DoEasy\Engine.mqh, die Methoden für den schnellen Zugriff auf diese Objekte fest und fügen die Variable zum Speichern des Objektnamenpräfixes im privaten Abschnitt der Klasse hinzu:

   ENUM_PROGRAM_TYPE    m_program_type;                  // Program type
   string               m_name_program;                  // Program name
   string               m_name_prefix;                   // Object name prefix
//--- Return the counter index by id
   int                  CounterIndex(const int id) const;

...

//--- Return the list of graphical elements by chart ID and object name
   CArrayObj           *GetListCanvElementByName(const long chart_id,const string name)
                          {
                           return this.m_graph_objects.GetListCanvElementByName(chart_id,name);
                          }
//--- Return the list of graphical elements by object type
   CArrayObj           *GetListCanvElementByType(const ENUM_GRAPH_ELEMENT_TYPE type)
                          {
                           return this.m_graph_objects.GetListCanvElementByType(type);
                          }
   
//--- Return the graphical element by chart ID and object name
   CGCnvElement        *GetCanvElementByName(const long chart_id,const string name)
                          {
                           return this.m_graph_objects.GetCanvElement(chart_id,name);
                          }
//--- Return the graphical element by chart and object IDs
   CGCnvElement        *GetCanvElementByID(const long chart_id,const int element_id)
                          {
                           return this.m_graph_objects.GetCanvElement(chart_id,element_id);
                          }
   
//--- Return the WForm Element object by object name on the current chart
   CGCnvElement        *GetWFElement(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Element object by chart ID and object name
   CGCnvElement        *GetWFElement(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Element object by object ID
   CGCnvElement        *GetWFElement(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
   
//--- Return the WForm Form object by object name on the current chart
   CForm               *GetWFForm(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Form object by chart ID and object name
   CForm               *GetWFForm(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Element object by object ID
   CForm               *GetWFForm(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
   
//--- Return the WForm Panel object by object name on the current chart
   CPanel              *GetWFPanel(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by chart ID and object name
   CPanel              *GetWFPanel(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by object ID
   CPanel              *GetWFPanel(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }

Die Logik hinter all diesen Methoden besteht in einer einfachen Sortierung von Listen nach den angegebenen Objekteigenschaften. Ich habe es mir viele Male überlegt, also sollte es keine Schwierigkeiten bereiten. Wenn Sie Fragen haben, können Sie diese gerne im Kommentarteil stellen.

Schreibern wir eine Methode, die das WinForm Element-Objekt erstellt:

//--- Create the WinForm Element object
   CGCnvElement        *CreateWFElement(const long chart_id,
                                        const int subwindow,
                                        const string name,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h,
                                        color &clr[],
                                        const uchar opacity,
                                        const bool v_gradient=true,
                                        const bool c_gradient=false,
                                        const bool redraw=false)
                          {
                           //--- Get the created object ID
                           int obj_id=
                             (
                              //--- In case of a vertical gradient:
                              v_gradient ?
                                (
                                 //--- if not a cyclic gradient, create an object with the vertical gradient filling
                                 !c_gradient ? this.m_graph_objects.CreateElementVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) :
                                 //--- otherwise, create an object with the cyclic vertical gradient filling
                                 this.m_graph_objects.CreateElementVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw)
                                ) :
                              //--- If this is not a vertical gradient:
                              !v_gradient ?
                                (
                                 //--- if not a cyclic gradient, create an object with the horizontal gradient filling
                                 !c_gradient ? this.m_graph_objects.CreateElementHGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) :
                                 //--- otherwise, create an object with the cyclic horizontal gradient filling
                                 this.m_graph_objects.CreateElementHGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw)
                                ) :
                              WRONG_VALUE
                             );
                           //--- return the pointer to an object by its ID
                           return this.GetWFElement(obj_id);
                          }

Wir rufen je nach Füllungstyp (vertikaler oder horizontaler Verlauf, zyklisch oder nicht) die entsprechenden Methoden zum Erstellen des grafischen Elementobjekts auf, das oben in der Sammlungsklasse für grafische Elemente implementiert wurde.

Mit dieser Methode schreiben wir die Methoden zum Erstellen von Objekten mit grafischen Elementen in das aktuelle Chart und in das angegebene Unterfenster sowie in das aktuelle Chart des Hauptfensters:

//--- Create the WinForm Element object in the specified subwindow on the current chart
   CGCnvElement        *CreateWFElement(const int subwindow,
                                        const string name,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h,
                                        color &clr[],
                                        const uchar opacity,
                                        const bool v_gradient=true,
                                        const bool c_gradient=false,
                                        const bool redraw=false)
                          {
                           return this.CreateWFElement(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,v_gradient,c_gradient,redraw);
                          }
//--- Create the WinForm Element object in the main window of the current chart
   CGCnvElement        *CreateWFElement(const string name,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h,
                                        color &clr[],
                                        const uchar opacity,
                                        const bool v_gradient=true,
                                        const bool c_gradient=false,
                                        const bool redraw=false)
                          {
                           return this.CreateWFElement(::ChartID(),0,name,x,y,w,h,clr,opacity,v_gradient,c_gradient,redraw);
                          }

Die Methoden geben das Ergebnis des Aufrufs der Methode zum Erstellen des grafischen Elements zurück, das die erforderliche Chart- und Unterfenster-ID angibt.

Die Methoden zum Erstellen von WinForm-Formularobjekten werden auf ähnliche Weise festgelegt:

//--- Create the WinForm Form object
   CForm               *CreateWFForm(const long chart_id,
                                     const int subwindow,
                                     const string name,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h,
                                     color &clr[],
                                     const uchar opacity,
                                     const bool movable,
                                     const bool v_gradient=true,
                                     const bool c_gradient=false,
                                     const bool shadow=false,
                                     const bool redraw=false)
                          {
                           int obj_id=
                             (
                              v_gradient ?
                                (
                                 !c_gradient ? this.m_graph_objects.CreateFormVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) :
                                 this.m_graph_objects.CreateFormVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw)
                                ) :
                              !v_gradient ?
                                (
                                 !c_gradient ? this.m_graph_objects.CreateFormVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) :
                                 this.m_graph_objects.CreateFormVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw)
                                ) :
                              WRONG_VALUE
                             );
                           return this.GetWFForm(obj_id);
                          }
//--- Create the WinForm Form object in the specified subwindow on the current chart
   CForm               *CreateWFForm(const int subwindow,
                                     const string name,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h,
                                     color &clr[],
                                     const uchar opacity,
                                     const bool movable,
                                     const bool v_gradient=true,
                                     const bool c_gradient=false,
                                     const bool shadow=false,
                                     const bool redraw=false)
                          {
                           return this.CreateWFForm(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,shadow,redraw);
                          }
//--- Create the WinForm Form object in the main window of the current chart
   CForm               *CreateWFForm(const string name,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h,
                                     color &clr[],
                                     const uchar opacity,
                                     const bool movable,
                                     const bool v_gradient=true,
                                     const bool c_gradient=false,
                                     const bool shadow=false,
                                     const bool redraw=false)
                          {
                           return this.CreateWFForm(::ChartID(),0,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,shadow,redraw);
                          }

Hier ist alles genau gleich: Eine Methode baut ein Formularobjekt mit der angegebenen Füllung auf, während die anderen beiden Methoden das Ergebnis des Aufrufs der ersten zurückgeben, wobei sie das gewünschte Chart und die Unterfenster-ID angeben.

Die Methoden zum Erstellen des WinForm Panel-Objekts werden auf die gleiche Weise festgelegt:

//--- Create the WinForm Panel object
   CForm               *CreateWFPanel(const long chart_id,
                                      const int subwindow,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      color &clr[],
                                      const uchar opacity,
                                      const bool movable,
                                      const bool v_gradient=true,
                                      const bool c_gradient=false,
                                      const int frame_width=-1,
                                      const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                      const bool shadow=false,
                                      const bool redraw=false)
                          {
                           int obj_id=
                             (
                              v_gradient ?
                                (
                                 !c_gradient ? this.m_graph_objects.CreatePanelVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) :
                                 this.m_graph_objects.CreatePanelVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw)
                                ) :
                              !v_gradient ?
                                (
                                 !c_gradient ? this.m_graph_objects.CreatePanelHGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) :
                                 this.m_graph_objects.CreatePanelHGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw)
                                ) :
                              WRONG_VALUE
                             );
                           return this.GetWFPanel(obj_id);
                          }
//--- Create the WinForm Panel object in the specified subwindow on the current chart
   CForm               *CreateWFPanel(const int subwindow,
                                      const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      color &clr[],
                                      const uchar opacity,
                                      const bool movable,
                                      const bool v_gradient=true,
                                      const bool c_gradient=false,
                                      const int frame_width=-1,
                                      const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                      const bool shadow=false,
                                      const bool redraw=false)
                          {
                           return this.CreateWFPanel(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,frame_width,frame_style,shadow,redraw);
                          }
//--- Create the WinForm Panel object in the main window of the current chart
   CForm               *CreateWFPanel(const string name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      color &clr[],
                                      const uchar opacity,
                                      const bool movable,
                                      const bool v_gradient=true,
                                      const bool c_gradient=false,
                                      const int frame_width=-1,
                                      const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                      const bool shadow=false,
                                      const bool redraw=false)
                          {
                           return this.CreateWFPanel(::ChartID(),0,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,frame_width,frame_style,shadow,redraw);
                          }

Ich überlasse diese Methoden einer unabhängigen Analyse. Sie sind alle identisch mit den oben besprochenen.

Erstellen wir im Klassenkonstruktor das Namenspräfix des grafischen Objekts:

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program_type=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name_program=::MQLInfoString(MQL_PROGRAM_NAME);
   this.m_name_prefix=this.m_name_program+"_";
   
//--- ...

//--- ...

  }

Wie Sie vielleicht bemerkt haben, wurde die Variablem_name in m_name_program umbenannt, um ihren Zweck genauer anzuzeigen.


Test

Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn unter \MQL5\Experts\TestDoEasy\Part103\ als TestDoEasyPart103.mq5.

Im Vergleich zum vorherigen EA habe ich die Erstellung von Steuerelementen vereinfacht. Die CEngine-Klasse bietet die Methoden für ihre Erstellung und gleichzeitiges Erhalten von Zeigern auf erstellte Objekte. Nachdem wir ein Panel-Objekt erstellt haben, erstellen wir fünf daran gebundene Formularobjekte:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//--- Create form objects
   string name="";
   int obj_id=WRONG_VALUE;
   CArrayObj *list=NULL;
   CForm *form=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      form=engine.CreateWFForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true);
      if(form==NULL)
         continue;
      //--- Set ZOrder to zero, display the text describing the gradient type and update the form
      //--- Text parameters: the text coordinates and the anchor point in the form center
      //--- Create a new text animation frame with the ID of 0 and display the text on the form
      form.SetZorder(0,false);
      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,false);
     }
//--- Create four graphical elements
   CGCnvElement *elm=NULL;
   array_clr[0]=C'0x65,0xA4,0xA9';
   array_clr[1]=C'0x48,0x75,0xA2';
//--- Vertical gradient
   elm=engine.CreateWFElement("CElmVG",form.RightEdge()+20,20,200,50,array_clr,127,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Vertical cyclic gradient
   elm=engine.CreateWFElement("CElmVGC",form.RightEdge()+20,80,200,50,array_clr,127,true,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Horizontal gradient
   elm=engine.CreateWFElement("CElmHG",form.RightEdge()+20,140,200,50,array_clr,127,false,false);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Horizontal cyclic gradient
   elm=engine.CreateWFElement("CElmHGC",form.RightEdge()+20,200,200,50,array_clr,127,false,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",elm.RightEdge()+20,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true);
   if(pnl!=NULL)
     {
      pnl.FontDrawStyle(FONT_STYLE_NORMAL);
      pnl.Bold(true);
      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());
      //--- In the loop, create five bound form objects located horizontally on top of the panel indented by 4 pixels from each other
      CGCnvElement *obj=NULL;
      for(int i=0;i<5;i++)
        {
         //--- create a form object with calculated coordinates along the X axis 
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_FORM,(obj==NULL ? 3 : obj.RightEdgeRelative()+4),3,40,30,C'0xCD,0xDA,0xD7',200,true);
         //--- To control the creation of bound objects,
         //--- get the pointer to the bound object by the loop index
         obj=pnl.GetElement(i);
         //--- take the pointer to the base object from the obtained object
         CPanel *p=obj.GetBase();
         //--- and display the name of a created bound object and the name of its base object in the journal
         Print
           (
            TextByLanguage("Объект ","Object "),obj.TypeElementDescription()," ",obj.Name(),
            TextByLanguage(" привязан к объекту "," is attached to object "),p.TypeElementDescription()," ",p.Name()
           );
        }
      pnl.Update(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Kompilieren Sie den EA und starten Sie ihn auf dem Chart:


Wie wir sehen können, wurden alle an das Panel angehängten Objekte erfolgreich erstellt. Der Bereichsobjektschatten befindet sich über anderen Chartobjekten und folgt dem Objekt, für das er konstruiert wurde. Beim Konstruieren einer vertikalen Linie sowie aller anderen standardmäßigen grafischen Objekte verbleiben alle Steuerelemente mit Ausnahme der festen grafischen Elemente über dem neu erstellten grafischen Objekt.

Beim Erstellen von Steuerelementen, die an das Panel angehängt sind, zeigt das Journal Einträge an, die angeben, ob jedes der gebundenen Objekte einen Zeiger auf das Basisobjekt hat:

Object Form TestDoEasyPart103_WFPanel_Elm01 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
Object Form TestDoEasyPart103_WFPanel_Elm02 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
Object Form TestDoEasyPart103_WFPanel_Elm03 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
Object Form TestDoEasyPart103_WFPanel_Elm04 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
Object Form TestDoEasyPart103_WFPanel_Elm05 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel


Was kommt als Nächstes?

Im nächsten Artikel werde ich meine Arbeit an der Funktionsweise des Panel-Steuerelements 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 im Kommentarteil.

Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

DoEasy. Steuerung (Teil 1): Erste Schritte
DoEasy. Steuerung (Teil 2): Arbeiten an der CPanel-Klasse