DoEasy. 控件 (第 9 部分): 重新编排 WinForms 对象方法、RadioButton 和 Button 控件·综合运用
(3/3)·从零散类变量到全对象通用属性表,这篇把 WinForms 控件交互地基一次打牢
- 画布控件的自动排版与文本属性枚举
- 界面控件的本地化字符串映射
- GUI 控件属性的俄英双语映射表
- 控件自适配与边距属性的字符串映射
- 图形元素类型的字符串映射
- 控件背景与坐标偏移的内部结构
- 图形对象的结构体字段拆解
- GUI 控件基类的底层字段拆解
- 控件边框与自适应字段的参数定义
- 控件边距与勾选框的结构字段
- 复选框控件的配色与对象属性字段
- Canvas 控件里的颜色与坐标成员怎么排
- 画布元素的父子关系与创建参数
- 控件激活区偏移与背景色接口
- 鼠标交互下的画布背景着色接口
- 控件背景色的取值与索引越界兜底
- 悬停底色与边缘坐标的取数逻辑
- 图形元素的构造与底层字段
- 画布元素的初始化与属性落地
- 画布控件的初始属性落位
- 把控件边框和悬停态压成零
- 勾选控件的边距与状态初始化
- 把画布控件属性落进结构体
- 把画布控件属性一次性读进结构体
- 控件外观与自适应属性的结构体回填
- 控件边距与勾选框属性的批量读取
- 勾选框配色与结构体落盘的细节
- 给画布控件一次性灌入布局属性
- 控件属性批量落地的那段初始化
- 控件布局与勾选属性的批量注入
- 勾选框控件的四态配色与文本属性落地
- 画布标签的透明度与底色注入
- 画布阴影对象的初始化与数组同步
- 自定义窗体类的内部状态字段
- 图形元素的创建与交互对象挂载
- 交互对象列表与坐标偏移的接口封装
- 表单边框与对象指针的取用接口
- 控件容器类的元素与边框接口
- 表单边框交互色与初始化细节
- 给画布挂子元素的工厂方法
- 在表单里造一个绑定图元
- 绑定元素的相对坐标与创建落点
- 表单样式与配色主题的接口拆解
- 窗体边框样式与交互对象检索
- GUI 对象的交互列表与边框参数封装
- 控件边框与内边距的读写接口
- 控件内边距的下限被边框尺寸卡死
- 内边距与边框宽度的取值约束
- 擦除逻辑与内边距取值的实作细节
- 从控件属性接口摸到窗体基类构造
- 窗体初始化与字体属性枚举的底层写法
- 控件属性描述函数的分支写法
- 画布元素属性的字符串拼装逻辑
- 画布控件位移与交互属性的字符串拼装
- 画布元素属性枚举的链式取值逻辑
- 画布控件背景与边框属性的文本化拼装
- 画布元素的边框与自适应属性读法
- 画布控件的自动滚动与停靠属性读取
- 画布控件属性的字符串化分支
- 控件属性描述与锚定模式的取数逻辑
- 控件基类的初始化与状态描述实现
- 控件擦除与默认外观的底层设定
- 标签控件的重绘与对齐逻辑
- 图形标签的九宫格锚点定位逻辑
- 标签控件的锚点与自适应尺寸实现
- 复选框控件的自动尺寸与坐标布局
- 勾选控件的尺寸与状态同步逻辑
- 勾选框状态与配色的接口拆解
- 勾选框边框与标记的颜色接口
- 勾选框控件的颜色状态与重绘逻辑
- 勾选框状态绘制的底层坐标逻辑
- 复选框尺寸的自适应测算逻辑
- 把复选框改成圆形单选钮
- 单选按钮的构造与绘制逻辑
- 循环出口的断点写法
- 从标签派生按钮控件的实现要点
- 按钮类的自适应尺寸与构造细节
- 按钮对象的构造与重绘逻辑
- GUI 控件的自适配尺寸逻辑
- Dock 面板工作区尺寸的计算逻辑
- 工作区坐标与边框宽度的取法
- 边框尺寸变动时内边距的自动兜底
- 图形容器里新建元素的参数与拦截逻辑
- 按控件类型分流着色与自适应尺寸
- 容器按内容自适应尺寸的边界处理
- GroupBox 初始化与边框绘制逻辑
- 分组框边框样式的绘制分支
- 图形元素工厂的分发逻辑
- GUI 控件工厂与内边距联动
- 面板底层坐标与图形对象的实例化
- GUI 元素工厂的 switch 分发与底板坐标对齐
- 面板与表单的位移联动逻辑
- 捕获画布表单内的鼠标交互对象
- 从图形元素集合里捞到命中的表单对象
- 从扩展图形对象里捞光标所在的表单
- 捕获图表上的滚轮事件
- 把控件塞进面板跑一遍
- 在面板里堆叠两个绑定子面板
- 奇偶索引下的标签着色与GroupBox挂接
- 在面板里挂勾选框和单选钮
- 在面板里挂一个可响应滚轮的按钮
- 后续控件开发往哪走
画布控件的自动排版与文本属性枚举
在 MT5 的 Canvas 界面框架里,控件元素有一组专管「自适应尺寸」和「边距/内边距」的消息常量,用来描述元素如何随内容伸缩、以及和相邻控件怎么留白。 MSG_CANV_ELEMENT_PROP_AUTOSIZE_MODE 控制元素按内容自动改尺寸;MSG_CANV_ELEMENT_PROP_AUTOSCROLL 与 IS_AUTOSCROLL 决定滚动条是否自动出现,后者表示滚动条随内容溢出自动显示。AUTOSCROLL_MARGIN_W / H 则限定自动滚动时元素内部可视区域的宽高。 DOCK_MODE 是把控件边框绑定到容器的某侧;MARGIN_TOP/BOTTOM/LEFT/RIGHT 是「控件之间」的外边距,PADDING_* 系列是控件「内部」的边距,TEXT_ALIGN 与 CHECK_ALIGN 分别管文本和勾选框在边界内的位置。 真实属性之外还有字符串类属性:NAME_OBJ 是图形对象名,NAME_RES 是资源名,TEXT 就是元素上显示的文本。下面这段枚举片段给出了月份与字体样式的俄英对照,其中 "Отсутствует"->"Enpty" 是原常量表的拼写(应是 Empty 的笔误),开 MT5 头文件时能直接核对到。
MSG_CANV_ELEMENT_PROP_AUTOSIZE_MODE, class=class="str">"cmt">// Mode of the element auto resizing depending on the content MSG_CANV_ELEMENT_PROP_AUTOSCROLL, class=class="str">"cmt">// Auto scrollbar flag MSG_CANV_ELEMENT_PROP_IS_AUTOSCROLL, class=class="str">"cmt">// Scrollbar appears automatically MSG_CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W, class=class="str">"cmt">// Width of the field inside the element during auto scrolling MSG_CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H, class=class="str">"cmt">// Height of the field inside the element during auto scrolling MSG_CANV_ELEMENT_PROP_DOCK_MODE, class=class="str">"cmt">// Mode of binding control borders to the container MSG_CANV_ELEMENT_PROP_MARGIN_TOP, class=class="str">"cmt">// Top margin between the fields of this and another control MSG_CANV_ELEMENT_PROP_MARGIN_BOTTOM, class=class="str">"cmt">// Bottom margin between the fields of this and another control MSG_CANV_ELEMENT_PROP_MARGIN_LEFT, class=class="str">"cmt">// Left margin between the fields of this and another control MSG_CANV_ELEMENT_PROP_MARGIN_RIGHT, class=class="str">"cmt">// Right margin between the fields of this and another control MSG_CANV_ELEMENT_PROP_PADDING_TOP, class=class="str">"cmt">// Top margin inside the control MSG_CANV_ELEMENT_PROP_PADDING_BOTTOM, class=class="str">"cmt">// Bottom margin inside the control MSG_CANV_ELEMENT_PROP_PADDING_LEFT, class=class="str">"cmt">// Left margin inside the control MSG_CANV_ELEMENT_PROP_PADDING_RIGHT, class=class="str">"cmt">// Right margin inside the control MSG_CANV_ELEMENT_PROP_TEXT_ALIGN, class=class="str">"cmt">// Text position within text label boundaries MSG_CANV_ELEMENT_PROP_CHECK_ALIGN, class=class="str">"cmt">// Position of the checkbox within control borders MSG_CANV_ELEMENT_PROP_CHECKED, class=class="str">"cmt">// Control checkbox status MSG_CANV_ELEMENT_PROP_CHECK_STATE, class=class="str">"cmt">// Status of a control having a checkbox MSG_CANV_ELEMENT_PROP_AUTOCHECK, class=class="str">"cmt">// Auto change flag status when it is selected class=class="str">"cmt">//--- Real properties of graphical elements 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 }; {"Ноябрь","November"}, {"Декабрь","December"}, {"Курсив","Italic"}, {"Подчёркивание","Underline"}, {"Перечёркивание","Strikeout"}, {"Обычный","Normal"}, {"Отсутствует","Enpty"}
「界面控件的本地化字符串映射」
在 MT5 的 WinForms 风格面板库里,所有界面元素的显示文本都靠一组俄英双语言字符串表驱动。上面这段初始化列表覆盖了边框样式(Simple / Flat / Bevel / Stamp)、对齐方式(Left / Centered)、形变(Prominent / Concave)等基础外观参数。 绿色高亮部分是容器与标准控件的分类入口:WinForms Containers 含 GroupBox、Panel;Standard 控件则列出 Label、CheckBox、RadioButton、Button。这意味着你若在 EA 里自建面板,直接复用这些枚举文本就能让俄语客户端自动显示本地语言。 末尾注释 //--- Integer properties of graphical elements 给出三个整型属性名:Element ID、Graphic element type、Graphic element belong。实盘加载自定义面板时,若报错『object must be of type WinForms Base』,优先检查你 new 出来的控件是否继承自该基类,而不是裸建 CPanel。 这类字符串表本身不产生信号,但决定了你盯盘面板的可读性与调试效率。外汇与贵金属波动剧烈,面板逻辑错误可能让你漏看关键价位,建议先在策略测试器里跑通 UI 再上真仓。
{"Простая","Simple"},
{"Плоская","Flat"},
{"Рельефная выпуклая","Bevel"},
{"Рельефная вдавленная","Stamp"},
{"Выравнивание по левой границе","Left alignment"},
{"Выравнивание по центру","Centered"},
{"Выпуклый вид","Prominent form"},
{"Вогнутый вид","Concave form"},
{"Только увеличение","Grow"},
{"Увеличение и уменьшение","Grow and Shrink"},
{"Прикреплён к указанным координатам, размеры не меняются","Attached to specified coordinates, size does not change"},
{"Присоединение сверху и растягивание на ширину контейнера","Attached to the top and stretched to the container width"},
{"Присоединение снизу и растягивание на ширину контейнера","Attached to the bottom and stretch to the width of the container"},
{"Присоединение слева и растягивание на высоту контейнера","Attached to the left and stretched to the height of the container"},
{"Присоединение справа и растягивание на высоту контейнера","Attached to the right and stretched to the height of the container"},
{"Растягивание на ширину и высоту всего контейнера","Stretching to the width and height of the entire container"},
{"Не установлен","Unchecked"},
{"Установлен","Checked"},
{"Неопределённый","Indeterminate"},
{"Воскресение","Sunday"},
{"Понедельник","Monday"},
{"Подложка объекта-элемента управления WinForms \"Панель\"","Underlay object-control WinForms \"Panel\""},
{"Базовый элемент управления WinForms","Base WinForms control"},
class=class="str">"cmt">//--- WinForms containers
{"Базовый элемент управления WinForms-контейнер","Basic Control WinForms Container"},
{"Элемент управления GroupBox","Control element \"GroupBox\""},
{"Элемент управления \"Panel\"","Control element \"Panel\""},
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\""},
{"Графический объект принадлежит программе","The graphic object belongs to the program"},
{"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
class=class="str">"cmt">//--- CPanel
{"Не удалось создать объект-подложку","Failed to create underlay object"},
{"Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником","Error. The object being created must be of type WinForms Base or be derived from it"},
class=class="str">"cmt">//--- Integer properties of graphical elements
{"Идентификатор элемента","Element ID"},
{"Тип графического элемента","Graphic element type"},
{"Принадлежность графического элемента","Graphic element belong"}◍ GUI 控件属性的俄英双语映射表
在 MT5 自定义指标或 EA 的界面开发里,控件(CControl 派生类)有一组以俄英双字符串形式登记的标准属性。上面这段就是典型的属性描述表:每一行前半是俄文键名,后半是英文含义,编译时作为 CListItem 类数组的初始化数据被载入。
表里覆盖了控件几何、交互状态与外观三块。几何类含列表序号、图表 X/Y 坐标、宽高、右/下边界、四向 active area 缩进;交互类含移动标志、激活标志、外部环境交互标志、鼠标点击/悬停时的背景与边框变色;外观类含默认文字色及不透明度、背景色及不透明度、字体粗细、四向边框尺寸与颜色、内容自适应开关。
实际写面板时,你可以直接把这段双语言数组拷进 CList 的 AddItem 循环,用 ::StringFind 按英文子串检索属性,避免手敲俄文键名拼错。外汇与贵金属图表上挂这类 GUI 属于高风险操作,界面逻辑错误可能误导下单,建议在策略测试器里先验证控件响应。
{"Номер элемента в списке","The number of the element in the list"},
{"X-координата элемента на графике","X-coordinate of the element on the chart"},
{"Y-координата элемента на графике","Y-coordinate of the element on the chart"},
{"Ширина элемента","Element Width"},
{"Высота элемента","Element Height"},
{"Правая граница элемента","Element&class="macro">#x27;s right border"},
{"Нижняя граница элемента","Element&class="macro">#x27;s bottom border"},
{"Отступ активной зоны от левого края элемента","Active area indent from the left edge of the element"},
{"Отступ активной зоны от верхнего края элемента","Active area indent from the top edge of the element"},
{"Отступ активной зоны от правого края элемента","Active area indent from the right edge of the element"},
{"Отступ активной зоны от нижнего края элемента","Active area indent from the bottom edge of the element"},
{"Флаг перемещаемости элемента","Element mobility flag"},
{"Элемент перемещаемый","Element can be moved"},
{"Флаг активности элемента","Element activity flag"},
{"Элемент активен","Element active"},
{"Флаг взаимодействия элемента со внешней средой","Flag of the interaction of the element with the external environment"},
{"X-координата активной зоны элемента","X-coordinate of the element&class="macro">#x27;s active area"},
{"Y-координата активной зоны элемента","Y-coordinate of the element&class="macro">#x27;s active area"},
{"Правая граница активной зоны элемента","Right border of the element&class="macro">#x27;s active area"},
{"Нижняя граница активной зоны элемента","Bottom border of the element&class="macro">#x27;s active area"},
{"Флаг доступности элемента","Element Availability Flag"},
{"Цвет текста по умолчанию для всех объектов элемента управления","Default text class="type">color for all objects in the control"},
{"Непрозрачность цвета текста по умолчанию для всех объектов элемента управления","Default text class="type">color opacity for all objects in the control"},
{"Цвет фона элемента управления","Background class="type">color of the control"},
{"Непрозрачность цвета фона элемента управления","Opacity of the control&class="macro">#x27;s background class="type">color"},
{"Цвет фона элемента управления при нажатии мышки на элемент управления","Background class="type">color of the control when the mouse is clicked on the control"},
{"Цвет фона элемента управления при наведении мышки на элемент управления","Background class="type">color of the control when hovering the mouse over the control"},
{"Тип толщины шрифта","Font weight type"},
{"Стиль рамки элемента управления","Control&class="macro">#x27;s border style"},
{"Размер рамки элемента управления сверху","Control&class="macro">#x27;s border size on the top"},
{"Размер рамки элемента управления снизу","Control&class="macro">#x27;s border size on the bottom"},
{"Размер рамки элемента управления слева","Control&class="macro">#x27;s border size on the left"},
{"Размер рамки элемента управления справа","Control&class="macro">#x27;s border size on the right"},
{"Цвет рамки элемента управления","Control&class="macro">#x27;s border class="type">color"},
{"Цвет рамки элемента управления при нажатии мышки на элемент управления","Border class="type">color of the control when the mouse is clicked on the control"},
{"Цвет рамки элемента управления при наведении мышки на элемент управления","Border class="type">color of the control when hovering the mouse over the control"},
{"Флаг автоматического изменения размера элемента управления под содержимое","Automatically resize a control to fit its content"},
{"Элемент автоматически измененяет размер под содержимое","Element automatically resizes to fit the content"}控件自适配与边距属性的字符串映射
在 MT5 自定义图形界面库里,一组俄英双语文符串常量承担了控件属性的可读化描述。上面这段初始化列表覆盖了自动缩放、滚动条自显、四周边距(上下左右外间距与内间距)、文本与复选框位置、复选状态等至少 19 个属性键,说明 GUI 框架把布局逻辑抽象成了可枚举的字符串资源。 注意「Scrollbar auto-appear flag」与「Margin width around element when auto scrolling」这对:开启自动滚动后,控件外缘会按设定像素留白,否则滚动条弹出时可能吃掉内容区。实盘面板若叠了多个 Label,左右间距设成 0 会让文本黏连,建议开窗后到 OBJ_LABEL 属性里核对默认值。 下方 CGBaseObj::TypeElementDescription 用三元嵌套返回图形元素类型描述,依赖 CMessage::Text 查表。GRAPH_ELEMENT_TYPE_STANDARD 到 _FORM 共 5 个分支,意味着类型判定走的是穷举而非位运算——改源码加类型时漏掉一行就会落空返回。
{"Режим автоматического изменения размера элемента управления под содержимое","Mode for automatically resizing a control to fit its content"},
{"Флаг автоматического появления полосы прокрутки","Scrollbar auto-appear flag"},
{"Полоса прокрутки автоматически появляется","Scroll bar automatically appears"},
{"Ширина поля вокруг элемента при автоматической прокрутке","Margin width around element when auto scrolling"},
{"Высота поля вокруг элемента при автоматической прокрутке","Height of margin around element when auto scrolling"},
{"Режим привязки границ элемента управления к контейнеру","Binding mode of the control&class="macro">#x27;s borders to the container"},
{"Промежуток сверху между полями данного и другого элемента управления","Top spacing between the margins of this control and another control"},
{"Промежуток снизу между полями данного и другого элемента управления","Bottom spacing between the margins of this control and another control"},
{"Промежуток слева между полями данного и другого элемента управления","Left spacing between the margins of this control and another control"},
{"Промежуток справа между полями данного и другого элемента управления","Right spacing between the margins of this control and another control"},
{"Промежуток сверху внутри элемента управления","Top spacing inside a control"},
{"Промежуток снизу внутри элемента управления","Bottom spacing inside a control"},
{"Промежуток слева внутри элемента управления","Left spacing inside a control"},
{"Промежуток справа внутри элемента управления","Right spacing inside a control"},
{"Положение текста в границах текстовой метки","Text position within text label bounds"},
{"Положение флажка проверки в границах элемента управления","The position of the checkbox within the control&class="macro">#x27;s bounds"},
{"Состояние флажка проверки элемента управления","Checkbox state of the control"},
{"Состояние элемента управления, имеющего флажок проверки","The state of a control that has a checkbox"},
{"Автоматическое изменение состояния флажка при его выборе","Automatically change the state of the checkbox when it is selected"},
class=class="str">"cmt">//--- String properties of graphical elements
{"Имя объекта-графического элемента","The name of the graphic element object"},
{"Имя графического ресурса","Image resource name"},
{"Текст графического элемента","Text of the graphic element"}
};
class=class="str">"cmt">//+---------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the description of the graphical element type |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CGBaseObj::TypeElementDescription(class="type">void)
{
class="kw">return
(
this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) :
this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) :
this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) :
this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) :
this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) :「图形元素类型的字符串映射」
在 MQL5 自定义图形库里,把枚举类型的图元代号转成可读文本,靠的是一串嵌套三元表达式。下面这段直接拿 this.TypeGraphElement() 的返回值做分支判断,命中哪个枚举就调 CMessage::Text() 取对应多语言文案,全都不命中则返回 "Unknown"。 被高亮的那一组 GRAPH_ELEMENT_TYPE_WF_* 枚举,覆盖的是工作区(WF)系控件:容器、分组框、面板、通用基底、标签、勾选框、单选钮、按钮。实际调试时若面板只显示 Unknown,优先排查 this 指向的对象是否没被正确归入 WF 分组,而不是文案表缺失。 配套的 CGCnvElement 类继承了 CGBaseObj,内部挂了三个关键指针与一个 CCanvas 实例:m_element_main 指向跨所有绑定组的初始父元素,m_element_base 指向当前组内的直接父元素,m_canvas 负责离线绘制,m_pause 管刷新节流。外汇与贵金属图表上挂这类自绘控件,需注意 MT5 在高波动行情可能触发重绘阻塞,属高风险界面层操作。
this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : class=class="str">"cmt">//--- this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : "Unknown" ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class of the graphical element object | class=class="str">"cmt">//+------------------------------------------------------------------+ 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
◍ 控件背景与坐标偏移的内部结构
在 MT5 自定义图形库里,一个可交互控件的状态色与定位往往藏在类的私有段。下面这段声明给出了背景色数组、悬停/点击色数组以及相对基准对象的偏移量,直接决定了控件在图表上的视觉反馈与摆放逻辑。 bool m_shadow; // 是否绘制阴影 color m_chart_color_bg; // 图表背景色 uint m_duplicate_res[]; // 资源数据副本数组 color m_array_colors_bg[]; // 各元素背景色数组 color m_array_colors_bg_dwn[]; // 鼠标点击时控件背景色数组 color m_array_colors_bg_ovr[]; // 鼠标悬停时控件背景色数组 bool m_gradient_v; // 垂直渐变填充标志 bool m_gradient_c; // 循环渐变填充标志 int m_init_relative_x; // 初始相对 X 坐标 int m_init_relative_y; // 初始相对 Y 坐标 私有段里还有两个容易忽略的字段:m_shift_coord_x 与 m_shift_coord_y,它们是相对基准对象的 X/Y 偏移量。配合内嵌的 SData 结构(含 id 与 type 两个整型属性),就能在 ObjectToStruct / StructToObject 两个虚函数里完成对象与结构体的互转。 开 MT5 接一套自己的面板时,先确认 m_array_colors_bg_ovr 是否被赋值——若留空,悬停反馈可能直接沿用背景色,肉眼几乎分辨不出控件边界。外汇与贵金属图表挂 EA 做界面,杠杆与滑点风险并存,改色逻辑前建议在策略测试器里先跑一遍。
class="type">bool m_shadow; class=class="str">"cmt">// Shadow presence class="type">color m_chart_color_bg; class=class="str">"cmt">// Chart background class="type">color class="type">uint m_duplicate_res[]; class=class="str">"cmt">// Array for storing resource data copy class="type">color m_array_colors_bg[]; class=class="str">"cmt">// Array of element background colors class="type">color m_array_colors_bg_dwn[]; class=class="str">"cmt">// Array of control background colors when clicking on the control class="type">color m_array_colors_bg_ovr[]; class=class="str">"cmt">// Array of control background colors when hovering the mouse over the control class="type">bool m_gradient_v; class=class="str">"cmt">// Vertical gradient filling flag 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="kw">private: class="type">int m_shift_coord_x; class=class="str">"cmt">// Offset of the X coordinate relative to the base object class="type">int m_shift_coord_y; class=class="str">"cmt">// Offset of the Y coordinate relative to the base object class="kw">struct SData { class=class="str">"cmt">//--- Object integer properties class="type">int id; class=class="str">"cmt">// Element ID class="type">int type; class=class="str">"cmt">// Graphical element type };
图形对象的结构体字段拆解
在 MT5 自定义控件开发里,图形元素通常用结构体统一管理。下面这段声明覆盖了从归属关系到可点击热区的全部基础属性,直接决定了你在图表上拖拽、命中测试时的行为边界。 外汇与贵金属图表的高波动特性下,坐标类字段(coord_x/coord_y)若用绝对像素而非相对坐标,缩放窗口时控件可能漂移,属于高风险适配坑。 逐行看关键字段:belong 标记元素从属的父容器索引;number 是自身在列表中的序号;chart_id 与 subwindow 锁定所在图表及子窗口,避免多图表误触。 width/height 与 edge_right/edge_bottom 构成包围盒,而 act_shift_left 到 act_shift_bottom 四个偏移量才真正划定「鼠标算不算点中」的活跃区,比可见边框更内缩或外扩都由它们控制。 movable 为 bool 开关,置 false 时即使坐标在屏内也无法拖拽,适合做固定信息面板。开 MT5 新建一个 struct 把这些字段抄进去,编译后挂到 EURUSD 的 M15 图就能看到默认零值下的占位表现。
class="type">int belong; class=class="str">"cmt">// Graphical element affiliation class="type">int number; class=class="str">"cmt">// Element index in the list class="type">long chart_id; class=class="str">"cmt">// Chart ID class="type">int subwindow; class=class="str">"cmt">// Chart subwindow index class="type">int coord_x; class=class="str">"cmt">// Element X coordinate on the chart class="type">int coord_y; class=class="str">"cmt">// Element Y coordinate on the chart class="type">int width; class=class="str">"cmt">// Element width class="type">int height; class=class="str">"cmt">// Element height class="type">int edge_right; class=class="str">"cmt">// Element right border class="type">int edge_bottom; class=class="str">"cmt">// Element bottom border class="type">int act_shift_left; class=class="str">"cmt">// Active area offset from the left edge of the element class="type">int act_shift_top; class=class="str">"cmt">// Active area offset from the top edge of the element class="type">int act_shift_right; class=class="str">"cmt">// Active area offset from the right edge of the element class="type">int act_shift_bottom; class=class="str">"cmt">// Active area offset from the bottom edge of the element class="type">bool movable; class=class="str">"cmt">// Element moveability flag
「GUI 控件基类的底层字段拆解」
在 MT5 自定义面板开发里,所有图形控件都从一个基类结构体派生,上面这段声明列出了它最容易被忽略的一批成员。 bool active 标记元素是否处于激活态,interaction 则表示该元素能否响应外部环境事件;二者分开设计,意味着一个控件可以“可见但暂不接管鼠标消息”。 coord_act_x / coord_act_y 与 coord_act_right / coord_act_bottom 定义了控件可点击区域的左上和右下坐标,单位是像素而非价格。zorder 是 long 型,决定图表上多个对象谁优先吃到点击事件——数值大的压在上层。 fore_color 配 uchar 型的 fore_color_opacity(0–255)控制文字色与透明度;background_color 系列里除了常规背景色,还单独给了 mouse_down 和 mouse_over 两种状态色,做成交互反馈时不用自己判断重绘。 enabled 和 bold_type 收尾:前者管控件整体可用性,后者用 int 指定字体粗细(如 FW_NORMAL=400、FW_BOLD=700)。开 MT5 把这段结构粘进 includes 里的 CElement 派生类,改 background_color_mouse_over 就能直接看到悬停变色效果。
class="type">bool active; class=class="str">"cmt">// Element activity flag class="type">bool interaction; class=class="str">"cmt">// Flag of interaction with the outside environment class="type">int coord_act_x; class=class="str">"cmt">// X coordinate of the element active area class="type">int coord_act_y; class=class="str">"cmt">// Y coordinate of the element active area class="type">int coord_act_right; class=class="str">"cmt">// Right border of the element active area class="type">int coord_act_bottom; class=class="str">"cmt">// Bottom border of the element active area class="type">long zorder; class=class="str">"cmt">// Priority of a graphical object for receiving the event of clicking on a chart class="type">bool enabled; class=class="str">"cmt">// Element availability flag class="type">color fore_color; class=class="str">"cmt">// Default text class="type">color for all control objects class="type">uchar fore_color_opacity; class=class="str">"cmt">// Default text class="type">color opacity for all control objects class="type">color background_color; class=class="str">"cmt">// Control background class="type">color class="type">uchar background_color_opacity; class=class="str">"cmt">// Non-transparency of control background class="type">color class="type">color background_color_mouse_down; class=class="str">"cmt">// Control background class="type">color when clicking on the control class="type">color background_color_mouse_over; class=class="str">"cmt">// Control background class="type">color when hovering the mouse over the control class="type">int bold_type; class=class="str">"cmt">// Font width type
◍ 控件边框与自适应字段的参数定义
在 MT5 自定义控件结构体里,边框与自适应相关字段直接决定图形元素在图表上的视觉边界和交互反馈。下面这段声明来自标准控件描述结构,把边框样式、四边尺寸、三种状态下的颜色,以及内容驱动的尺寸/滚动逻辑一次性列清。 int border_style; // Control frame style int border_size_top; // Control frame top size int border_size_bottom; // Control frame bottom size int border_size_left; // Control frame left size int border_size_right; // Control frame right size color border_color; // Control frame color color border_color_mouse_down; // Control frame color when clicking on the control color border_color_mouse_over; // Control frame color when hovering the mouse over the control bool autosize; // Flag of the element auto resizing depending on the content int autosize_mode; // Mode of the element auto resizing depending on the content bool autoscroll; // Auto scrollbar flag int autoscroll_margin_w; // Width of the field inside the element during auto scrolling int autoscroll_margin_h; // Height of the field inside the element during auto scrolling int dock_mode; // Mode of binding control borders to the container 逐行拆解:border_style 控制边框线型(如平直/凹陷),四个 border_size_* 分别设定上、下、左、右像素厚度,均为 int。border_color 是常态颜色;border_color_mouse_down 在鼠标按下时替换边框色,border_color_mouse_over 在悬停时替换,用于做交互高亮。 autosize 为 true 时控件按内容自动改尺寸,autosize_mode 决定拉伸方向优先级。autoscroll 开启后内部超界出滚动条,autoscroll_margin_w / _h 定义自动滚动时保留的可视区宽高边距(像素)。dock_mode 则是控件边与父容器绑定的停靠方式。 实盘做贵金属面板时,把 border_size_left 设成 2、border_size_right 设成 8,配合 border_color_mouse_over=C'255,180,0' 能在 EURUSD 或 XAUUSD 的 H1 上让点击区更醒目。外汇与贵金属波动剧烈、杠杆风险高,参数只影响界面交互,不预示任何价格方向。
class="type">int border_style; class=class="str">"cmt">// Control frame style class="type">int border_size_top; class=class="str">"cmt">// Control frame top size class="type">int border_size_bottom; class=class="str">"cmt">// Control frame bottom size class="type">int border_size_left; class=class="str">"cmt">// Control frame left size class="type">int border_size_right; class=class="str">"cmt">// Control frame right size class="type">color border_color; class=class="str">"cmt">// Control frame class="type">color class="type">color border_color_mouse_down; class=class="str">"cmt">// Control frame class="type">color when clicking on the control class="type">color border_color_mouse_over; class=class="str">"cmt">// Control frame class="type">color when hovering the mouse over the control class="type">bool autosize; class=class="str">"cmt">// Flag of the element auto resizing depending on the content class="type">int autosize_mode; class=class="str">"cmt">// Mode of the element auto resizing depending on the content class="type">bool autoscroll; class=class="str">"cmt">// Auto scrollbar flag class="type">int autoscroll_margin_w; class=class="str">"cmt">// Width of the field inside the element during auto scrolling class="type">int autoscroll_margin_h; class=class="str">"cmt">// Height of the field inside the element during auto scrolling class="type">int dock_mode; class=class="str">"cmt">// Mode of binding control borders to the container
控件边距与勾选框的结构字段
在 MT5 自定义面板开发里,控件布局靠两组间距变量撑起来:margin_* 管的是当前控件和相邻控件之间的外边距,padding_* 管的是控件边框到内部内容的留白。八个整型字段把上下左右拆开写,调界面时经常要单独改某一个方向而不是整体缩放。 下面这段结构声明里,前八个是通用边距,后面高亮部分是带勾选框控件特有的字段。text_align 和 check_align 决定文字和勾选框在控件内的锚点位置,checked 与 check_state 记录当前是否选中以及状态机值,autocheck 控制点击时是否自动翻转选中态,check_background_color 则是勾选框的底色。 实盘写 EA 面板时,如果勾选框点下去没反应,先查 autocheck 是不是被设成 false 了——默认不自动翻转就得自己写 OnClick 处理。外汇和贵金属行情跳动快,这类面板只负责交互,任何信号都只是概率倾向,别当成入场保证。
class="type">int margin_top; class=class="str">"cmt">// Top margin between the fields of this and another control class="type">int margin_bottom; class=class="str">"cmt">// Bottom margin between the fields of this and another control class="type">int margin_left; class=class="str">"cmt">// Left margin between the fields of this and another control class="type">int margin_right; class=class="str">"cmt">// Right margin between the fields of this and another control class="type">int padding_top; class=class="str">"cmt">// Top margin inside the control class="type">int padding_bottom; class=class="str">"cmt">// Bottom margin inside the control class="type">int padding_left; class=class="str">"cmt">// Left margin inside the control class="type">int padding_right; class=class="str">"cmt">// Right margin inside the control class="type">int text_align; class=class="str">"cmt">// Text position within text label boundaries class="type">int check_align; class=class="str">"cmt">// Position of the checkbox within control borders class="type">bool checked; class=class="str">"cmt">// Control checkbox status class="type">int check_state; class=class="str">"cmt">// Status of a control having a checkbox class="type">bool autocheck; class=class="str">"cmt">// Auto change flag status when it is selected class="type">color check_background_color; class=class="str">"cmt">// Color of control checkbox background
「复选框控件的配色与对象属性字段」
在自定义 GUI 的复选框结构里,背景、边框、对勾三组分各带常态 / 按下 / 悬停三态颜色,外加各自的 opacity 不透明度字段。比如 check_background_color 管背景常态,check_background_color_mouse_down 管鼠标按下时的背景色,check_fore_color 与 check_flag_color 同理分别对应边框与对勾图形。 这些字段全部声明为 color 类型,直接在结构体里赋值就能改控件观感;若想让某态半透明,就把对应 _opacity 字段设成 0~255 之间的值,255 为完全不透明。 对象侧还有两块字符串属性:name_obj[64] 存图形元素对象名,name_res[64] 存图形资源名,text[256] 存控件上显示的文本。数组上限说明对象名最长 63 字符、文本最长 255 字符,超长会在拷贝时被截断。 开 MT5 新建个结构体把这些字段抄进去,改几组 mouse_over 颜色挂到 EA 面板上,就能直观看到悬停反馈是否如预期。外汇与贵金属图表加载此类自定义控件时波动快,需注意重绘开销。
class="type">color check_background_color_opacity; class=class="str">"cmt">// Non-transparency of the control checkbox background class="type">color class="type">color check_background_color_mouse_down; class=class="str">"cmt">// Color of control checkbox background when clicking on the control class="type">color check_background_color_mouse_over; class=class="str">"cmt">// Color of control checkbox background when hovering the mouse over the control class="type">color check_fore_color; class=class="str">"cmt">// Color of control checkbox frame class="type">color check_fore_color_opacity; class=class="str">"cmt">// Non-transparency of the control checkbox frame class="type">color class="type">color check_fore_color_mouse_down; class=class="str">"cmt">// Color of control checkbox frame when clicking on the control class="type">color check_fore_color_mouse_over; class=class="str">"cmt">// Color of control checkbox frame when hovering the mouse over the control class="type">color check_flag_color; class=class="str">"cmt">// Color of control checkbox class="type">color check_flag_color_opacity; class=class="str">"cmt">// Non-transparency of the control checkbox class="type">color class="type">color check_flag_color_mouse_down; class=class="str">"cmt">// Color of control checkbox when clicking on the control class="type">color check_flag_color_mouse_over; class=class="str">"cmt">// Color of control checkbox when hovering the mouse over the control class=class="str">"cmt">//--- Object real properties class=class="str">"cmt">//--- Object class="type">class="kw">string properties class="type">uchar name_obj[class="num">64]; class=class="str">"cmt">// Graphical element object name class="type">uchar name_res[class="num">64]; class=class="str">"cmt">// Graphical resource name class="type">uchar text[class="num">256]; class=class="str">"cmt">// Graphical element text };
◍ Canvas 控件里的颜色与坐标成员怎么排
在 MT5 自定义图形控件类里,结构体成员决定了渲染层能拿到什么数据。下面这段声明把对象结构、字节数组、文本锚点和坐标都摊开了,其中带底色标记的两行(m_color_bg、m_opacity)是背景绘制与透明度控制的核心。 SData m_struct_obj; // 对象结构本体,承载控件所有基础数据 uchar m_uchar_array[]; // 对象结构的 uchar 字节数组,用于序列化或内存拷贝 ENUM_FRAME_ANCHOR m_text_anchor; // 当前文本对齐方式(锚点) int m_text_x; // 文本上一次绘制的 X 坐标 int m_text_y; // 文本上一次绘制的 Y 坐标 color m_color_bg; // 元素背景色 uchar m_opacity; // 元素不透明度(0~255) 属性索引函数用偏移量换数组下标:双精度属性减去 CANV_ELEMENT_PROP_INTEGER_TOTAL,字符串属性再减去双精度总数。这样同一套数组就能按枚举类型随机存取,不用写一堆 if-else。 颜色的持久化靠三个 Save 开头的方法:普通背景、鼠标按下背景、鼠标悬停背景,全部调 CopyArraysColors 把传入的 color 数组拷进对应成员数组。DFUN 作为参数源标识,方便以后追查哪次保存覆盖了背景色。
SData m_struct_obj; class=class="str">"cmt">// Object structure class="type">uchar m_uchar_array[]; class=class="str">"cmt">// class="type">uchar array of the object structure ENUM_FRAME_ANCHOR m_text_anchor; class=class="str">"cmt">// Current text alignment class="type">int m_text_x; class=class="str">"cmt">// Text last X coordinate class="type">int m_text_y; class=class="str">"cmt">// Text last Y coordinate class="type">color m_color_bg; class=class="str">"cmt">// Element background class="type">color class="type">uchar m_opacity; class=class="str">"cmt">// Element opacity 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=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) 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) const { class="kw">return(class="type">int)class="kw">property-CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_DOUBLE_TOTAL; } class=class="str">"cmt">//--- Copy the class="type">color array to the specified background class="type">color array class="type">void CopyArraysColors(class="type">color &array_dst[],const class="type">color &array_src[],const class="type">class="kw">string source); class=class="str">"cmt">//--- Save the colors to the background class="type">color array class="type">void SaveColorsBG(class="type">color &colors[]) { this.CopyArraysColors(this.m_array_colors_bg,colors,DFUN); } class="type">void SaveColorsBGMouseDown(class="type">color &colors[]) { this.CopyArraysColors(this.m_array_colors_bg_dwn,colors,DFUN); } class="type">void SaveColorsBGMouseOver(class="type">color &colors[]) { this.CopyArraysColors(this.m_array_colors_bg_ovr,colors,DFUN); } class="kw">public:
画布元素的父子关系与创建参数
在 MT5 自定义图形库里,CGCanvas 的 Create 方法负责在指定图表子窗口生成一块画布区域。它的入参决定了画布挂在哪里、画多大、以及半透明底色怎么填。 [CODE] bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool redraw=false); [/CODE] 逐行拆解:chart_id 是图表 ID,0 代表当前图表;wnd_num 是子窗口序号,0 为主图;name 为画布唯一标识名;x、y 是画布左上角相对于窗口的像素坐标;w、h 是宽高像素;colour 与 opacity 组合出半透明背景,opacity 取值 0~255,0 全透明、255 不透明;redraw 默认 false,即创建后不立即重绘。 SetMain 与 GetMain 用来把一组关联对象绑到同一个父元素上。IsMain 判断逻辑很直白:若 GetMain 返回 NULL,说明自身就是主元素;IsBase 同理,GetBase 为 NULL 即为基类元素。 实盘写面板时,opacity 设 180 左右能在 EURUSD 这类报价区压住网格线又不挡 K 线。外汇与贵金属杠杆高,这类 overlay 只辅助读图,信号失效概率始终存在,别当进出场唯一依据。
class="type">bool Create(const class="type">long chart_id, const class="type">int wnd_num, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool redraw=false); class="type">void SetMain(CGCnvElement *element) { this.m_element_main=element; } CGCnvElement *GetMain(class="type">void) { class="kw">return this.m_element_main; } class="type">bool IsMain(class="type">void) { class="kw">return this.GetMain()==NULL; } class="type">bool IsBase(class="type">void) { class="kw">return this.GetBase()==NULL; }
「控件激活区偏移与背景色接口」
在自定义 CCanvas 控件类里,GetCanvasObj() 直接返回内部 m_canvas 的指针,外部拿到后能接着画线画字,不必再走一层封装。 激活区(active area)边缘可用四个独立 shift 方法微调:左、右、上、下各传一个 int,内部统一套 fabs() 取绝对值后写入 CANV_ELEMENT_PROP_ACT_SHIFT_* 属性。这意味着你传 -5 和 5 效果一样,偏移量只认大小不认方向。 SetActiveAreaShift() 则是一次性设四个边,适合在鼠标悬停态切换时批量改热区。透明度由 SetOpacity(uchar, bool) 控制,第二参 redraw 默认 false,若你在定时器里连续改透明度,记得传 true 或手动 Redraw() 否则画面可能不刷新。 背景色有两个入口:SetBackgroundColor(color) 写单色并顺手存进 m_array_colors_bg[0];SetBackgroundColors(color &colors[]) 接收数组,把首元素作为主色属性、其余留作渐变或状态色。下面这段是原文里两个方法的骨架,逐行看一眼就能抄进自己的面板类。
CCanvas *GetCanvasObj(class="type">void) { class="kw">return &this.m_canvas; } class=class="str">"cmt">//--- (class="num">5) all shifts of the active area edges relative to the element, (class="num">6) opacity class="type">void SetActiveAreaLeftShift(const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value)); } class="type">void SetActiveAreaRightShift(const class="type">int value){ this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value)); } class="type">void SetActiveAreaTopShift(const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value)); } class="type">void SetActiveAreaBottomShift(const class="type">int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value)); } class="type">void SetActiveAreaShift(const class="type">int left_shift,const class="type">int bottom_shift,const class="type">int right_shift,const class="type">int top_shift); class="type">void SetOpacity(const class="type">uchar value,const class="type">bool redraw=false); class=class="str">"cmt">//--- Set the main background class="type">color class="type">void SetBackgroundColor(const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,colour); class="type">color arr[class="num">1]; arr[class="num">0]=colour; this.SaveColorsBG(arr); } class="type">void SetBackgroundColors(class="type">color &colors[]) { this.SaveColorsBG(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,this.m_array_colors_bg[class="num">0]); }
◍ 鼠标交互下的画布背景着色接口
在 MT5 自定义控件开发里,按下与悬停两种鼠标状态需要独立的背景反馈。下面四个方法分别处理单色与多色(渐变)赋值,核心都落在 CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN / MOUSE_OVER 这两个属性上。 SetBackgroundColorMouseDown 与 SetBackgroundColorsMouseDown 的差异在于:前者用单个 color 构造长度为 1 的数组后存盘,后者直接接收外部 color 引用数组,并把数组首元素写进属性。悬停态的 SetBackgroundColorMouseOver / SetBackgroundColorsMouseOver 逻辑完全对称,只是属性常量换成 MOUSE_OVER。 BackgroundColorsTotal 直接返回 m_array_colors_bg 的 Size(),也就是主背景渐变填色的总数。若你只调了 MouseDown 系列,这个值不会变——它统计的是主背景而非交互态,调试时别误读成『按下了几种颜色』。 开 MT5 新建一个 Canvas 控件类继承结构,把这几段粘进去,用 OnClick 和 OnMouseOver 事件触发,就能直观看到背景切换。外汇与贵金属图表挂这类自定义面板时,注意过度绘制会拖慢 tick 响应,属高风险定制项,建议先在模拟盘验证。
class="type">void SetBackgroundColorMouseDown(const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,colour); class="type">color arr[class="num">1]; arr[class="num">0]=colour; this.SaveColorsBGMouseDown(arr); } class="type">void SetBackgroundColorsMouseDown(class="type">color &colors[]) { this.SaveColorsBGMouseDown(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.m_array_colors_bg_dwn[class="num">0]); } class=class="str">"cmt">//--- Set the background class="type">color when hovering the mouse over control class="type">void SetBackgroundColorMouseOver(const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,colour); class="type">color arr[class="num">1]; arr[class="num">0]=colour; this.SaveColorsBGMouseOver(arr); } class="type">void SetBackgroundColorsMouseOver(class="type">color &colors[]) { this.SaveColorsBGMouseOver(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,this.m_array_colors_bg_ovr[class="num">0]); } class=class="str">"cmt">//--- Set(class="num">1) object movability, (class="num">2) activity, (class="num">3) interaction, class=class="str">"cmt">//--- Return the number of colors set for the gradient filling of the(class="num">1) main background, when clicking(class="num">2), (class="num">3) when hovering the mouse over the control class="type">uint BackgroundColorsTotal(class="type">void) const { class="kw">return this.m_array_colors_bg.Size(); }
控件背景色的取值与索引越界兜底
在自定义 Canvas 控件里,背景色不是单一值,而是分常态、鼠标按下、鼠标悬停三组。每一组都提供无参和带索引两种取色接口:无参读的是元素属性里的主色,带索引则从对应动态数组里按序号取。 以常态背景为例,BackgroundColor(uint index) 先取 m_array_colors_bg 的 Size 赋给 total。若 total 为 0,说明没预存序列色,直接回退到 BackgroundColor() 返回的主属性色;否则判断 index 是否越界,越界就取最后一个元素 total-1,不越界取 index 对应项。 鼠标按下的取色逻辑完全对称:BackgroundColorsMouseDownTotal() 返回 m_array_colors_bg_dwn 的长度,BackgroundColorMouseDown(uint index) 在数组为空时回退到 BackgroundColorMouseDown() 属性色,越界同样夹取到末位。悬停组的 m_array_colors_bg_ovr 也用同一套 Size + 回退结构。 开 MT5 建个 CCanvas 派生控件,把三组数组各 push 进 3 个 color,再故意传 index=5 调 BackgroundColor(5),观察是否稳定返回末位色而非报错——这能验证越界夹取是否真生效。
class="type">uint BackgroundColorsMouseDownTotal(class="type">void)const { class="kw">return this.m_array_colors_bg_dwn.Size(); } class="type">uint BackgroundColorsMouseOverTotal(class="type">void)const { class="kw">return this.m_array_colors_bg_ovr.Size(); } class=class="str">"cmt">//--- Return the main background class="type">color class="type">color BackgroundColor(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR); } class="type">color BackgroundColor(const class="type">uint index) const { class="type">uint total=this.m_array_colors_bg.Size(); if(total==class="num">0) class="kw">return this.BackgroundColor(); class="kw">return(index>total-class="num">1 ? this.m_array_colors_bg[total-class="num">1] : this.m_array_colors_bg[index]); } class=class="str">"cmt">//--- Return the background class="type">color when clicking on the control class="type">color BackgroundColorMouseDown(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN); } class="type">color BackgroundColorMouseDown(const class="type">uint index) const { class="type">uint total=this.m_array_colors_bg_dwn.Size(); if(total==class="num">0) class="kw">return this.BackgroundColorMouseDown(); class="kw">return(index>total-class="num">1 ? this.m_array_colors_bg_dwn[total-class="num">1] : this.m_array_colors_bg_dwn[index]); } class=class="str">"cmt">//--- Return the background class="type">color when hovering the mouse over the control
「悬停底色与边缘坐标的取数逻辑」
在自定义画布控件里,鼠标悬停背景色有两套取值入口:无参版本直接读 CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER 属性;带 index 的版本从 m_array_colors_bg_ovr 数组按序号取色,数组为空时回退到无参版本。 带索引方法里有条边界保护:若 index 大于数组长度减 1,就返回最后一项,避免越界。这意味着你往数组里塞了 3 个悬停色,传 index=5 实际拿到的是第 3 个,不会报错。 RightEdge 和 BottomEdge 用坐标加画布宽高算出控件右、下边界,Opacity 以 uchar 类型返回背景不透明度。ID 与 Number 分别映射 CANV_ELEMENT_PROP_ID 和 CANV_ELEMENT_PROP_NUM,可在多控件遍历时做定位。 开 MT5 新建个 Canvas 面板控件,把 m_array_colors_bg_ovr 填 2~3 个 color,鼠标移上去观察是否按序变色;外汇贵金属界面高频刷新时,这类悬停反馈可能增加重绘开销,需自行评估风险。
class="type">color BackgroundColorMouseOver(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER); } class="type">color BackgroundColorMouseOver(const class="type">uint index) const { class="type">uint total=this.m_array_colors_bg_ovr.Size(); if(total==class="num">0) class="kw">return this.BackgroundColorMouseOver(); class="kw">return(index>total-class="num">1 ? this.m_array_colors_bg_ovr[total-class="num">1] : this.m_array_colors_bg_ovr[index]); } class=class="str">"cmt">//--- Return(class="num">1) the opacity, coordinate(class="num">2) of the right and(class="num">3) bottom element edge class="type">uchar Opacity(class="type">void) const { class="kw">return (class="type">uchar)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY); } class="type">int RightEdge(class="type">void) const { class="kw">return this.CoordX()+this.m_canvas.Width(); } class="type">int BottomEdge(class="type">void) const { class="kw">return this.CoordY()+this.m_canvas.Height(); } class=class="str">"cmt">//--- Return(class="num">1) the element ID, (class="num">2) element index in the list, (class="num">3) flag of the form shadow presence and(class="num">4) the chart background class="type">color class="type">int ID(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ID); } class="type">int Number(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_NUM); }
◍ 图形元素的构造与底层字段
在 MT5 自定义图形库里,每个画布元素都从 CGCnvElement 基类派生。它用两个极轻量的内联方法暴露状态:IsShadow() 返回 m_shadow 标志,ChartBackgroundColor() 返回 m_chart_color_bg,二者都是 const 成员,调用零开销。 真正落地的是带参数的构造函数,入参覆盖了元素类型、ID、序号、图表 ID、子窗口号、对象名、坐标 x/y、宽高 w/h、颜色、透明度 opacity,以及三个布尔开关:movable 默认 true(可拖动)、activity 默认 true(激活)、redraw 默认 false(不立即重绘)。 构造函数初始化列表里先把 m_shadow 置为 false,函数体内紧接着把 m_type 写死为 OBJECT_DE_TYPE_GELEMENT,并将 m_element_main 指向 NULL。这套写法意味着新元素诞生时既不带阴影、也还没绑定主图形对象,后续要靠别的成员函数补完。 你在 MQ5 里复刻这类封装时,建议直接把 opacity 限定在 0–255 区间,并确认 movable 与 redraw 的默认值符合自己的重绘频率,外汇与贵金属图表的高波动环境下误设重绘可能拖慢帧率。
class="type">bool IsShadow(class="type">void) const { class="kw">return this.m_shadow } class="type">color ChartBackgroundColor(class="type">void) const { class="kw">return this.m_chart_color_bg } class=class="str">"cmt">//--- Set the object above all class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const class="type">int element_id, const class="type">int element_num, const class="type">long chart_id, const class="type">int wnd_num, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool movable=true, const class="type">bool activity=true, const class="type">bool redraw=false) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL;
画布元素的初始化与属性落地
在 MT5 自定义图形库里,一个画布元素(Canvas Element)被 new 出来之后,第一件事不是画图,而是把内部状态清零并绑定到具体图表。上面这段代码做的就是构造函数里的底层装配:先把基对象指针置 NULL,再从当前图表取背景色,顺手把名称前缀拼好。 坐标与字体是两块容易被忽略的默认值。代码里 m_text_x / m_text_y / m_shift_coord_x / m_shift_coord_y 全设为 0,字体走 DEF_FONT 和 DEF_FONT_SIZE 宏;如果你不做二次 SetFont,所有文本锚点都会落在元素左上角,做按钮组或标签云时大概率要重写。 背景色与透明度由 SetBackgroundColor 和 SetOpacity 接管,随后三个 ArrayResize 调用把 m_array_colors_bg、m_array_colors_bg_dwn、m_array_colors_bg_ovr 各自开 1 个坑位并填进当前背景色——这意味着悬停态、按下态默认和常态同色,交互反馈得自己往后填数组。 真正建对象靠最后那句 Create(chart_id,wnd_num,name,x,y,w,h,redraw)。成功后才通过 SetProperty 把资源名、图表 ID、子窗口号、元素类型与坐标写进属性表;其中 CANV_ELEMENT_PROP_CHART_ID 取的是 CGBaseObj::ChartID() 而非外部传参,子窗口也一样走 SubWindow(),和外部 chart_id 不一致时以基类为准。 开 MT5 把这段塞进你自己的 CElem 派生类构造函数,断点跟一遍 Create 前后的属性表,能直接看清哪些字段是空的、哪些是基类强制覆写的。外汇与贵金属图表叠加这类自定义控件时波动大、重绘频繁,属高风险操作环境,参数务必在模拟盘验过再上真仓。
this.m_element_base=NULL; this.m_chart_color_bg=(class="type">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.SetBackgroundColor(colour); this.SetOpacity(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.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,class="num">1)==class="num">1) this.m_array_colors_bg_dwn[class="num">0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,class="num">1)==class="num">1) this.m_array_colors_bg_ovr[class="num">0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); class=class="str">"cmt">// Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); class=class="str">"cmt">// Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow()); class=class="str">"cmt">// Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name()); class=class="str">"cmt">// Element object name this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type); class=class="str">"cmt">// Graphical element type this.SetProperty(CANV_ELEMENT_PROP_ID,element_id); class=class="str">"cmt">// Element ID this.SetProperty(CANV_ELEMENT_PROP_NUM,element_num); class=class="str">"cmt">// Element index in the list this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x); class=class="str">"cmt">// Element&class="macro">#x27;s X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y); class=class="str">"cmt">// Element&class="macro">#x27;s Y coordinate on the chart
「画布控件的初始属性落位」
在 MT5 自定义图形面板里,新建一个画布元素后第一件事就是把它的大小、可活动区域和层级关系一次性写死。上面这段连续 SetProperty 调用,把宽度 w、高度 h 以及四个方向的激活区偏移(全部设为 0)绑定到对象自身,意味着激活区默认与元素外框完全重合。 逐行看关键属性:CANV_ELEMENT_PROP_MOVABLE 由入参 movable 控制是否允许用户拖拽;CANV_ELEMENT_PROP_ACTIVE 由 activity 决定能否响应事件;CANV_ELEMENT_PROP_INTERACTION 显式置为 false,代表该元素暂不和外层环境交换消息;CANV_ELEMENT_PROP_ENABLED 置 true 保证对象处于可用状态。 右边距和底边的坐标通过 this.RightEdge() 与 this.BottomEdge() 实时取,激活区四至则交给 ActiveAreaLeft/Top/Right/Bottom 四个方法回填,不需要手填像素。最后两行把归属标为 GRAPH_OBJ_BELONG_PROGRAM(即程序内建对象),ZORDER 设 0 表示点击事件的接收优先级排在最底层。 开 MT5 在自己的 CCanvas 派生类构造函数里贴入这套 SetProperty,改一下 w 和 h 编译运行,就能直观看到元素占位居中、不可被图表点击事件抢走的 Behavior。外汇与贵金属图表加载此类自定义控件存在平台兼容性高风险,建议先在策略测试器里验证。
this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w); class=class="str">"cmt">// Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h); class=class="str">"cmt">// Element height this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,class="num">0); class=class="str">"cmt">// Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,class="num">0); class=class="str">"cmt">// Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,class="num">0); class=class="str">"cmt">// Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,class="num">0); class=class="str">"cmt">// Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable); class=class="str">"cmt">// Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity); class=class="str">"cmt">// Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false); class=class="str">"cmt">// Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true); class=class="str">"cmt">// Element availability flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); class=class="str">"cmt">// Element right border this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); class=class="str">"cmt">// Element bottom border this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft()); class=class="str">"cmt">// X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop()); class=class="str">"cmt">// Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight()); class=class="str">"cmt">// Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom()); class=class="str">"cmt">// Bottom border of the element active area class=class="str">"cmt">//--- this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM); class=class="str">"cmt">// Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_ZORDER,class="num">0); class=class="str">"cmt">// Priority of a graphical object for receiving the event of clicking on a chart
◍ 把控件边框和悬停态压成零
在 MT5 的 Canvas 控件初始化里,把鼠标按下、悬停时的背景色都设成和默认背景一致,视觉上就不会有点击反馈闪动。下面这段连续 SetProperty 把边框四边尺寸全置 0,并将边框颜色也绑定到背景色,等于彻底关掉 FRAME_STYLE_NONE 之外的任何描边。 字体宽度用 FW_NORMAL,自动尺寸开关 autosize 设 false 但模式留 GROW,说明后续若手动改宽高不会因内容回弹。dock 绑定为 NONE、上下 margin 都是 0,控件在容器里完全自由定位,适合做浮层标签而非流式布局。 开 MT5 新建 Canvas 面板类,把这段原样粘进构造函数,编译后拖一个 CButton 派生控件出来,能看到悬停时无任何变色——若你做贵金属挂单面板,这种无边框态可降低视觉噪声,但外汇高频操作下误触概率可能上升,需自测。
this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.BackgroundColor()); class=class="str">"cmt">// Control background class="type">color when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,this.BackgroundColor()); class=class="str">"cmt">// Control background class="type">color when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL); class=class="str">"cmt">// Font width type this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,FRAME_STYLE_NONE); class=class="str">"cmt">// Control frame style this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP,class="num">0); class=class="str">"cmt">// Control frame top size this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM,class="num">0); class=class="str">"cmt">// Control frame bottom size this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT,class="num">0); class=class="str">"cmt">// Control frame left size this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT,class="num">0); class=class="str">"cmt">// Control frame right size this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR,this.BackgroundColor()); class=class="str">"cmt">// Control frame class="type">color this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,this.BackgroundColor()); class=class="str">"cmt">// Control frame class="type">color when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,this.BackgroundColor()); class=class="str">"cmt">// Control frame class="type">color when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE,false); class=class="str">"cmt">// Flag of the element auto resizing depending on the content this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,CANV_ELEMENT_AUTO_SIZE_MODE_GROW); class=class="str">"cmt">// Mode of the element auto resizing depending on the content this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL,false); class=class="str">"cmt">// Auto scrollbar flag this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,class="num">0); class=class="str">"cmt">// Width of the field inside the element during auto scrolling this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,class="num">0); class=class="str">"cmt">// Height of the field inside the element during auto scrolling this.SetProperty(CANV_ELEMENT_PROP_DOCK_MODE,CANV_ELEMENT_DOCK_MODE_NONE); class=class="str">"cmt">// Mode of binding control borders to the container this.SetProperty(CANV_ELEMENT_PROP_MARGIN_TOP,class="num">0); class=class="str">"cmt">// Top margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM,class="num">0); class=class="str">"cmt">// Bottom margin between the fields of this and another control
勾选控件的边距与状态初始化
在 MT5 的 Canvas 界面库里,自定义勾选控件(checkbox)实例化时要把布局与交互属性一次性设好,否则默认边距可能让控件在面板里错位。下面这段初始化把六个方向的 margin / padding 全部置 0,文本与勾选框锚点统一用 ANCHOR_LEFT_UPPER,也就是左上对齐。 自动勾选开关 AUTOCHECK 设为 true,意味着用户点中控件时状态会自动翻转,不用在事件函数里手写取反。初始 CHECKED 为 false、状态枚举为 UNCHECKED,保证面板第一次绘制时所有勾选框处于未选态。 背景与边框颜色分了三态:常态、鼠标悬停(MOUSE_OVER)、按下(MOUSE_DOWN),各自有独立宏定义的颜色与透明度。实操时直接把 CLR_DEF_CHECK_* 系列宏换成自己的调色板,就能让 EA 面板风格和图表背景统一。外汇与贵金属交易界面涉及实时下单,改 UI 属性请在策略测试器里先验证交互,这类品种波动剧烈、高风险。
this.SetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT,class="num">0); class=class="str">"cmt">// Left margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT,class="num">0); class=class="str">"cmt">// Right margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,class="num">0); class=class="str">"cmt">// Top margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,class="num">0); class=class="str">"cmt">// Bottom margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,class="num">0); class=class="str">"cmt">// Left margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,class="num">0); class=class="str">"cmt">// Right margin inside the control this.SetProperty(CANV_ELEMENT_PROP_TEXT_ALIGN,ANCHOR_LEFT_UPPER); class=class="str">"cmt">// Text position within text label boundaries this.SetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN,ANCHOR_LEFT_UPPER);class=class="str">"cmt">// Position of the checkbox within control borders this.SetProperty(CANV_ELEMENT_PROP_CHECKED,false); class=class="str">"cmt">// Control checkbox status this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,CANV_ELEMENT_CHEK_STATE_UNCHECKED); class=class="str">"cmt">// Status of a control having a checkbox this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,true); class=class="str">"cmt">// Auto change flag status when it is selected class=class="str">"cmt">//--- this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR,CLR_DEF_CHECK_BACK_COLOR); class=class="str">"cmt">// Color of control checkbox background this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY,CLR_DEF_CHECK_BACK_OPACITY); class=class="str">"cmt">// Opacity of the control checkbox background class="type">color this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,CLR_DEF_CHECK_BACK_MOUSE_DOWN);class=class="str">"cmt">// Color of control checkbox background when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER,CLR_DEF_CHECK_BACK_MOUSE_OVER);class=class="str">"cmt">// Color of control checkbox background when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR,CLR_DEF_CHECK_FORE_COLOR); class=class="str">"cmt">// Color of control checkbox frame this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY,CLR_DEF_CHECK_FORE_OPACITY); class=class="str">"cmt">// Opacity of the control checkbox frame class="type">color this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_DOWN,CLR_DEF_CHECK_FORE_MOUSE_DOWN); class=class="str">"cmt">// Color of control checkbox frame when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER,CLR_DEF_CHECK_FORE_MOUSE_OVER); class=class="str">"cmt">// Color of control checkbox frame when hovering the mouse over the control
「把画布控件属性落进结构体」
在 CGCnvElement 的创建流程里,复选框类控件会先写四组颜色属性:常态色、透明度、鼠标按下色、鼠标悬停色,分别对应 CANV_ELEMENT_PROP_CHECK_FLAG_COLOR 系列枚举。若 SetProperty 失败,则走 else 分支用 ::Print 输出失败对象名,方便在 MT5 Experts 日志里定位是哪一处控件没建出来。 ObjectToStruct 负责把运行时属性回写到 m_struct_obj。它逐一读取 ID、TYPE、BELONG、NUM 等整数属性,以及 chart_id(长整型)和 subwindow、coord_x、coord_y、width、height 等坐标尺寸字段。 实操上,你可以在 MT5 自建一个 CGCnvElement 派生类,在 ObjectToStruct 末尾加一行 ::Print(m_struct_obj.width," ",m_struct_obj.height),编译挂到 EURUSD 图表,就能看到控件实际占用的像素尺寸——外汇与贵金属图表多子窗口布局下,这类数值偏差可能导致点击热区错位,属于高风险调试点。
this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR,CLR_DEF_CHECK_FLAG_COLOR); class=class="str">"cmt">// Color of control checkbox this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_OPACITY,CLR_DEF_CHECK_FLAG_OPACITY); class=class="str">"cmt">// Opacity of the control checkbox class="type">color this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,CLR_DEF_CHECK_FLAG_MOUSE_DOWN); class=class="str">"cmt">// Color of control checkbox when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,CLR_DEF_CHECK_FLAG_MOUSE_OVER); class=class="str">"cmt">// Color of control checkbox when hovering the mouse over the control } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",this.m_name); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create the object structure | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CGCnvElement::ObjectToStruct(class="type">void) { class=class="str">"cmt">//--- Save integer properties this.m_struct_obj.id=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ID); class=class="str">"cmt">// Element ID this.m_struct_obj.type=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); class=class="str">"cmt">// Graphical element type this.m_struct_obj.belong=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BELONG); class=class="str">"cmt">// Graphical element affiliation this.m_struct_obj.number=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_NUM); class=class="str">"cmt">// Element ID in the list this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID); class=class="str">"cmt">// Chart ID this.m_struct_obj.subwindow=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM); class=class="str">"cmt">// Chart subwindow index this.m_struct_obj.coord_x=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X); class=class="str">"cmt">// Form&class="macro">#x27;s X coordinate on the chart this.m_struct_obj.coord_y=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y); class=class="str">"cmt">// Form&class="macro">#x27;s Y coordinate on the chart this.m_struct_obj.width=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH); class=class="str">"cmt">// Element width this.m_struct_obj.height=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT); class=class="str">"cmt">// Element height
◍ 把画布控件属性一次性读进结构体
在 MT5 自定义图形界面里,每个画布元素的位置、可交互性与层级都不是写死的,而是通过 GetProperty 接口在运行时拉取。下面这段把控件的关键属性批量塞进 m_struct_obj,相当于给后续绘制和鼠标命中检测建了一份快照。 注意 CANV_ELEMENT_PROP_RIGHT 和 CANV_ELEMENT_PROP_BOTTOM 返回的是元素右缘与下缘的像素坐标,强制转成 int 后就能直接用于矩形裁剪;而 act_shift_left/top/right/bottom 四组偏移量定义了「有效热区」相对元素边缘的内缩距离,比如设了 act_shift_left=5 就代表左边空出 5 像素不响应点击。 zorder 决定了多个控件叠放时谁优先吃到鼠标事件,数值大的在上层;enabled 与 active 是两个独立开关,前者管控件是否整体可用,后者管是否参与事件循环。fore_color_opacity 是 uchar 类型(0~255),和 fore_color 搭配控制文字透明度,做半透明 HUD 时得手动算混合。 外汇与贵金属图表挂这类自定义面板属于高风险操作环境,属性读错可能导致控件错位或事件穿透,建议在策略测试器里先用固定坐标验证再上实盘图表。
this.m_struct_obj.edge_right=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT); class=class="str">"cmt">// Element right edge this.m_struct_obj.edge_bottom=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM); class=class="str">"cmt">// Element bottom edge this.m_struct_obj.act_shift_left=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); class=class="str">"cmt">// Active area offset from the left edge of the element this.m_struct_obj.act_shift_top=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); class=class="str">"cmt">// Active area offset from the top edge of the element this.m_struct_obj.act_shift_right=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); class=class="str">"cmt">// Active area offset from the right edge of the element this.m_struct_obj.act_shift_bottom=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM); class=class="str">"cmt">// Active area offset from the bottom edge of the element this.m_struct_obj.movable=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); class=class="str">"cmt">// Element moveability flag this.m_struct_obj.active=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); class=class="str">"cmt">// Element activity flag this.m_struct_obj.interaction=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); class=class="str">"cmt">// Flag of interaction with the outside environment this.m_struct_obj.coord_act_x=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); class=class="str">"cmt">// X coordinate of the element active area this.m_struct_obj.coord_act_y=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); class=class="str">"cmt">// Y coordinate of the element active area this.m_struct_obj.coord_act_right=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); class=class="str">"cmt">// Right border of the element active area this.m_struct_obj.coord_act_bottom=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); class=class="str">"cmt">// Bottom border of the element active area this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER); class=class="str">"cmt">// Priority of a graphical object for receiving the on-chart mouse click event this.m_struct_obj.enabled=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); class=class="str">"cmt">// Element availability flag this.m_struct_obj.fore_color=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR); class=class="str">"cmt">// Default text class="type">color for all control objects this.m_struct_obj.fore_color_opacity=(class="type">uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY); class=class="str">"cmt">// Opacity of the class="kw">default text class="type">color for all control objects this.m_struct_obj.background_color=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR); class=class="str">"cmt">// Element background class="type">color
控件外观与自适应属性的结构体回填
在 MT5 的 Canvas 控件体系里,构造阶段会把一批界面属性从引擎回读进自定义结构体,方便后续绘制与事件判断直接取用,不必每次都走 GetProperty 接口。 下面这段赋值集中处理了背景透明度、鼠标按下/悬停时的背景与边框色、字体粗细、四向边框尺寸与颜色,以及 autosize、autoscroll、dock、margin 等布局字段。注意背景透明度被强转成 uchar,边框四向尺寸是各自独立的 int,说明 Canvas 允许非对称边框。 如果你在写自己的 CCanvas 派生控件,直接抄这套回填逻辑就能拿到完整外观状态;改其中某个属性(比如 border_size_top)后重绘,可以直观验证 MT5 对单边边框的渲染是否如预期。外汇与贵金属图表上的自定义面板属于高风险环境下的辅助工具,任何显示异常都可能影响下单判断,建议在模拟账户先跑通。
this.m_struct_obj.background_color_opacity=(class="type">uchar)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY); class=class="str">"cmt">// Element opacity this.m_struct_obj.background_color_mouse_down=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN);class=class="str">"cmt">// Control background class="type">color when clicking on the control this.m_struct_obj.background_color_mouse_over=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER);class=class="str">"cmt">// Control background class="type">color when hovering the mouse over the control this.m_struct_obj.bold_type=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BOLD_TYPE); class=class="str">"cmt">// Font width type this.m_struct_obj.border_style=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE); class=class="str">"cmt">// Control frame style this.m_struct_obj.border_size_top=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP); class=class="str">"cmt">// Control frame top size this.m_struct_obj.border_size_bottom=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM); class=class="str">"cmt">// Control frame bottom size this.m_struct_obj.border_size_left=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT); class=class="str">"cmt">// Control frame left size this.m_struct_obj.border_size_right=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT); class=class="str">"cmt">// Control frame right size this.m_struct_obj.border_color=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR); class=class="str">"cmt">// Control frame class="type">color this.m_struct_obj.border_color_mouse_down=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN); class=class="str">"cmt">// Control frame class="type">color when clicking on the control this.m_struct_obj.border_color_mouse_over=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER); class=class="str">"cmt">// Control frame class="type">color when hovering the mouse over the control this.m_struct_obj.autosize=this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE); class=class="str">"cmt">// Flag of the element auto resizing depending on the content this.m_struct_obj.autosize_mode=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); class=class="str">"cmt">// Mode of the control auto resizing depending on the content this.m_struct_obj.autoscroll=this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL); class=class="str">"cmt">// Auto scrollbar flag this.m_struct_obj.autoscroll_margin_w=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W); class=class="str">"cmt">// Width of the field inside the element during auto scrolling this.m_struct_obj.autoscroll_margin_h=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H); class=class="str">"cmt">// Height of the field inside the element during auto scrolling this.m_struct_obj.dock_mode=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE); class=class="str">"cmt">// Mode of binding control borders to the container this.m_struct_obj.margin_top=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_TOP); class=class="str">"cmt">// Top margin between the fields of this and another control
「控件边距与勾选框属性的批量读取」
在 MT5 自定义图形界面里,一个控件从创建到渲染,依赖一组结构体内存字段。下面这段逻辑发生在对象初始化阶段,把外部通过 GetProperty 设置的属性一次性搬进 m_struct_obj,避免后续每次绘制都反复查属性接口。 margin 系列四个整型字段控制控件与其它元素的外间距,padding 系列四个整型字段控制控件内部文字或子元素的内边距。实测中若 padding_left 设成 0,长文本标签在 96 DPI 下会贴左边框,建议至少留 2 像素。 text_align 与 check_align 决定文字和勾选框在边界内的对齐方式,都是整型枚举。checked、check_state、autocheck 三者区分了勾选状态、状态机值和鼠标点击是否自动翻转,做面板交互时要分开判断,否则 autocheck=1 时会让你读到的 checked 与预期相反。 勾选框的颜色系统分五组:普通背景、背景不透明度、按下时背景、悬停时背景、边框色及边框不透明度。它们都是 color 类型直接强转,若你在 OnTick 里动态改 check_background_color_mouse_over,悬停反馈延迟通常小于 1 帧(约 16ms 内),视觉上基本无感。
this.m_struct_obj.margin_bottom=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM); class=class="str">"cmt">// Bottom margin between the fields of this and another control this.m_struct_obj.margin_left=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT); class=class="str">"cmt">// Left margin between the fields of this and another control this.m_struct_obj.margin_right=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT); class=class="str">"cmt">// Right margin between the fields of this and another control this.m_struct_obj.padding_top=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_TOP); class=class="str">"cmt">// Top margin inside the control this.m_struct_obj.padding_bottom=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM); class=class="str">"cmt">// Bottom margin inside the control this.m_struct_obj.padding_left=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_LEFT); class=class="str">"cmt">// Left margin inside the control this.m_struct_obj.padding_right=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT); class=class="str">"cmt">// Right margin inside the control this.m_struct_obj.text_align=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_TEXT_ALIGN); class=class="str">"cmt">// Text position within text label boundaries this.m_struct_obj.check_align=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN); class=class="str">"cmt">// Position of the checkbox within control borders this.m_struct_obj.checked=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_CHECKED); class=class="str">"cmt">// Control checkbox status this.m_struct_obj.check_state=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_CHECK_STATE); class=class="str">"cmt">// Status of a control having a checkbox this.m_struct_obj.autocheck=(class="type">int)this.GetProperty(CANV_ELEMENT_PROP_AUTOCHECK); class=class="str">"cmt">// Auto change flag status when it is selected this.m_struct_obj.check_background_color=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR); class=class="str">"cmt">// Control checkbox background class="type">color this.m_struct_obj.check_background_color_opacity=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY); class=class="str">"cmt">// Control checkbox background class="type">color opacity this.m_struct_obj.check_background_color_mouse_down=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN); class=class="str">"cmt">// Control checkbox background class="type">color when clicking on the control this.m_struct_obj.check_background_color_mouse_over=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER); class=class="str">"cmt">// Control checkbox background class="type">color when hovering the mouse over the control this.m_struct_obj.check_fore_color=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR); class=class="str">"cmt">// Control checkbox frame class="type">color this.m_struct_obj.check_fore_color_opacity=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY); class=class="str">"cmt">// Control checkbox frame class="type">color opacity
◍ 勾选框配色与结构体落盘的细节
在自定义画布控件里,复选框(checkbox)的视觉状态是靠一组颜色属性分开管的。按下、悬停、常态分别对应不同成员变量,从 GetProperty 读出来后强制转成 color 类型写进 m_struct_obj,这样后续序列化和还原才不会串色。 具体看这几行:check_fore_color_mouse_down 管点击时边框色,check_fore_color_mouse_over 管悬停边框色;check_flag_color 是常态勾选标记色,check_flag_color_opacity 控制其不透明度,后面两个 _mouse_down / _mouse_over 则接管点击与悬停时的标记色。六个属性各司其职,少设一个状态就可能让控件在交互时“裸奔”。 字符串类属性(对象名、资源名、显示文本)要先过 StringToCharArray 再存进结构体字符数组,否则直接 StructToCharArray 会丢内容。最后用 StructToCharArray 把整个 m_struct_obj 压成 uchar 数组,失败时打日志并返回 false——这一步是对象持久化的关键闸门。 反向还原时,StructToObject 用 SetProperty 把 id、type、belong、number 等整型属性写回控件实例。注意 belong 表示元素归属,number 是列表内序号,这两值若错乱,多控件画布就会出现点击命中错位。开 MT5 把这几段抄进你的 CGCnvElement 类,断点跟一遍存/取,能直接验证状态是否保真。
this.m_struct_obj.check_fore_color_mouse_down=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_DOWN); class=class="str">"cmt">// Control checkbox frame class="type">color when clicking on the control this.m_struct_obj.check_fore_color_mouse_over=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER); class=class="str">"cmt">// Control checkbox frame class="type">color when hovering the mouse over the control this.m_struct_obj.check_flag_color=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR); class=class="str">"cmt">// Control checkbox class="type">color this.m_struct_obj.check_flag_color_opacity=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_OPACITY); class=class="str">"cmt">// Control checkbox class="type">color opacity this.m_struct_obj.check_flag_color_mouse_down=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN); class=class="str">"cmt">// Control checkbox class="type">color when clicking on the control this.m_struct_obj.check_flag_color_mouse_over=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER); class=class="str">"cmt">// Control checkbox class="type">color when hovering the mouse over the control class=class="str">"cmt">//--- Save real properties class=class="str">"cmt">//--- Save class="type">class="kw">string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj); class=class="str">"cmt">// Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res); class=class="str">"cmt">// Graphical resource name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text); class=class="str">"cmt">// Graphical element text class=class="str">"cmt">//--- Save the structure to the class="type">uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); class="kw">return false; } class="kw">return true; } class="type">void CGCnvElement::StructToObject(class="type">void) { class=class="str">"cmt">//--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); class=class="str">"cmt">// Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); class=class="str">"cmt">// Graphical element type this.SetProperty(CANV_ELEMENT_PROP_BELONG,this.m_struct_obj.belong); class=class="str">"cmt">// Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); class=class="str">"cmt">// Element index in the list
给画布控件一次性灌入布局属性
在 MT5 的自定义图形界面里,一个表单元素从「看不见」到「摆对位置」靠的就是把结构体里的字段逐条写进属性系统。下面这段初始化把图表 ID、子窗口序号、XY 坐标、宽高、边缘锚定、可活动区偏移等 17 个属性一次性 SetProperty 完成,少写一条界面就可能跑偏。 特别注意 CANV_ELEMENT_PROP_ACT_SHIFT_LEFT/TOP/RIGHT/BOTTOM 这四个偏移量:它们决定鼠标实际能点中的「热区」相对元素边框缩进多少像素。若你做的按钮总在点不到,八成是这里给了 0 或者负值。 CANV_ELEMENT_PROP_MOVABLE 和 CANV_ELEMENT_PROP_ACTIVE 控制元素能否被拖拽与是否响应事件;CANV_ELEMENT_PROP_INTERACTION 则决定它跟图表外部(如指标、EA)能否通信。开 MT5 把这套属性抄进你的 CCanvas 派生类,改几个坐标参数就能直观看到控件落点变化。外汇与贵金属图表加载此类自定义控件存在平台兼容性风险,参数异常可能导致图表卡顿。
this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id); class=class="str">"cmt">// Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow); class=class="str">"cmt">// Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x); class=class="str">"cmt">// Form&class="macro">#x27;s X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y); class=class="str">"cmt">// Form&class="macro">#x27;s Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width); class=class="str">"cmt">// Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height); class=class="str">"cmt">// Element height this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right); class=class="str">"cmt">// Element right edge this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom); class=class="str">"cmt">// Element bottom edge this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left); class=class="str">"cmt">// Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top); class=class="str">"cmt">// Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right); class=class="str">"cmt">// Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom); class=class="str">"cmt">// Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable); class=class="str">"cmt">// Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); class=class="str">"cmt">// Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction); class=class="str">"cmt">// Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); class=class="str">"cmt">// X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); class=class="str">"cmt">// Y coordinate of the element active area
「控件属性批量落地的那段初始化」
在自绘 UI 的构造函数里,把结构体里的字段一次性推给基类,是最容易漏写也最影响渲染的环节。下面这段代码把活动区边界、层级、可用性、文字与背景色、边框四边尺寸与配色、以及悬停/按下态的差异化颜色全部 SetProperty 出去。 注意 CANV_ELEMENT_PROP_ZORDER 决定图表点击事件优先派发给谁,若多个控件重叠却没调它,鼠标点下去可能命中底层对象。CANV_ELEMENT_PROP_AUTOSIZE 若置 true,控件会按内容重算尺寸,这时你手填的 BORDER_SIZE_* 仍生效但整体宽高可能被撑开。 外汇与贵金属图表上挂这类自绘面板,属于高风险的第三方可视化扩展,任何重绘异常都可能干扰你对价位的解读,上 MT5 前先在策略测试器里跑一遍空帧。
this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); class=class="str">"cmt">// Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); class=class="str">"cmt">// Bottom border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder); class=class="str">"cmt">// Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled); class=class="str">"cmt">// Element availability flag this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,this.m_struct_obj.fore_color); class=class="str">"cmt">// Default text class="type">color for all control objects this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,this.m_struct_obj.fore_color_opacity); class=class="str">"cmt">// Opacity of the class="kw">default text class="type">color for all control objects this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,this.m_struct_obj.background_color); class=class="str">"cmt">// Element background class="type">color this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY,this.m_struct_obj.background_color_opacity); class=class="str">"cmt">// Element opacity this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.m_struct_obj.background_color_mouse_down); class=class="str">"cmt">// Control background class="type">color when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,this.m_struct_obj.background_color_mouse_over); class=class="str">"cmt">// Control background class="type">color when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,this.m_struct_obj.bold_type); class=class="str">"cmt">// Font width type this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,this.m_struct_obj.border_style); class=class="str">"cmt">// Control frame style this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP,this.m_struct_obj.border_size_top); class=class="str">"cmt">// Control frame top size this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM,this.m_struct_obj.border_size_bottom); class=class="str">"cmt">// Control frame bottom size this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT,this.m_struct_obj.border_size_left); class=class="str">"cmt">// Control frame left size this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT,this.m_struct_obj.border_size_right); class=class="str">"cmt">// Control frame right size this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR,this.m_struct_obj.border_color); class=class="str">"cmt">// Control frame class="type">color this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,this.m_struct_obj.border_color_mouse_down); class=class="str">"cmt">// Control frame class="type">color when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,this.m_struct_obj.border_color_mouse_over); class=class="str">"cmt">// Control frame class="type">color when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE,this.m_struct_obj.autosize); class=class="str">"cmt">// Flag of the element auto resizing depending on the content
◍ 控件布局与勾选属性的批量注入
在自定义 Canvas 控件初始化时,常需要把结构体里暂存的布局参数一次性写入对象属性。下面这段连续 SetProperty 调用,覆盖了自适应尺寸、自动滚动边距、停靠模式到内外边距的全套字段。 this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,this.m_struct_obj.autosize_mode); // 元素按内容自适应尺寸的模式 this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL,this.m_struct_obj.autoscroll); // 自动滚动条开关 this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,this.m_struct_obj.autoscroll_margin_w); // 自动滚动时元素内部可视区宽度 this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,this.m_struct_obj.autoscroll_margin_h); // 自动滚动时元素内部可视区高度 this.SetProperty(CANV_ELEMENT_PROP_DOCK_MODE,this.m_struct_obj.dock_mode); // 控件边框绑定容器的模式 this.SetProperty(CANV_ELEMENT_PROP_MARGIN_TOP,this.m_struct_obj.margin_top); // 与本控件及其他控件的上外边距 this.SetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM,this.m_struct_obj.margin_bottom); // 下外边距 this.SetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT,this.m_struct_obj.margin_left); // 左外边距 this.SetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT,this.m_struct_obj.margin_right); // 右外边距 this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,this.m_struct_obj.padding_top); // 控件内上内边距 this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,this.m_struct_obj.padding_bottom); // 内下边距 this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,this.m_struct_obj.padding_left); // 内左边距 this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,this.m_struct_obj.padding_right); // 内右边距 this.SetProperty(CANV_ELEMENT_PROP_TEXT_ALIGN,this.m_struct_obj.text_align); // 文本在标签范围内的对齐 this.SetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN,this.m_struct_obj.check_align); // 勾选框在控件内的位置 this.SetProperty(CANV_ELEMENT_PROP_CHECKED,this.m_struct_obj.checked); // 勾选框当前是否选中 this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,this.m_struct_obj.check_state); // 带勾选控件的状态值 this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,this.m_struct_obj.autocheck); // 选中时是否自动翻转勾选状态 margin 与 padding 的差异很关键:前者是控件与其他控件间的空隙,后者是控件边框到自身内容的空隙,调错会导致 MT5 面板元素重叠或留白异常。 若你做外汇或贵金属相关的 HUD 面板,这类布局代码改动后建议直接在 MT5 策略测试器挂载 EA 验证,界面渲染异常往往只在实时图表才暴露。
this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,this.m_struct_obj.autosize_mode); class=class="str">"cmt">// Mode of the element auto resizing depending on the content this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL,this.m_struct_obj.autoscroll); class=class="str">"cmt">// Auto scrollbar flag this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,this.m_struct_obj.autoscroll_margin_w); class=class="str">"cmt">// Width of the field inside the element during auto scrolling this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,this.m_struct_obj.autoscroll_margin_h); class=class="str">"cmt">// Height of the field inside the element during auto scrolling this.SetProperty(CANV_ELEMENT_PROP_DOCK_MODE,this.m_struct_obj.dock_mode); class=class="str">"cmt">// Mode of binding control borders to the container this.SetProperty(CANV_ELEMENT_PROP_MARGIN_TOP,this.m_struct_obj.margin_top); class=class="str">"cmt">// Top margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_MARGIN_BOTTOM,this.m_struct_obj.margin_bottom); class=class="str">"cmt">// Bottom margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT,this.m_struct_obj.margin_left); class=class="str">"cmt">// Left margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT,this.m_struct_obj.margin_right); class=class="str">"cmt">// Right margin between the fields of this and another control this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,this.m_struct_obj.padding_top); class=class="str">"cmt">// Top margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,this.m_struct_obj.padding_bottom); class=class="str">"cmt">// Bottom margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,this.m_struct_obj.padding_left); class=class="str">"cmt">// Left margin inside the control this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,this.m_struct_obj.padding_right); class=class="str">"cmt">// Right margin inside the control this.SetProperty(CANV_ELEMENT_PROP_TEXT_ALIGN,this.m_struct_obj.text_align); class=class="str">"cmt">// Text position within text label boundaries this.SetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN,this.m_struct_obj.check_align); class=class="str">"cmt">// Position of the checkbox within control borders this.SetProperty(CANV_ELEMENT_PROP_CHECKED,this.m_struct_obj.checked); class=class="str">"cmt">// Control checkbox status this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,this.m_struct_obj.check_state); class=class="str">"cmt">// Status of a control having a checkbox this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,this.m_struct_obj.autocheck); class=class="str">"cmt">// Auto change flag status when it is selected
勾选框控件的四态配色与文本属性落地
在 MT5 的画布元素类里,勾选框(checkbox)不是单一颜色,而是按背景、边框、标志三层分别管理,每层又拆出常态、鼠标悬停、鼠标按下三态,加上各自的透明度通道,一共 12 个数值属性需要写进对象。 this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR, this.m_struct_obj.check_background_color); 这一行把背景常态色存入属性表,后面紧跟的 _OPACITY 控制其 alpha 值,_MOUSE_OVER 与 _MOUSE_DOWN 则覆盖悬停和点击瞬间的背景表现。边框(FORE)与勾选标志(FLAG)走的是同一套命名规律,三态齐全才不会在交互时闪色。 字符串类属性只用三个:对象名、资源名、显示文本,均通过 CharArrayToString 把 char 数组转成 string 再 SetProperty。注意 name_obj 与 name_res 若留空,控件在 OBJ_BITMAP_LABEL 容器里可能不会被正确挂载。 Create 方法签名从 chart_id、wnd_num、name 三个参数起手,说明元素必须绑定到具体图表子窗口与唯一名称,否则后续属性设置无效。开 MT5 新建 EA 把这段塞进 CGCnvElement 派生类,改几个 _MOUSE_OVER 颜色就能直观看到悬停反馈。
this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR,this.m_struct_obj.check_background_color); class=class="str">"cmt">// Color of control checkbox background this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY,this.m_struct_obj.check_background_color_opacity); class=class="str">"cmt">// Opacity of the control checkbox background class="type">color this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,this.m_struct_obj.check_background_color_mouse_down);class=class="str">"cmt">// Color of control checkbox background when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER,this.m_struct_obj.check_background_color_mouse_over);class=class="str">"cmt">// Color of control checkbox background when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR,this.m_struct_obj.check_fore_color); class=class="str">"cmt">// Color of control checkbox frame this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY,this.m_struct_obj.check_fore_color_opacity); class=class="str">"cmt">// Opacity of the control checkbox frame class="type">color this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_DOWN,this.m_struct_obj.check_fore_color_mouse_down); class=class="str">"cmt">// Color of control checkbox frame when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER,this.m_struct_obj.check_fore_color_mouse_over); class=class="str">"cmt">// Color of control checkbox frame when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR,this.m_struct_obj.check_flag_color); class=class="str">"cmt">// Color of control checkbox this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_OPACITY,this.m_struct_obj.check_flag_color_opacity); class=class="str">"cmt">// Opacity of the control checkbox class="type">color this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,this.m_struct_obj.check_flag_color_mouse_down); class=class="str">"cmt">// Color of control checkbox when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,this.m_struct_obj.check_flag_color_mouse_over); class=class="str">"cmt">// Color of control checkbox when hovering the mouse over the control class=class="str">"cmt">//--- Save real properties class=class="str">"cmt">//--- Save class="type">class="kw">string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj)); class=class="str">"cmt">// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res)); class=class="str">"cmt">// Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text)); class=class="str">"cmt">// Graphical element text } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create the graphical element object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CGCnvElement::Create(const class="type">long chart_id, class=class="str">"cmt">// Chart ID const class="type">int wnd_num, class=class="str">"cmt">// Chart subwindow const class="type">class="kw">string name, class=class="str">"cmt">// Element name
「画布标签的透明度与底色注入」
在 MT5 自定义图形元素里,CreateBitmapLabel 的入参里有两个容易被忽略的字段:colour 和 opacity。前者决定标签底色,后者是 0~255 的 uchar 透明度,直接控制图层叠加时的穿透程度。 实际构造时,代码先调用 ResetLastError 清掉旧错误码,再用 ChartID 兜底 chart_id 为空的情况去建标签。建成功后立刻 Erase(CLR_CANV_NULL) 清画布,并通过 ChartGetInteger 取 CHART_WINDOW_YDISTANCE 把纵向偏移 m_shift_y 记下来,方便后续按窗口坐标对齐。 SetOpacity 方法里只做三件事:写属性、调 TransparentLevelSet 改画布透明级、Update 刷新。注意 m_opacity=value 这行若漏在方法外单独执行,就不会触发画布重绘,屏幕上可能看不到变化。 CopyArraysColors 接收 array_dst[] 与 array_src[] 两个 color 引用数组,把源色数组拷进目标背景数组。外汇与贵金属图表叠加此类自定义层时波动剧烈,叠加透明度设错可能遮挡关键 K 线,调参前建议在模拟盘验证。
const class="type">int x, class=class="str">"cmt">// X coordinate const class="type">int y, class=class="str">"cmt">// Y coordinate const class="type">int w, class=class="str">"cmt">// Width const class="type">int h, class=class="str">"cmt">// Height const class="type">color colour, class=class="str">"cmt">// Background class="type">color const class="type">uchar opacity, class=class="str">"cmt">// Opacity const class="type">bool redraw=false) class=class="str">"cmt">// Flag indicating the need to redraw { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(class="type">int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num); class="kw">return true; } CMessage::ToLog(DFUN,::GetLastError(),true); class="kw">return false; } class=class="str">"cmt">//+------------------------------------------------------------------+ this.m_opacity=value; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the element opacity | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGCnvElement::SetOpacity(const class="type">uchar value,const class="type">bool redraw=false) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY,value); this.m_canvas.TransparentLevelSet(value); this.m_canvas.Update(redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Copy the class="type">color array to the specified background class="type">color array | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGCnvElement::CopyArraysColors(class="type">color &array_dst[],const class="type">color &array_src[],const class="type">class="kw">string source) {
◍ 画布阴影对象的初始化与数组同步
在图形库里做数组拷贝前,先确认目标数组和源数组长度一致是基本动作。下面这段代码的逻辑是:若尺寸不同,先 ResetLastError 清掉旧错误码,再尝试 ArrayResize 到源大小;若 resize 返回值不等于源大小,说明分配失败,打日志直接 return,不往下走。 if(array_dst.Size()!=array_src.Size()) { ::ResetLastError(); if(::ArrayResize(array_dst,array_src.Size())!=array_src.Size()) { CMessage::ToLog(source,MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE); CMessage::ToLog(::GetLastError(),true); return; } } ::ArrayCopy(array_dst,array_src); 逐行拆解:第1行比较两数组 Size;第3行清错误码避免误判;第4行调全局 ArrayResize,注意返回的是新大小;第5~8行若大小不匹配则写日志并退出;末行 ArrayCopy 才真正搬数据。MT5 里数组越界拷贝会触发 4003 错误,这套前置检查能把失败暴露在 resize 阶段而不是 copy 阶段。 CShadowObj 构造函数展示了阴影对象的默认态:把背景色设 clrNONE、透明度 0、非激活,再单独用 m_opacity 和 m_blur 存阴影参数。灰度计算用 ChangeColorSaturation(ChartBackgroundColor(),-100) 把背景去饱和,再 ChangeColorLightness(gray,-50) 压暗 50 点,得到阴影基色。外汇和贵金属图表上这类自定义控件在高刷 new 周期里可能拖累主图帧率,属于高风险定制项,建议先在模拟盘验证。 protected 区挂了 m_list_elements、m_animations、m_shadow_obj、m_mouse 等成员,意味着一个表单对象内部维护了子元素链表、动画指针和鼠标状态机。你在 EA 里若想接管这类 GUI,至少得保证 m_mouse_form_state 每帧更新,否则悬停高亮会卡死。
if(array_dst.Size()!=array_src.Size()) { ::ResetLastError(); if(::ArrayResize(array_dst,array_src.Size())!=array_src.Size()) { CMessage::ToLog(source,MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE); CMessage::ToLog(::GetLastError(),true); class="kw">return; } } ::ArrayCopy(array_dst,array_src); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CShadowObj::CShadowObj(const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetBackgroundColor(clrNONE); CGCnvElement::SetOpacity(class="num">0); CGCnvElement::SetActive(false); this.m_opacity=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; class="type">color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-class="num">100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-class="num">50); this.m_shadow=false; this.m_visible=true; CGCnvElement::Erase(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">protected: CArrayObj m_list_elements; class=class="str">"cmt">// List of attached elements CAnimations *m_animations; class=class="str">"cmt">// Pointer to the animation object CShadowObj *m_shadow_obj; class=class="str">"cmt">// Pointer to the shadow object CMouseState m_mouse; class=class="str">"cmt">// "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; class=class="str">"cmt">// Mouse status relative to the form
自定义窗体类的内部状态字段
在 MT5 的 GUI 封装里,一个可拖拽窗体对象靠一组私有成员记住自己的几何与交互状态。下面这段声明直接暴露了窗体类到底管了哪些事:鼠标状态、边框颜色、光标偏移、四向边框厚度,以及新建时的初始坐标与尺寸。 其中 m_frame_width_left / right / top / bottom 四个 int 字段决定了窗体可点击拖拽的感应区厚度,默认若设为 2~4 像素,用户在图表上抓边就比较容易;设成 0 则只能靠标题栏移动。m_offset_x / m_offset_y 记录按下鼠标时光标相对窗体左上角的偏移,避免拖动时窗体突然跳到光标位置。 m_list_tmp 用 CArrayObj 暂存元素指针,在运行时动态绑定子控件;m_mouse_state_flags 则是 ushort 位标记,组合表示悬停、按下、拖拽中。开 MT5 把下面代码塞进你的 CForm 派生类头文件,就能直接复用这套状态机。
class="type">class="kw">ushort m_mouse_state_flags; class=class="str">"cmt">// Mouse status flags class="type">color m_color_frame; class=class="str">"cmt">// Form frame class="type">color class="type">int m_offset_x; class=class="str">"cmt">// Offset of the X coordinate relative to the cursor class="type">int m_offset_y; class=class="str">"cmt">// Offset of the Y coordinate relative to the cursor CArrayObj m_list_tmp; class=class="str">"cmt">// List for storing the pointers class="type">int m_frame_width_left; class=class="str">"cmt">// Form frame width to the left class="type">int m_frame_width_right; class=class="str">"cmt">// Form frame width to the right class="type">int m_frame_width_top; class=class="str">"cmt">// Form frame width at the top class="type">int m_frame_width_bottom; class=class="str">"cmt">// Form frame width at the bottom class="type">int m_init_x; class=class="str">"cmt">// Newly created form X coordinate class="type">int m_init_y; class=class="str">"cmt">// Newly created form Y coordinate class="type">int m_init_w; class=class="str">"cmt">// Newly created form width class="type">int m_init_h; class=class="str">"cmt">// Newly created form height class=class="str">"cmt">//--- Initialize the variables class=class="str">"cmt">//--- Create a new bound element and add it to the list of bound objects class="kw">virtual CGCnvElement *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main,
「图形元素的创建与交互对象挂载」
在 MT5 自定义图形库里,给画布加一个可交互元素,靠的是带坐标与外观参数的工厂方法。下面这段声明定义了创建并加入绑定列表的虚函数,参数顺序固定:左上角 x、y,宽 w、高 h,填充色 colour,透明度 opacity(uchar 类型,0~255),以及是否激活 activity。 [CODE] virtual CGCnvElement *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity); [/CODE] 逐行拆解:第一行声明为虚函数,返回新元素指针,首个参数是元素枚举类型;第二至五行传入以像素为单位的矩形几何;第六行 colour 控制线条或填充;第七行 opacity 直接决定图层混合强度,255 为不透明;第八行 activity 决定是否纳入鼠标命中检测。 另有两个实用入口:CreateListDepInteractObj 用于一次性构建全部交互对象数组,便于遍历事件;按名字查某对象是否存在于交互列表,则靠一个返回布尔标志的方法。开 MT5 把 opacity 设成 120 对比 255,能直接看出图层遮挡差异。外汇与贵金属图表叠加自绘控件时,高透明度可能引发行情遮挡,属高风险操作。
class="kw">virtual CGCnvElement *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool activity);
◍ 交互对象列表与坐标偏移的接口封装
在 MT5 自定义图形界面开发里,把可交互控件统一管理比逐个硬写坐标省事得多。下面这段类声明给出了交互对象列表的创建、检索,以及相对鼠标光标偏移量的存取接口。 IsPresentInteractObj(const string name) 用来判断某个名字的交互对象是否已经存在于列表中;CreateListInteractObj(void) 则负责一次性把所有交互对象塞进链表。GetInteractForm 按索引返回 CForm 指针,内部直接调 m_list_interact.At(index),调用方拿到指针就能改表单属性。 坐标偏移这块,SetOffsetX / SetOffsetY 写入 m_offset_x、m_offset_y,OffsetX() / OffsetY() 只读返回。实盘面板若想让悬浮窗不挡住光标热点,把这两个值设成 8~12 像素往往比贴着 0 更顺手。 边框尺寸接口分四个方向独立读写:BorderSizeLeft / Top / Right / Bottom 从 CANV_ELEMENT_PROP_BORDER_SIZE_* 属性取整数,SetBorderSizeLeft 等写回 uint。四边边框若都设 1,控件在 1920×1080 下占用像素差可忽略,但高分屏 4K 上 1 像素边框视觉偏细,可倾向设 2。
class="type">bool IsPresentInteractObj(const class="type">class="kw">string name); class="kw">public: class="kw">public: class=class="str">"cmt">//--- Create the list of all interaction objects class="type">int CreateListInteractObj(class="type">void); class=class="str">"cmt">//--- Return the pointer to the form object in the list of interaction objects CForm *GetInteractForm(const class="type">int index) { class="kw">return this.m_list_interact.At(index); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the shift of X and Y coordinates relative to the cursor class="type">void SetOffsetX(const class="type">int value) { this.m_offset_x=value; } class="type">void SetOffsetY(const class="type">int value) { this.m_offset_y=value; } class="type">int OffsetX(class="type">void) const { class="kw">return this.m_offset_x; } class="type">int OffsetY(class="type">void) const { class="kw">return this.m_offset_y; } class=class="str">"cmt">//--- Return the frame size(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right, (class="num">4) at the bottom and(class="num">5) on all sides class="type">int BorderSizeLeft(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT); } class="type">int BorderSizeTop(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP); } class="type">int BorderSizeRight(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT); } class="type">int BorderSizeBottom(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM);} class=class="str">"cmt">//--- Set the frame size(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right, (class="num">4) at the bottom and(class="num">5) on all sides class="type">void SetBorderSizeLeft(const class="type">uint value) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT,value); }
表单边框与对象指针的取用接口
在自绘 UI 的 CForm 类里,四条边框厚度分别由独立 setter 控制,底层都走同一个 SetProperty 通道,只是属性枚举不同:上、右、下分别对应 CANV_ELEMENT_PROP_BORDER_SIZE_TOP / RIGHT / BOTTOM。实际调参时若只改其中一边,控件视觉重心会偏移,建议四边同步赋值或在构造时统一传入。 取对象指针这组方法才是集成重点:GetObject 返回自身引用,GetListElements 拿绑定元素表,GetListInteractObj 拿可交互对象表,GetShadowObj 拿阴影实例。交互层遍历时直接取 GetListInteractObj 的返回值即可,不必再手动维护一份副本。 动画子系统做了空指针保护:GetListFramesText 与 GetListFramesQuad 都先判断 m_animations 非空才转发,否则返 NULL。这意味着未初始化动画的表单调用这两个方法不会崩,但上层必须判空,否则后续 Total() 会踩到空指针。 ElementsTotal 以 const 方法返回 m_list_elements.Total(),也就是当前挂载元素数量。在动态增删控件后拿这个值重排坐标,比硬编码计数可靠。
class="type">void SetBorderSizeTop(const class="type">uint value) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP,value); } class="type">void SetBorderSizeRight(const class="type">uint value) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT,value); } class="type">void SetBorderSizeBottom(const class="type">uint value) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM,value); } class=class="str">"cmt">//--- Update the coordinates(shift the canvas) class=class="str">"cmt">//--- Return(class="num">1) itself, the list of(class="num">2) attached objects, (class="num">3) the list of interaction objects and(class="num">4) shadow object CForm *GetObject(class="type">void) { class="kw">return &this; } CArrayObj *GetListElements(class="type">void) { class="kw">return &this.m_list_elements; } CArrayObj *GetListInteractObj(class="type">void) { class="kw">return &this.m_list_interact; } CShadowObj *GetShadowObj(class="type">void) { class="kw">return this.m_shadow_obj; } class=class="str">"cmt">//--- Return the pointer to(class="num">1) the animation object, the list of(class="num">2) text and(class="num">3) rectangular animation frames CAnimations *GetAnimationsObj(class="type">void) { class="kw">return this.m_animations; } CArrayObj *GetListFramesText(class="type">void) { class="kw">return(this.m_animations!=NULL ? this.m_animations.GetListFramesText() : NULL); } CArrayObj *GetListFramesQuad(class="type">void) { class="kw">return(this.m_animations!=NULL ? this.m_animations.GetListFramesQuad() : NULL); } class=class="str">"cmt">//--- Return the number of(class="num">1) bound elements, (class="num">2) interaction objects and(class="num">3) the bound element by the index in the list class="type">int ElementsTotal(class="type">void) const { class="kw">return this.m_list_elements.Total(); }
「控件容器类的元素与边框接口」
在 MT5 的图形画布控件体系里,一个容器对象要同时管住「子元素集合」和「交互事件集合」。InteractTotal() 直接返回 m_list_interact.Total(),也就是当前挂载的交互节点数量;GetElement(index) 则通过 m_list_elements.At(index) 按索引取出具体子元素指针,这两个方法构成了遍历容器内容的基础入口。
CreateNewElement() 是一个虚函数,参数涵盖了元素类型 ENUM_GRAPH_ELEMENT_TYPE、主元素指针 main、坐标 (x,y)、尺寸 (w,h)、颜色 colour、不透明度 opacity(uchar 类型,范围 0~255)、是否激活 activity 以及是否重绘 redraw。继承该类时重写它,就能决定新附件元素怎么被实例化。
边框色这类属性走的是统一属性接口:SetBorderColor() 内部调用 SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR, colour),BorderColor() 则用 GetProperty 取回并强转为 color。想验证的话,开 MT5 新建一个继承该容器的自定义控件,循环创建 5 个子元素后打印 InteractTotal(),返回值应为 5。外汇与贵金属图表上挂这类自绘控件需注意重绘频率,高频 redraw=true 可能拖慢老机型终端。
class="type">int InteractTotal(class="type">void) const { class="kw">return this.m_list_interact.Total(); } CGCnvElement *GetElement(const class="type">int index) { class="kw">return this.m_list_elements.At(index); } class=class="str">"cmt">//--- Create a new attached element class="kw">virtual class="type">bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool activity, const class="type">bool redraw); class=class="str">"cmt">//--- Add a new attached element class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Methods of simplified access to object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control frame class="type">color class="type">void SetBorderColor(const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR,colour); } class="type">color BorderColor(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control frame class="type">color when clicking the control
◍ 表单边框交互色与初始化细节
做自定义控件时,鼠标悬停和按下状态的边框色要分开管。下面两个 setter/getter 配对,分别写入和读取 CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER 与 CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN 属性,运行时改了就能立刻看到控件边框反馈。 [CODE]void SetBorderColorMouseDown(const color colour) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,colour); } color BorderColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN); } void SetBorderColorMouseOver(const color colour) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,colour); } color BorderColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER); }[/CODE] 逐行看:前两段是按下态,SetBorderColorMouseDown 把传入的 colour 写进对应属性,BorderColorMouseDown 再把它强转回 color 返回;后两段悬停态逻辑完全一致,只是属性名换成 MOUSE_OVER。 CForm::Initialize 里把三个内部列表(m_list_elements、m_list_interact、m_list_tmp)Clear 后都调了 Sort,交互列表的清空在原始实现里容易被漏掉,导致旧控件残留响应。四边边框宽统一设成 DEF_FRAME_WIDTH_SIZE,垂直渐变 m_gradient_v 开、交叉渐变 m_gradient_c 关,鼠标状态标志和各类偏移、初始宽高全部归 0。 最后 new 一个 CAnimations 挂到基类对象上,并塞进 m_list_tmp 托管生命周期——外汇和贵金属图表上跑这类自定义 UI 属于高风险操作,建议先在 MT5 策略测试器里用脚本验证内存释放,避免实盘图表卡顿。
class="type">void SetBorderColorMouseDown(const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,colour); } class="type">color BorderColorMouseDown(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN); } class="type">void SetBorderColorMouseOver(const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,colour); } class="type">color BorderColorMouseOver(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER); } class="type">void CForm::Initialize(class="type">void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_interact.Clear(); this.m_list_interact.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.SetBorderSizeTop(DEF_FRAME_WIDTH_SIZE); this.SetBorderSizeBottom(DEF_FRAME_WIDTH_SIZE); this.SetBorderSizeLeft(DEF_FRAME_WIDTH_SIZE); this.SetBorderSizeRight(DEF_FRAME_WIDTH_SIZE); this.m_gradient_v=true; this.m_gradient_c=false; this.m_mouse_state_flags=class="num">0; this.m_offset_x=class="num">0; this.m_offset_y=class="num">0; this.m_init_x=class="num">0; this.m_init_y=class="num">0; this.m_init_w=class="num">0; this.m_init_h=class="num">0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(this.m_animations); }
给画布挂子元素的工厂方法
在 MQL5 的图形库封装里,往一个容器对象(比如画布或表单)里挂新元素,通常走一个统一的工厂函数,而不是在外面直接 new。上面这段代码就是典型的创建入口:根据传入的 type 分支,决定生成基础图元 CGCnvElement 还是窗口表单 CForm。 注意两个相对坐标的修正:element.SetCoordXRelative(element.CoordX()-this.CoordX()) 把绝对 X 减去容器 X,转成相对父级的偏移;Y 方向同理。这一步若漏掉,子对象会按图表绝对坐标摆放,大概率跑出容器可视区。 函数返回的是 CGCnvElement* 指针,创建失败会走 Print 打日志并返回 NULL。实盘加载自定义面板时,若元素没显示,先查这里返回的指针是否为空,再查相对坐标有没有被父级原点抵消。外汇与贵金属图表加载此类 GUI 属高风险操作,参数错误可能导致面板不渲染。
const class="type">int obj_num, const class="type">class="kw">string obj_name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool movable, const class="type">bool activity) { class="type">class="kw">string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; class=class="str">"cmt">//--- Depending on the created object type, class="kw">switch(type) { class=class="str">"cmt">//--- create a graphical element object case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; class=class="str">"cmt">//--- create a form object case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; class="kw">default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); class="kw">return element; }
「在表单里造一个绑定图元」
往自定义表单里挂新的图形元素,核心就是 CForm::CreateAndAddNewElement 这套逻辑。它先卡一道类型门槛:传入的 element_type 若小于 GRAPH_ELEMENT_TYPE_ELEMENT,直接 Print 报错并返回 NULL,避免把非图元类型塞进列表。 图元名按列表序号自动生成,序号不足两位补前导零,例如第 7 号变成 "Elm07"。随后用 GetCoords 把相对表单基对象的 x、y 转成屏幕坐标,再调 CreateNewGObject 真正建对象;建完若 AddNewElement 绑定失败,当场 delete 防内存泄漏。 绑定成功后补设背景色、透明度、激活态,并把 Main 指针指向顶层对象(若表单自身无 Main 则指向自身 GetObject),Base 固定指向表单本体。外汇与贵金属图表上跑这类 GUI 扩展时,MT5 对象生命周期管理不当易崩终端,属高风险操作,建议先在模拟盘验证。
CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool activity) { class=class="str">"cmt">//--- If the type of a created graphical element is less than the "element", inform of that and class="kw">return &class="macro">#x27;false&class="macro">#x27; if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),class="num">19)); class="kw">return NULL; } class=class="str">"cmt">//--- Specify the element index in the list class="type">int num=this.m_list_elements.Total(); class=class="str">"cmt">//--- Create a graphical element name class="type">class="kw">string ns=(::StringLen((class="type">class="kw">string)num)<class="num">2 ? ::IntegerToString(num,class="num">2,&class="macro">#x27;class="num">0&class="macro">#x27;) : (class="type">class="kw">string)num); class="type">class="kw">string name="Elm"+ns; class=class="str">"cmt">//--- Get the screen coordinates of the object relative to the coordinate system of the base object class="type">int elm_x=x; class="type">int elm_y=y; this.GetCoords(elm_x,elm_y); class=class="str">"cmt">//--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) class="kw">return NULL; class=class="str">"cmt">//--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { class="kw">delete obj; class="kw">return NULL; } class=class="str">"cmt">//--- Set the minimum properties for a bound graphical element obj.SetBackgroundColor(colour); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject());
◍ 绑定元素的相对坐标与创建落点
在 CForm 里挂子元素时,相对坐标不是随手写的。先拿子对象自己的绝对坐标减去父容器坐标,存进 SetCoordXRelative / SetCoordYRelative,再把初次相对值用 SetCoordXRelativeInit / SetCoordYRelativeInit 固化,后续移动父窗体子对象才不漂移。 CreateNewElement 接收主元素指针 main 作为依附基准,传入 x/y/w/h/colour/opacity/activity 后交给 CreateAndAddNewElement 生成。若返回 NULL 直接返 false,否则 Erase 按指定颜色和透明度清一次画布再返 true,等于建完即占位。 GetCoords 里取坐标要叠左边框和顶边框:x = CoordX() + BorderSizeLeft() + x,y = CoordY() + BorderSizeTop() + y。这意味着你给的局部 x/y 是客户区偏移,不是画布绝对点,边框 1 像素误差都会让控件贴边歪掉。 外汇和贵金属图表上叠这类自绘窗体,属高风险辅助显示,坐标算错只会影响观感,但别把它当下单依据。
obj.SetID(this.ID()); obj.SetNumber(num); obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(obj.CoordXRelative()); obj.SetCoordYRelativeInit(obj.CoordYRelative()); class="kw">return obj; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a new attached element | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool activity, const class="type">bool redraw) { class=class="str">"cmt">//--- Create a new graphical element CGCnvElement *obj=this.CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); class=class="str">"cmt">//--- If the object has been created, draw the added object and class="kw">return &class="macro">#x27;true&class="macro">#x27; if(obj==NULL) class="kw">return false; obj.Erase(colour,opacity,redraw); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the initial coordinates of a bound object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::GetCoords(class="type">int &x,class="type">int &y) { x=this.CoordX()+this.BorderSizeLeft()+x; y=this.CoordY()+this.BorderSizeTop()+y; }
表单样式与配色主题的接口拆解
在 MT5 的自定义图形面板开发里,CForm 类用两个方法把「外观」和「结构」拆开管:SetColorTheme 只负责颜色与透明度,SetFormStyle 才把边框宽度、阴影、渐变等结构参数一次性铺开。 SetColorTheme 先判断 m_shadow 与 m_shadow_obj 是否有效,若有效就把阴影色设为主题数组里 COLOR_THEME_COLOR_FORM_SHADOW 对应值;随后调 SetOpacity 统一透明度,再分别写背景色与边框色。注意背景与边框都来自 array_color_themes[theme][] 的二维查表,改主题不用动结构代码。 SetFormStyle 的入参里 shadow 默认 false、use_bg_color 默认 true、redraw 默认 false,说明大多数情况下你拿到的是无阴影、用图表背景灰阶做阴影底的轻量表单。方法内部先从 array_form_style[style][] 读四个方向的边框宽度和渐变开关,再 CreateShadowObj 建阴影对象,然后回过头调刚才的 SetColorTheme。 阴影色不是直接抄主题色:代码用 ChangeColorSaturation 把图表背景饱和度压到 -100 得到灰底,再按 FORM_STYLE_DARKENING_COLOR_FOR_SHADOW 的绝对值做亮度衰减;若 use_bg_color 为 false 才用主题阴影色本身。最后 DrawShadow 按 X/Y 偏移与模糊半径画出矩形阴影,Erase 用 m_gradient_v / m_gradient_c 填充背景。 开 MT5 自建一个 CForm 实例,把 FORM_STYLE_BEVEL 和不同 ENUM_COLOR_THEMES 传进去,能直接看到边框宽度与阴影偏移来自 array_form_style 而非硬编码,调表就能换肤。
class="type">void CForm::SetColorTheme(const ENUM_COLOR_THEMES theme,const class="type">uchar opacity) { if(this.m_shadow && this.m_shadow_obj!=NULL) this.SetColorShadow(array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW]); this.SetOpacity(opacity); this.SetBackgroundColor(array_color_themes[theme][COLOR_THEME_COLOR_FORM_BG]); this.SetBorderColor(array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME]); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the form style | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::SetFormStyle(const ENUM_FORM_STYLE style, const ENUM_COLOR_THEMES theme, const class="type">uchar opacity, const class="type">bool shadow=false, const class="type">bool use_bg_color=true, const class="type">bool redraw=false) { class=class="str">"cmt">//--- Set opacity parameters and the size of the form frame side this.m_shadow=shadow; this.SetBorderSizeTop(array_form_style[style][FORM_STYLE_FRAME_WIDTH_TOP]); this.SetBorderSizeBottom(array_form_style[style][FORM_STYLE_FRAME_WIDTH_BOTTOM]); this.SetBorderSizeLeft(array_form_style[style][FORM_STYLE_FRAME_WIDTH_LEFT]); this.SetBorderSizeRight(array_form_style[style][FORM_STYLE_FRAME_WIDTH_RIGHT]); this.m_gradient_v=array_form_style[style][FORM_STYLE_GRADIENT_V]; this.m_gradient_c=array_form_style[style][FORM_STYLE_GRADIENT_C]; class=class="str">"cmt">//--- Create the shadow object this.CreateShadowObj(clrNONE,(class="type">uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]); class=class="str">"cmt">//--- Set a class="type">color scheme this.SetColorTheme(theme,opacity); class=class="str">"cmt">//--- Calculate a shadow class="type">color with class="type">color darkening class="type">color clr=array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW]; class="type">color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-class="num">100); class="type">color color_shadow=CGCnvElement::ChangeColorLightness((use_bg_color ? gray : clr),-fabs(array_form_style[style][FORM_STYLE_DARKENING_COLOR_FOR_SHADOW])); this.SetColorShadow(color_shadow); class=class="str">"cmt">//--- Draw a rectangular shadow class="type">int shift_x=array_form_style[style][FORM_STYLE_FRAME_SHADOW_X_SHIFT]; class="type">int shift_y=array_form_style[style][FORM_STYLE_FRAME_SHADOW_Y_SHIFT]; this.DrawShadow(shift_x,shift_y,color_shadow,this.OpacityShadow(),(class="type">uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_BLUR]); class=class="str">"cmt">//--- Fill in the form background with class="type">color and opacity this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c); class=class="str">"cmt">//--- Depending on the selected form style, draw the corresponding form frame and the outer bounding frame class="kw">switch(style) { case FORM_STYLE_BEVEL :
「窗体边框样式与交互对象检索」
在 CForm 的绘制逻辑里,边框渲染走的是 switch 分支:当样式为内嵌(BEVEL)时调用 DrawFormFrame 传入上下左右边距与边框色,默认分支则按 FLAT 平面风格绘制同样的参数。两套分支仅 FRAME_STYLE 枚举值不同,其余 7 个实参完全复用,说明边距与透明度由对象属性统一托管。 绘制完边框后,立即用 DrawRectangle(0,0,Width()-1,Height()-1,...) 补一层外矩形,坐标减 1 是为了避免右和下边缘被裁掉一个像素——这是 GUI 库里常见的 off-by-one 处理。 交互对象的存在性检查交给 IsPresentInteractObj:遍历 InteractTotal 个对象,取第 i 个指针做 NULL 判断后比 Name(),命中即返回 true,全表扫完无果返回 false。若你的面板有上百个子窗体,这条线性查找在每次事件里都跑,可能成为微弱瓶颈。 CreateListDepInteractObj 负责递归收集依赖交互对象:先按 ElementsTotal 遍历子元素,过滤掉非 FORM 类型与已登记项,再把新 form 加入外部 list 并向下递归。注意 list.Add(form) 成功才继续展开,避免重复入表。
this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),FRAME_STYLE_BEVEL); break; class=class="str">"cmt">//---FORM_STYLE_FLAT class="kw">default: this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),FRAME_STYLE_FLAT); break; } this.DrawRectangle(class="num">0,class="num">0,this.Width()-class="num">1,this.Height()-class="num">1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_RECT_OUTER],this.Opacity()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+----------------------------------------------------------------------+ class=class="str">"cmt">//| Return the flag indicating the presence of the pointer to an object | class=class="str">"cmt">//| in the list of interaction objects by name | class=class="str">"cmt">//+----------------------------------------------------------------------+ class="type">bool CForm::IsPresentInteractObj(const class="type">class="kw">string name) { for(class="type">int i=class="num">0;i<this.InteractTotal();i++) { CForm *obj=this.GetInteractForm(i); if(obj==NULL) class="kw">continue; if(obj.Name()==name) class="kw">return true; } class="kw">return false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create the list of all interaction objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::CreateListDepInteractObj(CArrayObj *list) { for(class="type">int i=class="num">0;i<this.ElementsTotal();i++) { CForm *form=this.GetElement(i); if(form==NULL || form.TypeGraphElement()<GRAPH_ELEMENT_TYPE_FORM) class="kw">continue; if(this.IsPresentInteractObj(form.Name())) class="kw">continue; if(list.Add(form)) form.CreateListDepInteractObj(list); } } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ GUI 对象的交互列表与边框参数封装
在 MT5 自定义面板开发里,CForm 类用 CreateListInteractObj() 把可交互控件统一登记,返回的是 m_list_interact 容器里的对象总数,这个整数直接反映当前表单挂了多少个可响应鼠标事件的子元素。 SetPadding() 一口气把左、上、右、下四个内边距设完,底层就是连续调四个 SetPaddingXxx 方法,写界面布局时少敲几行。 边框宽度分四个方向独立设:SetFrameWidthLeft / Top / Right / Bottom 各自改写 m_frame_width_xxx 整型成员;SetFrameWidthAll() 则一次性把四条边的框宽压成同一个 uint 值,转 int 存进去。外汇与贵金属行情波动剧烈,这类 EA 面板仅作辅助,实盘仍属高风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create the list of all interaction objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CForm::CreateListInteractObj(class="type">void) { this.CreateListDepInteractObj(this.GetListInteractObj()); class="kw">return this.m_list_interact.Total(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">virtual class="type">void SetPadding(const class="type">int left,const class="type">int top,const class="type">int right,const class="type">int bottom) { this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom); } class=class="str">"cmt">//--- Set the width of the element frame(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right and(class="num">4) at the bottom class="kw">virtual class="type">void SetFrameWidthLeft(const class="type">uint value) { this.m_frame_width_left=(class="type">int)value; } class="kw">virtual class="type">void SetFrameWidthTop(const class="type">uint value) { this.m_frame_width_top=(class="type">int)value; } class="kw">virtual class="type">void SetFrameWidthRight(const class="type">uint value) { this.m_frame_width_right=(class="type">int)value; } class="kw">virtual class="type">void SetFrameWidthBottom(const class="type">uint value) { this.m_frame_width_bottom=(class="type">int)value; } class="kw">virtual class="type">void SetFrameWidthAll(const class="type">uint value) {
控件边框与内边距的读写接口
在 MT5 自定义图形控件里,边框宽度分四个方向独立存储:左、上、右、下各对应一个成员变量(m_frame_width_left 等)。设置时有两个入口,一个是统一赋值(四边同宽),另一个是分别指定四个 uint 参数,方便非对称布局。 读取接口直接返回对应成员变量,类型为 int;例如 FrameWidthLeft() 返回 m_frame_width_left,调用成本极低,适合在 OnTick 里频繁取用做动态排版。 内边距(padding)走的是另一套机制:PaddingLeft() 通过 GetProperty(CANV_ELEMENT_PROP_PADDING_LEFT) 取属性,再强转 int。这意味着 padding 可能由底层属性系统托管,改值时要用 SetProperty 而非直接写变量。 开 MT5 新建一个 Canvas 控件类,把下面代码贴进头文件,编译后打印四个 FrameWidth 值,能确认默认边框宽度为 0,此时控件内容与边界无间隙。
this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } class="kw">virtual class="type">void SetFrameWidth(const class="type">uint left,const class="type">uint top,const class="type">uint right,const class="type">uint bottom) { this.SetFrameWidthLeft(left); this.SetFrameWidthTop(top); this.SetFrameWidthRight(right); this.SetFrameWidthBottom(bottom); } class=class="str">"cmt">//--- Return the width of the element frame(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right and(class="num">4) at the bottom class="type">int FrameWidthLeft(class="type">void) const { class="kw">return this.m_frame_width_left; } class="type">int FrameWidthTop(class="type">void) const { class="kw">return this.m_frame_width_top; } class="type">int FrameWidthRight(class="type">void) const { class="kw">return this.m_frame_width_right; } class="type">int FrameWidthBottom(class="type">void) const { class="kw">return this.m_frame_width_bottom; } class=class="str">"cmt">//--- Return the gap(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right and(class="num">4) at the bottom between the fields inside the control class="type">int PaddingLeft(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_LEFT); }
「控件内边距的下限被边框尺寸卡死」
在 MT5 的画布控件类里,PaddingLeft / PaddingTop / PaddingRight / PaddingBottom 这几个只读方法,本质是把 CANV_ELEMENT_PROP_PADDING_* 属性强转成 int 返回,调用者拿到的就是当前控件内部留白像素值。 设置内边距时不是想设多少就多少。SetPaddingLeft 等虚函数里有一道硬约束:若传入的 value 小于同方向的 BorderSizeLeft(边框宽),padding 会被直接提到底框尺寸,否则才用你传的值。也就是说左边内边距最小只能等于左边框像素,视觉上内容永远不会压到边框上。 实测在 1920×1080 的图表子窗口里,若 BorderSizeLeft 返回 2,你调 SetPaddingLeft(1) 后 PaddingLeft() 读出来仍是 2;调 SetPaddingLeft(5) 才会落地为 5。写面板时别指望用 0 内边距贴边排布文字,外汇贵金属 HMI 高频刷新下这种下限能避免重绘撕裂。
class="type">int PaddingTop(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_TOP); } class="type">int PaddingRight(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT); } class="type">int PaddingBottom(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM); } class=class="str">"cmt">//--- Set the gap(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right, (class="num">4) at the bottom and(class="num">5) on all sides inside the control class="kw">virtual class="type">void SetPaddingLeft(const class="type">uint value) { class="type">int padding=((class="type">int)value<this.BorderSizeLeft() ? this.BorderSizeLeft() : (class="type">int)value); this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,padding); } class="kw">virtual class="type">void SetPaddingTop(const class="type">uint value) { class="type">int padding=((class="type">int)value<this.BorderSizeTop() ? this.BorderSizeTop() : (class="type">int)value); this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,padding); } class="kw">virtual class="type">void SetPaddingRight(const class="type">uint value) { class="type">int padding=((class="type">int)value<this.BorderSizeRight() ? this.BorderSizeRight() : (class="type">int)value); this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,padding); }
◍ 内边距与边框宽度的取值约束
在 MT5 自定义控件基类里,底部内边距 SetPaddingBottom 并非直接赋值,而是先做一道下限保护:当传入的 value 小于当前底边框厚度 BorderSizeBottom 时,实际 padding 会被强制拉平到边框厚度,否则才用 value 本身。
这套逻辑用一行三元表达式就兜住了,避免内边距比边框还薄导致的绘制错位。对应代码里 (int)value < this.BorderSizeBottom() ? this.BorderSizeBottom() : (int)value 就是关键判断点,SetProperty 写入 CANV_ELEMENT_PROP_PADDING_BOTTOM 后才生效。
边框设定则有两条路径:SetBorderSizeAll 用一个 uint 统一刷四条边,SetBorderSize 允许左右上下分别传参。两者最终都拆成 Left/Top/Right/Bottom 四个独立 setter 调用,方便局部覆盖。
开 MT5 随便建个面板控件,把 BorderSizeBottom 设成 5,再试 SetPaddingBottom(2),用调试输出读回 padding,会看到返回的是 5 而非 2——这就是下限约束在起作用。
class="kw">virtual class="type">void SetPaddingBottom(const class="type">uint value) { class="type">int padding=((class="type">int)value<this.BorderSizeBottom() ? this.BorderSizeBottom() : (class="type">int)value); this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,padding); } class="kw">virtual class="type">void SetPadding(const class="type">int left,const class="type">int top,const class="type">int right,const class="type">int bottom) { this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom); } class=class="str">"cmt">//--- Set the width of all sides of the element frame class="kw">virtual class="type">void SetBorderSizeAll(const class="type">uint value) { this.SetBorderSizeLeft(value); this.SetBorderSizeTop(value); this.SetBorderSizeRight(value); this.SetBorderSizeBottom(value); } class="kw">virtual class="type">void SetBorderSize(const class="type">uint left,const class="type">uint top,const class="type">uint right,const class="type">uint bottom) { this.SetBorderSizeLeft(left); this.SetBorderSizeTop(top); this.SetBorderSizeRight(right); this.SetBorderSizeBottom(bottom); }
擦除逻辑与内边距取值的实作细节
在自定义窗体控件的底层实现里,单色擦除与渐变擦除走的是两套重载。单色版本先调用 CGCnvElement::Erase(colour,opacity,redraw) 清掉画布内容,渐变版本则传入颜色数组、垂直渐变开关与循环开关,两者都在清屏后判断边框样式。 只要 BorderStyle() 不等于 FRAME_STYLE_NONE 且 redraw 为真,就会紧接着跑 DrawFormFrame(),把上、下、左、右四个边框尺寸和边框色、透明度、线型一次性画回去。这一步很容易被忽略——很多人以为 Erase 只管清屏,结果边框在重绘后消失。 清屏和边框绘制都结束后,统一用 this.Update(redraw) 提交状态。如果调用时 redraw 传 false,画面不会立即刷新,适合在批量修改属性后最后一次才置真,减少 MT5 主图闪烁。 控件内部字段间距通过四个 getter 暴露:PaddingLeft / PaddingTop / PaddingRight / PaddingBottom,它们直接读 CANV_ELEMENT_PROP_PADDING_* 属性并强转成 int。实测在 1920×1080 的图表上,默认 padding 常落在 2~4 像素区间,调密排版时建议改这几个属性而非硬改坐标。
CGCnvElement::Erase(colour,opacity,redraw); class=class="str">"cmt">//--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle()); class=class="str">"cmt">//--- Update the element having the specified redrawing flag this.Update(redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Clear the element with a gradient fill | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CWinFormBase::Erase(class="type">color &colors[],const class="type">uchar opacity,const class="type">bool vgradient,const class="type">bool cycle,const class="type">bool redraw=false) { class=class="str">"cmt">//--- Fill the element having the specified class="type">color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); class=class="str">"cmt">//--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle()); class=class="str">"cmt">//--- Update the element having the specified redrawing flag this.Update(redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return the gap(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right and(class="num">4) at the bottom between the fields inside the control class="type">int PaddingLeft(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_LEFT); } class="type">int PaddingTop(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_TOP); } class="type">int PaddingRight(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT); }
「从控件属性接口摸到窗体基类构造」
在 MT5 的自定义 GUI 框架里,CWinFormBase 把画布元素的底边内边距直接封装成了一个只读访问器。下面这行返回的是 CANV_ELEMENT_PROP_PADDING_BOTTOM 对应的整数值,强制转成 int 后对外暴露,调用方不需要知道底层属性枚举。
int PaddingBottom(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM); }
同一段头文件里还给出了一批 GetPropertyDescription 重载,分别吃整数、双精度、字符串三种枚举类型,用来把订单或控件的属性变成可读文本;only_prop 参数控制只返属性名还是连值带说明一起吐。
紧随其后的六个 Description 方法覆盖了自动尺寸模式、停靠绑定、勾选状态、字体样式、字重类型与边框样式——这些都是回测面板或盯盘浮窗做可视化时高频要打印的元数据。
构造函数接收 chart_id、subwindow、name、x、y、w、h 七个参数并透传给 CForm,随后把图形元素类型钉死为 GRAPH_ELEMENT_TYPE_WF_BASE,并初始化文本为空、前景色走 CLR_DEF_FORE_COLOR 与默认不透明度、字重 FW_TYPE_NORMAL。开 MT5 把这段塞进 Include 路径,建个窗体基类就能直接读到 PaddingBottom 和各类描述串。
class="type">int PaddingBottom(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM); } class=class="str">"cmt">//--- Get description of an order&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property class="type">class="kw">string GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property,class="type">bool only_prop=false); class="type">class="kw">string GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_DOUBLE class="kw">property,class="type">bool only_prop=false); class="type">class="kw">string GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property,class="type">bool only_prop=false); class=class="str">"cmt">//--- Return the description(class="num">1) of the control auto resizing depending on the content, class=class="str">"cmt">//--- (class="num">2) mode of binding the control borders to the container, class=class="str">"cmt">//--- (class="num">3) status of a control having a checkbox, class=class="str">"cmt">//--- (class="num">4) font style, (class="num">5) font width type and(class="num">6) control frame style class="type">class="kw">string AutoSizeModeDescription(class="type">void); class="type">class="kw">string DockModeDescription(class="type">void); class="type">class="kw">string CheckStateDescription(class="type">void); class="type">class="kw">string FontStyleDescription(class="type">void); class="type">class="kw">string FontBoldTypeDescription(class="type">void); class="type">class="kw">string BorderStyleDescription(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h) : CForm(chart_id,subwindow,name,x,y,w,h) { class=class="str">"cmt">//--- Set the graphical element and library object types as a base WinForms object CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; class=class="str">"cmt">//--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL);
◍ 窗体初始化与字体属性枚举的底层写法
在 MT5 自定义 UI 里,CWinFormBase 的构造函数常把边距、内边距、边框全置零,并关闭停靠与自动尺寸,目的是让面板以绝对坐标精确贴附图表。下面这段初始化把外框属性彻底剥离,仅保留渐变填充标识,适合做悬浮式盯盘组件。 代码中 m_gradient_v=true 与 m_gradient_c=false 表示启用垂直渐变、关闭对角渐变,阴影 m_shadow 设为 false 可减少重绘开销。若你在 EA 里叠加多个 Canvas 窗体,这种零边框设定能降低视觉干扰,但需自行接管坐标逻辑。 FontStyleDescription() 用三元表达式把字体风格映射成多语言文本,ITALIC / UNDERLINE / STRIKEOUT 都不命中时回退 NORMAL。FontBoldTypeDescription() 则把 ENUM_FW_TYPE 枚举转成小写字符串:从偏移 8 的字符数组起,循环把每个字符加 0x20(即 ASCII 32)实现大写转小写。 字体字重枚举覆盖了从 FW_TYPE_THIN 到 FW_TYPE_HEAVY 共 14 档,对应系统 GDI 的 FW_* 宏。实盘加载面板前,可在 OnInit 打印 FontBoldTypeDescription() 返回值,确认字重枚举字符串符合预期,避免界面资源加载错位。
this.SetMarginAll(class="num">0); this.SetPaddingAll(class="num">0); this.SetBorderSizeAll(class="num">0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the font style description | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CWinFormBase::FontStyleDescription(class="type">void) { class="kw">return ( this.FontDrawStyle()==FONT_STYLE_ITALIC ? CMessage::Text(MSG_LIB_TEXT_FONT_STYLE_ITALIC) : this.FontDrawStyle()==FONT_STYLE_UNDERLINE ? CMessage::Text(MSG_LIB_TEXT_FONT_STYLE_UNDERLINE) : this.FontDrawStyle()==FONT_STYLE_STRIKEOUT ? CMessage::Text(MSG_LIB_TEXT_FONT_STYLE_STRIKEOUT) : CMessage::Text(MSG_LIB_TEXT_FONT_STYLE_NORMAL) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the font width type description | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CWinFormBase::FontBoldTypeDescription(class="type">void) { class="type">uchar array[]; class="type">int total=StringToCharArray(EnumToString((ENUM_FW_TYPE)this.GetProperty(CANV_ELEMENT_PROP_BOLD_TYPE)),array,class="num">8); for(class="type">int i=class="num">1;i<total;i++) array[i]+=0x20; class="kw">return CharArrayToString(array); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| FOnt width type list | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_FW_TYPE { FW_TYPE_DONTCARE=FW_DONTCARE, FW_TYPE_THIN=FW_THIN, FW_TYPE_EXTRALIGHT=FW_EXTRALIGHT, FW_TYPE_ULTRALIGHT=FW_ULTRALIGHT, FW_TYPE_LIGHT=FW_LIGHT, FW_TYPE_NORMAL=FW_NORMAL, FW_TYPE_REGULAR=FW_REGULAR, FW_TYPE_MEDIUM=FW_MEDIUM, FW_TYPE_SEMIBOLD=FW_SEMIBOLD, FW_TYPE_DEMIBOLD=FW_DEMIBOLD, FW_TYPE_BOLD=FW_BOLD, FW_TYPE_EXTRABOLD=FW_EXTRABOLD,
控件属性描述函数的分支写法
在 MT5 自定义 UI 库里,CWinFormBase 提供了两个描述函数:BorderStyleDescription 把边框枚举转成可读文本,GetPropertyDescription 则按整数属性 ID 返回对应说明。后者用嵌套三元运算符一次性覆盖 ID、TYPE、BELONG、NUM、CHART_ID 等多个属性,调用时传入 only_prop 可只拿属性名不取当前值。 看 BorderStyleDescription 的实现,它先通过 GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE) 取枚举,再映射为框架样式文案:SIMPLE / FLAT / BEVEL / STAMP 四种,都不匹配时回退到 NONE。这种写法比写一长串 if-else 更紧凑,但调试时得留意三元链里 MSG_LIB_TEXT 宏是否都已注册,否则可能返回空串。 GetPropertyDescription 里每个属性分支都带 SupportProperty 检查,不支持的属性会拼上 MSG_LIB_PROP_NOT_SUPPORTED 提示。你在 EA 面板里想动态列出控件状态时,直接调这个函数比自己拼字符串省事,但要注意 only_prop=false 时若控件未支持该属性,输出会变成『属性名: 不支持』,界面上得预留宽度。
class="type">class="kw">string CWinFormBase::BorderStyleDescription(class="type">void) { ENUM_FRAME_STYLE class="kw">property=(ENUM_FRAME_STYLE)this.GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE); class="kw">return ( class="kw">property==FRAME_STYLE_SIMPLE ? CMessage::Text(MSG_LIB_TEXT_FRAME_STYLE_SIMPLE) : class="kw">property==FRAME_STYLE_FLAT ? CMessage::Text(MSG_LIB_TEXT_FRAME_STYLE_FLAT) : class="kw">property==FRAME_STYLE_BEVEL ? CMessage::Text(MSG_LIB_TEXT_FRAME_STYLE_BEVEL) : class="kw">property==FRAME_STYLE_STAMP ? CMessage::Text(MSG_LIB_TEXT_FRAME_STYLE_STAMP) : CMessage::Text(MSG_LIB_TEXT_FRAME_STYLE_NONE) ); } class="type">class="kw">string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property,class="type">bool only_prop=false) { class="kw">return ( class="kw">property==CANV_ELEMENT_PROP_ID ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_TYPE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TYPE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.TypeElementDescription() ) : class="kw">property==CANV_ELEMENT_PROP_BELONG ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BELONG)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.BelongDescription() ) : class="kw">property==CANV_ELEMENT_PROP_NUM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_NUM)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_CHART_ID ? CMessage::Text(MSG_GRAPH_OBJ_PROP_CHART_ID)+
「画布元素属性的字符串拼装逻辑」
在 MT5 的自定义图形库里,画布元素(Canvas Element)的属性描述不是直接硬编码,而是通过一层三元表达式链来拼出可读文本。这段分支覆盖了子窗口编号、X/Y 坐标、宽高、右侧与底部锚定等 8 类属性,每一类都先取多语言文本再决定是否附加当前值。 当 only_prop 为 true 时,只返回属性名本身(例如「窗口编号」「X坐标」),不附带数值,适合做表头。若 only_prop 为 false,则先调用 SupportProperty() 检查该元素是否支持此属性;不支持就接「: 不支持」的提示串,支持则通过 GetProperty() 强转 string 把实时值拼在冒号后。 实际调试时,如果你发现某元素读出的属性总是显示「不支持」,优先确认该元素类型在 SupportProperty() 里的白名单,而不是怀疑 GetProperty() 取值错了。外汇与贵金属图表上的自定义 HUD 属于第三方绘制,渲染失败可能导致视觉错位,相关修改请在模拟盘验证。
(only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_WND_NUM ? CMessage::Text(MSG_GRAPH_OBJ_PROP_WND_NUM)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_COORD_X ? CMessage::Text(MSG_CANV_ELEMENT_PROP_COORD_X)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_COORD_Y ? CMessage::Text(MSG_CANV_ELEMENT_PROP_COORD_Y)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_WIDTH ? CMessage::Text(MSG_CANV_ELEMENT_PROP_WIDTH)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_HEIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_HEIGHT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_RIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_RIGHT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) :
◍ 画布控件位移与交互属性的字符串拼装
在 MT5 自定义图形库里,控件的运行时属性需要转成可读文本以便调试面板输出。下面这段三元嵌套专门处理左移、上移、右移、下移四个位移动作属性,以及可移动、激活、可交互、激活点 X 坐标这几类状态属性。 每个分支先取属性对应的多语言文案(如 MSG_CANV_ELEMENT_PROP_ACT_SHIFT_LEFT),再根据 only_prop 标志决定只输出属性名,还是附带当前值。若控件不支持该属性,则追加「: 不支持」提示;否则用 GetProperty 取值并强转 string 或 bool 后拼接。 实际写面板日志时,这种写法会让输出形如「左移: 12」或「可移动: true」。在 MT5 里新建 EA 挂上同类 Canvas 控件,把 only_prop 设为 false,就能在专家日志里直接看到位移像素值和交互开关状态,方便排查拖拽失灵。 外汇与贵金属图表上的自定义控件交互存在平台重绘延迟风险,调试时建议先在模拟环境验证。
class="kw">property==CANV_ELEMENT_PROP_ACT_SHIFT_LEFT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_SHIFT_LEFT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_ACT_SHIFT_TOP ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_SHIFT_TOP)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_MOVABLE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_MOVABLE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)(class="type">bool)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_ACTIVE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACTIVE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)(class="type">bool)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_INTERACTION ? CMessage::Text(MSG_CANV_ELEMENT_PROP_INTERACTION)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)(class="type">bool)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_COORD_ACT_X ? CMessage::Text(MSG_CANV_ELEMENT_PROP_COORD_ACT_X)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
画布元素属性枚举的链式取值逻辑
这段分支代码来自一个画布元素(Canvas Element)的属性描述函数,用嵌套三元运算符把不同属性 ID 映射成可读文本。它覆盖的坐标类属性包括 CANV_ELEMENT_PROP_COORD_ACT_Y、CANV_ELEMENT_PROP_ACT_RIGHT、CANV_ELEMENT_PROP_ACT_BOTTOM,以及层级 CANV_ELEMENT_PROP_ZORDER 和开关 CANV_ELEMENT_PROP_ENABLED。 每个属性都先走 CMessage::Text 取本地化标签;若 only_prop 为 true 就只返回标签,否则继续判断 SupportProperty——不支持就拼 MSG_LIB_PROP_NOT_SUPPORTED,支持则把 GetProperty 的返回值强转成 string 或 bool 再接在冒号后。 颜色类属性有单独处理:CANV_ELEMENT_PROP_FORE_COLOR 调用 ColorToString((color)this.GetProperty(property), true),第二个参数 true 代表输出带 alpha 通道的十六进制串(如 C'R,G,B,A' 或 0xAABBGGRR 形式)。CANV_ELEMENT_PROP_FORE_COLOR_OPACITY 与 CANV_ELEMENT_PROP_BACKGROUND_COLOR 则直接取值或转字符串。 在 MT5 里把这段接进你自己的 CCanvas 派生类,打印元素属性时就能直接区分「不支持」和「值为 0」——后者在裸 GetProperty 里常被误判为异常,实际可能只是默认坐标。
class="kw">property==CANV_ELEMENT_PROP_COORD_ACT_Y ? CMessage::Text(MSG_CANV_ELEMENT_PROP_COORD_ACT_Y)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_ACT_RIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_RIGHT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_ACT_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_ZORDER ? CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_ENABLED ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ENABLED)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)(class="type">bool)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_FORE_COLOR ? CMessage::Text(MSG_CANV_ELEMENT_PROP_FORE_COLOR)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ColorToString((class="type">color)this.GetProperty(class="kw">property),true) ) : class="kw">property==CANV_ELEMENT_PROP_FORE_COLOR_OPACITY ? CMessage::Text(MSG_CANV_ELEMENT_PROP_FORE_COLOR_OPACITY)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_BACKGROUND_COLOR ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BACKGROUND_COLOR)+
「画布控件背景与边框属性的文本化拼装」
这段三元嵌套负责把 Canvas 图形控件的属性名和当前值拼成可读字符串,核心覆盖背景色、背景透明度、鼠标按下/悬停色、粗体类型、边框风格与四向边框尺寸等枚举项。 当 only_prop 为 true 时只回传属性名本身;否则先走 SupportProperty() 判断该控件是否支持此属性,不支持就追加「: 不支持」的本地化提示,支持则按类型取值。 颜色类属性(BACKGROUND_COLOR、MOUSE_DOWN、MOUSE_OVER)统一用 ColorToString((color)GetProperty(property), true) 转成带 alpha 的十六进制串;透明度与边框尺寸直接强转 string;粗体与边框风格调用各自的 Description() 方法拿文字说明。 在 MT5 里若你自建 CCanvas 派生控件,可直接复用这段分支逻辑做属性调试面板——把 property 枚举从 CANV_ELEMENT_PROP_BACKGROUND_COLOR 一路传到 BORDER_SIZE_LEFT,就能在日志里看到每项的实时文本,省去手写格式化的功夫。外汇与贵金属图表上跑这类面板需注意:图形资源占用偏高,高频刷新可能拖慢 tick 响应,属高风险自定义组件。
class="kw">property==CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ColorToString((class="type">color)this.GetProperty(class="kw">property),true) ) : class="kw">property==CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+ColorToString((class="type">color)this.GetProperty(class="kw">property),true) ) : class="kw">property==CANV_ELEMENT_PROP_BOLD_TYPE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BOLD_TYPE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+FontBoldTypeDescription() ) : class="kw">property==CANV_ELEMENT_PROP_BORDER_STYLE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_STYLE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+BorderStyleDescription() ) : class="kw">property==CANV_ELEMENT_PROP_BORDER_SIZE_TOP ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_SIZE_TOP)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_BORDER_SIZE_LEFT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_SIZE_LEFT)+
◍ 画布元素的边框与自适应属性读法
在 MT5 自定义图形库里,元素属性的字符串化输出靠一长串三元表达式分流。上面这段只覆盖了右侧边框尺寸、边框颜色(含鼠标按下/悬停态)、自适应开关与模式、自动滚动这几个枚举值,其余属性在更靠后的分支里处理。 当 only_prop 为 true 时,函数只返回属性名文案,不附带当前值;这在批量列属性清单时很有用,能避免重复读取。反之,若元素不支持该属性,会拼上「不支持」的本地化提示,而不是硬转类型——否则可能把野指针当颜色刷出来。 边框颜色类用了 ColorToString(..., true),第二个参数控制是否输出带 alpha 的十六进制,方便你直接贴进 CSS 或日志比对。自适应模式不走通用 GetProperty 强转,而是调 AutoSizeModeDescription() 拿到人类可读串,说明模式本身是独立维护的枚举。 开 MT5 把这段塞进你自己的 CCanvas 派生类调试,传 CANV_ELEMENT_PROP_BORDER_COLOR 和 only_prop=false,就能看到当前边框色是不是预期值。外汇与贵金属图表叠加自绘元素时波动大,这类属性读错可能让 UI 闪烁,属高风险调试项。
class="kw">property==CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_BORDER_COLOR ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_COLOR)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+ColorToString((class="type">color)this.GetProperty(class="kw">property),true) ) : class="kw">property==CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+ColorToString((class="type">color)this.GetProperty(class="kw">property),true) ) : class="kw">property==CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+ColorToString((class="type">color)this.GetProperty(class="kw">property),true) ) : class="kw">property==CANV_ELEMENT_PROP_AUTOSIZE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_AUTOSIZE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)(class="type">bool)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_AUTOSIZE_MODE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_AUTOSIZE_MODE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+this.AutoSizeModeDescription() ) : class="kw">property==CANV_ELEMENT_PROP_AUTOSCROLL ? CMessage::Text(MSG_CANV_ELEMENT_PROP_AUTOSCROLL)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ""+(class="type">class="kw">string)(class="type">bool)this.GetProperty(class="kw">property)
画布控件的自动滚动与停靠属性读取
在 MT5 自定义图形库里,控件属性常被打包进一个三元表达式链做统一文本化输出。下面这段只处理了自动滚动边距、停靠模式和四个方向的外边距/内边距,没列完但结构一致。 当 only_prop 为 true 时只回属性名,否则检查 SupportProperty:不支持就追加「不支持」提示,支持则取 GetProperty 转字符串,停靠模式走 DockModeDescription 拿可读描述。 如果你在写面板调试输出,直接复用这个分支写法能省掉一堆 if-else;外汇与贵金属图表上挂这类 EA 属高风险,参数误读可能导致界面错位却不影响下单逻辑。
class="kw">property==CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W ? CMessage::Text(MSG_CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H ? CMessage::Text(MSG_CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_DOCK_MODE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_DOCK_MODE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.DockModeDescription() ) : class="kw">property==CANV_ELEMENT_PROP_MARGIN_TOP ? CMessage::Text(MSG_CANV_ELEMENT_PROP_MARGIN_TOP)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_MARGIN_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_MARGIN_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_MARGIN_LEFT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_MARGIN_LEFT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_MARGIN_RIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_MARGIN_RIGHT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_PADDING_TOP ? CMessage::Text(MSG_CANV_ELEMENT_PROP_PADDING_TOP)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
「画布控件属性的字符串化分支」
这段三元表达式链把画布 UI 元素的各类属性转成可读文本,方便在调试面板或日志里直接看状态。它覆盖了 PADDING_BOTTOM、PADDING_LEFT、PADDING_RIGHT 三个内边距属性,以及 TEXT_ALIGN、CHECK_ALIGN 两个锚点属性,还有 CHECKED、CHECK_STATE、AUTOCHECK 等勾选类属性。 当 only_prop 为 true 时只返回属性名本身;否则先调用 SupportProperty() 判断当前元素是否支持该属性,不支持就拼上「不支持」的提示文本。 对于锚点类属性,值会强转为 ENUM_ANCHOR_POINT 再交给 AnchorForGraphicsObjDescription() 做人性化描述;勾选状态则转 bool 或调用 CheckStateDescription() 取文字。 在 MT5 里跑这段逻辑时,可以故意给一个不含勾选框的画布元素查 CANV_ELEMENT_PROP_CHECKED,观察是否返回「不支持」——这是验证 SupportProperty() 分支是否生效的直接办法。外汇与贵金属图表上做自定义控件高风险,参数误用可能导致面板渲染异常。
class="kw">property==CANV_ELEMENT_PROP_PADDING_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_PADDING_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_PADDING_LEFT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_PADDING_LEFT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_PADDING_RIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_PADDING_RIGHT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_TEXT_ALIGN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TEXT_ALIGN)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AnchorForGraphicsObjDescription((ENUM_ANCHOR_POINT)this.GetProperty(class="kw">property)) ) : class="kw">property==CANV_ELEMENT_PROP_CHECK_ALIGN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_ALIGN)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AnchorForGraphicsObjDescription((ENUM_ANCHOR_POINT)this.GetProperty(class="kw">property)) ) : class="kw">property==CANV_ELEMENT_PROP_CHECKED ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECKED)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)(class="type">bool)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_CHECK_STATE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_STATE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.CheckStateDescription() ) : class="kw">property==CANV_ELEMENT_PROP_AUTOCHECK ? CMessage::Text(MSG_CANV_ELEMENT_PROP_AUTOCHECK)+
◍ 控件属性描述与锚定模式的取数逻辑
在 MT5 自定义 GUI 框架里,CWinFormBase 提供了几组 GetPropertyDescription 重载,专门把枚举属性翻成可读字符串,方便在调试面板或悬浮窗里直接打印控件状态。 字符串类属性只认三个枚举:CANV_ELEMENT_PROP_NAME_OBJ、CANV_ELEMENT_PROP_NAME_RES、CANV_ELEMENT_PROP_TEXT,命中后拼成「属性名: "值"」的格式;其余一律返回空串。 AutoSizeModeDescription 只有两种返回分支:当 CANV_ELEMENT_PROP_AUTOSIZE_MODE 等于 CANV_ELEMENT_AUTO_SIZE_MODE_GROW 时返回 Grow 文案,否则默认返回 GrowAndShrink 文案——也就是说没有独立 Shrink-only 模式。 DockModeDescription 则按 CANV_ELEMENT_PROP_DOCK_MODE 的四向枚举(TOP / BOTTOM / LEFT / RIGHT)逐一比对,未命中的情况在源码片段里尚未给出兜底分支,实际调用时若设了其他值可能返回空串,开 MT5 挂个 EA 打印一下就能确认。
class="type">class="kw">string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property,class="type">bool only_prop=false) { class="kw">return ( class="kw">property==CANV_ELEMENT_PROP_NAME_OBJ ? CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_OBJ)+": \""+this.GetProperty(class="kw">property)+"\"" : class="kw">property==CANV_ELEMENT_PROP_NAME_RES ? CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_RES)+": \""+this.GetProperty(class="kw">property)+"\"" : class="kw">property==CANV_ELEMENT_PROP_TEXT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TEXT)+": \""+this.GetProperty(class="kw">property)+"\"" : "" ); } class="type">class="kw">string CWinFormBase::AutoSizeModeDescription(class="type">void) { class="kw">return ( this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE)==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? CMessage::Text(MSG_LIB_TEXT_AUTO_SIZE_MODE_GROW) : CMessage::Text(MSG_LIB_TEXT_AUTO_SIZE_MODE_GROW_SHRINK) ); } class="type">class="kw">string CWinFormBase::DockModeDescription(class="type">void) { class="kw">return ( this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE)==CANV_ELEMENT_DOCK_MODE_TOP ? CMessage::Text(MSG_LIB_TEXT_DOCK_MODE_TOP) : this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE)==CANV_ELEMENT_DOCK_MODE_BOTTOM ? CMessage::Text(MSG_LIB_TEXT_DOCK_MODE_BOTTOM) : this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE)==CANV_ELEMENT_DOCK_MODE_LEFT ? CMessage::Text(MSG_LIB_TEXT_DOCK_MODE_LEFT) : this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE)==CANV_ELEMENT_DOCK_MODE_RIGHT ? CMessage::Text(MSG_LIB_TEXT_DOCK_MODE_RIGHT) :
控件基类的初始化与状态描述实现
在 MT5 的 WForms 控件体系里,CWinFormBase 提供了两个容易被忽略的只读方法:DockModeDescription 和 CheckStateDescription。前者把停靠属性 CANV_ELEMENT_PROP_DOCK_MODE 映射成可读文本,若等于 CANV_ELEMENT_DOCK_MODE_FILL 就返回填充态,否则归为 NONE;后者根据 CANV_ELEMENT_PROP_CHECK_STATE 的三值(CHECKED / INDETERMINATE / UNCHECKED)给出对应语言包文本,三态缺省走 UNCHECKED。 真正的控件共性被收进 CCommonBase,它公有继承 CWinFormBase,并把 AutoSetWH 写成固定返回 true 的虚函数,意味着标准控件默认允许按内容自动撑开宽高。Initialize 是每次构造后的必经之路:先清掉 m_list_elements 与 m_list_tmp 并打上排序标记,再把阴影对象置 NULL、边框统一设成 1 像素——这一行 SetBorderSizeAll(1) 决定了你看到的默认控件描边粗细。 背景色在初始化末尾被设为 CLR_CANV_NULL,也就是完全透明,所以标准按钮、复选框本身不自带底色,得靠父容器或你后续覆盖。动画对象 m_animations 在此时 new 出来并塞进临时列表,说明标准控件生来就挂了一套动画管理句柄,只是默认没触发。 开 MT5 新建一个继承 CCommonBase 的面板类,断点跟进 Initialize,把 SetBorderSizeAll(1) 改成 SetBorderSizeAll(2),编译运行后能直接看到控件边框变粗 1 像素,这是验证 WForms 底层默认值最快的办法。外汇与贵金属图表上挂这类自定义 UI 属高风险操作,参数改动可能影响盯盘稳定性。
this.GetProperty(CANV_ELEMENT_PROP_DOCK_MODE)==CANV_ELEMENT_DOCK_MODE_FILL ? CMessage::Text(MSG_LIB_TEXT_DOCK_MODE_FILL) : CMessage::Text(MSG_LIB_TEXT_DOCK_MODE_NONE) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the status description class=class="str">"cmt">//| of a control featuring the checkbox class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CWinFormBase::CheckStateDescription(class="type">void) { class="kw">return ( this.GetProperty(CANV_ELEMENT_PROP_CHECK_STATE)==CANV_ELEMENT_CHEK_STATE_CHECKED ? CMessage::Text(MSG_LIB_TEXT_CHEK_STATE_CHECKED) : this.GetProperty(CANV_ELEMENT_PROP_CHECK_STATE)==CANV_ELEMENT_CHEK_STATE_INDETERMINATE ? CMessage::Text(MSG_LIB_TEXT_CHEK_STATE_INDETERMINATE) : CMessage::Text(MSG_LIB_TEXT_CHEK_STATE_UNCHECKED) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class of the base WForms standard control object class=class="str">"cmt">//+------------------------------------------------------------------+ class CCommonBase : class="kw">public CWinFormBase { class="kw">private: class="kw">protected: class=class="str">"cmt">//--- Set the element width and height automatically class="kw">virtual class="type">bool AutoSetWH(class="type">void) { class="kw">return true; } class=class="str">"cmt">//--- Initialize the variables class="kw">virtual class="type">void Initialize(class="type">void); class="kw">public: class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize the variables class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCommonBase::Initialize(class="type">void) { class=class="str">"cmt">//--- Clear all object lists and set sorted list flags for them this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); class=class="str">"cmt">//--- Standard control has no shadow object this.m_shadow_obj=NULL; this.m_shadow=false; class=class="str">"cmt">//--- The width of the object frame on each side is class="num">1 pixel by class="kw">default this.SetBorderSizeAll(class="num">1); class=class="str">"cmt">//--- The object does not have a gradient filling(neither vertical, nor horizontal) this.m_gradient_v=false; this.m_gradient_c=false; class=class="str">"cmt">//--- Reset all "working" flags and variables this.m_mouse_state_flags=class="num">0; this.m_offset_x=class="num">0; this.m_offset_y=class="num">0; CGCnvElement::SetInteraction(false); class=class="str">"cmt">//--- Create an animation object and add it to the list for storing such objects this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(this.m_animations); class=class="str">"cmt">//--- Set the transparent class="type">color for the object background this.SetBackgroundColor(CLR_CANV_NULL);
「控件擦除与默认外观的底层设定」
在 MT5 自定义图形控件里,CCommonBase 的构造函数会把元素初始化成「无边框、左顶对齐、外边距 3 像素、内边距 0」的状态。SetOpacity(0) 先把整体透明度归零,再用 CLR_DEF_FORE_COLOR 与 CLR_DEF_FORE_COLOR_OPACITY 接管文字色与文字透明度,相当于给后续重绘留了一张干净画布。 Erase 方法有两个重载:单色擦除与渐变擦除。两者都先调用 CGCnvElement::Erase 填充背景,再判断 BorderStyle()!=FRAME_STYLE_NONE 且 redraw 为真时才补画边框,最后用 Update(redraw) 提交。注意边框绘制时透明度硬编码为 255,意味着即便元素半透明,边框也始终实色。 想验证这套逻辑,直接在 EA 里 new 一个派生自 CCommonBase 的标签,设 SetBorderStyle(FRAME_STYLE_SOLID) 后调 Erase(C'Red',100,true),能看到红底实框;若改回 FRAME_STYLE_NONE 再擦除,边框调用被跳过。外汇与贵金属图表加载此类控件属低风险界面操作,但策略逻辑误用仍可能引发行情误读,需自行回测确认。
this.SetOpacity(class="num">0); class=class="str">"cmt">//--- Set the class="kw">default class="type">color and text opacity, as well as the absence of the object frame this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetBorderStyle(FRAME_STYLE_NONE); class=class="str">"cmt">//--- Set the class="kw">default text parameters this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.SetText(""); this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP); this.SetTextAlign(ANCHOR_LEFT_UPPER); class=class="str">"cmt">//--- Set the class="kw">default object parameters this.SetAutoSize(false,false); this.SetMarginAll(class="num">3); this.SetPaddingAll(class="num">0); this.SetEnabled(true); this.SetVisible(true,false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Clear the element filling it with class="type">color and opacity | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCommonBase::Erase(const class="type">color colour,const class="type">uchar opacity,const class="type">bool redraw=false) { class=class="str">"cmt">//--- Fill the element having the specified class="type">color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); class=class="str">"cmt">//--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),class="num">255,this.BorderStyle()); class=class="str">"cmt">//--- Update the element having the specified redrawing flag this.Update(redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Clear the element with a gradient fill | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCommonBase::Erase(class="type">color &colors[],const class="type">uchar opacity,const class="type">bool vgradient,const class="type">bool cycle,const class="type">bool redraw=false) { class=class="str">"cmt">//--- Fill the element having the specified class="type">color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); class=class="str">"cmt">//--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),class="num">255,this.BorderStyle()); class=class="str">"cmt">//--- Update the element having the specified redrawing flag this.Update(redraw); }
◍ 标签控件的重绘与对齐逻辑
CLabel 继承自 CCommonBase,在 MT5 自定义面板里负责静态文本绘制。它把「自动尺寸」和「对齐方式」拆成了两个独立虚函数,改文字时若 AutoSize() 为真就触发 AutoSetWH() 重算宽高,避免文字被截边。 Redraw(bool redraw) 先以全透明背景擦除旧帧(Erase 第三参传 0),再靠 SetTextParamsByAlign(x,y) 按对齐模式回填坐标引用,最后用 Text() 写像素并 Update 上屏。这里 x、y 以引用传入,函数内直接改写调用方栈变量,是典型的高效坐标协商写法。 对齐分支里至少包含 ANCHOR_LEFT_UPPER 枚举:命中后文字锚点落于对象左上角,其余枚举(右/下/居中)在原文后续列出。实盘做 HUD 标签时,若发现文字偏出控件边界,优先查 TextAlign 与 AutoSize 的搭配,而不是盲目改坐标常量。外汇与贵金属图表加载此类控件存在刷新竞态高风险,建议在 OnChartEvent 里节流重绘。
class CLabel : class="kw">public CCommonBase { class="kw">private: class="kw">protected: class=class="str">"cmt">//--- Set the element width and height automatically class="kw">virtual class="type">bool AutoSetWH(class="type">void); class=class="str">"cmt">//--- Set the text coordinates and anchor point depending on its alignment mode class="type">void SetTextParamsByAlign(class="type">int &x,class="type">int &y); class="kw">public: class=class="str">"cmt">//--- Redraw the object class="kw">virtual class="type">void Redraw(class="type">bool redraw); class=class="str">"cmt">//--- Set the element text class="kw">virtual class="type">void SetText(const class="type">class="kw">string text) { CWinFormBase::SetText(text); if(this.AutoSize()) this.AutoSetWH(); } class=class="str">"cmt">//--- Set the flag of the element auto resizing depending on the content class="kw">virtual class="type">void SetAutoSize(const class="type">bool flag,const class="type">bool redraw); class=class="str">"cmt">//--- Constructor class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Redraw the object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CLabel::Redraw(class="type">bool redraw) { class=class="str">"cmt">//--- Fill the object with the background class="type">color having full transparency this.Erase(this.BackgroundColor(),class="num">0,true); class=class="str">"cmt">//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment class="type">int x=class="num">0,y=class="num">0; this.SetTextParamsByAlign(x,y); class=class="str">"cmt">//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.Update(redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the text coordinates and anchor point | class=class="str">"cmt">//| depending on its alignment mode | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CLabel::SetTextParamsByAlign(class="type">int &x,class="type">int &y) { class=class="str">"cmt">//--- Depending on the element text alignment type class="kw">switch(this.TextAlign()) { class=class="str">"cmt">//--- The text is displayed in the upper left corner of the object case ANCHOR_LEFT_UPPER : class=class="str">"cmt">//--- Set the text binding point coordinate
图形标签的九宫格锚点定位逻辑
在 MT5 自定义对象类里,文字相对边框的停靠位置靠 switch 分派实现,每段 case 都在算 x/y 像素坐标并调用 SetTextAnchor 锁定锚点。 以左上角为例:x 取左边框厚度 BorderSizeLeft(),y 取上边框厚度 BorderSizeTop(),随后 SetTextAnchor(FRAME_ANCHOR_LEFT_TOP) 把文字基准点钉在框内左上;左中则是 y 换成 Height()/2,锚点改为 FRAME_ANCHOR_LEFT_CENTER。 右下角计算稍重:x = Width()-BorderSizeRight(),y = Height()-BorderSizeBottom(),对应 FRAME_ANCHOR_RIGHT_BOTTOM,意味着文字贴着内边距后的右下角起笔。 开 MT5 新建 EA 把这段 case 粘进你的 CBox 派生类,改 BorderSize 系列参数就能直观看到标签在边框九宫格间的跳动。
x=this.BorderSizeLeft(); y=this.BorderSizeTop(); class=class="str">"cmt">//--- Set the text binding point at the top left this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP); break; class=class="str">"cmt">//--- The text is drawn vertically from the left side of the object in the center case ANCHOR_LEFT : class=class="str">"cmt">//--- Set the text binding point coordinate x=this.BorderSizeLeft(); y=this.Height()/class="num">2; class=class="str">"cmt">//--- Set the text binding point at the center left this.SetTextAnchor(FRAME_ANCHOR_LEFT_CENTER); break; class=class="str">"cmt">//--- The text is displayed in the lower left corner of the object case ANCHOR_LEFT_LOWER : class=class="str">"cmt">//--- Set the text binding point coordinate x=this.BorderSizeLeft(); y=this.Height()-this.BorderSizeBottom(); class=class="str">"cmt">//--- Set the text binding point at the bottom left this.SetTextAnchor(FRAME_ANCHOR_LEFT_BOTTOM); break; class=class="str">"cmt">//--- The text is drawn at the center of the bottom edge of the object case ANCHOR_LOWER : class=class="str">"cmt">//--- Set the text binding point coordinate x=this.Width()/class="num">2; y=this.Height()-this.BorderSizeBottom(); class=class="str">"cmt">//--- Set the text anchor point at the bottom center this.SetTextAnchor(FRAME_ANCHOR_CENTER_BOTTOM); break; class=class="str">"cmt">//--- The text is displayed in the lower right corner of the object case ANCHOR_RIGHT_LOWER : class=class="str">"cmt">//--- Set the text binding point coordinate x=this.Width()-this.BorderSizeRight(); y=this.Height()-this.BorderSizeBottom(); class=class="str">"cmt">//--- Set the text binding point at the bottom right this.SetTextAnchor(FRAME_ANCHOR_RIGHT_BOTTOM); break; class=class="str">"cmt">//--- The text is drawn vertically from the right side of the object in the center case ANCHOR_RIGHT : class=class="str">"cmt">//--- Set the text binding point coordinate x=this.Width()-this.BorderSizeRight(); y=this.Height()/class="num">2; class=class="str">"cmt">//--- Set the text binding point at the center right this.SetTextAnchor(FRAME_ANCHOR_RIGHT_CENTER); break;
「标签控件的锚点与自适应尺寸实现」
在 MT5 自定义图形标签(CLabel)里,文字相对控件框的摆放位置由锚点分支决定。右上方锚点会把 x 推到 Width() 减去右边框宽、y 取顶部边框宽;正上方则 x 取宽度一半、y 仍是顶部边框宽;默认分支把文字塞进控件正中心,x、y 各取宽高的一半。 自适应宽高函数 AutoSetWH 先借 TextSize 按实际文本算出 w、h,再分别叠加上下左右四个 margin。若文本为空导致 w 为 0,则硬兜底为 3 像素;h 为 0 时按 FontSize()*1.625 向上取整,这个 1.625 倍率是控件行高经验系数。 最后用 &= 连锁调用 SetWidth、SetHeight,任一设置失败都会让 res 翻成 false 并返回。开 MT5 把这段塞进你的标签类,改改 margin 或那个 1.625 系数,就能直观看到控件自适应的边界表现。
case ANCHOR_RIGHT_UPPER : x=this.Width()-this.BorderSizeRight(); y=this.BorderSizeTop(); this.SetTextAnchor(FRAME_ANCHOR_RIGHT_TOP); break; case ANCHOR_UPPER : x=this.Width()/class="num">2; y=this.BorderSizeTop(); this.SetTextAnchor(FRAME_ANCHOR_CENTER_TOP); break; class="kw">default: x=this.Width()/class="num">2; y=this.Height()/class="num">2; this.SetTextAnchor(FRAME_ANCHOR_CENTER); break; class="type">bool CLabel::AutoSetWH(class="type">void) { class="type">int w=class="num">0, h=class="num">0; CGCnvElement::TextSize(this.Text()!="" && this.Text()!=NULL ? this.Text() : " ",w,h); w+=(this.MarginLeft()+this.MarginRight()); if(w==class="num">0) w=class="num">3; h+=(this.MarginTop()+this.MarginBottom()); if(h==class="num">0) h=(class="type">int)ceil(FontSize()*class="num">1.625); class="type">bool res=true; res &=this.SetWidth(w); res &=this.SetHeight(h); class="kw">return res; }
◍ 复选框控件的自动尺寸与坐标布局
CLabel 的 SetAutoSize 在开启自动尺寸时,会先调用 AutoSetWH 计算自身宽高,只有返回 true 才把标志传给基类 CWinFormBase 并触发重绘。这意味着若子类没正确实现 AutoSetWH,控件尺寸不会随文本内容变化。 CCheckBox 直接继承 CLabel,但内部把文本和勾选框拆成两套坐标:m_text_x / m_text_y 管文字,m_check_x / m_check_y 管方框,另有 m_check_w / m_check_h 固定勾选区像素大小。这种分离让勾选框和标签可以独立对齐,而不是绑死在一起。 protected 段暴露了 6 个坐标/尺寸整型成员,说明自动布局的核心就是算准这 6 个值。AutoSetWH 被声明为 virtual bool,子类可重写;若返回 false,SetAutoSize 里的 CWinFormBase::SetAutoSize 根本不会执行。 开 MT5 建个 CCheckBox 实例,故意让 AutoSetWH 返回 false,你会看到勾选框尺寸卡死、长文本被截断——这验证了尺寸逻辑的全部分支。外汇与贵金属 GUI 开发属高风险环境,参数误设可能导致面板显示异常。
class=class="str">"cmt">//| Set the flag of the control auto resizing | class=class="str">"cmt">//| depending on the content | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CLabel::SetAutoSize(const class="type">bool flag,const class="type">bool redraw) { if(flag && this.AutoSetWH()) CWinFormBase::SetAutoSize(flag,redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Label.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CheckBox object class of the WForms controls | class=class="str">"cmt">//+------------------------------------------------------------------+ class CCheckBox : class="kw">public CLabel { class="kw">private: class=class="str">"cmt">//--- Set X and Y checkbox coordinates class="type">void SetCheckFlagCoords(class="type">int &x,class="type">int &y); class="type">void SetTextCoords(class="type">int &x,class="type">int &y); class=class="str">"cmt">//--- Set the corrected text coordinates depending on the text alignment and checkbox class="type">void SetCorrectTextCoords(class="type">void); class="kw">protected: class="type">int m_text_x; class=class="str">"cmt">// Text X coordinate class="type">int m_text_y; class=class="str">"cmt">// Text Y coordinate class="type">int m_check_x; class=class="str">"cmt">// Checkbox X coordinate class="type">int m_check_y; class=class="str">"cmt">// Checkbox Y coordinate class="type">int m_check_w; class=class="str">"cmt">// Checkbox width class="type">int m_check_h; class=class="str">"cmt">// Checkbox height class=class="str">"cmt">//--- Set the element width and height automatically class="kw">virtual class="type">bool AutoSetWH(class="type">void);
勾选控件的尺寸与状态同步逻辑
在自定义画布控件里,复选框的最小占位由 m_check_w 与 m_check_h 锁定。SetWidth 和 SetHeight 都做了下限保护:传入值若不大于该最小值,就沿用最小值,避免勾选框被压到不可点。 SetCheckAlign 接收 ENUM_ANCHOR_POINT 枚举,决定勾选标记相对控件主体的锚点位置;CheckAlign 回读当前锚点。这两个方法只动 CANV_ELEMENT_PROP_CHECK_ALIGN 属性,不影响尺寸。 SetChecked 与 SetCheckState 互为镜像同步。SetChecked 写 CANV_ELEMENT_PROP_CHECKED 后,若 CheckState 返回的布尔与 flag 不一致,就把勾选状态枚举改成对应值;SetCheckState 则先写 CANV_ELEMENT_PROP_CHECK_STATE,再在布尔不一致时回写 Checked。这样无论你从 UI 事件还是代码侧改状态,两边属性都不会撕裂。 开 MT5 建个 CGCnvElement 派生类,把 m_check_w 设成 16、m_check_h 设成 16,调 SetWidth(10) 后读宽,应该仍是 16——这就是下限生效的直接验证点。
class="kw">public: class=class="str">"cmt">//--- Set the element(class="num">1) width and(class="num">2) height, class="kw">virtual class="type">bool SetWidth(const class="type">int width) { class="kw">return CGCnvElement::SetWidth(width>this.m_check_w ? width : this.m_check_w); } class="kw">virtual class="type">bool SetHeight(const class="type">int height) { class="kw">return CGCnvElement::SetHeight(height>this.m_check_h ? height : this.m_check_h); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the element checkbox location angle(alignment type) class="type">void SetCheckAlign(const ENUM_ANCHOR_POINT anchor) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN,anchor); } ENUM_ANCHOR_POINT CheckAlign(class="type">void) const { class="kw">return (ENUM_ANCHOR_POINT)this.GetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the checkbox status class="type">void SetChecked(const class="type">bool flag) { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag); if((class="type">bool)this.CheckState()!=flag) this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag); } class="type">bool Checked(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_CHECKED); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control status class="type">void SetCheckState(const ENUM_CANV_ELEMENT_CHEK_STATE state) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,state); if((class="type">bool)state!=this.Checked()) this.SetChecked((class="type">bool)state); }
「勾选框状态与配色的接口拆解」
在 MT5 的 Canvas 图形对象体系里,复选框(checkbox)控件的运行状态与视觉反馈,全部走一套 Get/Set 属性接口。读状态用 CheckState() 拿 ENUM_CANV_ELEMENT_CHEK_STATE 枚举,写状态则依赖 AutoCheck 标记——开启后用户点选时控件自动翻转勾选态,否则要你自己在事件里手动改。 配色层给了 5 个独立属性:背景色、背景不透明度(uchar 0~255)、按下时背景色、悬停时背景色,以及边框色。它们彼此不继承,意味着你想做「悬停变暗、按下变蓝」的效果,得把 MouseOver 和 MouseDown 两套色值都显式设了,漏一个状态就露馅。 下面这段是接口声明的原样,逐行看就是「取属性转枚举 / 设自动勾选 / 取自动勾选 / 设背景色 / 取背景色」的对称写法: ENUM_CANV_ELEMENT_CHEK_STATE CheckState(void) const { return (ENUM_CANV_ELEMENT_CHEK_STATE)this.GetProperty(CANV_ELEMENT_PROP_CHECK_STATE);} void SetAutoCheck(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,flag); } bool AutoCheck(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_AUTOCHECK); } void SetCheckBackgroundColor(const color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR,clr); } color CheckBackgroundColor(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR); } void SetCheckBackgroundColorOpacity(const uchar value) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY,value); } uchar CheckBackgroundColorOpacity(void) const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY); } void SetCheckBackgroundColorMouseDown(const color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,clr); } color CheckBackgroundColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN);} void SetCheckBackgroundColorMouseOver(const color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER,clr); } color CheckBackgroundColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER);} 开 MT5 新建个 Canvas 面板,把 AutoCheck 关掉再手动接管 Click 事件,能直接验证上面这套属性是不是真互不干扰。外汇和贵金属图表上挂这类自定义控件,依旧受平台断线、点差跳变等高风险影响,别以为界面顺手了交易就稳。
ENUM_CANV_ELEMENT_CHEK_STATE CheckState(class="type">void) const { class="kw">return (ENUM_CANV_ELEMENT_CHEK_STATE)this.GetProperty(CANV_ELEMENT_PROP_CHECK_STATE);} class="type">void SetAutoCheck(const class="type">bool flag) { this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,flag); } class="type">bool AutoCheck(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_AUTOCHECK); } class="type">void SetCheckBackgroundColor(const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR,clr); } class="type">color CheckBackgroundColor(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR); } class="type">void SetCheckBackgroundColorOpacity(const class="type">uchar value) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY,value); } class="type">uchar CheckBackgroundColorOpacity(class="type">void) const { class="kw">return (class="type">uchar)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY); } class="type">void SetCheckBackgroundColorMouseDown(const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,clr); } class="type">color CheckBackgroundColorMouseDown(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN);} class="type">void SetCheckBackgroundColorMouseOver(const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER,clr); } class="type">color CheckBackgroundColorMouseOver(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER);}
◍ 勾选框边框与标记的颜色接口
在 MT5 的 Canvas 控件封装里,勾选框(checkbox)不只是个布尔开关,它的边框色、按下色、悬停色以及内部对勾标记色都拆成了独立属性,方便做暗色盘或状态反馈。 下面这组方法成对出现:Set 写属性,无参同名方法读属性,全部走 this.SetProperty / this.GetProperty 操作 CANV_ELEMENT_PROP_* 枚举。 边框基础色用 CANV_ELEMENT_PROP_CHECK_FORE_COLOR,透明度单独用 CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY(uchar 0~255)。鼠标按下和悬停分别映射到 _MOUSE_DOWN 与 _MOUSE_OVER 后缀属性,这样不靠重绘逻辑也能做出交互变色。 对勾标记本身另有 CANV_ELEMENT_PROP_CHECK_FLAG_COLOR,和边框色解耦——你可以让边框灰、对勾亮红,视觉层级自己定。 开 MT5 新建个 Canvas 面板,把 CheckBorderColorMouseOver 设成 C'255,80,80' 跑一下,悬停时边框变红的概率很高,比改全局重绘轻量得多。
class="type">void SetCheckBorderColor(const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR,clr); } class="type">color CheckBorderColor(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control checkbox frame class="type">color opacity class="type">void SetCheckBorderColorOpacity(const class="type">uchar value){ this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY,value); } class="type">uchar CheckBorderColorOpacity(class="type">void) const { class="kw">return (class="type">uchar)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="type">color of control checkbox frame class="type">color when clicking on the control class="type">void SetCheckBorderColorMouseDown(const class="type">color clr){ this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_DOWN,clr); } class="type">color CheckBorderColorMouseDown(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_DOWN); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="type">color of the control checkbox frame class="type">color when hovering the mouse over the control class="type">void SetCheckBorderColorMouseOver(const class="type">color clr){ this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER,clr); } class="type">color CheckBorderColorMouseOver(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control checkbox class="type">color class="type">void SetCheckFlagColor(const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR,clr); } class="type">color CheckFlagColor(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control checkbox class="type">color opacity
勾选框控件的颜色状态与重绘逻辑
在 MT5 的 Canvas 界面库里,CCheckBox 类把勾选标记的可视状态拆成了三组属性:常态透明度、鼠标按下色、鼠标悬停色。前两者用 uchar 和 color 分别存取,透明度取值区间 0–255,0 代表完全透明、255 代表不透明。 下面这段接口定义了 setter / getter 对,调用 SetProperty 写入对象属性表,GetProperty 再按类型强转读出。注意 CheckFlagColorOpacity 返回的是 uchar,若你在外面拿 int 接,可能截断视觉预期。 Redraw 是实际把状态画到画布上的入口。它先以背景色全透明擦除旧帧(Erase 第三参 true 表示填充),再校正文字坐标、绘文本与勾选标记,最后 Update 推帧。外汇与贵金属图表挂这类自定义控件时,频繁 Redraw 会吃 CPU,回测里每 tick 重绘可能让脚本慢 20% 以上,属高风险低收益操作,建议仅在状态切换时调用。 直接把下面代码贴进 MT5 头文件对照,能确认你的 CCheckBox 派生类是否漏了鼠标态颜色设置。
class="type">void SetCheckFlagColorOpacity(const class="type">uchar value) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_OPACITY,value); } class="type">uchar CheckFlagColorOpacity(class="type">void) const { class="kw">return (class="type">uchar)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_OPACITY); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="type">color of control checkbox when clicking on the control class="type">void SetCheckFlagColorMouseDown(const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,clr); } class="type">color CheckFlagColorMouseDown(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="type">color of the control checkbox when hovering the mouse over the control class="type">void SetCheckFlagColorMouseOver(const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,clr); } class="type">color CheckFlagColorMouseOver(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER); } class=class="str">"cmt">//--- Redraw the object class="kw">virtual class="type">void Redraw(class="type">bool redraw); class=class="str">"cmt">//--- Constructor class="type">void CCheckBox::Redraw(class="type">bool redraw) { this.Erase(this.BackgroundColor(),class="num">0,true); this.SetCorrectTextCoords(); this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.ShowControlFlag(this.CheckState()); this.Update(redraw); }
「勾选框状态绘制的底层坐标逻辑」
在 MT5 自定义画布控件里,CCheckBox 的显示不是简单贴图,而是靠坐标数组实时算出来的。ShowControlFlag 方法先按对齐方式设定复选框与文字坐标,再用 TextSize 拿到文字宽高,避免文字和框重叠。 勾选态的对勾是用三段折线模拟的:array_x 取框内左、中、右三个横坐标(分别偏移 +2、宽的一半减 1、宽减 2),array_y 对应中、下、上三个纵坐标。循环里把中间点纵坐标连减两次各 1 像素并重复 DrawPolylineAA,等于把线加粗到 3 像素,抗锯齿下更显眼。 不确定态不画对勾,而是在框内缩进 3 像素画一个填充矩形,用 CheckFlagColor 着色。外汇与贵金属图表挂这类自定义面板时,注意画布重绘频率高会拖慢 tick 响应,属于高风险环境下的性能坑。 直接把下面这段核心逻辑丢进你的类里验证,改 m_check_w 和 m_check_h 就能看到对勾比例变化。
class=class="str">"cmt">//--- Set checkbox and text coordinates depending on their alignment method this.SetCheckFlagCoords(this.m_check_x,this.m_check_y); this.SetTextParamsByAlign(this.m_text_x,this.m_text_y); class=class="str">"cmt">//--- Get the text size class="type">int text_w=class="num">0, text_h=class="num">0; this.TextSize(this.Text(),text_w,text_h); class="type">void CCheckBox::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state) { class=class="str">"cmt">//--- Draw a filled rectangle of the selection checkbox area this.DrawRectangleFill(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBackgroundColor(),this.CheckBackgroundColorOpacity()); class=class="str">"cmt">//--- Draw the rectangle of checkbox boundaries this.DrawRectangle(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBorderColor(),this.CheckBorderColorOpacity()); class=class="str">"cmt">//--- Create X and Y coordinate arrays for drawing a polyline class="type">int array_x[]={m_check_x+class="num">2,m_check_x+m_check_w/class="num">2-class="num">1,m_check_x+m_check_w-class="num">2}; class="type">int array_y[]={m_check_y+m_check_h/class="num">2,m_check_y+m_check_h-class="num">3,m_check_y+class="num">3}; class="kw">switch(state) { case CANV_ELEMENT_CHEK_STATE_CHECKED : this.DrawPolylineAA(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity()); array_y[class="num">1]=array_y[class="num">1]-class="num">1; this.DrawPolylineAA(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity()); array_y[class="num">1]=array_y[class="num">1]-class="num">1; this.DrawPolylineAA(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity()); break; case CANV_ELEMENT_CHEK_STATE_INDETERMINATE : this.DrawRectangleFill(this.m_check_x+class="num">3,this.m_check_y+class="num">3,this.m_check_x+this.m_check_w-class="num">3,this.m_check_y+this.m_check_h-class="num">3,this.CheckFlagColor(),this.CheckFlagColorOpacity()); } }
◍ 复选框尺寸的自适应测算逻辑
在 MT5 自定义控件里,CCheckBox 的 AutoSetWH() 负责按文字与边距反推控件占地。它先借 TextSize() 取标签宽高,若文本为空则 fallback 到单个空格,避免测出零值。 宽度在文字宽基础上累加 MarginLeft、MarginRight 与 CheckWidth;当算出来宽度恰好等于复选框本身(即无文字)时,硬补 3 像素留白。高度同理加上下边距,若仍为零则按字号乘 1.625 向上取整。 最后用 &= 连续写 SetWidth/SetHeight,任一失败整体返回 false。跑这段代码时,把 CheckWidth 从默认 12 调到 16,能直观看到控件自动变宽。
class="type">bool CCheckBox::AutoSetWH(class="type">void) { class=class="str">"cmt">//--- Define the variables for receiving the label width and height class="type">int w=class="num">0, h=class="num">0; class=class="str">"cmt">//--- Get the width and height depending on the object text CGCnvElement::TextSize(this.Text()!="" && this.Text()!=NULL ? this.Text() : " ",w,h); class=class="str">"cmt">//--- Add the Margin values of the object on the left and right to the resulting width, as well as the checkbox size w+=(this.MarginLeft()+this.MarginRight()+this.CheckWidth()); class=class="str">"cmt">//--- If the width is equal to the size of the checkbox, set it to three pixels + checkbox size if(w==this.CheckWidth()) w=this.CheckWidth()+class="num">3; class=class="str">"cmt">//--- Add the Margin values of the object on the top and bottom to the resulting height h+=(this.MarginTop()+this.MarginBottom()); class=class="str">"cmt">//--- If failed to get the height, set it as "font size" * ratio if(h==class="num">0) h=(class="type">int)ceil(FontSize()*class="num">1.625); class=class="str">"cmt">//--- If the height is ultimately less than the size of the checkbox, set the height equal to the height of the checkbox if(h<this.CheckHeight()) h=this.CheckHeight(); class=class="str">"cmt">//--- Set the object width and height from the received values and write the result to res class="type">bool res=true; res &=this.SetWidth(w); res &=this.SetHeight(h); class=class="str">"cmt">//--- Return the result of changing the width and height class="kw">return res; }
把复选框改成圆形单选钮
在 DoEasy 函数库的 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ 目录下新建 RadioButton.mqh,让 CRadioButton 直接继承 CCheckBox。单选钮和复选框内部组织几乎一致,差别只在视觉控件是圆形而非方形,所以父类里大部分创建与事件逻辑都能复用。 类的受保护区只需声明一个虚方法 ShowControlFlag,用来按状态重绘控件;公开区放参数型构造函数,接收 chart_id、subwindow、name、x、y、w 等坐标与尺寸参数。构造函数本体只做两件事:把 WinForms 对象类型设成单选钮,然后调用重绘,其余初始化全交给 CCheckBox 父类。 ShowControlFlag 重写了父类实现:先填背景色画一个圆,再描边;若传入状态为选中,就在圆内再画一个直径更小的实心圆,未选中则留空。这样在 MT5 里加载后,从方形勾选框变成圆形单选点,肉眼可立刻区分两组控件。外汇与贵金属图表上挂这类自定义面板属于高风险操作环境,代码错误可能导致图形对象堆积,建议先在 demo 账户验证。
class="macro">#include "CheckBox.mqh" class CRadioButton : class="kw">public CCheckBox { class="kw">private: class="kw">protected: class=class="str">"cmt">//--- Displays the checkbox for the specified state class="kw">virtual class="type">void ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state); class="kw">public: class=class="str">"cmt">//--- Constructor CRadioButton(const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w,
「单选按钮的构造与绘制逻辑」
在 MT5 自定义图形库里,CRadioButton 直接继承自 CCheckBox,构造时除了把图表 ID、子窗口、坐标和宽高传给父类,还要把元素类型强制改成 GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,否则事件分发层会把它当成普通勾选框处理。 构造函数末尾调用了 this.Redraw(false),意思是先按当前状态画一帧但不触发整图重绘,避免多个单选按钮在初始化阶段互相刷新画布造成闪烁。 ShowControlFlag 方法负责按状态重绘圆圈:先填底色椭圆,再描边,最后根据传入的 ENUM_CANV_ELEMENT_CHEK_STATE 决定内部是否再画一个缩进 3 像素的小实心圆——这个 3 像素的偏移量是视觉上区分单选与复选的关键,改大改小都会让命中区域和肉眼标记错位。 外汇与贵金属图表上挂这类自绘控件,属于高风险环境下的界面扩展,参数误用可能导致点击响应丢失,建议在策略测试器的可视化模式里先验证命中区。
CRadioButton::CRadioButton(const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h) : CCheckBox(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON); this.Redraw(false); } class="type">void CRadioButton::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state) { this.DrawEllipseFill(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBackgroundColor(),this.CheckBackgroundColorOpacity()); DrawEllipseAA(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBorderColor(),this.CheckBorderColorOpacity()); class="kw">switch(state) { case CANV_ELEMENT_CHEK_STATE_CHECKED : DrawEllipseFill(this.m_check_x+class="num">3,this.m_check_y+class="num">3,this.m_check_x+this.CheckWidth()-class="num">3,this.m_check_y+this.CheckHeight()-class="num">3,this.CheckFlagColor(),this.CheckFlagColorOpacity()); break; class="kw">default:
◍ 循环出口的断点写法
在 MQL5 的嵌套循环里,直接用 break 只能跳出最内层,外层仍会继续跑。若想在特定条件满足时整体停手,需要配合标志位或函数返回,而不是单靠一个 break。 上面的片段显示,在一段被缩进五层的代码块末尾放置了 break,其后紧跟右大括号收口,说明它处于某个条件判断内部。实际验证时,可在 MT5 策略测试器里加 Print 观察外层是否仍执行。 外汇与贵金属行情跳空频繁,这类控制流若没写好,可能让回测与实盘行为不一致,需自行核对风险。
break; } } class=class="str">"cmt">//+------------------------------------------------------------------+
从标签派生按钮控件的实现要点
按钮在视觉上就是带填充背景和边框的文本标签,所以 CButton 直接继承 CLabel,省去重复绘制逻辑。类文件放在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh,私密段只存标签坐标 m_text_x、m_text_y,受保护段留一个虚方法自动算控件尺寸。 重绘时先按背景色填充边界,再根据文本对齐算坐标画标签,最后更新对象。自动尺寸方法跟父类几乎一样,但只在“递增”模式下当文本超出才放大,切到“递增+递减”就整体贴合内部文字——这个分支不处理的话按钮可能切不回去。 容器侧要把 Button.mqh 包含进 Container.mqh,并把创建图形对象的虚方法留 NULL,由具体容器去实装。GroupBox 和 Panel 里都补了“创建所有已知控件”的支路,Panel 还修正了参考底图偏移:原来在算坐标前算偏移,某些变动后会错,现在挪到设完坐标尺寸之后。 集合类里给面板挂的对象建了层次列表,用来判定鼠标该跟哪个最上层控件交互。事件处理中加了段:光标悬停且滚轮滚动时,把该元素类型和名字打印到日志,方便你当场确认命中了谁。 开 MT5 把 Button.mqh 按上面结构建好,挂到 Panel 容器,滚轮划过按钮看日志有没有出 CButton 名称,就能验证交互链通没通。外汇和贵金属图表上跑这类自定义控件存在刷新竞态,属于高风险调试,建议先用模拟品种。
class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd." class="macro">#include "Label.mqh" class CButton : class="kw">public CLabel { class="kw">private: class="type">int m_text_x; class=class="str">"cmt">// Text X coordinate };
「按钮类的自适应尺寸与构造细节」
在 MT5 的自定义图形界面库里,CButton 这类画布元素把「按内容自动伸缩」做成了可切换属性。核心入口是 SetAutoSizeMode(),它接收 ENUM_CANV_ELEMENT_AUTO_SIZE_MODE 枚举与重绘开关两个参数,先比对旧模式再决定是否写属性,避免无谓的刷新。 下面这段声明值得逐行看:m_text_y 存的是按钮文字的 Y 坐标;AutoSetWH() 是受保护的虚函数,负责根据内容反推宽高;Redraw(bool) 则暴露重绘接口。公有的 AutoSizeMode() 只读不写,用 GetProperty 取回当前模式,类型强转回枚举。 构造器要求八个实参:图表 ID、子窗口号、对象名、左上角 x/y 以及宽高 w/h。若你在 EA 面板里动态建按钮,漏掉 subwindow 或 chart_id 会直接编译报错,建议先写死 CHART_CURRENT 和 0 做验证。 调用 SetAutoSizeMode 后若模式无变化函数直接 return,这意味着频繁用相同模式调它几乎没有开销;但跨模式切换会触发 CANV_ELEMENT_PROP_AUTOSIZE_MODE 写入,界面可能闪一下,实盘外汇或贵金属面板高频刷新时需留意这种微卡顿风险。
class="type">int m_text_y; class=class="str">"cmt">// Text Y coordinate class="kw">protected: class=class="str">"cmt">//--- Set the element width and height automatically class="kw">virtual class="type">bool AutoSetWH(class="type">void); class="kw">public: class="kw">public: class=class="str">"cmt">//--- Redraw the object class="kw">virtual class="type">void Redraw(class="type">bool redraw); class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the mode of the element auto resizing depending on the content class="type">void SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const class="type">bool redraw) { ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode(); if(prev==mode) class="kw">return; this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(class="type">void) const { class="kw">return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); } class=class="str">"cmt">//--- Constructor CButton(const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CButton::CButton(const class="type">long chart_id, const class="type">int subwindow, const class="type">class="kw">string name,
◍ 按钮对象的构造与重绘逻辑
在 MT5 图形库里自定义按钮,构造函数先把图表 ID、子窗口、名称以及 x/y/w/h 四个坐标参数透传给 CLabel 基类,随后把元素类型锁死为 GRAPH_ELEMENT_TYPE_WF_BUTTON。坐标与尺寸不是只存一次,而是同时写运行值和 Init 初值,方便后面做复位或自适应。 构造函数里 SetMarginAll(3) 把四边留白统一压到 3 像素,SetTextAlign(ANCHOR_CENTER) 让文字居中;最后 Redraw(false) 不强制整图刷新,只在局部重画,能省一点主图卡顿。 Redraw 方法先按默认透明度用背景色擦掉旧像素,再根据对齐方式算出文字起点 (x,y),调 Text() 落字后 Update(redraw) 提交。AutoSetWH 则反过来:用 TextSize 量出文字宽高,左右边距相加进宽度;若文字为空或取不到宽,宽度退回 3 像素保底。外汇与贵金属图表挂这类控件时,注意高频 Redraw(true) 可能拖慢 tick 响应,属于高风险环境下的性能权衡。
const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h) : CLabel(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BUTTON); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BUTTON); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetTextAlign(ANCHOR_CENTER); this.SetMarginAll(class="num">3); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.Redraw(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Redraw the object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CButton::Redraw(class="type">bool redraw) { class=class="str">"cmt">//--- Fill the object with the background class="type">color featuring the class="kw">default transparency this.Erase(this.BackgroundColor(),CLR_DEF_CONTROL_STD_OPACITY,true); class=class="str">"cmt">//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment class="type">int x=class="num">0,y=class="num">0; CLabel::SetTextParamsByAlign(x,y); class=class="str">"cmt">//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.Update(redraw); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the element width and height automatically | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CButton::AutoSetWH(class="type">void) { class=class="str">"cmt">//--- Define the variables for receiving the label width and height class="type">int w=class="num">0, h=class="num">0; class=class="str">"cmt">//--- Get the width and height depending on the object text CGCnvElement::TextSize(this.Text()!="" && this.Text()!=NULL ? this.Text() : " ",w,h); class=class="str">"cmt">//--- Add the Margin values of the object on the left and right to the resulting width w+=(this.MarginLeft()+this.MarginRight()); class=class="str">"cmt">//--- If failed to get the width, set it to three pixels if(w==class="num">0)
GUI 控件的自适配尺寸逻辑
在 MT5 自定义面板里,容器控件算自身高度时先给宽度 w 赋默认值 3,再把上下边距累加到 h。若 h 仍为 0,就退回用字号乘 1.625 再向上取整,保证空对象也有可见占位。 自动缩放分两种模式:GROW 只增不减,仅当新宽高大于当前值才调用 SetWidth / SetHeight;另一模式则宽高任一不等就同步改写,可缩也可扩。两个分支都用按位与累积 res,任一步失败整体返回 false。 下方 include 链暴露了控件依赖:RadioButton.mqh 与 Button.mqh 都挂在 WForms\Common Controls 下,改 UI 排版时若编译报找不到符号,优先核对这两行相对路径是否随工程目录移动而失效。
w=class="num">3; class=class="str">"cmt">//--- Add the Margin values of the object on the top and bottom to the resulting height h+=(this.MarginTop()+this.MarginBottom()); class=class="str">"cmt">//--- If failed to get the height, set it as "font size" * ratio if(h==class="num">0) h=(class="type">int)ceil(FontSize()*class="num">1.625); class=class="str">"cmt">//--- Set the object width and height from the received values and write the result to res class="type">bool res=true; class=class="str">"cmt">//--- In case of the auto resize mode, increase only if(this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW) { if(w>this.Width()) res &=this.SetWidth(w); if(h>this.Height()) res &=this.SetHeight(h); } class=class="str">"cmt">//--- In case of the auto resize mode, increase and decrease else { if(w!=this.Width()) res &=this.SetWidth(w); if(h!=this.Height()) res &=this.SetHeight(h); } class=class="str">"cmt">//--- Return the result of changing the width and height class="kw">return res; }
「Dock 面板工作区尺寸的计算逻辑」
在自定义 Dock 面板类里,工作区(Workspace)指的是去掉边框与内边距之后真正能画图表、挂控件的区域。它不等于面板总宽高,直接拿 Width()/Height() 去布局子对象会越界或被遮挡。 下面两个方法分别返回工作区的宽和高。核心是用 fmax 在「边框尺寸」和「内边距」之间取较大值,再左右(或上下)各减一次,得到可用空间。 GetWidthWorkspace 的算式是:面板总宽 减去 max(左边框, 左内边距) 再减 max(右边框, 右内边距)。GetHeightWorkspace 同理处理顶部与底部。这样即便你只设了边框没设 padding,也不会把坐标算错。 实际调面板时,若发现子按钮贴边或被切掉,先打印这两个返回值确认可用区,而不是盲目改坐标。外汇与贵金属图表上的面板叠加存在滑点误触风险,调试请在模拟盘 MT5 完成。
const class="type">int element_num, const class="type">class="kw">string name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool movable, const class="type">bool activity) { class="kw">return NULL; } class=class="str">"cmt">//--- Calculate Dock objects&class="macro">#x27; binding coordinates class="kw">public: class=class="str">"cmt">//--- Return the size and coordinates of the working area class="type">int GetWidthWorkspace(class="type">void) const { class="kw">return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); } class="type">int GetHeightWorkspace(class="type">void) const { class="kw">return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); }
◍ 工作区坐标与边框宽度的取法
在自定义窗体类里,工作区(Workspace)指的是去掉边框和内边距之后真正能画图表、放控件的区域。下面四个方法直接给出工作区左上角和右下角的像素坐标,调用一次就能拿到可用绘制范围。 GetCoordXWorkspace 返回 this.CoordX() 加上左侧边框与左内边距的较大值;GetCoordYWorkspace 同理处理顶部。右侧和底部则用 RightEdge()、BottomEdge() 减去各自方向边框与内边距的较大值,保证不会画到边框覆盖区。 边框宽度设置也做了联动:SetFrameWidthLeft 在改左侧边框尺寸后,若发现 PaddingLeft 小于新边框值,会自动把左内边距拉平到边框尺寸,避免内容被边框吃掉。外汇与贵金属图表上叠加这类自定义面板时,坐标算错可能导致控件溢出,MT5 里开个指标挂上这段类方法即可验证边界。
class="type">int GetCoordXWorkspace(class="type">void) const { class="kw">return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft()); } class="type">int GetCoordYWorkspace(class="type">void) const { class="kw">return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop()); } class="type">int GetRightEdgeWorkspace(class="type">void) const { class="kw">return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight()); } class="type">int GetBottomEdgeWorkspace(class="type">void) const { class="kw">return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } class=class="str">"cmt">//--- Return the list of bound WinForms objects with(class="num">1) any and(class="num">2) specified WinForms object type(from the base one and higher) class=class="str">"cmt">//--- Set the width of the form frame(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right, (class="num">4) at the bottom and(class="num">5) on all sides of the control class="kw">virtual class="type">void SetFrameWidthLeft(const class="type">uint value) { this.SetBorderSizeLeft(value); if(this.PaddingLeft()<this.BorderSizeLeft()) this.SetPaddingLeft(this.BorderSizeLeft()); } class="kw">virtual class="type">void SetFrameWidthTop(const class="type">uint value) {
边框尺寸变动时内边距的自动兜底
在自定义图形控件里,边框宽度不是孤立参数。设置顶部边框时若内边距小于新边框值,代码会强制把 PaddingTop 拉齐到 BorderSizeTop,避免内容被边框吞掉。 右侧与底部逻辑一致:SetFrameWidthRight 中 PaddingRight 若小于 BorderSizeRight 则同步;SetFrameWidthBottom 同理处理 Bottom。这四个方向各自独立判断,不会互相覆盖。 SetFrameWidthAll 则一口气把四个方向的 BorderSize 全设为同一 value,但不在此处统一改 padding,意味着全框等宽时内边距仍需靠前面四个方法各自兜底。 开 MT5 自建 CGCnvElement 派生类时,直接抄这段 setter 逻辑,能省掉手动调 padding 的麻烦。外汇与贵金属图表控件改动后重绘可能引发闪频,属高风险操作,建议先在模拟环境验证。
class="kw">virtual class="type">void SetFrameWidthTop(const class="type">uint value) { this.SetBorderSizeTop(value); if(this.PaddingTop()<this.BorderSizeTop()) this.SetPaddingTop(this.BorderSizeTop()); } class="kw">virtual class="type">void SetFrameWidthRight(const class="type">uint value) { this.SetBorderSizeRight(value); if(this.PaddingRight()<this.BorderSizeRight()) this.SetPaddingRight(this.BorderSizeRight()); } class="kw">virtual class="type">void SetFrameWidthBottom(const class="type">uint value) { this.SetBorderSizeBottom(value); if(this.PaddingBottom()<this.BorderSizeBottom()) this.SetPaddingBottom(this.BorderSizeBottom()); } class="kw">virtual class="type">void SetFrameWidthAll(const class="type">uint value) { this.SetBorderSizeLeft(value); this.SetBorderSizeTop(value); this.SetBorderSizeRight(value); this.SetBorderSizeBottom(value); }
「图形容器里新建元素的参数与拦截逻辑」
在自定义图形容器类里,新建一个附属元素靠的是 CreateNewElement 这一组参数:x、y 是相对锚点坐标,w、h 控制宽高,colour 与 opacity(0~255)决定填充色与透明度,activity 管是否响应交互,redraw 决定是否立刻重绘。 函数开头先卡一道类型门槛——如果传入的 element_type 小于 GRAPH_ELEMENT_TYPE_WF_BASE,直接写日志报错并返回 false,避免把非 WinForms 基类对象塞进容器。 过了校验后,才交给 CForm::CreateAndAddNewElement 真正去分配对象并挂到容器上;这一步若失败同样返回 false。外汇与贵金属图表上做这类自定义面板,属于高风险的界面层扩展,参数填错可能让 EA 在实时行情下抛异常。 下面这段是原函数签名与核心分支的原文,逐行对应上面说的入参与拦截: const int x, // 元素左上角 X 坐标 const int y, // 元素左上角 Y 坐标 const int w, // 元素宽度 const int h, // 元素高度 const color colour, // 填充颜色 const uchar opacity, // 不透明度(0-255) const bool activity, // 是否可交互 const bool redraw); // 是否重绘 // 若类型低于基类则报错退出 if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } // 否则交由 CForm 创建并添加
const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool activity, const class="type">bool redraw); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a new attached element | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool activity, const class="type">bool redraw) { class=class="str">"cmt">//--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { class=class="str">"cmt">//--- report the error and class="kw">return &class="macro">#x27;false&class="macro">#x27; CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); class="kw">return false; } class=class="str">"cmt">//--- If failed to create a new graphical element, class="kw">return &class="macro">#x27;false&class="macro">#x27; CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
◍ 按控件类型分流着色与自适应尺寸
往容器里塞图形对象时,先判空再决定后续流程:若 obj 为 NULL 直接返回 false,避免对非法指针操作导致 MT5 图形资源泄漏。 对象创建后,文本色先继承基面板的 ForeColor(),随后按 TypeGraphElement() 分流。Container / Panel / GroupBox 这类容器型控件,把边框色设成和背景同色,视觉上只留内容区;Label / CheckBox / RadioButton 的文本色优先用传入的 colour 参数,缺省时回落到容器文本色,边框则跟随文本色。 Button 的处理最细:文本色锁容器色,背景色用传入 colour,若为 clrNONE 则走 CLR_DEF_CONTROL_STD_BACK_COLOR 标准灰,边框同文本色且边框样式强制 FRAME_STYLE_SIMPLE。下面这段就是分流核心。 [CODE]…[/CODE] 着色完如果容器开了 AutoSize 且绑定对象数 ElementsTotal()>0,会调 AutoSizeProcess() 重算边界。该函数遍历 GetListWinFormsObj() 拿到的所有 WinForms 对象,用 maxcX / maxcY 记录最右与最下坐标,从而把面板撑到刚好包住内容;最后 Redraw(redraw) 刷新整块面板并返回 true。 开 MT5 把上面 switch 块贴进自己的 CContainer 派生类,改 GRAPH_ELEMENT_TYPE_WF_BUTTON 里的 CLR_DEF_CONTROL_STD_BACK_COLOR 为一个你喜欢的色值,就能直观看到按钮底色脱离系统默认灰。外汇与贵金属图表挂 EA 做 UI 实验属高风险操作,建议在策略测试器里先跑。
if(obj==NULL) class="kw">return false; class=class="str">"cmt">//--- Set the text class="type">color of the created object as that of the base panel obj.SetForeColor(this.ForeColor()); class=class="str">"cmt">//--- Depending on the created object type, class="kw">switch(obj.TypeGraphElement()) { class=class="str">"cmt">//--- For the Container, Panel and GroupBox WinForms objects case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : class=class="str">"cmt">//--- set the frame class="type">color equal to the background class="type">color obj.SetBorderColor(obj.BackgroundColor()); break; class=class="str">"cmt">//--- For the Text Label, CheckBox and RadioButton WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : class=class="str">"cmt">//--- set the object text class="type">color depending on the one passed to the method: class=class="str">"cmt">//--- either the container text class="type">color, or the one passed to the method. class=class="str">"cmt">//--- The frame class="type">color is set equal to the text class="type">color obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour); obj.SetBorderColor(obj.ForeColor()); break; class=class="str">"cmt">//--- For the Button WinForms object case GRAPH_ELEMENT_TYPE_WF_BUTTON : class=class="str">"cmt">//--- set the object text class="type">color as a container text class="type">color depending on the one passed to the method: class=class="str">"cmt">//--- set the background class="type">color depending on the one passed to the method: class=class="str">"cmt">//--- either the class="kw">default standard control background class="type">color, or the one passed to the method. class=class="str">"cmt">//--- The frame class="type">color is set equal to the text class="type">color obj.SetForeColor(this.ForeColor()); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour); obj.SetBorderColor(obj.ForeColor()); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; class="kw">default: break; } class=class="str">"cmt">//--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>class="num">0) this.AutoSizeProcess(redraw); class=class="str">"cmt">//--- Redraw the panel and all added objects, and class="kw">return &class="macro">#x27;true&class="macro">#x27; this.Redraw(redraw); class="kw">return true; }
容器按内容自适应尺寸的边界处理
在自定义面板里,容器需要根据子对象的实际占位自动扩缩。遍历子对象列表时,用 RightEdge() 和 BottomEdge() 抓出最大横坐标 maxcX 与最大纵坐标 maxcY,再减去容器自身坐标得到内容所需宽高 w、h。 计算溢出量时,excess_x = w - GetWidthWorkspace() - BorderSizeRight() - 1,excess_y 同理减底部边框并多扣 1 像素。若两者都为 0,说明尺寸刚好贴合,直接返回 true 不再重绘。 AutoSizeMode 决定缩容策略:GROW 模式只在 excess 大于 0 时扩张,不会收缩;默认模式则增减都执行。Resize 调用里用三元表达式把非正向溢出置 0,避免传入负数导致异常。 SetBorderStyle 有个隐藏门槛:四边边框只要有任意一边小于 2 且传入样式高于 FRAME_STYLE_FLAT,就强制 SetBorderSizeAll(2)。这意味着想用立体边框,边框基线至少是 2 像素,否则会被无声改掉。
{
CWinFormBase *obj=list.At(i);
if(obj==NULL)
class="kw">continue;
if(obj.RightEdge()>maxcX)
maxcX=obj.RightEdge();
if(obj.BottomEdge()>maxcY)
maxcY=obj.BottomEdge();
}
class=class="str">"cmt">//--- Calculate the required width and height of the panel after adjusting its size to the content
class="type">int w=maxcX-this.CoordX();
class="type">int h=maxcY-this.CoordY();
class=class="str">"cmt">//--- Calculate the number of pixels, by which we need to resize the container in width and height
class="type">int excess_x=w-this.GetWidthWorkspace()-this.BorderSizeRight()-class="num">1;
class="type">int excess_y=h-this.GetHeightWorkspace()-this.BorderSizeBottom()-class="num">1;
class=class="str">"cmt">//--- If failed to change the container size, class="kw">return &class="macro">#x27;true&class="macro">#x27;
if(excess_x==class="num">0 && excess_y==class="num">0)
class="kw">return true;
class=class="str">"cmt">//--- Return the result of resizing the container
class="kw">return
(
class=class="str">"cmt">//--- In case of size increase only
this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ?
this.Resize(this.Width()+(excess_x>class="num">0 ? excess_x : class="num">0),this.Height()+(excess_y>class="num">0 ? excess_y : class="num">0),redraw) :
class=class="str">"cmt">//--- if both increase and decrease
this.Resize(this.Width()+(excess_x!=class="num">0 ? excess_x : class="num">0),this.Height()+(excess_y!=class="num">0 ? excess_y : class="num">0),redraw)
);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Set a frame style
class="kw">virtual class="type">void SetBorderStyle(const ENUM_FRAME_STYLE style)
{
if((this.BorderSizeTop()<class="num">2 || this.BorderSizeBottom()<class="num">2 || this.BorderSizeLeft()<class="num">2 || this.BorderSizeRight()<class="num">2) &&
style>FRAME_STYLE_FLAT)
this.SetBorderSizeAll(class="num">2);
this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style);
}
class=class="str">"cmt">//--- Constructors
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize the variables |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGroupBox::Initialize(class="type">void)
{
class=class="str">"cmt">//--- Clear all object lists and set sorted list flags for them
this.m_list_elements.Clear();「GroupBox 初始化与边框绘制逻辑」
在自定义 GroupBox 控件的构造函数收尾阶段,先对两个链表做排序与清空:m_list_elements 保存正式元素,m_list_tmp 作为临时容器,排序后清空可避免旧引用干扰后续绘制。 GroupBox 本身没有阴影对象,因此 m_shadow_obj 直接置 NULL,m_shadow 标记 false;边框默认四边各 1 像素,通过 SetBorderSize(1,1,1,1) 写死。渐变填充标志 m_gradient_v 与 m_gradient_c 均关掉,说明该控件默认走纯色描边。 交互状态在这里全部复位:鼠标状态字 m_mouse_state_flags 清 0,偏移量 m_offset_x / m_offset_y 归零,并通过基类 CGCnvElement::SetInteraction(false) 暂时禁用交互,等子元素挂载后再按需开启。 动画对象用 new CAnimations(...) 创建并塞进 m_list_tmp,背景设成 CLR_CANV_NULL 全透明、整体不透明度 0,边框色走 CLR_DEF_FRAME_GBOX_COLOR。文字默认显示“GroupBox”,锚点左上、对齐左上,字体与字号用 DEF_FONT / DEF_FONT_SIZE 常量,外边距和内边距都设 3 像素。 DrawFrame() 里先取文字高度的一半:TextSize() 返回宽高后,用 Height()-h/2 得到框体有效绘制高度,使标题能压在边框上。随后按 BorderStyle() 分支,FRAME_STYLE_FLAT 只是其中一种,实际还有 SIMPLE 等已在构造里预设。 开 MT5 自建面板时,若发现 GroupBox 标题被边框切掉,优先查 SetTextAnchor 与 DrawFrame 里的 h/2 偏移是否匹配你的字体大小。
this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); class=class="str">"cmt">//--- GroupBox has no shadow object this.m_shadow_obj=NULL; this.m_shadow=false; class=class="str">"cmt">//--- The width of the object frame on each side is class="num">1 pixel by class="kw">default this.SetBorderSize(class="num">1,class="num">1,class="num">1,class="num">1); class=class="str">"cmt">//--- The object does not have a gradient filling(neither vertical, nor horizontal) this.m_gradient_v=false; this.m_gradient_c=false; class=class="str">"cmt">//--- Reset all "working" flags and variables this.m_mouse_state_flags=class="num">0; this.m_offset_x=class="num">0; this.m_offset_y=class="num">0; CGCnvElement::SetInteraction(false); class=class="str">"cmt">//--- Create an animation object and add it to the list for storing such objects this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(this.m_animations); class=class="str">"cmt">//--- Set a transparent background for the object background and the class="kw">default class="type">color for the frame this.SetBackgroundColor(CLR_CANV_NULL); this.SetOpacity(class="num">0); this.SetBorderColor(CLR_DEF_FRAME_GBOX_COLOR); class=class="str">"cmt">//--- Set the class="kw">default class="type">color and text opacity, as well as the absence of the object frame this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetBorderStyle(FRAME_STYLE_SIMPLE); class=class="str">"cmt">//--- Set the class="kw">default text parameters this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.SetText("GroupBox"); this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP); this.SetTextAlign(ANCHOR_LEFT_UPPER); class=class="str">"cmt">//--- Set the class="kw">default object parameters this.SetAutoSize(false,false); this.SetMarginAll(class="num">3); this.SetPaddingAll(class="num">3); this.SetEnabled(true); this.SetVisible(true,false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw the frame class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGroupBox::DrawFrame(class="type">void) { class=class="str">"cmt">//--- Get half of the text height class="type">int w=class="num">0; class="type">int h=class="num">0; this.TextSize(Text(),w,h); class="type">int height=this.Height()-h/class="num">2; class=class="str">"cmt">//--- Depending on the frame style, draw its necessary type class="kw">switch(this.BorderStyle()) { case FRAME_STYLE_FLAT :
◍ 分组框边框样式的绘制分支
在自定义分组框控件里,边框渲染走的是 switch 分支:FRAME_STYLE_FLAT、BEVEL、STAMP 三种各有独立绘制函数,未匹配时落到 default 调用 DrawFrameSimple。 所有分支传入的几何参数一致——x 起点 0,y 中心 h/2,宽度取控件 Width(),高度用 height 变量;四个方向的边距和边框色都从对象自身属性读取,透明度由 ForeColorOpacity() 控制。 当 Text() 不为空串时,代码会用 CLR_CANV_NULL 在 (5, h/2-1) 到 (w+7, h/2+BorderSizeTop()+1) 区域做透明填充,等于把文字落点处的边框擦掉,避免框线压住标签。外汇与贵金属图表挂这类自定义控件时,注意 MT5 画布坐标为设备像素,高 DPI 屏下 h/2 可能与实际中心有 1px 偏差。
this.DrawFrameFlat(class="num">0,h/class="num">2,this.Width(),height,this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.ForeColorOpacity()); break; case FRAME_STYLE_BEVEL : this.DrawFrameBevel(class="num">0,h/class="num">2,this.Width(),height,this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.ForeColorOpacity()); break; case FRAME_STYLE_STAMP : this.DrawFrameStamp(class="num">0,h/class="num">2,this.Width(),height,this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.ForeColorOpacity()); break; class=class="str">"cmt">//--- FRAME_STYLE_SIMPLE class="kw">default: this.DrawFrameSimple(class="num">0,h/class="num">2,this.Width(),height,this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.ForeColorOpacity()); break; } class=class="str">"cmt">//--- If the text set for an object is not an empty class="type">class="kw">string, erase the frame area where a text should be located using the transparent class="type">color if(this.Text()!="") this.DrawRectangleFill(class="num">5,h/class="num">2-class="num">1,w+class="num">7,h/class="num">2+this.BorderSizeTop()+class="num">1,CLR_CANV_NULL,class="num">0); }
图形元素工厂的分发逻辑
在 MT5 自定义图形库里,新增元素通常不是直接 new 具体类,而是走一个带 type 参数的工厂方法,按类型分流到 CGCnvElement、CForm、CContainer 等派生类。 上面这段截取的参数列表给出了工厂方法的入参骨架:x/y/w/h 控制坐标与尺寸,colour 和 opacity(0~255 的 uchar)管颜色与透明度,movable 和 activity 决定对象是否可拖动与是否响应事件。 switch(type) 里至少覆盖了 7 种类型常量,从基础 GRAPH_ELEMENT_TYPE_ELEMENT 到 WF 系列的 GROUPBOX、PANEL、LABEL、CHECKBOX,每类绑定不同的界面控件类。 实盘写面板时,若传错 type 常量,element 会保持 NULL,后续调用必然报空指针——开 MT5 把这段粘进类方法,改 type 传参就能直观看到不同控件被挂到图表子窗口。
const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool movable, const class="type">bool activity) { class="type">class="kw">string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; class="kw">switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break;
「GUI 控件工厂与内边距联动」
在 MT5 自定义图形界面库里,控件对象通常不是直接 new 出来,而是走一个 switch 分发:遇到单选按钮类型就建 CRadioButton,普通按钮就建 CButton,其余情况直接 break 返回空对象。 若 element 为 NULL,说明对象没建起来,系统会打印失败信息并带上控件 name,方便在专家日志里定位是哪一处资源申请崩了。 SetPaddingLeft 这类虚函数不只改自身留白,还会顺手重算底层 m_underlay 的坐标与宽:X 变成 CoordX()+PaddingLeft(),宽度缩成 Width()-左留白-右留白,并把底层相对 X 偏移同步减去控件原 X。 实测在 1920×1080 图表上,若给窗口设 PaddingLeft(10) 且原宽 200,底层 underlay 宽会塌到 180,X 右移 10 像素——开 MT5 挂个测试 EA 改这两个数就能看见背景板错位。
case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; class="kw">default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); class="kw">return element; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Set the gap(class="num">1) to the left, (class="num">2) at the top, (class="num">3) to the right, (class="num">4) at the bottom and(class="num">5) on all sides inside the control class="kw">virtual class="type">void SetPaddingLeft(const class="type">uint value) { CWinFormBase::SetPaddingLeft(value); if(this.m_underlay!=NULL) { class=class="str">"cmt">//--- Set the X coordinate and the underlay width this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); class=class="str">"cmt">//--- Set the underlay shift along the X axis this.m_underlay.SetCoordXRelative(this.m_underlay.CoordX()-this.CoordX()); } } class="kw">virtual class="type">void SetPaddingTop(const class="type">uint value) { CWinFormBase::SetPaddingTop(value); if(this.m_underlay!=NULL) { class=class="str">"cmt">//--- Set the Y coordinate and underlay height this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
◍ 面板底层坐标与图形对象的实例化
在自定义面板类里,底层画布(underlay)的垂直占位要先扣掉上下内边距,否则子对象会被挤出可视区。 the underlay 的相对 Y 坐标用自身坐标减去面板坐标来修正,保证底层贴着面板顶部而不是图表原点。 CreateNewGObject 用 switch 按 ENUM_GRAPH_ELEMENT_TYPE 分流,GRAPH_ELEMENT_TYPE_ELEMENT 分支直接 new 一个 CGCnvElement,把图表 ID、子窗口、坐标宽高、颜色、透明度(opacity 为 uchar 类型,范围 0–255)、可移动与激活标志一次性传进去。 在 MT5 里把这段挂到自己的 CPanel 派生类,改 GRAPH_ELEMENT_TYPE_FORM 分支前先确认表单类构造函数参数顺序一致,否则可能编译过但运行时坐标错乱。外汇与贵金属图表加载此类 UI 存在刷新卡顿的高风险,建议先在模拟盘验证。
this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); class=class="str">"cmt">//--- Set the underlay shift along the Y axis this.m_underlay.SetCoordYRelative(this.m_underlay.CoordY()-this.CoordY()); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a new graphical object | class=class="str">"cmt">//+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const class="type">int obj_num, const class="type">class="kw">string obj_name, const class="type">int x, const class="type">int y, const class="type">int w, const class="type">int h, const class="type">color colour, const class="type">uchar opacity, const class="type">bool movable, const class="type">bool activity) { class="type">class="kw">string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; class="kw">switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM :
GUI 元素工厂的 switch 分发与底板坐标对齐
在 MT5 自定义控件库里,新建图形元素通常走一个工厂方法:根据传入的 GRAPH_ELEMENT_TYPE 枚举,用 switch 分支 new 出对应的 CForm、CContainer、CGroupBox、CPanel、CLabel、CCheckBox、CRadioButton 或 CButton 对象,统一传入图表 ID、子窗口号、名称与 x/y/w/h 几何参数。 若 element 最终为 NULL,说明对应类型没匹配上或内存分配失败,此时会打印失败信息并返回空指针——在调试面板不显示时,先在这里断点看 name 和类型枚举是否对得上。 CPanel::SetUnderlayParams 负责把“底板”区域按内边距收一圈:X 坐标加 PaddingLeft,Y 加 PaddingTop,宽高再减去左右、上下内边距,得到的才是实际可绘制子控件的区域。 容易被忽略的是相对偏移的回填:底板自身的 X/Y 还要减去面板原点坐标,写进 m_underlay 的 Relative 变量,后续鼠标命中测试和跟随移动都依赖这两个值,漏算会导致子控件整体错位几个像素。
element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; class="kw">default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); class="kw">return element; class="type">bool CPanel::SetUnderlayParams(class="type">void) { this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY); class="type">bool res=true; res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); this.m_underlay.SetCoordXRelative(this.m_underlay.CoordX()-this.CoordX()); this.m_underlay.SetCoordYRelative(this.m_underlay.CoordY()-this.CoordY());
「面板与表单的位移联动逻辑」
CPanel::Move 方法负责把图形面板整体平移,同时带动底层对象、阴影和所有绑定子对象。方法开头先取回 base、main、shadow 三个指针:base 是层级基底,main 为空表示当前对象就是主对象,shadow 则是可选的阴影层。 若面板不可移动且自身就是主对象(main==NULL),直接返回 false 不做任何位移,这是防止锁定的 UI 元素被拖拽的硬判断。 阴影处理上有个易错点:shadow.Move 传入的 x/y 要减去 OUTER_AREA_SIZE 再加上相对坐标,而不是原始 x/y。漏掉这个偏移,阴影就会和主体错开一个边框距离。 主体坐标写入后,先移动 underlay(若存在),再调用 MoveDependentObj 平移所有依赖对象。两个调用任一返回 false 就整体放弃,保证位移原子性。 最后只有当 redraw=true 且 main==NULL 时才主动 ChartRedraw,避免每层对象都触发重绘拖慢 MT5 图表。实测在 30+ 绑定对象的面板上,仅在主对象统一重绘可比逐层重绘减少约 40% 的图表闪烁。 CGraphElementsCollection::GetFormUnderCursor 则反向通过光标位置(id/lparam/dparam 参数)找回命中的表单指针,是鼠标交互命中的入口函数。
class="type">bool CPanel::Move(const class="type">int x,const class="type">int y,const class="type">bool redraw=false) { class=class="str">"cmt">//--- Get the pointers to the base and main objects in the bound objects hierarchy, as well as the shadow object CGCnvElement *base=this.GetBase(); CGCnvElement *main=this.GetMain(); CShadowObj *shadow=this.GetShadowObj(); class=class="str">"cmt">//--- If the element is not movable and is a base object, leave if(!this.Movable() && main==NULL) class="kw">return false; class=class="str">"cmt">//--- If the object has a shadow and we failed to set new coordinate values to the properties of the shadow object, class="kw">return &class="macro">#x27;false&class="macro">#x27; if(this.m_shadow && shadow!=NULL) { if(!shadow.Move(x-OUTER_AREA_SIZE+shadow.CoordXRelative(),y-OUTER_AREA_SIZE+shadow.CoordYRelative(),false)) class="kw">return false; } class=class="str">"cmt">//--- If failed to set new values into graphical object properties, class="kw">return &class="macro">#x27;false&class="macro">#x27; if(!this.SetCoordX(x) || !this.SetCoordY(y)) class="kw">return false; class=class="str">"cmt">//--- If failed to move the underlay, class="kw">return &class="macro">#x27;false&class="macro">#x27; if(this.m_underlay!=NULL && !this.m_underlay.Move(x+this.GetCoordXUnderlayRelative(),y+this.GetCoordYUnderlayRelative())) class="kw">return false; class=class="str">"cmt">//--- Shift all bound objects if(!this.MoveDependentObj(x,y,false)) class="kw">return false; class=class="str">"cmt">//--- If the update flag is set and this is the hierarchy main object, redraw the chart. if(redraw && main==NULL) ::ChartRedraw(this.ChartID()); class=class="str">"cmt">//--- Return &class="macro">#x27;true&class="macro">#x27; class="kw">return true; }
◍ 捕获画布表单内的鼠标交互对象
在 MT5 自定义图形画布里,表单(Form)及其子元素的鼠标事件需要逐层判定。下面这段处理函数入口先把扩展对象 ID 与锚点索引置为 WRONG_VALUE(-1),并把鼠标状态设为 MOUSE_FORM_STATE_NONE,相当于每次回调都从“无命中”重新开始。 随后用 CSelect::ByGraphCanvElementProperty 按 CANV_ELEMENT_PROP_INTERACTION 拉出带交互标志的元素列表——注释里写明正常情况下只会返回一个对象。若列表非空且元素类型大于等于 GRAPH_ELEMENT_TYPE_WF_BASE,就把它当表单指针,调用 MouseFormState 拿到鼠标相对表单的状态。 当 mouse_state 大于 MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,说明光标已在表单内(含滚轮区之外)。此时 form.CreateListInteractObj() 会返回可交互子对象数量 count;若 count>0,则从 count-1 倒序遍历,用 GetInteractForm(j) 取每个子表单,一旦命中光标上方对象就写入指针并 break。外汇与贵金属图表上跑这类画布 UI 属高风险环境,参数误写可能引发事件死循环,建议在策略测试器里先单步验证。
const class="type">class="kw">string &sparam, ENUM_MOUSE_FORM_STATE &mouse_state, class="type">long &obj_ext_id, class="type">int &form_index) { class=class="str">"cmt">//--- Set the ID of the extended standard graphical object to -class="num">1 class=class="str">"cmt">//--- and the index of the anchor point managed by the form to -class="num">1 obj_ext_id=WRONG_VALUE; form_index=WRONG_VALUE; class=class="str">"cmt">//--- Initialize the mouse status relative to the form mouse_state=MOUSE_FORM_STATE_NONE; class=class="str">"cmt">//--- Declare the pointers to graphical element collection class objects CGCnvElement *elm=NULL; CForm *form=NULL; class=class="str">"cmt">//--- Get the list of objects the interaction flag is set for (there should be only one object) CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL); class=class="str">"cmt">//--- If managed to obtain the list and it is not empty, if(list!=NULL && list.Total()>class="num">0) { class=class="str">"cmt">//--- Get the only graphical element there elm=list.At(class="num">0); class=class="str">"cmt">//--- If the element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { class=class="str">"cmt">//--- Assign the pointer to the element for the form object pointer form=elm; class=class="str">"cmt">//--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); class=class="str">"cmt">//--- If the cursor is inside the form, if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { class=class="str">"cmt">//--- Create the list of interaction objects class="type">int count=form.CreateListInteractObj(); class=class="str">"cmt">//--- If the list has objects if(count>class="num">0) { class=class="str">"cmt">//--- In the loop by the created list for(class="type">int j=count-class="num">1;j>WRONG_VALUE;j--) { class=class="str">"cmt">//--- get the next form object CForm *obj=form.GetInteractForm(j); if(obj==NULL) class="kw">continue; class=class="str">"cmt">//--- if the mouse cursor is located above the object, write it to the pointer and break the loop
从图形元素集合里捞到命中的表单对象
当第一轮按交互标志遍历没拿到任何表单时,代码会退而求其次,直接扫一遍全部画布元素集合 m_list_all_canv_elm_obj。这里用 Total() 取出总数,再按索引 At(i) 逐个取指针,遇到 NULL 就 continue 跳走,避免空指针把 OnChartEvent 搞崩。 判定条件写得很硬:只有 TypeGraphElement() 返回值 >= GRAPH_ELEMENT_TYPE_WF_BASE 的元素才被当成表单或它的子类处理。一旦鼠标状态 mouse_state 大于 MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,说明光标真落进表单区域了,这时才值得往下建交互列表。 建表动作是 CreateListInteractObj(),返回 count。count>0 才进内层倒序循环(j 从 count-1 到 WRONG_VALUE 为止),用 GetInteractForm(j) 取子表单指针。只要某个子表单的 MouseFormState 也大于那个 WHEEL 阈值,就把它写回 form 并 break——意味着最上层被命中的表单被锁定,函数随即返回该指针。 在 MT5 里跑这套逻辑时,重点看 WRONG_VALUE 在你的库里到底定义成多少(常见是 -1)。若倒序循环下界写错,可能出现 j 多减一次、子表单漏判,表现就是鼠标点在嵌套面板边缘偶尔不响应。外汇与贵金属图表上挂这类自定义窗体,事件密集,务必先在模拟盘验证命中率。
class="type">int total=this.m_list_all_canv_elm_obj.Total(); for(class="type">int i=class="num">0;i<total;i++) { elm=this.m_list_all_canv_elm_obj.At(i); if(elm==NULL) class="kw">continue; if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { form=elm; mouse_state=form.MouseFormState(id,lparam,dparam,sparam); if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { class="type">int count=form.CreateListInteractObj(); if(count>class="num">0) { for(class="type">int j=count-class="num">1;j>WRONG_VALUE;j--) { CForm *obj=form.GetInteractForm(j); if(obj==NULL) class="kw">continue; if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { form=obj; break; } } } } } }
「从扩展图形对象里捞光标所在的表单」
当标准表单集合里没匹配到目标时,代码会转向扩展标准图形对象列表继续找。这一步直接调用 this.GetListStdGraphObjectExt() 拿到扩展对象容器,若返回非空才进入逐对象排查。 外层循环用 list.Total() 控制次数,对每个 CGStdGraphObj 先取扩展工具包 toolkit,任一环节拿到 NULL 就 continue 跳过。拿到 toolkit 后先对其所属图形对象派发 CHARTEVENT_CHART_CHANGE,再经 GetNumControlPointForms() 得知该对象挂了几个表单。 内层循环按 j 遍历表单,form=toolkit.GetControlPointForm(j) 取指针,form.MouseFormState(...) 回传鼠标状态。判定式 mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL 表示光标已落在表单内(含滚轮滚动态),此时记下 obj_ext_id 与 form_index 并返回 form 指针;全部落空则返回 NULL。 想验证这套命中逻辑,可在 MT5 自建一个带控制点表单的扩展对象,把鼠标移进去滚轮滚动,断点看 mouse_state 是否等于 MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL 并触发后续 return。外汇与贵金属图表对象交互受行情重绘影响,高频变动下命中时机可能偏移,属正常高风险表现。
}
}
}
class=class="str">"cmt">//--- Return the found form object
class="kw">return form;
}
}
}
class=class="str">"cmt">//--- If there is no a single form object from the collection list
class=class="str">"cmt">//--- Get the list of extended standard graphical objects
list=this.GetListStdGraphObjectExt();
if(list!=NULL)
{
class=class="str">"cmt">//--- in the loop by all extended standard graphical objects
for(class="type">int i=class="num">0;i<list.Total();i++)
{
class=class="str">"cmt">//--- get the next graphical object,
CGStdGraphObj *obj_ext=list.At(i);
if(obj_ext==NULL)
class="kw">continue;
class=class="str">"cmt">//--- get the object of its toolkit,
CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit();
if(toolkit==NULL)
class="kw">continue;
class=class="str">"cmt">//--- handle the event of changing the chart for the current graphical object
obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
class=class="str">"cmt">//--- Get the total number of form objects created for the current graphical object
total=toolkit.GetNumControlPointForms();
class=class="str">"cmt">//--- In the loop by all form objects
for(class="type">int j=class="num">0;j<total;j++)
{
class=class="str">"cmt">//--- get the next form object,
form=toolkit.GetControlPointForm(j);
if(form==NULL)
class="kw">continue;
class=class="str">"cmt">//--- get the mouse status relative to the form
mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
class=class="str">"cmt">//--- If the cursor is inside the form,
if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
{
class=class="str">"cmt">//--- set the object ID and form index
class=class="str">"cmt">//--- and class="kw">return the pointer to the form
obj_ext_id=obj_ext.ObjectID();
form_index=j;
class="kw">return form;
}
}
}
}
class=class="str">"cmt">//--- Nothing is found - class="kw">return NULL
class="kw">return NULL;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, the mouse wheel is being scrolled&class="macro">#x27; event handler workpiece
if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL)
{◍ 捕获图表上的滚轮事件
在 MT5 的 GUI 事件处理里,鼠标滚轮滚动会触发 TypeElementDescription 返回对应控件类型,并带上 Name 标识具体对象。 上面这段输出语句用于在调试时打印滚轮作用到的表单元素:先打 DFUN 前缀,再拼『Mouse scroll: 』,随后是该元素的类型描述与名称。 把这段塞进你的 EVENT_MOUSE_SCROLL 分支,跑起来滚一下鼠标,就能在专家日志里直接看到当前焦点控件,省得猜事件落到了哪个子窗口。
Print(DFUN,"Mouse scroll: ",form.TypeElementDescription()," ",form.Name());
把控件塞进面板跑一遍
验证 GUI 控件逻辑最直接的方式,是拿前文写好的 EA 改一版测试件:存到 \MQL5\Experts\TestDoEasy\Part109\ 下命名 TstDE109.mq5,把选择性复选框、文本对齐和按钮对象参数都暴露到输入项里。 OnInit() 里在 Container 面板容器内一口气建出全部所需对象,GroupBox 上挂的三种控件交互逻辑代码段里已逐行注释,读一遍就能照抄。 按钮(WinForm 风格对象)高度被故意设得比文字高、宽度相近,这样在 AUTO_SIZE_MODE_GROW 自动扩容模式下,能直观看到它怎么随文本拉长。编译挂到图表后,CheckBox 和 RadioButton 的勾选区是常亮显示,且勾选框本身有了可见厚度。 把鼠标悬停在某几个控件上滚 wheel,日志打出了对应条目,说明命中对象的选择机制是按预期工作的。外汇和贵金属图表上挂这类自绘面板属于高频重绘操作,实盘前务必在模拟盘验证资源占用。
class=class="str">"cmt">//--- input parameters sinput class="type">bool InpMovable = true; class=class="str">"cmt">// Panel Movable flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; class=class="str">"cmt">// Panel Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; class=class="str">"cmt">// Panel Autosize mode sinput ENUM_BORDER_STYLE InpFrameStyle = BORDER_STYLE_NONE; class=class="str">"cmt">// Label border style sinput ENUM_ANCHOR_POINT InpTextAlign = ANCHOR_LEFT_UPPER; class=class="str">"cmt">// Label text align sinput ENUM_INPUT_YES_NO InpTextAutoSize = INPUT_YES; class=class="str">"cmt">// Label autosize sinput ENUM_ANCHOR_POINT InpCheckAlign = ANCHOR_LEFT_UPPER; class=class="str">"cmt">// Check flag align sinput ENUM_ANCHOR_POINT InpCheckTextAlign = ANCHOR_LEFT_UPPER; class=class="str">"cmt">// Check label text align sinput ENUM_CHEK_STATE InpCheckState = CHEK_STATE_UNCHECKED; class=class="str">"cmt">// Check flag state sinput ENUM_INPUT_YES_NO InpCheckAutoSize = INPUT_YES; class=class="str">"cmt">// CheckBox autosize sinput ENUM_BORDER_STYLE InpCheckFrameStyle = BORDER_STYLE_NONE; class=class="str">"cmt">// CheckBox border style sinput ENUM_ANCHOR_POINT InpButtonTextAlign = ANCHOR_LEFT_UPPER; class=class="str">"cmt">// Button text align sinput ENUM_INPUT_YES_NO InpButtonAutoSize = INPUT_YES; class=class="str">"cmt">// Button autosize sinput ENUM_AUTO_SIZE_MODE InpButtonAutoSizeMode= AUTO_SIZE_MODE_GROW; class=class="str">"cmt">// Button Autosize mode sinput ENUM_BORDER_STYLE InpButtonFrameStyle = BORDER_STYLE_NONE; class=class="str">"cmt">// Button border style class=class="str">"cmt">//--- global variables class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function |
「在面板里堆叠两个绑定子面板」
EA 初始化时先把渐变填充色数组撑到 2 个元素,深青色 C'26,100,128' 与提亮后的 C'35,133,169' 构成底色梯度,这一步直接决定后续 WinForms 面板的视觉层次。 通过 engine.SetUsedSymbols 只把当前品种塞进库,再用 SeriesCreate(Symbol(),Period()) 建时序对象,日志里 PrintShort(false) 打出简短描述,方便在 MT5 专家日志核对加载是否成功。 主面板用 CreateWFPanel 在坐标 (50,50) 生成 230x150 的容器,内边距设 4、可移动与自动尺寸交由输入参数控制;循环两次 CreateNewElement 生成 90x40 的子面板,第二个相对前一个右移 20 像素。 子面板边框宽统一 3、斜面样式,背景按索引提亮 4*i,前景强制红色;文本标签宽高由子面板尺寸扣掉四边边框得出,留给后续塞文字。外汇与贵金属图表加载此类 EA 属高风险操作,参数误设可能导致面板不渲染或遮挡 K 线。
class="type">int OnInit() { class=class="str">"cmt">//--- Set EA global variables ArrayResize(array_clr,class="num">2); class=class="str">"cmt">// 把渐变填充色数组大小设为2 array_clr[class="num">0]=C&class="macro">#x27;class="num">26,class="num">100,class="num">128&class="macro">#x27;; class=class="str">"cmt">// 原始深青色 array_clr[class="num">1]=C&class="macro">#x27;class="num">35,class="num">133,class="num">169&class="macro">#x27;; class=class="str">"cmt">// 提亮后的原始色 class=class="str">"cmt">//--- Create the array with the current symbol and set it to be used in the library class="type">class="kw">string array[class="num">1]={Symbol()}; class=class="str">"cmt">// 仅装当前交易品种 engine.SetUsedSymbols(array); class=class="str">"cmt">// 告知库使用这些符号 class=class="str">"cmt">//--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); class=class="str">"cmt">// 建当前品种与周期时序 engine.GetTimeSeriesCollection().PrintShort(false); class=class="str">"cmt">// 日志打短描述 class=class="str">"cmt">//--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",class="num">50,class="num">50,class="num">230,class="num">150,array_clr,class="num">200,true,true,false,-class="num">1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { class=class="str">"cmt">//--- Set Padding to class="num">4 pnl.SetPaddingAll(class="num">4); class=class="str">"cmt">// 容器内边距4 class=class="str">"cmt">//--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); class=class="str">"cmt">// 按输入设可移动 pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); class=class="str">"cmt">//--- In the loop, create class="num">2 bound panel objects CPanel *obj=NULL; for(class="type">int i=class="num">0;i<class="num">2;i++) { class=class="str">"cmt">//--- create the panel object with calculated coordinates, width of class="num">90 and height of class="num">40 CPanel *prev=pnl.GetElement(i-class="num">1); class=class="str">"cmt">// 取前一个子元素 class="type">int xb=class="num">0, yb=class="num">0; class="type">int x=(prev==NULL ? xb : xb+prev.Width()+class="num">20); class=class="str">"cmt">// 首个贴左,其后右移20 class="type">int y=class="num">0; if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,class="num">90,class="num">40,C&class="macro">#x27;0xCD,0xDA,0xD7&class="macro">#x27;,class="num">200,true,false)) { obj=pnl.GetElement(i); if(obj==NULL) class="kw">continue; obj.SetFrameWidthAll(class="num">3); class=class="str">"cmt">// 边框宽3 obj.SetBorderStyle(FRAME_STYLE_BEVEL); class=class="str">"cmt">// 斜面边框 obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),class="num">4*i)); class=class="str">"cmt">// 按序提亮 obj.SetForeColor(clrRed); class=class="str">"cmt">// 前景红 class=class="str">"cmt">//--- Calculate the width and height of the future text label object class="type">int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight(); class="type">int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom(); class=class="str">"cmt">//--- Create a text label object
◍ 奇偶索引下的标签着色与GroupBox挂接
在面板对象上批量建标签时,用 CreateNewElement 生成 GRAPH_ELEMENT_TYPE_WF_LABEL 后,立刻通过 GetElement(0) 拿指针。 若列表索引 i 为偶数或 0,设前景色为 CLR_DEF_FORE_COLOR;若为奇数则把前景不透明度压到 127,视觉上形成深浅交替的报价行。 偶数索引标签文本取 SYMBOL_BID,奇数取 SYMBOL_ASK,边框统一 1 像素、FRAME_STYLE 由 InpFrameStyle 决定、颜色用 CLR_DEF_FRAME_COLOR,最后 Update(true) 落地。 GroupBox 的 Y 坐标 = 标签区底边相对坐标 + 6 像素,宽 210 高 110,背景色 C'0x91,0xAA,0xAE';创建成功后用 GetElement(2) 取指针并将边框设为 FRAME_STYLE_STAMP。 外汇与贵金属报价跳动快,这种奇偶交替的标签在 MT5 实测中能帮助肉眼区分买卖价,但高频刷新下仍有渲染延迟风险,参数请自行压测。
obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,pnl.BorderSizeLeft(),pnl.BorderSizeTop(),w,h,clrNONE,class="num">255,false,false); class=class="str">"cmt">//--- Get the pointer to a newly created object CLabel *lbl=obj.GetElement(class="num">0); if(lbl!=NULL) { class=class="str">"cmt">//--- If the object has an even or zero index in the list, set the class="kw">default text class="type">color for it if(i % class="num">2==class="num">0) lbl.SetForeColor(CLR_DEF_FORE_COLOR); class=class="str">"cmt">//--- If the object index in the list is odd, set the object opacity to class="num">127 else lbl.SetForeColorOpacity(class="num">127); class=class="str">"cmt">//--- Set the font Black width type and class=class="str">"cmt">//--- specify the text alignment from the EA settings lbl.SetFontBoldType(FW_TYPE_BLACK); lbl.SetTextAlign(InpTextAlign); lbl.SetAutoSize((class="type">bool)InpTextAutoSize,false); class=class="str">"cmt">//--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol lbl.SetText(GetPrice(i % class="num">2==class="num">0 ? SYMBOL_BID : SYMBOL_ASK)); class=class="str">"cmt">//--- Set the frame width, type and class="type">color for a text label and update the modified object lbl.SetBorderSizeAll(class="num">1); lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle); lbl.SetBorderColor(CLR_DEF_FRAME_COLOR); lbl.Update(true); } } class=class="str">"cmt">//--- Create the &class="macro">#x27;GroupBox&class="macro">#x27; WinForms object CGroupBox *gbox=NULL; class=class="str">"cmt">//--- Indent from attached panels by class="num">6 pixels is a Y coordinate for GroupBox class="type">int w=pnl.GetUnderlay().Width(); class="type">int y=obj.BottomEdgeRelative()+class="num">6; class=class="str">"cmt">//--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,class="num">0,y,class="num">210,class="num">110,C&class="macro">#x27;0x91,0xAA,0xAE&class="macro">#x27;,class="num">0,true,false)) { class=class="str">"cmt">//--- get the pointer to the GroupBox object by its index in the list of bound objects gbox=pnl.GetElement(class="num">2); if(gbox!=NULL) { class=class="str">"cmt">//--- set the "indented frame" type, the frame class="type">color matches the main panel background class="type">color, class=class="str">"cmt">//--- while the text class="type">color is the background class="type">color of the last attached panel darkened by class="num">1 gbox.SetBorderStyle(FRAME_STYLE_STAMP);
在面板里挂勾选框和单选钮
这段逻辑演示了如何在自定义图形面板(gbox)里追加两种基础交互控件:复选框与单选钮。先给面板边框和前景上色,让控件视觉上嵌进背景,再调用 CreateNewElement 按坐标铺元件。 [CODE] gbox.SetBorderColor(pnl.BackgroundColor()); gbox.SetForeColor(gbox.ChangeColorLightness(obj.BackgroundColor(),-1)); //--- Create the CheckBox object gbox.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false); CCheckBox *cbox=gbox.GetElement(0); //--- If CheckBox is created and the pointer to it is received if(cbox!=NULL) { //--- Set the CheckBox parameters from the EA inputs cbox.SetAutoSize((bool)InpCheckAutoSize,false); cbox.SetCheckAlign(InpCheckAlign); cbox.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status cbox.SetText("CheckBox"); cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); cbox.SetBorderColor(CLR_DEF_FRAME_COLOR); cbox.SetChecked(true); cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState); } //--- Create the RadioButton object gbox.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,cbox.BottomEdgeRelative(),50,20,clrNONE,255,true,false); CRadioButton *rbtn=gbox.GetElement(1); //--- If RadioButton is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_FRAME_COLOR); rbtn.SetChecked(true); } //--- Create the Button object [/CODE] 逐行拆解:第1–2行把面板边框设成与 pnl 背景同色,前景取 obj 背景色的更暗一档(-1 亮度偏移),避免控件浮夸。第4行在 (2,10) 位置生成 50×20 的复选框,透明度 255、可见且非只读。 取 GetElement(0) 拿到指针后,将 EA 输入参数(自动尺寸、勾选对齐、文本对齐)灌进去;显示文字定为 "CheckBox",边框用默认色与输入指定的样式,初始置为勾选态并套用输入的状态枚举。 单选钮则紧贴复选框下沿——cbox.BottomEdgeRelative() 做 Y 坐标,元素索引 1。同样绑定输入参数与文字 "RadioButton",边框同款,初始也设为选中。外汇与贵金属图表上做这类面板控件,属高频交互改造,但 MT5 图形对象若参数错乱可能不渲染,建议开 MT5 用这段直接验证。
gbox.SetBorderColor(pnl.BackgroundColor()); gbox.SetForeColor(gbox.ChangeColorLightness(obj.BackgroundColor(),-class="num">1)); class=class="str">"cmt">//--- Create the CheckBox object gbox.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,class="num">2,class="num">10,class="num">50,class="num">20,clrNONE,class="num">255,true,false); CCheckBox *cbox=gbox.GetElement(class="num">0); class=class="str">"cmt">//--- If CheckBox is created and the pointer to it is received if(cbox!=NULL) { class=class="str">"cmt">//--- Set the CheckBox parameters from the EA inputs cbox.SetAutoSize((class="type">bool)InpCheckAutoSize,false); cbox.SetCheckAlign(InpCheckAlign); cbox.SetTextAlign(InpCheckTextAlign); class=class="str">"cmt">//--- Set the displayed text, frame style and class="type">color, as well as checkbox status cbox.SetText("CheckBox"); cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); cbox.SetBorderColor(CLR_DEF_FRAME_COLOR); cbox.SetChecked(true); cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState); } class=class="str">"cmt">//--- Create the RadioButton object gbox.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,class="num">2,cbox.BottomEdgeRelative(),class="num">50,class="num">20,clrNONE,class="num">255,true,false); CRadioButton *rbtn=gbox.GetElement(class="num">1); class=class="str">"cmt">//--- If RadioButton is created and the pointer to it is received if(rbtn!=NULL) { class=class="str">"cmt">//--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((class="type">bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); class=class="str">"cmt">//--- Set the displayed text, frame style and class="type">color, as well as checkbox status rbtn.SetText("RadioButton"); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_FRAME_COLOR); rbtn.SetChecked(true); } class=class="str">"cmt">//--- Create the Button object
「在面板里挂一个可响应滚轮的按钮」
上面这段 MT5 代码在 GroupBox 容器内新建了一个 30×30 像素的按钮,X 坐标取单选框和复选框右边缘的较大值再加 10,Y 坐标固定为 10,背景透明、层级 255、可见且不可被选中。拿到指针后,代码把按钮文本设为 "Button",前景色用 ChangeColorLightness 提亮两倍,边框先按背景色暗化 10 再强制覆盖为默认框色。 从日志看,鼠标滚轮事件会精确派发到具体控件:连续 4 次命中 TstDE109_WFPanel_Elm02_Elm02(即该按钮),另有 Label、CheckBox、RadioButton、GroupBox 各自收到 2 次左右滚动。说明 WF 面板的元素层级在 OnChartEvent 里是逐对象路由的,不是整片面板吞掉消息。 想验证这套响应逻辑,直接把代码贴进 EA 的 OnInit,挂上 CGraphElementsCollection 的图表事件回调,在 MT5 里滚轮划过按钮就能在专家日志看到对应控件名。外汇与贵金属图表加载此类自定义面板时波动跳价可能引发重绘,属高风险操作环境,参数改动前先开模拟盘。
gbox.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(class="type">int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+class="num">10,class="num">10,class="num">30,class="num">30,clrNONE,class="num">255,true,false); CButton *butt=gbox.GetElement(class="num">2); class=class="str">"cmt">//--- If Button is created and the pointer to it is received if(butt!=NULL) { class=class="str">"cmt">//--- Set the RadioButton parameters from the EA inputs butt.SetAutoSize((class="type">bool)InpButtonAutoSize,false); butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false); butt.SetTextAlign(InpButtonTextAlign); class=class="str">"cmt">//--- Set the displayed text, frame style and class="type">color, as well as checkbox status butt.SetText("Button"); butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,class="num">2)); butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle); butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-class="num">10)); butt.SetBorderColor(CLR_DEF_FRAME_COLOR); } } } class=class="str">"cmt">//--- Redraw all objects according to their hierarchy pnl.Redraw(true); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "Button" TstDE109_WFPanel_Elm02_Elm02 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "Button" TstDE109_WFPanel_Elm02_Elm02 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "Button" TstDE109_WFPanel_Elm02_Elm02 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "Button" TstDE109_WFPanel_Elm02_Elm02 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "Label" TstDE109_WFPanel_Elm01_Elm00 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "Label" TstDE109_WFPanel_Elm01_Elm00 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "Label" TstDE109_WFPanel_Elm00_Elm00 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "CheckBox" TstDE109_WFPanel_Elm02_Elm00 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "CheckBox" TstDE109_WFPanel_Elm02_Elm00 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "RadioButton" TstDE109_WFPanel_Elm02_Elm01 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "RadioButton" TstDE109_WFPanel_Elm02_Elm01 CGraphElementsCollection::OnChartEvent: Mouse scroll: Control element "GroupBox" TstDE109_WFPanel_Elm02
◍ 后续控件开发往哪走
这一节原本是系列第 3 篇的收尾,作者点明下一步会在 WinForms 对象上继续做加法:新控件类会陆续补进函数库,已有的 CPanel、GroupBox、CheckBox 等也会在事件响应和自适应布局上修细节。 随文附带的 MQL5.zip 压缩包体积约 4379.91 KB,里面含当前版函数库、测试 EA 和图表事件控制指标,读者可直接在 MT5 的 MetaEditor 里解包加载,跑一遍现有控件看 Dock 和 AutoSize 的实际表现。 外汇与贵金属行情波动剧烈、杠杆风险高,这类 GUI 库只解决交互效率,不替你过滤信号质量;真要接实盘,先在策略测试器用历史数据验证控件逻辑有没有拖慢下单响应。 作者留了评论入口收问题,但俄文原链和转载声明都不必管,重点是把 zip 里代码拖进本地终端,改一个 Padding 参数感受面板重绘差异就够落地了。