Русский 中文 Español Deutsch 日本語 Português
preview
DoEasy. Controls (Part 33): Vertical ScrollBar

DoEasy. Controls (Part 33): Vertical ScrollBar

MetaTrader 5Examples | 19 June 2024, 12:38
1 432 3
Artyom Trishkin
Artyom Trishkin

Contents


Concept

In the previous article dedicated to library graphical elements, we created a horizontal scroll bar that appears on an object if the object attached to a form extends beyond the boundaries of its parent form on the left, right, or both edges. Here we will created a vertical scrollbar based on the horizontal scrollbar object. It will appear on the form if the object attached to it extends beyond its boundaries at the top, bottom, or both sides.

The article is relatively short, more of an overview, since making a copy of the horizontal scrollbar object, turning it into a vertical one, is a pretty easy task. We will need these scrollbars later when developing subsequent controls in the Windows Form style. The vertical scroll bar was essentially developed a long time ago, but the publication of the article was delayed due to a small error, or rather an omission, that led to very unpleasant artifacts when interacting with graphical elements, which manifested itself in the constant "blinking" of invisible object parts. This happened due to uncontrolled premature updating of objects, which were subsequently trimmed to the size of their parent. This is how this "blinking" appeared - first, the object was completely rendered and displayed on the chart. After that, it was cropped to fit the size of the parent object form. As is usually the case in such situations, the solution turned out to be simple - removing the premature update with redrawing. But it took a lot of time to find the place where the redrawing took place. Now this bug has been found and fixed, so we can safely continue developing the library.


Improving library classes

First of all, we will add useful functions and methods we are going to need in subsequent library improvements.

Sometimes, we need to find an opening time of a bar where an event occurred. If the event occurred at the moment the candle opened, then there are no problems with finding the time. But if the event occurred between the candle opening and closing time, then you can use the time of this event to calculate the opening time of the candle on a given chart period. We can, of course, use standard functions, convert the event time into a bar index, and finally get the opening time of the desired candle on the required chart period using the bar index... But all this takes CPU time. If the execution speed is important, it is still better to use the calculation, provided that the event occurred inside a real-life candle.

The Russian-language forum here has a useful thread, where resource users share interesting codes of this kind. Let's take advantage of the proposed algorithm and write the library function.

Implement the following function at the very end of the \MQL5\Include\DoEasy\Services\DELib.mqh library file:

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

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

The algorithm analysis is found on the forum at the link above. Subsequently, using this function, we can always find the opening time of the bar, within which there was some event. At the same time, we will not have to resort to insufficiently fast functions where calculation speed is important.

When working with graphical objects, sometimes it is necessary to change the color of the graphical object depending on the situation. We can, of course, use colors from the list of standard colors, but they are often not sufficient. For example, there is a certain object, say, of a neutral gray color. Depending on the situation, it may change its tone slightly. In one case, it may change color to slightly reddish, in another case, to slightly greenish. In other words, in this case, we only need to slightly add the saturation of one or another component of the color, rather than apply colors from the standard set.

To achieve this, declare the following method in the \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh graphical element object file:

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

Let's write its implementation outside the class body:

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

All is simple here - we get each of the color components passed to the method that needs to be changed, and add the corresponding values, passed to the method, to the values of the resulting components. If any of the values ends up being greater than 255, we adjust it to 255. As a result, we return the color comprised of the calculated new components using the RGBToColor method of the CColor library class.

Here, in the same file, there is a method that sets the coordinates and dimensions of the visible area of the graphical element:

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

Let's add to it the ability to independently indicate how the scope of visibility is set - only in the properties of a graphical element object, or in the properties and in a physical object. To do this, simply add another input variable and, correspondingly, fix the method call, which sets the scope to the entire object size:

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


In the implementation, clearing the element and filling it with color and opacity without cropping but updating the chart by flag, we need to slightly change the object update logic. Previously, the object was always updated here regardless of whether the chart redraw flag was set:

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

The Update() method with the chart redraw flag here always updates the object after it has been completely painted over with the color specified in the parameters of the EraseNoCrop() method. Accordingly, regardless of the redraw flag, the object was always updated (the applied changes were displayed). The redraw flag only affected the change display time — either immediately (if the flag was set to true), or upon tick arrival or chart update (if the flag was set to false). Since this method completely recolors the entire object, it can be displayed at any time on the chart in its full size. If this object should have been cropped to the size of its parent object it was attached to, then this redrawing caused unpleasant "blinking" of the invisible part of the object, since cropping of the invisible part is always performed after calling this method.
Now all has been fixed. There is no more blinking of the object invisible part:

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

Here, the object is updated only when the redraw flag is set. Accordingly, we can now control the display of an object programmatically - if we are absolutely sure that the object does not need to be cropped, then we call the method with the flag set, and its new appearance is immediately displayed on the chart. If the object needs to be cropped, then this method is first called with the flag cleared, and then the Crop() method is called, which crops hidden areas and updates the appearance of the object with the chart redrawn based on the flag. It was this logical error that prevented the further development of the library graphical elements. The error has now been resolved.

Now in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\BarProgressBar.mqh, namely in the timer handler, fix calling the SetVisibleArea() method by specifying the necessary flag:

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

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

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


Almost every library object includes a graphical control object that allows us to dynamically create graphical object forms. Let's add the methods that allow us to create some standard graphical objects. In the public section of the file for the graphical objects management object class \MQL5\Include\DoEasy\Objects\Graph\GraphElmControl.mqh, declare new methods for drawing trend lines and arrows:

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

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

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

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


In the private section, declare the method that sets general parameters for standard graphical objects:

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

Each newly created object should be assigned by default some properties, the meaning of which is the same for all created graphical objects without exception: the object should be hidden in the list of all chart objects, not selected and not selectable with the mouse, and it should be displayed on all timeframes. These are the properties set by the SetCommonParamsStdGraphObj method whose implementation is done outside the class body:

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


Also, let's write the implementation of methods, that create graphical objects, outside the class body:

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


The instance of an object of the graphical objects management class is included in each library object inherited from the CBaseObj library base object class. The graphical objects management object has the methods for creating such objects. In order for us to create graphical objects from the object class, we need to write methods for creating graphical objects in the base object class. This will simplify the development of graphics in applications. Essentially, we can first get the pointer to the desired object, then get the pointer to its graphical objects management object, and then, by accessing its methods, create graphical objects. But it is a long way. It is easier, more convenient and faster to simply get a pointer to an object and use its methods to create graphical objects, within which the entire chain described above will be executed.

In the public section of the \MQL5\Include\DoEasy\Objects\BaseObj.mqh library base object file, in the section for working with graphical objects, declare new methods for creating trend lines and arrow objects:

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

The appropriate methods of the graphics control object are simply called in the methods. In the future, we will add methods for creating other standard graphical objects. For now, these graphical objects will be enough for us to use in subsequent articles.

Let's start creating a vertical scrollbar object.

A scrollbar grab area is a slider you can grab with your mouse and move within the scrollbar, thereby moving the area controlled by it. When scrolling the mouse wheel in one direction or another, while the cursor is inside the scroll bar, a click event is generated on the corresponding scroll control button (arrow buttons at the edges of the scroll bar). The events are already generated when the mouse wheel is scrolled for the horizontal scroll bar - right and left arrow button clicking events. Now we need to add event generation for the up and down arrow button clicking for the vertical scrollbar slider.

In the \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh file, namely in the "The cursor is inside the active area, the mouse wheel is being scrolled" event handler, make the following changes:

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

Depending on which object is the base object for the capture area (the horizontal or vertical scroll bar), the event handler of the base object is called the corresponding event codes are passed to: either a mouse click on the left or right arrow button, or a mouse click on the arrow up or down button.

To create a vertical scrollbar object, take the horizontal scrollbar object class file \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh and save it as \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarVertical.mqh. Since a new class is created on the basis of an identical one, we just need to replace some calculations: instead of "left/right" in the calculations, use "top/bottom", and so on. There is no point in describing every change made. Read the appropriate article to learn more about creating such an object. Here we will just look at the entire class file with the modifications already made:

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

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

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

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

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

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

After changes have already been made to this file, a vertical scroll bar appears in objects, to which children objects are attached, and which extend beyond the parent object at the top, bottom, or both sides at once.

In order for scroll bars to appear on a parent object, which is a container for its children objects, when creating an object attached to it, we need to check whether it extends beyond the boundaries of its container. This check has already been implemented to display a horizontal scrollbar. Now we need to modify the container object class so that both scroll bars appear if an object attached to the container extends beyond its boundaries on either side.

Open the \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh file and add the necessary checks and scrollbars display in the method for creating attached objects:

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


Now we need to correct some classes that call the method of redrawing without cropping the EraseNoCrop() object. We need to set false, so that the object does not update inside the method.

Changes need to be made to the Redraw() methods of three objects in three library files \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh and \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh.

All changes boil down to setting the false flag:

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

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

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

The logic is as follows: first, the entire object is filled with color, while the update flag is reset, which means the changes are not displayed on the chart. Then the text is drawn with the specified parameters. Next, the object is cropped along the edges of the visible area (if necessary), and upon completion, the object update method is called, which displays the changes made to the object representation on its parent object in particular, and on the chart in general.


Test

To perform the test, let's use the EA from the previous article and save it in \MT5\MQL5\Experts\TestDoEasy\Part133\ as TestDoEasy133.mq5.

When creating a button object attached to the panel, we should change its dimensions so that it is larger in vertical dimensions than its parent. In other words, it should go beyond the edges upwards and downwards, while fitting completely inside the parent panel by width:

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

That's it. No other changes are required.

Compile the EA and launch it on the chart, while specifying No for Panel Autosize beforehand:


We see that the vertical scrollbar works exactly the same as the horizontal one implemented in the previous article.


What's next?

In the next article dedicated to creating graphical controls for the DoEasy library, we will connect together both scrollbars on a container object and continue creating other controls.

All files are attached to the article and you can study and test them yourself.


Back to contents


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

Attached files |
MQL5.zip (5257.78 KB)
Last comments | Go to discussion (3)
Jefferson Judge Metha
Jefferson Judge Metha | 26 Aug 2024 at 22:20
Good day i am trying out this library. Is it possible to create edit boxes 

Like for example the default Lotsize mt5 punel has up and down buttoms
Artyom Trishkin
Artyom Trishkin | 27 Aug 2024 at 04:58
Jefferson Judge Metha #:
Good afternoon, I am trying this library. Is it possible to create edit fields.

For example, Lotsize mt5 punel has up and down buttons by default.

Hello. Of course it is possible. But this control has not yet reached this element. Gradually a lot of things will be implemented. But not immediately and not quickly - at the moment the priorities are shifted not towards the library.

Jefferson Judge Metha
Jefferson Judge Metha | 27 Aug 2024 at 05:07
Thank you. 
Beautiful library, 
Angle-based operations for traders Angle-based operations for traders
This article will cover angle-based operations. We will look at methods for constructing angles and using them in trading.
How to earn money by fulfilling traders' orders in the Freelance service How to earn money by fulfilling traders' orders in the Freelance service
MQL5 Freelance is an online service where developers are paid to create trading applications for traders customers. The service has been successfully operating since 2010, with over 100,000 projects completed to date, totaling $7 million in value. As we can see, a substantial amount of money is involved here.
MQL5 Wizard Techniques you should know (Part 23): CNNs MQL5 Wizard Techniques you should know (Part 23): CNNs
Convolutional Neural Networks are another machine learning algorithm that tend to specialize in decomposing multi-dimensioned data sets into key constituent parts. We look at how this is typically achieved and explore a possible application for traders in another MQL5 wizard signal class.
Neural networks made easy (Part 76): Exploring diverse interaction patterns with Multi-future Transformer Neural networks made easy (Part 76): Exploring diverse interaction patterns with Multi-future Transformer
This article continues the topic of predicting the upcoming price movement. I invite you to get acquainted with the Multi-future Transformer architecture. Its main idea is to decompose the multimodal distribution of the future into several unimodal distributions, which allows you to effectively simulate various models of interaction between agents on the scene.