English Русский Español Deutsch 日本語 Português
preview
DoEasy. 控件(第三十一部分):滚动条控件内内容的滚动

DoEasy. 控件(第三十一部分):滚动条控件内内容的滚动

MetaTrader 5示例 | 8 五月 2023, 09:51
486 0
Artyom Trishkin
Artyom Trishkin

内容


概述

滚动条能够响应按下按钮和移动滑块。 不过,没有进一步的行动。 所需的行为应是,当我们单击滚动按钮时,容器的内容会在其内部平移,显示以前隐藏的区域,并在容器的另一侧隐藏以前显示的区域。 在本文中,我将创建在单击水平滚动条的按钮时滚动容器内容的功能。 在这种情况下,控制滑块将自动调整其大小和位置。

滚动条滑块不仅仅是元素的一部分,在移动时,它允许您通过在容器内滚动,来控制窗体内容的位置。 它还用作容器及其内容的相对位置的概念示意。 滚动条本身是容器整个内容的宽度,而滚动条上的滑块是容器宽度。 超出容器外的内容越多,滑块就越小。 滑块大小指示在窗口内可见的内容,而滚动条示意容器的全部内容。 通过沿滚动条移动滑块,我们请求程序显示我们当前要在容器中查看的内容。

以完全相同的方式,我们可以通过位于滚动条边缘的箭头按钮来控制内容的位置。 与此同时,容器本身的内容和滚动条上的滑块都会移动,由此向我们示意当前显示的内容是整体的哪一部分。

在本文中,我将实现利用水平滚动条的箭头按钮移动内容的功能。 然后是移动滑块,并在滚动条上摆放正确的相对大小和位置坐标。 首先,我将开发水平滚动条的功能。 然后我将它按现有形式转移到垂直条上,并令它们相伴工作。


改进库类

由于滚动条滑块会根据内容超出容器限制的程度自动调整其大小,因此如果容器宽度极大减小,滑块可能会变得太小。 为避免这种情况,我们应该设置滑块的最小可接受尺寸。 移动容器的内容时,我们需要以像素为单位设置步长,依据该步长,容器的内容可以按一个步长移动。 例如,在 MetaEditor 中,此步长是 6 个像素。 我们取更小的值 — 两个像素。

在 \MQL5\Include\DoEasy\Defines.mqh 中,创建两个新的宏替换,来指定上述参数:

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


当我们想从“图形元素的上、下、左和右边框”对象属性中获取数据时,我们可从对象的属性里提取数据。 这些参数含有成功创建对象时赋予的数值。 接下来,如果我们更改对象大小,则数据将不再保存在图形元素的属性当中。 当然,若我们调用返回此属性的方法之一,例如 BottomEdge() 请求数值时,我们也能得到该值,但此方法仅返回计算值。 但这些数值并未在对象属性中被更改。 这应该被修复。 在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 函数库基准图形元素的文件中,即在所有更改对象边框的方法中,实现把新值赋予对象属性:

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

现在,随着对象大小或坐标的任何更改,其边缘的坐标亦将被设置到图形元素的属性当中。 这就令我们能够依据对象属性排序,通过其边缘的给定值正确找到所需的对象。


我们之前在容器对象类中创建的一些方法仅对容器对象有用,例如,返回容器区域边界的方法,其内的附着对象可由此定位。 不幸的是,这些方法(容器对象除外)在其它函数库对象中不可用。 这确实是一个问题,因为我们从容器对象无法访问此类对象(很简单地因为根本不认识它)。 结果就是,我们必须通过其父对象的属性来访问此类对象的属性 — 所有 WinForms 函数库对象的基准对象。 该类看不到其后继者的的方法 — 容器对象类。 故此,形成了恶性循环。 为了解决这个问题,我将把所有必要的方法移动到 CWinFormBase 类,它是所有 WinForms 函数库对象的父类,因此会略微损害了对象结构,但在访问拥有不同目的的对象和不同属性时,我们将更容易获得必要的数据。 即使此类数据属于不同类型的对象,并且未在当前对象中使用,所有对象仍将能够访问当前对象中未使用,但却在所访问对象中应用的其它对象的方法。

这看起来很混乱,但在代码中则变得更加清晰。 只需将一些方法从容器对象类移动到所有 WinForms 库对象的基准对象类。 以这种方式,我们就可令方法在任何类中可见。

我们从 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh 文件中删除这些公开方法

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

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

将它们插入到 \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh 类的公开部分

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

现在,这些方法即可从函数库的所有 WinForms 对象中可见。

为了确保方法在所有 WinForms 对象类中的可见性,本质上,编写的方法应属于容器对象类,但对它们的请求可来自非容器的对象。

我们需要知道附着到容器的对象超出容器的像素数。 这些值将在一个方法中检查,并在包含顶部、底部、左侧和右侧像素数,等字段的结构中设置。 如果锚定的对象超出容器的任何边缘,或一次超出多个边缘,则结构将保存对象每一侧内容超出容器的像素数。 依据此数据,我们可以计算滚动条控件中的滚动条的尺寸。

在类的私密部分中,声明这样的结构,以及设置数据所需的含有结构类型的变量

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

public:


在公开部分中,声明检查绑定对象是否离开容器,并填充上述声明结构的方法、将绑定到容器的所有对象移动的方法,以及将指定属性的最小值和最大值从所有绑定对象返回到当前对象的方法

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

所有这些方法都是处理容器对象所必需的,但可从非容器对象调用,因此它们位于所有 WinForms 对象的共用父类当中。

由于结构和具有结构类型的变量是私密的,因此我们需要公开方法来返回写入此结构字段中的数值。 我们在类主体末尾的公开部分中实现它们:

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


在这两个类构造函数中,以零值初始化结构的所有字段

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


该方法返回内容超出容器边界的标志:

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

代码注释中完整描述了方法逻辑。 简而言之,我们需要知道任何绑定到容器的(或几个,甚至所有)对象超出了其限制。 为了知道对象超出其容器外部的最大值,在循环中遍历所有绑定对象,以便查找每侧的最大值。 在循环结束时,我们将得到所有绑定对象超出容器边界其两侧的数值。 基于这些数值,我们稍后就能够计算进度条滑块的大小。 超出容器尺寸的体量越大,滑块的尺寸就越小。

移动所有附着对象的方法:

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

该方法的逻辑在代码中已有注释。 我们需要在单击滚动按钮、或移动滑块时移动绑定到容器的对象。 该方法移动绑定到它的对象列表中的所有对象。 位移由 Move() 方法执行。 在该方法中,所有内容都已安排好,如此即便附着到正在移动的对象的其它元素也会一并移动。 通常,于此我们将附着到容器的所有对象(滚动条除外)按指定量移动,因为它们是容器对象的控件,而不是附着到容器对象的控件(尽管它们也在常规列表中)。


该方法返回从属于基准对象的所有对象的指定整数型属性的最大值:

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

此处的整个逻辑在代码注释中已讲述。 这里的一切都很简单:我们遍历所有对象(滚动条除外),查找具有指定属性最大值的对象。 返回找到的最大值。


该方法返回从属于基准对象的所有对象的指定整数型属性的最小值:

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

该方法与前一个方法类似:我们遍历所有对象(滚动条除外),查找具有指定属性最小值的对象。 我们返回找到的最小值。


当鼠标光标悬停在滚动条滑块上时,需要将整个滚动条对象切换到前景。 通常,当我们将鼠标悬停在此对象的区域上时,我们需要将其移动到所有容器对象上方。 这是必要的,以便光标可以与最顶层的对象交互,而不是与可能位于其上方的对象进行交互,因为它们是后来创建的。 在此刻,捕获区域对象的类派生自按钮对象类,并利用父类的功能与鼠标交互。 由于处理不同鼠标交互事件的方法都是虚拟的,因此我们需要在捕获区域对象类中覆盖它们。

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh 中,即在受保护部分,声明鼠标事件的虚拟处理程序,而在公开部分中,声明最后一个鼠标事件处理程序

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

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

//--- Protected constructor with object type, chart ID and subwindow
                     CScrollBarThumb(const ENUM_GRAPH_ELEMENT_TYPE type,
                                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
                             
public:
//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);

//--- Constructor
                     CScrollBarThumb(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+


我们实现声明的虚拟处理程序。

“光标在活动区域内,未单击鼠标按钮”事件处理程序:

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

在此,我们获取指向基准对象(滚动条)的指针,并将其移动到前景。 接下来,调用与该方法对应的父对象鼠标事件的处理程序


“光标位于活动区域内,单击任何鼠标按钮”事件处理程序:

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

这里的一切都完全一样。 首先,我们将基准对象置于前景(它将是一个包含所有控件的滚动条),然后我们调用父对象鼠标事件处理程序。


“光标位于活动区域内,单击鼠标左键”事件处理程序:

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

所有处理程序都与上面的两个处理程序雷同。


最后一个鼠标事件处理程序已完全从父对象中移出,目的是进一步优化它:

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

此刻,处理程序与父类处理程序雷同。


在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBar.mqh 抽象滚动条对象类中,即在创建捕获区域对象(滑块)和滚动按钮的方法中,在专门设计存储滚动条按钮大小的宏替换中设置按钮的正确大小

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


在计算捕获区域的方法中,返回在宏替换中指定的值,该值存储滑块的默认最小尺寸,而不是零值:

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


滚动条对象类应根据容器里的内容超出滑块的像素数来计算滑块的尺寸和位置。 调整容器或其内容的大小时,应重新计算滑块的尺寸和位置。 按下滚动按钮时,容器的内容(如果超出容器)应沿与所按下按钮箭头的方向相反的方向移动。 滑块则应沿箭头方向移动。

在本文中,我将为水平滚动条创建此类功能。 之后,我会把现成的功能移植到垂直滚动条对象类当中。 如此这般,它们就能实现联合工作。

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh 中,将 CalculateThumbAreaSize 私密方法重命名为 CalculateThumbAreaDistance()。 在公开部分中,声明计算滑块大小和坐标的方法,以及重新计算其参数的主要方法

//+------------------------------------------------------------------+
//| CScrollBarHorisontal object class of WForms controls             |
//+------------------------------------------------------------------+
class CScrollBarHorisontal : public CScrollBar
  {
private:
//--- Create the ArrowButton objects
   virtual void      CreateArrowButtons(const int width,const int height);
//--- Calculate the distance of the capture area (slider)
   int               CalculateThumbAreaDistance(const int thumb_size);

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CScrollBarHorisontal(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          CGCnvElement *main_obj,CGCnvElement *base_obj,
                                          const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);

public:
//--- Supported object properties (1) integer, (2) real and (3) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Return the (1) left and (2) right arrow button
   CArrowLeftButton *GetArrowButtonLeft(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0);    }
   CArrowRightButton*GetArrowButtonRight(void)  { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,0);   }

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

//--- Constructor
                     CScrollBarHorisontal(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                          const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);
//--- Timer
   virtual void      OnTimer(void);
//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
  };
//+------------------------------------------------------------------+


我们来研究所声明的方法。

该方法设置对象新尺寸:

//+-------------------------------------------+
//| Set the new size                          |
//+-------------------------------------------+
bool CScrollBarHorisontal::Resize(const int w,const int h,const bool redraw)
  {
//--- If failed to change the object size, return 'false'
   if(!CWinFormBase::Resize(w,h,redraw))
      return false;
//--- Get the button object with the right arrow
   CArrowRightButton *br=this.GetArrowButtonRight();
//--- If the button is not received, return 'false'
   if(br==NULL)
      return false;
//--- Move the button to the right edge of the scrollbar
   if(br.Move(this.RightEdge()-this.BorderSizeRight()-br.Width(),br.CoordY()))
     {
      //--- Set new relative coordinates for the button
      br.SetCoordXRelative(br.CoordX()-this.CoordX());
      br.SetCoordYRelative(br.CoordY()-this.CoordY());
     }
//--- Set the slider parameters
   this.SetThumbParams();
//--- Successful
   return true;
  }
//+------------------------------------------------------------------+

首先,调用父类方法设置滚动条的新大小。 接下来,成功调整大小后,我们需要移动位于对象右侧的按钮,令其位于调整大小后的对象边缘(随着调整大小,其右边缘的坐标也会发生变化)。 完成所有变更和移动后,调用 SetThumbParams() 方法重新计算滑块的大小和位置,我将在下面研究该方法。


该方法计算和设置捕获区域(滑块)参数:

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

该方法的每一行都进行了详细注释。 该方法的本质是根据超出容器边界的内容大小来计算滑块的大小。 内容超出容器(以像素为单位)越多,滑块就越小。 滑块位置的坐标是从容器的左边缘计算的,也是相对大小,如此即可对应于容器的可见内容部分相对于其不可见部分,即延伸到左边缘之外的部分。 容器里的内容超出左边缘越多(以像素为单位),滑块将向右放置得越远。 结果就是,它证明滚动条和滑块是容器及其内容的简化副本(或实例)。 在滚动条中,条本身示意容器的全部内容,滑块示意容器 — 其中内容可见的部分。


该方法计算捕获区域(滑块)距离:

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

滑块的大小将传递给方法。 接下来,我们计算滑块尺寸小小于容器工作区(其中内容可见)的程度。 然后以此比率计算滑块距离,并返回该距离。 换言之,尽管滑块小于容器,但在某种程度上,滑块与其坐标原点的距离小于容器内容超出其左边缘的像素数。


该方法返回滑块工作区大小:

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

滑块的工作区域是它在滚动条中移动的区域。 换句话说,这是两个滚动条箭头按钮之间的区域。 因此,若要计算该距离,我们需要获取指向这些按钮的指针,并计算右按钮的左边缘和左按钮的右边缘之间的距离。


该方法返回滑块工作区起始坐标:

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

该方法返回左滚动条按钮右边缘的坐标。 此坐标是滑块位置的初始坐标,我们在计算其尺寸和坐标时正是依据该坐标计算其位置的。


鼠标事件处理程序现在也经过重新设计,如下所示:

//+-------------------------------------------+
//| Event handler                             |
//+-------------------------------------------+
void CScrollBarHorisontal::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- Get the pointers to control objects of the scrollbar
   CArrowLeftButton  *buttl=this.GetArrowButtonLeft();
   CArrowRightButton *buttr=this.GetArrowButtonRight();
   CScrollBarThumb   *thumb=this.GetThumb();
   if(buttl==NULL || buttr==NULL || thumb==NULL)
      return;
//--- If the event ID is an object movement
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Move the scrollbar to the foreground
      this.BringToTop();
      //--- Declare the variables for the coordinates of the capture area
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Set the Y coordinate equal to the Y coordinate of the control element
      y=this.CoordY()+this.BorderSizeTop();
      //--- Adjust the X coordinate so that the capture area does not go beyond the control, taking into account the arrow buttons
      if(x<buttl.RightEdge())
        x=buttl.RightEdge();
      if(x>buttr.CoordX()-thumb.Width())
        x=buttr.CoordX()-thumb.Width();
      //--- If the capture area object is shifted by the calculated coordinates
      if(thumb.Move(x,y,true))
        {
         //--- set the object relative coordinates
         thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
         thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
         ::ChartRedraw(this.ChartID());
        }
      //--- Get the pointer to the base object
      CWinFormBase *base=this.GetBase();
      if(base!=NULL)
        {
         //--- Check if the content goes beyond the container and recalculate the slider parameters
         base.CheckForOversize();
         this.SetThumbParams();
        }
     }
//--- If any scroll button is clicked
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
     {
      //--- Move the scrollbar to the foreground
      this.BringToTop();
      //--- Get the base object
      CWinFormBase *base=this.GetBase();
      if(base==NULL)
         return;
      //--- Calculate how much each side of the content of the base object goes beyond its borders
      base.CheckForOversize();
      //--- Get the largest and smallest coordinates of the right and left sides of the base object content
      int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT);
      int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X);
      //--- Set the number of pixels, by which the content of the base object should be shifted
      int shift=DEF_CONTROL_SCROLL_BAR_SCROLL_STEP;
      //--- If the left button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT)
        {
         if(cntt_l+shift<=base.CoordXWorkspace())
            base.ShiftDependentObj(shift,0);
        }
      //--- If the right button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT)
        {
         if(cntt_r-shift>=base.RightEdgeWorkspace())
            base.ShiftDependentObj(-shift,0);
        }
      //--- Calculate the width and coordinates of the slider
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+

在此,我们添加点击滚动条按钮的处理单击按钮时,计算偏移坐标,并将容器的整个内容沿与按下的箭头按钮相反的方向移动移位后,重新调整滑块的大小和坐标


在完成水平滚动条对象的类时,同时对垂直滚动条对象的类进行了类似的修改和改进。 但并非所有更改都已完成,且并非每个更改都经过测试,因为我将首先实现水平滚动条的功能,然后将其移植到垂直条。 故此,我现在于此不会考虑针对垂直滚动条类的代码所做的更改。 我将在水平滚动条代码的操作和调试彻底完工后,再考虑重建它。


我们回到 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh 中的容器对象类。

在受保护的部分中,声明将滚动条移动到前景的方法。 在公开部分中,实现返回指向两个滚动条的指针的方法声明移动对象的虚拟方法,而在类的主体外部实现所声明的虚拟重绘方法

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

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

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

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

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

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

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


在设置自动调整元素大小模式以便适配内容的方法中,添加检查元素根据内容自动调整大小的标志是否未设置

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

设置大小自动更改模式时,如果设置的模式与当前模式不匹配则会调整容器大小以便适配其内容。 与此同时,如果未启用自动调整大小标志,则无需整理任何内容,这就是所添加代码的作用。 若自动调整大小标志清除时,它从方法直接返回。


创建容器对象后,如果对象未设置自动调整容器大小的标志,且容器里的内容超出其边界则应显示相应的滚动条

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

此处,我们首先检查附着到容器的对象,看看它们是否超出边框,如果它们确实超出左边框或右边框,则显示水平滚动条。 但我们要首先计算并设置滚动条滑块的参数,然后再显示滚动条对象本身。

由于现在滚动条对象会自己处理大小调整,故当前对象调整大小的方法已被修改:

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

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

由于现在无需处理滚动条的大小调整即可将按钮移动到新位置,因此该方法已变得更短、更清晰。 调整大小后,每个滚动条都会转到前景


该方法重绘对象:

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

首先,调用父类的方法重绘对象,然后调用该方法将滚动条置于前景


该方法将滚动条置于前景:

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

获取指向水平和垂直滚动条对象的指针,并在成功接收指针后,将每个对象切换至前景。


该方法更新坐标:

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

首先,调用父类方法来移动对象。 接下来,在成功移动后,将滚动条切换至前景


现在我们需要在图形元素的集合类中包含需处理的滚动条对象的事件。

在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 文件中,即在其事件处理程序中,添加以下代码模块来调用滚动条对象鼠标点击事件的处理程序

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

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

一切都准备好,可以进行测试了。


测试

为了执行测试,我将延用上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part131\ 中,命名为 TestDoEasy131.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,-40,10,pnl.WidthWorkspace()+80,pnl.HeightWorkspace()-30,clrNONE,255,true,false);
         CButton *btn=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,0);
         btn.SetText("123456789012345678901234567890");
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,40,20,60,20,clrDodgerBlue,255,false,false);
         CLabel *lbl=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LABEL,0);
         lbl.SetText("LABEL");
         /*
         //--- Create TabControl
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
         CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
         if(tc!=NULL)
           {


编译 EA,并在图表上启动它:


如您所见,由箭头按钮管理的滚动效果良好。 当尝试用鼠标移动滑块时,它会“抗拒”,这是很自然的 — 我们仍未处理滑块移位,但我们已经重新计算了它的尺寸和坐标。 故此,当我们尝试用鼠标移动滑块时,设置其坐标的方法会返回到与其可见区域中容器内容的位置相对应的位置。 此行为将在后续文章中终结。



下一步是什么?

在下一篇文章中,我将继续开发滚动条控件。

返回内容目录

*该系列的前几篇文章:

 
DoEasy. 控件 (第 26 部分): 完成 ToolTip(工具提示)WinForms 对象,并转移至 ProgressBar(进度条)开发
DoEasy. 控件 (第 27 部分): 继续致力 ProgressBar(进度条)WinForms 对象
DoEasy. 控件 (第 28 部分): 进度条控件中的柱线样式
DoEasy. 控件 (第 29 部分): 滚动条(ScrollBar)辅助控件
DoEasy. 控件 (第 30 部分): 动态滚动条控件

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

附加的文件 |
MQL5.zip (4551.42 KB)
构建自动运行的 EA(第 09 部分):自动化(I) 构建自动运行的 EA(第 09 部分):自动化(I)
尽管创建自动 EA 并非一项非常困难的任务,但在缺乏必要知识的情况下可能会犯许多错误。 在本文中,我们将研究如何构建初级自动化,其中包括创建一个触发器来激活盈亏平衡和尾随停止价位。
种群优化算法:萤火虫算法(FA) 种群优化算法:萤火虫算法(FA)
在本文中,我将研究萤火虫算法(FA)优化方法。 致谢优化修订,该算法已从局外人变成了评级表上的真正领先者。
构建自动运行的 EA(第 09 部分):自动化(II) 构建自动运行的 EA(第 09 部分):自动化(II)
如果您无法控制其调度表,则自动化就意味着毫无意义。 没有工人能够一天 24 小时高效工作。 然而,许多人认为自动化系统理所当然地每天 24 小时运行。 但为 EA 设置工作时间范围总是有好处的。 在本文中,我们将研究如何正确设置这样的时间范围。
数据科学与机器学习(第 10 部分):岭回归 数据科学与机器学习(第 10 部分):岭回归
岭回归是一种简单的技术,可降低模型复杂度,并防止简单线性回归可能导致的过度拟合。