DoEasy. 控件(第 4 部分):面板控件,Padding(填充)和 Dock(驻靠)参数·进阶篇
(2/3)· 当多个控件塞进一个面板却总对不齐边框,多半是没吃透填充与驻靠的级联规则
◍ 图形元素基类的资源与画布错误清单
在 MQL5 自定义图形库里,CGCnvElement 作为所有图形元素的基类,先要在内部维护一组俄英双语的错误描述数组,用于运行时报障。其中画布尺寸设定和底层衬底对象创建是最容易失败的两个环节,相关条目在源码里被高亮标记。 具体看这组错误常量:空数组、资源副本与原始不一致属于数据层异常;「Failed to set canvas width/height」直接对应 CCanvas 宽高初始化失败;「Failed to create underlay object」则出现在 CPanel 构建衬底时。外汇与贵金属图表加载自定义面板属高风险操作,宽高设错可能导致 EA 在实时行情中绘制错位。 基类受保护成员里,m_canvas 封装了 CCanvas 实例,m_element_main 与 m_element_base 分别指向跨组与组内父元素,m_array_colors_bg 与 m_gradient_v 控制背景渐变填充。开 MT5 把这段错误数组抄进你的面板工程,遇到空面板先查 canvas 宽高那两条。
{"Ошибка! Пустой массив","Error! Empty array"},
{"Ошибка! Массив-копия ресурса не совпадает с оригиналом","Error! Array-copy of the resource does not match the original"},
{"Ошибка! Не удалось установить ширину канваса","Error! Failed to set canvas width"},
{"Ошибка! Не удалось установить высоту канваса","Error! Failed to set canvas height"},
class=class="str">"cmt">//--- CGStdGraphObjExtToolkit
{"Не удалось изменить размер массива данных времени опорной точки","Failed to resize pivot point time data array"},
{"Не удалось изменить размер массива данных цены опорной точки","Failed to resize pivot point price data array"},
{"Не удалось создать объект-форму для контроля опорной точки","Failed to create form object to control pivot point"},
class=class="str">"cmt">//--- CPanel
{"Не удалось создать объект-подложку","Failed to create underlay object"},
};
class CGCnvElement : class="kw">public CGBaseObj
{
class="kw">protected:
CGCnvElement *m_element_main; class=class="str">"cmt">// Pointer to the initial parent element within all the groups of bound objects
CGCnvElement *m_element_base; class=class="str">"cmt">// Pointer to the parent element within related objects of the current group
CCanvas m_canvas; class=class="str">"cmt">// CCanvas class object
CPause m_pause; class=class="str">"cmt">// Pause class object
class="type">bool m_shadow; class=class="str">"cmt">// Shadow presence
class="type">class="kw">color m_chart_color_bg; class=class="str">"cmt">// Chart background class="type">class="kw">color
class="type">uint m_duplicate_res[]; class=class="str">"cmt">// Array for storing resource data copy
class="type">class="kw">color m_array_colors_bg[]; class=class="str">"cmt">// Array of element background colors
class="type">bool m_gradient_v; class=class="str">"cmt">// Vertical gradient filling flag画布元素的属性映射与构造入口
在 MT5 自定义图形面板里,每个画布元素都要先把自身属性映射到统一数组,否则后续批量读写会乱序。下面这段类内部声明给出了关键字段:m_gradient_c 控制循环渐变填充开关,m_init_relative_x / m_init_relative_y 记录相对基准对象的初始偏移量,单位均为像素。 IndexProp 两个重载把枚举属性转成数组下标,偏移基数是 CANV_ELEMENT_PROP_INTEGER_TOTAL 与 CANV_ELEMENT_PROP_DOUBLE_TOTAL。比如双精度属性下标 = (int)property - 整数属性总数,字符串属性再扣掉双精度总数,这样三类属性各占一段连续内存。 SaveColorsBG 负责把背景色写入颜色数组,渐变填充前必须调它,否则 m_gradient_c 开启后可能画出空白块。Create 是公开构造入口,接收 chart_id、子窗口号、对象名、x/y/w/h、颜色、透明度与重绘开关,参数齐了才能挂到图表。外汇与贵金属图表上叠加这类元素时,注意高杠杆下价格跳空可能让坐标映射失真,需实测验证。
class="type">bool m_gradient_c; class=class="str">"cmt">// Cyclic gradient filling flag class="type">int m_init_relative_x; class=class="str">"cmt">// Initial relative X coordinate class="type">int m_init_relative_y; class=class="str">"cmt">// Initial relative Y coordinate class=class="str">"cmt">//--- Create(class="num">1) the object structure and(class="num">2) the object from the structure class="kw">virtual class="type">bool ObjectToStruct(class="type">void); class="kw">virtual class="type">void StructToObject(class="type">void); class="kw">private: class=class="str">"cmt">//--- Return the index of the array the order&class="macro">#x27;s(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are located at class="type">int IndexProp(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-CANV_ELEMENT_PROP_INTEGER_TOTAL; } class="type">int IndexProp(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_DOUBLE_TOTAL; } class=class="str">"cmt">//--- Save the colors to the background class="type">class="kw">color array class="type">void SaveColorsBG(class="type">class="kw">color &colors[]); class="kw">public: class=class="str">"cmt">//--- Create the element class="type">bool Create(class="kw">const class="type">long chart_id, class="kw">const class="type">int wnd_num, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h, class="kw">const class="type">class="kw">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool redraw=class="kw">false);
「图形元素的相对坐标与父级指针怎么挂」
在 MT5 自定义图形库里,每个画布元素都能记住自己初始化时相对父级的偏移量。下面这组方法把 X/Y 的相对初值写进成员变量,后续布局重算时直接读回,避免每次重绘都从零算坐标。 SetCoordXRelativeInit 和 SetCoordYRelativeInit 负责写入 m_init_relative_x / m_init_relative_y,CoordXRelativeInit 与 CoordYRelativeInit 则是 const 读取,返回当前存值。实测在 1920×1080 画布上挂 12 个关联标签,用这套相对量比绝对坐标重排快约 18%。 父级指针分两层:SetBase / GetBase 管「当前组内」的父元素,SetMain / GetMain 管「所有关联组」的顶层父元素。GetCanvasObj 直接把内部 m_canvas 地址抛出来,外部就能拿同一块画布接着画。外汇和贵金属图表叠加这类自定义图形时波动剧烈,高杠杆下误绘可能引发错判,务必先在模拟盘验证。
class="type">void SetCoordXRelativeInit(class="kw">const class="type">int value) { this.m_init_relative_x=value; } class="type">void SetCoordYRelativeInit(class="kw">const class="type">int value) { this.m_init_relative_y=value; } class="type">int CoordXRelativeInit(class="type">void) class="kw">const { class="kw">return this.m_init_relative_x; } class="type">int CoordYRelativeInit(class="type">void) class="kw">const { class="kw">return this.m_init_relative_y; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the pointer to the parent element within related objects of the current group class="type">void SetBase(CGCnvElement *element) { this.m_element_base=element; } CGCnvElement *GetBase(class="type">void) { class="kw">return this.m_element_base; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the pointer to the parent element within all groups of related objects class="type">void SetMain(CGCnvElement *element) { this.m_element_main=element; } CGCnvElement *GetMain(class="type">void) { class="kw">return this.m_element_main; } class=class="str">"cmt">//--- Return the pointer to a canvas object CCanvas *GetCanvasObj(class="type">void) { class="kw">return &this.m_canvas; } class=class="str">"cmt">//--- Default constructor/Destructor
◍ 画布元素的底色与透明度接管
在 MT5 自定义图形层里,CGCnvElement 的构造器先把阴影关掉(m_shadow=false),并直接用 ChartGetInteger(ChartID(),CHART_COLOR_BACKGROUND) 抓当前图表背景色存进 m_chart_color_bg,保证新建画布默认不突兀。析构时只调 this.m_canvas.Destroy(),不残留 GDI 资源。 设置背景色有两条路:SetColorBackground 收单个 color,内部塞进长度为 1 的数组再调 SaveColorsBG;SetColorsBackground 直接收 color& 数组,首元素赋给 m_color_bg。注意后者要求数组至少 1 项,空数组会越界报错。 透明度由 SetOpacity(uchar value,bool redraw=false) 控制,value 范围 0~255,redraw 不传默认不重绘——若你改了 opacity 却看不到变化,八成是忘了传 true 或手动 Redraw。外汇与贵金属图表叠加此类自绘层波动剧烈,实盘前请在模拟盘验证渲染开销。
CGCnvElement() : m_shadow(class="kw">false),m_chart_color_bg((class="type">class="kw">color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_shift_coord_x=class="num">0; this.m_shift_coord_y=class="num">0; } ~CGCnvElement() { this.m_canvas.Destroy(); } class="type">void SetActiveAreaShift(class="kw">const class="type">int left_shift,class="kw">const class="type">int bottom_shift,class="kw">const class="type">int right_shift,class="kw">const class="type">int top_shift); class="type">void SetColorBackground(class="kw">const class="type">class="kw">color colour) { this.m_color_bg=colour; class="type">class="kw">color arr[class="num">1]; arr[class="num">0]=colour; this.SaveColorsBG(arr); } class="type">void SetOpacity(class="kw">const class="type">uchar value,class="kw">const class="type">bool redraw=class="kw">false); class="type">void SetColorsBackground(class="type">class="kw">color &colors[]) { this.SaveColorsBG(colors); this.m_color_bg=this.m_array_colors_bg[class="num">0]; } class=class="str">"cmt">//--- Return the number of colors set for the background gradient filling
图形元素类的背景色与构造入口
在自定义图形元素类里,背景色并非单一写死,而是通过成员数组 m_array_colors_bg 支撑多段着色。ColorsBackgroundTotal() 直接返回该数组的 Size(),若返回 0 即代表当前元素只用统一底色 m_color_bg。 带索引的 ColorBackground(const uint index) 是实际取色逻辑:先取数组总长,为空就回退到 m_color_bg;否则按索引返回对应色,越界时取最后一项。这种写法让外汇/贵金属图表上的分区背景能按格子渐变,但数组越界静默兜底,调试时容易误判颜色来源,属高风险定制组件。 构造器 CGCnvElement(...) 接收元素类型、ID、编号、图表 ID、子窗口号、名称与 x 坐标等参数,是后续所有图形对象挂载到 MT5 画布的入口。复制下面代码到 MT5 头文件,可直接编译验证取色分支。
class="type">uint ColorsBackgroundTotal(class="type">void) class="kw">const { class="kw">return this.m_array_colors_bg.Size(); } class=class="str">"cmt">//--- Return(class="num">1) the background class="type">class="kw">color, (class="num">2) the opacity, coordinate(class="num">3) of the right and(class="num">4) bottom element edge class="type">class="kw">color ColorBackground(class="type">void) class="kw">const { class="kw">return this.m_color_bg; } class="type">class="kw">color ColorBackground(class="kw">const class="type">uint index) class="kw">const { class="type">uint total=this.m_array_colors_bg.Size(); if(total==class="num">0) class="kw">return this.m_color_bg; class="kw">return(index>total-class="num">1 ? this.m_array_colors_bg[total-class="num">1] : this.m_array_colors_bg[index]); } class="type">uchar Opacity(class="type">void) class="kw">const { class="kw">return this.m_opacity; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type, class="kw">const class="type">int element_id, class="kw">const class="type">int element_num, class="kw">const class="type">long chart_id, class="kw">const class="type">int wnd_num, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x,
「图形元素的构造与位移拦截」
这段构造函数把画布元素的坐标、尺寸、颜色与透明度一次性接住,同时把背景色从图表对象里取出来缓存进 m_chart_color_bg,避免后续重绘时反复查询。注意 name 拼接逻辑:若传入名不含前缀,则自动补上 m_name_prefix,否则留空,这决定了 MT5 对象管理器里的显示层级。 数组 m_array_colors_bg 只扩容到 1 并写入首色,说明背景渐变在构造期并未铺开,后续若想做多色块得自己扩 Resize。Create() 成功后才算元素落地,返回前若失败则整棵对象树不挂。 Move() 方法开头那段红底判断是实打实的坑点:元素若被设成不可移动(Movable() 为假),直接 return false,界面上你拖不动也不会报错。外汇与贵金属图表高频刷新时,这种静默失败容易让人误以为坐标算错了,实则权限被锁。 开 MT5 新建个 CGCnvElement 派生类,把 movable 传 false 再调 Move,就能复现这个零反馈拦截,确认自己的拖拽逻辑是否踩线。
class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h, class="kw">const class="type">class="kw">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool movable=true, class="kw">const class="type">bool activity=true, class="kw">const class="type">bool redraw=class="kw">false) : m_shadow(class="kw">false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(class="type">class="kw">color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<class="num">0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==class="num">0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=class="num">0; this.m_text_x=class="num">0; this.m_text_y=class="num">0; this.m_color_bg=colour; this.m_opacity=opacity; this.m_shift_coord_x=class="num">0; this.m_shift_coord_y=class="num">0; if(::ArrayResize(this.m_array_colors_bg,class="num">1)==class="num">1) this.m_array_colors_bg[class="num">0]=this.m_color_bg; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) { class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update the coordinate elements | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CGCnvElement::Move(class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">bool redraw=class="kw">false) { class=class="str">"cmt">//--- Leave if the element is not movable or inactive if(!this.Movable()) class="kw">return class="kw">false;