English Русский 中文 Español 日本語 Português
preview
DoEasy. Steuerung (Teil 31): Scrollen des Inhalts des ScrollBar-Steuerelements

DoEasy. Steuerung (Teil 31): Scrollen des Inhalts des ScrollBar-Steuerelements

MetaTrader 5Beispiele | 24 Februar 2023, 09:56
322 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Die Bildlaufleiste kann auf das Drücken von Tasten und das Bewegen von Schiebereglern reagieren. Es gibt jedoch keine weiteren Maßnahmen. Das gewünschte Verhalten ist, dass sich der Inhalt des Containers innerhalb des Containers verschiebt, wenn man auf die Bildlauftasten klickt, wodurch zuvor verborgene Bereiche sichtbar werden und zuvor angezeigte Bereiche auf der gegenüberliegenden Seite des Containers ausgeblendet werden. In diesem Artikel werde ich die Möglichkeit schaffen, den Inhalt des Containers beim Klicken auf die Schaltflächen der horizontalen Bildlaufleiste zu verschieben. In diesem Fall passt der Schieberegler seine Größe und Position automatisch an.

Der Schieberegler für die Bildlaufleiste ist nicht nur ein Teil des Elements, mit dem man die Position des Formularinhalts steuern kann, indem man ihn innerhalb des Containers verschiebt. Sie dient auch als schematische Darstellung der relativen Position des Behälters und seines Inhalts. Die Bildlaufleiste selbst entspricht der Breite des gesamten Inhalts des Containers, während der Schieberegler auf der Bildlaufleiste die Breite des Containers darstellt. Je mehr Inhalt außerhalb des Containers liegt, desto kleiner wird der Schieberegler. Die Schiebereglergröße gibt das Fenster an, in dem der Inhalt sichtbar ist, während die Bildlaufleiste den gesamten Inhalt des Containers anzeigt. Indem wir den Schieberegler entlang der Bildlaufleiste bewegen, zeigen wir dem Programm, welchen Inhalt innerhalb des Containers wir gerade sehen wollen.

Auf die gleiche Weise können wir die Position des Inhalts mit den Pfeilschaltflächen an den Rändern der Bildlaufleiste steuern. Gleichzeitig werden sowohl der Inhalt des Containers selbst als auch der Schieberegler auf der Bildlaufleiste verschoben, was uns zeigt, welcher Teil des gesamten Inhalts gerade angezeigt wird.

In diesem Artikel werde ich die Möglichkeit implementieren, den Inhalt mit den Pfeiltasten der horizontalen Bildlaufleiste zu verschieben. Der Schieberegler bewegt sich dann und hat die richtige relative Größe und Positionskoordinaten auf der Bildlaufleiste. Zunächst werde ich die Funktionalität einer horizontalen Bildlaufleiste entwickeln. Dann übertrage ich sie in einer vorgefertigten Form auf die vertikale Leiste und lasse sie zusammenarbeiten.


Verbesserung der Bibliotheksklassen

Da der Schieberegler automatisch seine Größe anpasst, je nachdem, wie weit der Inhalt des Containers über seine Grenzen hinausgeht, kann der Schieberegler zu klein werden, wenn er stark verkleinert wird. Um diese Situation zu vermeiden, sollten wir die Mindestgröße des Schiebereglers festlegen. Beim Verschieben des Containerinhalts müssen wir die Schrittgröße in Pixeln festlegen, um die sich der Containerinhalt in einem Schritt bewegen kann. In MetaEditor beträgt dieser Schritt zum Beispiel sechs Pixel. Machen wir ihn kleiner - zwei Pixel.

In \MQL5\Include\DoEasy\Defines.mqh erstellen wir zwei neue Makrosubstitutionen, um die oben genannten Parameter anzugeben:

#define DEF_CONTROL_SCROLL_BAR_WIDTH                  (11)                 // Default ScrollBar control width
#define DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN         (8)                  // Minimum size of the capture area (slider)
#define DEF_CONTROL_SCROLL_BAR_SCROLL_STEP            (2)                  // Shift step in pixels of the container content when scrolling
#define DEF_CONTROL_CORNER_AREA                       (4)                  // Number of pixels defining the corner area to resize
#define DEF_CONTROL_LIST_MARGIN_X                     (1)                  // Gap between columns in ListBox controls
#define DEF_CONTROL_LIST_MARGIN_Y                     (0)                  // Gap between rows in ListBox controls


Wenn wir Daten aus den Objekteigenschaften „oberer, unterer, linker und rechter Rand eines grafischen Elements“ abrufen wollen, holen wir sie aus den Eigenschaften des Objekts. Diese Parameter weisen die Werte auf, die bei der erfolgreichen Erstellung des Objekts implementiert wurden. Wenn wir nun die Objektgröße ändern, werden die Daten nicht mehr in den Eigenschaften des grafischen Elements gespeichert. Natürlich erhalten wir Werte, wenn wir sie mit einer der Methoden anfordern, die diese Eigenschaft zurückgeben, wie z. B. BottomEdge(), aber diese Methode gibt einfach den berechneten Wert zurück. Diese Werte ändern sich jedoch nicht in den Objekteigenschaften. Dies sollte behoben werden. In der Datei \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh der Bibliothek grafisches Basiselement, und zwar in allen Methoden, die die Objektgrenzen ändern, implementieren wir das Setzen der neuen Werte für die Objekteigenschaften:

//+-------------------------------------------+
//| Set the new  X coordinate                 |
//+-------------------------------------------+
bool CGCnvElement::SetCoordX(const int coord_x)
  {
   int x=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE);
   if(coord_x==x)
     {
      if(coord_x==this.GetProperty(CANV_ELEMENT_PROP_COORD_X))
         return true;
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x);
      this.SetRightEdge();
      return true;
     }
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,coord_x))
     {
      this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x);
      this.SetRightEdge();
      return true;
     }
   return false;
  }
//+-------------------------------------------+
//| Set the new Y coordinate                  |
//+-------------------------------------------+
bool CGCnvElement::SetCoordY(const int coord_y)
  {
   int y=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE);
   if(coord_y==y)
     {
      if(coord_y==this.GetProperty(CANV_ELEMENT_PROP_COORD_Y))
         return true;
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y);
      this.SetBottomEdge();
      return true;
     }
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,coord_y))
     {
      this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y);
      this.SetBottomEdge();
      return true;
     }
   return false;
  }
//+-------------------------------------------+
//| Set a new width                           |
//+-------------------------------------------+
bool CGCnvElement::SetWidth(const int width)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width)
      return true;
   if(!this.m_canvas.Resize(width,this.m_canvas.Height()))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width);
   this.SetVisibleAreaX(0,true);
   this.SetVisibleAreaWidth(width,true);
   this.SetRightEdge();
   return true;
  }
//+-------------------------------------------+
//| Set a new height                          |
//+-------------------------------------------+
bool CGCnvElement::SetHeight(const int height)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height)
      return true;
   if(!this.m_canvas.Resize(this.m_canvas.Width(),height))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height);
   this.SetVisibleAreaY(0,true);
   this.SetVisibleAreaHeight(height,true);
   this.SetBottomEdge();
   return true;
  }
//+------------------------------------------------------------------+

Jetzt werden bei jeder Änderung der Größe oder der Koordinaten eines Objekts die Koordinaten seiner Seiten in die Eigenschaften des grafischen Elements übernommen. Dies ermöglicht es uns, ein gewünschtes Objekt anhand der gegebenen Werte seiner Seiten korrekt zu finden, indem wir nach den Objekteigenschaften sortieren.


Einige der Methoden, die wir zuvor in der Container-Objektklasse erstellt haben, werden nur für Container-Objekte benötigt, z. B. die Methoden, die die Grenzen des Containerbereichs zurückgeben, innerhalb dessen sich angehängte Objekte befinden können. Leider sind diese Methoden (außer bei Containerobjekten) in anderen Bibliotheksobjekten nicht verfügbar. Dies ist ein Problem, da wir auf das Container-Objekt von einer Klasse aus zugreifen, die keinen Zugriff auf ein solches Objekt hat (sie kennt es einfach nicht). Infolgedessen müssen wir auf die Eigenschaften eines solchen Objekts über die Eigenschaften des übergeordneten Objekts zugreifen - dem Basisobjekt aller WinForms-Bibliotheksobjekte. Diese Klasse sieht die Methoden ihres Nachfolgers - der Container-Objektklasse - nicht. So entsteht ein Teufelskreis. Um dieses Problem zu lösen, werde ich alle notwendigen Methoden in die Klasse CWinFormBase verschieben, die eine übergeordnete Klasse für alle WinForms-Bibliotheksobjekte ist, zum leichten Nachteil der Strukturierung der Objekte. Selbst wenn diese Daten zu einem anderen Objekttyp gehören und im aktuellen Objekt nicht verwendet werden, können alle Objekte weiterhin auf die Methoden anderer Objekte zugreifen, die im aktuellen Objekt nicht verwendet werden, aber in den Objekten, auf die zugegriffen wird, Anwendung finden.

Das sieht ziemlich verwirrend aus, aber im Code wird es viel klarer. Wir verschieben einfach einige Methoden aus der Container-Objektklasse in die Basisobjektklasse aller WinForms-Bibliotheksobjekte. Auf diese Weise machen wir die Methoden von jeder Klasse aus sichtbar.

Entfernen wir diese öffentlichen Methoden aus der Datei \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh:

public:
//--- Return the size and coordinates of the working area
   int               WidthWorkspace(void)          const
                       {
                        return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               HeightWorkspace(void)         const
                       {
                        return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }
   int               CoordXWorkspace(void)         const
                       {
                        return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft());
                       }
   int               CoordYWorkspace(void)         const
                       {
                        return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop());
                       }
   int               RightEdgeWorkspace(void)      const
                       {
                        return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               BottomEdgeWorkspace(void)     const
                       {
                        return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }

//--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)

und fügen sie in den öffentlichen Abschnitt der Klasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh ein:

//--- Destructor
                    ~CWinFormBase(void)
                      {
                       if(this.m_list_active_elements!=NULL)
                         {
                           this.m_list_active_elements.Clear();
                           delete this.m_list_active_elements;
                         }
                      }
                      
//--- Return the size and coordinates of the working area
   int               WidthWorkspace(void)       const { return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); }
   int               HeightWorkspace(void)      const { return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());}
   int               CoordXWorkspace(void)      const { return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft());                           }
   int               CoordYWorkspace(void)      const { return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop());                             }
   int               RightEdgeWorkspace(void)   const { return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight());                      }
   int               BottomEdgeWorkspace(void)  const { return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom());                   }
                      
//--- (1) Set and (2) return the default text color of all panel objects
   void              SetForeColor(const color clr,const bool set_init_color)
                       {
                        if(this.ForeColor()==clr)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr);
                        if(set_init_color)
                           this.SetForeColorInit(clr);
                       }

Jetzt werden diese Methoden von allen WinForms-Objekten der Bibliothek aus sichtbar sein.

Um die Sichtbarkeit der Methoden in allen WinForms-Objektklassen zu gewährleisten, schreiben wir die Methoden, die im Wesentlichen zur Klasse des Containerobjekts gehören sollten, aber die Anfrage an sie kommt von Objekten, die keine Container sind.

Wir müssen wissen, wie viele Pixel die an das Container-Objekt angehängten Objekte über den Container hinausgehen. Diese Werte werden in einer Methode geprüft und in eine Struktur gesetzt, die Felder mit der Anzahl der Pixel oben, unten, links und rechts enthält. Wenn verankerte Objekte über einen oder mehrere Ränder des Containers hinausgehen, speichert die Struktur die Werte der Anzahl der Pixel auf jeder Seite des Objekts, wo der Inhalt über den Container hinausgeht. Aus diesen Daten können wir dann die Größe der Bildlaufleiste im ScrollBar-Steuerelement berechnen.

Im Abschnitt der Klasse deklarieren wir eine solche Struktur und die Variable mit dem Strukturtyp, auf den die erforderlichen Daten gesetzt werden:

protected:
   CArrayObj        *m_list_active_elements;                   // Pointer to the list of active elements
   color             m_fore_color_init;                        // Initial color of the control text
   color             m_fore_state_on_color_init;               // Initial color of the control text when the control is "ON"
private:
   struct SOversizes                                           // Structure of values for bound objects leaving the container
     {
      int   top;     // top
      int   bottom;  // bottom
      int   left;    // left
      int   right;   // right
     };
   SOversizes        m_oversize;                               // Structure of values for leaving the container
//--- Return the font flags
   uint              GetFontFlags(void);

public:


Im öffentlichen Abschnitt deklarieren wir die Methode, die prüft, ob die gebundenen Objekte den Container verlassen, und die die oben deklarierte Struktur füllt, die Methode, die alle an den Container gebundenen Objekte verschiebt, und die Methoden, die die Minimal- und Maximalwerte der angegebenen Eigenschaft von allen gebundenen Objekten zum aktuellen Objekt zurückgeben:

//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Set the new size for the (1) current object and (2) the object specified by index
   virtual bool      Resize(const int w,const int h,const bool redraw);
   virtual bool      Resize(const int index,const int w,const int h,const bool redraw);
//--- Return the flag of the container content leaving the container borders
   bool              CheckForOversize(void);
//--- Shift all bound objects
   bool              ShiftDependentObj(const int shift_x,const int shift_y);
//--- Return the (1) maximum and (2) minimum values of the specified integer property from all attached objects to the current one
   long              GetMaxLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop);
   long              GetMinLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop);
protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CWinFormBase(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);
public:
//--- Constructor

Alle diese Methoden werden benötigt, um mit Container-Objekten zu arbeiten, werden aber von Nicht-Container-Objekten aufgerufen, sodass sie in der gemeinsamen Elternklasse aller WinForms-Objekte zu finden sind.

Da die Struktur und die Variable mit dem Typ der Struktur privat sind, benötigen wir öffentliche Methoden, um die in die Felder dieser Struktur geschriebenen Werte zurückzugeben. Wir implementieren sie im öffentlichen Abschnitt ganz am Ende des Klassenkörpers:

//--- Return the number of pixels, by which attached objects go beyond the container at the (1) top, (2) bottom, (3) left and (4) right
   int               OversizeTop(void)                         const { return this.m_oversize.top;    }
   int               OversizeBottom(void)                      const { return this.m_oversize.bottom; }
   int               OversizeLeft(void)                        const { return this.m_oversize.left;   }
   int               OversizeRight(void)                       const { return this.m_oversize.right;  }
  };
//+------------------------------------------------------------------+


In beiden Klassenkonstruktoren initialisieren wir alle Felder der Struktur mit Nullen:

//+-------------------------------------------+
//| Protected constructor with an object type,|
//| chart ID and subwindow                    |
//+-------------------------------------------+
CWinFormBase::CWinFormBase(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) : CForm(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.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_list_active_elements=new CArrayObj();
   ::ZeroMemory(this.m_oversize);
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,             |
//| chart ID and subwindow                    |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(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) : CForm(GRAPH_ELEMENT_TYPE_WF_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the graphical element and library object types as a base WinForms object
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Initialize all variables
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_list_active_elements=new CArrayObj();
   ::ZeroMemory(this.m_oversize);
  }
//+------------------------------------------------------------------+


Die Methode, die das Flag des Containerinhalts zurückgibt, der über seine Grenzen hinausgeht:

//+------------------------------------------------------------------+
//| Return the flag of the container content leaving its borders     |
//+------------------------------------------------------------------+
bool CWinFormBase::CheckForOversize(void)
  {
//--- Update the structure of values for bound objects leaving the container
   ::ZeroMemory(this.m_oversize);
//--- In the loop by the number of attached objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Get the next object and skip scrollbars
      CWinFormBase *obj=this.GetElement(i);
      if(obj==NULL                                                            || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL)
         continue;
      //--- Get the value in pixels of the object leaving the form at the right
      //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure
      int r=obj.RightEdge()-this.RightEdgeWorkspace();
      if(r>0 && r>this.m_oversize.right)
         this.m_oversize.right=r;
      //--- Get the value in pixels of the object leaving the form at the left
      //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure
      int l=this.CoordXWorkspace()-obj.CoordX();
      if(l>0 && l>this.m_oversize.left)
         this.m_oversize.left=l;
      //--- Get the value in pixels of the object leaving the form at the top
      //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure
      int t=this.CoordYWorkspace()-obj.CoordY();
      if(t>0 && t>this.m_oversize.top)
         this.m_oversize.top=t;
      //--- Get the value in pixels of the object leaving the form at the bottom
      //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure
      int b=obj.BottomEdge()-this.BottomEdgeWorkspace();
      if(b>0 && b>this.m_oversize.bottom)
         this.m_oversize.bottom=b;
     }
//--- Return the flag indicating that at least one side of the attached object goes beyond the form borders
   return(m_oversize.top>0 || m_oversize.bottom>0 || m_oversize.left>0 || m_oversize.right>0);
  }
//+------------------------------------------------------------------+

Die Methodenlogik wird in den Codekommentaren beschrieben. Kurz gesagt, wir müssen wissen, dass eines (oder mehrere oder sogar alle) der an den Container gebundenen Objekte dessen Grenzen überschreitet. Um die Maximalwerte zu ermitteln, um die sich die Objekte außerhalb ihres Containers befinden, suchen wir in einer Schleife über alle gebundenen Objekte den Maximalwert auf jeder Seite. Am Ende der Schleife haben wir alle Werte, um die die gebundenen Objekte auf jeder Seite über die Containergrenzen hinausgehen. Anhand dieser Werte können wir später die Größe des Schiebereglers für den Fortschrittsbalken berechnen. Je größer die Überschreitung der Containergröße ist, desto kleiner wird der Schieberegler.

Die Methode, die alle angehängten Objekte verschiebt:

//+-------------------------------------------+
//| Shift all bound objects                   |
//+-------------------------------------------+
bool CWinFormBase::ShiftDependentObj(const int shift_x,const int shift_y)
  {
//--- In the loop by all bound objects,
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- get the next object and skip scrollbars
      CWinFormBase *obj=this.GetElement(i);
      if(obj==NULL                                                            || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL
        ) continue;
      //--- Set the offset coordinates
      int x=obj.CoordX()+shift_x;
      int y=obj.CoordY()+shift_y;
      if(!obj.Move(x,y,false))
         return false;
      //--- After a successful offset, set relative coordinates and redraw the object
      obj.SetCoordXRelative(obj.CoordX()-this.CoordX());
      obj.SetCoordYRelative(obj.CoordY()-this.CoordY());
      obj.Redraw(false);
     }
   return true;
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist im Code kommentiert. Wir müssen die an den Container gebundenen Objekte verschieben, wenn die Bildlauftasten angeklickt werden oder wenn der Schieberegler bewegt wird. Die Methode verschiebt alle Objekte in der Liste der mit ihr verbundenen Objekte. Die Verschiebung wird mit der Methode Move() durchgeführt. Bei dieser Methode ist bereits alles so arrangiert, dass sich andere Elemente, die an dem zu verschiebenden Objekt befestigt sind, ebenfalls bewegen. Im Allgemeinen werden hier alle Objekte, die an den Container angehängt sind, mit Ausnahme der Bildlaufleisten, um den angegebenen Betrag verschoben, da sie Steuerelemente des Containerobjekts sind und nicht der an den Container angehängten Objekte (obwohl sie in der allgemeinen Liste enthalten sind).


Die Methode, die den Maximalwert der angegebenen Integer-Eigenschaft aller Objekte zurückgibt, die dem Basisobjekt untergeordnet sind:

//+------------------------------------------------------------------+
//| Return the maximum value of the specified integer                |
//| property from all objects subordinate to the base one            |
//+------------------------------------------------------------------+
long CWinFormBase::GetMaxLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop)
  {
//--- Initialize 'property' with -1
   long property=-LONG_MAX;
//--- In the loop through the list of bound objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- get the next object and skip scrollbars
      CWinFormBase *obj=this.GetElement(i);
      if(obj==NULL                                                            || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL
        ) continue;
      //--- If the property value of the received object is greater than the value set in the property,
      //--- set the current object property value to 'property'
      if(obj.GetProperty(prop)>property)
         property=obj.GetProperty(prop);
      //--- Get the maximum property value from objects bound to the current one
      long prop_form=obj.GetMaxLongPropFromDependent(prop);
      //--- If the received value is greater than the 'property' value
      //--- set the received value to 'property'
      if(prop_form>property)
         property=prop_form;
     }
//--- Return the found maximum property value
   return property;
  }
//+------------------------------------------------------------------+

Auch hier wird die gesamte Logik in den Code-Kommentaren beschrieben. Hier ist alles ganz einfach: Wir durchlaufen eine Schleife durch alle Objekte (außer Bildlaufleisten) und suchen ein Objekt mit dem maximalen Wert der angegebenen Eigenschaft. Der gefundene Höchstwert wird zurückgegeben.


Die Methode, die den Mindestwert der angegebenen Integer-Eigenschaft von allen untergeordneten Basisobjekten zurückgibt:

//+------------------------------------------------------------------+
//| Return the minimum value of the specified integer                |
//| property from all objects subordinate to the base one            |
//+------------------------------------------------------------------+
long CWinFormBase::GetMinLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop)
  {
//--- Initialize 'property' using the LONG_MAX value
   long property=LONG_MAX;
//--- In the loop through the list of bound objects
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- get the next object and skip scrollbars
      CWinFormBase *obj=this.GetElement(i);
      if(obj==NULL                                                            || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR             || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL  || 
         obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL
        ) continue;
      //--- If the value of the obtained object property is less than the value set in 'property',
      //--- set the current object property value to 'property'
      if(obj.GetProperty(prop)<property)
         property=obj.GetProperty(prop);
      //--- Get the minimum property value from bound objects to the current one
      long prop_form=obj.GetMinLongPropFromDependent(prop);
      //--- If the obtained value is less than the property value
      //--- set the received value to 'property'
      if(prop_form<property)
         property=prop_form;
     }
//--- Return the found minimum property value
   return property;
  }
//+------------------------------------------------------------------+

Die Methode ähnelt der vorhergehenden: Wir durchlaufen eine Schleife durch alle Objekte (außer Bildlaufleisten) und suchen ein Objekt mit dem Mindestwert der angegebenen Eigenschaft. Wir geben den gefundenen Mindestwert zurück.


Wenn der Mauszeiger über dem Schieberegler der Bildlaufleiste schwebt, muss das gesamte ScrollBar-Objekt in den Vordergrund gebracht werden. Wenn wir mit der Maus über den Bereich dieses Objekts fahren, müssen wir es im Allgemeinen über alle Container-Objekte bewegen. Dies ist notwendig, damit der Cursor mit dem obersten Objekt interagieren kann und nicht mit den Objekten, die sich möglicherweise darüber befinden, da sie später erstellt wurden. Im Moment ist die Klasse des Capture-Bereichs-Objekts von der Klasse des Button-Objekts abgeleitet und verwendet die Funktionalität der übergeordneten Klasse, um mit der Maus zu interagieren. Da die Methoden zur Behandlung verschiedener Ereignisse bei der Interaktion mit der Maus virtuell sind, müssen wir sie in der Objektklasse des Erfassungsbereichs außer Kraft setzen.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh, und zwar im geschützten Abschnitt, deklarieren wir die virtuelle Ereignisbehandlung für Mausereignisse, während wir im öffentlichen Abschnitt die letzte Ereignisbehandlung der Maus deklarieren:

//+-------------------------------------------+
//| Label object class of WForms controls     |
//+-------------------------------------------+
class CScrollBarThumb : public CButton
  {
private:

protected:
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler
   virtual void      MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the left mouse button is clicked' event handler
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Protected constructor with object type, chart ID and subwindow
                     CScrollBarThumb(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);
                             
public:
//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);

//--- Constructor
                     CScrollBarThumb(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);
  };
//+------------------------------------------------------------------+


Implementieren wir die angegebenen virtuellen Ereignisbehandlungen.

Der Cursor befindet sich innerhalb des aktiven Bereichs, die Maustasten sind nicht geklickt:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base!=NULL)
     {
      base.BringToTop();
     }
   CButton::MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Hier erhalten wir den Zeiger auf das Basisobjekt (Schieberegler) und heben ihn in den Vordergrund. Dann rufen wir die Ereignisbehandlung der Mausereignisse des übergeordneten Objekts auf, das der Methode entspricht.


Der Cursor befindet sich innerhalb des aktiven Bereichs, eine beliebige Maustaste wurde angeklickt:

//+-------------------------------------------+
//| 'The cursor is inside the active area,    |
//| a mouse button is clicked (any)           |
//+-------------------------------------------+
void CScrollBarThumb::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base!=NULL)
     {
      base.BringToTop();
     }
   CButton::MouseActiveAreaPressedHandler(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Hier ist alles genau gleich. Zuerst bringen wir das Basisobjekt in den Vordergrund (es wird eine Bildlaufleiste mit allen Steuerelementen sein), dann rufen wir den Maus-Event-Handler des übergeordneten Objekts auf.


Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt:

//+-------------------------------------------+
//| 'The cursor is inside the active area,    |
//| left mouse button released                |
//+-------------------------------------------+
void CScrollBarThumb::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base!=NULL)
     {
      base.BringToTop();
     }
   CButton::MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Alles ist identisch mit den beiden oben genannten Ereignisbehandlungen.


Die letzte Mausereignisbehandlung wird vollständig aus dem übergeordneten Objekt herausgelöst, mit dem Ziel seiner möglichen weiteren Verfeinerung:

//+-------------------------------------------+
//| Last mouse event handler                  |
//+-------------------------------------------+
void CScrollBarThumb::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=GetMouseState();
   switch(state)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED     :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL       :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED)
          {
           this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false);
           this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
           this.Redraw(false);
          }
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      //--- The cursor is inside the form, any mouse button is clicked
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      //--- The cursor is inside the active area, any mouse button is clicked
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, left mouse button is released
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window resizing area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window separator area, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED                     :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED                         :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL                           :
//--- Within the active area
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED              :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED                  :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL                    :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED                 :
//--- Within the scrolling area at the bottom
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_NOT_PRESSED       :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_PRESSED           :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_WHEEL             :
//--- Within the scrolling area to the right
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_NOT_PRESSED        :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_PRESSED            :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_WHEEL              :
//--- Within the window resizing area at the top
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_NOT_PRESSED          :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_PRESSED              :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_WHEEL                :
//--- Within the window resizing area at the bottom
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_NOT_PRESSED       :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_PRESSED           :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_WHEEL             :
//--- Within the window resizing area to the left
      case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_WHEEL               :
//--- Within the window resizing area to the right
      case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_NOT_PRESSED        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_PRESSED            :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_WHEEL              :
//--- Within the window resizing area to the top-left
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_NOT_PRESSED     :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_WHEEL           :
//--- Within the window resizing area to the top-right
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_NOT_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_PRESSED        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_WHEEL          :
//--- Within the window resizing area at the bottom left
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_WHEEL        :
//--- Within the window resizing area at the bottom-right
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_NOT_PRESSED :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_PRESSED     :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_WHEEL       :
//--- Within the control area
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED                 :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL                   :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Im Moment ist die Ereignisbehandlung identisch mit der der übergeordneten Klasse.


In der abstrakten Objektklasse des Schiebereglers in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBar.mqh, d. h. in der Methode zur Erstellung des Objekts für den Erfassungsbereich (Schieberegler) und der Schaltflächen dafür, stellen wir die korrekte Größe der Schaltflächen ein, die in der Makrosubstitution aufgezeichnet wurde, die speziell für die Speicherung der Größe der Schaltflächen der Schieberegler entwickelt wurde:

//+-------------------------------------------+
//| Create the capture area object            |
//+-------------------------------------------+
void CScrollBar::CreateThumbArea(void)
  {
   this.CreateArrowButtons(DEF_CONTROL_SCROLL_BAR_WIDTH,DEF_CONTROL_SCROLL_BAR_WIDTH);
  }
//+------------------------------------------------------------------+


Geben wir in der Methode, die den Erfassungsbereich berechnet, den Wert zurück , der in der Makrosubstitution angegeben ist, die die standardmäßige Mindestgröße des Schiebereglers speichert, und nicht Null:

//+-------------------------------------------+
//| Calculate the capture area size           |
//+-------------------------------------------+
int CScrollBar::CalculateThumbAreaSize(void)
  {
   return DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
  }
//+------------------------------------------------------------------+


Die Objektklasse des Schiebereglers sollte die Größe und Position des Schiebereglers in Abhängigkeit davon berechnen, wie viele Pixel der Inhalt des Containers über ihn hinausragt. Wenn die Größe des Containers oder seines Inhalts geändert wird, sollten die Größe und Position des Schiebereglers neu berechnet werden. Wenn die Bildlauftasten gedrückt werden, sollte sich der Inhalt des Containers (sofern er über den Container hinausgeht) in die entgegengesetzte Richtung des Pfeils der gedrückten Taste bewegen. Der Schieberegler sollte sich in Richtung des Pfeils bewegen.

In diesem Artikel werde ich eine solche Funktionalität für die horizontale Bildlaufleiste erstellen. Danach werde ich die vorgefertigte Funktionalität in die Objektklasse des vertikalen Schiebereglers übertragen. Ihre gemeinsame Arbeit wird anschließend umgesetzt.

Benennen wir in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh die private Methode CalculateThumbAreaSize in CalculateThumbAreaDistance() um. Im öffentlichen Teil deklarieren wir die Methoden zur Berechnung der Größe und der Koordinaten des Schiebereglers sowie die Hauptmethode zur Neuberechnung seiner Parameter:

//+------------------------------------------------------------------+
//| CScrollBarHorisontal object class of WForms controls             |
//+------------------------------------------------------------------+
class CScrollBarHorisontal : 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
                     CScrollBarHorisontal(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);

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 (1) left and (2) right arrow button
   CArrowLeftButton *GetArrowButtonLeft(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0);    }
   CArrowRightButton*GetArrowButtonRight(void)  { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,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
                     CScrollBarHorisontal(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);
  };
//+------------------------------------------------------------------+


Betrachten wir nun die deklarierten Methoden.

Die Methode, die die neuen Abmessungen des Objekts festlegt:

//+-------------------------------------------+
//| Set the new size                          |
//+-------------------------------------------+
bool CScrollBarHorisontal::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 right arrow
   CArrowRightButton *br=this.GetArrowButtonRight();
//--- If the button is not received, return 'false'
   if(br==NULL)
      return false;
//--- Move the button to the right edge of the scrollbar
   if(br.Move(this.RightEdge()-this.BorderSizeRight()-br.Width(),br.CoordY()))
     {
      //--- Set new relative coordinates for the button
      br.SetCoordXRelative(br.CoordX()-this.CoordX());
      br.SetCoordYRelative(br.CoordY()-this.CoordY());
     }
//--- Set the slider parameters
   this.SetThumbParams();
//--- Successful
   return true;
  }
//+------------------------------------------------------------------+

Legen wir zunächst die neue Größe der Bildlaufleiste mit der Methode der übergeordneten Klasse fest. Als Nächstes müssen wir nach erfolgreicher Größenänderung die Schaltfläche auf der rechten Seite des Objekts so verschieben, dass sie sich am Rand des größenveränderten Objekts befindet (mit der Größenänderung ändert sich auch die Koordinate des rechten Randes). Nach allen Änderungen und Bewegungen berechnen wir die Größe und Position des Schiebereglers mit der Methode SetThumbParams() neu, die ich weiter unten erläutern werde.


Die Methode zur Berechnung und Einstellung der Parameter des Erfassungsbereichs (Schieberegler):

//+------------------------------------------------------------------+
//| Calculate and set the parameters of the capture area (slider)    |
//+------------------------------------------------------------------+
int CScrollBarHorisontal::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 width size of the visible part inside the container
   int base_w=base.WidthWorkspace();
//--- Calculate the total width of all attached objects and the window size of the visible part in %
   int objs_w=base_w+base.OversizeLeft()+base.OversizeRight();
   double px=base_w*100.0/objs_w;
//--- Calculate and adjust the size of the slider in % relative to the width of its workspace (not less than the minimum size)
   int thumb_size=(int)::ceil(this.BarWorkAreaSize()/100.0*px);
   if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN)
      thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
//--- Calculate the coordinate of the slider and change its size to match the previously calculated one
   int thumb_x=this.CalculateThumbAreaDistance(thumb_size);
   if(!thumb.Resize(thumb_size,thumb.Height(),true))
      return 0;
//--- Shift the slider by the calculated X coordinate
   if(thumb.Move(this.BarWorkAreaCoord()+thumb_x,thumb.CoordY()))
     {
      thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
      thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
     }
//--- Return the calculated slider size
   return thumb_size;
  }
//+------------------------------------------------------------------+

Jede Zeile der Methode wird ausführlich kommentiert. Die Methode besteht im Wesentlichen darin, die Größe des Schiebereglers in Abhängigkeit von der Größe des Inhalts des Containers zu berechnen, der über seine Grenzen hinausgeht. Je größer der Wert in Pixeln ist, über den der Inhalt des Containers hinausreicht, desto kleiner ist der Schieberegler. Die Koordinate der Schiebereglerposition wird vom linken Rand des Containers aus berechnet, ebenfalls in relativer Größe, sodass sie der Position des sichtbaren Teils des Containerinhalts im Verhältnis zu seinem unsichtbaren Teil entspricht, der über den linken Rand hinausgeht. Je weiter (in Pixeln) der Inhalt des Containers über den linken Rand hinausragt, desto weiter rechts wird der Schieberegler positioniert. Daraus ergibt sich, dass die Bildlaufleiste zusammen mit dem Schieberegler eine verkleinerte Kopie (oder Instanz) des Containers und seines Inhalts ist. Bei der Bildlaufleiste zeigt die Leiste selbst den gesamten Inhalt des Containers an, und der Schieberegler zeigt den Container an - den Teil, in dem der Inhalt sichtbar ist.


Die Methode zur Berechnung der Entfernung des Erfassungsbereichs (Schieberegler):

//+------------------------------------------------------------------+
//| Calculate the distance of the capture area (slider)              |
//+------------------------------------------------------------------+
int CScrollBarHorisontal::CalculateThumbAreaDistance(const int thumb_size)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return 0;
   double x=(double)thumb_size/(double)base.WidthWorkspace();
   return (int)::ceil((double)base.OversizeLeft()*x);
  }
//+------------------------------------------------------------------+

Die Größe des Schiebereglers wird an die Methode übergeben. Als Nächstes wird berechnet, um wie viel die Größe des Schiebereglers kleiner ist als die Größe des Container-Arbeitsbereichs (in dem der Inhalt sichtbar ist). Anhand dieses Verhältnisses wird dann der Abstand des Schiebers berechnet und zurückgegeben. Mit anderen Worten: Der Schieberegler ist kleiner als der Container, und zwar so weit, dass der Abstand des Schiebereglers vom Koordinatenursprung kleiner ist als die Anzahl der Pixel, um die der Inhalt des Containers über seinen linken Rand hinausgeht.


Die Methode, die die Größe des Arbeitsbereichs des Schiebereglers zurückgibt:

//+-------------------------------------------+
//| Return the size of the slider working area|
//+-------------------------------------------+
int CScrollBarHorisontal::BarWorkAreaSize(void)
  {
   CArrowLeftButton  *bl=this.GetArrowButtonLeft();
   CArrowRightButton *br=this.GetArrowButtonRight();
   int x1=(bl!=NULL ? bl.RightEdge() : this.CoordX()+this.BorderSizeLeft());
   int x2=(br!=NULL ? br.CoordX() : this.RightEdge()-this.BorderSizeRight());
   return(x2-x1);
  }
//+------------------------------------------------------------------+

Der Arbeitsbereich des Schiebereglers ist der Bereich, in dem er sich in der Bildlaufleiste bewegt. Mit anderen Worten, dies ist der Bereich zwischen den beiden Pfeilschaltflächen der Bildlaufleiste. Um diesen Abstand zu berechnen, müssen wir also die Zeiger auf diese Schaltflächen erhalten und den Abstand zwischen dem linken Rand der rechten Schaltfläche und dem rechten Rand der linken Schaltfläche berechnen.


Die Methode, die die Koordinate des Beginns des Arbeitsbereichs des Schiebereglers zurückgibt:

//+------------------------------------------------------------------+
//| Return the coordinate of the beginning of the slider working area|
//+------------------------------------------------------------------+
int CScrollBarHorisontal::BarWorkAreaCoord(void)
  {
   CArrowLeftButton  *bl=this.GetArrowButtonLeft();
   return(bl!=NULL ? bl.RightEdge() : this.CoordX()+this.BorderSizeLeft());
  }
//+------------------------------------------------------------------+

Die Methode gibt die Koordinate der rechten Kante der linken Bildlaufleisten-Schaltfläche zurück. Diese Koordinate ist die Anfangskoordinate für die Position des Schiebereglers. Von dieser Koordinate aus wird seine Position bei der Berechnung seiner Abmessungen und Koordinaten berechnet.


Die Maus-Ereignisbehandlung ist jetzt neu gestaltet und sieht wie folgt aus:

//+-------------------------------------------+
//| Event handler                             |
//+-------------------------------------------+
void CScrollBarHorisontal::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
   CArrowLeftButton  *buttl=this.GetArrowButtonLeft();
   CArrowRightButton *buttr=this.GetArrowButtonRight();
   CScrollBarThumb   *thumb=this.GetThumb();
   if(buttl==NULL || buttr==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 Y coordinate equal to the Y coordinate of the control element
      y=this.CoordY()+this.BorderSizeTop();
      //--- Adjust the X coordinate so that the capture area does not go beyond the control, taking into account the arrow buttons
      if(x<buttl.RightEdge())
        x=buttl.RightEdge();
      if(x>buttr.CoordX()-thumb.Width())
        x=buttr.CoordX()-thumb.Width();
      //--- 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());
         ::ChartRedraw(this.ChartID());
        }
      //--- Get the pointer to the base object
      CWinFormBase *base=this.GetBase();
      if(base!=NULL)
        {
         //--- Check if the content goes beyond the container and recalculate the slider parameters
         base.CheckForOversize();
         this.SetThumbParams();
        }
     }
//--- If any scroll button is clicked
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
     {
      //--- 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 right and left sides of the base object content
      int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
      int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);
      //--- Set the number of pixels, by which the content of the base object should be shifted
      int shift=DEF_CONTROL_SCROLL_BAR_SCROLL_STEP;
      //--- If the left button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT)
        {
         if(cntt_l+shift<=base.CoordXWorkspace())
            base.ShiftDependentObj(shift,0);
        }
      //--- If the right button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
        {
         if(cntt_r-shift>=base.RightEdgeWorkspace())
            base.ShiftDependentObj(-shift,0);
        }
      //--- Calculate the width and coordinates of the slider
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+

Hier fügen wir die Behandlung von Klicks auf Scrollbar-Schaltflächen hinzu. Wenn die Schaltfläche angeklickt wird, werden die Offset-Koordinaten berechnet und der gesamte Inhalt des Containers in die Richtung verschoben, die der gedrückten Pfeiltaste entgegengesetzt ist. Nach der Verschiebung passen wir die Größe und die Koordinaten des Schiebereglers erneut an.


Bei der Fertigstellung der Klasse des horizontalen Scrollbar-Objekts wurden gleichzeitig ähnliche Änderungen und Verbesserungen an der Klasse des vertikalen Scrollbar-Objekts vorgenommen. Aber nicht alle Änderungen wurden umgesetzt und nicht alle wurden getestet, weil ich zuerst die Funktionsweise der horizontalen Bildlaufleiste implementieren und dann auf die vertikale Leiste übertragen werde. Daher werde ich hier nicht auf die Änderungen eingehen, die am Code der Klasse der vertikalen Bildlaufleiste vorgenommen wurden. Ich werde es nach einer vollständigen Neuerstellung auf der Grundlage des funktionierenden und fehlerbereinigten Codes für die horizontale Bildlaufleiste in Betracht ziehen.


Kommen wir zurück zur Container-Objektklasse in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh.

Im geschützten Abschnitt deklarieren wir die Methode, mit der die Bildlaufleisten in den Vordergrund gehoben werden. Im öffentlichen Abschnitt implementieren wir die Methoden, die die Zeiger auf die beiden Bildlaufleisten zurückgeben, deklarieren die virtuelle Methode zum Verschieben des Objekts, während die virtuelle Redrawing-Methode ihre Deklaration mit einer Implementierung außerhalb des Klassenkörpers erhält:

//+------------------------------------------------------------------+
//| Class of the base container object of WForms controls            |
//+------------------------------------------------------------------+
class CContainer : public CWinFormBase
  {
private:
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);

//--- Calculate Dock objects' binding coordinates
   void              CalculateCoords(CArrayObj *list);

//--- Create the (1) vertical and (2) horizontal ScrollBar
   CWinFormBase     *CreateScrollBarVertical(const int width);
   CWinFormBase     *CreateScrollBarHorisontal(const int width);

protected:
//--- Adjust the element size to fit its content
   bool              AutoSizeProcess(const bool redraw);
//--- Set parameters for the attached object
   void              SetObjParams(CWinFormBase *obj,const color colour);
//--- Create vertical and horizontal ScrollBar objects
   void              CreateScrollBars(const int width);
//--- Move the scrollbars to the foreground
   void              BringToTopScrollBars(void);

public:
//--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)
   CArrayObj        *GetListWinFormsObj(void);
   CArrayObj        *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type);
//--- Return the pointer to the specified WinForms object with the specified type by index
   CWinFormBase     *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Return the pointer to the (1) vertical and (2) horizontal scrollbar
   CScrollBarVertical   *GetScrollBarVertical(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL,0);  }
   CScrollBarHorisontal *GetScrollBarHorisontal(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL,0);}
   
//--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height
   virtual bool      SetCoordX(const int coord_x)              { return CGCnvElement::SetCoordX(coord_x);   }
   virtual bool      SetCoordY(const int coord_y)              { return CGCnvElement::SetCoordY(coord_y);   }
   virtual bool      SetWidth(const int width)                 { return CGCnvElement::SetWidth(width);      }
   virtual bool      SetHeight(const int height)               { return CGCnvElement::SetHeight(height);    }
   
//--- Set the new size for the (1) current object and (2) the object specified by index
   virtual bool      Resize(const int w,const int h,const bool redraw);
//--- Update the coordinates
   virtual bool      Move(const int x,const int y,const bool redraw=false);

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

//--- Redraw the object
   virtual void      Redraw(bool redraw);
   
//--- Reset the size of all bound objects to the initial ones
   bool              ResetSizeAllToInit(void);
//--- Place bound objects in the order of their Dock binding
   virtual bool      ArrangeObjects(const bool redraw);


In der Methode, die den Modus der automatischen Größenanpassung des Elements an den Inhalt festlegt, fügen wir eine Prüfung hinzu, dass das Flag der automatischen Größenanpassung des Elements in Abhängigkeit vom Inhalt nicht gesetzt ist:

//--- (1) Set and (2) return the mode of the element auto resizing depending on the content
   void              SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw)
                       {
                        ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode();
                        if(prev==mode)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode);
                        if(!this.AutoSize())
                           return;
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)   const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); }

Bei der Einstellung des Modus für die automatische Größenänderung wird die Größe des Containers an seinen Inhalt angepasst, wenn ein Modus eingestellt wird, der nicht mit dem aktuellen Modus übereinstimmt. Wenn das Flag für die automatische Größenanpassung nicht aktiviert ist, muss auch nichts sortiert werden, was der hinzugefügte Code tut. Sie wird von der Methode zurückgegeben, wenn das Flag für die automatische Größenänderung gelöscht wird.


Wenn nach der Erstellung des Container-Objekts das Flag für die automatische Größenanpassung des Containers für den Content nicht gesetzt ist und der Inhalt des Containers über seine Grenzen hinausgeht, sollte die entsprechende Bildlaufleiste angezeigt werden:

//+-------------------------------------------+
//| 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
        {
         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();
              }
           }
        }
     }
//--- Crop the created object along the edges of the visible part of the container
   obj.Crop();
//--- return 'true'
   return true;
  }
//+------------------------------------------------------------------+

In diesem Fall werden zunächst die an den Container angehängten Objekte überprüft, um festzustellen, ob sie sich außerhalb des linken oder rechten Rahmens befinden, und dann wird die horizontale Bildlaufleiste angezeigt. Aber zuerst werden die Parameter des Schiebereglers berechnet und eingestellt, und danach wird das Scrollbar-Objekt selbst angezeigt.

Da nun das Scrollbar-Objekt selbst die Größenänderung vornimmt, hat sich die Methode zur Größenänderung des aktuellen Objekts geändert:

//+-------------------------------------------+
//| Set the new size for the current object   |
//+-------------------------------------------+
bool CContainer::Resize(const int w,const int h,const bool redraw)
  {
//--- If it was not possible to change the size of the container, return 'false'
   if(!CWinFormBase::Resize(w,h,redraw))
      return false;

//--- Get the vertical scrollbar and
   CScrollBarVertical *scroll_v=this.GetScrollBarVertical();
   if(scroll_v!=NULL)
     {
      //--- If the vertical size of the scrollbar is changed to fit the size of the container workspace
      if(scroll_v.Resize(scroll_v.Width(),this.HeightWorkspace(),false))
        {
         //--- Move the vertical scrollbar to new coordinates
         if(scroll_v.Move(this.RightEdgeWorkspace()-scroll_v.Width(),this.CoordYWorkspace()))
           {
            scroll_v.SetCoordXRelative(scroll_v.CoordX()-this.CoordX());
            scroll_v.SetCoordYRelative(scroll_v.CoordY()-this.CoordY());
           }
        }
      scroll_v.BringToTop();
     }
//--- Get the horizontal scrollbar and
   CScrollBarHorisontal *scroll_h=this.GetScrollBarHorisontal();//this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL,0);
   if(scroll_h!=NULL)
     {
      //--- If the horizontal size of the scrollbar is changed to fit the size of the container workspace
      if(scroll_h.Resize(this.WidthWorkspace(),scroll_h.Height(),false))
        {
         //--- Move the horizontal scrollbar to new coordinates
         if(scroll_h.Move(this.CoordXWorkspace(),this.BottomEdgeWorkspace()-scroll_h.Height()))
           {
            scroll_h.SetCoordXRelative(scroll_h.CoordX()-this.CoordX());
            scroll_h.SetCoordYRelative(scroll_h.CoordY()-this.CoordY());
           }
        }
      scroll_h.BringToTop();
     }
   return true;
  }
//+------------------------------------------------------------------+

Da es nun nicht mehr notwendig ist, die Größe der Bildlaufleisten zu ändern, um ihre Schaltflächen an neue Stellen zu verschieben, ist die Methode kürzer und übersichtlicher geworden. Jede Bildlaufleiste wird nach der Größenänderung in den Vordergrund gebracht.


Die Methode zum Neuzeichnen eines Objekts:

//+-------------------------------------------+
//| Redraw the object                         |
//+-------------------------------------------+
void CContainer::Redraw(bool redraw)
  {
   CWinFormBase::Redraw(redraw);
   this.BringToTopScrollBars();
  }
//+------------------------------------------------------------------+

Zunächst zeichnen wir das Objekt mit der Methode der übergeordneten Klasse neu und rufen dann die Methode auf, um die Bildlaufleisten in den Vordergrund zu bringen.


Die Methode, die Bildlaufleisten in den Vordergrund bringt:

//+-------------------------------------------+
//| Bring scrollbars to the foreground        |
//+-------------------------------------------+
void CContainer::BringToTopScrollBars(void)
  {
   CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal();
   CScrollBarVertical   *sbv=this.GetScrollBarVertical();
   if(sbh!=NULL)
      sbh.BringToTop();
   if(sbv!=NULL)
      sbv.BringToTop();
  }
//+------------------------------------------------------------------+

Holen wir uns die Zeiger auf die horizontalen und vertikalen Bildlaufleisten-Objekte und verschieben bei erfolgreichem Erhalt des Zeigers jedes Objekt in den Vordergrund.


Die Methode zur Aktualisierung der Koordinaten:

//+-------------------------------------------+
//| Update the coordinates                    |
//+-------------------------------------------+
bool CContainer::Move(const int x,const int y,const bool redraw=false)
  {
   if(!CForm::Move(x,y,redraw))
      return false;
   this.BringToTopScrollBars();
   return true;
  }
//+------------------------------------------------------------------+

Zunächst rufen wir die Methode der übergeordneten Klasse auf, um das Objekt zu verschieben. Als Nächstes, nach einer erfolgreichen Verschiebung, verschieben wir die Bildlaufleisten in den Vordergrund.


Nun müssen wir die Ereignisse der Scrollbar-Objekte in die Verarbeitung in der Kollektionsklasse der grafischen Elemente aufnehmen.

In der Datei \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, und zwar in ihrer Ereignisbehandlung, fügen wir den folgenden Codeblock hinzu, um den Handler für Mausereignisse des Scrollbar-Objekts im Objekt-Click-Handler aufzurufen:

      //+-------------------------------------------+
      //|  Clicking the control                     |
      //+-------------------------------------------+
      if(idx==WF_CONTROL_EVENT_CLICK)
        {
         //--- If TabControl type is set in dparam
         if((ENUM_GRAPH_ELEMENT_TYPE)dparam==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            //--- Set the event type depending on the element type that generated the event
            int event_id=
              (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT   ?  WF_CONTROL_EVENT_CLICK_SCROLL_LEFT  :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT  ?  WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP     ?  WF_CONTROL_EVENT_CLICK_SCROLL_UP    :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN   ?  WF_CONTROL_EVENT_CLICK_SCROLL_DOWN  :
               WF_CONTROL_EVENT_NO_EVENT);
            //--- If the base control is received, call its event handler
            if(base_elm!=NULL)
               base_elm.OnChartEvent(event_id,lparam,dparam,sparam);
           }
         //--- If the base object is a horizontal or vertical scrollbar
         if(base!=NULL && (base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL))
           {
            //--- Set the event type depending on the element type that generated the event
            int event_id=
              (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT   ?  WF_CONTROL_EVENT_CLICK_SCROLL_LEFT  :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT  ?  WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP     ?  WF_CONTROL_EVENT_CLICK_SCROLL_UP    :
               object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN   ?  WF_CONTROL_EVENT_CLICK_SCROLL_DOWN  :
               WF_CONTROL_EVENT_NO_EVENT);
            //--- Call the event handler of the base element
            base.OnChartEvent(event_id,lparam,dparam,sparam);
           }
        }

      //+-------------------------------------------+
      //|  Selecting the TabControl tab             |
      //+-------------------------------------------+
      if(idx==WF_CONTROL_EVENT_TAB_SELECT)
        {
         if(base!=NULL)
            base.OnChartEvent(idx,lparam,dparam,sparam);
        }

Alles ist bereit für einen Test.


Test

Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part131\ als TestDoEasy131.mq5.

Beim letzten Mal habe ich das große Schaltflächenobjekt auf dem Bedienfeld erstellt. Nun ist es an der Zeit, einen Text hinzuzufügen, damit wir die horizontale Verschiebung des Objekts sehen können, wenn wir auf die Bildlauftasten klicken. Die Schaltfläche selbst sollte das Textbeschriftungsobjekt haben, das anzeigt, dass alle angehängten Objekte korrekt verschoben sind:

//--- 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,-40,10,pnl.WidthWorkspace()+80,pnl.HeightWorkspace()-30,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");
         /*
         //--- Create TabControl
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
         CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
         if(tc!=NULL)
           {


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


Wie Sie sehen können, funktioniert der Bildlauf mit den Pfeiltasten gut. Wenn wir versuchen, den Schieberegler mit der Maus zu bewegen, „widersteht“ er, was natürlich ist - wir haben noch keine Verarbeitung der Schiebereglerverschiebung, aber wir haben bereits eine Neuberechnung seiner Größe und Koordinaten. Wenn wir also versuchen, den Schieberegler mit der Maus zu verschieben, wird er durch die Methode zum Festlegen seiner Koordinaten an die Position zurückgebracht, die der Position des Containerinhalts in seinem sichtbaren Bereich entspricht. Dieses Verhalten wird in den folgenden Artikeln näher erläutert.



Was kommt als Nächstes?

Im nächsten Artikel werde ich die Entwicklung des ScrollBar-Steuerelements fortsetzen.

Zurück zum Inhalt

*Vorherige Artikel in dieser Reihe:

 
DoEasy. Steuerung (Teil 26): Fertigstellung des ToolTip WinForms-Objekts und Weiterführung der ProgressBar-Entwicklung
DoEasy. Steuerung (Teil 27): Arbeiten am WinForms Objekt der ProgressBar
DoEasy. Steuerung (Teil 28): Balkenstile im ProgressBar-Steuerelement
DoEasy. Steuerung (Teil 29): ScrollBar-Hilfssteuerelement
DoEasy. Steuerung (Teil 30): Animieren des ScrollBar-Steuerelements

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

Beigefügte Dateien |
MQL5.zip (4551.42 KB)
Erstellen eines EA, der automatisch funktioniert (Teil 02): Erste Schritte mit dem Code Erstellen eines EA, der automatisch funktioniert (Teil 02): Erste Schritte mit dem Code
Heute werden wir sehen, wie man einen Expert Advisor erstellt, der einfach und sicher im automatischen Modus arbeitet. Im vorigen Artikel haben wir die ersten Schritte besprochen, die jeder verstehen muss, bevor er einen Expert Advisor für den automatischen Handel erstellen kann. Wir haben uns Gedanken über die Konzepte und die Struktur gemacht.
Erstellen eines EA, der automatisch funktioniert (Teil 01): Konzepte und Strukturen Erstellen eines EA, der automatisch funktioniert (Teil 01): Konzepte und Strukturen
Heute werden wir sehen, wie man einen Expert Advisor erstellt, der einfach und sicher im automatischen Modus arbeitet.
Erstellen eines EA, der automatisch funktioniert (Teil 03): Neue Funktionen Erstellen eines EA, der automatisch funktioniert (Teil 03): Neue Funktionen
Heute werden wir sehen, wie man einen Expert Advisor erstellt, der einfach und sicher im automatischen Modus arbeitet. Im vorherigen Artikel haben wir begonnen, ein Auftragssystem zu entwickeln, das wir in unserem automatisierten EA verwenden werden. Wir haben jedoch nur eine der benötigten Funktionen geschaffen.
Erstellen eines Ticker-Panels: Verbesserte Version Erstellen eines Ticker-Panels: Verbesserte Version
Was halten Sie von der Idee, die Grundversion unseres Ticker-Panels wiederzubeleben? Als Erstes werden wir das Panel so ändern, dass wir ein Bild hinzufügen können, z. B. ein Anlagenlogo oder ein anderes Bild, damit der Nutzer das angezeigte Logo schnell und einfach identifizieren kann.