![Grafik in der Bibliothek DoEasy (Teil 80): Die Objektklasse "Geometrischer Animationsrahmen"](https://c.mql5.com/i/articles/overlay.png)
Grafik in der Bibliothek DoEasy (Teil 80): Die Objektklasse "Geometrischer Animationsrahmen"
Inhalt
- Konzept
- Verbesserung der Klassenbibliothek
- Die Objektklasse "Geometrischer Animationsrahmen"
- Test
- Was kommt als Nächstes?
Konzept
In diesem Artikel werde ich die Arbeit an Klassen fortsetzen, die zum Zeichnen einer Figur auf dem Hintergrund gedacht sind. Ich habe bereits die Klassen für Animationsrahmen erstellt, die es ermöglichen, einen einzelnen Animationsrahmen in einem bestimmten Bereich des Hintergrunds zu zeichnen, wobei der Hintergrund, auf dem das Bild liegt, erhalten bleibt. Dies ermöglicht es uns, den Hintergrund wiederherzustellen, wenn wir das Bild löschen oder ändern. Mit diesen vorab erstellten Frames können wir Animationssequenzen für schnelle Frame-Wechsel zusammenstellen. Ein einzelnes Bild ermöglicht auch die Erstellung von Animationen innerhalb seines Bereichs.
Heute werde ich die zuvor erstellten Codes dieser Klassen leicht optimieren. Ich werde mich an das Konzept halten, dass, wenn es sich wiederholende Codeabschnitte gibt, die gesamte Logik in einer separaten Funktion/Methode formalisiert werden kann (und sollte), die dann aufgerufen wird. Dadurch wird der Code lesbarer und sein Umfang verringert sich.
Darüber hinaus werde ich die Objektklasse des geometrischen Animationsrahmens erstellen. Was bedeutet das?
Wir haben bereits genug Methoden, um verschiedene Polygone zu konstruieren. Aber wenn wir ein regelmäßiges Polygon zeichnen müssen, ist es viel einfacher, die Geometrie zu verwenden, als die Koordinaten der Eckpunkte manuell zu berechnen. Später werde ich vielleicht andere geometrische Figuren hinzufügen, deren Koordinaten des Scheitelpunkts mit Hilfe von Gleichungen berechnet werden können, anstatt sie manuell festzulegen.
Laut Wikipedia:
Ein regelmäßiges Polygon ist in der Geometrie ein ebenes Polygon, das sowohl gleichseitig (alle Seiten sind gleich lang) als auch gleichwinklig (alle Winkel sind gleich groß) ist, zum Beispiel:
Regelmäßiges Achteck
Jedes regelmäßige Vieleck kann in einen Kreis eingeschrieben werden. Ein solcher Kreis wird Umkreis genannt. Der Kreis geht durch alle Scheitelpunkte des Polygons.
Umkreis
Es gibt auch einen Innenkreis. Das ist ein Kreis, der in ein Polygon eingeschrieben ist. In diesem Fall berühren alle Seiten des Polygons die Kreislinie.
Innenkreis
Ich werde solche Polygone nicht besprechen, mit Ausnahme eines Quadrats, in das ein Kreis eingeschrieben wird. Ein regelmäßiges Polygon wird wiederum in den Kreis eingeschrieben.
Das Quadrat stellt den Animationsrahmen dar — seine Koordinaten der oberen linken Ecke und die Größe (Länge) seiner Seiten. Der Kreis, dessen Durchmesser der Seitenlänge des Animationsrahmenquadrats entsprechen soll, wird ein eingeschriebenes Polygon enthalten, dessen Eckpunkte die Kreislinie berühren.
Es besteht also keine Notwendigkeit, Polygon-Koordinatenfelder zu erstellen. Stattdessen müssen wir nur die erforderliche Anzahl von Scheitelpunkten, die Koordinaten der oberen linken Ecke und die Länge der quadratischen Seiten angeben.
Verbesserung der Klassenbibliothek
In \MQL5\Include\DoEasy\Data.mqh, ergänzen wir die neuen Indices der Nachrichten:
//--- CGCnvElement MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY, // Error! Empty array MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH, // Error! Array-copy of the resource does not match the original //--- CForm
und den Nachrichtentext, der dem neu hinzugefügten Index entspricht:
//--- CGCnvElement {"Ошибка! Пустой массив","Error! Empty array"}, {"Ошибка! Массив-копия ресурса не совпадает с оригиналом","Error! Array-copy of the resource does not match the original"}, //--- CForm
Die Ausrichtung (Ankerwinkel) von Animationsrahmen hängt jetzt von allen Animationsrahmen ab (Text, rechteckig, geometrisch und andere). Daher habe ich beschlossen, die Namen der Enumeration und ihrer Konstanten leicht zu ändern, sodass sie an Rahmen und nicht an einen Text gebunden sind.
In \MQL5\Include\DoEasy\Defines.mqh, nämlich in der Enumeration der Ankerwinkel, ersetzen wir "TEXT" durch "FRAME":
//+------------------------------------------------------------------+ //| Data for handling graphical elements | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of anchoring methods | //| (horizontal and vertical text alignment) | //+------------------------------------------------------------------+ enum ENUM_FRAME_ANCHOR { FRAME_ANCHOR_LEFT_TOP = 0, // Frame anchor point at the upper left corner of the bounding rectangle FRAME_ANCHOR_CENTER_TOP = 1, // Frame anchor point at the top center side of the bounding rectangle FRAME_ANCHOR_RIGHT_TOP = 2, // Frame anchor point at the upper right corner of the bounding rectangle FRAME_ANCHOR_LEFT_CENTER = 4, // Frame anchor point at the center of the left side of the bounding rectangle FRAME_ANCHOR_CENTER = 5, // Frame anchor point at the center of the bounding rectangle FRAME_ANCHOR_RIGHT_CENTER = 6, // Frame anchor point at the center of the right side of the bounding rectangle FRAME_ANCHOR_LEFT_BOTTOM = 8, // Frame anchor point at the lower left corner of the bounding rectangle FRAME_ANCHOR_CENTER_BOTTOM = 9, // Frame anchor point at the bottom center side of the bounding rectangle FRAME_ANCHOR_RIGHT_BOTTOM = 10, // Frame anchor point at the lower right corner of the bounding rectangle }; //+------------------------------------------------------------------+
In der Enumeration der Animationsrahmentypen fügen wir einen neuen Typ hinzu — den Rahmen für Animationen geometrischer Figuren:
//+------------------------------------------------------------------+ //| Data for working with graphical element animation | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of animation frame types | //+------------------------------------------------------------------+ enum ENUM_ANIMATION_FRAME_TYPE { ANIMATION_FRAME_TYPE_TEXT, // Text animation frame ANIMATION_FRAME_TYPE_QUAD, // Rectangular animation frame ANIMATION_FRAME_TYPE_GEOMETRY, // Square animation frame of geometric shapes }; //+------------------------------------------------------------------+
während wir in der Liste der gezeichneten Formtypen den gefüllten Bereich hinzufügen, den ich in den vorherigen Artikeln vergessen habe zu implementieren:
//+------------------------------------------------------------------+ //| List of drawn shape types | //+------------------------------------------------------------------+ enum ENUM_FIGURE_TYPE { FIGURE_TYPE_PIXEL, // Pixel FIGURE_TYPE_PIXEL_AA, // Pixel with antialiasing FIGURE_TYPE_LINE_VERTICAL, // Vertical line FIGURE_TYPE_LINE_VERTICAL_THICK, // a Vertical segment of a freehand line having a specified width using antialiasing algorithm FIGURE_TYPE_LINE_HORIZONTAL, // Horizontal line FIGURE_TYPE_LINE_HORIZONTAL_THICK, // Horizontal segment of a freehand line having a specified width using antialiasing algorithm FIGURE_TYPE_LINE, // Arbitrary line FIGURE_TYPE_LINE_AA, // Line with antialiasing FIGURE_TYPE_LINE_WU, // Line with WU smoothing FIGURE_TYPE_LINE_THICK, // Segment of a freehand line having a specified width using antialiasing algorithm FIGURE_TYPE_POLYLINE, // Polyline FIGURE_TYPE_POLYLINE_AA, // Polyline with antialiasing FIGURE_TYPE_POLYLINE_WU, // Polyline with WU smoothing FIGURE_TYPE_POLYLINE_SMOOTH, // Polyline with a specified width using two smoothing algorithms FIGURE_TYPE_POLYLINE_THICK, // Polyline with a specified width using a smoothing algorithm FIGURE_TYPE_POLYGON, // Polygon FIGURE_TYPE_POLYGON_FILL, // Filled polygon FIGURE_TYPE_POLYGON_AA, // Polygon with antialiasing FIGURE_TYPE_POLYGON_WU, // Polygon with WU smoothing FIGURE_TYPE_POLYGON_SMOOTH, // Polygon with a specified width using two smoothing algorithms FIGURE_TYPE_POLYGON_THICK, // Polygon with a specified width using a smoothing algorithm FIGURE_TYPE_RECTANGLE, // Rectangle FIGURE_TYPE_RECTANGLE_FILL, // Filled rectangle FIGURE_TYPE_CIRCLE, // Circle FIGURE_TYPE_CIRCLE_FILL, // Filled circle FIGURE_TYPE_CIRCLE_AA, // Circle with antialiasing FIGURE_TYPE_CIRCLE_WU, // Circle with WU smoothing FIGURE_TYPE_TRIANGLE, // Triangle FIGURE_TYPE_TRIANGLE_FILL, // Filled triangle FIGURE_TYPE_TRIANGLE_AA, // Triangle with antialiasing FIGURE_TYPE_TRIANGLE_WU, // Triangle with WU smoothing FIGURE_TYPE_ELLIPSE, // Ellipse FIGURE_TYPE_ELLIPSE_FILL, // Filled ellipse FIGURE_TYPE_ELLIPSE_AA, // Ellipse with antialiasing FIGURE_TYPE_ELLIPSE_WU, // Ellipse with WU smoothing FIGURE_TYPE_ARC, // Ellipse arc FIGURE_TYPE_PIE, // Ellipse sector FIGURE_TYPE_FILL, // Filled area }; //+------------------------------------------------------------------+
In \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh ersetzen wir den Namen des Arrays zum Speichern einer Kopie einer grafischen Ressource durch einen anschaulicheren, da die Array-Namen ziemlich verwirrend sind und es schwierig machen, zu definieren, welches Array zum Speichern der Kopie des ursprünglich erstellten Formulars verwendet werden soll. Außerdem sollte die Methode, die die grafische Ressource im Array speichert, aus dem Abschnitt für geschützte Klassen entfernt werden:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: 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 //--- Return the cursor position relative to the (1) entire element and (2) the element's active area bool CursorInsideElement(const int x,const int y); bool CursorInsideActiveArea(const int x,const int y); //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); //--- Save the graphical resource to the array bool ResourceCopy(const string source); private:
Wir ersetzen "TEXT_ANCHOR" durch "FRAME_ANCHOR" im Code der Klasse (oder noch besser in allen Bibliotheksdateien auf einmal). Um alle Vorkommen in allen Bibliotheksdateien zu finden, drücken Sie einfach Umschalt+Strg+H und geben Sie in dem neuen Fenster die folgenden Such- und Ersetzungskriterien ein:
Das Feld "Ordner:" (im Bild oben "Folder") sollte den Pfad enthalten, der auf dem Speicherort Ihres Editors basiert.
Im öffentlichen Teil der Klasse deklarieren wir die Methoden zum Speichern der grafischen Ressource im Array und zum Wiederherstellen der Ressource aus dem Array, sowie die Methoden zum Aktualisieren des Hintergrunds und die Methode, die die Größe des Arrays für die Kopie der grafischen Ressource zurückgibt:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)];} string GetProperty(ENUM_CANV_ELEMENT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)];} //--- Return the flag of the object supporting this property virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return false; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return itself CGCnvElement *GetObject(void) { return &this; } //--- Compare CGCnvElement objects with each other by all possible properties (for sorting the lists by a specified object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CGCnvElement objects with each other by all properties (to search equal objects) bool IsEqual(CGCnvElement* compared_obj) const; //--- (1) Save the object to file and (2) upload the object from the file virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); //--- (1) Save the graphical resource to the array and (2) restore the resource from the array bool ResourceStamp(const string source); virtual bool Reset(void); //--- 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); //--- Return the pointer to a canvas object CCanvas *GetCanvasObj(void) { return &this.m_canvas; } //--- Set the canvas update frequency void SetFrequency(const ulong value) { this.m_pause.SetWaitingMSC(value); } //--- Update the canvas void CanvasUpdate(const bool redraw=false) { this.m_canvas.Update(redraw); } //--- Return the size of the graphical resource copy array uint DuplicateResArraySize(void) { return ::ArraySize(this.m_duplicate_res); } //--- Update the coordinates (shift the canvas) 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[]); //--- Change the lightness of (1) ARGB and (2) COLOR by a specified amount uint ChangeColorLightness(const uint clr,const double change_value); color ChangeColorLightness(const color colour,const double change_value); //--- Change the saturation of (1) ARGB and (2) COLOR by a specified amount uint ChangeColorSaturation(const uint clr,const double change_value); color ChangeColorSaturation(const color colour,const double change_value); protected:
Die frühere Methode ResourceCopy() heißt jetzt ResourceStamp():
//+------------------------------------------------------------------+ //| Save the graphical resource to the array | //+------------------------------------------------------------------+ bool CGCnvElement::ResourceStamp(const string source) { return this.ImageCopy(DFUN,this.m_duplicate_res); } //+------------------------------------------------------------------+
Die Methode stellt die grafische Ressource aus dem Array wieder her:
//+------------------------------------------------------------------+ //| Restore the graphical resource from the array | //+------------------------------------------------------------------+ bool CGCnvElement::Reset(void) { //--- Get the size of the graphical resource copy array int size=::ArraySize(this.m_duplicate_res); //--- If the array is empty, inform of that and return 'false' if(size==0) { CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY); return false; } //--- If the size of the graphical resource copy array does not match the size of the graphical resource, //--- inform of that in the journal and return 'false' if(this.m_canvas.Width()*this.m_canvas.Height()!=size) { CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH); return false; } //--- Set the index of the array for setting the image pixel int n=0; //--- In the loop by the resource height, for(int y=0;y<this.m_canvas.Height();y++) { //--- in the loop by the resource width for(int x=0;x<this.m_canvas.Width();x++) { //--- Restore the next image pixel from the array and increase the array index this.m_canvas.PixelSet(x,y,this.m_duplicate_res[n]); n++; } } //--- Update the data on the canvas and return 'true' this.m_canvas.Update(false); return true; } //+------------------------------------------------------------------+
Die Logik der Methode wird in den Code-Kommentaren beschrieben. Kurz gesagt, wir überprüfen die Größe des Arrays der Ressourcenkopie. Wenn es leer ist oder die Größe der Kopie nicht mit dem Original übereinstimmt, wird der Fehler an das Journal gemeldet und die Methode beendet. Als Nächstes werden alle Daten aus der Array-Kopie Pixel für Pixel auf den Hintergrund kopiert.
Da ich den Namen der Array-Kopie der Ressource und die Methode, die die grafische Ressource im Array speichert, geändert habe, muss ich Korrekturen in der Klassendatei \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh für Schattenobjekte vornehmen.
Die Korrekturen betreffen nur die GaussianBlur() Methode:
//+------------------------------------------------------------------+ //| Gaussian blur | //| https://www.mql5.com/de/articles/1612#chapter4 | //+------------------------------------------------------------------+ bool CShadowObj::GaussianBlur(const uint radius) { //--- int n_nodes=(int)radius*2+1; //--- Read graphical resource data. If failed, return false if(!CGCnvElement::ResourceStamp(DFUN)) return false; //--- Check the blur amount. If the blur radius exceeds half of the width or height, return 'false' if((int)radius>=this.Width()/2 || (int)radius>=this.Height()/2) { ::Print(DFUN,CMessage::Text(MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE)); return false; } //--- Decompose image data from the resource into a, r, g, b color components int size=::ArraySize(this.m_duplicate_res); //--- arrays for storing A, R, G and B color components //--- for horizontal and vertical blur uchar a_h_data[],r_h_data[],g_h_data[],b_h_data[]; uchar a_v_data[],r_v_data[],g_v_data[],b_v_data[]; //--- Change the size of component arrays according to the array size of the graphical resource data if(::ArrayResize(a_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_h_data\""); return false; } if(::ArrayResize(r_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_h_data\""); return false; } if(::ArrayResize(g_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_h_data\""); return false; } if(ArrayResize(b_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_h_data\""); return false; } if(::ArrayResize(a_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_v_data\""); return false; } if(::ArrayResize(r_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_v_data\""); return false; } if(::ArrayResize(g_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_v_data\""); return false; } if(::ArrayResize(b_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_v_data\""); return false; } //--- Declare the array for storing blur weight ratios and, //--- if failed to get the array of weight ratios, return 'false' double weights[]; if(!this.GetQuadratureWeights(1,n_nodes,weights)) return false; //--- Set components of each image pixel to the color component arrays for(int i=0;i<size;i++) { a_h_data[i]=GETRGBA(this.m_duplicate_res[i]); r_h_data[i]=GETRGBR(this.m_duplicate_res[i]); g_h_data[i]=GETRGBG(this.m_duplicate_res[i]); b_h_data[i]=GETRGBB(this.m_duplicate_res[i]); } //--- Blur the image horizontally (along the X axis) uint XY; // Pixel coordinate in the array double a_temp=0.0,r_temp=0.0,g_temp=0.0,b_temp=0.0; int coef=0; int j=(int)radius; //--- Loop by the image width for(int Y=0;Y<this.Height();Y++) { //--- Loop by the image height for(uint X=radius;X<this.Width()-radius;X++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { a_temp+=a_h_data[XY+i]*weights[coef]; r_temp+=r_h_data[XY+i]*weights[coef]; g_temp+=g_h_data[XY+i]*weights[coef]; b_temp+=b_h_data[XY+i]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_h_data[XY]=(uchar)::round(a_temp); r_h_data[XY]=(uchar)::round(r_temp); g_h_data[XY]=(uchar)::round(g_temp); b_h_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts to the left by copying adjacent pixels for(uint x=0;x<radius;x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[Y*this.Width()+radius]; r_h_data[XY]=r_h_data[Y*this.Width()+radius]; g_h_data[XY]=g_h_data[Y*this.Width()+radius]; b_h_data[XY]=b_h_data[Y*this.Width()+radius]; } //--- Remove blur artifacts to the right by copying adjacent pixels for(int x=int(this.Width()-radius);x<this.Width();x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[(Y+1)*this.Width()-radius-1]; r_h_data[XY]=r_h_data[(Y+1)*this.Width()-radius-1]; g_h_data[XY]=g_h_data[(Y+1)*this.Width()-radius-1]; b_h_data[XY]=b_h_data[(Y+1)*this.Width()-radius-1]; } } //--- Blur vertically (along the Y axis) the image already blurred horizontally int dxdy=0; //--- Loop by the image height for(int X=0;X<this.Width();X++) { //--- Loop by the image width for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { dxdy=i*(int)this.Width(); a_temp+=a_h_data[XY+dxdy]*weights[coef]; r_temp+=r_h_data[XY+dxdy]*weights[coef]; g_temp+=g_h_data[XY+dxdy]*weights[coef]; b_temp+=b_h_data[XY+dxdy]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_v_data[XY]=(uchar)::round(a_temp); r_v_data[XY]=(uchar)::round(r_temp); g_v_data[XY]=(uchar)::round(g_temp); b_v_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts at the top by copying adjacent pixels for(uint y=0;y<radius;y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+radius*this.Width()]; r_v_data[XY]=r_v_data[X+radius*this.Width()]; g_v_data[XY]=g_v_data[X+radius*this.Width()]; b_v_data[XY]=b_v_data[X+radius*this.Width()]; } //--- Remove blur artifacts at the bottom by copying adjacent pixels for(int y=int(this.Height()-radius);y<this.Height();y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+(this.Height()-1-radius)*this.Width()]; r_v_data[XY]=r_v_data[X+(this.Height()-1-radius)*this.Width()]; g_v_data[XY]=g_v_data[X+(this.Height()-1-radius)*this.Width()]; b_v_data[XY]=b_v_data[X+(this.Height()-1-radius)*this.Width()]; } } //--- Set the twice blurred (horizontally and vertically) image pixels to the graphical resource data array for(int i=0;i<size;i++) this.m_duplicate_res[i]=ARGB(a_v_data[i],r_v_data[i],g_v_data[i],b_v_data[i]); //--- Display the image pixels on the canvas in a loop by the image height and width from the graphical resource data array for(int X=0;X<this.Width();X++) { for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; this.m_canvas.PixelSet(X,Y,this.m_duplicate_res[XY]); } } //--- Done return true; } //+------------------------------------------------------------------+
Verbessern wir die Animationsrahmen-Objektklasse in \MQL5\Include\DoEasy\Objects\Graph\Animations\Frame.mqh.
Im geschützten Abschnitt der Klasse deklarieren wir die Methode zum Schreiben von Koordinatenwerten und zum Verschieben des Umrissrechtecks wie die vorherigen für ihre spätere Verwendung, und schreiben die virtuelle Methode zum Speichern und Wiederherstellen des Hintergrunds unter dem Bild:
//+------------------------------------------------------------------+ //| Single animation frame class | //+------------------------------------------------------------------+ class CFrame : public CPixelCopier { protected: ENUM_ANIMATION_FRAME_TYPE m_frame_figure_type; // Type of the figure drawn by the frame ENUM_FRAME_ANCHOR m_anchor_last; // Last frame anchor point double m_x_last; // X coordinate of the upper left corner of the last frame double m_y_last; // Y coordinate of the upper left corner of the last frame int m_shift_x_prev; // Offset of the X coordinate of the last frame upper left corner int m_shift_y_prev; // Offset of the Y coordinate of the last frame upper left corner //--- Set the coordinates and offset of the outlining rectangle as the previous ones void SetLastParams(const double quad_x,const double quad_y,const int shift_x,const int shift_y,const ENUM_FRAME_ANCHOR anchor=FRAME_ANCHOR_LEFT_TOP); //--- Save and restore the background under the image virtual bool SaveRestoreBG(void) { return false; } public:
Alle diese Methoden sind das Ergebnis der Optimierung des Codes für die Methoden zum Zeichnen von Figuren in den Klassen, die ich in den vorherigen Artikeln erstellt habe.
Die virtuelle Methode gibt hier einfach false zurück und sollte in den Nachfolgeklassen implementiert werden. Wenn sich herausstellt, dass ihre Implementierung in allen geerbten Klassen gleich ist, wird die Methode nicht-virtuell gemacht. Außerdem wird sie nur in dieser Klasse implementiert. Zur Methode SetLastParams() kommen wir etwas später.
Wir schreiben in den öffentlichen Abschnitt der Klasse die Methode zum Zurücksetzen des Pixel-Arrays:
public: //--- Reset the pixel array void ResetArray(void) { ::ArrayResize(this.m_array,0); } //--- Return the last (1) anchor point, (2) X and (3) Y coordinate, //--- previous offset by (4) X and (5) Y, (6) type of the figure drawn by the frame ENUM_FRAME_ANCHOR LastAnchor(void) const { return this.m_anchor_last; } double LastX(void) const { return this.m_x_last; } double LastY(void) const { return this.m_y_last; } int LastShiftX(void) const { return this.m_shift_x_prev; } int LastShiftY(void) const { return this.m_shift_y_prev; } ENUM_ANIMATION_FRAME_TYPE FrameFigureType(void) const { return this.m_frame_figure_type; } //--- Default constructor CFrame(); protected:
Die Methode setzt einfach die Größe des Pixelarrays auf Null. Dies ermöglicht eine korrekte Behandlung von Änderungen der Größe des Umrissrechtecks, da die Methode, die den Hintergrund für seine spätere Wiederherstellung speichert, zunächst die Array-Größe überprüft. Ist sie gleich Null, wird der Hintergrund gespeichert. Andernfalls wird besprochen, dass der Hintergrund zuvor mit korrekten Koordinaten und gespeicherter Bereichsgröße gespeichert wurde. Wenn wir also die gezeichnete Figur ändern, sollte das Array zurückgesetzt werden. Andernfalls wird der Hintergrund unter einem neuen Bild nicht gespeichert und anschließend ein völlig anderer Hintergrund aus einem anderen Bereich wiederhergestellt (der zuvor gespeicherte — bevor die Größe, die Koordinaten und das Aussehen der gezeichneten Figur geändert wurden).
In dem geschützten Klassenabschnitt deklarieren wir den Klassenkonstruktor — geometrischer Figur-Animationsrahmen, den ich heute erstellen und testen werde:
protected: //--- Text frame constructor CFrame(const int id, const int x, const int y, const string text, CGCnvElement *element); //--- Rectangular frame constructor CFrame(const int id, const int x, const int y, const int w, const int h, CGCnvElement *element); //--- Geometric frame constructor CFrame(const int id, const int x, const int y, const int len, CGCnvElement *element); }; //+------------------------------------------------------------------+
Ähnlich wie bei den zuvor erstellten geerbten Klassen übergeben wir dem Klassenkonstruktor die Objekt-ID, die X- und Y-Koordinaten des oberen linken Rahmenwinkels, die Länge der quadratischen Rahmenseiten und den Zeiger auf das grafische Element, aus dem ein neues Objekt erstellt wird.
Implementierung des Konstruktors des geometrischen Animationsrahmenobjekts:
//+------------------------------------------------------------------+ //| Geometric frame constructor | //+------------------------------------------------------------------+ CFrame::CFrame(const int id,const int x,const int y,const int len,CGCnvElement *element) : CPixelCopier(id,x,y,len,len,element) { this.m_frame_figure_type=ANIMATION_FRAME_TYPE_GEOMETRY; this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP; this.m_x_last=x; this.m_y_last=y; this.m_shift_x_prev=0; this.m_shift_y_prev=0; } //+------------------------------------------------------------------+
Übergeben wir in der Initialisierungsliste alle notwendigen Parameter an den Konstruktor der übergeordneten Klasse, während wir im Klassenkörper den Typ der Figur als ANIMATION_FRAME_TYPE_GEOMETRY festlegen, den ich der Liste der Animationsrahmentypen im aktuellen Artikel hinzugefügt habe. Andere Parameter werden ähnlich initialisiert wie die zuvor besprochenen Konstruktoren der Text- und Rechteckanimationsklassen.
Die Methode, die die Koordinaten und den Versatz des umrahmenden Rechtecks festlegt, ist die gleiche wie die vorherige:
//+------------------------------------------------------------------+ //| Set the coordinates and the offset | //| of the outlining rectangle as the previous ones | //+------------------------------------------------------------------+ void CFrame::SetLastParams(const double quad_x,const double quad_y,const int shift_x,const int shift_y,const ENUM_FRAME_ANCHOR anchor=FRAME_ANCHOR_LEFT_TOP) { this.m_anchor_last=anchor; this.m_x_last=quad_x; this.m_y_last=quad_y; this.m_shift_x_prev=shift_x; this.m_shift_y_prev=shift_y; } //+------------------------------------------------------------------+
Das sich ständig wiederholende Stück Code aus den zuvor besprochenen Methoden zum Zeichnen von Figuren und zum Speichern und Wiederherstellen des Formularhintergrunds wurde in die Methode verschoben.
Verbessern wir die von der CFrame-Klasse abstammenden Klassen.
Öffnen wir die Datei der rechteckigen Animationsklasse \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameQuad.mqh und nehmen die notwendigen Änderungen vor.
Im privaten Teil der Klasse deklarieren wir die beiden Variablen zum Speichern der Offsets der Koordinaten des Umrissrechtecks und die virtuelle Methode zum Speichern und Wiederherstellen des Hintergrunds unter dem Bild:
//+------------------------------------------------------------------+ //| Class of a single rectangular animation frame | //+------------------------------------------------------------------+ class CFrameQuad : public CFrame { private: double m_quad_x; // X coordinate of the rectangle enclosing the shape double m_quad_y; // Y coordinate of the rectangle enclosing the shape uint m_quad_width; // Width of the rectangle enclosing the shape uint m_quad_height; // Height of the rectangle enclosing the shape int m_shift_x; // Offset of the X coordinate of the rectangle enclosing the shape int m_shift_y; // Offset of the Y coordinate of the rectangle enclosing the shape //--- Save and restore the background under the image virtual bool SaveRestoreBG(void); public:
Im öffentlichen Teil der Klasse ergänzen wir die Implementierung des parametrischen Konstruktors. Jetzt müssen alle Klassenvariablen in seinem Körper initialisiert werden (vorher wurden sie nicht initialisiert, was falsch ist):
public: //--- Constructors CFrameQuad() {;} CFrameQuad(const int id,CGCnvElement *element) : CFrame(id,0,0,0,0,element) { this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP; this.m_quad_x=0; this.m_quad_y=0; this.m_quad_width=0; this.m_quad_height=0; this.m_shift_x=0; this.m_shift_y=0; }
Schauen wir uns die Zeichenmethoden mit der Hintergrundspeicherung/-wiederherstellung an, die wir als Beispiel für die Punktzeichenmethode hatten:
//+------------------------------------------------------------------+ //| Set the color of the dot with the specified coordinates | //+------------------------------------------------------------------+ bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false) { //--- Set the coordinates of the outlining rectangle this.m_quad_x=x; this.m_quad_y=y; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=1; this.m_quad_height=1; //--- Calculate coordinate offsets for the saved area depending on the anchor point int shift_x=0,shift_y=0; this.m_element.GetShiftXYbySize(this.m_quad_width,this.m_quad_height,TEXT_ANCHOR_LEFT_TOP,shift_x,shift_y); //--- If the pixel array is not empty, the background under the image has already been saved - //--- restore the previously saved background (by the previous coordinates and offsets) if(::ArraySize(this.m_array)>0) { if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev))) return false; } //--- If a background area with calculated coordinates and size under the future image is successfully saved if(!CPixelCopier::CopyImgDataToArray(int(this.m_quad_x+shift_x),int(this.m_quad_y+shift_y),this.m_quad_width,this.m_quad_height)) return false; //--- Draw the shape and update the element this.m_element.SetPixel(x,y,clr,opacity); this.m_element.Update(redraw); this.m_anchor_last=TEXT_ANCHOR_LEFT_TOP; this.m_x_last=this.m_quad_x; this.m_y_last=this.m_quad_y; this.m_shift_x_prev=shift_x; this.m_shift_y_prev=shift_y; return true; } //+------------------------------------------------------------------+
Wir können nun die hervorgehobenen Codesegmente durch die neu erstellten Methoden ersetzen. So sieht die Methode jetzt aus:
//+------------------------------------------------------------------+ //| Set the color of the dot with the specified coordinates | //+------------------------------------------------------------------+ bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false) { //--- Set the coordinates of the outlining rectangle this.m_quad_x=x; this.m_quad_y=y; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=1; this.m_quad_height=1; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.SetPixel(x,y,clr,opacity); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Wie wir sehen können, hat das Ersetzen der angegebenen Codesegmente durch Aufruf der neuen Methoden den Code erheblich verkürzt und lesbarer gemacht. Identische Änderungen wurden in allen Methoden zum Zeichnen von Figuren mit Speichern und Wiederherstellen des Hintergrunds vorgenommen. Da diese Methoden zahlreich sind und ähnliche Änderungen aufweisen, ist es nicht sinnvoll, sie hier alle zu besprechen. Sie finden sie in den unten angehängten Dateien.
Ich werde mich nur mit den Methoden zum Zeichnen von Ellipsen befassen. Wie Sie sich vielleicht erinnern, habe ich im vorherigen Artikel keine Ellipsen gezeichnet, da CCanvas eine potenzielle Division durch Null aufweist. Das passiert, wenn die Methode ähnliche x1- und x2- bzw. y1- und y2-Koordinaten des Rechtecks erhält, in dem die Ellipse gezeichnet wird. Daher müssen wir in diesem Fall die Werte derselben Koordinaten anpassen, wenn sie gleich sind:
//+------------------------------------------------------------------+ //| Draw an ellipse using two points while applying | //| AntiAliasing algorithm | //+------------------------------------------------------------------+ bool CFrameQuad::DrawEllipseAAOnBG(const double x1, // X coordinate of the first point defining the ellipse const double y1, // Y coordinate of the first point defining the ellipse const double x2, // X coordinate of the second point defining the ellipse const double y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Get the minimum and maximum coordinates double xn1=::fmin(x1,x2); double xn2=::fmax(x1,x2); double yn1=::fmin(y1,y2); double yn2=::fmax(y1,y2); if(xn2==xn1) xn2=xn1+0.1; if(yn2==yn1) yn2=yn1+0.1; //--- Set the coordinates of the outlining rectangle this.m_quad_x=xn1-1; this.m_quad_y=yn1-1; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=int(::ceil((xn2-xn1)+1))+2; this.m_quad_height=int(::ceil((yn2-yn1)+1))+2; //--- Adjust the width and height of the outlining rectangle if(this.m_quad_width<3) this.m_quad_width=3; if(this.m_quad_height<3) this.m_quad_height=3; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.DrawEllipseAA(xn1,yn1,xn2,yn2,clr,opacity,style); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Draw an ellipse using two points while applying | //| Wu algorithm | //+------------------------------------------------------------------+ bool CFrameQuad::DrawEllipseWuOnBG(const int x1, // X coordinate of the first point defining the ellipse const int y1, // Y coordinate of the first point defining the ellipse const int x2, // X coordinate of the second point defining the ellipse const int y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Get the minimum and maximum coordinates double xn1=::fmin(x1,x2); double xn2=::fmax(x1,x2); double yn1=::fmin(y1,y2); double yn2=::fmax(y1,y2); if(xn2==xn1) xn2=xn1+0.1; if(yn2==yn1) yn2=yn1+0.1; //--- Set the coordinates of the outlining rectangle this.m_quad_x=xn1-1; this.m_quad_y=yn1-1; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=int(::ceil((xn2-xn1)+1))+2; this.m_quad_height=int(::ceil((yn2-yn1)+1))+2; //--- Adjust the width and height of the outlining rectangle if(this.m_quad_width<3) this.m_quad_width=3; if(this.m_quad_height<3) this.m_quad_height=3; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.DrawEllipseWu((int)xn1,(int)yn1,(int)xn2,(int)yn2,clr,opacity,style); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Die Methode zum Speichern und Wiederherstellen des Hintergrunds unter dem Bild:
//+------------------------------------------------------------------+ //| Save and restore the background under the image | //+------------------------------------------------------------------+ bool CFrameQuad::SaveRestoreBG(void) { //--- Calculate coordinate offsets for the saved area depending on the anchor point this.m_element.GetShiftXYbySize(this.m_quad_width,this.m_quad_height,FRAME_ANCHOR_LEFT_TOP,this.m_shift_x,this.m_shift_y); //--- If the pixel array is not empty, the background under the image has already been saved - //--- restore the previously saved background (by the previous coordinates and offsets) if(::ArraySize(this.m_array)>0) { if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev))) return false; } //--- Return the result of saving the background area with the calculated coordinates and size under the future image return CPixelCopier::CopyImgDataToArray(int(this.m_quad_x+this.m_shift_x),int(this.m_quad_y+this.m_shift_y),this.m_quad_width,this.m_quad_height); } //+------------------------------------------------------------------+
Der sich ständig wiederholende Codeblock aus den Methoden zum Zeichnen von Figuren mit Speichern und Wiederherstellen des Hintergrunds wurde einfach in die Methode verschoben.
\MQL5\Include\DoEasy\Objects\Graph\Animations\FrameText.mqh weist nur minimale Änderungen auf — einfach durch das Ersetzen der Strings "ENUM_TEXT_ANCHOR" durch "ENUM_FRAME_ANCHOR" in zwei Codefragmenten:
//+------------------------------------------------------------------+ //| Single text animation frame class | //+------------------------------------------------------------------+ class CFrameText : public CFrame { private: public: //--- Display the text on the background while saving and restoring the background bool TextOnBG(const string text,const int x,const int y,const ENUM_FRAME_ANCHOR anchor,const color clr,const uchar opacity,bool redraw=false); //--- Constructors CFrameText() {;} CFrameText(const int id,CGCnvElement *element) : CFrame(id,0,0,"",element) {} }; //+------------------------------------------------------------------+ //| Display the text on the background, while saving and restoring the background | //+------------------------------------------------------------------+ bool CFrameText::TextOnBG(const string text,const int x,const int y,const ENUM_FRAME_ANCHOR anchor,const color clr,const uchar opacity,bool redraw=false) {
Die Objektklasse "Geometrischer Animationsrahmen"
Die Logik hinter der Objektklasse geometrischer Animationsrahmen ist ihren beiden Vorgängern — Objekten der Text- und Rechteckanimation — sehr ähnlich. Wir müssen nur die Methode erstellen, die die Koordinaten der Polygonscheitelpunkte auf dem Kreis in Abhängigkeit von der Anzahl der Polygonscheitelpunkte berechnet:
Die Gleichungen der kartesischen Koordinaten des regelmäßigen Polygons:
xc und yc seien die Mittelpunktskoordinaten, R sei der Radius eines Kreises, der ein regelmäßiges Polygon umschreibt, und ϕ0 sei die Winkelkoordinate des ersten Scheitelpunkts relativ zum Mittelpunkt. In diesem Fall werden die kartesischen Koordinaten der Scheitelpunkte eines regelmäßigen n-Ecks durch die folgenden Gleichungen bestimmt:
wobei i Werte von 0 bis n-1. annimmt.
In \MQL5\Include\DoEasy\Objects\Graph\Animations\ erstellen wir die neue Datei FrameGeometry.mqh der Klasse CFrameGeometry.
Die Datei sollte die Klassendatei des Animationsrahmenobjekts enthalten, und die Klasse sollte von ihr abgeleitet werden:
//+------------------------------------------------------------------+ //| FrameGeometry.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Frame.mqh" //+------------------------------------------------------------------+ //| Class of a single rectangular animation frame | //+------------------------------------------------------------------+ class CFrameGeometry : public CFrame { }
Kommen wir zur Definition des Klassenkörpers in seiner Gesamtheit, wobei alle Klassenvariablen und die virtuelle Methode zum Speichern und Wiederherstellen des Bildhintergrunds im privaten Bereich deklariert werden (ich habe die Methode oben im Zusammenhang mit der Klasse der rechteckigen Animationsobjekte besprochen, sie überträgt einfach sich wiederholende Codeblöcke aus den in den vorherigen Artikeln betrachteten Methoden zum Zeichnen von Figuren). Die Methode zur Berechnung der regelmäßigen Polygonkoordinaten wird ebenfalls im privaten Bereich deklariert.
Der öffentliche Teil der Klasse enthält die Konstruktoren (die Standard- und die parametrischen) und die Methoden zum Zeichnen regelmäßiger Polygone — einfache, gefüllte und solche mit Glättung:
//+------------------------------------------------------------------+ //| Class of a single rectangular animation frame | //+------------------------------------------------------------------+ class CFrameGeometry : public CFrame { private: double m_square_x; // X coordinate of the square enclosing the shape double m_square_y; // Y coordinate of the square enclosing the shape uint m_square_length; // Length of the sides of the square enclosing the shape int m_shift_x; // Offset of the X coordinate of the square enclosing the shape int m_shift_y; // Offset of the Y coordinate of the square enclosing the shape int m_array_x[]; // Array of shape X coordinates int m_array_y[]; // Array of shape Y coordinates //--- Save and restore the background under the image virtual bool SaveRestoreBG(void); //--- Calculate coordinates of the regular polygon built in a circumscribed circle inscribed in a square void CoordsNgon(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left square angle the circle will be inscribed into const int coord_y, // Y coordinate of the upper-left square angle whose inscribed circle is used to build a polygon const int len, // Square sides length const double angle); // Polygon rotation angle (the polygon is built from the point 0 to the right of the circle center) public: //--- Constructors CFrameGeometry() {;} CFrameGeometry(const int id,CGCnvElement *element) : CFrame(id,0,0,0,0,element) { ::ArrayResize(this.m_array_x,0); ::ArrayResize(this.m_array_y,0); this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP; this.m_square_x=0; this.m_square_y=0; this.m_square_length=0; this.m_shift_x=0; this.m_shift_y=0; } //--- Destructor ~CFrameGeometry() { ::ArrayFree(this.m_array_x); ::ArrayFree(this.m_array_y); } //+------------------------------------------------------------------+ //| Methods of drawing regular polygons | //+------------------------------------------------------------------+ //--- Draw a regular polygon without smoothing bool DrawNgonOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false); // Chart redraw flag //--- Draw a regular filled polygon bool DrawNgonFillOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false); // Chart redraw flag //--- Draw a regular polygon using AntiAliasing algorithm bool DrawNgonAAOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX); // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value //--- Draw a regular polygon using Wu algorithm bool DrawNgonWuOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX); // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value //--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms. //--- First, individual segments are smoothed based on Bezier curves. //--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. bool DrawNgonSmoothOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND);// Line style is one of the ENUM_LINE_END enumeration's values //--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration bool DrawNgonThickOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND); // line ends style }; //+------------------------------------------------------------------+
Werfen wir einen Blick auf die Implementierung einiger Klassenmethoden.
Die Methode zum Zeichnen eines regelmäßigen Polygons:
//+------------------------------------------------------------------+ //| Draw a regular polygon | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonOnBG(const int N, const int coord_x, const int coord_y, const int len, const double angle, const color clr, const uchar opacity=255, const bool redraw=false) { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygon(this.m_array_x,this.m_array_y,clr,opacity); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Die Methode unterscheidet sich von ähnlichen Polygon-Zeichnungsmethoden der vorherigen Klassen (rechteckige Animationsrahmenklasse) nur dadurch, dass die zuvor vorbereiteten Arrays mit den Koordinaten der Polygonscheitelpunkte hier nicht übergeben werden. Stattdessen erhält die Methode die Anzahl der Polygonscheitelpunkte und die Koordinaten des oberen linken Winkels des quadratischen Rahmens, in den das Polygon gezeichnet wird. Die Methode berechnet die Koordinaten der Polygonscheitelpunkte durch die Anzahl der Scheitelpunkte, die Koordinaten, den Kreisradius und den Drehwinkel, und füllt die Arrays der X- und Y-Scheitelpunktkoordinaten aus, die in der Methode aufgerufen werden. Das Polygon, das der Methode entspricht, wird dann einfach mit der CCanvas-Klasse gezeichnet.
Schauen wir uns zum Vergleich die Methode an, die ein gefülltes Polygon zeichnet:
//+------------------------------------------------------------------+ //| Draw a regular filled polygon | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonFillOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false) // Chart redraw flag { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonFill(this.m_array_x,this.m_array_y,clr,opacity); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Der Unterschied zur ersten Methode besteht nur im Aufruf der Methode zum Zeichnen eines gefüllten Polygons.
Die übrigen Methoden sind fast identisch mit den beiden oben besprochenen, abgesehen von einigen Besonderheiten bei der Berechnung der Koordinaten des Umrissrechtecks für das Zeichnen eines Polygons mit einer bestimmten Linienbreite. Dort sollte man die Breite der gezeichneten Linie besprechen, wenn man die Koordinaten und die Größe des umschließenden Rechtecks berechnet.
Die übrigen Methoden zum Zeichnen von regelmäßigen Polygonen:
//+------------------------------------------------------------------+ //| Draw a regular filled polygon | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonFillOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false) // Chart redraw flag { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonFill(this.m_array_x,this.m_array_y,clr,opacity); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //| Draw a regular polygon using | //| AntiAliasing algorithm | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonAAOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonAA(this.m_array_x,this.m_array_y,clr,opacity,style); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //| Draw a regular polygon using | //| Wu algorithm | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonWuOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonWu(this.m_array_x,this.m_array_y,clr,opacity,style); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //| Draw a regular polygon of a specified width | //| using two smoothing algorithms in series. | //| First, individual segments are smoothed based on Bezier curves. | //| Then, to improve the rendering quality, | //| a raster smoothing algorithm is applied | //| made of these segments. | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonSmoothOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonSmooth(this.m_array_x,this.m_array_y,size,clr,opacity,tension,step,style,end_style); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //| Draw a regular polygon with a specified width using | //| a smoothing algorithm with the preliminary sorting | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonThickOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style { //--- Calculate the adjustment of the outlining rectangle coordinates depending on the line size int correct=int(::ceil((double)size/2.0))+1; //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-correct; this.m_square_y=coord_y-correct; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+correct*2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonThick(this.m_array_x,this.m_array_y,size,clr,opacity,style,end_style); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Die virtuelle Methode zum Speichern und Wiederherstellen des Hintergrunds eins Bildes:
//+------------------------------------------------------------------+ //| Save and restore the background under the image | //+------------------------------------------------------------------+ bool CFrameGeometry::SaveRestoreBG(void) { //--- Calculate coordinate offsets for the saved area depending on the anchor point this.m_element.GetShiftXYbySize(this.m_square_length,this.m_square_length,FRAME_ANCHOR_LEFT_TOP,this.m_shift_x,this.m_shift_y); //--- If the pixel array is not empty, the background under the image has already been saved - //--- restore the previously saved background (by the previous coordinates and offsets) if(::ArraySize(this.m_array)>0) { if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev))) return false; } //--- Return the result of saving the background area with the calculated coordinates and size under the future image return CPixelCopier::CopyImgDataToArray(int(this.m_square_x+this.m_shift_x),int(this.m_square_y+this.m_shift_y),this.m_square_length,this.m_square_length); } //+------------------------------------------------------------------+
Dies ist ein verschobener, wiederholter Codeblock aus den Methoden zum Zeichnen von Figuren aus den vorherigen Artikeln.
Die Methode berechnet die Koordinaten des regelmäßigen Polygons, das in einen Kreis eingeschrieben ist:
//+------------------------------------------------------------------+ //| Calculate the coordinates of the regular polygon | //+------------------------------------------------------------------+ void CFrameGeometry::CoordsNgon(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left square angle the circle will be inscribed into const int coord_y, // Y coordinate of the upper-left square angle whose inscribed circle is used to build a polygon const int len, // Length of the sides of the square a polygon is to be inscribed into const double angle) // Polygon rotation angle (the polygon is built from the point 0 to the right of the circle center) { //--- If there are less than three sides, there will be three int n=(N<3 ? 3 : N); //--- Set the size of coordinate arrays according to the number of vertices ::ArrayResize(this.m_array_x,n); ::ArrayResize(this.m_array_y,n); //--- Calculate the radius of the circumscribed circle double R=(double)len/2.0; //--- X and Y coordinates of the circle center double xc=coord_x+R; double yc=coord_y+R; //--- Calculate the polygon inclination angle in degrees double grad=angle*M_PI/180.0; //--- In the loop by the number of vertices, calculate the coordinates of each next polygon vertex for(int i=0; i<n; i++) { //--- Angle of the current polygon vertex with the rotation in degrees double a=2.0*M_PI*i/n+grad; //--- X and Y coordinates of the current polygon vertex double xi=xc+R*::cos(a); double yi=yc+R*::sin(a); //--- Set the current coordinates to the arrays this.m_array_x[i]=int(::floor(xi)); this.m_array_y[i]=int(::floor(yi)); } } //+------------------------------------------------------------------+
Die Logik der Methode ist in den Code-Kommentaren detailliert beschrieben. Die Gleichungen zur Berechnung der kartesischen Koordinaten des Polygons:
Ich überlasse die Methode dem Selbststudium. Ich glaube, da ist alles klar. Wenn Sie Fragen haben, können Sie diese gerne im Kommentarteil unten stellen.
Die Klasse des geometrischen Animationsrahmenobjekts ist fertig.
Nun müssen wir den Zugriff darauf von einem externen Programm aus ermöglichen und die Möglichkeit schaffen, schnell Objekte dieser Klasse zu erstellen.
Alle neu erstellten Animationsframe-Objekte werden in einer eigenen Liste in der Klasse CAnimations gespeichert.
Lassen Sie uns die notwendigen Verbesserungen in der Klassendatei \MQL5\Include\DoEasy\Objects\Graph\Animations\Animations.mqh vornehmen.
Wir binden die Datei mit der neu erstellten geometrischen Animationsrahmenobjektklasse in die Klassendatei ein und deklarieren die Liste, die alle neu erstellten Klassenobjekte speichern soll, im privaten Abschnitt der Klasse:
//+------------------------------------------------------------------+ //| Animations.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "FrameText.mqh" #include "FrameQuad.mqh" #include "FrameGeometry.mqh" //+------------------------------------------------------------------+ //| Pixel copier class | //+------------------------------------------------------------------+ class CAnimations : public CObject { private: CGCnvElement *m_element; // Pointer to the graphical element CArrayObj m_list_frames_text; // List of text animation frames CArrayObj m_list_frames_quad; // List of rectangular animation frames CArrayObj m_list_frames_geom; // List of geometric shape animations frames //--- Return the flag indicating the presence of the frame object with the specified ID in the list bool IsPresentFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id); //--- Return or create a new animation frame object CFrame *GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new); public:
Im öffentlichen Teil der Klasse deklarieren wir die Methode zur Erstellung eines neuen Objekts des geometrischen Animationsrahmens und schreiben die Methode, die den Zeiger auf die Liste dieser Objekte zurückgibt:
public: CAnimations(CGCnvElement *element); CAnimations(){;} //--- Create a new (1) rectangular, (2) text and geometric animation frame object CFrame *CreateNewFrameText(const int id); CFrame *CreateNewFrameQuad(const int id); CFrame *CreateNewFrameGeometry(const int id); //--- Return the animation frame objects by ID CFrame *GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id); //--- Return the list of (1) text, (2) rectangular and (3) geometric shape animation frames CArrayObj *GetListFramesText(void) { return &this.m_list_frames_text; } CArrayObj *GetListFramesQuad(void) { return &this.m_list_frames_quad; } CArrayObj *GetListFramesGeometry(void) { return &this.m_list_frames_geom; }
Als Nächstes deklarieren wir die Methoden zum Zeichnen von regelmäßigen Polygonen:
//+------------------------------------------------------------------+ //| Methods of drawing regular polygons | //+------------------------------------------------------------------+ //--- Draw a regular polygon without smoothing bool DrawNgonOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false); // Chart redraw flag //--- Draw a regular filled polygon bool DrawNgonFillOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false); // Chart redraw flag //--- Draw a regular polygon using AntiAliasing algorithm bool DrawNgonAAOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX); // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value //--- Draw a regular polygon using Wu algorithm bool DrawNgonWuOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX); // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value //--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms. //--- First, individual segments are smoothed based on Bezier curves. //--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. bool DrawNgonSmoothOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND);// Line style is one of the ENUM_LINE_END enumeration's values //--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration bool DrawNgonThickOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND); // line ends style }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+
Alle Vorkommen von "ENUM_TEXT_ANCHOR" im Code der Klasse sollten durch "ENUM_FRAME_ANCHOR" ersetzt werden.
Die Behandlung eines neuen Typs des Animationsrahmenobjekts in der Methode, die das Animationsrahmenobjekt nach Typ und ID zurückgibt, hinzufügen:
//+------------------------------------------------------------------+ //| Return the animation frame objects by type and ID | //+------------------------------------------------------------------+ CFrame *CAnimations::GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id) { //--- Declare the pointer to the animation frame object CFrame *frame=NULL; //--- Depending on the necessary object type, receive their number in the appropriate list int total= ( frame_type==ANIMATION_FRAME_TYPE_TEXT ? this.m_list_frames_text.Total() : frame_type==ANIMATION_FRAME_TYPE_QUAD ? this.m_list_frames_quad.Total() : frame_type==ANIMATION_FRAME_TYPE_GEOMETRY ? this.m_list_frames_geom.Total() : 0 ); //--- Get the next object in the loop ... for(int i=0;i<total;i++) { //--- ... by the list corresponding to the animation frame type switch(frame_type) { case ANIMATION_FRAME_TYPE_TEXT : frame=this.m_list_frames_text.At(i); break; case ANIMATION_FRAME_TYPE_QUAD : frame=this.m_list_frames_quad.At(i); break; case ANIMATION_FRAME_TYPE_GEOMETRY : frame=this.m_list_frames_geom.At(i); break; default: break; } //--- if failed to get the pointer, move on to the next one if(frame==NULL) continue; //--- If the object ID correspond to the required one, //--- return the pointer to the detected object if(frame.ID()==id) return frame; } //--- Nothing is found - return NULL return NULL; } //+------------------------------------------------------------------+
Die Methode zur Erstellung eines neuen geometrischen Animationsrahmenobjekts:
//+------------------------------------------------------------------+ //| Create a new geometric animation frame object | //+------------------------------------------------------------------+ CFrame *CAnimations::CreateNewFrameGeometry(const int id) { //--- If the object with such an ID is already present, inform of that in the journal and return NULL if(this.IsPresentFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id)) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_FRAME_ALREADY_IN_LIST),(string)id); return NULL; } //--- Create a new geometric animation frame object with the specified ID CFrame *frame=new CFrameGeometry(id,this.m_element); //--- If failed to create an object, inform of that and return NULL if(frame==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_FRAME)); return NULL; } //--- If failed to add the created object to the list, inform of that, remove the object and return NULL if(!this.m_list_frames_geom.Add(frame)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST)," ID: ",id); delete frame; return NULL; } //--- Return the pointer to a newly created object return frame; } //+------------------------------------------------------------------+
Die Logik der Methode ist in den Code-Kommentaren vollständig beschrieben.
Behandlung eines neuen Animationsrahmen-Typs in der Methode, die ein neues Animationsrahmen-Objekt zurückgibt oder erzeugt:
//+------------------------------------------------------------------+ //| Return or create a new animation frame object | //+------------------------------------------------------------------+ CFrame *CAnimations::GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new) { //--- Declare null pointers to objects CFrameQuad *frame_q=NULL; CFrameText *frame_t=NULL; CFrameGeometry *frame_g=NULL; //--- Depending on the required object type switch(frame_type) { //--- If this is a text animation frame, case ANIMATION_FRAME_TYPE_TEXT : //--- get the pointer to an object with a specified ID frame_t=this.GetFrame(ANIMATION_FRAME_TYPE_TEXT,id); //--- If the pointer is obtained, return it if(frame_t!=NULL) return frame_t; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new text animation frame object (pointer to the created object) return this.CreateNewFrameText(id); //--- If this is a rectangular animation frame case ANIMATION_FRAME_TYPE_QUAD : //--- get the pointer to an object with a specified ID frame_q=this.GetFrame(ANIMATION_FRAME_TYPE_QUAD,id); //--- If the pointer is obtained, return it if(frame_q!=NULL) return frame_q; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new rectangular animation frame object (pointer to the created object) return this.CreateNewFrameQuad(id); //--- If this is a geometric animation frame case ANIMATION_FRAME_TYPE_GEOMETRY : //--- get the pointer to an object with a specified ID frame_g=this.GetFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id); //--- If the pointer is obtained, return it if(frame_g!=NULL) return frame_g; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new geometric animation frame object (pointer to the created object) return this.CreateNewFrameGeometry(id); //--- In the remaining cases, return NULL default: return NULL; } } //+------------------------------------------------------------------+
Wie üblich ist die gesamte Logik hier in den Codekommentaren beschrieben.
Ganz am Ende des Codes der Klasse implementieren wir die Methoden zum Zeichnen von regelmäßigen Polygonen:
//+------------------------------------------------------------------+ //| Draw a regular polygon without smoothing | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false) // Chart redraw flag { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw); } //+------------------------------------------------------------------+ //| Draw a regular filled polygon | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonFillOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false) // Chart redraw flag { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonFillOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw); } //+------------------------------------------------------------------+ //| Draw a regular polygon using | //| AntiAliasing algorithm | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonAAOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonAAOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw,style); } //+------------------------------------------------------------------+ //| Draw a regular polygon using | //| Wu algorithm | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonWuOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonWuOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw,style); } //+------------------------------------------------------------------+ //| Draw a regular polygon of a specified width | //| using two smoothing algorithms in series. | //| First, individual segments are smoothed based on Bezier curves. | //| Then, to improve the rendering quality, | //| a raster smoothing algorithm is applied | //| to the polygon made of these segments. | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonSmoothOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonSmoothOnBG(N,coord_x,coord_y,len,angle,size,clr,opacity,tension,step,redraw,style,end_style); } //+------------------------------------------------------------------+ //| Draw a regular polygon with a specified width using | //| a smoothing algorithm with the preliminary sorting | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonThickOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonThickOnBG(N,coord_x,coord_y,len,angle,size,clr,opacity,redraw,style,end_style); } //+------------------------------------------------------------------+
Die Logik all dieser Methoden ist absolut identisch, daher wollen wir die letzte Methode als Beispiel verwenden.
Wie wir sehen können, ist hier alles ganz einfach: Zuerst holen wir entweder das fertige geometrische Animationsrahmen-Objekt aus der Liste, oder wir erstellen es, wenn es nicht in der Liste ist. Wenn das Flag zur Erstellung eines neuen Objekts aktiviert ist und wir das Objekt nicht bekommen oder erstellen können, geben wir false zurück.
Andernfalls wird das Ergebnis des Aufrufs der gleichnamigen Methode der geometrischen Animationsrahmen-Objektklasse zurückgegeben, die aus der Liste erhalten oder von Grund auf neu erstellt wurde.
Verbessern wir nun die Formularobjektklasse in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Alle Vorkommen von "ENUM_TEXT_ANCHOR" im Code der Klasse sollten durch "ENUM_FRAME_ANCHOR" ersetzt werden.
Im privaten Abschnitt der Klasse deklarieren wir die Methoden zum Zurücksetzen der Größe der Pixel-Arrays der drei Animationsrahmenklassen:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object color m_color_frame; // Form frame color 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); //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Return the name of the dependent object
Dies ist für das korrekte Funktionieren der Methoden zum Speichern und Wiederherstellen des Hintergrunds des Formulars, in dem die Figuren gezeichnet werden, notwendig (dies wurde oben besprochen).
Im öffentlichen Teil der Klasse schreiben wir die Methode zur Erfassung des Erscheinungsbildes des Formulars und deklarieren die virtuelle Methode zur Wiederherstellung der grafischen Ressource aus dem Array:
//--- Draw an embossed (concave) field void DrawFieldStamp(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Field width const int height, // Field height const color colour, // Field color const uchar opacity); // Field opacity //--- Capture the appearance of the created form void Done(void) { CGCnvElement::CanvasUpdate(false); CGCnvElement::ResourceStamp(DFUN); } //--- Restore the resource from the array virtual bool Reset(void); //+------------------------------------------------------------------+ //| Methods of working with image pixels | //+------------------------------------------------------------------+
Warum brauchen wir die Methode zur Erfassung des Erscheinungsbildes des Formulars?
Angenommen, wir haben das Formular erstellt und alle notwendigen unveränderlichen Elemente darauf gezeichnet. Nun müssen wir das neu erstellte Erscheinungsbild des Formulars in das Array "copy" der grafischen Ressource kopieren, damit wir bei Bedarf das ursprüngliche Erscheinungsbild des Formulars zurückgeben können. Alle Änderungen im Formular werden exakt in der grafischen Ressource angezeigt. Um ein erneutes Zeichnen des Formulars zu vermeiden, sollten wir einfach die Kopie des ursprünglich erstellten Formulars in einem speziellen Array speichern, aus dem wir jederzeit das ursprüngliche Aussehen wiederherstellen können. Die Methode Reset() tut genau das.
Die Methoden zum Zeichnen von regelmäßigen Polygonen schreiben wir in den öffentlichen Teil der Klasse:
//--- Draw a regular polygon without smoothing bool DrawNgonOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false) // Chart redraw flag { return(this.m_animations!=NULL ? this.m_animations.DrawNgonOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw) : false); } //--- Draw a regular filled polygon bool DrawNgonFillOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false) // Chart redraw flag { return(this.m_animations!=NULL ? this.m_animations.DrawNgonFillOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw) : false); } //--- Draw a regular polygon using AntiAliasing algorithm bool DrawNgonAAOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { return(this.m_animations!=NULL ? this.m_animations.DrawNgonAAOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw,style) : false); } //--- Draw a regular polygon using Wu algorithm bool DrawNgonWuOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { return(this.m_animations!=NULL ? this.m_animations.DrawNgonWuOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw,style) : false); } //--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms. //--- First, individual segments are smoothed based on Bezier curves. //--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. bool DrawNgonSmoothOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values { return(this.m_animations!=NULL ? this.m_animations.DrawNgonSmoothOnBG(id,N,coord_x,coord_y,len,angle,size,clr,opacity,tension,step,create_new,redraw,style,end_style) : false); } //--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration bool DrawNgonThickOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style { return(this.m_animations!=NULL ? this.m_animations.DrawNgonThickOnBG(id,N,coord_x,coord_y,len,angle,size,clr,opacity,create_new,redraw,style,end_style) : false); }
Alle Methoden sind identisch und geben das Ergebnis des Aufrufs der entsprechenden Methoden der CAnimations-Klasseninstanz zurück, die ich oben besprochen habe.
Implementieren wir die deklarierten Methoden außerhalb des Klassenkörpers.
Drei Methoden, die die Größen der Arrays von drei Animationsframe-Objekten zurücksetzen:
//+------------------------------------------------------------------+ //| Reset the array size of the text animation frames | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameT(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesText(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameText *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+ //| Reset the size of the rectangular animation frame array | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameQ(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesQuad(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameQuad *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+ //| Reset the size of the geometric animation frame array | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameG(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesGeometry(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameGeometry *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+
Alle Methoden sind identisch zueinander:
Wenn das Objekt der Klasse CAnimation nicht existiert, verlasse die Methode. Das Objekt hat keine Animationen.
Erhalte den Zeiger auf die Liste der Animationsbilder, die der Methode entspricht. In der Schleife durch die erhaltene Liste, holen wir uns den Zeiger auf das nächste Animationsrahmen-Objekt und setzen seines Pixel-Arrays zurück auf Null.
Die Methode, die die Ressource aus dem Array wiederherstellt:
//+------------------------------------------------------------------+ //| Restore the resource from the array | //+------------------------------------------------------------------+ bool CForm::Reset(void) { CGCnvElement::Reset(); this.ResetArrayFrameQ(); this.ResetArrayFrameT(); this.ResetArrayFrameG(); return true; } //+------------------------------------------------------------------+
Zuerst rufen wir die Methode der übergeordneten Klasse auf, die die grafische Ressource aus dem Copy Array wiederherstellt. Dann setzen der Pixel-Arrays aller Animationsframe-Objekte zurück, sodass wir in der Lage sind, den Hintergrund mit den notwendigen Koordinaten und der Größe des gespeicherten Hintergrundbereichs zu kopieren, nachdem wir das Aussehen des Formulars wiederhergestellt haben.
Wir sind nun bereit, das Zeichnen von regelmäßigen Polygonen auf dem Formular zu testen.
Test
Um den Test durchzuführen, verwenden wir den EA aus dem vorigen Artikel und speichern ihn in \MQL5\Experts\TestDoEasy\Part80\ als TestDoEasyPart80.mq5.
Im vorigen Artikel haben wir Figuren auf dem Formularobjekt durch Drücken von Tasten gezeichnet. Ich schlage vor, dies auch hier zu tun. Weisen Sie einfach die Tasten neu zu, die Polygone zeichnen, deren Koordinaten und Größe dynamisch festgelegt werden. Hier werde ich auch die Koordinaten des Animationsrahmens entlang der X-Achse und die Anzahl der Scheitelpunkte des gezeichneten Polygons (von 3 bis 10) dynamisch ändern.
- Y - ungeglättetes regelmäßiges Polygon,
- U - ungeglättetes gefülltes Polygon,
- I - regelmäßiges Polygon mit AntiAlliasing (AA),
- O - regelmäßiges Polygon mit Wu,
- P - regelmäßiges Polygon mit einer bestimmten Breite und zwei Glättungsalgorithmen (Smooth),
- A - regelmäßiges Polygon einer bestimmten Breite, auf das eine Glättung mit Vorsortierung angewendet wird (Dick),
- . - einen gefüllten Bereich zeichnen. Das bedeutet, dass das gesamte Formular mit einer bestimmten Farbe gefüllt wird.
Jeder Klick auf das Formular ändert die X-Koordinate des gezeichneten Rahmens und erhöht die Anzahl der gezeichneten Polygonscheitelpunkte um eins.
Ersetzen Sie alle Vorkommen von "TEXT_ANCHOR" durch "FRAME_ANCHOR".
In OnInit() des EAs wird das Aussehen jedes erstellten Formulars erfasst:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables ArrayResize(array_clr,2); array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the specified number of form objects list_forms.Clear(); int total=FORMS_TOTAL; for(int i=0;i<total;i++) { int y=40; if(i>0) { CForm *form_prev=list_forms.At(i-1); if(form_prev==NULL) continue; y=form_prev.BottomEdge()+10; } //--- When creating an object, pass all the required parameters to it CForm *form=new CForm("Form_0"+(string)(i+1),300,y,100,(i<2 ? 70 : 30)); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(false); //--- Set the form ID equal to the loop index and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the partial opacity for the middle form and the full one for the rest uchar opacity=(i==1 ? 250 : 255); //--- Set the form style and its color theme depending on the loop index if(i<2) { ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i; ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i; //--- Set the form style and theme form.SetFormStyle(style,theme,opacity,true,false); } //--- If this is the first (top) form if(i==0) { //--- Draw a concave field slightly shifted from the center of the form downwards form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity()); form.Done(); } //--- If this is the second form if(i==1) { //--- Draw a concave semi-transparent "tainted glass" field in the center form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200); form.Done(); } //--- If this is the third form if(i==2) { //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity()); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- 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.TextOnBG(0,TextByLanguage("V-Градиент","V-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- If this is the fourth (bottom) form if(i==3) { //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a horizontal gradient form.Erase(array_clr,form.Opacity(),false); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- 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.TextOnBG(0,TextByLanguage("H-Градиент","H-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); } //--- Add objects to the list if(!list_forms.Add(form)) { delete form; continue; } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Im Block zur Behandlung von Tastendrücken in OnChartEvent(), implementieren wir den Aufruf der Methode zur Wiederherstellung des Erscheinungsbildes des Formulars und setzen die Arrays der Pixel des Rahmenobjekts auf Null zurück:
//--- If a key is pressed if(id==CHARTEVENT_KEYDOWN) { //--- Get a drawn shape type depending on a pressed key figure_type=FigureType(lparam); //--- If the shape type has changed if(figure_type!=figure_type_prev) { //--- Get the text of the drawn shape type description figure=FigureTypeDescription(figure_type); //--- In the loop by all forms, for(int i=0;i<list_forms.Total();i++) { //--- get the pointer to the next form object CForm *form=list_forms.At(i); if(form==NULL) continue; //--- If the form ID is 2, if(form.ID()==2) { //--- Reset all coordinate shifts to zero, restore the form background and display the text describing the drawn shape type nx1=ny1=nx2=ny2=nx3=ny3=nx4=ny4=nx5=ny5=0; form.Reset(); form.TextOnBG(0,figure,form.TextLastX(),form.TextLastY(),form.TextAnchor(),C'211,233,149',255,false,true); } } //--- Write the new shape type figure_type_prev=figure_type; } }
In der Funktion FigureType() ergänzen wir den Tastendruck von ".":
//+------------------------------------------------------------------+ //| Return the shape depending on the pressed key | //+------------------------------------------------------------------+ ENUM_FIGURE_TYPE FigureType(const long key_code) { switch((int)key_code) { //--- "1" = Dot case 49 : return FIGURE_TYPE_PIXEL; //--- "2" = Dot with AntiAlliasing case 50 : return FIGURE_TYPE_PIXEL_AA; //--- "3" = Vertical line case 51 : return FIGURE_TYPE_LINE_VERTICAL; //--- "4" = Vertical segment of a freehand line having a specified width using a smoothing algorithm case 52 : return FIGURE_TYPE_LINE_VERTICAL_THICK; //--- "5" = Horizontal line case 53 : return FIGURE_TYPE_LINE_HORIZONTAL; //--- "6" = Horizontal segment of a freehand line having a specified width using a smoothing algorithm case 54 : return FIGURE_TYPE_LINE_HORIZONTAL_THICK; //--- "7" = Freehand line case 55 : return FIGURE_TYPE_LINE; //--- "8" = Line with AntiAlliasing case 56 : return FIGURE_TYPE_LINE_AA; //--- "9" = Line with WU case 57 : return FIGURE_TYPE_LINE_WU; //--- "0" = Segment of a freehand line having a specified width using a smoothing algorithm case 48 : return FIGURE_TYPE_LINE_THICK; //--- "q" = Polyline case 81 : return FIGURE_TYPE_POLYLINE; //--- "w" = Polyline with AntiAlliasing case 87 : return FIGURE_TYPE_POLYLINE_AA; //--- "e" = Polyline with WU case 69 : return FIGURE_TYPE_POLYLINE_WU; //--- "r" = Polyline with a specified width using two smoothing algorithms case 82 : return FIGURE_TYPE_POLYLINE_SMOOTH; //--- "t" = Polyline with a specified width using a smoothing algorithm case 84 : return FIGURE_TYPE_POLYLINE_THICK; //--- "y" = Polygon case 89 : return FIGURE_TYPE_POLYGON; //--- "u" = Filled polygon case 85 : return FIGURE_TYPE_POLYGON_FILL; //--- "i" = Polygon with AntiAlliasing case 73 : return FIGURE_TYPE_POLYGON_AA; //--- "o" = Polygon with WU case 79 : return FIGURE_TYPE_POLYGON_WU; //--- "p" = Polygon with a specified width using two smoothing algorithms case 80 : return FIGURE_TYPE_POLYGON_SMOOTH; //--- "a" = Polygon with a specified width using a smoothing algorithm case 65 : return FIGURE_TYPE_POLYGON_THICK; //--- "s" = Rectangle case 83 : return FIGURE_TYPE_RECTANGLE; //--- "d" = Filled rectangle case 68 : return FIGURE_TYPE_RECTANGLE_FILL; //--- "f" = Circle case 70 : return FIGURE_TYPE_CIRCLE; //--- "g" = Filled circle case 71 : return FIGURE_TYPE_CIRCLE_FILL; //--- "h" = Circle with AntiAlliasing case 72 : return FIGURE_TYPE_CIRCLE_AA; //--- "j" = Circle with WU case 74 : return FIGURE_TYPE_CIRCLE_WU; //--- "k" = Triangle case 75 : return FIGURE_TYPE_TRIANGLE; //--- "l" = Filled triangle case 76 : return FIGURE_TYPE_TRIANGLE_FILL; //--- "z" = Triangle with AntiAlliasing case 90 : return FIGURE_TYPE_TRIANGLE_AA; //--- "x" = Triangle with WU case 88 : return FIGURE_TYPE_TRIANGLE_WU; //--- "c" = Ellipse case 67 : return FIGURE_TYPE_ELLIPSE; //--- "v" = Filled ellipse case 86 : return FIGURE_TYPE_ELLIPSE_FILL; //--- "b" = Ellipse with AntiAlliasing case 66 : return FIGURE_TYPE_ELLIPSE_AA; //--- "n" = Ellipse with WU case 78 : return FIGURE_TYPE_ELLIPSE_WU; //--- "m" = Ellipse arc case 77 : return FIGURE_TYPE_ARC; //--- "," = Ellipse sector case 188 : return FIGURE_TYPE_PIE; //--- "." = Filled area case 190 : return FIGURE_TYPE_FILL; //--- Default = Dot default : return FIGURE_TYPE_PIXEL; } } //+------------------------------------------------------------------+
In der Funktion FigureProcessing() machen wir die Koordinatenarrays beweglich:
//+------------------------------------------------------------------+ //| Handle the selected shape | //+------------------------------------------------------------------+ void FigureProcessing(CForm *form,const ENUM_FIGURE_TYPE figure_type) { int array_x[]; int array_y[]; switch(figure_type) {
and set the array size wherever it is necessary to pass the coordinate arrays to the class methods:
//--- "q" = Polyline case FIGURE_TYPE_POLYLINE : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
...
//--- "w" = Polyline with AntiAlliasing case FIGURE_TYPE_POLYLINE_AA : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
...
//--- "e" = Polyline with WU case FIGURE_TYPE_POLYLINE_WU : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
...
//--- "r" = Polyline with a specified width using two smoothing algorithms case FIGURE_TYPE_POLYLINE_SMOOTH : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
...
//--- "t" = Polyline with a specified width using a smoothing algorithm case FIGURE_TYPE_POLYLINE_THICK : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
Die Codes für die Verarbeitung von Tastatureingaben zum Zeichnen von Polygonen werden durch den Aufruf der Methoden zum Zeichnen regulärer Polygone ersetzt:
//--- "y" = Polygon case FIGURE_TYPE_POLYGON : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrAliceBlue); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "u" = Filled polygon case FIGURE_TYPE_POLYGON_FILL : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonFillOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightCoral); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "i" = Polygon with AntiAlliasing case FIGURE_TYPE_POLYGON_AA : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonAAOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightCyan); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "o" = Polygon with WU case FIGURE_TYPE_POLYGON_WU : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonWuOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightGoldenrod); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "p" = Polygon with a specified width using two smoothing algorithms case FIGURE_TYPE_POLYGON_SMOOTH : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonSmoothOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,3,clrLightGreen,255,0.5,10.0,true,false,STYLE_SOLID,LINE_END_BUTT); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "a" = Polygon with a specified width using a smoothing algorithm case FIGURE_TYPE_POLYGON_THICK : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonThickOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,5,clrLightSalmon,255,true,false,STYLE_SOLID,LINE_END_BUTT); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "s" = Rectangle
Der Code ist ausführlich kommentiert. Hier sollte also alles klar sein. Wenn Sie Fragen haben, können Sie diese gerne in den Kommentaren stellen.
Vergessen Sie nicht die Handhabung beim Drücken der "."-Taste zum Ausfüllen des Formulars mit Farbe hinzuzufügen:
//--- "." = Filled area case FIGURE_TYPE_FILL : coordX1=START_X+nx1; coordY1=START_Y+ny1; form.FillOnBG(0,coordX1,coordY1,clrLightSteelBlue,255,10); break; //--- Default = Nothing default : break; } } //+------------------------------------------------------------------+
Kompilieren Sie den EA und starten Sie ihn auf dem Chart.
Nach dem Start drücken Sie die Tasten, um regelmäßige Polygone zu zeichnen und den Bereich mit Farbe zu füllen:
Alles funktioniert wie vorgesehen. Allerdings werden die Figuren ziemlich ungleichmäßig... Meiner Meinung nach ist das Aussehen der Polygone mit dem Wu-Glättungsalgorithmus am besten. Während des Füllens können wir den Grad (Schwellenwert) der Farbfüllung einstellen, indem wir den notwendigen Schwellenwert-Parameter angeben:
form.FillOnBG(0,coordX1,coordY1,clrLightSteelBlue,255,10);
Was kommt als Nächstes?
Im nächsten Artikel werde ich die Entwicklung der Animationen und des Formularobjekts fortsetzen.
Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit der Test-EA-Datei für MQL5 zum Testen und Herunterladen angehängt.
Ihre Fragen und Vorschläge schreiben Sie bitte in den Kommentarteil.
*Frühere Artikel dieser Serie:
Grafiken in der Bibliothek DoEasy (Teil 73): Das Formularobjekt eines grafischen Elements
Grafiken in der Bibliothek DoEasy (Teil 74): Das grafisches Basiselement, das von der Klasse CCanvas unterstützt wird
Grafiken in der Bibliothek DoEasy (Teil 75): Methoden zur Handhabung von Primitiven und Text im grafischen Grundelement
Grafiken in der Bibliothek DoEasy (Teil 76): Das Formularobjekt und vordefinierte Farbschemata
Grafik in der Bibliothek DoEasy (Teil 77): Objektklasse der Schatten
Grafik in der Bibliothek DoEasy (Teil 78): Animationsprinzipien in der Bibliothek. Schneiden von Bildern
Grafik in der Bibliothek DoEasy (Teil 79): Die Objektklasse "Animationsrahmen" und ihre abgeleiteten Objekte
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/9689
![Grafik in der Bibliothek DoEasy (Teil 81): Integration von Grafiken in Bibliotheksobjekt](https://c.mql5.com/2/43/MQL5-avatar-doeasy-library3-2.png)
![Erkunden der Möglichkeiten mehrfarbige Kerzen zu erstellen](https://c.mql5.com/2/43/multicolored-candlesticks.png)
![Kombinatorik und Wahrscheinlichkeitsrechnung für den Handel (Teil II): Das universelle Fraktal](https://c.mql5.com/2/42/Centropolis2.png)
![Besser Programmieren (Teil 05): Wie man ein schnellerer Entwickler wird](https://c.mql5.com/2/43/speed__1.png)
![MQL5 - Sprache von Handelsstrategien, eingebaut ins Kundenterminal MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.