English Русский Español Português
preview
DoEasy. Steuerung (Teil 33): Vertikale Bildlaufleiste

DoEasy. Steuerung (Teil 33): Vertikale Bildlaufleiste

MetaTrader 5Beispiele | 10 Juli 2024, 11:01
107 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Im vorangegangenen Artikel über grafische Bibliothekselemente haben wir eine horizontale Bildlaufleiste erstellt, die auf einem Objekt erscheint, wenn das an ein Formular angehängte Objekt links, rechts oder an beiden Rändern über die Grenzen des übergeordneten Formulars hinausragt. Hier werden wir eine vertikale Bildlaufleiste auf der Grundlage des horizontalen Bildlaufleistenobjekts erstellen. Sie erscheint auf dem Formular, wenn das daran befestigte Objekt oben, unten oder an beiden Seiten über die Grenzen des Formulars hinausgeht.

Der Artikel ist relativ kurz, eher ein Überblick, denn eine Kopie des horizontalen Scrollbar-Objekts zu erstellen und es in ein vertikales zu verwandeln, ist eine ziemlich einfache Aufgabe. Wir werden diese Bildlaufleisten später bei der Entwicklung weiterer Steuerelemente im Windows-Formularstil benötigen. Die vertikale Bildlaufleiste wurde im Grunde schon vor langer Zeit entwickelt, aber die Veröffentlichung des Artikels verzögerte sich aufgrund eines kleinen Fehlers, oder besser gesagt einer Auslassung, die zu sehr unangenehmen Artefakten bei der Interaktion mit grafischen Elementen führte, die sich in einem ständigen „Blinken“ unsichtbarer Objektteile äußerten. Dies geschah aufgrund einer unkontrollierten vorzeitigen Aktualisierung von Objekten, die anschließend auf die Größe ihres übergeordneten Objekts getrimmt wurden. Dieses „Blinken“ trat folgendermaßen auf: Zunächst wurde das Objekt vollständig gerendert und auf dem Chart angezeigt. Danach wurde es auf die Größe der Form des übergeordneten Objekts zugeschnitten. Wie in solchen Situationen üblich, erwies sich die Lösung als einfach - die vorzeitige Aktualisierung wurde durch eine Neuberechnung beseitigt. Aber es hat viel Zeit gekostet, den Ort zu finden, an das Neuzeichnen stattgefunden hat. Jetzt wurde dieser Fehler gefunden und behoben, sodass wir die Bibliothek sicher weiterentwickeln können.


Verbesserung der Bibliotheksklassen

Zunächst werden wir nützliche Funktionen und Methoden hinzufügen, die wir bei späteren Verbesserungen der Bibliothek benötigen werden.

Manchmal müssen wir eine Öffnungszeit einer Bar finden, in der ein Ereignis stattgefunden hat. Wenn das Ereignis in dem Moment eingetreten ist, in dem die Kerze geöffnet wurde, gibt es keine Probleme bei der Ermittlung der Uhrzeit. Wenn das Ereignis jedoch zwischen der Öffnungs- und der Schlusszeit der Kerze eingetreten ist, können Sie den Zeitpunkt dieses Ereignisses verwenden, um die Öffnungszeit der Kerze in einem bestimmten Zeitrahmen zu berechnen. Wir können natürlich Standardfunktionen verwenden, die Ereigniszeit in einen Balkenindex umwandeln und schließlich die Eröffnungszeit der gewünschten Kerze in dem gewünschten Zeitrahmen mit Hilfe des Balkenindexes ermitteln... Doch all dies erfordert CPU-Zeit. Wenn die Ausführungsgeschwindigkeit wichtig ist, ist es immer noch besser, die Berechnung zu verwenden, vorausgesetzt, das Ereignis trat innerhalb einer realen Kerze ein.

Das russischsprachige Forum hier hat einen nützlichen Thread, in dem Ressourcennutzer interessante Codes dieser Art austauschen. Machen wir uns den vorgeschlagenen Algorithmus zunutze und schreiben wir die Bibliotheksfunktion.

Wir implementieren die folgende Funktion ganz am Ende der Bibliotheksdatei \MQL5\Include\DoEasy\Services\DELib.mqh:

//+---------------------------------------------------------------------------------+
//| Get the opening time of the virtual bar based on input time and                 |
//| timeframe, regardless of the existence of a real bar.                           |
//| It counts correctly only till 28.02.2100                                        |
//| It is not a replacement for iBarShift!!! It does not depend on the bar history. |
//| https://www.mql5.com/de/forum/170952/page234#comment_50523898                   |
//+---------------------------------------------------------------------------------+
datetime GetStartTimeOfBarFast(const ENUM_TIMEFRAMES timeframe, const datetime time)
  {
   ENUM_TIMEFRAMES tf=(timeframe==PERIOD_CURRENT ? _Period : timeframe);

   int ts=0;
   if(tf<PERIOD_MN1)
     {
      ushort i_tf=ushort(tf);
      uchar _i=uchar(i_tf>>14);
      int n=i_tf & 0x0FFF;
      ts=(_i==0 ? n*60 : _i==1 ? n*60*60 : 60*60*24*7);
     }
   if(tf<PERIOD_W1)
      return time-time % ts;
   if(tf==PERIOD_W1)
      return time-(time+4*24*60*60) % ts;
   else // Period MN1
     {
      static int dm[12] = {0,31,61,92,122,153,184, 214, 245, 275, 306, 337};
      static int last_days = 0;
      static datetime last_result = 0;
      int days = int(time/(24*60*60));
      if(last_days!=days)
        {
         last_days = days;
         int d1 = (days+306+365)%1461;
         int y = d1/365;
         datetime t1 = time - time % (24*60*60) - d1*24*60*60;
         int m = 0;
         if(d1==1460)
           {
            m=11;
            y--;
           };
         int d = d1-y*365+1;
         if(d!=31)
            if(d==276)
               m = 9;
            else
               m = int(d/30.68);
         if(m<0 || m>11)
            return WRONG_VALUE;
         last_result = t1+y*365*24*60*60+dm[m]*24*60*60;
        }
      return last_result;
     }
  }
//+------------------------------------------------------------------+

Die Algorithmusanalyse finden Sie im Forum unter dem obigen Link. Mit dieser Funktion können wir dann immer die Öffnungszeit des Balkens finden, in dem ein Ereignis stattgefunden hat. Gleichzeitig müssen wir nicht auf unzureichend schnelle Funktionen zurückgreifen, wenn es auf die Rechengeschwindigkeit ankommt.

Bei der Arbeit mit grafischen Objekten ist es manchmal notwendig, die Farbe des grafischen Objekts je nach Situation zu ändern. Wir können natürlich Farben aus der Liste der Standardfarben verwenden, aber sie sind oft nicht ausreichend. Zum Beispiel gibt es ein bestimmtes Objekt, sagen wir, mit einer neutralen grauen Farbe. Je nach Situation kann sie ihren Tonfall leicht verändern. In einem Fall kann sie sich leicht rötlich verfärben, in einem anderen Fall leicht grünlich. Mit anderen Worten, in diesem Fall müssen wir nur die Sättigung der einen oder anderen Komponente der Farbe leicht erhöhen, anstatt Farben aus dem Standardsatz zu verwenden.

Um dies zu erreichen, deklarieren wir die folgende Methode in der Objektdatei \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh für grafische Elemente:

//--- Change the lightness of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorLightness(const uint clr,const double change_value);
   color             ChangeColorLightness(const color colour,const double change_value);
//--- Change the saturation of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorSaturation(const uint clr,const double change_value);
   color             ChangeColorSaturation(const color colour,const double change_value);
//--- Changes the color component of RGB-Color
   color             ChangeRGBComponents(color clr,const uchar R,const uchar G,const uchar B);
   

Nun schreiben wir die Implementierung außerhalb des Hauptteils der Klasse:

//+------------------------------------------------------------------+
//| Change the color component of RGB-Color                          |
//+------------------------------------------------------------------+
color CGCnvElement::ChangeRGBComponents(color clr,const uchar R,const uchar G,const uchar B)
  {
   double r=CColors::GetR(clr)+R;
   if(r>255)
      r=255;
   double g=CColors::GetG(clr)+G;
   if(g>255)
      g=255;
   double b=CColors::GetB(clr)+B;
   if(b>255)
      b=255;
   return CColors::RGBToColor(r,g,b);
  }
//+------------------------------------------------------------------+
//| Save the image to the array                                      |
//+------------------------------------------------------------------+

Hier ist alles ganz einfach - wir holen uns jede der an die Methode übergebenen Farbkomponenten, die geändert werden muss, und fügen die entsprechenden Werte, die an die Methode übergeben wurden, zu den Werten der resultierenden Komponenten hinzu. Wenn einer der Werte größer als 255 ist, wird er auf 255 gesetzt. Als Ergebnis geben wir die Farbe zurück, die sich aus den berechneten neuen Komponenten zusammensetzt, indem wir die Methode RGBToColor der Bibliotheksklasse CColor verwenden.

Hier gibt es in derselben Datei eine Methode, die die Koordinaten und Abmessungen des sichtbaren Bereichs des grafischen Elements festlegt:

//--- Set relative coordinates and size of the visible area
   void              SetVisibleArea(const int x,const int y,const int w,const int h)
                       {
                        this.SetVisibleAreaX(x,false);
                        this.SetVisibleAreaY(y,false);
                        this.SetVisibleAreaWidth(w,false);
                        this.SetVisibleAreaHeight(h,false);
                       }

Hinzu kommt die Möglichkeit, unabhängig voneinander anzugeben, wie der Umfang der Sichtbarkeit festgelegt wird - nur in den Eigenschaften eines grafischen Elementes oder in den Eigenschaften und in einem physischen Objekt. Dazu fügen wir einfach eine weitere Eingabevariable hinzu und legen den Methodenaufruf entsprechend fest, wodurch der Geltungsbereich auf die gesamte Objektgröße festgelegt wird:

//--- Set relative coordinates and size of the visible area
   void              SetVisibleArea(const int x,const int y,const int w,const int h,const bool only_prop)
                       {
                        this.SetVisibleAreaX(x,only_prop);
                        this.SetVisibleAreaY(y,only_prop);
                        this.SetVisibleAreaWidth(w,only_prop);
                        this.SetVisibleAreaHeight(h,only_prop);
                       }
//--- Sets the size of the visible area equal to the entire object
   void              ResetVisibleArea(void)                    { this.SetVisibleArea(0,0,this.Width(),this.Height(),false);            }


Bei der Implementierung, dem Löschen des Elements und dem Füllen mit Farbe und Deckkraft ohne Beschneidung, aber dem Aktualisieren des Charts durch das Flag, müssen wir die Objektaktualisierungslogik leicht ändern. Zuvor wurde das Objekt hier immer aktualisiert, unabhängig davon, ob das Flag zum Neuzeichnen des Charts gesetzt war:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//| without cropping and with the chart update by flag               |
//+------------------------------------------------------------------+
void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false)
  {
   color arr[1];
   arr[0]=colour;
   this.SaveColorsBG(arr);
   this.m_canvas.Erase(::ColorToARGB(colour,opacity));
   this.Update(redraw);
  }

Die Methode Update() mit dem Flag zum Neuzeichnen des Charts aktualisiert das Objekt immer, nachdem es vollständig mit der in den Parametern der Methode EraseNoCrop() angegebenen Farbe übermalt wurde. Dementsprechend wurde das Objekt unabhängig vom Flag zum Neuzeichnen immer aktualisiert (die vorgenommenen Änderungen wurden angezeigt). Das Flag zum Neuzeichnen wirkte sich nur auf die Änderungsanzeigezeit aus — entweder sofort (wenn das Flag auf true gesetzt war) oder bei Ankunft des Ticks oder bei der Aktualisierung des Charts (wenn das Flag auf false gesetzt war). Da diese Methode das gesamte Objekt vollständig neu einfärbt, kann es jederzeit in voller Größe auf dem Chart angezeigt werden. Wenn dieses Objekt auf die Größe des übergeordneten Objekts, an das es angehängt war, beschnitten werden sollte, dann verursachte dieses Neuzeichnen ein unangenehmes „Blinken“ des unsichtbaren Teils des Objekts, da das Beschneiden des unsichtbaren Teils immer nach dem Aufruf dieser Methode erfolgt.
Jetzt ist alles wieder in Ordnung. Es gibt kein Blinken des unsichtbaren Teils des Objekts mehr:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//| without cropping and with the chart update by flag               |
//+------------------------------------------------------------------+
void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false)
  {
   color arr[1];
   arr[0]=colour;
   this.SaveColorsBG(arr);
   this.m_canvas.Erase(::ColorToARGB(colour,opacity));
   if(redraw)
      this.Update(redraw);
  }

Hier wird das Objekt nur dann aktualisiert, wenn das Flag zum Neuzeichnen gesetzt ist. Dementsprechend können wir nun die Anzeige eines Objekts programmatisch steuern - wenn wir absolut sicher sind, dass das Objekt nicht beschnitten werden muss, dann rufen wir die Methode mit dem gesetzten Flag auf, und sein neues Aussehen wird sofort im Chart angezeigt. Wenn das Objekt beschnitten werden muss, wird diese Methode zunächst mit deaktivierter Markierung aufgerufen, und dann wird die Methode Crop() aufgerufen, die verdeckte Bereiche beschneidet und das Erscheinungsbild des Objekts aktualisiert, wobei das Chart entsprechend der Markierung neu gezeichnet wird. Dieser logische Fehler verhinderte die weitere Entwicklung der grafischen Elemente der Bibliothek. Der Fehler ist nun behoben.

Jetzt, in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\BarProgressBar.mqh, und zwar im Timer, beheben wir den Aufruf der Methode SetVisibleArea(), indem wir das erforderliche Flag angeben:

//--- ...
//--- ...

//--- If the object is in the normal state (hidden)
   if(glare.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_NORMAL)
     {
      //--- set the state of waiting for fading in to the object (in our case, waiting for a shift along the progress bar),
      //--- set the waiting duration and set the countdown time
      glare.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN);
      this.m_pause.SetWaitingMSC(this.ShowDelay());
      this.m_pause.SetTimeBegin();
      //--- If the right edge of the glare object is to the right of the left edge of the progress bar object
      if(glare.RightEdge()>=this.CoordX())
        {
         //--- Hide the glare object and move it beyond the right edge of the progress bar
         glare.Hide();
         if(glare.Move(this.CoordX()-glare.Width(),this.CoordY()))
           {
            //--- Set the relative coordinates of the glare object
            glare.SetCoordXRelative(glare.CoordX()-this.CoordX());
            glare.SetCoordYRelative(glare.CoordY()-this.CoordY());
            //--- and its visibility scope equal to the entire object
            glare.SetVisibleArea(0,0,glare.Width(),glare.Height(),false);
           }
        }
      return;
     }

//--- ...
//--- ...


Fast jedes Bibliotheksobjekt enthält ein grafisches Steuerobjekt, mit dem wir dynamisch grafische Objektformen erstellen können. Fügen wir nun die Methoden hinzu, mit denen wir einige grafische Standardobjekte erstellen können. Im öffentlichen Abschnitt der Datei für die Objektklasse \MQL5\Include\DoEasy\Objects\Graph\GraphElmControl.mqh deklarieren wir neue Methoden zum Zeichnen von Trendlinien und Pfeilen:

public:
//--- Return itself
   CGraphElmControl *GetObject(void)                  { return &this;               }
//--- Set a type of the object the graphics is constructed for
   void              SetTypeNode(const int type_node) { this.m_type_node=type_node; }
   
//--- Create a form object
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h);

//--- Creates the trend line standard graphical object
   bool              CreateTrendLine(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID);
   bool              CreateTrendLine(const string name,const int subwindow,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID);
   bool              CreateTrendLine(const string name,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID);

//--- Create the arrow standard graphical object
   bool              CreateArrow(const long chart_id,const string name,const int subwindow,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1);
   bool              CreateArrow(const string name,const int subwindow,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1);
   bool              CreateArrow(const string name,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1);

//--- Constructors
                     CGraphElmControl(){ this.m_type=OBJECT_DE_TYPE_GELEMENT_CONTROL; }
                     CGraphElmControl(int type_node);
  };


Im privaten Abschnitt deklarieren wir die Methode, die allgemeine Parameter für grafische Standardobjekte festlegt:

//+------------------------------------------------------------------+
//| Class for managing graphical elements                            |
//+------------------------------------------------------------------+
class CGraphElmControl : public CObject
  {
private:
   int               m_type;                          // Object type
   int               m_type_node;                     // Type of the object the graphics is constructed for
//--- Set general parameters for standard graphical objects
   void              SetCommonParamsStdGraphObj(const long chart_id,const string name);
public:
//--- Return itself
   CGraphElmControl *GetObject(void)                  { return &this;               }

Jedem neu erstellten Objekt sollten standardmäßig einige Eigenschaften zugewiesen werden, deren Bedeutung für alle erstellten grafischen Objekte ausnahmslos gleich ist: Das Objekt sollte in der Liste aller Chartobjekte ausgeblendet, nicht ausgewählt und nicht mit der Maus auswählbar sein, und es sollte in allen Zeitrahmen angezeigt werden. Dies sind die Eigenschaften, die von der Methode SetCommonParamsStdGraphObj festgelegt werden, deren Implementierung außerhalb des Klassenkörpers erfolgt:

//+------------------------------------------------------------------+
//|Set general parameters for standard graphical objects             |
//+------------------------------------------------------------------+
void CGraphElmControl::SetCommonParamsStdGraphObj(const long chart_id,const string name)
  {
   ::ObjectSetInteger(chart_id,name,OBJPROP_HIDDEN,true);
   ::ObjectSetInteger(chart_id,name,OBJPROP_SELECTED,false);
   ::ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
   ::ObjectSetInteger(chart_id,name,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS);
  }


Außerdem sollten wir die Implementierung von Methoden, die grafische Objekte erzeugen, außerhalb des Klassenkörpers schreiben:

//+------------------------------------------------------------------+
//| Create the trend line standard graphical object                  |
//| on a specified chart in a specified subwindow                    |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateTrendLine(const long chart_id,const string name,const int subwindow,
                                       const datetime time1,const double price1,
                                       const datetime time2,const double price2,
                                       color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
  {
   if(!CreateNewStdGraphObject(chart_id,name,OBJ_TREND,subwindow,time1,price1,time2,price2))
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),": ",StdGraphObjectTypeDescription(OBJ_TREND));
      return false;
     }
   this.SetCommonParamsStdGraphObj(chart_id,name);
   ::ObjectSetInteger(chart_id,name,OBJPROP_COLOR,clr);
   ::ObjectSetInteger(chart_id,name,OBJPROP_WIDTH,width);
   ::ObjectSetInteger(chart_id,name,OBJPROP_STYLE,style);
   return true;
  }
//+------------------------------------------------------------------+
//| Create the trend line standard graphical object                  |
//| on the current chart in a specified subwindow                    |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateTrendLine(const string name,const int subwindow,
                                       const datetime time1,const double price1,
                                       const datetime time2,const double price2,
                                       color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
  {
   return this.CreateTrendLine(::ChartID(),name,subwindow,time1,price1,time2,price2,clr,width,style);
  }
//+------------------------------------------------------------------+
//| Create the trend line standard graphical object                  |
//| on the current chart in the main window                          |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateTrendLine(const string name,
                                       const datetime time1,const double price1,
                                       const datetime time2,const double price2,
                                       color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
  {
   return this.CreateTrendLine(::ChartID(),name,0,time1,price1,time2,price2,clr,width,style);
  }
//+------------------------------------------------------------------+
//| Create the arrow standard graphical object                       |
//| on a specified chart in a specified subwindow                    |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateArrow(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,
                                   color clr,uchar arrow_code,int width=1)
  {
   if(!CreateNewStdGraphObject(chart_id,name,OBJ_ARROW,subwindow,time1,price1))
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),": ",StdGraphObjectTypeDescription(OBJ_ARROW));
      return false;
     }
   this.SetCommonParamsStdGraphObj(chart_id,name);
   ::ObjectSetInteger(chart_id,name,OBJPROP_COLOR,clr);
   ::ObjectSetInteger(chart_id,name,OBJPROP_WIDTH,width);
   ::ObjectSetInteger(chart_id,name,OBJPROP_ARROWCODE,arrow_code);
   return true;
  }
//+------------------------------------------------------------------+
//| Create the arrow standard graphical object                       |
//| on the current chart in a specified subwindow                    |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateArrow(const string name,const int subwindow,
                                   const datetime time1,const double price1,
                                   color clr,uchar arrow_code,int width=1)
  {
   return this.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width);
  }
//+------------------------------------------------------------------+
//| Create the arrow standard graphical object                       |
//| on the current chart in the main window                          |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateArrow(const string name,
                                   const datetime time1,const double price1,
                                   color clr,uchar arrow_code,int width=1)
  {
   return this.CreateArrow(::ChartID(),name,0,time1,price1,clr,arrow_code,width);
  }


Die Instanz eines Objekts der Verwaltungsklasse für grafische Objekte ist in jedem Bibliotheksobjekt enthalten, das von der Basisobjektklasse der CBaseObj-Bibliothek geerbt wurde. Das Verwaltungsobjekt für grafische Objekte verfügt über die Methoden zur Erstellung solcher Objekte. Damit wir grafische Objekte aus der Objektklasse erstellen können, müssen wir Methoden zur Erstellung grafischer Objekte in die Basisobjektklasse schreiben. Dies wird die Entwicklung von Grafiken in Anwendungen vereinfachen. Im Wesentlichen können wir zunächst den Zeiger auf das gewünschte Objekt abrufen, dann den Zeiger auf das Verwaltungsobjekt für grafische Objekte, und dann, durch Zugriff auf seine Methoden, grafische Objekte erstellen. Aber es ist ein weiter Weg. Es ist einfacher, bequemer und schneller, einfach einen Zeiger auf ein Objekt zu erhalten und dessen Methoden zu verwenden, um grafische Objekte zu erstellen, innerhalb derer die gesamte oben beschriebene Kette ausgeführt wird.

Im öffentlichen Abschnitt der Basisobjektdatei der Bibliothek \MQL5\Include\DoEasy\Objects\BaseObj.mqh, im Abschnitt für die Arbeit mit grafischen Objekten, deklarieren wir neue Methoden für die Erstellung von Trendlinien und Pfeilobjekten:

//+------------------------------------------------------------------+
//| Methods for handling graphical elements                          |
//+------------------------------------------------------------------+
//--- Create a form object on a specified chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,chart_id,wnd,name,x,y,w,h);                }
//--- Create a form object on the current chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,wnd,name,x,y,w,h);                         }
//--- Create the form object on the current chart in the main window
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,name,x,y,w,h);                             }
   
//--- Create a standard graphical trend line object in the specified subwindow of the specified chart
   bool              CreateTrendLine(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
                       { return this.m_graph_elm.CreateTrendLine(chart_id,name,subwindow,time1,price1,time2,price2,clr,width,style);   }
//--- Create a standard graphical trend line object in the specified subwindow of the current chart
   bool              CreateTrendLine(const string name,const int subwindow,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
                       { return this.m_graph_elm.CreateTrendLine(::ChartID(),name,subwindow,time1,price1,time2,price2,clr,width,style);}
//--- Create a standard graphical trend line object in the main window of the current chart
   bool              CreateTrendLine(const string name,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
                       { return this.m_graph_elm.CreateTrendLine(::ChartID(),name,0,time1,price1,time2,price2,clr,width,style);        }
   
//--- Create a standard arrow graphical object in the specified subwindow of the specified chart
   bool              CreateArrow(const long chart_id,const string name,const int subwindow,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1)
                       { return this.m_graph_elm.CreateArrow(chart_id,name,subwindow,time1,price1,clr,arrow_code,width);               }
//--- Create a standard arrow graphical object in the specified subwindow of the current chart
   bool              CreateArrow(const string name,const int subwindow,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1)
                       { return this.m_graph_elm.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width);            }
//---  Create a standard arrow graphical object in the main window of the current chart
   bool              CreateArrow(const string name,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1)
                       { return this.m_graph_elm.CreateArrow(::ChartID(),name,0,time1,price1,clr,arrow_code,width);                    }
   
//--- Constructor

Die entsprechenden Methoden des Grafiksteuerungsobjekts werden einfach in den Methoden aufgerufen. In Zukunft werden wir Methoden zur Erstellung anderer grafischer Standardobjekte hinzufügen. Für den Moment werden diese grafischen Objekte ausreichen, um sie in den folgenden Artikeln zu verwenden.

Beginnen wir mit der Erstellung eines vertikalen Scrollbar-Objekts.

Ein Rollbalken-Greifbereich ist ein Schieberegler, den Sie mit der Maus greifen und innerhalb des Rollbalkens verschieben können, wodurch der von ihm kontrollierte Bereich verschoben wird. Wenn das Mausrad in die eine oder andere Richtung gedreht wird, während sich der Cursor in der Bildlaufleiste befindet, wird ein Klick-Ereignis auf der entsprechenden Bildlauftaste (Pfeilschaltflächen an den Rändern der Bildlaufleiste) erzeugt. Die Ereignisse werden bereits erzeugt, wenn das Mausrad für die horizontale Bildlaufleiste gescrollt wird - Ereignisse beim Klicken auf die rechte und linke Pfeiltaste. Nun müssen wir die Ereignisgenerierung für das Anklicken der Aufwärts- und Abwärtspfeiltasten für den vertikalen Schieberegler hinzufügen.

Nehmen wir in der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh, und zwar in der Ereignisbehandlung von „Der Cursor befindet sich im aktiven Bereich, das Mausrad wird gescrollt“, die folgenden Änderungen vor:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
   base.BringToTop();
   ENUM_WF_CONTROL_EVENT evn=WF_CONTROL_EVENT_NO_EVENT;
   switch(base.TypeGraphElement())
     {
      case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL: evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : WF_CONTROL_EVENT_NO_EVENT); break;
      case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL  : evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP   : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN  : WF_CONTROL_EVENT_NO_EVENT); break;
      default                                         : break;
     }
   base.OnChartEvent(evn,lparam,dparam,sparam);
   ::ChartRedraw(base.ChartID());
  }

Je nachdem, welches Objekt das Basisobjekt für den Erfassungsbereich ist (die horizontale oder vertikale Bildlaufleiste), wird die Ereignisbehandlung des Basisobjekts aufgerufen und die entsprechenden Event-Codes übergeben: entweder ein Mausklick auf die linke oder rechte Pfeiltaste oder ein Mausklick auf die Pfeil-nach-oben- oder Pfeil-nach-unten-Taste.

Um ein vertikales Scrollbar-Objekt zu erstellen, nehmen Sie die horizontale Scrollbar-Objektklassendatei \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh und speichern Sie sie als \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarVertical.mqh. Da eine neue Klasse auf der Grundlage einer identischen Klasse erstellt wird, müssen wir nur einige Berechnungen ersetzen: Statt „links/rechts“ in den Berechnungen verwenden wir „oben/unten“ usw. Es macht keinen Sinn, jede Änderung zu beschreiben. Lesen Sie den entsprechenden Artikel, um mehr über die Erstellung eines solchen Objekts zu erfahren. Hier werden wir nur die gesamte Klassendatei mit den bereits vorgenommenen Änderungen betrachten:

//+------------------------------------------------------------------+
//|                                            ScrollBarVertical.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 "ScrollBarThumb.mqh"
#include "ArrowDownButton.mqh"
#include "ArrowUpButton.mqh"
#include "ScrollBar.mqh"
//+------------------------------------------------------------------+
//| CScrollBarVertical object class of WForms controls               |
//+------------------------------------------------------------------+
class CScrollBarVertical : public CScrollBar
  {
private:
//--- Create the ArrowButton objects
   virtual void      CreateArrowButtons(const int width,const int height);
//--- Calculate the distance of the capture area (slider)
   int               CalculateThumbAreaDistance(const int thumb_size);
protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CScrollBarVertical(const ENUM_GRAPH_ELEMENT_TYPE type,
                                        CGCnvElement *main_obj,CGCnvElement *base_obj,
                                        const long chart_id,
                                        const int subwindow,
                                        const string descript,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h);
                                        
//--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler
   virtual void      MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
public:
//--- Supported object properties (1) integer, (2) real and (3) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Return the button with the (1) up, (2) down arrow
   CArrowUpButton   *GetArrowButtonUp(void)     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0);      }
   CArrowDownButton *GetArrowButtonDown(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0);    }

//--- Return the size of the slider working area
   int               BarWorkAreaSize(void);
//--- Return the coordinate of the beginning of the slider working area
   int               BarWorkAreaCoord(void);
   
//--- Set the new size
   virtual bool      Resize(const int w,const int h,const bool redraw);
//--- Calculate and set the parameters of the capture area (slider)
   int               SetThumbParams(void);

//--- Constructor
                     CScrollBarVertical(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                        const long chart_id,
                                        const int subwindow,
                                        const string descript,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h);
//--- Timer
   virtual void      OnTimer(void);
//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CScrollBarVertical::CScrollBarVertical(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       CGCnvElement *main_obj,CGCnvElement *base_obj,
                                       const long chart_id,
                                       const int subwindow,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h) : CScrollBar(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.CreateThumbArea();
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CScrollBarVertical::CScrollBarVertical(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                       const long chart_id,
                                       const int subwindow,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h) : CScrollBar(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL);
   this.CreateThumbArea();
  }
//+------------------------------------------------------------------+
//| Create the ArrowButton objects                                   |
//+------------------------------------------------------------------+
void CScrollBarVertical::CreateArrowButtons(const int width,const int height)
  {
//--- Set the size of the buttons equal to the width of the scrollbar without the size of its frame
   int size=this.Thickness()-this.BorderSizeLeft()-this.BorderSizeRight();
//--- Create the buttons with up and down arrows and the area capture object. The arrow size is set to 2
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,  0,0,size,size,this.BackgroundColor(),255,true,false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0,this.Height()-height,size,size,this.BackgroundColor(),255,true,false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB,0,this.Height()/2-height,size,30,CLR_DEF_CONTROL_SCROLL_BAR_THUMB_COLOR,255,true,false);
   this.SetArrowSize(2);
//--- Get the pointer to the up arrow button and set the colors of its various states for it
   CArrowUpButton *bu=this.GetArrowButtonUp();
   if(bu!=NULL)
     {
      bu.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_COLOR,true);
      bu.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_DOWN);
      bu.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_OVER);
      bu.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_COLOR,true);
      bu.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_DOWN);
      bu.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_OVER);
     }
//--- Get the pointer to the down arrow button and set the colors of its various states for it
   CArrowDownButton *bd=this.GetArrowButtonDown();
   if(bd!=NULL)
     {
      bd.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_COLOR,true);
      bd.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_DOWN);
      bd.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_OVER);
      bd.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_COLOR,true);
      bd.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_DOWN);
      bd.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_OVER);
     }
//--- Get the pointer to the capture area object and set the colors of its various states for it
   CScrollBarThumb *th=this.GetThumb();
   if(th!=NULL)
     {
      th.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_COLOR,true);
      th.SetBorderColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_BORDER_COLOR,true);
      th.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_MOUSE_DOWN);
      th.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_MOUSE_OVER);
      th.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_COLOR,true);
      th.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_MOUSE_DOWN);
      th.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_MOUSE_OVER);
     }
  }
//+------------------------------------------------------------------+
//| Set the new size                                                 |
//+------------------------------------------------------------------+
bool CScrollBarVertical::Resize(const int w,const int h,const bool redraw)
  {
//--- If failed to change the object size, return 'false'
   if(!CWinFormBase::Resize(w,h,redraw))
      return false;
//--- Get the button object with the down arrow
   CArrowDownButton *bd=this.GetArrowButtonDown();
//--- If the button is not received, return 'false'
   if(bd==NULL)
      return false;
//--- Move the button to the bottom edge of the scrollbar
   if(bd.Move(bd.CoordX(),this.BottomEdge()-this.BorderSizeBottom()-bd.Height()))
     {
      //--- Set new relative coordinates for the button
      bd.SetCoordXRelative(bd.CoordX()-this.CoordX());
      bd.SetCoordYRelative(bd.CoordY()-this.CoordY());
     }
//--- Set the slider parameters
   this.SetThumbParams();
//--- Successful
   return true;
  }
//+------------------------------------------------------------------+
//| Calculate and set the parameters of the capture area (slider)    |
//+------------------------------------------------------------------+
int CScrollBarVertical::SetThumbParams(void)
  {
//--- Get the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return 0;
//--- Get the capture area object (slider)
   CScrollBarThumb *thumb=this.GetThumb();
   if(thumb==NULL)
      return 0;
//--- Get the height size of the visible part inside the container
   int base_h=base.HeightWorkspace();
//--- Calculate the total height of all attached objects
   int objs_h=base_h+base.OversizeTop()+base.OversizeBottom();
//--- Calculate the relative size of the visible part window
   double px=(double)base_h/double(objs_h!=0 ? objs_h : 1);
//--- Calculate and adjust the size of the slider relative to the height of its workspace (not less than the minimum size)
   int thumb_size=(int)::floor(this.BarWorkAreaSize()*px);
   if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN)
      thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
   if(thumb_size>this.BarWorkAreaSize())
      thumb_size=this.BarWorkAreaSize();
//--- Calculate the coordinate of the slider and change its size to match the previously calculated one
   int thumb_y=this.CalculateThumbAreaDistance(thumb_size);
   if(!thumb.Resize(thumb.Width(),thumb_size,true))
      return 0;
//--- Shift the slider by the calculated Y coordinate
   if(thumb.Move(thumb.CoordX(),this.BarWorkAreaCoord()+thumb_y))
     {
      thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
      thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
     }
//--- Return the calculated slider size
   return thumb_size;
  }
//+------------------------------------------------------------------+
//| Calculate the distance of the capture area (slider)              |
//+------------------------------------------------------------------+
int CScrollBarVertical::CalculateThumbAreaDistance(const int thumb_size)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return 0;
   double x=(double)thumb_size/(double)base.HeightWorkspace();
   return (int)::ceil((double)base.OversizeTop()*x);
  }
//+------------------------------------------------------------------+
//| Return the size of the slider working area                       |
//+------------------------------------------------------------------+
int CScrollBarVertical::BarWorkAreaSize(void)
  {
   CArrowUpButton  *bu=this.GetArrowButtonUp();
   CArrowDownButton *bd=this.GetArrowButtonDown();
   int y1=(bu!=NULL ? bu.BottomEdge() : this.CoordY()+this.BorderSizeTop());
   int y2=(bd!=NULL ? bd.CoordY() : this.BottomEdge()-this.BorderSizeBottom());
   return(y2-y1);
  }
//+------------------------------------------------------------------+
//| Return the coordinate of the beginning of the slider working area|
//+------------------------------------------------------------------+
int CScrollBarVertical::BarWorkAreaCoord(void)
  {
   CArrowUpButton  *bu=this.GetArrowButtonUp();
   return(bu!=NULL ? bu.BottomEdge() : this.CoordY()+this.BorderSizeTop());
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CScrollBarVertical::OnTimer(void)
  {

  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CScrollBarVertical::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- Get the pointers to control objects of the scrollbar
   CArrowUpButton  *buttu=this.GetArrowButtonUp();
   CArrowDownButton *buttd=this.GetArrowButtonDown();
   CScrollBarThumb   *thumb=this.GetThumb();
   if(buttu==NULL || buttd==NULL || thumb==NULL)
      return;
//--- If the event ID is an object movement
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Move the scrollbar to the foreground
      this.BringToTop();
      //--- Declare the variables for the coordinates of the capture area
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Set the X coordinate equal to the X coordinate of the control element
      x=this.CoordX()+this.BorderSizeLeft();
      //--- Adjust the Y coordinate so that the capture area does not go beyond the control, taking into account the arrow buttons
      if(y<buttu.BottomEdge())
        y=buttu.BottomEdge();
      if(y>buttd.CoordY()-thumb.Height())
        y=buttd.CoordY()-thumb.Height();
      //--- If the capture area object is shifted by the calculated coordinates
      if(thumb.Move(x,y,true))
        {
         //--- set the object relative coordinates
         thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
         thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
        }
      //--- Get the pointer to the base object
      CWinFormBase *base=this.GetBase();
      if(base!=NULL)
        {
         //--- Check if the content goes beyond the container
         base.CheckForOversize();

         //--- Calculate the distance the slider is from the upper border of the scrollbar (from the bottom side of the upper arrow button)
         int distance=thumb.CoordY()-buttu.BottomEdge();
         
         //--- Declare a variable that stores the distance value before the slider shift
         static int distance_last=distance;
         //--- Declare a variable that stores the value in screen pixels the slider was shifted by
         int shift_value=0;
         
         //--- If the values of the past and current distances are not equal (the slider is shifted),
         if(distance!=distance_last)
           {
            //--- calculate the value the slider is shifted by
            shift_value=distance_last-distance;
            //--- and enter the new distance into the value of the previous distance for the next calculation
            distance_last=distance;
           }
         
         //--- Get the largest and smallest coordinates of the lower and upper sides of the base object content
         int cntt_d=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_BOTTOM);
         int cntt_u=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_Y);

         //--- Get the coordinate offset of the upper side of the base object content
         //--- relative to the initial coordinate of the base object working area
         int extu=base.CoordYWorkspace()-cntt_u;
         
         //--- Calculate the relative value of the desired coordinate,
         //--- where the contents of the base object, shifted by the slider, should be located
         double y=(double)this.HeightWorkspace()*(double)distance/double(thumb.Height()!=0 ? thumb.Height() : DBL_MIN);
         
         //--- Calculate the required shift value of the base object content along the above calculated coordinate 
         int shift_need=extu-(int)::round(y);
         
         //--- If the slider is shifted upwards (positive shift value)
         if(shift_value>0)
           {
            if(cntt_u+shift_need<=base.CoordYWorkspace())
               base.ShiftDependentObj(0,shift_need);
           }
         //--- If the slider is shifted downwards (negative shift value)
         if(shift_value<0)
           {
            if(cntt_d-shift_need>=base.BottomEdgeWorkspace())
               base.ShiftDependentObj(0,shift_need);
           }
         ::ChartRedraw(this.ChartID());
        }
     }
//--- If any scroll button is clicked
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP || id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN)
     {
      //--- Move the scrollbar to the foreground
      this.BringToTop();
      //--- Get the base object
      CWinFormBase *base=this.GetBase();
      if(base==NULL)
         return;
      //--- Calculate how much each side of the content of the base object goes beyond its borders
      base.CheckForOversize();
      //--- Get the largest and smallest coordinates of the lower and upper sides of the base object content
      int cntt_d=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_BOTTOM);
      int cntt_u=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_Y);
      //--- Set the number of pixels, by which the content of the base object should be shifted
      int shift=(sparam!="" ? DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK : DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL);
      //--- If the up button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP)
        {
         if(cntt_u+shift<=base.CoordYWorkspace())
            base.ShiftDependentObj(0,shift);
        }
      //--- If the down button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN)
        {
         if(cntt_d-shift>=base.BottomEdgeWorkspace())
            base.ShiftDependentObj(0,-shift);
        }
      //--- Calculate the width and coordinates of the slider
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
void CScrollBarVertical::MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   ENUM_WF_CONTROL_EVENT evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT);
   this.OnChartEvent(evn,lparam,dparam,sparam);
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Nachdem bereits Änderungen an dieser Datei vorgenommen wurden, erscheint ein vertikaler Rollbalken in Objekten, an die untergeordnete Objekte angehängt sind und die oben, unten oder an beiden Seiten gleichzeitig über das übergeordnete Objekt hinausragen.

Damit bei einem übergeordneten Objekt, das als Container für seine untergeordneten Objekte dient, Bildlaufleisten angezeigt werden, müssen wir bei der Erstellung eines mit ihm verbundenen Objekts prüfen, ob es über die Grenzen seines Containers hinausreicht. Diese Prüfung wurde bereits implementiert, um eine horizontale Bildlaufleiste anzuzeigen. Nun müssen wir die Container-Objektklasse so ändern, dass beide Rollbalken erscheinen, wenn ein an den Container angehängtes Objekt auf beiden Seiten über seine Grenzen hinausragt.

Wir öffnen die Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh und fügen in der Methode zur Erstellung von angehängten Objekten die erforderlichen Prüfungen und die Anzeige der Bildlaufleisten hinzu:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h,
                                  const color colour,
                                  const uchar opacity,
                                  const bool activity,
                                  const bool redraw)
  {
//--- If the object type is less than the base WinForms object
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- report the error and return 'false'
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- If failed to create a new graphical element, return 'false'
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Set parameters for the created object
   this.SetObjParams(obj,colour);
//--- If there are bound objects
   if(this.ElementsTotal()>0)
     {
      //--- If the panel has auto resize enabled, call the auto resize method
      if(this.AutoSize())
         this.AutoSizeProcess(redraw);
      //--- If auto resize is disabled, determine whether scrollbars should be displayed 
      else
        {
         if(this.CheckForOversize())
           {
            //--- If the attached objects go beyond the visibility window to the left or right
            if(this.OversizeLeft()>0 || this.OversizeRight()>0)
              {
               CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal();
               if(sbh!=NULL)
                 {
                  sbh.SetThumbParams();
                  sbh.SetDisplayed(true);
                  sbh.Show();
                 }
              }
            //--- If the attached objects go beyond the visibility window from above or below
            if(this.OversizeTop()>0 || this.OversizeBottom()>0)
              {
               CScrollBarVertical *sbv=this.GetScrollBarVertical();
               if(sbv!=NULL)
                 {
                  sbv.SetThumbParams();
                  sbv.SetDisplayed(true);
                  sbv.Show();
                 }
              }
           }
        }
     }
//--- Crop the created object along the edges of the visible part of the container
   obj.Crop();
//--- return 'true'
   return true;
  }


Nun müssen wir einige Klassen korrigieren, die die Methode des Neuzeichnens aufrufen, ohne das Objekt EraseNoCrop() zu beschneiden. Wir müssen false setzen, damit das Objekt innerhalb der Methode nicht aktualisiert wird.

Es müssen Änderungen vorgenommen werden an den Redraw()-Methoden von drei Objekten in drei Bibliotheksdateien \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh und \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh.

Alle Veränderungen laufen darauf hinaus, das Flag auf false zu setzen:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CButton::Redraw(bool redraw)
  {
//--- Fill the object with the background color
   this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),false);
//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment
   int x=0,y=0;
   CLabel::SetTextParamsByAlign(x,y);
//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Crop();
   this.Update(redraw);
  }

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CCheckBox::Redraw(bool redraw)
  {
//--- Fill the object with the background color having full transparency
   this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),false);
//--- Set corrected text coordinates relative to the checkbox
   this.SetCorrectTextCoords();
//--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object 
   this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.ShowControlFlag(this.CheckState());
   this.Crop();
   this.Update(redraw);
  }

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CLabel::Redraw(bool redraw)
  {
//--- Fill the object with the background color having full transparency
   this.EraseNoCrop(this.BackgroundColor(),0,false);
//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment
   int x=0,y=0;
   this.SetTextParamsByAlign(x,y);
//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Crop();
   this.Update(redraw);
  }

Die Logik ist wie folgt: Zunächst wird das gesamte Objekt mit Farbe gefüllt, während das Aktualisierungsflag zurückgesetzt wird, d. h. die Änderungen werden nicht im Chart angezeigt. Dann wird der Text mit den angegebenen Parametern gezeichnet. Anschließend wird das Objekt entlang der Ränder des sichtbaren Bereichs beschnitten (falls erforderlich), und nach Abschluss wird die Objektaktualisierungsmethode aufgerufen, die die an der Objektdarstellung vorgenommenen Änderungen auf dem übergeordneten Objekt im Besonderen und dem Chart im Allgemeinen anzeigt.


Test

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

Bei der Erstellung eines Schaltflächenobjekts, das mit dem Bedienfeld verbunden ist, sollten wir seine Abmessungen so ändern, dass es in der Vertikalen größer ist als sein übergeordnetes Objekt. Mit anderen Worten, er sollte nach oben und unten über die Ränder hinausgehen und dabei von der Breite her vollständig in das übergeordnete Feld passen:

//--- Create the required number of WinForms Panel objects
   CPanel *pnl=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
      if(pnl!=NULL)
        {
         pnl.Hide();
         //--- Set Padding to 4
         pnl.SetPaddingAll(3);
         //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
         pnl.SetMovable(InpMovable);
         pnl.SetAutoSize(InpAutoSize,false);
         pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
         //---
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,10,-40,pnl.WidthWorkspace()-30,pnl.HeightWorkspace()+50,clrNONE,255,true,false);
         CButton *btn=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,0);
         btn.SetText("123456789012345678901234567890");
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,40,20,60,20,clrDodgerBlue,255,false,false);
         CLabel *lbl=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LABEL,0);
         lbl.SetText("LABEL");

Das war's. Weitere Änderungen sind nicht erforderlich.

Wir kompilieren den EA und starten ihn auf dem Chart, wobei wir vorher No (Nein) für Panel Autosize angeben:


Wir sehen, dass die vertikale Bildlaufleiste genauso funktioniert wie die horizontale, die im vorherigen Artikel implementiert wurde.


Was kommt als Nächstes?

Im nächsten Artikel, der sich mit der Erstellung von grafischen Steuerelementen für die DoEasy-Bibliothek befasst, werden wir die beiden Scrollbars mit einem Container-Objekt verbinden und mit der Erstellung weiterer Steuerelemente fortfahren.

Alle Dateien sind dem Artikel beigefügt und Sie können sie selbst studieren und testen.


Zurück zum Inhalt


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

Beigefügte Dateien |
MQL5.zip (5257.78 KB)
Entwicklung eines Replay Systems (Teil 39): Den Weg ebnen (III) Entwicklung eines Replay Systems (Teil 39): Den Weg ebnen (III)
Bevor wir zur zweiten Stufe der Entwicklung übergehen, müssen wir einige Ideen überarbeiten. Wissen Sie, wie Sie MQL5 dazu bringen können, das zu tun, was Sie brauchen? Haben Sie jemals versucht, über das hinauszugehen, was in der Dokumentation enthalten ist? Wenn nicht, dann machen Sie sich bereit. Denn wir werden etwas tun, was die meisten Menschen normalerweise nicht tun.
MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 22): Conditional GANs MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 22): Conditional GANs
Generative Adversarial Networks (GAN) sind eine Kombination von neuronalen Netzen, die sich gegenseitig trainieren, um genauere Ergebnisse zu erzielen. Wir nehmen den bedingten Typ dieser Netze an, da wir eine mögliche Anwendung bei der Vorhersage von Finanzzeitreihen innerhalb einer Klasse von Expertensignalen anstreben.
Entwicklung eines Replay Systems (Teil 40): Beginn der zweiten Phase (I) Entwicklung eines Replay Systems (Teil 40): Beginn der zweiten Phase (I)
Heute werden wir über die neue Phase des Replay/Simulator-Systems sprechen. In dieser Phase wird das Gespräch wirklich interessant und sehr inhaltsreich. Ich empfehle Ihnen dringend, den Artikel sorgfältig zu lesen und die darin enthaltenen Links zu nutzen. Dies wird Ihnen helfen, den Inhalt besser zu verstehen.
Eine Schritt-für-Schritt-Anleitung zum Handel mit der Break of Structure (BoS)-Strategie Eine Schritt-für-Schritt-Anleitung zum Handel mit der Break of Structure (BoS)-Strategie
Ein umfassender Leitfaden für die Entwicklung eines automatisierten Handelsalgorithmus auf der Grundlage der Break of Structure (BoS)-Strategie. Detaillierte Informationen zu allen Aspekten der Erstellung eines Advisors in MQL5 und dessen Test in MetaTrader 5 - von der Analyse von Preisunterstützung und -widerstand bis hin zum Risikomanagement