DoEasy. 控件 (第 17 部分): 裁剪对象不可见部分、辅助箭头按钮 WinForms 对象·进阶篇
🧩

DoEasy. 控件 (第 17 部分): 裁剪对象不可见部分、辅助箭头按钮 WinForms 对象·进阶篇

(2/3)· 画布不支持位图裁剪属性,靠自绘矩形区域遮住越界像素,并补上滚动条要用的箭头按钮

偏理论进阶 第 2/3 篇

很多交易者以为把控件塞进容器就自动只显示框内部分,结果标签超出边框还挂在背景上。MQL 的 OBJ_BITMAP 裁剪属性对内存画布无效,直接套用只会得到全显或全隐。这篇用自绘透明区解决越界,并顺手造出箭头按钮给后续 TabControl 滚动用。

「画布元素排序与控件枚举的底层常量」

在 MT5 自定义图形界面开发里,画布元素(Canvas Element)有一套固定的排序枚举,用来决定遍历或命中测试时的先后顺序。上面这段枚举片段列出了按可见区域宽高、所属分组、Z 序(点击接收优先级)、Tab 列索引以及对象内对齐方式等维度排序的常量,例如 SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH 对应按可见范围宽度排,SORT_BY_CANV_ELEMENT_ZORDER 决定图表点击事件的分发层级。 紧接着是一组界面控件类型消息常量,从 TabControl 到 ArrowButton 系列(上/下/左/右及组合框)都有独立标识。高亮部分如 MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP 专门指代向上箭头按钮控件,方便在消息路由里精准区分用户点了哪个方向键。 画布元素属性侧也有坐标与尺寸类消息常量,像 MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X / Y / WIDTH 分别描述可见区域的左上角坐标与宽度。实盘写面板时,直接用这些枚举做 switch 分支,比字符串比对快且不易拼错;外汇与贵金属图表挂 EA 做 UI 时波动滑点风险高,先在策略测试器里验证控件响应再上真仓。

MQL5 / C++
  SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH,              class=class="str">"cmt">// Sort by visibility scope width
  SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT,             class=class="str">"cmt">// Sort by visibility scope height
  SORT_BY_CANV_ELEMENT_GROUP,                           class=class="str">"cmt">// Sort by a group the graphical element belongs to
  SORT_BY_CANV_ELEMENT_ZORDER,                          class=class="str">"cmt">// Sort by the priority of a graphical object for receiving the event of clicking on a chart
class=class="str">"cmt">//---...
class=class="str">"cmt">//---...
  SORT_BY_CANV_ELEMENT_TAB_PAGE_COLUMN,                 class=class="str">"cmt">// Sort by tab column index
  SORT_BY_CANV_ELEMENT_ALIGNMENT,                       class=class="str">"cmt">// Sort by the location of the object inside the control
class=class="str">"cmt">//--- Sort by real properties
class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties
  SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,class=class="str">"cmt">// Sort by an element object name
  SORT_BY_CANV_ELEMENT_NAME_RES,                        class=class="str">"cmt">// Sort by the graphical resource name
  SORT_BY_CANV_ELEMENT_TEXT,                            class=class="str">"cmt">// Sort by graphical element text
  SORT_BY_CANV_ELEMENT_DESCRIPTION,                     class=class="str">"cmt">// Sort by graphical element description
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
  MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                  class=class="str">"cmt">// TabControl tab field
  MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                class=class="str">"cmt">// TabControl
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,               class=class="str">"cmt">// ArrowButton control
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,            class=class="str">"cmt">// UpArrowButton control
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,          class=class="str">"cmt">// DownArrowButton control
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,          class=class="str">"cmt">// LeftArrowButton control
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,         class=class="str">"cmt">// RightArrowButton control
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,       class=class="str">"cmt">// UpDownArrowBox control
  MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,       class=class="str">"cmt">// LeftRightArrowBox control
  MSG_GRAPH_OBJ_BELONG_PROGRAM,                         class=class="str">"cmt">// Graphical object belongs to a program
  MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                      class=class="str">"cmt">// Graphical object does not belong to a program
  MSG_CANV_ELEMENT_PROP_ACT_RIGHT,                      class=class="str">"cmt">// Right border of the element active area
  MSG_CANV_ELEMENT_PROP_ACT_BOTTOM,                     class=class="str">"cmt">// Bottom border of the element active area
  MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X,                 class=class="str">"cmt">// Visibility scope X coordinate
  MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y,                 class=class="str">"cmt">// Visibility scope Y coordinate
  MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,             class=class="str">"cmt">// Visibility scope width

把图形元素枚举名转成可读字符串

在 MT5 自定义控件库里,图形元素类型用 ENUM_GRAPH_ELEMENT_TYPE 枚举表达,但枚举名自带前缀且全大写,直接显示给用户会很丑。下面这段函数专门干「枚举转字符串」的脏活。 它先用 EnumToString 拿到完整枚举名,再从第 18 个字符起截取子串——因为前缀 GRAPH_ELEMENT_TYPE_ 刚好占 18 个字符(含末尾下划线)。截完丢进 ushort 数组,逐字符处理:遇到下划线(ASCII 95)就跳过一个字符不处理,其余字符统一加 0x20 转成小写。 最后用 ShortArrayToString 拼回字符串,并把控件基类后缀 _Wf_Base 替换成 WFBase。跑一遍你能看到 GRAPH_ELEMENT_TYPE_TAB_CONTROL 变成 tab control,可读性立刻上来。外汇与贵金属图表挂这种自绘面板时,注意控件坐标基于画布像素,缩放后可能错位,属高风险定制项。 别把枚举前缀长度写死 上面那个魔法数字 18 只在这套枚举命名规则下成立。哪天官方改了前缀长度,截取就会从中间断词。更稳的做法是用 StringFind 定位首个下划线后再 Substr,别赌命名规范不变。

MQL5 / C++
class="type">class="kw">string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   class="type">class="kw">ushort array[];
   class="type">int total=StringToShortArray(StringSubstr(EnumToString(type),class="num">18),array);  class=class="str">"cmt">// 枚举名去前18字符转ushort数组
   for(class="type">int i=class="num">0;i<total-class="num">1;i++)
     {
      if(array[i]==class="num">95)  class=class="str">"cmt">// 95即&class="macro">#x27;_&class="macro">#x27;,遇到下划线
        {
         i+=class="num">1;  class=class="str">"cmt">// 跳过下划线后的那个字符
         class="kw">continue;
        }
      else
         array[i]+=0x20;  class=class="str">"cmt">// 非下划线字符转小写
     }
   class="type">class="kw">string txt=ShortArrayToString(array);  class=class="str">"cmt">// 数组合回字符串
   StringReplace(txt,"_Wf_Base","WFBase");  class=class="str">"cmt">// 替换基类后缀

◍ 列表项对象的命名清洗与构造细节

在图形库里,列表框子项从原始对象名生成可读文本时,要先做一轮字符串规整。下面这段连续调用会把 '_Wf_'、'_Obj'、单独下划线全部剔掉,并把 'Groupbox' 修正为 'GroupBox',顺手把 'ButtonsUdBox'、'ButtonsLrBox' 的大小写补全为 'ButtonsUDBox'、'ButtonsLRBox',避免界面上出现五花八门的标签。 StringReplace(txt,"_Wf_",""); StringReplace(txt,"_Obj",""); StringReplace(txt,"_",""); StringReplace(txt,"Groupbox","GroupBox"); StringReplace(txt,"ButtonsUdBox","ButtonsUDBox"); StringReplace(txt,"ButtonsLrBox","ButtonsLRBox"); return txt; 清洗完名字后,CListBoxItem 的受保护构造函数接管对象本体。它接收图形元素类型、图表 ID、子窗口号以及坐标宽高,并直接复用 CButton 的初始化链:把元素类型写回内部字段,将 m_type 固定为 OBJECT_DE_TYPE_GWF_HELPER,再把文本对齐设为左锚、文字左侧留 1 像素间距。 CListBoxItem::CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } 还有一个对外暴露的轻量构造版本,只吃 chart_id、subwindow、descript 和 x/y,其余宽高走默认值,方便在 EA 里快速挂一个列表项而不必每次算尺寸。外汇与贵金属图表加载这类自定义图形对象存在刷新失败可能,建议在 MT5 策略测试器里先跑一次创建/销毁循环验证稳定性。

MQL5 / C++
StringReplace(txt,"_Wf_","");
StringReplace(txt,"_Obj","");
StringReplace(txt,"_","");
StringReplace(txt,"Groupbox","GroupBox");
StringReplace(txt,"ButtonsUdBox","ButtonsUDBox");
StringReplace(txt,"ButtonsLrBox","ButtonsLRBox");
class="kw">return txt;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Protected constructor with an object type, chart ID and subwindow |
class=class="str">"cmt">//+------------------------------------------------------------------+
CListBoxItem::CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type,
                           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) : CButton(type,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.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetTextAlign(ANCHOR_LEFT);
   this.SetTextShiftSpace(class="num">1);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CListBoxItem::CListBoxItem(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,

「自建列表项控件的层叠与偏移写法」

在 MT5 自定义图形库里,列表框子项常从 CButton 派生,构造时直接把元素类型钉死为 GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,并顺手把 m_type 标成 OBJECT_DE_TYPE_GWF_HELPER,避免后续事件路由误判。 SetZorder 这层封装值得留意:它先 ResetLastError,再走 ObjectSetInteger 改 OBJPROP_ZORDER;若 only_prop 为真就只存内存值不碰图表对象,返回 true 的概率取决于图表 ID 与对象名是否有效。外汇与贵金属图表上叠太多辅助控件时,Z 序乱了点不到是常事,属于高概率但非必现的坑。 SetXOffset 针对 OBJ_BITMAP_LABEL / OBJ_BITMAP 的可见区左上角 X 坐标,失败就吐错误码并返回 false。下面这段是原文核心,逐行拆一遍就能直接抄进自己的类。 虚拟方法重写时若省掉 only_prop 分支,控件在脚本重载后可能丢失层叠状态,倾向在初始化阶段显式调一次 SetZorder(0,false) 做基线。

MQL5 / C++
const class="type">int w,
const class="type">int h) : CButton(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,chart_id,subwindow,descript,x,y,w,h)
{
 this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM);
 this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
 this.SetTextAlign(ANCHOR_LEFT);
 this.SetTextShiftSpace(class="num">1);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Set the priority of a graphical object for receiving the event of clicking on a chart 
  class="kw">virtual class="type">bool      SetZorder(const class="type">long value,const class="type">bool only_prop)
                 {
                  ::ResetLastError();
                  if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
                    {
                     this.m_zorder=value;
                     class="kw">return true;
                    }
                  else
                     CMessage::ToLog(DFUN,::GetLastError(),true);
                  class="kw">return false;
                 }

class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the X coordinate of the upper left corner of the rectangle visibility scope of the OBJ_BITMAP_LABEL and OBJ_BITMAP graphical object
  class="kw">virtual class="type">bool      SetXOffset(const class="type">long value)
                 {
                  ::ResetLastError();
                  if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET,value))
                    {
                     CMessage::ToLog(DFUN,::GetLastError(),true);
                     class="kw">return false;
                    }

图形对象的偏移与尺寸读写接口

在 MT5 自定义图形类里,位图标签(OBJ_BITMAP_LABEL)和位图(OBJ_BITMAP)的可见范围定位,靠的是 XOffset / YOffset 这两个虚函数。Set 方法先 ResetLastError 清掉旧错误码,再用 ObjectSetInteger 写 OBJPROP_XOFFSET / OBJPROP_YOFFSET,失败就通过 CMessage::ToLog 把 GetLastError 打进日志并返回 false,成功返回 true。 读取值时直接用 XOffset() 和 YOffset(),内部把 ObjectGetInteger 的返回值强转成 int,零参数调用,开销极低。 宽度控制走 SetXSize,目标属性是 OBJPROP_XSIZE,适用于 OBJ_LABEL(只读)、OBJ_BUTTON、OBJ_CHART、OBJ_BITMAP、OBJ_BITMAP_LABEL、OBJ_EDIT 和 OBJ_RECTANGLE_LABEL。设置逻辑与偏移一致:清错误码→写整数→失败记日志返 false。外汇与贵金属图表上叠加这类对象时,坐标偏移量建议用像素整数,避免高分屏下定位漂移。

MQL5 / C++
  class="kw">virtual class="type">int       XOffset(class="type">void)  const { class="kw">return (class="type">int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET);  }
class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the Y coordinate of the upper left corner of the rectangle visibility scope of the OBJ_BITMAP_LABEL and OBJ_BITMAP graphical object
  class="kw">virtual class="type">bool      SetYOffset(const class="type">long value)
    {
      ::ResetLastError();
      if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET,value))
        {
         CMessage::ToLog(DFUN,::GetLastError(),true);
         class="kw">return false;
        }
      class="kw">return true;
    }
  class="kw">virtual class="type">int       YOffset(class="type">void)  const { class="kw">return (class="type">int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET);  }
class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the width of OBJ_LABEL(read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT and OBJ_RECTANGLE_LABEL objects
  class="kw">virtual class="type">bool      SetXSize(const class="type">long value)
    {
      ::ResetLastError();
      if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE,value))
        {
         CMessage::ToLog(DFUN,::GetLastError(),true);
         class="kw">return false;
        }

◍ 对象尺寸与跨周期可见性的底层封装

在 MT5 自定义图形对象类里,XSize 与 YSize 是两个只读取值器,直接调 ObjectGetInteger 抓 OBJPROP_XSIZE / OBJPROP_YSIZE,返回 int 类型像素值。这两个方法对 OBJ_LABEL、OBJ_BUTTON、OBJ_RECTANGLE_LABEL 等带尺寸的控件都有效,写面板时拿它做自适应布局比硬编常数稳。 SetYSize 的写法是先 ResetLastError,再 ObjectSetInteger 写 OBJPROP_YSIZE,失败就通过 CMessage::ToLog 把错误码丢进日志并返回 false,成功返回 true。注意它只负责写属性,不回读校验,若图表对象被手动删了,调用可能静默失败,得靠日志排查。 SetVisibleFlag 用 OBJ_ALL_PERIODS 和 OBJ_NO_PERIODS 两个宏切换对象在所有周期显隐。only_prop 参数为 true 时跳过实际 ObjectSetInteger 调用,只改类内 m_visible 标记——这在进行批量状态同步、暂不落盘时很有用。外汇与贵金属图表上过度绘制跨周期对象会拖慢终端,实盘前建议在模拟盘测一下对象数量上限。

MQL5 / C++
  class="kw">virtual class="type">int       XSize(class="type">void)    const { class="kw">return (class="type">int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE);    }
  class="kw">virtual class="type">bool      SetYSize(const class="type">long value)
    {
    ::ResetLastError();
    if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE,value))
      {
       CMessage::ToLog(DFUN,::GetLastError(),true);
       class="kw">return false;
      }
    class="kw">return true;
    }
  class="kw">virtual class="type">int       YSize(class="type">void)    const { class="kw">return (class="type">int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE);    }
class=class="str">"cmt">//--- Set object visibility on all timeframes
  class="type">bool              SetVisibleFlag(const class="type">bool flag,const class="type">bool only_prop)
    {
    class="type">long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
    ::ResetLastError();
    if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop)
      {
       this.m_visible=flag;
把越界检测交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到控件溢出与按钮状态的实时标注,你只需判断布局是否影响下单区域。

常见问题

裁剪属性仅针对基于物理 bmp 文件的 OBJ_BITMAP 和 OBJ_BITMAP_LABEL,内存画布资源不被归入此类,因此设置后无可视变化。
元素读取相对容器坐标后,把超出内边框的部分用透明色覆盖,等效于隐藏溢出像素,且不依赖官方位图裁剪通道。
小布盯盘内置的 AIGC 看板可识别自定义指标面板布局,但库文件需你自行编译进 EA,小布只做视觉诊断不代编代码。
滚动条、下拉列表、以及任何需要步进切换隐藏项的容器都倾向复用左右与上下两组按钮对象。
边界取边框内边缘,附着对象不能压边框,裁剪矩形相应缩进边框厚度,避免重叠遮挡。