
DoEasy.控件(第 33 部分):垂直滚动条
目录
概念
在上一篇专门介绍库图形元素的文章中,我们创建了一个水平滚动条,如果窗体上的对象在左侧、右侧或两侧边缘超出了父窗体的边界,该滚动条就会出现在对象上。在此,我们将基于水平滚动条对象创建一个垂直滚动条。如果所连接的对象在顶部、底部或这两个部分超出了表格的边界,表格上就会出现该对象。
这篇文章相对较短,更像是一个概述,因为复制水平滚动条对象并将其转换为垂直滚动条对象是一件非常容易的事情。以后我们在开发 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



下午好,我正在试用这个库。是否可以创建编辑字段。
您好。当然可以。但这种控件还没有达到这种程度。很多东西都将逐步实现。但不会立即实现,也不会很快实现--目前的重点不是图书馆。