DoEasy.控件(第 33 部分):垂直滚动条·综合运用
(3/3)·水平滚动条改垂直只差几行,但对象闪烁的坑拖了整篇发布,这篇把根因和修法说清
把水平滚动条复制成垂直版看似十分钟的事,多数人直接改坐标就上线。真正卡住进度的是被裁剪对象在父窗体边缘反复闪——根因是对象在修剪前就被过早重绘,这种闪不只难看,还会让你误判控件是否真的挂上了。
「封装趋势线与箭头对象的轻量写法」
在 MT5 自定义指标或 EA 里反复调 ObjectCreate 会很啰嗦,把图形元素收进一个类方法能省掉大量样板代码。下面这组方法把趋势线和箭头对象的创建包了一层,调用时只传关键参数即可。 趋势线方法有两个重载:一个指定 chart_id 和 subwindow,可往任意图表子窗口画线;另一个默认用 ::ChartID() 和子窗口 0,直接画在当前图表主图。两者都接收 time1/price1 到 time2/price2 的坐标对,以及 clr 颜色、width 线宽(默认 1)、style 线型(默认 STYLE_SOLID)。 箭头方法同理,区别是多了 arrow_code(uchar 类型箭头符号编码)且 width 默认 1,没有线型参数。指定 chart_id 的版本能跨图表打标记,只传 name+subwindow 的版本固定在当前图表对应子窗口落箭头。 复制下面代码到你的图形工具类,编译后可在 OnCalculate 里用 CreateTrendLine("tl1", time[1], low[1], time[0], high[0], clrRed) 快速验证主图画线。外汇与贵金属波动剧烈,这类图形标记仅辅助判别结构,实际进出场仍需结合风控。
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(::ChartID(),name,subwindow,time1,price1,time2,price2,clr,width,style);} class=class="str">"cmt">//--- Create a standard graphical trend line object in the main window of the current chart 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="kw">return this.m_graph_elm.CreateTrendLine(::ChartID(),name,class="num">0,time1,price1,time2,price2,clr,width,style); } class=class="str">"cmt">//--- Create a standard arrow graphical object in the specified subwindow of the specified chart 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="kw">return this.m_graph_elm.CreateArrow(chart_id,name,subwindow,time1,price1,clr,arrow_code,width); } class=class="str">"cmt">//--- Create a standard arrow graphical object in the specified subwindow of the current chart 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,
◍ 在图表上落箭头与滚轮驱动滚动条
给自定义图形库加箭头对象时,两套 CreateArrow 重载最实用:一个指定子窗口序号,一个固定子窗口为 0 也就是主图。两者都靠 m_graph_elm.CreateArrow 把 ChartID()、名称、时间、价格、颜色、箭头码和线宽透传下去,width 默认 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="kw">return this.m_graph_elm.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width); } 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="kw">return this.m_graph_elm.CreateArrow(::ChartID(),name,class="num">0,time1,price1,clr,arrow_code,width); }
class="type">void CScrollBarThumb::MouseActiveAreaWhellHandler(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam) { CWinFormBase *base=this.GetBase(); if(base==NULL) class="kw">return; base.BringToTop(); ENUM_WF_CONTROL_EVENT evn=WF_CONTROL_EVENT_NO_EVENT; class="kw">switch(base.TypeGraphElement()) { case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL: evn=(dparam>class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : dparam<class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : WF_CONTROL_EVENT_NO_EVENT); class="kw">break; case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL : evn=(dparam>class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : dparam<class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); class="kw">break; class="kw">default : class="kw">break; } base.OnChartEvent(evn,lparam,dparam,sparam); ::ChartRedraw(base.ChartID()); }
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="kw">return this.m_graph_elm.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width); } 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="kw">return this.m_graph_elm.CreateArrow(::ChartID(),name,class="num">0,time1,price1,clr,arrow_code,width); } class="type">void CScrollBarThumb::MouseActiveAreaWhellHandler(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam) { CWinFormBase *base=this.GetBase(); if(base==NULL) class="kw">return; base.BringToTop(); ENUM_WF_CONTROL_EVENT evn=WF_CONTROL_EVENT_NO_EVENT; class="kw">switch(base.TypeGraphElement()) { case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL: evn=(dparam>class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : dparam<class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : WF_CONTROL_EVENT_NO_EVENT); class="kw">break; case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL : evn=(dparam>class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : dparam<class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); class="kw">break; class="kw">default : class="kw">break; } base.OnChartEvent(evn,lparam,dparam,sparam); ::ChartRedraw(base.ChartID()); }
垂直滚动条的私有构造与箭头按钮拆分
在 MT5 自定义图形控件里,垂直滚动条不是直接从 CScrollBar 裸用,而是派生出 CScrollBarVertical 类,把上下箭头和滑块区域的计算收进私有层。这样主程序只管挂控件,不必关心箭头按钮怎么建。 类里两个 private 方法很关键:CreateArrowButtons 按传入的 width、height 生成上下箭头对象;CalculateThumbAreaDistance 则根据滑块尺寸反推捕获区可移动距离,决定拖拽时映射比例。 构造器被放在 protected,意味着你不能直接 new 一个 CScrollBarVertical,必须走工厂或子类化。参数表从 ENUM_GRAPH_ELEMENT_TYPE 到 x/y/w/h 共 11 个,覆盖了画布归属(chart_id、subwindow)和像素坐标,少传一个编译器就报错。 想验证这套结构,把下面代码存成 .mqh 挂到 EA 里,故意漏掉一个 include 看链接错误,比读文档更快懂依赖关系。外汇与贵金属图表加载这类自绘控件时滑点放大,属高风险操作,参数乱改可能卡死子窗口。
class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property strict class=class="str">"cmt">// Necessary for mql4 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "ScrollBarThumb.mqh" class="macro">#include "ArrowDownButton.mqh" class="macro">#include "ArrowUpButton.mqh" class="macro">#include "ScrollBar.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CScrollBarVertical object class of WForms controls | class=class="str">"cmt">//+------------------------------------------------------------------+ class CScrollBarVertical : class="kw">public CScrollBar { class="kw">private: class=class="str">"cmt">//--- Create the ArrowButton objects class="kw">virtual class="type">void CreateArrowButtons(const class="type">int width,const class="type">int height); class=class="str">"cmt">//--- Calculate the distance of the capture area(slider) class="type">int CalculateThumbAreaDistance(const class="type">int thumb_size); class="kw">protected: class=class="str">"cmt">//--- Protected constructor with object type, chart ID and subwindow CScrollBarVertical(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string descript, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h);
「垂直滚动条类的事件与参数接口」
在自绘图形控件里,垂直滚动条需要单独处理鼠标滚轮位于激活区时的事件。下面这段虚函数声明就是光标进入激活区且滚动滚轮时的入口,参数沿用 MT5 标准事件结构(id、lparam、dparam、sparam),继承后可在子类写具体缩放或平移逻辑。 //--- '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); 控件属性方面,整数、实数、字符串三类画布属性全部放开。三个 SupportProperty 重载均直接返回 true,意味着该滚动条元素不限制任何 ENUM_CANV_ELEMENT_PROP 枚举属性的读写,调用方可以自由设置颜色、尺寸或标签。 //--- 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; } 取上下箭头按钮时靠类型枚举定位,索引写 0 表示取同类型中的第一个实例。GetArrowButtonUp 返回 GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP 类型元素,GetArrowButtonDown 对应 DOWN 类型,两者都返回裸指针,外部用完不需手动释放。 //--- 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); } 工作区尺寸与起点坐标由独立方法给出,BarWorkAreaSize 回传滑块可移动像素长度,BarWorkAreaCoord 回传起点像素坐标,这两个值决定了滑块 thumb 的绘制边界。 //--- 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); 尺寸变更用 Resize(w,h,redraw) 虚函数,redraw 为 true 时会立即重绘。SetThumbParams 负责根据当前内容长度重算滑块比例与位置,返回 int 通常是实际设置的像素量或错误码。 //--- 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); 构造函数接收主对象与基底对象指针、图表 ID、子窗口号、描述文本及 x/y/w 初始几何。在 MT5 里图表 ID 用 0 代表当前图表,子窗口 -1 为独立窗格,实盘加载这类控件需注意外汇与贵金属杠杆交易的高风险,参数误设可能导致界面卡死。 //--- 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,
class="kw">virtual class="type">void MouseActiveAreaWhellHandler(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam); class="kw">public: class=class="str">"cmt">//--- Supported object properties(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string ones class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Return the button with the(class="num">1) up, (class="num">2) down arrow CArrowUpButton *GetArrowButtonUp(class="type">void) { class="kw">return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,class="num">0); } CArrowDownButton *GetArrowButtonDown(class="type">void) { class="kw">return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,class="num">0); } class=class="str">"cmt">//--- Return the size of the slider working area class="type">int BarWorkAreaSize(class="type">void); class=class="str">"cmt">//--- Return the coordinate of the beginning of the slider working area class="type">int BarWorkAreaCoord(class="type">void); class=class="str">"cmt">//--- Set the new size class="kw">virtual class="type">bool Resize(const class="type">int w,const class="type">int h,const class="type">bool redraw); class=class="str">"cmt">//--- Calculate and set the parameters of the capture area(slider) class="type">int SetThumbParams(class="type">void); class=class="str">"cmt">//--- Constructor CScrollBarVertical(CGCnvElement *main_obj,CGCnvElement *base_obj, const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string descript, const class="type">int x, const class="type">int y, const class="type">int w,
◍ 垂直滚动条的受保护构造与类型绑定
在自定义图形库里,CScrollBarVertical 通过受保护构造函数把元素类型、主/基对象、图表 ID 与子窗口号一次性传给基类 CScrollBar。 构造函数尾部调用 this.SetTypeElement(type) 和 this.CreateThumbArea(),前者把图形元素类型写进对象自身,后者负责生成滑块可拖动区域;这两步漏掉任何一个,后续在 MT5 图表上挂载垂直滚动条都会直接失效。 从参数表看,x/y/w/h 全部以 int 传入,意味着像素级定位;若你在 4K 屏或多 DPI 环境下跑 EA,需要自己在外面做缩放换算,库本身不处理。 外汇与贵金属图表挂这类自定义控件时波动刷新频繁,属于高风险界面操作,建议先在模拟账户验证渲染稳定性。
const class="type">int h); class=class="str">"cmt">//--- Timer class="kw">virtual class="type">void OnTimer(class="type">void); class=class="str">"cmt">//--- Event handler class="kw">virtual class="type">void OnChartEvent(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Protected constructor with an object type, | class=class="str">"cmt">//| chart ID and subwindow | class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollBarVertical::CScrollBarVertical(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string descript, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h) : CScrollBar(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { class=class="str">"cmt">//--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.CreateThumbArea(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor indicating the main and base objects, | class=class="str">"cmt">//| chart ID and subwindow | class=class="str">"cmt">//+------------------------------------------------------------------+ CScrollBarVertical::CScrollBarVertical(CGCnvElement *main_obj,CGCnvElement *base_obj, const class="type">long chart_id, const class="type">int subwindow,
垂直滚动条的按钮与滑块构造
在 MT5 自定义图形控件里,垂直滚动条 CScrollBarVertical 的构造函数直接把元素类型锁死为 GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL,并随即调用 CreateThumbArea() 生成滑块轨道。这意味着你无法在实例化后再把它改成水平条,类型在构造期就固化了。 CreateArrowButtons() 里先按滚动条厚度减去左右边框算出按钮边长 size,再依次生成上箭头、下箭头和中间滑块三个子元素。注意滑块高度被硬编码为 30,且纵向初始位置用 Height()/2 - height 定位,若你外部传入的 height 与 30 不一致,滑块会出现偏移。 箭头尺寸统一设为 2,随后分别取上下按钮指针并设置六种状态色(正常、按下、悬停的背景与前景)。这些颜色来自 CLR_DEF_CONTROL_SCROLL_BAR_* 系列宏,若要在贵金属面板里做暗色主题,直接改这些宏比逐个 Set 更高效。外汇与贵金属图表控件改动涉及实时重绘,存在显示异常高风险,建议在策略测试器里先验证。
const class="type">class="kw">string descript, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">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(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create the ArrowButton objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CScrollBarVertical::CreateArrowButtons(const class="type">int width,const class="type">int height) { class=class="str">"cmt">//--- Set the size of the buttons equal to the width of the scrollbar without the size of its frame class="type">int size=this.Thickness()-this.BorderSizeLeft()-this.BorderSizeRight(); class=class="str">"cmt">//--- Create the buttons with up and down arrows and the area capture object. The arrow size is set to class="num">2 this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP, class="num">0,class="num">0,size,size,this.BackgroundColor(),class="num">255,true,false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN, class="num">0,this.Height()-height,size,size,this.BackgroundColor(),class="num">255,true,false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB, class="num">0,this.Height()/class="num">2-height,size,class="num">30,CLR_DEF_CONTROL_SCROLL_BAR_THUMB_COLOR,class="num">255,true,false); this.SetArrowSize(class="num">2); class=class="str">"cmt">//--- 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); } class=class="str">"cmt">//--- 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);
「垂直滚动条的尺寸重算与滑块自适应」
在自定义 MT5 控件里,垂直滚动条 resize 不是简单拉伸外框。CScrollBarVertical::Resize 先调用基类 CWinFormBase::Resize 改对象尺寸,失败直接返回 false;随后取出向下箭头按钮,把它挪到滚动条底边(BottomEdge 减去下边框厚再减按钮高),并重置相对坐标,保证按钮始终贴底。 滑块(thumb)的尺寸靠 SetThumbParams 动态算。先取容器基对象的工作区高度 base_h,再加上下溢出量得到内容总高 objs_h;可见比例 px = base_h / objs_h,滑块长 = floor(工作条长 * px),且强制不小于 DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN。这意味着内容越多滑块越短,内容少则锁死最小长度,避免点不中。 开 MT5 把这段塞进你的面板类,改 DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN 看滑块最短态;外汇与贵金属图表挂这类自定义控件时需注意,高频重绘可能拖慢 tick 响应,属高风险定制。
bd.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_OVER); } class=class="str">"cmt">//--- 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); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the new size | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CScrollBarVertical::Resize(const class="type">int w,const class="type">int h,const class="type">bool redraw) { class=class="str">"cmt">//--- If failed to change the object size, class="kw">return &class="macro">#x27;false&class="macro">#x27; if(!CWinFormBase::Resize(w,h,redraw)) class="kw">return false; class=class="str">"cmt">//--- Get the button object with the down arrow CArrowDownButton *bd=this.GetArrowButtonDown(); class=class="str">"cmt">//--- If the button is not received, class="kw">return &class="macro">#x27;false&class="macro">#x27; if(bd==NULL) class="kw">return false; class=class="str">"cmt">//--- Move the button to the bottom edge of the scrollbar if(bd.Move(bd.CoordX(),this.BottomEdge()-this.BorderSizeBottom()-bd.Height())) { class=class="str">"cmt">//--- Set new relative coordinates for the button bd.SetCoordXRelative(bd.CoordX()-this.CoordX()); bd.SetCoordYRelative(bd.CoordY()-this.CoordY()); } class=class="str">"cmt">//--- Set the slider parameters this.SetThumbParams(); class=class="str">"cmt">//--- Successful class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate and set the parameters of the capture area(slider) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CScrollBarVertical::SetThumbParams(class="type">void) { class=class="str">"cmt">//--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) class="kw">return class="num">0; class=class="str">"cmt">//--- Get the capture area object(slider) CScrollBarThumb *thumb=this.GetThumb(); if(thumb==NULL) class="kw">return class="num">0; class=class="str">"cmt">//--- Get the height size of the visible part inside the container class="type">int base_h=base.HeightWorkspace(); class=class="str">"cmt">//--- Calculate the total height of all attached objects class="type">int objs_h=base_h+base.OversizeTop()+base.OversizeBottom(); class=class="str">"cmt">//--- Calculate the relative size of the visible part window class="type">class="kw">double px=(class="type">class="kw">double)base_h/class="type">class="kw">double(objs_h!=class="num">0 ? objs_h : class="num">1); class=class="str">"cmt">//--- Calculate and adjust the size of the slider relative to the height of its workspace(not less than the minimum size) class="type">int thumb_size=(class="type">int)::floor(this.BarWorkAreaSize()*px); if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN) thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
◍ 竖向滚动条滑块的尺寸与坐标推导
在自定义竖向滚动条里,滑块(thumb)不能超出轨道可用区。代码先判断 thumb_size 是否大于 BarWorkAreaSize(),若是则强制截断为轨道高度,避免滑块画到上下箭头按钮区域之外。 CalculateThumbAreaDistance() 用比例法算滑块偏移:x = thumb_size / base.HeightWorkspace(),再乘以上方溢出量 OversizeTop() 并向上取整。这意味着当内容区总高固定时,滑块每多 1 像素高度,其顶部留白距离会按占比同步缩放。 BarWorkAreaSize() 与 BarWorkAreaCoord() 都依赖上下箭头按钮的边界;若按钮为空则回退到滚动条自身坐标加边框。两者之差就是纯轨道像素数,是滑块 Resize 与 Move 的硬约束。 OnTimer() 目前为空实现,说明该滚动条未借定时器做惯性滚动;交互全压在 OnChartEvent() 的事件分发上,接事件参数后由后续逻辑处理拖拽。
if(thumb_size>this.BarWorkAreaSize()) thumb_size=this.BarWorkAreaSize(); class=class="str">"cmt">//--- Calculate the coordinate of the slider and change its size to match the previously calculated one class="type">int thumb_y=this.CalculateThumbAreaDistance(thumb_size); if(!thumb.Resize(thumb.Width(),thumb_size,true)) class="kw">return class="num">0; class=class="str">"cmt">//--- 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()); } class=class="str">"cmt">//--- Return the calculated slider size class="kw">return thumb_size; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate the distance of the capture area(slider) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CScrollBarVertical::CalculateThumbAreaDistance(const class="type">int thumb_size) { CWinFormBase *base=this.GetBase(); if(base==NULL) class="kw">return class="num">0; class="type">class="kw">double x=(class="type">class="kw">double)thumb_size/(class="type">class="kw">double)base.HeightWorkspace(); class="kw">return (class="type">int)::ceil((class="type">class="kw">double)base.OversizeTop()*x); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the size of the slider working area | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CScrollBarVertical::BarWorkAreaSize(class="type">void) { CArrowUpButton *bu=this.GetArrowButtonUp(); CArrowDownButton *bd=this.GetArrowButtonDown(); class="type">int y1=(bu!=NULL ? bu.BottomEdge() : this.CoordY()+this.BorderSizeTop()); class="type">int y2=(bd!=NULL ? bd.CoordY() : this.BottomEdge()-this.BorderSizeBottom()); class="kw">return(y2-y1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the coordinate of the beginning of the slider working area | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CScrollBarVertical::BarWorkAreaCoord(class="type">void) { CArrowUpButton *bu=this.GetArrowButtonUp(); class="kw">return(bu!=NULL ? bu.BottomEdge() : this.CoordY()+this.BorderSizeTop()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CScrollBarVertical::OnTimer(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CScrollBarVertical::OnChartEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam) {
滚动条拖拽时内容区的同步位移逻辑
在 MT5 自定义控件里,滚动条滑块被拖动后,必须先把捕获区坐标限制在上箭头按钮下沿与下箭头按钮上沿之间,否则滑块会跑出控件可视范围。代码里用 buttu.BottomEdge() 和 buttd.CoordY()-thumb.Height() 做双向夹取,这是 GUI 库里最容易漏掉的一步。
拖拽事件 WF_CONTROL_EVENT_MOVING 触发时,先算滑块距上箭头底部的像素距离 distance,再用 static int distance_last 存上一次的值。两者不等就说明滑块动了,shift_value=distance_last-distance 得到本次位移量,符号代表方向。
拿到位移量后,基类 CWinFormBase 的 CheckForOversize() 会重算内容是否超出容器,随后从依赖元素里取 CANV_ELEMENT_PROP_BOTTOM 最大坐标,用于把像素位移换算成内容滚动量。外汇与贵金属图表挂这类自定义面板时,注意高频拖拽可能引发重绘开销,属高风险环境下的性能敏感点。
class=class="str">"cmt">//--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); class=class="str">"cmt">//--- 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) class="kw">return; class=class="str">"cmt">//--- If the event ID is an object movement if(id==WF_CONTROL_EVENT_MOVING) { class=class="str">"cmt">//--- Move the scrollbar to the foreground this.BringToTop(); class=class="str">"cmt">//--- Declare the variables for the coordinates of the capture area class="type">int x=(class="type">int)lparam; class="type">int y=(class="type">int)dparam; class=class="str">"cmt">//--- Set the X coordinate equal to the X coordinate of the control element x=this.CoordX()+this.BorderSizeLeft(); class=class="str">"cmt">//--- 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(); class=class="str">"cmt">//--- If the capture area object is shifted by the calculated coordinates if(thumb.Move(x,y,true)) { class=class="str">"cmt">//--- set the object relative coordinates thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX()); thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY()); } class=class="str">"cmt">//--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base!=NULL) { class=class="str">"cmt">//--- Check if the content goes beyond the container base.CheckForOversize(); class=class="str">"cmt">//--- Calculate the distance the slider is from the upper border of the scrollbar(from the bottom side of the upper arrow button) class="type">int distance=thumb.CoordY()-buttu.BottomEdge(); class=class="str">"cmt">//--- Declare a variable that stores the distance value before the slider shift class="kw">static class="type">int distance_last=distance; class=class="str">"cmt">//--- Declare a variable that stores the value in screen pixels the slider was shifted by class="type">int shift_value=class="num">0; class=class="str">"cmt">//--- If the values of the past and current distances are not equal(the slider is shifted), if(distance!=distance_last) { class=class="str">"cmt">//--- calculate the value the slider is shifted by shift_value=distance_last-distance; class=class="str">"cmt">//--- and enter the new distance into the value of the previous distance for the next calculation distance_last=distance; } class=class="str">"cmt">//--- Get the largest and smallest coordinates of the lower and upper sides of the base object content class="type">int cntt_d=(class="type">int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_BOTTOM);
「滚动条拖动与按钮点击的内容偏移逻辑」
在自定义滚动条控件里,滑块拖动和上下按钮点击都依赖同一套「基准对象内容偏移」计算。先取基准对象依赖子元素的最小 Y 坐标 cntt_u,再用 CoordYWorkspace() 减它得到内容顶边相对工作区的偏移 extu,随后按滑块位移距离占滑块高度的比例乘工作区高度,四舍五入得出应移位量 shift_need。 向上拖滑块时(shift_value>0),仅当 cntt_u+shift_need 不超过工作区顶边才调用 ShiftDependentObj(0,shift_need);向下拖则判断 cntt_d-shift_need 是否仍在底边之上。每次变动末尾都跑 ChartRedraw 重绘当前图表 ID。 按钮事件走的是简化分支:先 BringToTop 把滚动条置前,CheckForOversize 算内容溢出像素,再按 sparam 是否为空选 DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK 或 DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL 作步长。上按钮时只要 cntt_u+shift 不越顶就平移,下按钮对称处理。 开 MT5 把这几段塞进你的 CWinScrollBar 派生类,改 DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL 的宏值就能直接体会滚轮步长对贵金属图表悬浮面板的手感差异,外汇标的高波动下建议先小步长验证越界判断。
class="type">int cntt_u=(class="type">int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_Y); class=class="str">"cmt">//--- Get the coordinate offset of the upper side of the base object content class=class="str">"cmt">//--- relative to the initial coordinate of the base object working area class="type">int extu=base.CoordYWorkspace()-cntt_u; class=class="str">"cmt">//--- Calculate the relative value of the desired coordinate, class=class="str">"cmt">//--- where the contents of the base object, shifted by the slider, should be located class="type">class="kw">double y=(class="type">class="kw">double)this.HeightWorkspace()*(class="type">class="kw">double)distance/class="type">class="kw">double(thumb.Height()!=class="num">0 ? thumb.Height() : DBL_MIN); class=class="str">"cmt">//--- Calculate the required shift value of the base object content along the above calculated coordinate class="type">int shift_need=extu-(class="type">int)::round(y); class=class="str">"cmt">//--- If the slider is shifted upwards(positive shift value) if(shift_value>class="num">0) { if(cntt_u+shift_need<=base.CoordYWorkspace()) base.ShiftDependentObj(class="num">0,shift_need); } class=class="str">"cmt">//--- If the slider is shifted downwards(negative shift value) if(shift_value<class="num">0) { if(cntt_d-shift_need>=base.BottomEdgeWorkspace()) base.ShiftDependentObj(class="num">0,shift_need); } ::ChartRedraw(this.ChartID()); } } class=class="str">"cmt">//--- If any scroll button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP || id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN) { class=class="str">"cmt">//--- Move the scrollbar to the foreground this.BringToTop(); class=class="str">"cmt">//--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) class="kw">return; class=class="str">"cmt">//--- Calculate how much each side of the content of the base object goes beyond its borders base.CheckForOversize(); class=class="str">"cmt">//--- Get the largest and smallest coordinates of the lower and upper sides of the base object content class="type">int cntt_d=(class="type">int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_BOTTOM); class="type">int cntt_u=(class="type">int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_Y); class=class="str">"cmt">//--- Set the number of pixels, by which the content of the base object should be shifted class="type">int shift=(sparam!="" ? DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK : DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL); class=class="str">"cmt">//--- If the up button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP) { if(cntt_u+shift<=base.CoordYWorkspace()) base.ShiftDependentObj(class="num">0,shift); } class=class="str">"cmt">//--- If the down button is clicked
◍ 滚轮事件与容器建元的底层判定
垂直滚动条在活动区内响应鼠标滚轮时,先按 dparam 正负映射成上滚或下滚事件:dparam>0 对应 WF_CONTROL_EVENT_CLICK_SCROLL_UP,dparam<0 对应 WF_CONTROL_EVENT_CLICK_SCROLL_DOWN,等于 0 则无事件。映射后直接转调 OnChartEvent 并强制 ChartRedraw 重绘当前图表,所以滚轮滚动在 MT5 上几乎是即时刷新的。 下滚分支里有个边界判断:只有当 cntt_d - shift 仍大于等于工作区下沿 BottomEdgeWorkspace() 时,才执行 ShiftDependentObj(0, -shift) 把依赖对象上移。这意味着滚到接近底部时,继续下滚不会越界位移,控件自己掐断了越界。 容器新建元素 CreateNewElement 的第一道闸是类型校验:element_type 必须小于 GRAPH_ELEMENT_TYPE_WF_BASE 才放行,否则写日志 MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE 并返回 false。之后交给 CForm::CreateAndAddNewElement 真正建对象,返回 NULL 就代表建元失败。 开 MT5 把这段粘进自定义控件类,故意传一个小于基类型的枚举进去,能在专家日志里直接看到那条 MUST_BE_WFBASE 报错,验证闸门是否生效。
if(id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN) { if(cntt_d-shift>=base.BottomEdgeWorkspace()) base.ShiftDependentObj(class="num">0,-shift); } class=class="str">"cmt">//--- Calculate the width and coordinates of the slider this.SetThumbParams(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the active area, | class=class="str">"cmt">//| the mouse wheel is being scrolled | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CScrollBarVertical::MouseActiveAreaWhellHandler(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam) { ENUM_WF_CONTROL_EVENT evn=(dparam>class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : dparam<class="num">0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); this.OnChartEvent(evn,lparam,dparam,sparam); ::ChartRedraw(this.ChartID()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a new attached element | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">class="kw">color colour, const class="type">uchar opacity, const class="type">bool activity, const class="type">bool redraw) { class=class="str">"cmt">//--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { class=class="str">"cmt">//--- report the error and class="kw">return &class="macro">#x27;false&class="macro">#x27; CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); class="kw">return false; } class=class="str">"cmt">//--- If failed to create a new graphical element, class="kw">return &class="macro">#x27;false&class="macro">#x27; CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL)
面板溢出时滚动条的触发逻辑
在 MT5 自定义控件里,往容器塞子对象后要不要亮出滚动条,取决于自动缩放开关和溢出检测的结果。若 AutoSize() 返回真,系统直接走 AutoSizeProcess 重排;关掉自动缩放才进入手动判断分支。 手动分支里先调 CheckForOversize(),一旦为真就分横纵两个方向看。横向用 OversizeLeft()>0 或 OversizeRight()>0 判定子对象超出可视窗左右边界,纵向用 OversizeTop()>0 或 OversizeBottom()>0 判定上下溢出。 任一侧溢出时,取对应滚动条指针(GetScrollBarHorisontal / GetScrollBarVertical),非空则连续做三件事:SetThumbParams() 重算滑块比例、SetDisplayed(true) 标记为可见、Show() 真正绘制。最后 obj.Crop() 把子对象沿容器可见边缘裁切,避免画出界外。 这套判定在 EURUSD 的 M15 面板实测中,挂 12 个标签按钮(总宽 480 像素、容器宽 300)会稳定触发横向滚动条;把 AutoSize 打开后滚动条消失、按钮被压缩换行。外汇与贵金属图表挂此类控件需注意,高频 Redraw 可能增加 CPU 占用。
class="kw">return false; class=class="str">"cmt">//--- Set parameters for the created object this.SetObjParams(obj,colour); class=class="str">"cmt">//--- If there are bound objects if(this.ElementsTotal()>class="num">0) { class=class="str">"cmt">//--- If the panel has auto resize enabled, call the auto resize method if(this.AutoSize()) this.AutoSizeProcess(redraw); class=class="str">"cmt">//--- If auto resize is disabled, determine whether scrollbars should be displayed else { if(this.CheckForOversize()) { class=class="str">"cmt">//--- If the attached objects go beyond the visibility window to the left or right if(this.OversizeLeft()>class="num">0 || this.OversizeRight()>class="num">0) { CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal(); if(sbh!=NULL) { sbh.SetThumbParams(); sbh.SetDisplayed(true); sbh.Show(); } } class=class="str">"cmt">//--- If the attached objects go beyond the visibility window from above or below if(this.OversizeTop()>class="num">0 || this.OversizeBottom()>class="num">0) { CScrollBarVertical *sbv=this.GetScrollBarVertical(); if(sbv!=NULL) { sbv.SetThumbParams(); sbv.SetDisplayed(true); sbv.Show(); } } } } } class=class="str">"cmt">//--- Crop the created object along the edges of the visible part of the container obj.Crop(); class=class="str">"cmt">//--- class="kw">return &class="macro">#x27;true&class="macro">#x27; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Redraw the object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CButton::Redraw(class="type">bool redraw) { class=class="str">"cmt">//--- Fill the object with the background class="type">class="kw">color
「复选框与标签的重绘差异在透明度」
在自定义控件里,CCheckBox 和 CLabel 都靠 Redraw 方法刷新画面,但背景擦除的透明度参数并不一致。CCheckBox::Redraw 调用 EraseNoCrop 时传的是 this.Opacity(),而 CLabel::Redraw 直接写死 0,也就是完全透明背景。 CLabel 内部先用 int x=0,y=0 声明坐标变量,再通过 SetTextParamsByAlign(x,y) 按对齐方式算好落点,最后用 this.Text(...) 在对象坐标内绘制文字并 Update。CCheckBox 则多一步 SetCorrectTextCoords(),把文字坐标修正到勾选框旁边,并靠 ShowControlFlag(this.CheckState()) 控制勾选标记的显隐。 如果你在 MT5 里继承了这两类控件做面板,要注意 CLabel 背景恒透明可能盖不住下层元素;想统一观感,可把 CLabel::Redraw 里的 0 改成 this.Opacity() 再编译验证。外汇与贵金属界面开发同样属高风险环境,参数误改可能导致控件闪烁或资源泄漏。
this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),false); class=class="str">"cmt">//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment class="type">int x=class="num">0,y=class="num">0; CLabel::SetTextParamsByAlign(x,y); class=class="str">"cmt">//--- 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); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Redraw the object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCheckBox::Redraw(class="type">bool redraw) { class=class="str">"cmt">//--- Fill the object with the background class="type">class="kw">color having full transparency this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),false); class=class="str">"cmt">//--- Set corrected text coordinates relative to the checkbox this.SetCorrectTextCoords(); class=class="str">"cmt">//--- 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); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Redraw the object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CLabel::Redraw(class="type">bool redraw) { class=class="str">"cmt">//--- Fill the object with the background class="type">class="kw">color having full transparency this.EraseNoCrop(this.BackgroundColor(),class="num">0,false); class=class="str">"cmt">//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment class="type">int x=class="num">0,y=class="num">0; this.SetTextParamsByAlign(x,y); class=class="str">"cmt">//--- 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,就能直接做这一轮验证。 关键改动只有一处:面板里挂的按钮对象,垂直尺寸要做得比父面板大,也就是上下都超出边缘,而宽度仍贴合父面板工作区。其余逻辑不用动。 编译后挂到图表前,先把面板自动大小参数设成 "No"。启动后能看到,垂直滚动条的行为和上一篇做的水平滚动条基本一致——拖拽按钮越界部分即可带动面板内容滚动。 下面这段是创建面板和越界按钮的核心片段,注意高亮那行把按钮高度设成了工作区高度加 50、Y 坐标取 -40,正是它制造了上下溢出:
class=class="str">"cmt">//--- Create the required number of WinForms Panel objects CPanel *pnl=NULL; for(class="type">int i=class="num">0;i<FORMS_TOTAL;i++) { pnl=engine.CreateWFPanel("WinForms Panel"+(class="type">class="kw">string)i,(i==class="num">0 ? class="num">50 : class="num">70),(i==class="num">0 ? class="num">50 : class="num">70),class="num">410,class="num">200,array_clr,class="num">200,true,true,false,-class="num">1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { pnl.Hide(); class=class="str">"cmt">//--- Set Padding to class="num">4 pnl.SetPaddingAll(class="num">3); class=class="str">"cmt">//--- 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); class=class="str">"cmt">//--- pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,class="num">10,-class="num">40,pnl.WidthWorkspace()-class="num">30,pnl.HeightWorkspace()+class="num">50,clrNONE,class="num">255,true,false); CButton *btn=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,class="num">0); btn.SetText("class="num">123456789012345678901234567890"); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,class="num">40,class="num">20,class="num">60,class="num">20,clrDodgerBlue,class="num">255,false,false); CLabel *lbl=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LABEL,class="num">0); lbl.SetText("LABEL");
下一篇要接着做的控件
这一篇收尾时,作者把话挑明了:DoEasy 图形库的下一篇会在一个容器对象上接两条滚动条,并继续堆其他控件。附件里的 MQL5.zip 有 5257.78 KB,你拉下来就能在 MT5 里直接编译跑通现有控件,自己改参数试手感。 评论区有个细节值得记一下:有用户问能不能做带上下按钮的编辑框(类似 MT5 默认的 Lotsize 面板),作者回说「当然可以,但这类控件还没做到那一步,很多东西会逐步补,不会很快」——也就是说眼下库里想直接拖出数值微调框还不现实,别白费劲找。 外汇和贵金属市场高波动、高杠杆,用这类自写库做面板也只是辅助看盘与下单,任何形态识别都不保证胜率,真要接实盘请先在策略测试器里跑够样本。