DoEasy.控件(第 33 部分):垂直滚动条·进阶篇
📘

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

第 2/3 篇

控件对象的相对坐标与图形基件封装

在自定义图形控件里,高光(glare)对象的位置不能写死绝对坐标,否则父容器一移动它就错位。正确做法是在初始化块里用父对象坐标做差,把高光改成相对定位:X 相对值 = 高光自身 X 减父对象 X,Y 同理。 可见区域也要跟着父对象走。调用 SetVisibleArea(0,0,glare.Width(),glare.Height(),false) 时,前两个 0 代表从父对象左上角起算,宽高直接吃高光自己的尺寸,最后一个 false 表示不裁剪子对象——这样高光范围恰好罩住整个控件。 对外暴露的接口层,GetObject() 直接返回 this 指针方便外部持有;SetTypeNode(int) 只存节点类型不做事。真正画图交给 CreateForm 的三个重载和 CreateTrendLine:趋势线函数默认线宽 1、实线样式,调用时若不给 width 和 style 参数就走这套默认值。 开 MT5 新建 EA 把这段塞进你的 CGraphElmControl 派生类,改两行坐标差看看高光是否跟手;外汇和贵金属图表上做控件高风险,参数没调稳前别挂实盘。

MQL5 / C++
    {
      class=class="str">"cmt">//--- Set the relative coordinates of the glare object
      glare.SetCoordXRelative(glare.CoordX()-this.CoordX());
      glare.SetCoordYRelative(glare.CoordY()-this.CoordY());
      class=class="str">"cmt">//--- and its visibility scope equal to the entire object
      glare.SetVisibleArea(class="num">0,class="num">0,glare.Width(),glare.Height(),false);
     }
   }
  class="kw">return;
  }
class=class="str">"cmt">//--- ...
class=class="str">"cmt">//--- ...
class="kw">public:
class=class="str">"cmt">//--- Return itself
   CGraphElmControl *GetObject(class="type">void)                { class="kw">return &this;       }
class=class="str">"cmt">//--- Set a type of the object the graphics is constructed for
   class="type">void              SetTypeNode(const class="type">int type_node) { this.m_type_node=type_node; }

class=class="str">"cmt">//--- Create a form object
   CForm             *CreateForm(const class="type">int form_id,const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h);
   CForm             *CreateForm(const class="type">int form_id,const class="type">int wnd,const class="type">class="kw">string name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h);
   CForm             *CreateForm(const class="type">int form_id,const class="type">class="kw">string name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h);
class=class="str">"cmt">//--- Creates the trend line standard graphical object
   class="type">bool              CreateTrendLine(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                                     const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                                     const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                                     class="type">class="kw">color clr,class="type">int width=class="num">1,ENUM_LINE_STYLE style=STYLE_SOLID);
   class="type">bool              CreateTrendLine(const class="type">class="kw">string name,const class="type">int subwindow,

「把趋势线和箭头塞进自己的图形类」

在 MT5 里做价格行为标记,如果每次都调 ObjectCreate,代码会散得到处都是。把趋势线和箭头封装成一个控制类的成员函数,回测和盯盘脚本都能直接复用。 下面这组声明给出了三种重载思路:带 chart_id 的版本用于指定图表和子窗口,不带 chart_id 的版本默认当前图表,只传 name、time、price 的版本则连子窗口都走默认。外汇与贵金属波动剧烈,用这类封装快速重画结构线,能降低手动拖线的误差,但信号失效风险始终存在。 CreateTrendLine 固定收 time1/price1 与 time2/price2 两组坐标,线宽默认 1、线型默认 STYLE_SOLID;CreateArrow 则多一个 uchar arrow_code 参数决定箭头样式,同样 width 默认 1。构造函数里把 m_type 设为 OBJECT_DE_TYPE_GELEMENT_CONTROL,等于给这类对象打了个类型标签,后面做批量清理或属性修改时可以直接筛。

MQL5 / C++
const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
class="type">class="kw">color clr,class="type">int width=class="num">1,ENUM_LINE_STYLE style=STYLE_SOLID);
 class="type">bool        CreateTrendLine(const class="type">class="kw">string name,
                            const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                            const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                            class="type">class="kw">color clr,class="type">int width=class="num">1,ENUM_LINE_STYLE style=STYLE_SOLID);
class=class="str">"cmt">//--- Create the arrow standard graphical object
 class="type">bool        CreateArrow(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                         class="type">class="kw">color clr,class="type">uchar arrow_code,class="type">int width=class="num">1);
 class="type">bool        CreateArrow(const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                         class="type">class="kw">color clr,class="type">uchar arrow_code,class="type">int width=class="num">1);
 class="type">bool        CreateArrow(const class="type">class="kw">string name,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                         class="type">class="kw">color clr,class="type">uchar arrow_code,class="type">int width=class="num">1);
class=class="str">"cmt">//--- Constructors
                     CGraphElmControl(){ this.m_type=OBJECT_DE_TYPE_GELEMENT_CONTROL; }

◍ 图形对象管理类的隐藏与不可选设定

在 MT5 里批量绘制趋势线、通道等图形时,如果不把对象设为隐藏且不可选,鼠标误触会拖歪整组分析线,回测可视化也容易被干扰。下面这段 CGraphElmControl 类的私有方法,专门给标准图形对象统一设置基础属性。 SetCommonParamsStdGraphObj 接收图表 ID 和对象名两个参数,内部连续调用四次 ObjectSetInteger:先把 OBJPROP_HIDDEN 置 true 让对象不在对象列表里显示;再把 OBJPROP_SELECTED 设 false、OBJPROP_SELECTABLE 设 false,彻底禁止鼠标点选;最后 OBJPROP_TIMEFRAMES 配 OBJ_ALL_PERIODS,使图形在所有周期都可见。 注意 CreateTrendLine 在构造 OBJ_TREND 前会先调 CreateNewStdGraphObject,失败则用 Print 输出错误描述。外汇与贵金属图表波动剧烈,这类自动画线工具仅作概率辅助,实盘仍需人工复核,杠杆品种高风险。

MQL5 / C++
class CGraphElmControl : class="kw">public CObject
  {
class="kw">private:
   class="type">int               m_type;                     class=class="str">"cmt">// Object type
   class="type">int               m_type_node;                class=class="str">"cmt">// Type of the object the graphics is constructed for
class=class="str">"cmt">//--- Set general parameters for standard graphical objects
   class="type">void              SetCommonParamsStdGraphObj(const class="type">long chart_id,const class="type">class="kw">string name);
class="kw">public:
class=class="str">"cmt">//--- Return itself
   CGraphElmControl *GetObject(class="type">void)              { class="kw">return &this;       }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Set general parameters for standard graphical objects             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGraphElmControl::SetCommonParamsStdGraphObj(const class="type">long chart_id,const class="type">class="kw">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);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create the trend line standard graphical object                 |
class=class="str">"cmt">//| on a specified chart in a specified subwindow                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGraphElmControl::CreateTrendLine(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                                       const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                                       const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                                       class="type">class="kw">color clr,class="type">int width=class="num">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));

趋势线与箭头对象的封装创建

在 MT5 自定义指标或 EA 里,反复调用 ObjectCreate 写趋势线很啰嗦。把创建逻辑收进 CGraphElmControl 类的方法,主图与子图各行其是,调用端只传坐标和样式即可。 CreateTrendLine 提供了两个重载:一个显式接收 subwindow 参数,可把线画到指定子窗口;另一个默认把 subwindow 填 0,直接落主图。两者最终都转发给带 chart_id 的内部实现,后者统一设置颜色、线宽与线型。 底层那段设置值得逐行看:SetCommonParamsStdGraphObj 先写公共属性,随后 ObjectSetInteger 三次分别绑定 OBJPROP_COLOR、OBJPROP_WIDTH、OBJPROP_STYLE,默认 width=1、style=STYLE_SOLID。若前面校验不通过会 return false,正常则 return true,调用方据此判断对象是否真的建出来了。 外汇与贵金属图表上加这类对象属于高风险环境下的辅助标记,线段本身不代表任何方向判定,仅帮你把价格行为区肉眼锁定。

MQL5 / C++
  class="kw">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);
  class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create the trend line standard graphical object                  |
class=class="str">"cmt">//| on the current chart in a specified subwindow                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGraphElmControl::CreateTrendLine(const class="type">class="kw">string name,const class="type">int subwindow,
                                     const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                                     const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                                     class="type">class="kw">color clr,class="type">int width=class="num">1,ENUM_LINE_STYLE style=STYLE_SOLID)
  {
   class="kw">return this.CreateTrendLine(::ChartID(),name,subwindow,time1,price1,time2,price2,clr,width,style);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create the trend line standard graphical object                  |
class=class="str">"cmt">//| on the current chart in the main window                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGraphElmControl::CreateTrendLine(const class="type">class="kw">string name,
                                     const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                                     const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                                     class="type">class="kw">color clr,class="type">int width=class="num">1,ENUM_LINE_STYLE style=STYLE_SOLID)
  {
   class="kw">return this.CreateTrendLine(::ChartID(),name,class="num">0,time1,price1,time2,price2,clr,width,style);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create the arrow standard graphical object                       |
class=class="str">"cmt">//| on a specified chart in a specified subwindow                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGraphElmControl::CreateArrow(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,

「箭头对象的三种重载入口」

在 MT5 自定义图形库里,箭头对象(OBJ_ARROW)的创建被拆成了三个不同签名的成员函数,核心都收敛到带 chart_id 参数的那个重载。 带全参数的版本先调用 CreateNewStdGraphObject 尝试在主图或子窗口建对象,失败就通过 Print 输出标准错误描述并返回 false;成功后才用 ObjectSetInteger 依次写颜色、线宽、箭头编码。width 默认值是 1,arrow_code 是 uchar 类型,直接决定箭头外观。 另外两个重载省略了 chart_id,内部用 ChartID() 取当前图表句柄:一个允许指定 subwindow 子窗口号,另一个硬编码为 0 直接丢进主窗口。这样写的好处是脚本层调用时不用每次传图表 ID,但调试时得清楚真正干活的是第一个函数。 想验证的话,在 EA 里 include 这个类,用 CreateArrow("myarr",Time[0],Low[0],clrRed,159) 就能在主图最低价画一个红色箭头,159 对应 Wingdings 里的下指三角。

MQL5 / C++
class="type">bool CGraphElmControl::CreateArrow(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                              class="type">class="kw">color clr,class="type">uchar arrow_code,class="type">int width=class="num">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));
      class="kw">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);
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create the arrow standard graphical object                          |
class=class="str">"cmt">//| on the current chart in a specified subwindow                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGraphElmControl::CreateArrow(const class="type">class="kw">string name,const class="type">int subwindow,
                              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                              class="type">class="kw">color clr,class="type">uchar arrow_code,class="type">int width=class="num">1)
  {
   class="kw">return this.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create the arrow standard graphical object                          |
class=class="str">"cmt">//| on the current chart in the main window                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGraphElmControl::CreateArrow(const class="type">class="kw">string name,
                              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                              class="type">class="kw">color clr,class="type">uchar arrow_code,class="type">int width=class="num">1)
  {
   class="kw">return this.CreateArrow(::ChartID(),name,class="num">0,time1,price1,clr,arrow_code,width);
  }

◍ 封装图形对象的几个重载入口

在自定义指标或 EA 里批量画图时,直接调底层 CGraphElements 容易把 chart_id、subwindow 这些参数写散。上面这组 CreateForm 用了三个重载:带 chart_id 的版本可指定任意图表,只带 wnd 的版本锁定当前图表子窗口,最简版本默认丢进主窗口。 实际写面板类时,推荐统一走带 chart_id 的重载,避免切换品种后对象跑到错误图表。MT5 里图表 ID 用 ChartID() 取,子窗口序号从 0(主图)开始递增,IndicatorWindowTotal() 能拿到当前指标挂了几个子窗口。 趋势线封装更直白:CreateTrendLine 把时间-价格两点、颜色、线宽、线型一次传进去,默认 width=1、STYLE_SOLID。若想画虚线通道,把 style 改成 STYLE_DASH 即可, price1/price2 用 iLow/iHigh 取到的极值,回测里肉眼校一根趋势线平均省 3 行对象句柄代码。 外汇和贵金属波动大,图形对象只是辅助标记,任何画线信号都只是概率参考,下单前仍按自己的风控过一遍。

MQL5 / C++
CForm              *CreateForm(const class="type">int form_id,const class="type">long chart_id,const class="type">int wnd,const class="type">class="kw">string name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h)
                 { class="kw">return this.m_graph_elm.CreateForm(form_id,chart_id,wnd,name,x,y,w,h); }
class=class="str">"cmt">//--- Create a form object on the current chart in a specified subwindow
   CForm              *CreateForm(const class="type">int form_id,const class="type">int wnd,const class="type">class="kw">string name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h)
                 { class="kw">return this.m_graph_elm.CreateForm(form_id,wnd,name,x,y,w,h); }
class=class="str">"cmt">//--- Create the form object on the current chart in the main window
   CForm              *CreateForm(const class="type">int form_id,const class="type">class="kw">string name,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h)
                 { class="kw">return this.m_graph_elm.CreateForm(form_id,name,x,y,w,h); }

class=class="str">"cmt">//--- Create a standard graphical trend line object in the specified subwindow of the specified chart
   class="type">bool               CreateTrendLine(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                                     const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,
                                     const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                                     class="type">class="kw">color clr,class="type">int width=class="num">1,ENUM_LINE_STYLE style=STYLE_SOLID)
                 { class="kw">return this.m_graph_elm.CreateTrendLine(chart_id,name,subwindow,time1,price1,time2,price2,clr,width,style); }
class=class="str">"cmt">//--- Create a standard graphical trend line object in the specified subwindow of the current chart
   class="type">bool               CreateTrendLine(const class="type">class="kw">string name,const class="type">int subwindow,

常见问题

建一个图形对象管理类,把趋势线、箭头都当作基件封装进同一个类里,用隐藏和不可选标记控制交互,避免散落在图表上难维护。
在图形对象管理类里给对象设 OBJPROP_SELECTABLE=false 并配合隐藏设定,就能让趋势线不可选且不在普通点选时冒出来。
小布可读取当前品种页面的对象属性,自动标出未设不可选或仍可见的封装图形,提醒你补隐藏与锁定设定。
三种重载分别吃坐标、吃基准对象、吃预设结构,写 EA 时按你手头数据是裸价格还是已有对象来挑对应入口,少传参更稳。
基件坐标必须相对控件客户区左上角实时换算,别缓存绝对坐标;滚动时重算相对偏移,才能保证线 and 箭头跟着视口走。