English Русский 中文 Español 日本語 Português
preview
Grafiken in der DoEasy-Bibliothek (Teil 99): Verschieben eines erweiterten grafischen Objekts mit einem einzigen Steuerpunkt

Grafiken in der DoEasy-Bibliothek (Teil 99): Verschieben eines erweiterten grafischen Objekts mit einem einzigen Steuerpunkt

MetaTrader 5Beispiele | 16 Mai 2022, 08:58
204 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Im letzten Artikel, habe ich die Möglichkeit implementiert, Angelpunkte eines erweiterten grafischen Objekts mit Hilfe von Steuerformularen zu verschieben. Ich verfüge jedoch noch nicht über die Funktionsweise, ein solches grafisches Objekt als Ganzes zu verschieben. Jedes grafische Standardobjekt kann vollständig verschoben werden, wenn man seinen zentralen Punkt verschiebt. In ähnlicher Weise werde ich hier einen einzigen zentralen Punkt für die Verwaltung eines grafischen Objekts festlegen, um das gesamte grafische Objekt (und nicht nur seine Angelpunkte) durch Verschieben des Punkts bewegen zu können. Für den Test habe ich ein zusammengesetztes grafisches Objekt gewählt, das aus einer Trendlinie besteht, an deren Enden sich Preisetiketten befinden. In diesem Sinne wird die gesamte Arbeit für grafische Objekte durchgeführt, die zwei Angelpunkte für die Verschiebung ihrer Enden und einen einzigen zentralen Punkt für die Verschiebung des gesamten grafischen Objekts haben (zwei Punkte für die Änderung der Objektenden und ein zentraler Punkt für die Verschiebung). Später werde ich die gleichen Formulare mit Kontrollpunkten für grafische Objekte mit mehr als drei Kontrollpunkten erstellen.

Außerdem werde ich den Code zur Berechnung der Bildschirmkoordinaten der Angelpunkte des grafischen Objekts leicht optimieren, indem ich ihn in separate Methoden aufteile, um das Verständnis der grundlegenden Logik zu vereinfachen. Schließlich ist es viel einfacher, den Code mit dem Aufruf der Methode zu lesen, die einen bestimmten Wert zurückgibt (während es wiederum eine andere Methode darin gibt, die ebenfalls etwas berechnet), als den gesamten Code solcher Methoden in den Hauptberechnungsblock zu setzen, was ihn unhandlich und schwer verständlich macht.

Nicht alles, was hier implementiert ist, wird immer wie beabsichtigt funktionieren. Aber das Ziel des Artikels ist es, den Prozess der Entwicklung und Erstellung des Codes zu beschreiben, um das notwendige Ergebnis zu erhalten. Ich glaube, es ist viel interessanter, fast den ganzen Weg von der Planung der Funktionalität bis zu ihrer Implementierung zu gehen, als nur eine trockene Präsentation darüber zu lesen, "wie alles am Ende ausgegangen ist".

Da die Funktion zum Abrufen der Bildschirmkoordinaten, ChartTimePriceToXY(), nur die Koordinaten des sichtbaren Teils des Charts zurückgibt, können wir die Bildschirmkoordinaten des Linienpunkts außerhalb der Chartgrenzen nicht berechnen. Die Funktion gibt immer 0 zurück, wenn die X-Koordinate in Bildschirmzeitpixeln außerhalb des linken Teils des sichtbaren Charts abgefragt wird. Wenn Sie also ein zusammengesetztes grafisches Objekt auf dem Bildschirm verschieben und sein linker Teil über den linken Rand des Bildschirms hinausgeht, bleibt der linke Angelpunkt des Objekts an der Koordinate 0 in Chart-Pixeln. Dies führt zu einer Verzerrung des grafischen Objekts. Dasselbe gilt für die rechte Seite des grafischen Objekts und die rechte Seite des Chart-Bildschirms (sowie für die obere und untere Seite). Daher werde ich eine Begrenzung für ein zusammengesetztes grafisches Objekt einführen, die seine Bewegung über den sichtbaren Teil des Charts hinaus einschränkt. Dadurch wird verhindert, dass das grafische Objekt verzerrt wird, wenn eine seiner Seiten beim Verschieben gegen den Bildschirmrand stößt.


Verbesserung der Klassenbibliothek

Da das Objektformular, das zur Anzeige eines Kontrollpunkts für die Verwaltung von Angelpunkten eines erweiterten grafischen Objekts verwendet wird, ein wichtiges Objekt unter den Bibliotheksobjekten ist, die Formulare aber nicht in die Sammlung der grafischen Objekte aufgenommen werden, müssen wir einen neuen Typ für solche Formulare definieren. Alle wichtigen Bibliotheksobjekte haben einen eigenen Namen für den Typ des Bibliotheksobjekts, der es uns ermöglicht, zu definieren, welches Objekt gerade aktiv ist. Definieren wir den Typ für die Formularobjekte, die für die Verwaltung von Angelpunkten innerhalb der erweiterten grafischen Objekte der Bibliothek gedacht sind.

In \MQL5\Include\DoEasy\Defines.mqh fügen wir den neuen Typ zur Enumeration der Bibliotheksobjekttypen hinzu:

//+------------------------------------------------------------------+
//| List of library object types                                     |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Graphics
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // "Base object of all library graphical objects" object type
   OBJECT_DE_TYPE_GELEMENT,                                       // "Graphical element" object type
   OBJECT_DE_TYPE_GFORM,                                          // Form object type
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // "Form for managing pivot points of graphical object" object type
   OBJECT_DE_TYPE_GSHADOW,                                        // Shadow object type
//--- Animation
   OBJECT_DE_TYPE_GFRAME,                                         // "Single animation frame" object type
   OBJECT_DE_TYPE_GFRAME_TEXT,                                    // "Single text animation frame" object type
   OBJECT_DE_TYPE_GFRAME_QUAD,                                    // "Single rectangular animation frame" object type
   OBJECT_DE_TYPE_GFRAME_GEOMETRY,                                // "Single geometric animation frame" object type
   OBJECT_DE_TYPE_GANIMATIONS,                                    // "Animations" object type
//--- Managing graphical objects
   ...
   ...
   ...
  }


In \MQL5\Include\DoEasy\Data.mqh ergänzen wir die Indices der neuen Nachrichten:

   MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY,            // Request outside long array
   MSG_LIB_SYS_REQUEST_OUTSIDE_DOUBLE_ARRAY,          // Request outside double array
   MSG_LIB_SYS_REQUEST_OUTSIDE_STRING_ARRAY,          // Request outside string array
   MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY,                 // Request outside the array
   MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY,    // Failed to convert graphical object coordinates to screen ones
   MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY,    // Failed to convert time/price coordinates to screen ones

und die Textmeldungen zu den neu hinzugefügten Indizes:

   {"Запрос за пределами long-массива","Data requested outside the long-array"},
   {"Запрос за пределами double-массива","Data requested outside the double-array"},
   {"Запрос за пределами string-массива","Data requested outside the string-array"},
   {"Запрос за пределами массива","Data requested outside the array"},
   {"Не удалось преобразовать координаты графического объекта в экранные","Failed to convert graphics object coordinates to screen coordinates"},
   {"Не удалось преобразовать координаты время/цена в экранные","Failed to convert time/price coordinates to screen coordinates"},


Wenn bei der Entwicklung der Funktionen zum Verschieben von grafischen Objekten ein Fehler bei der Konvertierung von Zeit-/Preiskoordinaten in Bildschirmkoordinaten festgestellt wird, wird dieser Fehler gemeldet, um die Kette von der logischen Fehlersuche auszuschließen.

Die Funktion ChartTimePriceToXY(), die möglicherweise den Koordinatenumwandlungsfehler verursacht, wird auch in der Objektklasse des Chartfensters in \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh verwendet. Fügen wir der Methode TimePriceToXY() die Anzeige der Fehlermeldung der Koordinatenumrechnung im Journal hinzu:

//+------------------------------------------------------------------+
//| Convert chart coordinates from the time/price representation     |
//| to X and Y coordinates                                           |
//+------------------------------------------------------------------+
bool CChartWnd::TimePriceToXY(const datetime time,const double price)
  {
   ::ResetLastError();
   if(!::ChartTimePriceToXY(this.m_chart_id,this.WindowNum(),time,price,this.m_wnd_coord_x,this.m_wnd_coord_y))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY);
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Zuerst wird die Meldung "Failed to convert time/price coordinates to screen ones" (Konvertieren der Zeit-/Preiskoordinaten in Bildschirmkoordinaten ist fehlgeschlagen) angezeigt, gefolgt von der Fehlerbeschreibung zusammen mit dem Fehlercode.


Da ich nun den neuen Bibliotheksobjekttyp für Kontrollpunkte zur Verwaltung von Angelpunkten eines erweiterten grafischen Standardobjekts deklariert habe, muss ich die Klasse eines solchen Objekts erstellen, die von der Klasse der Formularobjekte geerbt wird. Innerhalb der Klasse werde ich einige Variablen und Methoden hinzufügen, um den Umgang mit solchen Objekten zu vereinfachen.

Wir platzieren sie in das Toolkit eines erweiterten grafischen Standardobjekts in \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh:

//+------------------------------------------------------------------+
//|                                      CGStdGraphObjExtToolkit.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Graph\Form.mqh"
//+------------------------------------------------------------------+
//| Class of the form for managing pivot points of a graphical object|
//+------------------------------------------------------------------+
class CFormControl : public CForm
  {
private:
   bool              m_drawn;                   // Flag indicating that the pivot point is drawn on the form
   int               m_pivot_point;             // Pivot point managed by the form
public:
//--- (1) Return and (2) set the drawn point flag
   bool              IsControlAlreadyDrawn(void)               const { return this.m_drawn;                       }
   void              SetControlPointDrawnFlag(const bool flag)       { this.m_drawn=flag;                         }
//--- (1) Return and (2) set the pivot point managed by the form
   int               GraphObjPivotPoint(void)                  const { return this.m_pivot_point;                 }
   void              SetGraphObjPivotPoint(const int index)          { this.m_pivot_point=index;                  }
//--- Constructor
                     CFormControl(void)                              { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL;  }
                     CFormControl(const long chart_id,const int subwindow,const string name,const int pivot_point,const int x,const int y,const int w,const int h) :
                        CForm(chart_id,subwindow,name,x,y,w,h)
                          {
                           this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL;
                           this.m_pivot_point=pivot_point;
                          }
  };
//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject

Die private Klassenvariable m_drawn dient dazu, das Flag zu speichern, das angibt, dass der Punkt bereits auf dem Formular gezeichnet wurde. Warum brauchen wir eine solche Variable? Wenn der Mauszeiger aus der aktiven Zone des Formulars für die Verwaltung der Kontrollpunkte eines grafischen Objekts entfernt wird, muss der auf dem Formular gezeichnete Punkt gelöscht werden. Gegenwärtig wird der gezeichnete Punkt auf allen derartigen Formularen ständig entfernt, wenn der Mauszeiger nicht über dem aktiven Bereich des Formulars schwebt. Warum sollten wir das System mit dem ständigen Neuzeichnen all dieser Formulare belasten, wenn wir vorher das Flag für das Neuzeichnen des Formulars ansehen können? Das Flag informiert uns darüber, ob der Punkt gezeichnet oder entfernt wird. In der Zukunft werde ich einige visuelle Effekte für das Zeichnen solcher Punkte entwickeln (zusätzlich zu einigen anderen Dingen), daher ist es besser, das Flag zu haben, das sofort nach dem Ausführen des Handlers für visuelle Effekte gesetzt wird, als zu versuchen, zu definieren, dass das Zeichnen bereits abgeschlossen ist.

Die private Klassenvariable m_pivot_point speichert den vom Formular verwalteten Index des Angelpunkts. Ein grafisches Objekt hat mehrere Kontrollpunkte. Zum Beispiel hat eine Trendlinie drei Punkte — zwei an den Enden der Linien für die unabhängige Änderung der Position der Linienenden und einen zentralen Punkt für die Bewegung des gesamten Objekts. Die in den Formularobjekten gespeicherten Indizes entsprechen den Indizes der Angelpunkte der Linie: 0 und 1 — für die Punkte entlang der Linienränder und 2 — für den zentralen Andere grafische Objekte können völlig andere Kontrollpunkte haben, aber alle Indizes müssen den Angelpunkten des Objekts entsprechen und einen zusätzlichen (wenn auch nicht in allen Fällen, dies wird in späteren Artikeln besprochen) für die Bewegung des gesamten Objekts.

Die öffentlichen Methoden der Klasse werden verwendet, um die Werte der oben beschriebenen Variablen zu setzen/zurückzugeben. Die Klasse verfügt außerdem über zwei Konstruktoren. Im Standardkonstruktor wird der im aktuellen Artikel neu hinzugefügte OBJECT_DE_TYPE_GFORM_CONTROL-Typ im Objekttyp gesetzt.
Der parametrische Konstruktor übergibt alle Werte, die an den Konstruktor der übergeordneten Klasse übergeben wurden, plus eine zusätzliche Variable — den Index des grafischen Objekts Angelpunkt, das von dem erstellten Formular verwaltet wird.

Nun werden alle Angelpunkt-Steuerungsformulare in der Klasse CGStdGraphObjExtToolkit vom Typ CFormControl sein, daher müssen wir den Typ des CForm-Formularobjekts durch CFormControl ersetzen und die neuen Methoden zur Handhabung der Steuerungsformulare für die Verwaltung grafischer Objekt-Angelpunkte hinzufügen:

//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject
  {
private:
   long              m_base_chart_id;           // Base graphical object chart ID
   int               m_base_subwindow;          // Base graphical object chart subwindow
   ENUM_OBJECT       m_base_type;               // Base object type
   string            m_base_name;               // Base object name
   int               m_base_pivots;             // Number of base object reference points
   datetime          m_base_time[];             // Time array of base object reference points
   double            m_base_price[];            // Price array of base object reference points
   int               m_base_x;                  // Base object X coordinate
   int               m_base_y;                  // Base object Y coordinate
   int               m_ctrl_form_size;          // Size of forms for managing reference points
   int               m_shift;                   // Shift coordinates for adjusting the form location
   CArrayObj         m_list_forms;              // List of form objects for managing reference points
//--- Create a form object on a base object reference point
   CFormControl     *CreateNewControlPointForm(const int index);
//--- Return X and Y (1) screen coordinates of the specified reference point of the graphical object
   bool              GetControlPointCoordXY(const int index,int &x,int &y);
//--- Set the parameters of a form object for managing pivot points
   void              SetControlFormParams(CFormControl *form,const int index);
public:
//--- Set the parameters of the base object of a composite graphical object
   void              SetBaseObj(const ENUM_OBJECT base_type,const string base_name,
                                const long base_chart_id,const int base_subwindow,
                                const int base_pivots,const int ctrl_form_size,
                                const int base_x,const int base_y,
                                const datetime &base_time[],const double &base_price[]);
//--- Set the base object (1) time, (2) price, (3) time and price coordinates
   void              SetBaseObjTime(const datetime time,const int index);
   void              SetBaseObjPrice(const double price,const int index);
   void              SetBaseObjTimePrice(const datetime time,const double price,const int index);
//--- Set the base object (1) X, (2) Y, (3) X and Y screen coordinates
   void              SetBaseObjCoordX(const int value)                        { this.m_base_x=value;                          }
   void              SetBaseObjCoordY(const int value)                        { this.m_base_y=value;                          }
   void              SetBaseObjCoordXY(const int value_x,const int value_y)   { this.m_base_x=value_x; this.m_base_y=value_y; }
//--- (1) Set and (2) return the size of the form of pivot point management control points
   void              SetControlFormSize(const int size);
   int               GetControlFormSize(void)                           const { return this.m_ctrl_form_size;                 }
//--- Return the pointer to the pivot point form by (1) index and (2) name
   CFormControl     *GetControlPointForm(const int index)                     { return this.m_list_forms.At(index);           }
   CFormControl     *GetControlPointForm(const string name,int &index);
//--- Return the number of (1) base object pivot points and (2) newly created form objects for managing control points
   int               GetNumPivotsBaseObj(void)                          const { return this.m_base_pivots;                    }
   int               GetNumControlPointForms(void)                      const { return this.m_list_forms.Total();             }
//--- Create form objects on the base object pivot points
   bool              CreateAllControlPointForm(void);
//--- (1) Draw a control point on the form, (2) draw a control point on the form and delete it on all other forms
   void              DrawControlPoint(CFormControl *form,const uchar opacity,const color clr);
   void              DrawOneControlPoint(CFormControl *form,const uchar opacity=255,const color clr=CTRL_POINT_COLOR);
//--- (1) Draw using a default color, (remove) a control point on the form
   void              DrawControlPoint(CFormControl *form)                     { this.DrawControlPoint(form,255,CTRL_POINT_COLOR);}
   void              ClearControlPoint(CFormControl *form)                    { this.DrawControlPoint(form,0,CTRL_POINT_COLOR);  }
//--- Remove all form objects from the list
   void              DeleteAllControlPointForm(void);
   
//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructor/destructor
                     CGStdGraphObjExtToolkit(const ENUM_OBJECT base_type,const string base_name,
                                             const long base_chart_id,const int base_subwindow,
                                             const int base_pivots,const int ctrl_form_size,
                                             const int base_x,const int base_y,
                                             const datetime &base_time[],const double &base_price[])
                       {
                        this.m_list_forms.Clear();
                        this.SetBaseObj(base_type,base_name,base_chart_id,base_subwindow,base_pivots,ctrl_form_size,base_x,base_y,base_time,base_price);
                        this.CreateAllControlPointForm();
                       }
                     CGStdGraphObjExtToolkit(){;}
                    ~CGStdGraphObjExtToolkit(){;}
  };
//+------------------------------------------------------------------+


Verbesserung der Methode GetControlPointCoordXY(), die die X- und Y-Koordinaten des angegebenen Angelpunkts des grafischen Objekts in Bildschirmkoordinaten zurückgibt.

Bisher gab die Methode einfach die berechneten Koordinaten des angegebenen Angelpunkts des grafischen Objekts zurück. Jetzt müssen wir berücksichtigen, dass grafische Objekte eine unterschiedliche Anzahl von Angelpunkten und eine andere Lage des zentralen Angelpunkts haben können. Daher sollten wir die Berechnung für verschiedene Arten von Objekten in switch() vornehmen. Außerdem sollten wir die Koordinaten des gewünschten Angelpunkts berücksichtigen - einen der an den Rändern des Objekts liegenden oder einen zentralen. Wenn der Index des an die Methode übergebenen Angelpunkts kleiner ist als die Gesamtzahl der Angelpunkte des grafischen Objekts, werden die Koordinaten des Angelpunkts angefordert. Andernfalls werden die Koordinaten des zentralen Angelpunkts angefordert.

Im Moment werde ich den Empfang von X- und Y-Koordinaten nur für die grafischen Objekte mit zwei Angelpunkten an den Rändern und einem einzigen zentralen Punkt implementieren:

//+------------------------------------------------------------------+
//| 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             :
      case OBJ_EVENT             :
        //--- Write object screen coordinates and return 'true'
        x=this.m_base_x;
        y=this.m_base_y;
        return true;
      
      //--- Lines (vertical and horizontal)
      case OBJ_VLINE             : break;
      case OBJ_HLINE             : break;
      //--- 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;
      //--- Graphical objects with time/price coordinates
      case OBJ_TEXT              : break;
      case OBJ_BITMAP            : break;
      //---
      default                    : break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Die Berechnung des Angelpunkts erfolgt anhand der Werte, die in den Arrays m_base_time und m_base_price der Koordinaten des Angelpunkts der Linie festgelegt sind. Zur Berechnung der Zentralpunktkoordinaten werden die Koordinaten von Formularobjekten verwendet, die an Angelpunkten an den Linienkanten angebracht sind. Im Falle einer erfolgreichen Koordinatenberechnung gibt die Methode sofort true zurück. In allen anderen Fällen gibt sie entweder false zurück oder wendet break an, um die Codeausführung im Fall von switch anzuhalten und zum Ende der Methode zu gelangen, wo false zurückgegeben wird.

In der Methode, die den Zeiger auf das Angelpunkt-Formular namentlich zurückgibt, ersetzen wir CForm durch CFormControl:

//+------------------------------------------------------------------+
//| Return the pointer to the pivot point form by name               |
//+------------------------------------------------------------------+
CFormControl *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index)
  {
   index=WRONG_VALUE;
   for(int i=0;i<this.m_list_forms.Total();i++)
     {
      CFormControl *form=this.m_list_forms.At(i);
      if(form==NULL)
         continue;
      if(form.Name()==name)
        {
         index=i;
         return form;
        }
     }
   return NULL;
  }
//+------------------------------------------------------------------+

In der Methode zum Erstellen eines Formularobjekts auf einem Basisobjekt Angelpunkt, ersetzen wir CForm durch CFormControl und setzen die Parameter für ein erfolgreich erstelltes Formularobjekt:

//+------------------------------------------------------------------+
//| Create a form object on a base object reference point            |
//+------------------------------------------------------------------+
CFormControl *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index)
  {
   string name=this.m_base_name+"_CP_"+(index<this.m_base_pivots ? (string)index : "X");
   CFormControl *form=this.GetControlPointForm(index);
   if(form!=NULL)
      return NULL;
   int x=0, y=0;
   if(!this.GetControlPointCoordXY(index,x,y))
      return NULL;
   form=new CFormControl(this.m_base_chart_id,this.m_base_subwindow,name,index,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize());
//--- Set all the necessary properties for the created form object
   if(form!=NULL)
      this.SetControlFormParams(form,index);
   return form;
  }
//+------------------------------------------------------------------+


In der Methode zum Erzeugen von Formularobjekten auf dem Basisobjekt Angelpunkte, ersetzen wir CForm durch CFormControl und entfernen die Zeichenketten zum Setzen der Parameter eines erzeugten Formularobjekts, da die Parameter jetzt sofort beim Erzeugen des Objekts in der oben betrachteten Methode gesetzt werden:

//+------------------------------------------------------------------+
//| Create form objects on the base object pivot points              |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::CreateAllControlPointForm(void)
  {
   bool res=true;
//--- In the loop by the number of base object pivot points
   for(int i=0;i<=this.m_base_pivots;i++)
     {
      //--- Create a new form object on the current pivot point corresponding to the loop index
      CFormControl *form=this.CreateNewControlPointForm(i);
      //--- If failed to create the form, inform of that and add 'false' to the final result
      if(form==NULL)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM);
         res &=false;
        }
      //--- If failed to add the form to the list, inform of that, remove the created form and add 'false' to the final result
      if(!this.m_list_forms.Add(form))
        {
         CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
         delete form;
         res &=false;
        }
     }
//--- Redraw the chart for displaying changes (if successful) and return the final result
   if(res)
      ::ChartRedraw(this.m_base_chart_id);
   return res;
  }
//+------------------------------------------------------------------+

Jetzt läuft die Schleife um die Anzahl der Angelpunkte des Basisobjekts plus einen weiteren. Mit anderen Worten: Die Anzahl der erzeugten Formulare übersteigt die Anzahl der Angelpunkte des grafischen Objekts um eins. Das letzte Formular ist ein zentrales Formular, das für die Bewegung des gesamten grafischen Objekts bestimmt ist.

Die Methode setzt die Parameter eines Formularobjekts zur Verwaltung von Angelpunkten:

//+------------------------------------------------------------------+
//| Set the parameters of a form object for managing pivot points    |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetControlFormParams(CFormControl *form,const int index)
  {
   form.SetBelong(GRAPH_OBJ_BELONG_PROGRAM);                // Object is created programmatically
   form.SetActive(true);                                    // Form object is active
   form.SetMovable(true);                                   // Movable object
   int x=(int)::floor((form.Width()-CTRL_POINT_RADIUS*2)/2);// Shift the active area from the form edge
   form.SetActiveAreaShift(x,x,x,x);                        // Object active area is located at the center of the form, its size is equal to two CTRL_POINT_RADIUS values
   form.SetFlagSelected(false,false);                       // Object is not selected
   form.SetFlagSelectable(false,false);                     // Object cannot be selected by mouse
   form.Erase(CLR_CANV_NULL,0);                             // Fill in the form with transparent color and set the full transparency
   //form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,clrSilver);    // Draw an outlining rectangle for visual display of the form location
   //form.DrawRectangle(x,x,form.Width()-x-1,form.Height()-x-1,clrSilver);// Draw an outlining rectangle for visual display of the form active area location
   form.SetID(index+1);                                     // Set the form ID
   form.SetControlPointDrawnFlag(false);                    // Set the flag that the pivot point is not drawn on the form
   form.Done();                                             // Save the initial form object state (its appearance)
  }
//+------------------------------------------------------------------+

Hier haben wir die Zeichenketten des Codes aus der oben betrachteten Methode übernommen. Außerdem gibt es ein Flag für einen auf dem Formular gezeichneten Punkt und die Formular-ID.

In der Methode, die den Kontrollpunkt auf dem Formular zeichnet, verlagert man die Berechnung der Formularmitte in einen separaten String, um doppelte Berechnungen zu vermeiden. Nach Beendigung der Methode setze das Flag eines gezeichneten Punktes auf dem Formular:

//+------------------------------------------------------------------+
//| Draw a control point on the form                                 |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DrawControlPoint(CFormControl *form,const uchar opacity,const color clr)
  {
   if(form==NULL)
      return;
   int c=int(::floor(form.Width()/2));                      // Form center (coordinates)
   form.DrawCircle(c,c,CTRL_POINT_RADIUS,clr,opacity);      // Draw a circle in the form center
   form.DrawCircleFill(c,c,2,clr,opacity);                  // Draw a circle in the form center
   form.SetControlPointDrawnFlag(opacity>0 ? true : false); // Set the flag that the pivot point is drawn on the form
  }
//+------------------------------------------------------------------+


Zurzeit erscheint ein Punkt, wenn der Mauszeiger über das Formular zur Verwaltung des Angelpunkts eines grafischen Objekts bewegt wird. Der Punkt wird erst entfernt, wenn der Mauszeiger das Formular verlässt. Bringt man jedoch alle Kontrollpunkte des Objekts näher zusammen, so dass sich die Formulare an den Enden des grafischen Objekts und das zentrale Formular zu überlappen beginnen, dann führt das Wegbewegen des Cursors von einem Formular dazu, dass sich der Cursor zu einem anderen Formular in der Nähe bewegt. So können wir erreichen, dass alle Punkte auf allen Objektformen angezeigt werden:

Wenn wir das Formular anfassen und bewegen, bewegt sich auch der Angelpunkt des Objekts. Formulare, die aufgrund eines Fehlers sichtbar sind, bleiben dort, wo sie vor der Verschiebung waren. Dieses Verhalten ist nicht korrekt. Deshalb brauchen wir die Methode, die einen Punkt auf einem Formularobjekt eines grafischen Objekts zeichnet und gleichzeitig Punkte auf anderen Formularobjekten desselben Objekts löscht.

Die Methode, die einen Kontrollpunkt auf dem Formular zeichnet und sie auf allen anderen Formularen entfernt:

//+------------------------------------------------------------------+
//| Draw a control point on the form,                                |
//| remove it on all other forms                                     |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DrawOneControlPoint(CFormControl *form,const uchar opacity=255,const color clr=CTRL_POINT_COLOR)
  {
   this.DrawControlPoint(form,opacity,clr);
   for(int i=0;i<this.GetNumControlPointForms();i++)
     {
      CFormControl *ctrl=this.GetControlPointForm(i);
      if(ctrl==NULL || ctrl.ID()==form.ID())
         continue;
      this.ClearControlPoint(ctrl);
     }
  }
//+------------------------------------------------------------------+

Hier erhält die Methode den Zeiger auf das Formular, über dem der Cursor schwebt. Zeichne den Punkt auf dem Formular. Dann, in der Schleife durch alle Objektformulare, wählt man das Formular aus und, wenn das Formular nicht an die Methode übergeben wurde, löscht man den Punkt auf ihm.

In der Ereignisbehandlung ersetzen wir den Formulartyp CForm durch CFormControl:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      for(int i=0;i<this.m_list_forms.Total();i++)
        {
         CFormControl *form=this.m_list_forms.At(i);
         if(form==NULL)
            continue;
         int x=0, y=0;
         if(!this.GetControlPointCoordXY(i,x,y))
            continue;
         form.SetCoordX(x-this.m_shift);
         form.SetCoordY(y-this.m_shift);
         form.Update();
        }
      ::ChartRedraw(this.m_base_chart_id);
     }
  }
//+------------------------------------------------------------------+


Machen wir noch einige Verbesserungen bei der Optimierung des Methodencodes in der Klasse des abstrakten grafischen Standardobjekts in \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh. Da wir ähnliche Codefragmente in verschiedenen Methoden haben, ist es sinnvoll, solche Codeblöcke als separate Methoden zu implementieren und sie dort aufzurufen, wo sie benötigt werden, damit der Code leichter zu lesen ist.

In dem öffentlichen und und dem privaten Klassenabschnitt deklarieren wir neue Methoden, die die sich wiederholenden Codefragmente enthalten sollen:

//--- 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 the pivot point data object
   CLinkedPivotPoint*GetLinkedPivotPoint(void)        { return &this.m_linked_pivots;  }

...

private:
//--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set X and Y coordinates into the appropriate pivot point of a specified subordinate object
   void              SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj,CLinkedPivotPoint *pivot_point,const int index);
//--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property
   void              SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);

public:
//--- Event handler


In der Methode, die die Änderungen in den Objekteigenschaften überprüft, entfernen wir den angegebenen Codeblock (der Code wird in eine separate Methode verschoben):

      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- get the data object of its pivot points,
            CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
            if(pp==NULL)
               continue;
            //--- get the number of coordinate points the object is attached to
            int num=pp.GetNumLinkedCoords();
            //--- In the loop by the object coordinate points,
            for(int j=0;j<num;j++)
              {
               //--- get the number of coordinate points of the base object for setting the X coordinate
               int numx=pp.GetBasePivotsNumX(j);
               //--- In the loop by each coordinate point for setting the X coordinate,
               for(int nx=0;nx<numx;nx++)
                 {
                  //--- get the property for setting the X coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyX(j,nx);
                  int modifier_from=pp.GetPropertyModifierX(j,nx);
                  this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
                 }
               //--- Get the number of coordinate points of the base object for setting the Y coordinate
               int numy=pp.GetBasePivotsNumY(j);
               //--- In the loop by each coordinate point for setting the Y coordinate,
               for(int ny=0;ny<numy;ny++)
                 {
                  //--- get the property for setting the Y coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyY(j,ny);
                  int modifier_from=pp.GetPropertyModifierY(j,ny);
                  this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
                 }
              }
            dep.PropertiesCopyToPrevData();
           }
         //--- Move reference control points to new coordinates
         if(ExtToolkit!=NULL)
           {
            for(int i=0;i<this.Pivots();i++)
              {
               ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
              }
            ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
            long   lparam=0;
            double dparam=0;
            string sparam="";
            ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }

Wir fügen den Aufruf einer neuen Methode anstelle eines entfernten Blocks hinzu:

      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- Set X and Y coordinates to all pivot points of a subordinate object and
            //--- save the current properties of a subordinate graphical object as the previous ones
            if(this.SetCoordsXYtoDependentObj(dep))
               dep.PropertiesCopyToPrevData();
           }
         //--- Move reference control points to new coordinates
         if(this.ExtToolkit!=NULL)
           {
            for(int i=0;i<this.Pivots();i++)
              {
               this.ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
              }
            this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
            long   lparam=0;
            double dparam=0;
            string sparam="";
            this.ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }


Da die derzeitige Logik der Punktlöschung impliziert, dass, wenn der Cursor auf keinem der Formulare gesetzt ist, jedes dieser Formulare ständig neu gezeichnet wird (was suboptimal und ressourcenintensiv ist), implementieren wir die Prüfung, die sicherstellt, dass das Formular zur Punktlöschung nur dann neu gezeichnet wird, wenn der Punkt tatsächlich gelöscht werden soll und noch vorhanden ist in der Methode, die das Formular zur Verwaltung eines Kontrollpunkts eines erweiterten grafischen Standardobjekts neu zeichnet. Außerdem werde ich den Objekttyp des Formulars durch den neuen Typ ersetzen:

//+------------------------------------------------------------------+
//| Redraw the form for managing a control point                     |
//| of an extended standard graphical object                         |
//+------------------------------------------------------------------+
void CGStdGraphObj::RedrawControlPointForms(const uchar opacity,const color clr)
  {
//--- Leave if the object has no toolkit of an extended standard graphical object
   if(this.ExtToolkit==NULL)
      return;
//--- Get the number of pivot point management forms
   int total_form=this.GetNumControlPointForms();
//--- In the loop by the number of pivot point management forms
   for(int i=0;i<total_form;i++)
     {
      //--- get the next form object
      CFormControl *form=this.ExtToolkit.GetControlPointForm(i);
      if(form==NULL)
         continue;
      //--- Draw a point and a circle with a specified non-transparency and color
      //--- If a point should be completely transparent (deleted)
      //--- and the form still has the point, delete the point,
      if(opacity==0 && form.IsControlAlreadyDrawn())
         this.ExtToolkit.DrawControlPoint(form,0,clr);
      //--- otherwise, draw the point with a specified non-transparency and color
      else
         this.ExtToolkit.DrawControlPoint(form,opacity,clr);
     }
   
//--- Get the total number of bound graphical objects
   int total_dep=this.GetNumDependentObj();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<total_dep;i++)
     {
      //--- get the next graphical object from the list
      CGStdGraphObj *dep=this.GetDependentObj(i);
      if(dep==NULL)
         continue;
      //--- call the method for it
      dep.RedrawControlPointForms(opacity,clr);
     }
  }
//+------------------------------------------------------------------+

Der Punkt wird jetzt nur noch gelöscht, wenn er tatsächlich gelöscht werden soll (die Nicht-Transparenz des Punktes ist auf Null gesetzt) und wenn der Punkt noch vorhanden ist (gezeichneter Punkt-Flag ist gesetzt).

Außerdem überarbeiten wir die Methode, die die X- und Y-Koordinaten des aktuellen und aller abhängigen Objekte ändert, indem wir die Codesegmente löschen, die durch den Aufruf der neuen Methode ersetzt werden:

//+----------------------------------------------------------------------+
//| Change X and Y coordinates of the current and all dependent objects  |
//+----------------------------------------------------------------------+
bool CGStdGraphObj::ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false)
  {
//--- Set new coordinates for the pivot point specified in 'modifier'
   if(!this.SetTimePrice(x,y,modifier))
      return false;
//--- If the object is not a composite graphical object
//--- or if subordinate graphical objects are not attached to the object,
//--- there is nothing else to do here, return 'true'
   if(this.ExtToolkit==NULL || this.m_list.Total()==0)
      return true;
//--- Get the graphical object bound to the 'modifier' point
   CGStdGraphObj *dep=this.GetDependentObj(modifier);
   if(dep==NULL)
      return false;
//--- Get the object of pivot point data of the bound graphical object
   CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
   if(pp==NULL)
      return false;
//--- get the number of coordinate points the object is attached to
   int num=pp.GetNumLinkedCoords();
//--- In the loop by the object coordinate points,
   for(int j=0;j<num;j++)
     {
      //--- get the number of coordinate points of the base object for setting the X coordinate
      int numx=pp.GetBasePivotsNumX(j);
      //--- In the loop by each coordinate point for setting the X coordinate,
      for(int nx=0;nx<numx;nx++)
        {
         //--- get the property for setting the X coordinate, its modifier
         //--- and set it to the subordinate graphical object attached to the 'modifier' point
         int prop_from=pp.GetPropertyX(j,nx);
         int modifier_from=pp.GetPropertyModifierX(j,nx);
         this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
        }
      //--- Get the number of coordinate points of the base object for setting the Y coordinate
      int numy=pp.GetBasePivotsNumY(j);
      //--- In the loop by each coordinate point for setting the Y coordinate,
      for(int ny=0;ny<numy;ny++)
        {
         //--- get the property for setting the Y coordinate, its modifier
         //--- and set it to the subordinate graphical object attached to the 'modifier' point
         int prop_from=pp.GetPropertyY(j,ny);
         int modifier_from=pp.GetPropertyModifierY(j,ny);
         this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
        }
     }
//--- Save the current properties of a subordinate graphical object as the previous ones
   dep.PropertiesCopyToPrevData();
//--- Move a reference control point to new coordinates
   this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier);
   this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
//--- If the flag is active, redraw the chart
   if(redraw)
      ::ChartRedraw(m_chart_id);
//--- All is successful - return 'true'
   return true;
  }
//+------------------------------------------------------------------+

Jetzt ist die Methode viel einfacher:

//+----------------------------------------------------------------------+
//| Change X and Y coordinates of the current and all dependent objects  |
//+----------------------------------------------------------------------+
bool CGStdGraphObj::ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false)
  {
//--- Set new coordinates for the pivot point specified in 'modifier'
   if(!this.SetTimePrice(x,y,modifier))
      return false;
//--- If the object is a composite graphical object,
//--- and subordinate graphical objects are attached to the object
   if(this.ExtToolkit!=NULL && this.m_list.Total()>0)
     {
      //--- Get the graphical object bound to the 'modifier' point
      CGStdGraphObj *dep=this.GetDependentObj(modifier);
      if(dep==NULL)
         return false;
      //--- Set X and Y coordinates to all pivot points of a subordinate object and
      //--- save the current properties of a subordinate graphical object as the previous ones
      if(this.SetCoordsXYtoDependentObj(dep))
         dep.PropertiesCopyToPrevData();
     }
//--- Move a reference control point to new coordinates
   this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier);
   this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
//--- If the flag is active, redraw the chart
   if(redraw)
      ::ChartRedraw(m_chart_id);
//--- All is successful - return 'true'
   return true;
  }
//+------------------------------------------------------------------+


Implementieren wir die Methode, die X- und Y-Koordinaten in den entsprechenden Angelpunkt eines angegebenen untergeordneten Objekts setzt:

//+------------------------------------------------------------------+
//| Set X and Y coordinates to the associated pivot point            |
//| of a specified subordinate object by index                       |
//+------------------------------------------------------------------+
void CGStdGraphObj::SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj,CLinkedPivotPoint *pivot_point,const int index)
  {
//--- get the number of coordinate points of the base object for setting the X coordinate
   int numx=pivot_point.GetBasePivotsNumX(index);
//--- In the loop by each coordinate point for setting the X coordinate,
   for(int nx=0;nx<numx;nx++)
     {
      //--- get the property for setting the X coordinate, its modifier
      //--- and set it to the subordinate graphical object attached to the 'index' point
      int prop_from=pivot_point.GetPropertyX(index,nx);
      int modifier_from=pivot_point.GetPropertyModifierX(index,nx);
      this.SetCoordXToDependentObj(dependent_obj,prop_from,modifier_from,nx);
     }
//--- Get the number of coordinate points of the base object for setting the Y coordinate
   int numy=pivot_point.GetBasePivotsNumY(index);
   //--- In the loop by each coordinate point for setting the Y coordinate,
   for(int ny=0;ny<numy;ny++)
     {
      //--- get the property for setting the Y coordinate, its modifier
      //--- and set it to the subordinate graphical object attached to the 'index' point
      int prop_from=pivot_point.GetPropertyY(index,ny);
      int modifier_from=pivot_point.GetPropertyModifierY(index,ny);
      this.SetCoordYToDependentObj(dependent_obj,prop_from,modifier_from,ny);
     }
  }
//+------------------------------------------------------------------+

Tatsächlich sind dies genau die Codeblöcke, die aus den Klassenmethoden entfernt wurden. Ich habe die Codelogik in den vorherigen Artikeln berücksichtigt. Außerdem ist sie in den Kommentaren zum Code beschrieben. Ich glaube also, dass es keiner Erklärungen bedarf.

Implementierung der Methode, die X- und Y-Koordinaten in zugehörige Angelpunkte eines bestimmten untergeordneten Objekts setzt:

//+------------------------------------------------------------------+
//| Set X and Y coordinates to associated pivot points               |
//| of the specified subordinate object                              |
//+------------------------------------------------------------------+
bool CGStdGraphObj::SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj)
  {
//--- Get the object of pivot point data of the bound graphical object
   CLinkedPivotPoint *pp=dependent_obj.GetLinkedPivotPoint();
   if(pp==NULL)
      return false;
//--- get the number of coordinate points the object is attached to
   int num=pp.GetNumLinkedCoords();
//--- In the loop by the object coordinate points,
//--- set X and Y to all pivot points of a subordinate object
   for(int j=0;j<num;j++)
      this.SetCoordsXYtoDependentObj(dependent_obj,pp,j);
   return true;
  }
//+------------------------------------------------------------------+

Die Methode ermöglicht das Setzen von Koordinaten für alle Angelpunkte eines untergeordneten Objekts. Wenn dem zusammengesetzten grafischen Objekt weitere grafische Objekte hinzugefügt werden, setzt die Methode die angegebenen Koordinaten in ihnen.


Fügen wir Verbesserungen in die Sammelklasse der grafischen Elemente \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh ein.

Da die Standardfunktion ChartTimePriceToXY() gleich zwei Koordinaten — X und Y — zurückgibt, werde ich die Struktur im privaten Bereich deklarieren, um sie zu speichern. Zusätzlich zu den Koordinaten werden in der Struktur auch die Koordinatenverschiebungen relativ zum zentralen Punkt gespeichert. Da das grafische Objekt mehrere Angelpunkte haben kann, deklarieren wir das Array mit dem erstellten Strukturtyp, um die Koordinaten jedes Angelpunktes für das grafische Objekt zu speichern. In diesem Fall enthält jede Array-Zelle die X- und Y-Koordinaten des Bildschirms, die aus den "Zeit/Preis"-Koordinaten umgewandelt wurden, sowie die Verschiebungen der Koordinaten der Angelpunkte relativ zum zentralen Punkt des grafischen Objekts.

Im privaten Teil der Klasse erstellen wir die Struktur und deklarieren das benötigte Array:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
#resource "\\"+PATH_TO_EVENT_CTRL_IND;          // Indicator for controlling graphical object events packed into the program resources
class CGraphElementsCollection : public CBaseObj
  {
private:
   //--- Pivot point data structure
   struct SDataPivotPoint
     {
      public:
         int         X;                         // Pivot point X coordinate
         int         Y;                         // Pivot point Y coordinate
         int         ShiftX;                    // Pivot point X coordinate shift from the central one
         int         ShiftY;                    // Pivot point Y coordinate shift from the central one
     };
   SDataPivotPoint   m_data_pivot_point[];      // Pivot point data structure array
   CArrayObj         m_list_charts_control;     // List of chart management objects
   CListObj          m_list_all_canv_elm_obj;   // List of all graphical elements on canvas
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   CArrayObj         m_list_deleted_obj;        // List of removed graphical objects
   CMouseState       m_mouse;                   // "Mouse status" class object
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check

Im privaten Abschnitt der Klasse deklarieren wir die Methode, die die Bildschirmkoordinaten jedes Angelpunkts eines grafischen Objekts zurückgibt

private:
//--- Find an object present in the collection but not on a chart
   CGStdGraphObj    *FindMissingObj(const long chart_id);
   CGStdGraphObj    *FindMissingObj(const long chart_id,int &index);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object class object from the graphical object collection list: (1) specified object, (2) by chart ID
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   void              DeleteGraphObjectsFromList(const long chart_id);
//--- Move the graphical object class object to the list of removed graphical objects: (1) specified object, (2) by index
   bool              MoveGraphObjToDeletedObjList(CGStdGraphObj *obj);
   bool              MoveGraphObjToDeletedObjList(const int index);
//--- Move all objects by chart ID to the list of removed graphical objects
   void              MoveGraphObjectsToDeletedObjList(const long chart_id);
//--- Remove the object of managing charts from the list
   bool              DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj);
//--- Set the flags of scrolling the chart with the mouse, context menu and crosshairs tool for the specified chart
   void              SetChartTools(const long chart_id,const bool flag);
//--- Return the screen coordinates of each pivot point of the graphical object
   bool              GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]);
public:

Implementieren der Methode außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Return screen coordinates                                        |
//| of each graphical object pivot point                             |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[])
  {
//--- 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             : break;
      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 shifts of all pivot points from the central one and write them to the structure array
        array_pivots[0].ShiftX=(array_pivots[1].X-array_pivots[0].X)/2; 
        array_pivots[0].ShiftY=(array_pivots[1].Y-array_pivots[0].Y)/2;
        array_pivots[1].ShiftX=(array_pivots[0].X-array_pivots[1].X)/2;
        array_pivots[1].ShiftY=(array_pivots[0].Y-array_pivots[1].Y)/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;
      //--- Graphical objects with time/price coordinates
      case OBJ_TEXT              : break;
      case OBJ_BITMAP            : break;
      //---
      default: break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Vorerst werden nur die Bildschirmkoordinaten der grafischen Objekte mit zwei Angelpunkten und einem zentralen in der Struktur gesetzt.

Die Methode erhält den Zeiger auf das grafische Objekt, dessen Angelpunkt-Koordinaten gesetzt werden sollen, in dem Struktur-Array, das ebenfalls per Link an die Methode übergeben wird. Bei erfolgreicher Koordinatenkonvertierung gibt die Methode true zusammen mit dem vollständig gefüllten Strukturarray mit Bildschirmkoordinaten für jeden Angelpunkt eines grafischen Objekts zurück. Bei einem Fehler gibt die Methode false zurück.

In der Ereignisbehandlung der Klasse müssen wir die Formverschiebung der Objektverwaltung so behandeln, dass das Objekt in seiner Gesamtheit verschoben wird, wenn es sich um einen zentralen Punkt handelt. Um dies zu erreichen, müssen wir die Verschiebungen der Randformen relativ zur zentralen Form (die zum Ziehen des Objekts verwendet wird) berechnen und beide Angelpunkte um einen in der Struktur berechneten und festgelegten Verschiebungswert verschieben. Auf diese Weise werden alle Angelpunkte um denselben Wert verschoben wie der zentrale, der mit der Maus gezogen wird.

Fügen wir eine solche Behandlung eines zentralen Kontrollpunktes (Formular) hinzufügen:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj_std=NULL;  // Pointer to the standard graphical object
   CGCnvElement  *obj_cnv=NULL;  // Pointer to the graphical element object on canvas
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   if(id==CHARTEVENT_OBJECT_CHANGE  || id==CHARTEVENT_OBJECT_DRAG    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
      //--- If the event ID corresponds to a user event, the chart ID is received from lparam
      //--- Otherwise, the chart ID is assigned to -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Get the object, whose properties were changed or which was relocated,
      //--- from the collection list by its name set in sparam
      obj_std=this.GetStdGraphObject(sparam,chart_id);
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      if(obj_std==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj_std=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         if(obj_std==NULL)
            return;
         //--- Get the name of the renamed graphical object on the chart, which is not in the collection list
         string name_new=this.FindExtraObj(chart_id);
         //--- set a new name for the collection list object, which does not correspond to any graphical object on the chart,
         //--- and send an event with the new name of the object to the control program chart
         if(obj_std.SetNamePrev(obj_std.Name()) && obj_std.SetName(name_new))
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj_std.ChartID(),obj_std.TimeCreate(),obj_std.Name());
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj_std.PropertiesRefresh();
      obj_std.PropertiesCheckChanged();
     }

//--- Handle standard graphical object events in the collection list
   for(int i=0;i<this.m_list_all_graph_obj.Total();i++)
     {
      //--- Get the next graphical object and
      obj_std=this.m_list_all_graph_obj.At(i);
      if(obj_std==NULL)
         continue;
      //--- call its event handler
      obj_std.OnChartEvent((id<CHARTEVENT_CUSTOM ? id : idx),lparam,dparam,sparam);
     }

//--- Handle chart changes for extended standard objects
   if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE)
     {
      CArrayObj *list=this.GetListStdGraphObjectExt();
      if(list!=NULL)
        {
         for(int i=0;i<list.Total();i++)
           {
            obj_std=list.At(i);
            if(obj_std==NULL)
               continue;
            obj_std.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
        }
     }
//--- Handling mouse events of graphical objects on canvas
//--- If the event is not a chart change
   else
     {
      //--- Check whether the mouse button is pressed
      bool pressed=(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)==MOUSE_BUTT_KEY_STATE_LEFT ? true : false);
      ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE;
      //--- Declare static variables for the active form and status flags
      static CForm *form=NULL;
      static bool pressed_chart=false;
      static bool pressed_form=false;
      static bool move=false;
      //--- Declare static variables for the index of the form for managing an extended standard graphical object and its ID
      static int  form_index=WRONG_VALUE;
      static long graph_obj_id=WRONG_VALUE;
      
      //--- If the button is not pressed on the chart and the movement flag is not set, get the form, above which the cursor is located
      if(!pressed_chart && !move)
         form=this.GetFormUnderCursor(id,lparam,dparam,sparam,mouse_state,graph_obj_id,form_index);
      
      //--- If the button is not pressed, reset all flags and enable the chart tools 
      if(!pressed)
        {
         pressed_chart=false;
         pressed_form=false;
         move=false;
         this.SetChartTools(::ChartID(),true);
        }
      
      //--- If this is a mouse movement event and the movement flag is active, move the form, above which the cursor is located (if the pointer to it is valid)
      if(id==CHARTEVENT_MOUSE_MOVE && move)
        {
         if(form!=NULL)
           {
            //--- calculate the cursor movement relative to the form coordinate origin
            int x=this.m_mouse.CoordX()-form.OffsetX();
            int y=this.m_mouse.CoordY()-form.OffsetY();
            //--- get the width and height of the chart the form is located at
            int chart_width=(int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow());
            int chart_height=(int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow());
            //--- If the form is not within an extended standard graphical object
            if(form_index==WRONG_VALUE)
              {
               //--- Adjust the calculated form coordinates if the form is out of the chart range
               if(x<0) x=0;
               if(x>chart_width-form.Width()) x=chart_width-form.Width();
               if(y<0) y=0;
               if(y>chart_height-form.Height()) y=chart_height-form.Height();
               //--- If the one-click trading panel is not present on the chart,
               if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK))
                 {
                  //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel button
                  if(y<17 && x<41)
                     y=17;
                 }
               //--- If the one-click trading panel is on the chart,
               else
                 {
                  //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel
                  if(y<80 && x<192)
                     y=80;
                 }
              }
            //--- If the form is included into the extended standard graphical object
            else
              {
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- Get the list of objects by object ID (there should be one object)
                  CArrayObj *list_ext=CSelect::ByGraphicStdObjectProperty(GetListStdGraphObjectExt(),GRAPH_OBJ_PROP_ID,0,graph_obj_id,EQUAL);
                  //--- If managed to obtain the list and it is not empty,
                  if(list_ext!=NULL && list_ext.Total()>0)
                    {
                     //--- get the graphical object from the list
                     CGStdGraphObj *ext=list_ext.At(0);
                     //--- If the pointer to the object has been received,
                     if(ext!=NULL)
                       {
                        //--- get the object type
                        ENUM_OBJECT type=ext.GraphObjectType();
                        //--- If the object is built using screen coordinates, set the coordinates to the object
                        if(type==OBJ_LABEL || type==OBJ_BUTTON || type==OBJ_BITMAP_LABEL || type==OBJ_EDIT || type==OBJ_RECTANGLE_LABEL)
                          {
                           ext.SetXDistance(x);
                           ext.SetYDistance(y);
                          }
                        //--- otherwise, if the object is built based on time/price coordinates,
                        else
                          {
                           //--- calculate the coordinate shift
                           int shift=(int)::ceil(form.Width()/2)+1;
                           //--- If the form is located on one of the graphical object pivot points,
                           if(form_index<ext.Pivots())
                             {
                              //--- limit the form coordinates so that they do not move beyond the chart borders
                              if(x+shift<0)
                                 x=-shift;
                              if(x+shift>chart_width)
                                 x=chart_width-shift;
                              if(y+shift<0)
                                 y=-shift;
                              if(y+shift>chart_height)
                                 y=chart_height-shift;
                              //--- set the calculated coordinates to the object
                              ext.ChangeCoordsExtendedObj(x+shift,y+shift,form_index);
                             }
                           //--- 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
                                    if(x+shift-this.m_data_pivot_point[i].ShiftX<0)
                                       x=-shift+m_data_pivot_point[i].ShiftX;
                                    if(x+shift+this.m_data_pivot_point[i].ShiftX>chart_width)
                                       x=chart_width-shift-this.m_data_pivot_point[i].ShiftX;
                                    if(y+shift+this.m_data_pivot_point[i].ShiftY<0)
                                       y=-shift-this.m_data_pivot_point[i].ShiftY;
                                    if(y+shift-this.m_data_pivot_point[i].ShiftY>chart_height)
                                       y=chart_height-shift+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);
                                   }
                                }
                             }
                          }
                       }
                    }
                 }
              }
            //--- Move the form by the obtained coordinates
            form.Move(x,y,true);
           }
        }
   
      //--- Display debugging comments on the chart
      Comment
        (
         (form!=NULL ? form.Name()+":" : ""),"\n",
         EnumToString((ENUM_CHART_EVENT)id),"\n",
         EnumToString(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)),
         "\n",EnumToString(mouse_state),
         "\npressed=",pressed,", move=",move,(form!=NULL ? ", Interaction="+(string)form.Interaction() : ""),
         "\npressed_chart=",pressed_chart,", pressed_form=",pressed_form,
         "\nform_index=",form_index,", graph_obj_id=",graph_obj_id
        );
      
      //--- If the cursor is not above the form
      if(form==NULL)
        {
         //--- If the mouse button is pressed
         if(pressed)
           {
            //--- If the button is still pressed and held on the form, exit
            if(pressed_form)
              {
               return;
              }
            //--- If the button hold flag is not enabled yet, set the flags and enable chart tools
            if(!pressed_chart)
              {
               pressed_chart=true;  // Button is held on the chart
               pressed_form=false;  // Cursor is not above the form
               move=false;          // movement disabled
               this.SetChartTools(::ChartID(),true);
              }
           }
         //--- If the mouse button is not pressed
         else
           {
            //--- Get the list of extended standard graphical objects
            CArrayObj *list_ext=GetListStdGraphObjectExt();
            //--- In the loop by all extended graphical objects,
            int total=list_ext.Total();
            for(int i=0;i<total;i++)
              {
               //--- get the next graphical object
               CGStdGraphObj *obj=list_ext.At(i);
               if(obj==NULL)
                  continue;
               //--- and redraw it without a point and a circle
               obj.RedrawControlPointForms(0,CTRL_POINT_COLOR);
              }
           }
        }
      //--- If the cursor is above the form
      else
        {
         //--- If the button is still pressed and held on the chart, exit
         if(pressed_chart)
           {
            return;
           }
         
         //--- If the flag of holding the button on the form is not set yet
         if(!pressed_form)
           {
            pressed_chart=false;    // The button is not pressed on the chart
            this.SetChartTools(::ChartID(),false);
            
            //--- 'The cursor is inside the form, no mouse buttons are clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED)
              {
               //--- If the cursor is above the form for managing the pivot point of an extended graphical object,
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- get the object by its ID and by the chart ID
                  CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID());
                  if(graph_obj!=NULL)
                    {
                     //--- Get the toolkit of an extended standard graphical object
                     CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit();
                     if(toolkit!=NULL)
                       {
                        //--- Draw a point with a circle on the form and delete it on all other forms
                        toolkit.DrawOneControlPoint(form);
                       }
                    }
                 }
              }
            //--- 'The cursor is inside the form, a mouse button is clicked (any)' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED)
              {
               this.SetChartTools(::ChartID(),false);
               //--- If the flag of holding the form is not set yet
               if(!pressed_form)
                 {
                  pressed_form=true;      // set the flag of pressing on the form
                  pressed_chart=false;    // disable the flag of pressing on the form
                 }
              }
            //--- 'The cursor is inside the form, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_WHEEL)
              {
               
              }
            
            
            //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED)
              {
               //--- Set the cursor shift relative to the form initial coordinates
               form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX());
               form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY());
               //--- If the cursor is above the active area of the form for managing the pivot point of an extended graphical object,
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- get the object by its ID and by the chart ID
                  CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID());
                  if(graph_obj!=NULL)
                    {
                     //--- Get the toolkit of an extended standard graphical object
                     CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit();
                     if(toolkit!=NULL)
                       {
                        //--- Draw a point with a circle on the form and delete it on all other forms
                        toolkit.DrawOneControlPoint(form);
                       }
                    }
                 }
              }
            //--- '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
                  this.ResetAllInteractionExeptOne(form);               // Reset interaction flags for all forms except the current one
                  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
                 }
              }
            //--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL)
              {
               
              }
            
            
            //--- 'The cursor is inside the window scrolling area, no mouse buttons are clicked' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED)
              {
               
              }
            //--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED)
              {
               
              }
            //--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL)
              {
               
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Neben der neuen Ereignisbehandlung für das Verschieben des zentralen Verwaltungsformulars habe ich auch den Aufruf der Methode hinzugefügt, die einen Punkt auf dem Objektformular unter dem Cursor zeichnet und diese Punkte auf anderen Formularen des grafischen Objekts löscht. Damit kann man vermeiden, dass gleichzeitig Punkte auf mehreren Formularobjekten gezeichnet werden, wenn diese nahe beieinander liegen und sich überlappen, wie oben gezeigt.

Derzeit ist alles bereit, um die neue Funktionsweise zu testen.


Test

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

Ich muss keine Änderungen in den EA einfügen — alle Änderungen werden erst einmal nur in den Bibliotheksklassen vorgenommen.

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


Wie wir sehen, funktionieren alle Beschränkungen in Bezug auf Angelpunkte, die die Grenzen des Charts überschreiten, korrekt, wenn wir das zusammengesetzte grafische Objekt in der Form verschieben, in der es erstellt wurde. Wenn wir jedoch die Position der Angelpunkte relativ zu ihrer ursprünglichen Position "umkehren", wird die "Konfiguration" des Objekts verzerrt, wenn der Angelpunkt die Grenzen des Charts überschreitet. Dies impliziert die falsche Berechnung der Einschränkungen und die Abhängigkeit davon, welcher Angelpunkt den rechten, linken, oberen oder unteren Rand des Charts überschreitet.
Dies ist nicht verwunderlich, da die Verschiebungen der Angelpunkte relativ zum zentralen Punkt berechnet werden, was bedeutet, dass ein Punkt eine positive Verschiebung hat, während der zweite Punkt eine negative Verschiebung hat. Der Fehler bei der Berechnung der Begrenzung tritt auf, wenn man die Lage der Angelpunkte relativ zum zentralen Punkt ändert. Ich werde dies im nächsten Artikel korrigieren.


Was kommt als Nächstes?

Im nächsten Artikel werde ich meine Arbeit an zusammengesetzten grafischen Objekten und ihrer Funktionsweisen 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

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/10584

Beigefügte Dateien |
MQL5.zip (4372.39 KB)
Mehrere Indikatoren in einem Chart (Teil 05): Umwandlung des MetaTrader 5 in ein RAD-System (I) Mehrere Indikatoren in einem Chart (Teil 05): Umwandlung des MetaTrader 5 in ein RAD-System (I)
Es gibt viele Menschen, die keine Ahnung vom Programmieren haben, aber sehr kreativ sind und tolle Ideen haben. Der Mangel an Programmierkenntnissen hindert sie jedoch daran, diese Ideen umzusetzen. Schauen wir uns gemeinsam an, wie man einen Chart Trade mit der MetaTrader 5 Plattform selbst erstellt, als wäre es eine IDE (integrierte Entwicklungsumgebung).
Mehrere Indikatoren in einem Chart (Teil 04): Weiterentwicklung zum Expert Advisor Mehrere Indikatoren in einem Chart (Teil 04): Weiterentwicklung zum Expert Advisor
In meinen früheren Artikeln habe ich erklärt, wie man einen Indikator mit mehreren Unterfenstern erstellt, was bei der Verwendung von nutzerdefinierten Indikatoren interessant wird. Dieses Mal werden wir sehen, wie man mehrere Fenster einem Expert Advisor hinzufügen kann.
Mehrere Indikatoren in einem Chart (Teil 06): Umwandlung des MetaTrader 5 in ein RAD-System (II) Mehrere Indikatoren in einem Chart (Teil 06): Umwandlung des MetaTrader 5 in ein RAD-System (II)
In meinem letzten Artikel habe ich Ihnen gezeigt, wie man einen Chart Trade mit MetaTrader 5 Objekten erstellt und so die Plattform in ein RAD-System verwandelt. Das System funktioniert sehr gut, und sicher haben viele der Leser über die Erstellung einer Bibliothek nachgedacht, die es ermöglichen würde, die Funktionsweise des vorgeschlagenen Systems zu erweitern. Auf dieser Grundlage wäre es möglich, einen intuitiveren Expert Advisor mit einer schöneren und einfacher zu bedienenden Oberfläche zu entwickeln.
Lernen Sie, wie Sie ein Handelssystem mit Hilfe des ATR entwickeln Lernen Sie, wie Sie ein Handelssystem mit Hilfe des ATR entwickeln
In diesem Artikel werden wir ein neues technisches Instrument kennenlernen, das beim Handel verwendet werden kann, als Fortsetzung der Serie, in der wir lernen, wie man einfache Handelssysteme entwickelt. Diesmal werden wir mit einem anderen beliebten technischen Indikator arbeiten: Average True Range (ATR).