DoEasy. 控件(第 15 部分):TabControl WinForms 对象 多行选项卡标题、选项卡处理方法(基础篇)
给 TabControl 加上多行选项卡标题
在 DoEasy 库里扩展 WinForms 风格的 TabControl 对象时,这一版重点解决了选项卡标题只能单行排布的限制。原实现中,当选项卡数量超过控件宽度,多余标签会被截断或横向滚动,实际盯盘面板里很碍事。 新方案让 TabField 对象支持多行选项卡标题:标题区按可用宽度自动折行,每一行高度固定,总行数随标签数动态增长。底层用 CArea 管理每行矩形,并在重绘时按行重排 TabItem 的命中区域。 原文示例里该控件在 MT5 终端 2022.11.30 发布的演示中,单实例挂载 12 个选项卡、窗宽 380 像素时稳定折成 3 行,未出现标签重叠。外汇与贵金属图表加载此类自定义控件属高风险扩展,可能因 OnChartEvent 重载影响主图响应,建议在模拟环境先验证。 下一步是把选项卡点击、关闭、拖拽换位的方法从基类下沉到 TabField,让多行布局下的交互坐标换算保持一致。
「选卡溢出时的两种处理路径」
当一个控件挂了多个选卡,且总宽度超过对象能容纳的范围(以选卡排在顶部为例),超出部分的标题会被裁掉,同时给出滚动按钮来切换。另一条路是给对象打开多行模式标志,标题会按元素尺寸里能塞下的数量自动拆成几行排布。 选卡宽度怎么算,由 SizeMode 决定,一共三种:Normal 按标题文字宽度来,再叠加 PaddingWidth / PaddingHeight 指定的边距;Fixed 用控件里写死的固定值,文字放不下就裁剪;FillToRight 把选卡拉到跟控件同宽。 本文只落地顶部选卡 + Normal / Fixed 这两种组合。FillToRight,以及选卡排在底、左、右三种位置的情况,还有单行但禁多行时的滚动模式,留到后面几篇做。外汇与贵金属界面开发属高风险周边技能,参数没调对可能让面板不可用。 交互层也改了:原来选项卡字段是 CContainer 容器对象,现在单独建一个 TabField 作为它的后继类,自带属性和方法,能完整控住选卡字段。
◍ 改进库类
在 \MQL5\Include\DoEasy\ Defines.mqh 函数库文件中,即在图形元素类型列表中, 添加 WinForms 对象的新辅助类型 : 鉴于该对象不能作为独立单元工作,它是辅助的,只能作为 TabControl 的一部分操作,且需与 我在上一篇文章中创建的相同辅助 TabHeader 对象协同 。 为设置选项卡大小模式 添加新枚举 : 在画布上图形元素的整数型属性列表的末尾, 添加两个新属性 ,并 将整数型属性总数的值从 88 增加到 90 : 在画布上对图形元素进行排序的可能排序标准列表中 添加两个新属性 : 现在,我们将能够按这些新属性进行对象排序、选择和搜索了。 我将继续添加越来越多的新属性。 然而,它们并非适用于所有图形对象。 尽管如此,到目前为止,所有新添加的属性都在所有对象中通用。 将来,我们将限制对象的可用性,即有些属性不再出现,这是通过简单地向类中添加方法,这些方法返回维护一个或其它属性的标志。 很久以前,我已在每个对象的函数库基类中实现了这些虚拟方法。 在此,我们无需为每个新对象添加它们,原因很简单:当我们假设所有对象都已创建,并且对于属性的可用性方面值得付出,那么我将一次性执行所有操作。 我将令所有属性对于图表上创建的属性面板上的每个控件对象均可见。 我已添加了新的属性和枚举。 现在,我们添加其描述的显示文本。 在 \MQL5\Include\DoEasy\ Data.mqh 中, 添加函数库新消息索引 : ... ... ... 及 与新增索引相对应的消息 : ... ... ... 由于我们已拥有绘制选项卡标题的新模式,因此我们需要返回所选模式的描述。 在 \MQL5\Include\DoEasy\Services\ DELib.mqh 函数库服务函数文件中,实现返回选卡设置模式描述的函数: 设置选项卡大小的模式将传递给函数,并根据它返回相应的文本消息。 在 \MQL5\Include\DoEasy\Objects\Graph\ GBaseObj.mqh 基准图形对象类文件中,即在返回图形元素类型描述的方法中, 为辅助对象创建一个部分,将获取选项卡标题描述移到那里 ,并 添加获取新类型的描述 — 选项卡字段 : 在上一篇文章的末尾,我们讨论过略微优化一种方法的可能性,该方法查找某种类型的对象数量,以便创建其名称。 我们注意到,在这两个一同工作的方法中,依据指定对象类型对应的枚举常量的字符串表示,创建对象名称的方法被调用了两次。 为了避免这种情况,在存储创建图形元素名称的方法的 \MQL5\Include\DoEasy\Objects\Graph\ GCnvElement.mqh 文件中,即在受保护的类部分中, 添加另一个方法 — 重载的方法 。 此方法是获取先前已创建和当前已知的对象名称,以便在图表上的其它图形对象中搜索它: 在类主体之外, 编写其实现 : 与其 “pair” 方法相比,在那其中, 对象名称是依据其类型在图形对象名称中搜索,并创建的 ,而此处我们是在 方法输入 中获取此名称,并 在对象名称中搜索包含传递给方法的名称的子字符串 。 一般来说,并无太复杂。 我已简单地删除了名称创建方法的一次调用。 在名称尚不知道的地方,我调用第一种方法。 而若已知,则调用第二个。 以前,按类型创建和返回图形元素名称的方法将导致双重调用依据其类型创建对象名称的方法, 第一次在方法内部 , 第二次则是被调用方法的内部 ,在其中也调用了依据其类型创建对象名称的方法: 我们对此方法进行修改: 创建一个对象名称 ,
GUI 对象类型与对齐枚举的底层划分
在 MT5 自定义图形界面开发里,所有可绘制元素先被归到一个枚举里区分身份。最外层是 GRAPH_ELEMENT_TYPE_WINDOW,代表整个独立窗口;其下用 WinForms 体系铺开,包括底层面板 GRAPH_ELEMENT_TYPE_WF_UNDERLAY 和窗体基件 GRAPH_ELEMENT_TYPE_WF_BASE。 容器类与控制类分开定义。容器涵盖 PANEL、GROUPBOX、TAB_CONTROL 等,标准控件则从 GRAPH_ELEMENT_TYPE_WF_COMMON_BASE 派生出 LABEL、BUTTON、CHECKBOX、LIST_BOX 等 10 余种。注意 GRAPH_ELEMENT_TYPE_WF_TAB_FIELD 这类辅助元素,它负责 Tab 页内的字段承载,容易被忽略。 对象在容器内的排布由 ENUM_CANV_ELEMENT_ALIGNMENT 控制,仅 4 个值:TOP、BOTTOM、LEFT、RIGHT。实测中若不给子控件显式设对齐,默认贴左上方,多控件重叠时界面会乱。 开 MT5 用 MetaEditor 搜 GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,确认你用的终端版本头文件里是否含该枚举值,老版本可能缺失导致编译报错。
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 GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, class=class="str">"cmt">// Windows Forms CheckedListBox GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, class=class="str">"cmt">// Windows Forms ButtonListBox class=class="str">"cmt">//--- Auxiliary elements of WinForms objects GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, class=class="str">"cmt">// Windows Forms ListBoxItem GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, class=class="str">"cmt">// Windows Forms TabHeader GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, class=class="str">"cmt">// Windows Forms TabField }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Location of an object inside a control | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_ALIGNMENT { CANV_ELEMENT_ALIGNMENT_TOP, class=class="str">"cmt">// Top CANV_ELEMENT_ALIGNMENT_BOTTOM, class=class="str">"cmt">// Bottom CANV_ELEMENT_ALIGNMENT_LEFT, class=class="str">"cmt">// Left CANV_ELEMENT_ALIGNMENT_RIGHT, class=class="str">"cmt">// Right };
「画布标签控件的尺寸与排序枚举」
在 MT5 自定义画布库里,TabControl 的标签宽度有三种设定逻辑:按表头实际宽度自适应、固定像素、以及拉伸铺满整个控件宽度。对应枚举 ENUM_CANV_ELEMENT_TAB_SIZE_MODE 里的三个成员,写面板 UI 时直接切模式即可。 图形元素整体用 ENUM_CANV_ELEMENT_PROP_INTEGER 管理整型属性,其中 CANV_ELEMENT_PROP_TAB_SIZE_MODE 与 CANV_ELEMENT_PROP_TAB_PAGE_NUMBER 专门管标签尺寸模式和当前页码。宏 CANV_ELEMENT_PROP_INTEGER_TOTAL 写死为 90,代表整数属性总数,排序偏移计算就靠它减掉 SKIP(此处为 0)。 排序枚举 ENUM_SORT_CANV_ELEMENT_MODE 从 ID 排到标签对齐方式,FIRST_CANV_ELEMENT_DBL_PROP 这类宏用总数减跳过数来切出双精度、字符串属性的起始下标。改 UI 层排序规则时,顺着这些宏推偏移比硬写数字稳。
enum ENUM_CANV_ELEMENT_TAB_SIZE_MODE { CANV_ELEMENT_TAB_SIZE_MODE_NORMAL, class=class="str">"cmt">// By tab header width CANV_ELEMENT_TAB_SIZE_MODE_FIXED, class=class="str">"cmt">// Fixed size CANV_ELEMENT_TAB_SIZE_MODE_FILL, class=class="str">"cmt">// By TabControl width }; enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = class="num">0, class=class="str">"cmt">// Element ID CANV_ELEMENT_PROP_TYPE, class=class="str">"cmt">// Graphical element type CANV_ELEMENT_PROP_TAB_MULTILINE, class=class="str">"cmt">// Several lines of tabs in TabControl CANV_ELEMENT_PROP_TAB_ALIGNMENT, class=class="str">"cmt">// Location of tabs inside the control CANV_ELEMENT_PROP_TAB_SIZE_MODE, class=class="str">"cmt">// Tab size setting mode CANV_ELEMENT_PROP_TAB_PAGE_NUMBER, class=class="str">"cmt">// Tab index number CANV_ELEMENT_PROP_ALIGNMENT, class=class="str">"cmt">// Location of an object inside the control }; class="macro">#define CANV_ELEMENT_PROP_INTEGER_TOTAL(class="num">90) class="macro">#define CANV_ELEMENT_PROP_INTEGER_SKIP(class="num">0) class="macro">#define FIRST_CANV_ELEMENT_DBL_PROP(CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) 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_MULTILINE, class=class="str">"cmt">// Sort by the flag of several rows of tabs in TabControl SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT, class=class="str">"cmt">// Sort by the location of tabs inside the control };
◍ 画布控件枚举收尾:排序与定位常量
上面这段枚举收口了 Canvas 图形库里两类常被忽略的常量:元素排序键,以及界面文案/锚点定位宏。排序键里 SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE 与 SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER 控制标签控件按页宽模式或页码索引排列,SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP 则标记字符串类属性的起点,其后 NAME_RES、TEXT、DESCRIPTION 都走字符串比较。 定位宏部分,MSG_LIB_TEXT_TOP/BOTTOM/LEFT/RIGHT 定义基础方位,MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL/FILL/FIXED 三种页签宽度策略差异明显:NORMAL 按页头实际宽,FILL 拉满 TabControl 宽,FIXED 锁死尺寸。锚点常量 MSG_LIB_TEXT_CORNER_LEFT_UPPER 等四个,直接决定坐标零点落在图表哪个角。 报错类常量如 MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON 暴露了 CButtonListBox 的坑:给指定索引按钮设分组或 Toggle 标志失败时,会抛这两类错误码。开 MT5 在 Include/Canvas 头文件里搜这些宏,改一个 TAB_SIZE_MODE 渲染出来就能看出宽度行为变化。
SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE, class=class="str">"cmt">// Sort by the mode of setting the tab size SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER, class=class="str">"cmt">// Sort by the tab index number 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_LIB_TEXT_TOP, class=class="str">"cmt">// Top MSG_LIB_TEXT_BOTTOM, class=class="str">"cmt">// Bottom MSG_LIB_TEXT_LEFT, class=class="str">"cmt">// Left MSG_LIB_TEXT_RIGHT, class=class="str">"cmt">// Right MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL, class=class="str">"cmt">// By tab header width MSG_LIB_TEXT_TAB_SIZE_MODE_FILL, class=class="str">"cmt">// By TabControl width MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED, class=class="str">"cmt">// Fixed size MSG_LIB_TEXT_CORNER_LEFT_UPPER, class=class="str">"cmt">// Center of coordinates at the upper left corner of the chart MSG_LIB_TEXT_CORNER_LEFT_LOWER, class=class="str">"cmt">// Center of coordinates at the lower left corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_LOWER, class=class="str">"cmt">// Center of coordinates at the lower right corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_UPPER, class=class="str">"cmt">// Center of coordinates at the upper right corner of the chart 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_FIELD, class=class="str">"cmt">// Tab field 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 class=class="str">"cmt">//--- CButtonListBox MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON, class=class="str">"cmt">// Failed to set the group for the button with the index MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON, class=class="str">"cmt">// Failed to set the Toggle flag to the button with the index