DoEasy. 控件(第 4 部分):面板控件,Padding(填充)和 Dock(驻靠)参数·进阶篇
🧩

DoEasy. 控件(第 4 部分):面板控件,Padding(填充)和 Dock(驻靠)参数·进阶篇

(2/3)· 当多个控件塞进一个面板却总对不齐边框,多半是没吃透填充与驻靠的级联规则

含代码示例实战向 第 2/3 篇
在面板里手动算每个子控件坐标,改一次布局就要重调全部位置。用 Dock 绑定边界再配合 Padding,后续对象会自动贴着前一个对象,省掉大量硬编码偏移。

◍ 图形元素基类的资源与画布错误清单

在 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 宽高那两条。

MQL5 / C++
  {"Ошибка! Пустой массив","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、颜色、透明度与重绘开关,参数齐了才能挂到图表。外汇与贵金属图表上叠加这类元素时,注意高杠杆下价格跳空可能让坐标映射失真,需实测验证。

MQL5 / C++
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 地址抛出来,外部就能拿同一块画布接着画。外汇和贵金属图表叠加这类自定义图形时波动剧烈,高杠杆下误绘可能引发错判,务必先在模拟盘验证。

MQL5 / C++
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。外汇与贵金属图表叠加此类自绘层波动剧烈,实盘前请在模拟盘验证渲染开销。

MQL5 / C++
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 头文件,可直接编译验证取色分支。

MQL5 / C++
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,就能复现这个零反馈拦截,确认自己的拖拽逻辑是否踩线。

MQL5 / C++
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;
让小布替你跑这套
这些控件排布逻辑在小布盯盘的 AIGC 诊断里已内置,打开对应品种页就能看到容器结构示意,把重复劳动交给小布,你专注决策。

常见问题

面板内每个后续对象会粘附到同一侧的前一个对象边界,而非容器的 Padding 距离,这是级联绑定的默认行为,需手动调整顺序或嵌套容器。
目前小布盯盘提供结构可视化和参数诊断,代码仍需在终端编写,但布局思路可参照内置示意快速对齐。
参考底图会按 Padding 值自行移动,在其上绘制图形时不必再算偏移,坐标直接相对底图即可。
在 GraphINI.mqh 为窗体背景新增渐变参数,并在 Defines.mqh 调整控件边界枚举顺序,将绑定常数置首,样式参数总量由 9 增至 11。
绑定到边界的对象按 Padding 值留空,若忽略该值会导致子对象紧贴边沿,重叠或溢出概率升高,尤其多对象布局时更明显。