English Русский Español Deutsch 日本語 Português
preview
DoEasy.控件(第 33 部分):垂直滚动条

DoEasy.控件(第 33 部分):垂直滚动条

MetaTrader 5示例 |
478 3
Artyom Trishkin
Artyom Trishkin

目录


概念

在上一篇专门介绍库图形元素的文章中,我们创建了一个水平滚动条,如果窗体上的对象在左侧、右侧或两侧边缘超出了父窗体的边界,该滚动条就会出现在对象上。在此,我们将基于水平滚动条对象创建一个垂直滚动条。如果所连接的对象在顶部、底部或这两个部分超出了表格的边界,表格上就会出现该对象。

这篇文章相对较短,更像是一个概述,因为复制水平滚动条对象并将其转换为垂直滚动条对象是一件非常容易的事情。以后我们在开发 Windows 窗体样式的后续控件时,将需要这些滚动条。垂直滚动条基本上在很久以前就开发出来了,但由于一个小错误,或者说是一个疏漏,导致在与图形元素交互时出现非常令人不愉快的现象,表现为不可见的对象部分不断 "闪烁",从而延误了文章的发表。出现这种情况的原因是未加控制地过早更新了对象,这些对象随后被修剪为其父对象的大小。这种 "闪烁" 是这样出现的 - 首先,对象被完全渲染并显示在图表上。然后,对其进行裁剪,以适应父对象窗体的大小。在这种情况下,解决方法通常很简单,那就是通过重绘消除过早更新。但是,我花了很多时间才找到重新绘图的地方。现在,这个错误已被发现并修复,我们可以放心地继续开发该库了。


改进库类

首先,我们将添加一些有用的函数和方法,这些函数和方法是我们在后续库改进中需要用到的。

有时,我们需要查找发生事件的柱形的开启时间。如果事件发生在烛形开启的那一刻,那么找到时间就没有问题。但如果事件发生在烛形的开启时间和关闭时间之间,则可以使用该事件发生的时间来计算指定图表周期内烛形的开启时间。当然,我们可以使用标准函数,将事件时间转换为柱形索引,最后使用柱形索引获得所需图表周期内所需柱形的开启时间...但这一切都需要 CPU 时间。如果执行速度很重要,最好还是使用计算方法,前提是事件发生在真实的烛形中。

这里的俄语论坛有一个有用的主题,资源用户在那里分享此类有趣的代码。让我们利用所提出的算法,编写库函数。

在 \MQL5\Include\DoEasy\Services\DELib.mqh 库文件的最后执行以下函数:

//+---------------------------------------------------------------------------------+
//| Get the opening time of the virtual bar based on input time and                 |
//| timeframe, regardless of the existence of a real bar.                           |
//| It counts correctly only till 28.02.2100                                        |
//| It is not a replacement for iBarShift!!! It does not depend on the bar history. |
//| https://www.mql5.com/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;
     }
  }
//+------------------------------------------------------------------+

算法分析可在上述链接的论坛上找到。随后,利用这个函数,我们总能找到柱形的开启时间,在这段时间内发生了一些事件。同时,在计算速度非常重要的情况下,我们将不必使用速度不够快的函数。

在使用图形对象时,有时需要根据情况改变图形对象的颜色。当然,我们也可以使用标准色列表中的颜色,但这些颜色往往并不够。例如,有一个中性灰色的对象。视情况而定,它的色彩可能会略有变化。在一种情况下,它的颜色可能会变为微红,在另一种情况下,它的颜色可能会变为微绿。换句话说,在这种情况下,我们只需要稍微增加颜色中一个或另一个成分的饱和度,而不需要应用标准设置中的颜色。

为此,请在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 图形元素对象文件中声明以下方法

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

我们在类主体之外编写其实现:

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

这里一切都很简单 - 我们获取传递给方法的每个需要更改的颜色成分,并将传递给方法的相应值添加到结果成分的值中。如果任何一个值最终大于 255,我们就将其调整为 255。因此,我们使用 CColor 库类的 RGBToColor 方法,返回计算出的新成分所包含的颜色。

在同一个文件中,有一个方法可以设置图形元素可见区域的坐标和尺寸:

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

此外,我们还可以单独指定可见性范围的设置方式 - 仅在图形元素对象的属性中设置,或在属性和物理对象中设置。要做到这一点,只需添加另一个输入变量,并相应地修改方法调用,将作用域设置为整个对象的大小:

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


在实现过程中,我们需要清除元素并填充颜色和不透明度,但不进行裁剪,而是通过标志更新图表,因此需要对对象更新逻辑稍作修改。在此之前,无论是否设置了图表重绘标记,对象都会在此处更新

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

此处带有图表重绘标记的 Update() 方法总是在对象完全涂上 EraseNoCrop() 方法参数中指定的颜色后才更新对象。因此,无论重绘标志是什么,对象始终会都被更新(来显示应用的更改)。重绘标志只影响变化显示时间 - 要么立即显示(如果标志设置为 true),要么在 分时报价到达或图表更新时显示(如果标志设置为 false)。由于这种方法可以对整个对象完全重新着色,因此可以随时在图表中以完全尺寸显示。如果该对象本应按照其所连接的父对象的大小进行裁剪,那么这种重绘会导致对象的不可见部分出现令人不快的 "闪烁",因为不可见部分的裁剪总是在调用该方法后进行的。
现在一切都已修复。对象不可见的部分不再闪烁:

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

在这里,只有设置了重绘标记,对象才会更新。因此,我们现在可以通过编程来控制对象的显示 - 如果我们完全确定对象不需要裁剪,那么我们调用设置了标记的方法,它的新外观就会立即显示在图表上。如果需要裁剪对象,则首先调用此方法并清除标记,然后调用 Crop() 方法裁剪隐藏区域,并根据标记更新对象的外观和重新绘制的图表。正是这一逻辑错误阻碍了库中图形元素的进一步开发。该错误现已解决。

现在在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\BarProgressBar.mqh 中,即在定时器处理程序中,通过指定必要的标志来固定调用 SetVisibleArea() 方法

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

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

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


几乎每个库对象都包含一个图形控件对象,允许我们动态创建图形对象窗体。让我们添加一些方法,以便创建一些标准图形对象。在图形对象管理对象类 \MQL5\Include\DoEasy\Objects\Graph\GraphElmControl.mqh 文件的公有部分,声明绘制趋势线和箭头的新方法

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


在私有部分,声明为标准图形对象设置一般参数的方法

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

默认情况下,每个新创建的对象都应分配一些属性,这些属性对所有创建的图形对象的意义都是一样的:对象应隐藏在所有图表对象的列表中,不能被选中,也不能用鼠标选择,而且应显示在所有时间框架上。这些属性由 SetCommonParamsStdGraphObj 方法设置,该方法的实现在类主体之外:

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


此外,让我们在类主体之外编写创建图形对象的方法的实现:

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


图形对象管理类对象的实例包含在从 CBaseObj 库基本对象类继承的每个库对象中。图形对象管理对象拥有创建此类对象的方法。为了从对象类中创建图形对象,我们需要在基本对象类中编写创建图形对象的方法。这将简化应用程序中的图形开发。基本上,我们可以首先获取所需对象的指针,然后获取其图形对象管理对象的指针,然后通过访问其方法来创建图形对象。但这是个漫长的过程。更简单、更方便、更快捷的做法是,只需获取指向对象的指针,然后使用其方法创建图形对象,并在其中执行上述整个链条。

在 \MQL5\Include\DoEasy\Objects\BaseObj.mqh 库基础对象文件的公共部分,在处理图形对象部分,声明用于创建趋势线和箭头对象的新方法

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

只需在方法中调用图形控件对象的相应方法即可。今后,我们将添加创建其他标准图形对象的方法。目前,这些图形对象足够我们在后续文章中使用。

让我们开始创建垂直滚动条对象。

滚动条抓取区域是一个滑块,你可以用鼠标抓取并在滚动条内移动,从而移动它所控制的区域。当鼠标滚轮向一个或另一个方向滚动时,光标位于滚动条内,相应的滚动控制按钮(滚动条边缘的箭头按钮)上就会产生一个单击事件。当鼠标滚轮滚动水平滚动条时已经生成了事件 - 点击左右箭头按钮事件。现在,我们需要为点击垂直滚动条滑块的上下箭头按钮添加事件生成功能。

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh 文件中,即在 "光标位于活动区域内,鼠标滚轮正在滚动" 事件处理程序中,进行以下更改

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

根据捕捉区域的基本对象(水平滚动条或垂直滚动条),基本对象的事件处理函数会调用相应的事件代码:鼠标点击左或右箭头按钮,或者鼠标点击向上或向下箭头按钮。

要创建垂直滚动条对象,请将水平滚动条对象类文件 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh 保存为 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarVertical.mqh。由于新类是在相同类的基础上创建的,因此我们只需替换一些计算方法:在计算中使用 "top/bottom" 代替 "left/right",等等。没有必要对所做的每一项改动都进行描述。请阅读相关文章,了解创建此类对象的更多信息。在此,我们将只查看已做修改的整个类文件:

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

在对该文件进行更改后,子对象在顶部、底部或两侧同时超出父对象的时候,对象上就会出现一个垂直滚动条。

父对象是子对象的容器,为了让滚动条出现在父对象上,在创建附加到父对象上的对象时,我们需要检查对象是否超出了容器的边界。在显示水平滚动条时已经实现了这种检查。现在,我们需要修改容器对象类,以便在容器上的对象向两个方向上扩展时,两个滚动条都会出现。

打开 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh 文件,并在创建附加对象的方法中添加必要的检查滚动条显示

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


现在,我们需要修正一些调用重绘方法而不裁剪 EraseNoCrop() 对象的类。我们需要设置 false,这样对象就不会在方法中更新。

需要对三个库文件(MQL5/Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh、\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh 和 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh)中三个对象的 Redraw() 方法进行更改。

所有的变化都是设置 false 标志:

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

其逻辑如下:首先,整个对象被填充颜色,同时重置更新标志,这意味着图表上不会显示更改。然后使用指定参数绘制文本。接下来,对象会沿着可视区域的边缘被裁剪(如有必要),完成后,对象更新方法会被调用,该方法会在其父对象和整个图表上显示对对象表示法所做的更改。


测试

要执行测试,让我们使用前一篇文章中的 EA,并将其保存在 \MT5\MQL5\Experts\TestDoEasy\Part133\ 中,作为 TestDoEasy133.mq5

在创建附加到面板上的按钮对象时,我们应改变其尺寸,使其垂直尺寸大于父对象。换句话说,它应该向上和向下超出边缘,同时在宽度上完全贴合父面板:

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

就是这些了。无需进行其他更改。

编译 EA 并在图表上启动,同时事先指定面板自动大小为 "No":


我们可以看到,垂直滚动条的工作原理与上一篇文章中实现的水平滚动条完全相同。


接下来做什么?

在为 DoEasy 库创建图形控件的下一篇文章中,我们将在一个容器对象上连接两个滚动条,并继续创建其他控件。

所有文件都附在文章后面,您可以自己学习和测试。


返回内容目录


本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14278

附加的文件 |
MQL5.zip (5257.78 KB)
最近评论 | 前往讨论 (3)
Jefferson Judge Metha
Jefferson Judge Metha | 26 8月 2024 在 22:20
你好,我正在试用这个库。是否可以创建编辑框

例如,默认的 Lotsize mt5 punel 有上下按钮
Artyom Trishkin
Artyom Trishkin | 27 8月 2024 在 04:58
Jefferson Judge Metha #:
下午好,我正在试用这个库。是否可以创建编辑字段。

例如,Lotsize mt5 punel 默认有向上和向下按钮。

您好。当然可以。但这种控件还没有达到这种程度。很多东西都将逐步实现。但不会立即实现,也不会很快实现--目前的重点不是图书馆。

Jefferson Judge Metha
Jefferson Judge Metha | 27 8月 2024 在 05:07
谢谢你们
美丽的图书馆
龟壳演化算法(TSEA) 龟壳演化算法(TSEA)
这是一种受乌龟壳演化启发的独特优化算法。TSEA算法模拟了角质化皮肤区域的逐渐形成,这些区域代表了一个问题的最优解。最优解会变得更加“坚硬”,并位于更靠近外层表面的位置,而不太理想的解则保持“较软”的状态,并位于内部。该算法通过根据质量和距离对解进行聚类,从而保留了不太理想的选项,并提供了灵活性和适应性。
两样本Kolmogorov-Smirnov检验作为时间序列非平稳性的指标 两样本Kolmogorov-Smirnov检验作为时间序列非平稳性的指标
本文探讨了最著名的非参数同质性检验之一——两样本柯尔莫哥洛夫-斯米尔诺夫(Kolmogorov-Smirnov)检验。文章对模型数据和实际价格都进行了分析。此外,本文还给出了构建非平稳性指标(iSmirnovDistance)的一个示例。
构建一个K线图趋势约束模型(第二部分):融合原生指标 构建一个K线图趋势约束模型(第二部分):融合原生指标
这篇文章的重点在于如何利用MetaTrader 5的内置指标来甄别逆势信号。在上一篇文章的基础上,我们将进一步探讨如何使用MQL5代码将我们的想法最终用代码实现。
DoEasy.服务功能(第 2 部分):孕线形态 DoEasy.服务功能(第 2 部分):孕线形态
本文将继续探讨 DoEasy 库中的价格形态。我们还将创建价格行为形态中的 "孕线"(Inside Bar)形态类。