DoEasy. 控件 (第 14 部分): 命名图形元素的新算法。 继续操控 TabControl WinForms 对象(基础篇)
给 TabControl 选项卡标题换一套命名规则
在 DoEasy 库第 14 部分里,核心改动是重写了 TabControl 下图形元素的命名算法,重点落在 TabHeader 类——它管的是 WinForms 风格选项卡控件的标题区。旧逻辑里同名元素容易在树里撞车,新算法给每个 TabHeader 拼了父容器 ID 与序号后缀,MT5 里用 OBJPROP_NAME 取对象名时不会再出现重复串。 原文给出的示例发布于 2022-11-23,截至发文被查看 1007 次,说明这套 WinForms 模拟控件在自定义面板里确有刚需。要验证,直接开 MT5 把库里 TabHeader::Create() 生成的名字打印出来,对比第 13 版的命名即可看到差别。 外汇与贵金属自定义 EA 面板属于高风险开发场景,控件逻辑错乱不会爆仓但会拖慢盯盘效率;改命名算法前先在策略测试器外的普通图表挂脚本跑一遍对象枚举。
◍ 绕开 63 字符命名墙的新思路
早先做 TabControl 的 WinForms 对象时,子元素名字要带父级引用和整条层级链,每嵌套一层名称就更长,直接撞上 MT5 图形资源名 63 个字符的硬上限,导致对象建不全。 现在的做法是不再拼层级链,而是用「程序名 + 元素类型 + 该类元素已建数量」来命名。比如测试程序里新建的同类型控件,名字只记录自己是第几个,长度恒定,嵌套再深也不会超限。 代价是光看名字没法还原它在对象树里的位置。补法是给图形元素加一个字符串属性「描述」,像切换按钮就写「用于切换交易方向的按钮」,程序里按描述引用比认「MyProgram_Elm00_Elm01_Elm00」这种模糊串直观得多。 这一节顺带推进 TabControl:先写 TabHeader 对象,负责画选项卡标题。它得能和同组其他标题联动,选中时轻微放大;并且按 TabControl 的标题位置(上/下/左/右)只画三边框——比如顶部标题就只画左、顶、右,底边留空和字段区接成一体,不显分界。今天先落地按位置绘边框,字段边框和下层控件摆放留到下篇。
「把控件派生和命名逻辑塞进 DoEasy 根目录」
ListBox 这类复合控件本质上是拿 Button 改出来的——它要在每一项上画文本,所以得从 CButton 派生一个带位移能力的新对象。这类辅助控件(ListBoxItem、TabHeader)最好丢进图形控件根目录的单独类别,而不是埋在各自文件夹里,方便后续 TabControl 等容器直接调用。 在 Defines.mqh 的枚举里要补三件事:给 TabControl 加容器类型,给辅助控件加 ListBoxItem 和 TabHeader 两个新类型;同时在 canvas 字符串属性枚举里加“描述”属性,并把属性总数从 3 改到 4。排序条件列表末尾也要挂上按新属性排序的逻辑,否则图形元素没法按描述筛选。 DELib.mqh 里那个从类型枚举抽文件名的函数值得细看。以 GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX 为例,先转文本,从第 18 个字符截出 “_WF_CHECKED_LIST_BOX”,逐字符拷进 ushort 数组(数组里存的是每个字符的 ASCII 码),再遍历:碰到下划线(码值 95)就跳到下一个字符不动,其余全部加 32 转小写,最后拼回字符串得到 “_Wf_Checked_List_Box”。这套逻辑直接决定了你后面自动生成的对象名长什么样。 [CODE] // 从类型枚举常量抽取并格式化描述子串 string GetTypeDescription(ENUM_GRAPH_ELEMENT_TYPE type) { string name=EnumToString(type); // 枚举转文本,如 "GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX" string sub=StringSubstr(name,18); // 自第18字符起截出 "_WF_CHECKED_LIST_BOX" ushort arr[]; StringToShortArray(sub,arr); // 逐字符存入 ushort 数组 for(int i=0;i<ArraySize(arr);i++) { if(arr[i]==95) continue; // 下划线(95)后首字母保留大写,跳过 else arr[i]+=32; // 其余字符加32转小写 } return ShortArrayToString(arr); // 得 "_Wf_Checked_List_Box" } [/CODE] GCnvElement 里现在不再从外部传 name,改成内部按类型拼名:前缀(“Program_name”+“_”)拼上类型描述再拼上同类型已存在数量。统计同类型数量的方法就是扫图表和子窗口所有图形对象,名字里含该类型子串就计数器加一。外汇/贵金属 EA 面板若对象泛滥,这个计数能避免重名覆盖,但 MT5 对象上限和图表卡顿风险得自己压测。 继承链上传类型是关键改动:每个 WinForms 类加一个受保护构造函数,只接收 type 参数并甩给父类,一直到 CGCnvElement 才真正建对象。这样哪怕派生层级很深,类型也能沿链“抵达”父类,不用在每层硬写类型。Button.mqh 里把设置状态的方法改虚函数,ListBoxItem 才能重定义左对齐时的文本右移——平移量存私有变量,SetTextShiftSpace() 按对齐方式塞空格,每构造函数默认右移一个字符。
class=class="str">"cmt">// 从类型枚举常量抽取并格式化描述子串 class="type">class="kw">string GetTypeDescription(ENUM_GRAPH_ELEMENT_TYPE type) { class="type">class="kw">string name=EnumToString(type); class=class="str">"cmt">// 枚举转文本,如 "GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX" class="type">class="kw">string sub=StringSubstr(name,class="num">18); class=class="str">"cmt">// 自第18字符起截出 "_WF_CHECKED_LIST_BOX" class="type">class="kw">ushort arr[]; StringToShortArray(sub,arr); class=class="str">"cmt">// 逐字符存入 class="type">class="kw">ushort 数组 for(class="type">int i=class="num">0;i<ArraySize(arr);i++) { if(arr[i]==class="num">95) class="kw">continue; class=class="str">"cmt">// 下划线(class="num">95)后首字母保留大写,跳过 else arr[i]+=class="num">32; class=class="str">"cmt">// 其余字符加32转小写 } class="kw">return ShortArrayToString(arr); class=class="str">"cmt">// 得 "_Wf_Checked_List_Box" }
给 ListBox 控件补上文本平移与对象清理
在 ListBox.mqh 的私有段加一个保存元素文本平移量的集合变量,公开段暴露读写方法,保护段放受保护构造函数。构造逻辑和之前 CButton 那些类一致,不必重写一遍。 把原来操控 CButton 的地方换成 CListBoxItem:对象一建出来就立刻建文本平移,再给集合里每个元素填默认文本。平移量以字符为单位,从元素左边缘往右推。 方法里收一个字符数,写进变量,然后轮询绑定对象列表,逐个取出来调 SetText() 按平移量重设文本。这样列表项就能整体缩进,不再贴边。 Container.mqh 里要删掉多余的构造函数,只留保护段声明的那个,实现方式和其它 WinForms 对象相同。附着参数设置方法中补上 ListBoxItem 的处理分支,同时移除已不用的 TabPage 分支。 ElementsListBox.mqh 包含 CListBoxItem 头文件后,列表集合就能在库内其它对象直接用了。同样声明保护构造函数,其实现和参数化构造优化与其余 WinForms 对象雷同。下面这段枚举展示了图形元素类型从标准对象到 WinForms 控件的完整谱系,其中 GRAPH_ELEMENT_TYPE_WF_LIST_BOX 就是本次接入的列表框类型。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The list of graphical element types | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, class=class="str">"cmt">// Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, class=class="str">"cmt">// Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, class=class="str">"cmt">// Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, class=class="str">"cmt">// Element GRAPH_ELEMENT_TYPE_FORM, class=class="str">"cmt">// Form GRAPH_ELEMENT_TYPE_WINDOW, class=class="str">"cmt">// Window class=class="str">"cmt">//--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, class=class="str">"cmt">// Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, class=class="str">"cmt">// Windows Forms Base class=class="str">"cmt">//--- &class="macro">#x27;Container&class="macro">#x27; object types are to be set below GRAPH_ELEMENT_TYPE_WF_CONTAINER, class=class="str">"cmt">// Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, class=class="str">"cmt">// Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, class=class="str">"cmt">// Windows Forms GroupBox GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, class=class="str">"cmt">// Windows Forms TabControl class=class="str">"cmt">//--- &class="macro">#x27;Standard control&class="macro">#x27; object types are to be set below GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, class=class="str">"cmt">// Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, class=class="str">"cmt">// Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, class=class="str">"cmt">// Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, class=class="str">"cmt">// Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, class=class="str">"cmt">// Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, class=class="str">"cmt">// Base list object of Windows Forms elements GRAPH_ELEMENT_TYPE_WF_LIST_BOX, class=class="str">"cmt">// Windows Forms ListBox
◍ 画布图元枚举与排序属性的底层定义
在 MT5 自建 UI 框架里,图元类型枚举收尾处列出了 WinForms 系的复合控件:勾选列表框、按钮列表框,以及作为辅助项的列表子项与标签页头。这些常量决定了你在 CCanvas 上能实例化哪类交互对象。 字符串属性单独成枚举 ENUM_CANV_ELEMENT_PROP_STRING,基址偏移由整型与双精度属性总量相加得出。可写字段含对象名、资源名、显示文本与描述,宏 CANV_ELEMENT_PROP_STRING_TOTAL 硬编码为 4,说明字符串槽位固定为四项,超界赋值会被编译器拦下。 排序模式枚举 ENUM_SORT_CANV_ELEMENT_MODE 把 ID、类型、对齐方式等整型维度排在前面,字符串维度从 FIRST_CANV_ELEMENT_STR_PROP 起跳。该宏展开为整型总量减跳过项再加双精度总量减跳过项,实测若前面整数跳过了 3 个保留位,字符串排序起点就会顺移,写比较器时得自己算准偏移。 直接把这段枚举抄进 EA 的 include 头,开 MT5 用 __LINE__ 配合 Print 打出各 SORT_BY_* 的实际值,能确认你本地 build 的偏移是否和文档一致。
enum ENUM_CANV_ELEMENT_PROP_STRING { CANV_ELEMENT_PROP_NAME_OBJ = (CANV_ELEMENT_PROP_INTEGER_TOTAL+CANV_ELEMENT_PROP_DOUBLE_TOTAL), class=class="str">"cmt">// Graphical element object name CANV_ELEMENT_PROP_NAME_RES, class=class="str">"cmt">// Graphical resource name CANV_ELEMENT_PROP_TEXT, class=class="str">"cmt">// Graphical element text CANV_ELEMENT_PROP_DESCRIPTION class=class="str">"cmt">// Graphical element description }; class="macro">#define CANV_ELEMENT_PROP_STRING_TOTAL(class="num">4) class=class="str">"cmt">// Total number of class="type">class="kw">string properties class="macro">#define FIRST_CANV_ELEMENT_STR_PROP(CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { SORT_BY_CANV_ELEMENT_ID = class="num">0, class=class="str">"cmt">// Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, class=class="str">"cmt">// Sort by graphical element type SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT, class=class="str">"cmt">// Sort by the location of tabs inside the control SORT_BY_CANV_ELEMENT_ALIGNMENT, class=class="str">"cmt">// Sort by the location of the object inside the control 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 };
「WinForms 控件枚举与多语言字符串映射」
在 MT5 的图表图形框架里,WinForms 类控件被拆成一组枚举常量,从基础控件到复合容器都有独立标识。上面列出的 MSG_GRAPH_ELEMENT_TYPE_WF_* 系列覆盖了 Label、CheckBox、RadioButton、Button 以及 ListBox 家族(含 ListBox Item、CheckedListBox、ButtonListBox),还有 Tab 相关的 Header、Page、Control 三层结构。 这些枚举后面紧跟着一个俄英双字符串的映射表,例如 { "Базовый стандартный элемент управления WinForms", "Basic Standard WinForms Control" } 对应 MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE。做多语种面板时,直接读这张表就能拿到运行时显示文案,不必自己硬编码。 字符串属性块另给了 MSG_CANV_ELEMENT_PROP_NAME_OBJ、MSG_CANV_ELEMENT_PROP_TEXT、MSG_CANV_ELEMENT_PROP_DESCRIPTION 等键,分别指向对象名、资源名、文本与描述。开 MT5 后搜这些宏,能确认你用的终端版本是否补齐了 ListBox Item 集合对象那一行——旧 build 可能漏掉这个条目。
MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, class=class="str">"cmt">// WinForms base standard control MSG_GRAPH_ELEMENT_TYPE_WF_LABEL, class=class="str">"cmt">// Label control MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX, class=class="str">"cmt">// CheckBox control MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, class=class="str">"cmt">// RadioButton control MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON, class=class="str">"cmt">// Button control MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, class=class="str">"cmt">// Base list object of Windows Forms elements MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX, class=class="str">"cmt">// ListBox control MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, class=class="str">"cmt">// ListBox control collection object MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, class=class="str">"cmt">// CheckedListBox control MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, class=class="str">"cmt">// ButtonListBox control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, class=class="str">"cmt">// Tab header MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE, class=class="str">"cmt">// TabPage control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, class=class="str">"cmt">// TabControl 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 class=class="str">"cmt">//--- String properties of graphical elements MSG_CANV_ELEMENT_PROP_NAME_OBJ, class=class="str">"cmt">// Graphical element object name MSG_CANV_ELEMENT_PROP_NAME_RES, class=class="str">"cmt">// Graphical resource name MSG_CANV_ELEMENT_PROP_TEXT, class=class="str">"cmt">// Graphical element text MSG_CANV_ELEMENT_PROP_DESCRIPTION, class=class="str">"cmt">// Graphical element description }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- WinForms standard {"Базовый стандартный элемент управления WinForms","Basic Standard WinForms Control"}, {"Элемент управления \"Label\"","Control element \"Label\""}, {"Элемент управления \"CheckBox\"","Control element \"CheckBox\""}, {"Элемент управления \"RadioButton\"","Control element \"RadioButton\""}, {"Элемент управления \"Button\"","Control element \"Button\""}, {"Базовый объект-список Windows Forms элементов","Basic Windows Forms List Object"}, {"Элемент управления \"ListBox\"","Control element \"ListBox\""}, {"Объект коллекции элемента управления ListBox","Collection object of the ListBox control"}, {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""}, {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""}, {"Заголовок вкладки","Tab header"}