Grafiken in der Bibliothek DoEasy (Teil 100): Verbesserungen im Umgang mit erweiterten grafischen Standardobjekten

Artyom Trishkin | 26 Mai, 2022

Inhalt


Konzept

Im Laufe der letzten Artikel habe ich die Funktionsweise für die Handhabung von zusammengesetzten grafischen Objekten entwickelt, die auf der Grundlage von erweiterten grafischen Standardobjekten erstellt wurden. Wir haben uns schrittweise auf die Schaffung dieser Funktionsweise Artikel für Artikel zubewegt. Manchmal war ich gezwungen, von einem Thema zum anderen zu wechseln: Zuerst erstellte ich Objekte auf der Basis der Klasse CCanvas und begann dann mit der Erstellung von grafischen Objekten, da die geplante Funktionalität für die Einführung von grafischen Objekten in alle Bibliotheksobjekte das Vorhandensein einer zumindest teilweise funktionierenden Funktionalität von grafischen Standardobjekten erforderte. Danach kehrte ich zu Formularobjekten auf der Basis von CCanvas zurück, da fortgeschrittene grafische Objekte die Verbesserung von Klassen auf der Basis des Canvas erforderten. Jetzt müssen wir wieder weitergehen, um die Entwicklung von Objekten auf Canvas fortzusetzen.

Daher werde ich im aktuellen Artikel offensichtliche Mängel bei der gleichzeitigen Handhabung von erweiterten (und Standard-) grafischen Objekten und Formularobjekten auf Canvas beseitigen sowie Fehler beheben, die während des im vorherigen Artikel durchgeführten Tests entdeckt wurden. Der Artikel schließt diesen Teil der Bibliotheksbeschreibung ab. Der nächste Artikel wird einen neuen Abschnitt einleiten, in dem ich mit der Entwicklung von grafischen Objekten auf der Leinwand beginnen werde, die Windows Forms in MS Visual Studio imitieren. Ich werde diese Objekte benötigen, um die Entwicklung von erweiterten grafischen Standardobjekten sowie von darauf basierenden zusammengesetzten Objekten fortzusetzen.


Verbesserung der Klassenbibliothek

Wenn das Chart grafische Objekte auf dem Canvas zur Erstellung von GUI-Elementen — Element, Gestalt, Fenster (noch nicht implementiert) oder andere ähnliche Steuerelemente — enthält, führt das Platzieren von grafischen Standardobjekten sowie anderen darauf basierenden Bibliotheksobjekten auf dem Diagramm (entweder manuell oder programmatisch) dazu, dass diese Objekte über den Steuerelementen gezeichnet werden, was unpraktisch ist. Daher müssen wir einen Mechanismus entwickeln, um neue grafische Objekte im Diagramm zu verfolgen und alle GUI-Elemente in den Vordergrund zu bringen. Um dies zu erreichen, können wir die Eigenschaft ZOrder eines grafischen Objekts verwenden (Priorität eines grafischen Objekts für den Empfang des Ereignisses beim Klicken auf ein Chart (CHARTEVENT_CLICK)).

Unten finden Sie die Hilfeinformationen:

Der Standardwert ist Null, wenn ein Objekt erstellt wird, aber die Priorität kann bei Bedarf erhöht werden. Beim Übereinanderlegen von Objekten wird nur das Objekt mit der höchsten Priorität das Ereignis CHARTEVENT_CLICK erhalten.

Aber ich werde diese Eigenschaft breiter einsetzen — der Wert dieser Eigenschaft gibt die Reihenfolge an, in der GUI-Elemente relativ zueinander sowie relativ zu anderen grafischen Objekten angeordnet sind.

In der Enumeration der Integer-Eigenschaften eines grafischen Elements auf der Leinwand in der Datei \MQL5\Include\DoEasy\Defines.mqh fügen wir eine neue Eigenschaft hinzu und erhöhen die Anzahl der Integer-Eigenschaften von 23 auf 24:

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Element ID
   CANV_ELEMENT_PROP_TYPE,                            // Graphical element type
   CANV_ELEMENT_PROP_BELONG,                          // Graphical element affiliation
   CANV_ELEMENT_PROP_NUM,                             // Element index in the list
   CANV_ELEMENT_PROP_CHART_ID,                        // Chart ID
   CANV_ELEMENT_PROP_WND_NUM,                         // Chart subwindow index
   CANV_ELEMENT_PROP_COORD_X,                         // Form's X coordinate on the chart
   CANV_ELEMENT_PROP_COORD_Y,                         // Form's Y coordinate on the chart
   CANV_ELEMENT_PROP_WIDTH,                           // Element width
   CANV_ELEMENT_PROP_HEIGHT,                          // Element height
   CANV_ELEMENT_PROP_RIGHT,                           // Element right border
   CANV_ELEMENT_PROP_BOTTOM,                          // Element bottom border
   CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,                  // Active area offset from the left edge of the element
   CANV_ELEMENT_PROP_ACT_SHIFT_TOP,                   // Active area offset from the upper edge of the element
   CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,                 // Active area offset from the right edge of the element
   CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,                // Active area offset from the bottom edge of the element
   CANV_ELEMENT_PROP_MOVABLE,                         // Element moveability flag
   CANV_ELEMENT_PROP_ACTIVE,                          // Element activity flag
   CANV_ELEMENT_PROP_INTERACTION,                     // Flag of interaction with the outside environment
   CANV_ELEMENT_PROP_COORD_ACT_X,                     // X coordinate of the element active area
   CANV_ELEMENT_PROP_COORD_ACT_Y,                     // Y coordinate of the element active area
   CANV_ELEMENT_PROP_ACT_RIGHT,                       // Right border of the element active area
   CANV_ELEMENT_PROP_ACT_BOTTOM,                      // Bottom border of the element active area
   CANV_ELEMENT_PROP_ZORDER,                          // Priority of a graphical object for receiving the event of clicking on a chart
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (24)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Fügen wir die neue Eigenschaft zur Liste der möglichen Kriterien für die Sortierung von grafischen Elementen auf der Leinwand hinzu:

//+------------------------------------------------------------------+
//| Possible sorting criteria of graphical elements on the canvas    |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Sort by integer properties
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Sort by element ID
   SORT_BY_CANV_ELEMENT_TYPE,                         // Sort by graphical element type
   SORT_BY_CANV_ELEMENT_BELONG,                       // Sort by a graphical element affiliation
   SORT_BY_CANV_ELEMENT_NUM,                          // Sort by form index in the list
   SORT_BY_CANV_ELEMENT_CHART_ID,                     // Sort by chart ID
   SORT_BY_CANV_ELEMENT_WND_NUM,                      // Sort by chart window index
   SORT_BY_CANV_ELEMENT_COORD_X,                      // Sort by the element X coordinate on the chart
   SORT_BY_CANV_ELEMENT_COORD_Y,                      // Sort by the element Y coordinate on the chart
   SORT_BY_CANV_ELEMENT_WIDTH,                        // Sort by the element width
   SORT_BY_CANV_ELEMENT_HEIGHT,                       // Sort by the element height
   SORT_BY_CANV_ELEMENT_RIGHT,                        // Sort by the element right border
   SORT_BY_CANV_ELEMENT_BOTTOM,                       // Sort by the element bottom border
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT,               // Sort by the active area offset from the left edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP,                // Sort by the active area offset from the top edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT,              // Sort by the active area offset from the right edge of the element
   SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM,             // Sort by the active area offset from the bottom edge of the element
   SORT_BY_CANV_ELEMENT_MOVABLE,                      // Sort by the element moveability flag
   SORT_BY_CANV_ELEMENT_ACTIVE,                       // Sort by the element activity flag
   SORT_BY_CANV_ELEMENT_INTERACTION,                  // Sort by the flag of interaction with the outside environment
   SORT_BY_CANV_ELEMENT_COORD_ACT_X,                  // Sort by X coordinate of the element active area
   SORT_BY_CANV_ELEMENT_COORD_ACT_Y,                  // Sort by Y coordinate of the element active area
   SORT_BY_CANV_ELEMENT_ACT_RIGHT,                    // Sort by the right border of the element active area
   SORT_BY_CANV_ELEMENT_ACT_BOTTOM,                   // Sort by the bottom border of the element active area
   SORT_BY_CANV_ELEMENT_ZORDER,                       // Sort by the priority of a graphical object for receiving the event of clicking on a chart
//--- Sort by real properties

//--- Sort by string properties
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Sort by the graphical resource name
  };
//+------------------------------------------------------------------+

Jetzt können wir grafische Elemente auf der Leinwand anhand dieser Eigenschaft auswählen und sortieren.


Alle grafischen Objekte in der Bibliothek sind vom Basisobjekt aller grafischen Objekte der Bibliothek abgeleitet — standardmäßige grafische Objekte und grafische Elemente auf der Leinwand. In der Datei der grafischen Basisobjektklasse \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh werden die Methoden zur Handhabung der Eigenschaft ZOrder virtuell gemacht:

//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag
   bool              SetFlagHidden(const bool flag,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop)
                          {
                           this.m_hidden=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set the priority of a graphical object for receiving the event of clicking on a chart 
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
                          {
                           this.m_zorder=value;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set object visibility on all timeframes
   bool              SetVisible(const bool flag,const bool only_prop)   
                       {
                        long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop)
                          {
                           this.m_visible=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set visibility flags on timeframes specified as flags

...

//--- Return the values of class variables
   ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void)        const { return this.m_type_element;       }
   ENUM_GRAPH_OBJ_BELONG   Belong(void)                  const { return this.m_belong;             }
   ENUM_GRAPH_OBJ_SPECIES  Species(void)                 const { return this.m_species;            }
   ENUM_OBJECT       TypeGraphObject(void)               const { return this.m_type_graph_obj;     }
   datetime          TimeCreate(void)                    const { return this.m_create_time;        }
   string            Name(void)                          const { return this.m_name;               }
   long              ChartID(void)                       const { return this.m_chart_id;           }
   long              ObjectID(void)                      const { return this.m_object_id;          }
   virtual long      Zorder(void)                        const { return this.m_zorder;             }
   int               SubWindow(void)                     const { return this.m_subwindow;          }
   int               ShiftY(void)                        const { return this.m_shift_y;            }
   int               VisibleOnTimeframes(void)           const { return this.m_timeframes_visible; }
   int               Digits(void)                        const { return this.m_digits;             }
   int               Group(void)                         const { return this.m_group;              }
   bool              IsBack(void)                        const { return this.m_back;               }
   bool              IsSelected(void)                    const { return this.m_selected;           }
   bool              IsSelectable(void)                  const { return this.m_selectable;         }
   bool              IsHidden(void)                      const { return this.m_hidden;             }
   bool              IsVisible(void)                     const { return this.m_visible;            }

//--- Return the graphical object type (ENUM_OBJECT) calculated from the object type (ENUM_OBJECT_DE_TYPE) passed to the method

Die gleichen Methoden sind in der Basisobjektklasse aller grafischen Bibliotheksobjekte vorhanden.
Die Methoden sind in ihnen ebenfalls virtuell.

In der Datei der abstrakten grafischen Standardobjektklasse \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh:

//--- Background object
   bool              Back(void)                    const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);                          }
   bool              SetFlagBack(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagBack(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag);
                        return true;
                       }
//--- Priority of a graphical object for receiving the event of clicking on a chart
   virtual long      Zorder(void)                  const { return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,0);                              }
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        if(!CGBaseObj::SetZorder(value,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,value);
                        return true;
                       }
//--- Disable displaying the name of a graphical object in the terminal object list
   bool              Hidden(void)                  const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);                        }
   bool              SetFlagHidden(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetFlagHidden(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag);
                        return true;
                       }
//--- Object selection


In der Datei der grafischen Elementobjektklasse \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh fügen wir die gleichen virtuellen Methoden hinzu:

//--- (1) Show and (2) hide the element
   virtual void      Show(void)                                { CGBaseObj::SetVisible(true,false);                                    }
   virtual void      Hide(void)                                { CGBaseObj::SetVisible(false,false);                                   }
   
//--- Priority of a graphical object for receiving the event of clicking on a chart
   virtual long      Zorder(void)                        const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER);                    }
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        if(!CGBaseObj::SetZorder(value,only_prop))
                           return false;
                        this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value);
                        return true;
                       }
//+------------------------------------------------------------------+
//| The methods of receiving raster data                             |
//+------------------------------------------------------------------+

Da die Objektklasse des grafischen Elements bereits über die Struktur der Objekteigenschaften zum Speichern und Wiederherstellen verfügt, sollten wir das Feld für die neue Integer-Eigenschaft hinzufügen:

//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
private:
   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type
      int            number;                                   // Element index in the list
      long           chart_id;                                 // Chart ID
      int            subwindow;                                // Chart subwindow index
      int            coord_x;                                  // Form's X coordinate on the chart
      int            coord_y;                                  // Form's Y coordinate on the chart
      int            width;                                    // Element width
      int            height;                                   // Element height
      int            edge_right;                               // Element right border
      int            edge_bottom;                              // Element bottom border
      int            act_shift_left;                           // Active area offset from the left edge of the element
      int            act_shift_top;                            // Active area offset from the top edge of the element
      int            act_shift_right;                          // Active area offset from the right edge of the element
      int            act_shift_bottom;                         // Active area offset from the bottom edge of the element
      uchar          opacity;                                  // Element opacity
      color          color_bg;                                 // Element background color
      bool           movable;                                  // Element moveability flag
      bool           active;                                   // Element activity flag
      bool           interaction;                              // Flag of interaction with the outside environment
      int            coord_act_x;                              // X coordinate of the element active area
      int            coord_act_y;                              // Y coordinate of the element active area
      int            coord_act_right;                          // Right border of the element active area
      int            coord_act_bottom;                         // Bottom border of the element active area
      long           zorder;                                   // Priority of a graphical object for receiving the event of clicking on a chart
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
     };
   SData             m_struct_obj;                             // Object structure


In der Methode zur Erstellung der Objektstruktur fügen wir das Speichern einer neuen Objekteigenschaft hinzu:

//+------------------------------------------------------------------+
//| Create the object structure                                      |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Save integer properties
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                            // Element ID
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                        // Graphical element type
   this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                       // Eleemnt ID in the list
   this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID);                     // Chart ID
   this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM);                // Chart subwindow index
   this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X);                  // Form's X coordinate on the chart
   this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y);                  // Form's Y coordinate on the chart
   this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH);                      // Element width
   this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT);                    // Element height
   this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT);                 // Element right edge
   this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM);               // Element bottom edge
   this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);    // Active area offset from the left edge of the element
   this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);      // Active area offset from the top edge of the element
   this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);  // Active area offset from the right edge of the element
   this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element
   this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE);                 // Element moveability flag
   this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE);                   // Element activity flag
   this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION);         // Flag of interaction with the outside environment
   this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X);          // X coordinate of the element active area
   this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y);          // Y coordinate of the element active area
   this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT);        // Right border of the element active area
   this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM);      // Bottom border of the element active area
   this.m_struct_obj.color_bg=this.m_color_bg;                                                  // Element background color
   this.m_struct_obj.opacity=this.m_opacity;                                                    // Element opacity
   this.m_struct_obj.zorder=this.m_zorder;                                                      // Priority of a graphical object for receiving the on-chart mouse click event
//--- Save real properties

//--- Save string properties
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name
   //--- Save the structure to the uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

In der Methode der Wiederherstellung eines Objekts aus der Struktur fügen wir das Lesen der Eigenschaft zu einem Objekt hinzu:

//+------------------------------------------------------------------+
//| Create the object from the structure                             |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Save integer properties
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                 // Element ID
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                             // Graphical element type
   this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number);                            // Element index in the list
   this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id);                     // Chart ID
   this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow);                     // Chart subwindow index
   this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x);                       // Form's X coordinate on the chart
   this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y);                       // Form's Y coordinate on the chart
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width);                           // Element width
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height);                         // Element height
   this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right);                      // Element right edge
   this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom);                    // Element bottom edge
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left);         // Active area offset from the left edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top);           // Active area offset from the upper edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right);       // Active area offset from the right edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom);     // Active area offset from the bottom edge of the element
   this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable);                       // Element moveability flag
   this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active);                         // Element activity flag
   this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction);               // Flag of interaction with the outside environment
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x);               // X coordinate of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y);               // Y coordinate of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right);             // Right border of the element active area
   this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom);           // Bottom border of the element active area
   this.m_color_bg=this.m_struct_obj.color_bg;                                                  // Element background color
   this.m_opacity=this.m_struct_obj.opacity;                                                    // Element opacity
   this.m_zorder=this.m_struct_obj.zorder;                                                      // Priority of a graphical object for receiving the on-chart mouse click event
//--- Save real properties

//--- Save string properties
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name
  }
//+------------------------------------------------------------------+

Wir brauchen all dies, um das Objekt korrekt auf der Festplatte zu speichern und es in Zukunft beim Lesen und Schreiben von Bibliotheksobjekten in Dateien von der Festplatte wiederherzustellen. Dies wird notwendig sein, damit die Bibliothek beim Neustart des Terminals den Zustand des Programms und seiner Daten wiederherstellen kann, um die Arbeit normal fortzusetzen. Aber all dies wird erst später implementiert werden. In der Zwischenzeit bereiten wir lediglich die notwendige Funktionsweise vor.


Kehren wir zu der Datei der abstrakten grafischen Standardobjektklasse \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh zurück.

Da wir in Zukunft die minimalen und maximalen Koordinaten des gesamten Objekts kennen müssen, wenn wir aus den erweiterten grafischen Standardobjekten zusammengesetzte grafische Objekte erstellen, ist es jetzt an der Zeit, Methoden hinzuzufügen, die die Werte der minimalen und maximalen X- und Y-Koordinaten des grafischen Standardobjekts ermitteln und zurückgeben. Später werden wir in der Lage sein, die minimalen/maximalen Koordinaten des gesamten zusammengesetzten grafischen Objekts auf der Grundlage dieser Methoden zu ermitteln.

Wir deklarieren zwei neue Methoden im öffentlichen Teil der Klasse:

//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects
   CArrayObj        *GetListDependentObj(void)        { return &this.m_list;           }
   CGStdGraphObj    *GetDependentObj(const int index) { return this.m_list.At(index);  }
   int               GetNumDependentObj(void)         { return this.m_list.Total();    }
//--- Return the name of the dependent object by index
   string            NameDependent(const int index);
//--- Add the dependent graphical object to the list
   bool              AddDependentObj(CGStdGraphObj *obj);
//--- Change X and Y coordinates of the current and all dependent objects
   bool              ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false);
//--- Set X and Y coordinates into the appropriate pivot points of a specified subordinate object
   bool              SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj);

//--- Return (1) the minimum and (2) maximum XY screen coordinate of the specified pivot point
   bool              GetMinCoordXY(int &x,int &y);
   bool              GetMaxCoordXY(int &x,int &y);
   
//--- Return the pivot point data object
   CLinkedPivotPoint*GetLinkedPivotPoint(void)        { return &this.m_linked_pivots;  }

Schreiben wir ihre Implementierung außerhalb des Klassenkörpers.

Die Methode, die die minimale XY-Bildschirmkoordinate von allen Pivotpunkten zurückgibt:

//+------------------------------------------------------------------+
//| Return the minimum XY screen coordinate                          |
//| from all pivot points                                            |
//+------------------------------------------------------------------+
bool CGStdGraphObj::GetMinCoordXY(int &x,int &y)
  {
//--- Declare the variables storing the minimum time and maximum price
   datetime time_min=LONG_MAX;
   double   price_max=0;
//--- Depending on the object type
   switch(this.TypeGraphObject())
     {
      //--- Objects constructed based on screen coordinates
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
         //--- Write XY screen coordinates to x and y variables and return 'true'
         x=this.XDistance();
         y=this.YDistance();
         return true;
      
      //--- Objects constructed in time/price coordinates
      default                    :
         //--- In the loop by all pivot points
         for(int i=0;i<this.Pivots();i++)
           {
            //--- Define the minimum time
            if(this.Time(i)<time_min)
               time_min=this.Time(i);
            //--- Define the maximum price
            if(this.Price(i)>price_max)
               price_max=this.Price(i);
           }
         //--- Return the result of converting time/price coordinates into XY screen coordinates
         return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_min,price_max,x,y) ? true : false);
     }
   return false;
  }
//+------------------------------------------------------------------+

Bei Objekten, die in Bildschirmkoordinaten konstruiert wurden, ist es nicht notwendig, nach der Maximum/Minimum-Koordinate zu suchen, da solche Objekte einen einzigen Chart-Ankerpunkt haben, der bereits in den Bildschirmkoordinaten enthalten ist — wir sollten sie einfach zurückgeben.
Umgekehrt müssen wir bei Objekten, die in Zeit-/Preiskoordinaten konstruiert wurden, diese Koordinaten über die Funktion ChartTimePriceToXY() ermitteln. Zunächst muss jedoch die minimale Zeitkoordinate aus allen Pivotpunkten des Objekts und die maximale Preiskoordinate aus denselben Pivotpunkten ermittelt werden. Warum suchen wir nach der maximalen Preiskoordinate, wenn wir die minimale Koordinate im Chart benötigen? Die Antwort ist einfach: Der Preis auf dem Chart steigt von unten nach oben, während die Koordinaten in Pixeln auf dem Chart von der linken oberen Ecke aus gezählt werden. Je höher also der Preis ist, desto kleiner ist der Wert der Pixelkoordinate für diesen Preispunkt.
In der Schleife durch alle Pivotpunkte finden wir die minimale Zeit und den maximalen Preis, die wir schließlich an die Funktion ChartTimePriceToXY() übergeben, um Koordinaten in Chart-Pixeln zu erhalten.

Die ähnliche Methode wird verwendet, um die maximale Bildschirm-XY-Koordinate von allen Pivot-Punkten zu erhalten:

//+------------------------------------------------------------------+
//| Return the maximum XY screen coordinate                          |
//| from all pivot points                                            |
//+------------------------------------------------------------------+
bool CGStdGraphObj::GetMaxCoordXY(int &x,int &y)
  {
//--- Declare the variables that store the maximum time and minimum price
   datetime time_max=0;
   double   price_min=DBL_MAX;
//--- Depending on the object type
   switch(this.TypeGraphObject())
     {
      //--- Objects constructed based on screen coordinates
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
         //--- Write XY screen coordinates to x and y variables and return 'true'
         x=this.XDistance();
         y=this.YDistance();
         return true;
      
      //--- Objects constructed in time/price coordinates
      default                    :
         //--- In the loop by all pivot points
         for(int i=0;i<this.Pivots();i++)
           {
            //--- Define the maximum time
            if(this.Time(i)>time_max)
               time_max=this.Time(i);
            //--- Define the maximum price
            if(this.Price(i)<price_min)
               price_min=this.Price(i);
           }
         //--- Return the result of converting time/price coordinates into XY screen coordinates
         return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_max,price_min,x,y) ? true : false);
     }
   return false;
  }
//+------------------------------------------------------------------+

Die Methode ähnelt der vorherigen, nur dass wir hier nach der maximalen Zeit und dem minimalen Preis suchen.

Wie ich bereits erwähnt habe, werde ich diese Methoden später bei der Arbeit mit zusammengesetzten grafischen Objekten benötigen.


In der Klasse des erweiterten Standard-Toolkits für grafische Objekte \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh muss die Methode, die die X- und Y-Koordinaten des angegebenen Drehpunkts des grafischen Objekts in Bildschirmkoordinaten zurückgibt, leicht angepasst werden. Der Grund dafür ist, dass wir die grafischen Objekte durch die "switch"-Operator-Fälle so verteilen müssen, dass jeder Fall grafische Objekte nach der Anzahl der Pivot-Punkte für Objekte enthält, die mit Zeit-/Preiskoordinaten konstruiert wurden. Verteilen wir sie also in der richtigen Reihenfolge:

//+------------------------------------------------------------------+
//| Return the X and Y coordinates of the specified pivot point      |
//| of the graphical object in screen coordinates                    |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y)
  {
//--- Declare form objects, from which we are to receive their screen coordinates
   CFormControl *form0=NULL, *form1=NULL;
//--- Set X and Y to zero - these values will be received in case of a failure
   x=0;
   y=0;
//--- Depending on the graphical object type
   switch(this.m_base_type)
     {
      //--- Objects drawn using screen coordinates
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        //--- Write object screen coordinates and return 'true'
        x=this.m_base_x;
        y=this.m_base_y;
        return true;
      
      //--- One pivot point (price only)
      case OBJ_HLINE             : break;
      
      //--- One pivot point (time only)
      case OBJ_VLINE             :
      case OBJ_EVENT             : break;
      
      //--- One reference point (time/price)
      case OBJ_TEXT              :
      case OBJ_BITMAP            : break;
      
      //--- Two pivot points and a central one
      //--- Lines
      case OBJ_TREND             :
      case OBJ_TRENDBYANGLE      :
      case OBJ_CYCLES            :
      case OBJ_ARROWED_LINE      :
      //--- Channels
      case OBJ_CHANNEL           :
      case OBJ_STDDEVCHANNEL     :
      case OBJ_REGRESSION        :
      //--- Gann
      case OBJ_GANNLINE          :
      case OBJ_GANNGRID          :
      //--- Fibo
      case OBJ_FIBO              :
      case OBJ_FIBOTIMES         :
      case OBJ_FIBOFAN           :
      case OBJ_FIBOARC           :
      case OBJ_FIBOCHANNEL       :
      case OBJ_EXPANSION         :
        //--- Calculate coordinates for forms on the line pivot points
        if(index<this.m_base_pivots)
           return(::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y) ? true : false);
        //--- Calculate the coordinates for the central form located between the line pivot points
        else
          {
           form0=this.GetControlPointForm(0);
           form1=this.GetControlPointForm(1);
           if(form0==NULL || form1==NULL)
              return false;
           x=(form0.CoordX()+this.m_shift+form1.CoordX()+this.m_shift)/2;
           y=(form0.CoordY()+this.m_shift+form1.CoordY()+this.m_shift)/2;
           return true;
          }

      //--- Channels
      case OBJ_PITCHFORK         : break;
      
      //--- Gann
      case OBJ_GANNFAN           : break;
      
      //--- Elliott
      case OBJ_ELLIOTWAVE5       : break;
      case OBJ_ELLIOTWAVE3       : break;
      
      //--- Shapes
      case OBJ_RECTANGLE         : break;
      case OBJ_TRIANGLE          : break;
      case OBJ_ELLIPSE           : break;
      
      //--- Arrows
      case OBJ_ARROW_THUMB_UP    : break;
      case OBJ_ARROW_THUMB_DOWN  : break;
      case OBJ_ARROW_UP          : break;
      case OBJ_ARROW_DOWN        : break;
      case OBJ_ARROW_STOP        : break;
      case OBJ_ARROW_CHECK       : break;
      case OBJ_ARROW_LEFT_PRICE  : break;
      case OBJ_ARROW_RIGHT_PRICE : break;
      case OBJ_ARROW_BUY         : break;
      case OBJ_ARROW_SELL        : break;
      case OBJ_ARROW             : break;
      
      //---
      default                    : break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Objekte, die anfänglich in Bildschirmkoordinaten gezeichnet werden, geben einfach die Bildschirmkoordinaten ihres Basisobjekts zurück (das Objekt, an das sie angehängt sind).

Objekte mit implementierter Rückgabe von Bildschirmkoordinaten haben zwei Drehpunkte. Die Bildschirmkoordinaten werden für alle Objekt-Drehpunkte und den zentralen Punkt berechnet.

Die restlichen Objekte, die noch nicht so funktionieren, werden einfach in ihre Gruppen eingeteilt (einige Objekte werden in Gruppen nach Typen und nicht nach der Anzahl der Pivotpunkte zusammengefasst, ich werde sie später implementieren).


Jetzt müssen wir sicherstellen, dass alle GUI-Steuerelemente im Chart immer über den grafischen Objekten bleiben, die neu zum Chart hinzugefügt werden. Außerdem müssen wir dafür sorgen, dass die GUI-Elemente in der gleichen Reihenfolge angeordnet werden, in der sie sich befanden, bevor ein grafisches Objekt zum Chart hinzugefügt wurde.

Um ein grafisches Objekt in den Vordergrund zu bringen, müssen wir es ausblenden und nacheinander wieder einblenden. Dies geschieht, indem man das Sichtbarkeitsflag für grafische Objekte zurücksetzt und dann einbaut. Um das Objekt auszublenden, müssen wir die ObjectSetInteger() Funktion verwenden, um das OBJ_NO_PERIODS Flag für die OBJPROP_TIMEFRAMES Eigenschaft zu setzen. Das OBJ_ALL_PERIODS-Flag wird verwendet, um das Objekt anzuzeigen. Wenn wir dies für alle GUI-Objekte in der Reihenfolge tun, in der sie sich in der Liste der grafischen Elemente befinden, verlieren wir die Reihenfolge ihrer Position auf dem Chart. Mit anderen Worten, die Objekte werden so angeordnet, dass das allererste in der Liste auf dem Chart ganz unten steht und das letzte ganz oben. Diese Objekte können aber auch in einer völlig anderen Reihenfolge angeordnet sein, die beim Neuzeichnen geändert wird. Hier wenden wir uns der neuen Eigenschaft zu, die wir heute hinzugefügt haben — ZOrder. Wir müssen die Liste der grafischen Objekte in aufsteigender Reihenfolge der ZOrder-Eigenschaft sortieren. Dann werden die Objekte in der richtigen Reihenfolge neu gezeichnet.

Öffnen Sie die Datei der Sammlungsklasse für grafische Elemente \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh und nehmen wir darin alle notwendigen Verbesserungen vor.

Wir deklarieren drei neue Methoden im privaten Abschnitt der Klasse:

//--- Event handler
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

private:
//--- Move all objects on canvas to the foreground
   void              BringToTopAllCanvElm(void);
//--- (1) Return the maximum ZOrder of all CCanvas elements,
//--- (2) Set ZOrde to the specified element and adjust it in all the remaining elements
   long              GetZOrderMax(void);
   bool              SetZOrderMAX(CGCnvElement *obj);
//--- Handle the removal of extended graphical objects
   void              DeleteExtendedObj(CGStdGraphObj *obj);

Schreiben wir ihre Implementierung außerhalb des Klassenkörpers.

Die Methode verschiebt alle Objekte auf der Leinwand in den Vordergrund:

//+------------------------------------------------------------------+
//| Move all objects on canvas to the foreground                     |
//+------------------------------------------------------------------+
void CGraphElementsCollection::BringToTopAllCanvElm(void)
  {
//--- Create a new list, reset the flag of managing memory and sort by ZOrder
   CArrayObj *list=new CArrayObj();
   list.FreeMode(false);
   list.Sort(SORT_BY_CANV_ELEMENT_ZORDER);
//--- Declare the graphical element object
   CGCnvElement *obj=NULL;
//--- In the loop by the total number of all graphical elements,
   for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++)
     {
      //--- get the next object
      obj=this.m_list_all_canv_elm_obj.At(i);
      if(obj==NULL)
         continue;
      //--- and insert it to the list in the order of sorting by ZOrder
      list.InsertSort(obj);
     }
//--- In a loop by the created list sorted by ZOrder,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next object
      obj=list.At(i);
      if(obj==NULL)
         continue;
      //--- and move it to the foreground
      obj.BringToTop();
     }
//--- Remove the list sorted by 'new'
   delete list;
  }
//+------------------------------------------------------------------+

Was wir hier also sehen. Wir deklarieren das neue Listenobjekt CArrayObj und setzen sein Speicherverwaltungsflag zurück. Als Nächstes setzen wir die Eigenschaft des Sortierflags der Liste für ZOrder. Warum tun wir das? Um die CObject-Objekte nach einer beliebigen Eigenschaft sortieren zu können, müssen wir den Eigenschaftswert als Sortierkennzeichen setzen. In diesem Fall gibt die virtuelle Methode Compare(), die in allen von der Klasse CObject abgeleiteten Objekten implementiert ist, den Wert zurück, der sich aus dem Vergleich ähnlicher Eigenschaften (die als Sortiermodus festgelegte Eigenschaft) zweier Objekte ergibt, was für die Operation der Methode Sort() erforderlich ist.

Das Zurücksetzen des Speichermanagement-Flags ist erforderlich, damit wir schließlich Kopien von Objekten aus der Kollektionsliste in dem erstellten Array erhalten und nicht nur Zeiger auf die Sammlungsobjekte. Das ist wichtig, denn wenn wir es mit den Zeigern zu tun haben, dann wird jede Änderung an ihnen in der neuen Liste automatisch das Objekt in der Kollektionsliste ändern, da es sich um die Zeiger auf dasselbe Objekt der Kollektionsliste handelt, die sich in zwei Listen befinden. Ich erstelle eine unabhängige Listenkopie, deren Änderung sich nicht auf das Original auswirkt, um solche "Überraschungen" zu vermeiden. Nach Abschluss der Methode sollten wir die erstellte Liste löschen, um Speicherlecks zu vermeiden. Dies wird auch in der Hilfe der Standardbibliothek (FreeMode) erwähnt:

Das Setzen des Speichermanagement-Flags ist ein wichtiger Bestandteil bei der Verwendung der Klasse CArrayObj. Da die Array-Elemente Zeiger auf dynamische Objekte sind, ist es wichtig zu bestimmen, was mit ihnen geschehen soll, wenn sie aus dem Array entfernt werden.

Wenn das Flag gesetzt ist und ein Element aus dem Array entfernt wird, wird das Element automatisch durch den delete-Operator gelöscht. Wenn das Flag nicht gesetzt ist, wird davon ausgegangen, dass ein Zeiger auf das gelöschte Objekt noch irgendwo im Benutzerprogramm vorhanden ist und anschließend vom Programm freigegeben wird.

Wenn das Nutzerprogramm das Flag der Speicherverwaltung zurücksetzt, muss der Benutzer seine Verantwortung für die Entfernung des Arrays vor Beendigung des Programms verstehen. Andernfalls wird der vom neuen Operator für Elemente zugewiesene Speicher nicht freigegeben.

Bei großen Datenmengen kann dies sogar zu einem Absturz des Terminals führen. Wenn der Nutzer das Speicherverwaltungsflag nicht zurücksetzt, gibt es einen weiteren Fallstrick.

Wenn Zeigerelemente in einem Array irgendwo in den lokalen Variablen gespeichert sind, dann führt das Entfernen des Arrays zu einem kritischen Fehler und zum Absturz des Nutzerprogramms. Standardmäßig ist das Speicherverwaltungsflag gesetzt, d. h. die Klasse des Arrays ist für das Freigeben der Speicherelemente verantwortlich.

Als Nächstes durchlaufen wir in einer Schleife die Kollektionsliste der grafischen Elemente und fügen das jeweils nächste Objekt in der Sortierreihenfolge in die neue Liste ein.

Nach Abschluss der Schleife erhalten wir die neue Liste sortiert in aufsteigender Reihenfolge der ZOrder-Eigenschaften aller grafischen Elemente.
In der Schleife durch die Liste holen wir das nächste Element und bringen es in den Vordergrund. Jedes nachfolgende Element wird immer höher sein als das vorherige.
Wenn die Schleife beendet ist, entferne die neu erstellte Liste.

Die Methode gibt die maximale ZOrder aller CCanvas-Elemente zurück:

//+------------------------------------------------------------------+
//| Return the maximum ZOrder of all CCanvas elements                |
//+------------------------------------------------------------------+
long CGraphElementsCollection::GetZOrderMax(void)
  {
   this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ZORDER);
   int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ZORDER);
   CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index);
   return(obj!=NULL ? obj.Zorder() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

Hier sortieren wir die Liste nach der Eigenschaft ZOrder und erhalten den Elementindex in der Liste mit dem maximalen Eigenschaftswert.
Wir holen uns den Zeiger auf das Element über den erhaltenen Index und geben die Eigenschaft ZOrder des Elements zurück.
Wenn das Element nicht empfangen wurde, gibt die Methode -1 zurück.

Wenn wir zum Beispiel drei GUI-Objekte haben, wäre es sinnvoll, drei ZOrder-Werte für sie zu haben. Alle Objekte haben zunächst einen ZOrder-Wert von Null — sie befinden sich alle ganz unten. Sobald wir ein Objekt erfassen, wird seine ZOrder um 1 erhöht. Aber zuerst sollten wir sehen, ob irgendein anderes Objekt einen ZOrder-Wert hat, der gleich oder größer als 1 ist, denn das letzte ausgewählte Objekt sollte höher als alle anderen sein und die maximal verfügbare ZOrder um 1 überschreiten. Natürlich können wir die maximale ZOrder ermitteln und sie einfach um 1 erhöhen. Aber das ist nicht die eleganteste Lösung. Stattdessen werde ich dafür sorgen, dass von den drei Objekten nur ZOrder-Werte im Bereich von 0 - 2 möglich sind.

Daher müssen wir für das letzte ausgewählte Objekt entweder die ZOrder um 1 erhöhen oder sie so hoch wie möglich lassen (um die Anzahl aller Objekte, die bei Null beginnen), während wir die ZOrder für die übrigen Objekte um 1 verringern. Wenn sich das Objekt ganz unten befindet und seine ZOrder bereits Null ist, wird sie nicht verringert. Die Änderung der ZOrder-Werte erfolgt also "on loop" entsprechend der Anzahl der GUI-Objekte.

Die Methode setzt ZOrder auf das angegebene Element und passt es in anderen Elementen an:

//+------------------------------------------------------------------+
//| Set ZOrde to the specified element                               |
//| and adjust it in other elements                                  |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj)
  {
//--- Get the maximum ZOrder of all graphical elements
   long max=this.GetZOrderMax();
//--- If an invalid pointer to the object has been passed or the maximum ZOrder has not been received, return 'false'
   if(obj==NULL || max<0)
      return false;
//--- Declare the variable for storing the method result
   bool res=true;
//--- If the maximum ZOrder is zero, ZOrder is equal to 1,
//--- if the maximum ZOrder is less than (the total number of graphical elements)-1, ZOrder will exceed it by 1,
//--- otherwise, ZOrder will be equal to (the total number of graphical elements)-1
   long value=(max==0 ? 1 : max<this.m_list_all_canv_elm_obj.Total()-1 ? max+1 : this.m_list_all_canv_elm_obj.Total()-1);
//--- If failed to set ZOrder for an object passed to the method, return 'false'
   if(!obj.SetZorder(value,false))
      return false;
//--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder
   CForm *form=obj;
//--- and draw a text specifying ZOrder on the form
   form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
//--- Sort the list of graphical elements by an element ID
   this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
//--- Get the list of graphical elements without an object whose ID is equal to the ID of the object passed to the method
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID,obj.ID(),NO_EQUAL);
//--- If failed to obtain the list and the list size exceeds one,
//--- which indicates the presence of other objects in it in addition to the one sorted by ID, return 'false'
   if(list==NULL && this.m_list_all_canv_elm_obj.Total()>1)
      return false;
//--- In the loop by the obtained list of remaining graphical element objects
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next object
      CGCnvElement *elm=list.At(i);
      //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object
      //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one
      if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0)
         continue;
      //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value
      if(!elm.SetZorder(elm.Zorder()-1,false))
         res &=false;
      //--- Temporarily (for the test purpose), if the element is a form,
      if(elm.Type()==OBJECT_DE_TYPE_GFORM)
        {
         //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form 
         form=elm;
         form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
        }
     }
//--- Upon the loop completion, return the result set in 'res'
   return res;
  }
//+------------------------------------------------------------------+

Jeder Methodenstring ist mit Kommentaren versehen, um die Logik der Methode zu verdeutlichen. Zu Testzwecken wurde eine Textanzeige auf dem Formular mit einem Hinweis auf den ZOrder-Wert hinzugefügt, damit wir die Änderung der Eigenschaften jedes Formularobjekts während der Tests visuell verfolgen können.

Es gibt zwei Fälle, in denen wir GUI-Objekte über die neu erstellten grafischen Objekte verschieben müssen:

  1. Manuelles Hinzufügen eines grafischen Standardobjekts zu einem Chart,
  2. Programmgesteuertes Hinzufügen eines grafischen Objekts (ein Standard- oder ein erweitertes zusammengesetztes Objekt).

Diese Fälle werden in der Bibliothek unabhängig voneinander behandelt.
Daher müssen wir an verschiedenen Stellen einen Aufruf einer Methode hinzufügen, die GUI-Objekte auf eine höhere Ebene hebt.

Wenn wir ein grafisches Objekt programmatisch erstellen, können wir einen Aufruf zum Neuzeichnen von GUI-Objekten direkt in einer privaten Methode platzieren, die das erstellte grafische Standardobjekt zur Liste hinzufügt. Dies ist genau die Stelle, an der der Erfolg der Erstellung eines grafischen Objekts letztendlich bestimmt wird, und wir können den Aufruf einer Methode zum Verschieben aller GUI-Objekte in den Vordergrund genau an das Ende dieser Methode setzen:

//--- Add a newly created standard graphical object to the list
   bool              AddCreatedObjToList(const string source,const long chart_id,const string name,CGStdGraphObj *obj)
                       {

                        if(!this.m_list_all_graph_obj.Add(obj))
                          {
                           CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
                           ::ObjectDelete(chart_id,name);
                           delete obj;
                           return false;
                          }
                        //--- Move all the forms above the created chart object and redraw the chart
                        this.BringToTopAllCanvElm();
                        ::ChartRedraw(chart_id);
                        return true;
                       }

Nach dem Aufruf der Methode müssen wir das Chart aktualisieren, damit die Änderungen sofort angezeigt werden.


Im Falle einer manuellen Deklaration von grafischen Objekten wird dieses Ereignis in der Methode zur Aktualisierung der Liste aller grafischen Objekte definiert.

In dem Code-Block, in dem das Hinzufügen eines neuen grafischen Objekts definiert ist, fügen wir den Aufruf der Methode hinzu, die GUI-Objekte in den Vordergrund bringt:

//+------------------------------------------------------------------+
//| Update the list of all graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
   this.RefreshForExtraObjects();
//--- Declare variables to search for charts
   long chart_id=0;
   int i=0;
//--- In the loop by all open charts in the terminal (no more than 100)
   while(i<CHARTS_MAX)
     {
      //--- Get the chart ID
      chart_id=::ChartNext(chart_id);
      if(chart_id<0)
         break;
      //--- Get the pointer to the object for managing graphical objects
      //--- and update the list of graphical objects by chart ID
      CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id);
      //--- If failed to get the pointer, move on to the next chart
      if(obj_ctrl==NULL)
         continue;
      //--- If the number of objects on the chart changes
      if(obj_ctrl.IsEvent())
        {
         //--- If a graphical object is added to the chart
         if(obj_ctrl.Delta()>0)
           {
            //--- Get the list of added graphical objects and move them to the collection list
            //--- (if failed to move the object to the collection, move on to the next object)
            if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
            //--- Move all the forms above the created chart object and redraw the chart
            this.BringToTopAllCanvElm();
            ::ChartRedraw(obj_ctrl.ChartID());
           }
         //--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<0)
           {
            int index=WRONG_VALUE;
            //--- In the loop by the number of removed graphical objects
            for(int j=0;j<-obj_ctrl.Delta();j++)
              {
               // Find an extra object in the list
               CGStdGraphObj *obj=this.FindMissingObj(chart_id,index);
               if(obj!=NULL)
                 {
                  //--- Get the removed object parameters
                  long   lparam=obj.ChartID();
                  string sparam=obj.Name();
                  double dparam=(double)obj.TimeCreate();
                  //--- If this is an extended graphical object
                  if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
                    {
                     this.DeleteExtendedObj(obj);
                    }
                  //--- Move the graphical object class object to the list of removed objects
                  //--- and send the event to the control program chart
                  if(this.MoveGraphObjToDeletedObjList(index))
                     ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam);
                 }
              }
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+


Bei der Durchführung des Tests im vorigen Artikel habe ich festgestellt, dass die Koordinaten eines zusammengesetzten grafischen Objekts nicht immer korrekt durch die Grenzen des Charts begrenzt werden. Bei unterschiedlichen Positionen der Objektdrehpunkte kann das Objekt "deformiert" werden, wenn es einen Chart-Rand erreicht.
Den Grund habe ich gleich nach dem Test aufgedeckt:

... die Verschiebungen der Pivotpunkte werden relativ zum zentralen Punkt berechnet, d.h. ein Punkt hat eine positive Verschiebung, der zweite eine negative. Der Fehler bei der Berechnung der Begrenzung tritt auf, wenn man die Lage der Angelpunkte relativ zum zentralen Punkt ändert.

Wir beheben dies, indem wir die notwendigen Änderungen vornehmen. Wir werden sofort die Koordinaten des zentralen Punktes sowie die Verschiebungen von den Drehpunkten der grafischen Objekte zu ihrem zentralen Punkt berechnen. Bei der Berechnung der Begrenzungen durch die Chart-Größe sollten Sie den Wert für die Verschiebung ohne Vorzeichen verwenden. In diesem Fall hat das Vorzeichen der Verschiebung keinen Einfluss auf die Berechnung.

In der Ereignisbehandlung des Blocks für die Behandlung eines erweiterten grafischen Standardobjekts fügen wir eine neue Berechnung der Begrenzungen hinzu und zeigen die Debugging-Daten im Chart an:

                           //--- If the form is central for managing all pivot points of a graphical object
                           else
                             {
                              //--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure
                              if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point))
                                {
                                 //--- In the loop by the number of object pivot points,
                                 for(int i=0;i<(int)this.m_data_pivot_point.Size();i++)
                                   {
                                    //--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders
                                    //--- By X coordinate
                                    if(x+shift-::fabs(this.m_data_pivot_point[i].ShiftX)<0)
                                       x=-shift+::fabs(m_data_pivot_point[i].ShiftX);
                                    if(x+shift+::fabs(this.m_data_pivot_point[i].ShiftX)>chart_width)
                                       x=chart_width-shift-::fabs(this.m_data_pivot_point[i].ShiftX);
                                    //--- By Y coordinate
                                    if(y+shift-::fabs(this.m_data_pivot_point[i].ShiftY)<0)
                                       y=-shift+::fabs(this.m_data_pivot_point[i].ShiftY);
                                    if(y+shift+::fabs(this.m_data_pivot_point[i].ShiftY)>chart_height)
                                       y=chart_height-shift-::fabs(this.m_data_pivot_point[i].ShiftY);
                                    //--- set the calculated coordinates to the current object pivot point
                                    ext.ChangeCoordsExtendedObj(x+shift+this.m_data_pivot_point[i].ShiftX,y+shift+this.m_data_pivot_point[i].ShiftY,i);
                                   }
                                }
                                
                              //--- Display debugging comments on the chart
                              if(m_data_pivot_point.Size()>=2)
                                {
                                 int max_x,min_x,max_y,min_y;
                                 if(ext.GetMinCoordXY(min_x,min_y) && ext.GetMaxCoordXY(max_x,max_y))
                                    Comment
                                      (
                                       "MinX=",min_x,", MaxX=",max_x,"\n",
                                       "MaxY=",min_y,", MaxY=",max_y
                                      );
                                }
                                
                             }


Sobald wir auf das GUI-Objekt klicken, müssen wir es in den Vordergrund bringen und seine maximale ZOrder setzen.

In der Ereignisbehandlung des Cursor-Handling-Blocks innerhalb eines aktiven Bereichs der gedrückten Maustaste fügen wir den Code-Block zum Setzen einer maximalen ZOrder für das Objekt ein:

            //--- 'The cursor is inside the active area,  any mouse button is clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move)
              {
               pressed_form=true;   // the flag of holding the mouse button on the form
               //--- If the left mouse button is pressed
               if(this.m_mouse.IsPressedButtonLeft())
                 {
                  //--- Set flags and form parameters
                  move=true;                                            // movement flag
                  form.SetInteraction(true);                            // flag of the form interaction with the environment
                  form.BringToTop();                                    // form on the background - above all others
                  form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate
                  form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate
                  this.ResetAllInteractionExeptOne(form);               // Reset interaction flags for all forms except the current one
                  
                  //--- Get the maximum ZOrder
                  long zmax=this.GetZOrderMax();
                  //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero
                  if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0))
                    {
                     //--- If the form is not a control point for managing an extended standard graphical object,
                     //--- set the form's ZOrder above all others
                     if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL)
                        this.SetZOrderMAX(form);
                    }
                 }
              }

Hier setze ich ZOrder für Formularobjekte, die keine erweiterten grafischen Standardobjektsteuerungen sind.

Die Behandlung der Klassenereignisse der grafischen Elementsammlung ist recht umfangreich. Es macht also keinen Sinn, den gesamten Code hier zu veröffentlichen. Sie finden alle Änderungen (siehe oben) in den angehängten Dateien.

Ändern wir die Berechnung der Verschiebung in der Methode, die die Bildschirmkoordinaten jedes Drehpunkts eines grafischen Objekts zurückgibt.
Jetzt berechnen wir sofort die Koordinaten des zentralen Drehpunktes (der zum Verschieben des Objekts verwendet wird) und berechnen die Verschiebungen auf der Grundlage dieses Punktes. Genauer gesagt werden wir die Verschiebungen von jedem Drehpunkt bis zum zentralen Punkt berechnen:

//+------------------------------------------------------------------+
//| Return screen coordinates                                        |
//| of each graphical object pivot point                             |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[])
  {
//--- Central point coordinates
   int xc=0, yc=0;
//--- If failed to increase the array of structures to match the number of pivot points,
//--- inform of that in the journal and return 'false'
   if(::ArrayResize(array_pivots,obj.Pivots())!=obj.Pivots())
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      return false;
     }
//--- In the loop by the number of graphical object pivot points
   for(int i=0;i<obj.Pivots();i++)
     {
      //--- Convert the object coordinates into screen ones. If failed, inform of that and return 'false'
      if(!::ChartTimePriceToXY(obj.ChartID(),obj.SubWindow(),obj.Time(i),obj.Price(i),array_pivots[i].X,array_pivots[i].Y))
        {
         CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY);
         return false;
        }
     }
//--- Depending on the graphical object type
   switch(obj.TypeGraphObject())
     {
      //--- One pivot point in screen coordinates
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             : break;
      
      //--- One pivot point (price only)
      case OBJ_HLINE             : break;
      
      //--- One pivot point (time only)
      case OBJ_VLINE             :
      case OBJ_EVENT             : break;
      
      //--- Two pivot points and a central one
      //--- Lines
      case OBJ_TREND             :
      case OBJ_TRENDBYANGLE      :
      case OBJ_CYCLES            :
      case OBJ_ARROWED_LINE      :
      //--- Channels
      case OBJ_CHANNEL           :
      case OBJ_STDDEVCHANNEL     :
      case OBJ_REGRESSION        :
      //--- Gann
      case OBJ_GANNLINE          :
      case OBJ_GANNGRID          :
      //--- Fibo
      case OBJ_FIBO              :
      case OBJ_FIBOTIMES         :
      case OBJ_FIBOFAN           :
      case OBJ_FIBOARC           :
      case OBJ_FIBOCHANNEL       :
      case OBJ_EXPANSION         :
        //--- Calculate the central point coordinates
        xc=(array_pivots[0].X+array_pivots[1].X)/2;
        yc=(array_pivots[0].Y+array_pivots[1].Y)/2;
        //--- Calculate the shifts of all pivot points from the central one and write them to the structure array
        array_pivots[0].ShiftX=array_pivots[0].X-xc;  // X shift from 0 to the central point
        array_pivots[1].ShiftX=array_pivots[1].X-xc;  // X shift from 1 to the central point
        array_pivots[0].ShiftY=array_pivots[0].Y-yc;  // Y shift from 0 to the central point
        array_pivots[1].ShiftY=array_pivots[1].Y-yc;  // Y shift from 1 to the central point
        return true;

      //--- Channels
      case OBJ_PITCHFORK         : break;
      //--- Gann
      case OBJ_GANNFAN           : break;
      //--- Elliott
      case OBJ_ELLIOTWAVE5       : break;
      case OBJ_ELLIOTWAVE3       : break;
      //--- Shapes
      case OBJ_RECTANGLE         : break;
      case OBJ_TRIANGLE          : break;
      case OBJ_ELLIPSE           : break;
      //--- Arrows
      case OBJ_ARROW_THUMB_UP    : break;
      case OBJ_ARROW_THUMB_DOWN  : break;
      case OBJ_ARROW_UP          : break;
      case OBJ_ARROW_DOWN        : break;
      case OBJ_ARROW_STOP        : break;
      case OBJ_ARROW_CHECK       : break;
      case OBJ_ARROW_LEFT_PRICE  : break;
      case OBJ_ARROW_RIGHT_PRICE : break;
      case OBJ_ARROW_BUY         : break;
      case OBJ_ARROW_SELL        : break;
      case OBJ_ARROW             : break;
      
      //--- Graphical objects with time/price coordinates
      case OBJ_TEXT              : break;
      case OBJ_BITMAP            : break;
      //---
      default: break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Alle Verschiebungen und Begrenzungen sollten nun korrekt berechnet werden. Führen wir den Test durch, um das zu überprüfen.


Test

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

Da wir während des Tests den Wert der ZOrder-Eigenschaft auf jedem Formular sofort sehen wollen, fügen wir in OnInit() den Nullwert der Eigenschaft für das Formular (das Objekt ganz unten) ein und fügen die Anzeige der ID des Formularobjekts und seinen ZOrder-Eigenschaftswert hinzu:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//--- Create form objects
   CForm *form=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      //--- When creating an object, pass all the required parameters to it
      form=new CForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30);
      if(form==NULL)
         continue;
      //--- Set activity and moveability flags for the form
      form.SetActive(true);
      form.SetMovable(true);
      //--- Set the form ID 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 opacity of 200
      form.SetOpacity(245);
      //--- 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(false);
      //--- 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(),true);
      //--- 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();
      
      //--- Set ZOrder to zero, display the text describing the gradient type and update the form
      //--- Text parameters: the text coordinates and the anchor point in the form center
      //--- Create a new text animation frame with the ID of 0 and display the text on the form
      form.SetZorder(0,false);
      form.TextOnBG(0,TextByLanguage("Тест: ID ","Test: ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
      //--- Add the form to the list
      if(!engine.GraphAddCanvElmToCollection(form))
         delete form;
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


Kompilieren und starten wir den EA auf dem Chart:


Wie wir sehen können, ist der ZOrder-Wert jedes Formularobjekts unmittelbar nach der Erstellung gleich Null, aber grafische Objekte werden immer noch "unter ihnen" aufgebaut. Änderungen des ZOrder-Werts eines jeden Objekts werden "kreisförmig" durchgeführt — nicht mehr als die Anzahl der Formularobjekte auf dem Chart (von Null an gerechnet). Jedes konstruierte grafische Objekt erscheint immer "unter" den GUI-Objekten, und ihre relative Position bleibt unverändert, was bedeutet, dass sie in der Liste entsprechend den Werten ihrer ZOrder-Eigenschaft korrekt verteilt sind. Schließlich ist das zusammengesetzte grafische Objekt jetzt korrekt auf die Ränder des Bildschirms begrenzt und wird unabhängig von der Lage seiner Drehpunkte nicht verzerrt. Wie andere grafische Objekte wird es unterhalb der Formularobjekte gezeichnet.

Was kommt als Nächstes?

Der nächste Artikel wird einen großen Abschnitt über die Erstellung von GUI-Objekten im Stile von Windows Forms beginnen.
Das bedeutet aber nicht, dass erweiterte grafische Objekte und darauf basierende zusammengesetzte Objekte nicht weiterentwickelt werden. Wir brauchen einfach vollwertige Steuerelemente für ihre Weiterentwicklung. Im nächsten Abschnitt der Bibliothek werde ich die Entwicklung und Verfeinerung von erweiterten grafischen Objekten schrittweise fortsetzen.

Alle Dateien der aktuellen Bibliotheksversion, des Test-EA und des Chart-Event-Control-Indikators für MQL5 sind unten zum Testen und Herunterladen angehängt. Stellen Sie Ihre Fragen, Kommentare und Vorschläge bitte im Kommentarteil.

Zurück zum Inhalt

*Frühere Artikel dieser Serie:

Grafiken in der Bibliothek DoEasy (Teil 93): Vorbereiten der Funktionen zur Erstellung zusammengesetzter grafischer Objekte
Grafiken in der Bibliothek DoEasy (Teil 94): Bewegen und Löschen zusammengesetzter grafischer Objekte
Grafiken in der DoEasy-Bibliothek (Teil 95): Steuerelemente für zusammengesetzte grafische Objekte
Grafiken in der DoEasy-Bibliothek (Teil 96): Grafiken in Formularobjekten und Behandlung von Mausereignissen
Grafiken in der DoEasy-Bibliothek (Teil 97): Unabhängige Handhabung der Bewegung von Formularobjekten
Grafiken in der Bibliothek DoEasy (Teil 98): Verschieben von Angelpunkten erweiterter grafischer Standardobjekte
Grafiken in der DoEasy-Bibliothek (Teil 99): Verschieben eines erweiterten grafischen Objekts mit einem einzigen Steuerpunkt