Русский 中文 Español Deutsch 日本語 Português
preview
DoEasy. Controls (Part 31): Scrolling the contents of the ScrollBar control

DoEasy. Controls (Part 31): Scrolling the contents of the ScrollBar control

MetaTrader 5Examples | 15 February 2023, 14:20
2 227 0
Artyom Trishkin
Artyom Trishkin

Contents


Concept

The scrollbar is able to respond to button presses and slider movements. However, there is no further action. The desired behavior is that when we click on the scroll buttons, the content of the container shifts inside it showing previously hidden areas and hiding previously displayed ones from the opposite side of the container. In the current article, I will create the ability to scroll the contents of the container when clicking on the buttons of the horizontal scrollbar. In this case, the control slider will automatically adjust its size and position.

The scrollbar slider is not just a part of the element that, when moved, allows you to control the position of the form content by scrolling it inside the container. It also serves as a schematic representation of the relative position of the container and its content. The scrollbar itself is the width of the entire content of the container, while the slider on the scrollbar is the container width. The more content goes outside the container, the smaller the slider. The slider size indicates the window, inside which the content is visible, while the scroll bar shows the entire contents of the container. By moving the slider along the scrollbar, we show the program what content within the container we currently want to see.

In exactly the same way, we can control the position of the content using the arrow buttons located at the edges of the scrollbar. At the same time, both the contents of the container itself and the slider on the scrollbar are shifted, showing us which part of the entire content is currently displayed.

In the current article, I will implement the ability to shift the content using the arrow buttons of the horizontal scroll bar. The slider will then move and will have the correct relative size and location coordinates on the scrollbar. First, I am going to develop the functionality of a horizontal scrollbar. Then I will transfer it in a ready-made form to the vertical bar and make them work together.


Improving library classes

Since the scrollbar slider automatically adjusts its size depending on how much the content of the container extends beyond its limits, if the size is greatly reduced, the slider can become too small. To avoid this situation, we should set the minimum acceptable size of the slider. When moving the contents of the container, we need to set the step size in pixels, by which the contents of the container can move in one step. For example, in MetaEditor, this step is six pixels. Let's make it smaller - two pixels.

In \MQL5\Include\DoEasy\Defines.mqh, create two new macro substitutions to specify the above parameters:

#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


When we want to get data from the "top, bottom, left and right borders of a graphical element" object properties, we get it from the properties of the object. These parameters feature the values implemented upon the object successful creation. Next, if we change the object size, the data is no longer saved in the properties of the graphical element. Of course, we get values when we request them with one of the methods that return this property, such as BottomEdge(), but this method simply returns the calculated value. But these values do not change in the object properties. This should be fixed. In the \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh file of the library base graphical element, namely in all methods changing the object borders, implement setting the new values to the object properties:

//+-------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Now, with any change in the size or coordinates of an object, the coordinates of its sides will be set into the properties of the graphical element. This will allow us to correctly find a desired object by the given values of its sides using sorting by the object properties.


Some of the methods we created earlier in the container object class are only needed for container objects, for example, the methods that return the boundaries of the container area, inside which attached objects can be located. Unfortunately, these methods (except for container objects) are not available in other library objects. This is an issue since we access the container object from the class having no access to such an object (it simply does not know about it). As a result, we have to access the properties of such an object through the properties of its parent - the base object of all WinForms library objects. This class does not see the methods of its successor — the container object class. Thus, a vicious circle is formed. To solve this issue, I will move all the necessary methods to the CWinFormBase class, which is a parent class for all WinForms library objects, to the slight detriment of the objects structuring Thus, it will be easier for us to obtain the necessary data when accessing different properties of objects with different purposes. Even if such data belongs to a different type of object and is not used in the current one, all objects will still be able to access the methods of other objects not used in the current one but applied in those being accessed.

This looks pretty confusing but it becomes much clearer in the code. Simply move some methods from the container object class to the base object class of all WinForms library objects. This way we will make the methods visible from any class.

Let's cut these public methods out of the \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh file:

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)

and insert them to the public section of the class in \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:

//--- 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);
                       }

Now these methods will be visible from all WinForms objects of the library.

In order to ensure the visibility of the methods in all WinForms object classes, write the methods that, in essence, should belong to the class of the container object, but the request to them arrives from objects that are not containers.

We need to know how many pixels the objects attached to the container object go beyond the container. Such values will be checked in one method and set into a structure containing fields with the number of pixels on top, bottom, left and right. If anchored objects go beyond any edge of the container, or several at once, then the structure will store the values of the number of pixels on each side of the object, where the content goes beyond the container. From this data, we can then calculate the size of the scroll bar in the ScrollBar control.

In the section of the class, declare such a structure and the variable with the structure type the necessary data is set to:

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:


In the public section, declare the method checking if the bound objects leave the container and filling the above declared structure, the method that shifts all objects bound to the container and the methods returning the minimum and maximum values of the specified property from all bound objects to the current one:

//--- 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

All these methods are needed to work with container objects, but will be called from non-container objects, so they are located in the common parent class of all WinForms objects.

Since the structure and the variable with the type of the structure are private, we need public methods to return the values written in the fields of this structure. Let's implement them in the public section at the very end of the class body:

//--- 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 both class constructors, initialize all fields of the structure using zeros:

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


The method that returns the flag of the container content going beyond its borders:

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

The method logic is fully described in the code comments. In short, we need to know that any (or several, or even all) of the objects bound to the container go beyond its limits. In order to know the maximum values, by which the objects are located outside their container, look for the maximum value on each side in a loop through all the bound objects. At the end of the loop, we will have all the values, by which the bound objects go beyond the container borders on each of its sides. Based on these values, we will later be able to calculate the size of the progress bar slider. The larger the amount of exceeding the container size, the smaller the size of the slider.

The method that shifts all attached objects:

//+-------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

The logic of the method is commented in the code. We need to move the objects bound to the container when the scroll buttons are clicked or when the slider is moved. The method shifts all objects in the list of objects bound to it. The displacement is performed by the Move() method. In this method, everything has already been arranged so that other elements attached to the object being shifted also move. In general, here we shift all objects attached to the container, except scrollbars, by the specified amount, since they are controls of the container object, and not of the objects attached to it (although they are in the general list).


The method returning the maximum value of the specified integer property of all objects subordinate to the base one:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Here the entire logic is also described in the code comments. All is simple here: we loop through all objects (except scrollbars) for an object with the maximum value of the specified property. The found maximum value is returned.


The method that returns the minimum value of the specified integer property from all subordinate base objects:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

The method is similar to the previous one: we loop through all objects (except scrollbars) for an object with the minimum value of the specified property. We return the found minimum value.


When hovering the mouse cursor over the slider of the scrollbar, the entire ScrollBar object needs to be moved to the foreground. Generally, when we hover the mouse over the area of this object, we need to move it above all the container objects. This is necessary so that the cursor can interact with the topmost object, and not with those that may be above it, since they were created later. At the moment, the class of the capture area object is derived from the button object class and uses the functionality of the parent class to interact with the mouse. Since the methods for handling different events when interacting with the mouse are virtual, we need to override them in the capture area object class.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh, namely in the protected section, declare the virtual handlers of mouse events, while in the public section, declare the last mouse event handler:

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


Let's implement the declared virtual handlers.

"The cursor is inside the active area, the mouse buttons are not clicked" event handler:

//+------------------------------------------------------------------+
//| '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);
  }
//+------------------------------------------------------------------+

Here we get the pointer to the base object (scrollbar) and move it to the foreground. Next, call the handler of the parent object mouse event corresponding to the method.


'The cursor is inside the active area, any mouse button is clicked' event handler:

//+-------------------------------------------+
//| '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);
  }
//+------------------------------------------------------------------+

Everything is exactly the same here. First, we bring the base object to the foreground (it will be a scrollbar with all the controls), then we call the parent object mouse event handler.


'The cursor is inside the active area, the left mouse button is clicked' event handler:

//+-------------------------------------------+
//| '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);
  }
//+------------------------------------------------------------------+

All is identical to the two handlers above.


The last mouse event handler is completely moved from the parent object with the aim of its possible further refinement:

//+-------------------------------------------+
//| 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;
     }
  }
//+------------------------------------------------------------------+

At the moment, the handler is identical to the parent class handler.


In the abstract scrollbar object class in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBar.mqh, namely in the method creating the capture area object (slider) and the scroll buttons, set the correct size of the buttons recorded in the macro substitution specifically designed to store the size of the scrollbar buttons:

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


In the method calculating the capture area, return the value specified in the macro substitution that stores the default minimum size of the slider, rather than zero:

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


The scrollbar object class should calculate the size and position of the slider depending on how many pixels the content of the container extends beyond it. When resizing the container or its content, the size and position of the slider should be recalculated. When the scroll buttons are pressed, the content of the container (if it goes beyond the container) should move in the opposite direction from the direction of the pressed button arrow. The slider should move in the direction of the arrow.

In the current article, I will create such functionality for the horizontal scroll bar. After that, I will transfer the ready-made functionality to the vertical scrollbar object class. Their joint work will be implemented afterwards.

In \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh, rename the CalculateThumbAreaSize private method into CalculateThumbAreaDistance(). In the public section, declare the methods for calculating the slider size and coordinates and the main method for recalculating its parameters:

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


Let's consider the declared methods.

The method that sets the new dimensions of the object:

//+-------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

First, set the new size of the scrollbar using the parent class method. Next, upon successful resizing, we need to move the button located on the right side of the object so that it is located at the edge of the resized object (along with resizing, the coordinate of its right edge also changes). After all the changes and movements, recalculate the size and position of the slider using the SetThumbParams() method, I will consider below.


The method that calculates and sets the parameters of the capture area (slider):

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Each line of the method is commented in detail. The essence of the method is to calculate the size of the slider depending on the size of the content of the container that goes beyond its borders. The larger the value in pixels that the contents of the container extends beyond, the smaller the slider. The coordinate of the slider position is calculated from the left edge of the container, also in relative size, so as to correspond to the position of the visible part of the container content relative to its invisible part, which extends beyond the left edge. The more (in pixels) the content of the container extends beyond the left edge, the further to the right the slider will be positioned. As a result, it turns out that the scrollbar, along with the slider, is a reduced copy (or instance) of the container and its contents. In the scroll bar, the bar itself displays the entire content of the container, and the slider displays the container - that part of it, in which the content is visible.


The method that calculates the distance of the capture area (slider):

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

The size of the slider is passed to the method. Next, we calculate how much the size of the slider is less than the size of the container workspace (in which the content is visible). This ratio is then used to calculate the slider distance, which is returned. In other words, as much as the slider is smaller than the container, to such an extent the distance of the slider from the origin of its coordinates is less than the number of pixels by which the content of the container goes beyond its left edge.


The method that returns the size of the slider working area:

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

The working area of the slider is the area, within which it moves in the scroll bar. In other words, this is the area between the two scrollbar arrow buttons. Therefore, to calculate this distance, we need to get the pointers to these buttons and calculate the distance between the left edge of the right button and the right edge of the left one.


The method that returns the coordinate of the beginning of the slider work area:

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

The method returns the coordinate of the right edge of the left scrollbar button. This coordinate is initial for the location of the slider, and it is from this coordinate that we count its position when calculating its dimensions and coordinates.


The mouse event handler is now redesigned and looks like this:

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

Here we add handling clicks on scrollbar buttons. When the button is clicked, calculate the offset coordinates and shift the entire content of the container in the direction opposite to the pressed arrow button. After the shift, re-adjust the size and coordinates of the slider.


When finalizing the class of the horizontal scrollbar object, similar changes and improvements were simultaneously made to the class of the vertical scrollbar object. But not all changes have been made, and not every one of them has been tested because I am going to implement the functionality of the horizontal scroll bar first and then move it to the vertical bar. Therefore, I will not consider here the changes made to the code of the vertical scrollbar class. I will consider it after a complete re-creation based on the working and debugged horizontal scrollbar code.


Let's get back to the container object class in \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh.

In the protected section, declare the method for moving the scrollbars to the foreground. In the public section, implement the methods returning the pointers to both scrollbars, declare the virtual method for shifting the object, while the virtual redrawing method will be made its declaration with an implementation outside the class body:

//+------------------------------------------------------------------+
//| 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 the method that sets the mode of auto resizing the element to fit the content, add a check that the flag of the element auto resizing depending on the content is not set:

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

When setting the size auto change mode, resizing the container to fit its content is called in case a mode is set that does not match the current one. At the same time, if the auto resizing flag is not enabled, then nothing needs to be sorted, which is what the added code does. It gets back from the method when the auto resize flag is cleared.


After the container object is created, if the flag for automatically resizing the container for the content is not set for the object and if the content of the container goes beyond its borders, the corresponding scrollbar should be displayed:

//+-------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Here, we first check the objects attached to the container to see if they are out of borders, and if they really are out of either the left or right border, then we display the horizontal scroll bar. But first we calculate and set the parameters of the scrollbar slider, and after that, display the scrollbar object itself.

Since now the scrollbar object itself handles resizing, the method that resizes the current object has changed:

//+-------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Since it is now not necessary to handle the resizing of the scrollbars here to move their buttons to new places, the method has become shorter and clearer. Each scrollbar is brought to the foreground after resizing.


The method redrawing an object:

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

First, redraw the object using the method of the parent class, then call the method to bring the scrollbars to the foreground.


The method that brings scrollbars to the foreground:

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

Get the pointers to the horizontal and vertical scrollbar objects and, upon successful receipt of the pointer, move each object to the foreground.


The method that updates the coordinates:

//+-------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

First, call the parent class method to move the object. Next, after a successful move, move the scrollbars to the foreground.


Now we need to include events of the scrollbar objects to processing in the collection class of graphical elements.

In the \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh file, namely in its event handler, add the following code block to call the handler of scrollbar object mouse events in the object click handler:

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

Everything is ready for a test.


Test

To perform the test, I will use the EA from the previous article and save it in \MQL5\Experts\TestDoEasy\Part131\ as TestDoEasy131.mq5.

Last time I created the large button object on the panel. Now it is time to add a text to it so that we can see the horizontal shift of the object when clicking on the scroll buttons. The button itself should have the text label object indicating that all attached objects are shifted correctly:

//--- 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)
           {


Compile the EA and launch it on the chart:


As you can see, the scrolling managed by the arrow buttons works well. When trying to move the slider with the mouse, it "resists", which is natural - we still do not have processing of the slider shift, but we already have a recalculation of its size and coordinates. So, when we try to move the slider with the mouse, the method of setting its coordinates returns it to the position that corresponds to the position of the container content in its visible area. This behavior will be finalized in the subsequent articles.



What's next?

In the next article, I will continue the development of the ScrollBar control.

Back to contents

*Previous articles within the series:

 
DoEasy. Controls (Part 26): Finalizing the ToolTip WinForms object and moving on to ProgressBar development
DoEasy. Controls (Part 27): Working on ProgressBar WinForms object
DoEasy. Controls (Part 28): Bar styles in the ProgressBar control
DoEasy. Controls (Part 29): ScrollBar auxiliary control
DoEasy. Controls (Part 30): Animating the ScrollBar control

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/11926

Attached files |
MQL5.zip (4551.42 KB)
How to choose an Expert Advisor: Twenty strong criteria to reject a trading bot How to choose an Expert Advisor: Twenty strong criteria to reject a trading bot
This article tries to answer the question: how can we choose the right expert advisors? Which are the best for our portfolio, and how can we filter the large trading bots list available on the market? This article will present twenty clear and strong criteria to reject an expert advisor. Each criterion will be presented and well explained to help you make a more sustained decision and build a more profitable expert advisor collection for your profits.
Creating a ticker tape panel: Improved version Creating a ticker tape panel: Improved version
How do you like the idea of reviving the basic version of our ticker tape panel? The first thing we will do is change the panel to be able to add an image, such as an asset logo or some other image, so that the user could quickly and easily identify the displayed symbol.
Creating an EA that works automatically (Part 01): Concepts and structures Creating an EA that works automatically (Part 01): Concepts and structures
Today we'll see how to create an Expert Advisor that simply and safely works in automatic mode.
Creating a ticker tape panel: Basic version Creating a ticker tape panel: Basic version
Here I will show how to create screens with price tickers which are usually used to display quotes on the exchange. I will do it by only using MQL5, without using complex external programming.