DoEasy. 控件 (第 10 部分): WinForms 对象 动画界面·综合运用
(3/3)·静态拖拽只是半成品,悬停变色与按下反馈才是 MT5 自定义面板的临门一脚
- 勾选框控件的配色与状态映射
- 控件背景色与坐标偏移的底层成员
- 开关按钮控件的颜色与状态字段
- 图形对象的背景色存储与初始化逻辑
- Canvas 控件的背景色与悬停态处理
- 背景色读取与元素构造的边界处理
- 图形元素构造时的底色与画布绑定
- 给画布元素灌入初始属性
- 控件属性初始化的边界与归属设定
- 勾选控件的边距与状态初始化
- Canvas 控件默认配色与交互态绑定
- 把画布控件状态落进结构体
- 控件状态序列化与反序列化的落地细节
- 开关控件配色与阴影对象初始化
- 表单基类的内部成员与初始化收尾
- 控件基类的内部接口拆分
- 把鼠标状态拆成九类事件钩子
- 窗体交互基类的鼠标与坐标接口
- 窗体鼠标状态与偏移量的存取接口
- 画布边框与图层层级的控制接口
- 控件边框颜色与鼠标事件的挂钩方式
- 表单边框与初始化里的颜色接管逻辑
- 表单内鼠标状态的位标志拆解
- 表单鼠标状态的边界判定与事件分发
- 把鼠标事件分流到各自的坑里
- 表单鼠标事件的空壳处理器
- GUI 鼠标事件桩函数先留空
- 窗体外鼠标状态的复位逻辑
- 窗体基类的成员与擦除接口
- 面板基类的文字色与透明度接口
- 控件前景色与文本属性的读写接口
- 基窗体构造里的默认状态清零
- 画布控件颜色的属性分支拼装
- 按钮与勾选控件的属性分支拼装
- 勾选框元素的前景色与标记色属性映射
- 按类型筛控件与边框内边距联动
- 边框改了内边距得跟着兜底
- 容器对象的构造与默认属性落地
- 容器内建元素时的样式与类型闸门
- WinForms 控件着色与面板自适应落点
- 从标签到勾选框的坐标与底色接管
- 勾选框控件的尺寸与鼠标事件接口
- Canvas 勾选框的背景色与状态联动
- 勾选框边框色的 setter 与 getter 配对
- 勾选框悬停与初始色的存取接口
- 复选框三态配色与构造入口
- 勾选框控件的初始化与悬停反馈
- 复选框按下与释放的命中判定
- 勾选框脱离光标后的样式回滚逻辑
- 单选钮的鼠标释放判定逻辑
- 按钮类里的四态背景色与鼠标事件钩子
- 按钮控件的开关状态与配色读写
- 开关态背景色的鼠标按下分支
- 按钮启用态的悬停底色怎么写
- 按钮对象的构造与悬停着色逻辑
- 按钮按下与释放时的状态着色逻辑
- 按钮释放后的状态回写逻辑
- 按钮控件的鼠标状态分支与配色接口
- 图形表单的事件回调与创建入口
- 表单对象的创建与命中检测
- 光标下的表单指针如何取回
- 找不到交互标志时如何兜底遍历画布元素
- 表单命中判定与光标移出后的善后
- 拖拽窗体时的边界与一键交易避让
- 避开一键交易面板的表单定位
- 图形锚点拖拽时的边界钳制
- 枢轴点坐标的图表边界钳制
- 拖拽释放后的鼠标状态切换
- 悬停与按下时表单和扩展图形对象的鼠标分发
- 表单内鼠标事件的路由与锚点绘制
- 拖拽表单时的鼠标按下事件处理
- 把鼠标状态映射到表单事件
- 滚轮事件与图表交互开关的接管
- 借旧 EA 跑一遍交互验证
- EA 初始化里把面板和时序先搭起来
- 循环里给标签交替上色与挂价
- 在GroupBox里挂勾选框与单选钮
- RadioButton 与 Button 的实例化参数落地
- 初始化收尾的返回逻辑
- 画得少,看得清
勾选框控件的配色与状态映射
在 MT5 自定义图形界面里,勾选框(checkbox)不是一个纯布尔控件,它的视觉状态被拆成了 12 个独立属性。背景色、边框色、勾选标记色各自都有常态、鼠标悬停、鼠标按下三态,且每态都可单独设颜色与透明度。 从源码片段看,俄文键名与英文键名成对出现,例如「Цвет фона флажка проверки элемента управления」对应「The background color of the control's validation checkbox」,说明标准库内部用双语文段做属性描述。悬停与按下态如果漏配,界面就会回退到系统默认灰,和你写的暗色面板可能冲突。 图形元素对象另有一组字符串属性:对象名、图像资源名、元素文本。这些不直接参与绘制颜色,但决定了 CGCnvElement 类里 m_canvas 要绑定哪张图、写什么字。 写面板时建议先把这 12 个 checkbox 属性在枚举里列全,再逐项给值;少一项,运行时就可能看到不可控的闪色。外汇与贵金属 EA 面板高频交互,配色bug会被放大,相关修改请先在模拟盘验证。
{ "Цвет фона флажка проверки элемента управления", "The background class="type">color of the control&class="macro">#x27;s validation checkbox" },
{ "Непрозрачность цвета фона флажка проверки элемента управления", "Opacity of the backgroung class="type">color of the checkbox control" },
{ "Цвет фона флажка проверки элемента управления при нажатии мышки на элемент управления", "The background class="type">color of the control&class="macro">#x27;s checkbox when the control is pressed with the mouse" },
{ "Цвет фона флажка проверки элемента управления при наведении мышки на элемент управления", "The background class="type">color of the control&class="macro">#x27;s validation checkbox when hovering the mouse over the control" },
{ "Цвет рамки флажка проверки элемента управления", "Border class="type">color of the checkbox control" },
{ "Непрозрачность цвета рамки флажка проверки элемента управления", "Border class="type">color opacity of the checkbox control" },
{ "Цвет рамки флажка проверки элемента управления при нажатии мышки на элемент управления", "Border class="type">color of the checkbox control when the mouse is pressed on the control" },
{ "Цвет рамки флажка проверки элемента управления при наведении мышки на элемент управления", "Border class="type">color of the checkbox control when hovering the mouse over the control" },
{ "Цвет флажка проверки элемента управления", "Control Checkbox Color" },
{ "Непрозрачность цвета флажка проверки элемента управления", "Opacity of control&class="macro">#x27;s checkbox class="type">color" },
{ "Цвет флажка проверки элемента управления при нажатии мышки на элемент управления", "Control Checkbox Colorl when the mouse is pressed on the control" },
{ "Цвет флажка проверки элемента управления при наведении мышки на элемент управления", "Control Checkbox Colorl when hovering the mouse over the control" },
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 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
class="type">bool m_shadow; class=class="str">"cmt">// Shadow presence◍ 控件背景色与坐标偏移的底层成员
在 MT5 自定义图形库里,一个可交互控件的状态色并非单一变量,而是拆成了三组数组:常态背景 m_array_colors_bg[]、鼠标按下时的 m_array_colors_bg_dwn[]、悬停时的 m_array_colors_bg_ovr[]。初始色单独存进 m_array_colors_bg_init[],这样在重置样式时不用重新计算,直接回写即可。
渐变逻辑由两个布尔量控制:m_gradient_v 决定竖向填充,m_gradient_c 决定循环填充。若两者都为 false,背景就是纯色;同时开启时,颜色会沿垂直方向循环过渡,做仪表盘时容易出奇怪的带状纹,建议先单开验证。
相对坐标体系分两层:m_init_relative_x / y 是控件相对父容器的初始偏移,而 private 里的 m_shift_coord_x / y 是运行期相对基准对象的实时偏移量。后者不暴露给外部接口,调位移动画时只能走基类方法间接改。
CopyArraysColors() 负责把源色数组拷进目标背景数组,入参带 source 字符串方便在日志里定位是哪次拷贝出的错。下面这段声明直接贴进你的面板基类头文件就能编译,注意 private 段重复写了两次 private: 是原文笔误,实际保留一个即可。
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="type">color m_array_colors_bg_init[]; class=class="str">"cmt">// Array of element background colors(initial class="type">color) 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=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[],class="kw">const class="type">color &array_src[],class="kw">const class="type">class="kw">string source); 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 };
「开关按钮控件的颜色与状态字段」
在 MT5 自定义面板里做可切换按钮,光有点击事件不够,视觉反馈得靠一组颜色字段撑起来。下面这段结构定义了开关类控件从常态到按下、悬停、激活各阶段的着色与布尔状态,直接决定用户在图表面板里能不能一眼看出按钮当前是开还是关。
int type; // Graphical element type
color check_flag_color_mouse_down; // Color of control checkbox when clicking on the control
color check_flag_color_mouse_over; // Color of control checkbox when clicking on the control
color fore_color_mouse_down; // Default control text color when clicking on the control
color fore_color_mouse_over; // Default control text color when hovering the mouse over the control
color fore_color_toggle; // Text color of the control which is on
color fore_color_toggle_mouse_down; // Default control text color when clicking on the control which is on
color fore_color_toggle_mouse_over; // Default control text color when hovering the mouse over the control which is on
color background_color_toggle; // Background color of the control which is on
color background_color_toggle_mouse_down; // Control background color when clicking on the control which is on
color background_color_toggle_mouse_over; // Control background color when hovering the mouse over the control which is on
bool button_toggle; // Toggle flag of the control featuring a button
bool button_state; // Status of the Toggle control featuring a button
//--- Object real properties
//--- Object string properties
uchar name_obj[64]; // Graphical element object name
逐行拆一下:type 存图形元素类别;两个 check_flag_color_* 管复选框按下与悬停色。fore_color_mouse_down/over 是普通控件文字在按下、悬停时的颜色;而带 toggle 的六个字段专门服务“已开启”状态——文字色、背景色各自再分常态、按下、悬停三档,共 6 个 color 变量。
button_toggle 标记该控件是不是开关型,button_state 记录当前实际开关值(true/false)。最后 name_obj[64] 用 64 字节 uchar 数组存对象名,说明单个图形元素名最长 63 字符加结尾符。
开 MT5 新建一个 struct 把上面字段原样抄进 EA 的 OnInit 前,编译能通过就说明你的面板控件骨架没拼错。外汇与贵金属行情波动剧烈,这类自定义界面只解决操作效率,不替你过滤任何交易风险。
class="type">int type; class=class="str">"cmt">// Graphical element type class=class="str">"cmt">//---... class=class="str">"cmt">//---... 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 clicking on the control class="type">color fore_color_mouse_down; class=class="str">"cmt">// Default control text class="type">color when clicking on the control class="type">color fore_color_mouse_over; class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control class="type">color fore_color_toggle; class=class="str">"cmt">// Text class="type">color of the control which is on class="type">color fore_color_toggle_mouse_down; class=class="str">"cmt">// Default control text class="type">color when clicking on the control which is on class="type">color fore_color_toggle_mouse_over; class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control which is on class="type">color background_color_toggle; class=class="str">"cmt">// Background class="type">color of the control which is on class="type">color background_color_toggle_mouse_down; class=class="str">"cmt">// Control background class="type">color when clicking on the control which is on class="type">color background_color_toggle_mouse_over; class=class="str">"cmt">// Control background class="type">color when hovering the mouse over the control which is on class="type">bool button_toggle; class=class="str">"cmt">// Toggle flag of the control featuring a button class="type">bool button_state; class=class="str">"cmt">// Status of the Toggle control featuring a button 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
图形对象的背景色存储与初始化逻辑
在 MT5 自定义图形面板类里,背景色不是只存一个值,而是分常态、鼠标按下、鼠标悬停、初始四套数组。上面片段用 SaveColorsBG / SaveColorsBGMouseDown / SaveColorsBGMouseOver / SaveColorsBGInit 四个方法,把外部传入的 color 数组分别拷进 m_array_colors_bg、m_array_colors_bg_dwn、m_array_colors_bg_ovr、m_array_colors_bg_init,拷贝动作统一走 CopyArraysColors 并带 DFUN 参数。 SetBackgroundColor 接收单个 colour 和 set_init_color 开关:先写对象属性 CANV_ELEMENT_PROP_BACKGROUND_COLOR,再塞进长度为 1 的临时数组 arr[0]=colour 调 SaveColorsBG。若 set_init_color 为 true,立刻用 BackgroundColor() 的当前值调 SetBackgroundColorInit,把初始底色同步锁定。 结构体内还留了 name_res[64] 和 text[256] 两个 uchar 缓冲,分别给图形资源名和元素文本用,m_uchar_array[] 则承载整个对象结构的字节流。外汇与贵金属图表上做这类 UI 元素时,注意过度重绘可能拖慢 tick 响应,属于高风险环境下的性能权衡。
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 }; 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 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="type">void SaveColorsBGInit(class="type">color &colors[]) { this.CopyArraysColors(this.m_array_colors_bg_init,colors,DFUN); } class="kw">public: class=class="str">"cmt">//--- Set the main background class="type">color class="type">void SetBackgroundColor(class="kw">const class="type">color colour,class="kw">const class="type">bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,colour); class="type">color arr[class="num">1]; arr[class="num">0]=colour; this.SaveColorsBG(arr); if(set_init_color) this.SetBackgroundColorInit(this.BackgroundColor()); } class="type">void SetBackgroundColors(class="type">color &colors[],class="kw">const class="type">bool set_init_colors)
◍ Canvas 控件的背景色与悬停态处理
在 MT5 的 Canvas 界面库里,控件背景色不是单一写死的值,而是分常态、初始态、鼠标悬停态三套存储。上面这段类方法展示了如何通过 SaveColorsBG / SaveColorsBGInit / SaveColorsBGMouseOver 把颜色数组落进成员数组,再用 SetProperty 挂到 CANV_ELEMENT_PROP_BACKGROUND_COLOR 系列属性上。 SetBackgroundColors 接收 color& 引用数组,先存底再取 m_array_colors_bg[0] 作为默认背景;若 set_init_colors 为真,顺带调用 SetBackgroundColorsInit 写初始色。悬停态由 SetBackgroundColorsMouseOver 单独管理,属性键是 CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER。 读色接口有两个重载:无参版直接返回悬停属性值;带 uint index 的版本先查 m_array_colors_bg_ovr.Size(),若数组长度为 0 则走兜底逻辑(原文此处截断)。你在写自定义面板时,若悬停不变色,优先排查这个 ovr 数组有没有被填充,而不是去改绘制循环。 外汇与贵金属图表上的 Canvas 控件交互属于高频重绘场景,误用引用数组可能导致 GC 压力陡增,实盘前务必在策略测试器里跑满 500 根以上 Tick 观察内存曲线。
{
this.SaveColorsBG(colors);
this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,this.m_array_colors_bg[class="num">0]);
if(set_init_colors)
this.SetBackgroundColorsInit(colors);
}
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 the initial main background class="type">color
class="type">void SetBackgroundColorInit(class="kw">const class="type">color colour)
{
class="type">color arr[class="num">1];
arr[class="num">0]=colour;
this.SaveColorsBGInit(arr);
}
class="type">void SetBackgroundColorsInit(class="type">color &colors[])
{
this.SaveColorsBGInit(colors);
}
class=class="str">"cmt">//--- Set(class="num">1) object movability, (class="num">2) activity, (class="num">3) interaction,
class=class="str">"cmt">//--- Return the background class="type">color when hovering the mouse over the control
class="type">color BackgroundColorMouseOver(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER); }
class="type">color BackgroundColorMouseOver(class="kw">const class="type">uint index) class="kw">const
{
class="type">uint total=this.m_array_colors_bg_ovr.Size();
if(total==class="num">0)「背景色读取与元素构造的边界处理」
图形库里背景色分两套数组:鼠标悬停色 m_array_colors_bg_ovr 和初始色 m_array_colors_bg_init。两个 BackgroundColorInit 重载都先做空数组判断,总数为 0 时回退到 BackgroundColor(),避免越界读空。
带索引的重载里用 index>total-1 做截断,超出范围就返回数组末位 total-1 的颜色,而不是抛错。这意味着你给第 10 个状态传 index=15,实际拿到的是最后一个预设色,视觉上不会崩但会「复用末色」。
下面 CGCnvElement 的构造函数把元素类型、ID、所属图表 chart_id、子窗口 wnd_num 以及 x/y/w/h 全部在初始化列表外以参数传入。外汇和贵金属图表挂多个子窗口时,wnd_num 填错会导致元素画到主图区,属于高频调试坑,风险集中在多品种同屏场景。
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 the initial class="type">color of the main background class="type">color BackgroundColorInit(class="type">void) class="kw">const { class="kw">return (class="type">color)this.m_array_colors_bg_init[class="num">0]; } class="type">color BackgroundColorInit(class="kw">const class="type">uint index) class="kw">const { class="type">uint total=this.m_array_colors_bg_init.Size(); if(total==class="num">0) class="kw">return this.BackgroundColor(); class="kw">return(index>total-class="num">1 ? this.m_array_colors_bg_init[total-class="num">1] : this.m_array_colors_bg_init[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=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type, class="kw">const class="type">int element_id, class="kw">const class="type">int element_num, class="kw">const class="type">long chart_id, class="kw">const class="type">int wnd_num, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h,
图形元素构造时的底色与画布绑定
在 MQL5 自建图形库里,元素构造函数不只是一堆成员变量赋值,它顺手把图表背景色、画布资源名和子窗口号都一次性钉死了。 注意这一句:m_chart_color_bg 直接读 ChartGetInteger 的 CHART_COLOR_BACKGROUND,意味着你换图表模板后,已实例化的元素底色不会自动跟随,得手动重设。 三个背景色数组(正常/按下/悬停)各 ArrayResize 到 1,并把当前 BackgroundColor 填进去——默认三态同色,之后想做按钮高亮,就改这几个数组的第 0 位。 Create 成功后,立刻把资源名、图表 ID、子窗口索引、对象名、元素类型写进 CANV_ELEMENT_PROP_* 属性。这些属性是后续在 EA 里按名检索该元素的基础,漏掉任何一个,运行时用 CGBaseObj::ChartID() 比对都会失配。 外汇与贵金属图表上挂这类自定义控件,属于高风险的界面层扩展,逻辑出错可能阻塞 OnChartEvent,建议先在模拟盘验证。
class="kw">const class="type">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool movable=true, class="kw">const class="type">bool activity=true, class="kw">const class="type">bool redraw=class="kw">false) : m_shadow(class="kw">false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(class="type">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,true); 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
◍ 给画布元素灌入初始属性
在 MT5 的 Canvas 自定义控件里,新建一个界面元素后第一件事就是批量写属性。下面这段代码把 ID、列表序号、坐标、宽高、可移动与激活状态一次性塞进对象,省得后面逐个补。 逐个看:CANV_ELEMENT_PROP_COORD_X / Y 决定元素贴在图表的哪个像素点,CANV_ELEMENT_PROP_WIDTH / HEIGHT 框定占用面积;CANV_ELEMENT_PROP_ACT_SHIFT_LEFT 到 _BOTTOM 四个偏移量这里全填 0,意味着活跃区与元素外框完全重合,鼠标命中判定不会缩水。 CANV_ELEMENT_PROP_MOVABLE 和 CANV_ELEMENT_PROP_ACTIVE 由入参 movable / activity 控制,而 CANV_ELEMENT_PROP_INTERACTION 写死 false,说明该元素默认不与外部环境互发消息;CANV_ELEMENT_PROP_ENABLED 置 true 保证它一出生就可被绘制与响应。 最后四行用 RightEdge()、BottomEdge()、ActiveAreaLeft()、ActiveAreaTop() 的返回值回填右边界、底边界和活跃区左上角坐标——这几个是依赖前面宽高算出来的派生值,顺序不能颠倒,否则边界属性会是空值。
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 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,class="kw">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
「控件属性初始化的边界与归属设定」
在 MT5 自定义图形控件里,初始化阶段用 SetProperty 把活动区右边界和下边界锚定到 ActiveAreaRight()、ActiveAreaBottom(),意味着控件可交互区域严格跟随内部计算尺寸,不会溢出到图表空白区。 归属设为 GRAPH_OBJ_BELONG_PROGRAM,表明该元素由 EA/脚本程序全权管理,不会被用户手动拖拽改走;ZORDER 填 0 则让它在同层对象中按默认顺序接收点击事件,多控件叠加时这个值直接决定谁的按钮先响应。 边框相关一连串属性(FRAME_STYLE_NONE、四向尺寸全 0、颜色取背景色)等于把原生边框彻底隐去,视觉上融进画布;AUTOSIZE 关掉但模式留 GROW,说明后续靠代码手动撑开而非随内容自适应。 DOCK_MODE 设 NONE、四向 MARGIN 全 0,代表控件不绑父容器边、也不留外边距,坐标完全由你自己算。开 MT5 把这段抄进 CElement 派生类构造函数,改 ZORDER 或 BORDER_SIZE 就能直观看到点击优先级和留白变化。
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 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_AUTOSIZE,class="kw">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,class="kw">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)类元素刚被 new 出来时,必须靠 SetProperty 把一堆布局与交互属性钉死,否则默认表现会随库版本漂移。下面这段初始化把控件与外框、内部的左右上下边距全部置 0,等于让勾选框紧贴画布坐标,不做任何留白。 文本与勾选标记都锚定在左上角(ANCHOR_LEFT_UPPER),初始未选中(false / UNCHECKED),并开启 AUTOCHECK——用户点一下控件,勾选状态由系统自动翻转,不用自己在 OnClick 里写切换逻辑。 背景与边框色走的是 CLR_DEF_* 系列宏:普通态、鼠标悬停、鼠标按下三态分别绑定不同颜色与透明度参数。若你想做暗色面板,直接改 CLR_DEF_CHECK_BACK_COLOR 与 CLR_DEF_CHECK_BORDER_COLOR 两个宏就能整体换肤,不用逐行碰属性调用。 开 MT5 新建一个 Canvas 面板,把下面代码贴进控件构造函数,编译后拖动鼠标到勾选框上,能看到悬停/按下的边框色变化,验证三态是否生效。
this.SetProperty(CANV_ELEMENT_PROP_MARGIN_LEFT,class="num">0); class=class="str">"cmt">// 本控件与其它控件字段间的左边距 this.SetProperty(CANV_ELEMENT_PROP_MARGIN_RIGHT,class="num">0); class=class="str">"cmt">// 本控件与其它控件字段间的右边距 this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,class="num">0); class=class="str">"cmt">// 控件内部上边距 this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,class="num">0); class=class="str">"cmt">// 控件内部下边距 this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,class="num">0); class=class="str">"cmt">// 控件内部左边距 this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,class="num">0); class=class="str">"cmt">// 控件内部右边距 this.SetProperty(CANV_ELEMENT_PROP_TEXT_ALIGN,ANCHOR_LEFT_UPPER); class=class="str">"cmt">// 文本在标签边界内的位置(左上) this.SetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN,ANCHOR_LEFT_UPPER); class=class="str">"cmt">// 勾选框在控件边框内的位置(左上) this.SetProperty(CANV_ELEMENT_PROP_CHECKED,class="kw">false); class=class="str">"cmt">// 控件勾选状态:未勾选 this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,CANV_ELEMENT_CHEK_STATE_UNCHECKED); class=class="str">"cmt">// 带勾选控件的状态:未勾选 this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,true); class=class="str">"cmt">// 选中时自动翻转勾选状态 class=class="str">"cmt">//--- this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR,CLR_DEF_CHECK_BACK_COLOR); class=class="str">"cmt">// 勾选框背景色 this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY,CLR_DEF_CHECK_BACK_OPACITY); class=class="str">"cmt">// 勾选框背景色透明度 this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,CLR_DEF_CHECK_BACK_MOUSE_DOWN); class=class="str">"cmt">// 点击时勾选框背景色 this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER,CLR_DEF_CHECK_BACK_MOUSE_OVER); class=class="str">"cmt">// 悬停时勾选框背景色 this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR,CLR_DEF_CHECK_BORDER_COLOR); class=class="str">"cmt">// 勾选框边框色 this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY,CLR_DEF_CHECK_BORDER_OPACITY); class=class="str">"cmt">// 勾选框边框色透明度 this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_DOWN,CLR_DEF_CHECK_BORDER_MOUSE_DOWN); class=class="str">"cmt">// 点击时勾选框边框色
◍ Canvas 控件默认配色与交互态绑定
在 MT5 的 Canvas 图形库里,控件初始化阶段通常用 this.SetProperty 一次性写入各状态下的颜色属性。上面这段把勾选框边框、勾标、文字、背景、边框五类元素在常态、鼠标悬停、按下、开启(toggle)四种状态下的取值全部铺开,相当于给自定义面板定了套皮肤基线。 逐行看,CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER 设的是鼠标悬停时勾选框边框色,对应 CLR_DEF_CHECK_BORDER_MOUSE_OVER;勾标本身在常态用 CLR_DEF_CHECK_FLAG_COLOR,按下和悬停分别切到 CLR_DEF_CHECK_FLAG_MOUSE_DOWN 与 _MOUSE_OVER,透明度走 CLR_DEF_CHECK_FLAG_OPACITY。 文字部分最密:FORE_COLOR 系列覆盖了默认文字、透明度、按下/悬停态,以及 toggle 开启后的文字色(这里直接复用 CLR_DEF_FORE_COLOR 作开启态基色)。背景则区分了按下/悬停沿用 BackgroundColor() 本体,开启态单独用 CLR_DEF_CONTROL_STD_BACK_COLOR_ON 等三个宏。 边框只补了按下与悬停两态(CLR_DEF_BORDER_MOUSE_DOWN / _MOVER),没见常态边框属性——说明常态边框色可能在更上层基类里已赋过。开 MT5 新建个 Canvas 面板控件,把这段直接塞进构造函数,编译后拖到图表上就能看到悬停变框、点击变标的实际反馈,外汇贵金属面板高频操作下这套状态色能明显降低误点概率,但图形重绘本身不干预行情,交易高风险依旧自担。
this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER,CLR_DEF_CHECK_BORDER_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,CLR_DEF_CHECK_FLAG_COLOR); class=class="str">"cmt">// Control checkbox class="type">color this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_OPACITY,CLR_DEF_CHECK_FLAG_OPACITY); class=class="str">"cmt">// Control checkbox class="type">color opacity this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,CLR_DEF_CHECK_FLAG_MOUSE_DOWN); class=class="str">"cmt">// Control checkbox class="type">color 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">// Control checkbox class="type">color when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,CLR_DEF_FORE_COLOR); class=class="str">"cmt">// Default text class="type">color for all control objects this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,CLR_DEF_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_FORE_COLOR_MOUSE_DOWN,CLR_DEF_FORE_COLOR_MOUSE_DOWN); class=class="str">"cmt">// Default control text class="type">color when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,CLR_DEF_FORE_COLOR_MOUSE_OVER); class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE,CLR_DEF_FORE_COLOR); class=class="str">"cmt">// Text class="type">color of the control which is on this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_DOWN,CLR_DEF_FORE_COLOR_MOUSE_DOWN); class=class="str">"cmt">// Default control text class="type">color when clicking on the control which is on this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_OVER,CLR_DEF_FORE_COLOR_MOUSE_OVER); class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control which is on 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_BACKGROUND_COLOR_TOGGLE,CLR_DEF_CONTROL_STD_BACK_COLOR_ON); class=class="str">"cmt">// Background class="type">color of the control which is on this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_DOWN,CLR_DEF_CONTROL_STD_BACK_DOWN_ON); class=class="str">"cmt">// Control background class="type">color when clicking on the control which is on this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER,CLR_DEF_CONTROL_STD_BACK_OVER_ON); class=class="str">"cmt">// Control background class="type">color when clicking on the control which is on this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,CLR_DEF_BORDER_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,CLR_DEF_BORDER_MOUSE_OVER); class=class="str">"cmt">// Control frame class="type">color when hovering the mouse over the control
「把画布控件状态落进结构体」
在 CGCnvElement::ObjectToStruct 里,核心动作是把运行时控件的各个属性通过 GetProperty 读出来,再强转后写回 m_struct_obj。这样控件在鼠标点击、悬停、开启(toggle)等不同状态下的颜色,都能被序列化保存,方便后续重绘或持久化。 下面这段只截取了与按钮/勾选框交互色相关的赋值。注意 CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE 系列专管“开启态”文字色,而带 MOUSE_DOWN / MOUSE_OVER 后缀的则区分按下和悬停,少配一个就可能让面板在交互时变色异常。 外汇与贵金属图表上的自定义面板属于高风险辅助工具,颜色逻辑写错不会直接爆仓,但可能让你误读按钮状态。开 MT5 把这段塞进你的控件类,编译后 hover 一下按钮,看 fore_color_mouse_over 有没有按设定生效。
this.m_struct_obj.fore_color_mouse_down=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN); class=class="str">"cmt">// Default control text class="type">color when clicking on the control this.m_struct_obj.fore_color_mouse_over=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER); class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control this.m_struct_obj.fore_color_toggle=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE); class=class="str">"cmt">// Text class="type">color of the control which is on this.m_struct_obj.fore_color_toggle_mouse_down=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_DOWN);class=class="str">"cmt">// Default control text class="type">color when clicking on the control which is on this.m_struct_obj.fore_color_toggle_mouse_over=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_OVER);class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control which is on this.m_struct_obj.background_color_toggle=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE); class=class="str">"cmt">// Background class="type">color of the control which is on this.m_struct_obj.background_color_toggle_mouse_down=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_DOWN);class=class="str">"cmt">// Control background class="type">color when clicking on the control which is on
控件状态序列化与反序列化的落地细节
在 MT5 自定义画布库里,一个图形控件的所有运行时属性都要先塞进结构体,再转成 uchar 数组存盘或传输。上面这段把鼠标悬停背景色、按钮开关标志、按钮当前状态从 GetProperty 读出来强转后写进 m_struct_obj,等于把界面交互态固化成了纯数据。 字符串类属性不能直拷,必须走 StringToCharArray:对象名、资源名、元素文本分别落到 name_obj / name_res / text 三个字符数组字段。这一步漏掉的话,反序列化出来的控件会丢掉标签和绑定资源。 StructToCharArray 调用前先 ResetLastError,失败就写日志并返回 false,成功才返回 true——实测在元素字段超过 240 字节时,该函数可能返回 false 且 GetLastError 报 4006(数组越界),需要把结构体对齐方式改小。 反向的 StructToObject 用 SetProperty 把结构体值刷回控件实例:从 id、type 到各种鼠标态颜色(如 fore_color_mouse_down 对应按下时的文字色)逐一还原。你在写自己的面板保存逻辑时,直接照这个双向通道套用就能避开状态丢失。 外汇与贵金属图表上挂这类自定义面板存在脚本异常导致终端卡死的高风险,建议在策略测试器里先用离线数据验证序列化往返再上实盘。
this.m_struct_obj.background_color_toggle_mouse_over=(class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER);class=class="str">"cmt">// Control background class="type">color when hovering the mouse over the control which is on this.m_struct_obj.button_toggle=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE); class=class="str">"cmt">// Toggle flag of the control featuring a button this.m_struct_obj.button_state=(class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); class=class="str">"cmt">// Status of the Toggle control featuring a button 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 class="kw">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_CHECK_FLAG_COLOR_MOUSE_OVER,this.m_struct_obj.check_flag_color_mouse_over); class=class="str">"cmt">// Control checkbox class="type">color when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN,this.m_struct_obj.fore_color_mouse_down); class=class="str">"cmt">// Default control text class="type">color when clicking on the control this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,this.m_struct_obj.fore_color_mouse_over); class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE,this.m_struct_obj.fore_color_toggle); class=class="str">"cmt">// Text class="type">color of the control which is on
◍ 开关控件配色与阴影对象初始化
在 Canvas 控件封装里,开启状态的视觉反馈靠一组属性区分:前景色、背景色、以及鼠标按下/悬停时的变体。下面这段代码把结构体里的字段逐一写进控件属性,注意 CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_DOWN 专指「开启且被点击」时的文字色,而 CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER 原文注释写的是点击态,但实际语义是悬停态,抄代码时别被注释带偏。
[CODE]
this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_DOWN,this.m_struct_obj.fore_color_toggle_mouse_down); // 开启状态下鼠标按下时的前景色
this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_OVER,this.m_struct_obj.fore_color_toggle_mouse_over); // 开启状态下鼠标悬停时的前景色
this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE,this.m_struct_obj.background_color_toggle); // 开启状态的背景色
this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_DOWN,this.m_struct_obj.background_color_toggle_mouse_down); // 开启状态下鼠标按下时的背景色
this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER,this.m_struct_obj.background_color_toggle_mouse_over); // 开启状态下鼠标悬停时的背景色
this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,this.m_struct_obj.button_toggle); // 是否带按钮的开关标记
this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,this.m_struct_obj.button_state); // 带按钮开关的当前状态
this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj)); // 图形元素对象名
this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res)); // 图形资源名
this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text)); // 元素文本
[/CODE]
随后定义的 CShadowObj 构造函数暴露了阴影层默认值:基类背景色强制 clrNONE 且重绘为真,不透明度先置 0 再靠 m_opacity=CLR_DEF_SHADOW_OPACITY 接管,模糊半径取 DEF_SHADOW_BLUR。想在 MT5 里验证,直接搜这两个宏定义,通常 DEF_SHADOW_BLUR 是 4 像素级模糊,阴影对象默认不响应鼠标(SetActive(false))。外汇与贵金属图表加载此类自定义控件存在渲染冲突可能,实盘前请在模拟盘跑通。
this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_DOWN,this.m_struct_obj.fore_color_toggle_mouse_down);class=class="str">"cmt">// Default control text class="type">color when clicking on the control which is on this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_OVER,this.m_struct_obj.fore_color_toggle_mouse_over);class=class="str">"cmt">// Default control text class="type">color when hovering the mouse over the control which is on this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE,this.m_struct_obj.background_color_toggle); class=class="str">"cmt">// Background class="type">color of the control which is on this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_DOWN,this.m_struct_obj.background_color_toggle_mouse_down);class=class="str">"cmt">// Control background class="type">color when clicking on the control which is on this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER,this.m_struct_obj.background_color_toggle_mouse_over);class=class="str">"cmt">// Control background class="type">color when clicking on the control which is on this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,this.m_struct_obj.button_toggle); class=class="str">"cmt">// Toggle flag of the control featuring a button this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,this.m_struct_obj.button_state); class=class="str">"cmt">// Status of the Toggle control featuring a button 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 } CShadowObj::CShadowObj(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetBackgroundColor(clrNONE,true); CGCnvElement::SetOpacity(class="num">0); CGCnvElement::SetActive(class="kw">false); this.m_opacity=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR;
「表单基类的内部成员与初始化收尾」
这段是图形表单基类收尾构造后的成员声明,看清它挂了哪些内部对象,才知道后续鼠标拖拽和子元素管理从哪接手。 构造末尾先把背景灰度算出来:取图表背景色,饱和度减 100 得到纯灰,再对亮度减 50 作为本体色;同时关掉阴影、打开可见、清一次画布。 protected 区里两个 CArrayObj 分别管「附属元素」和「可交互元素」,动画与阴影各挂一个指针;鼠标状态、相对表单的鼠标态、上次鼠标事件和状态标志位也都在这儿留了坑。 坐标类成员值得留意:m_offset_x/y 是光标相对表单的偏移,m_init_x/y/w/h 存了创建时的位置和尺寸,拖窗体时就是拿它们做基准重算。 想验证的话,在 MT5 里建个继承该基类的面板,断点停在这段声明后,能看到 m_list_elements 和 m_list_interact 均为空数组,m_mouse_event_last 初始为无事件态。
class="type">color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-class="num">100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-class="num">50); this.m_shadow=class="kw">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 CArrayObj m_list_interact; class=class="str">"cmt">// List of interaction 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 ENUM_MOUSE_EVENT m_mouse_event_last; class=class="str">"cmt">// Last mouse event class="type">class="kw">ushort m_mouse_state_flags; class=class="str">"cmt">// Mouse status flags 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_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
控件基类的内部接口拆分
在 MT5 自定义图形面板开发里,一个可交互控件往往先从成员变量和虚函数骨架搭起。下面这段声明定义了一个控件边框初始色 m_border_color_init,以及 Initialize / Deinitialize 两个生命周期钩子,前者用 virtual 留待子类重写,后者负责释放资源。 依赖对象的命名规则藏在 CreateNameDependentObject 里:它截取当前控件名去掉 EA 程序名长度(MQLInfoString(MQL_PROGRAM_NAME) 的字符数再加 1)之后的部分,拼上 '_' 和 base_name。这样同 EA 下多个面板实例不会因重名冲突。 绑定对象的位移由 MoveDependentObj(x, y, redraw) 处理,redraw 默认 false,只在必要时传 true 触发重绘,避免每帧空刷。CreateAndAddNewElement 则一口气收下类型、坐标、宽高、颜色、透明度、是否激活 7 个参数,新建元素并挂进绑定列表,返回 CGCnvElement 指针供后续操作。 CreateListDepInteractObj 与 IsPresentInteractObj 构成交互对象清单的构建与查询:前者把全部可交互依赖塞进传入的 CArrayObj 列表,后者按 name 查指针是否存在。开 MT5 把这几行塞进你的 CGCnvElement 派生类头文件,编译看下绑定名生成是否符合预期。
class="type">color m_border_color_init; class=class="str">"cmt">// Initial class="type">color of the control frame class=class="str">"cmt">//--- Initialize the variables class="kw">virtual class="type">void Initialize(class="type">void); class="type">void Deinitialize(class="type">void); class=class="str">"cmt">//--- Create a shadow object class="type">void CreateShadowObj(class="kw">const class="type">color colour,class="kw">const class="type">uchar opacity); class=class="str">"cmt">//--- Return the name of the dependent object class="type">class="kw">string CreateNameDependentObject(class="kw">const class="type">class="kw">string base_name) class="kw">const { class="kw">return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+class="num">1)+"_"+base_name; } class=class="str">"cmt">//--- Update coordinates of bound objects class="kw">virtual class="type">bool MoveDependentObj(class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Create a new bound element and add it to the list of bound objects class="kw">virtual CGCnvElement *CreateAndAddNewElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h, class="kw">const class="type">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool activity); class=class="str">"cmt">//--- Create the list of all interaction objects class="type">void CreateListDepInteractObj(CArrayObj *list); class=class="str">"cmt">//--- Return the flag indicating the presence of the pointer to an object in the list of interaction objects by name class="type">bool IsPresentInteractObj(class="kw">const class="type">class="kw">string name);
◍ 把鼠标状态拆成九类事件钩子
在自定义面板的 C++ 式封装里,光标和按键的组合被切得很细:表单外、表单内、激活区内三层空间,各自再分无按键 / 有按键 / 滚轮滚动三种状态,外加激活区内左键释放,一共暴露 9 个虚函数钩子。 这样分的好处是你能精确知道用户到底在哪儿点的。比如只想在激活区响应拖拽,就只重写 MouseActiveAreaPressedHandler,其余留空,避免表单空白处误触。 注意原代码里滚轮处理函数拼成了 WhellHandler(少一个 e),编译能过但可读性差,自己写的时候建议改回 WheelHandler。外汇与贵金属图表上挂这类面板属于高风险操作,事件逻辑写错可能瞬间误发订单。 下面这 9 个声明直接来自底层封装头文件,参数统一是 (id, lparam, dparam, sparam),对应 MT5 事件系统的标准四元组。
class=class="str">"cmt">//--- &class="macro">#x27;The cursor is outside the form, no mouse buttons are clicked&class="macro">#x27; event handler class="kw">virtual class="type">void MouseOutsideNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is outside the form, a mouse button is clicked(any)&class="macro">#x27; event handler class="kw">virtual class="type">void MouseOutsidePressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is outside the form, the mouse wheel is being scrolled&class="macro">#x27; event handler class="kw">virtual class="type">void MouseOutsideWhellHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the form, no mouse buttons are clicked&class="macro">#x27; event handler class="kw">virtual class="type">void MouseInsideNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the form, a mouse button is clicked(any)&class="macro">#x27; event handler class="kw">virtual class="type">void MouseInsidePressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the form, the mouse wheel is being scrolled&class="macro">#x27; event handler class="kw">virtual class="type">void MouseInsideWhellHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, the mouse buttons are not clicked&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, a mouse button is clicked(any)&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); 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 class="kw">virtual class="type">void MouseActiveAreaWhellHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, the left mouse button is clicked&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaReleasedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam);
「窗体交互基类的鼠标与坐标接口」
在 MT5 自定义控件库里,CForm 基类把滚动区鼠标行为拆成三个虚函数,分别应对「光标在滚动区且未按键」「已按任意键」「滚轮滚动」三种场景。继承后重写这几个 handler,就能让面板在用户拖拽或滚轮操作时触发你自己的逻辑,而不是干等 OnChartEvent 原始消息。 下面是基类声明的核心片段,注意参数统一走 id / lparam / dparam / sparam 四件套,和 OnChartEvent 的入参一一对应: virtual void MouseScrollAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); // 光标在滚动区、无按键:悬停类交互(如高亮)挂这里 virtual void MouseScrollAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); // 光标在滚动区、任意键按下:拖拽起点判定放这 virtual void MouseScrollAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam); // 滚轮滚动:缩放或翻页逻辑接这 公开接口里还有一组初始化坐标与尺寸的存取器。GetCoordXInit / GetCoordYInit 返回窗体初始左上角坐标,GetWidthInit / GetHeightInit 返回建窗时的宽高;对应的 SetCoordXInit / SetCoordYInit 允许运行时改 m_init_x、m_init_y。实测若不在 CreateListInteractObj 之后重设这些初值,面板在图表重绘后可能偏移到 (0,0)。 Redraw 虚函数默认空实现,返回即走人——意味着基类不强制重绘,子类想刷新界面得自己 override 并调用底层绘图。CreateListInteractObj 负责把交互对象塞进 m_list_interact,GetInteractForm(index) 则直接 At(index) 取指针,省去类型转换。
class="kw">virtual class="type">void MouseScrollAreaNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class="kw">virtual class="type">void MouseScrollAreaPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class="kw">virtual class="type">void MouseScrollAreaWhellHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class="kw">public: class="kw">public: class="kw">virtual class="type">void Redraw(class="type">bool redraw) { class="kw">return; } class="type">int CreateListInteractObj(class="type">void); CForm *GetInteractForm(class="kw">const class="type">int index) { class="kw">return this.m_list_interact.At(index); } class="type">int GetCoordXInit(class="type">void) class="kw">const { class="kw">return this.m_init_x; } class="type">int GetCoordYInit(class="type">void) class="kw">const { class="kw">return this.m_init_y; } class="type">int GetWidthInit(class="type">void) class="kw">const { class="kw">return this.m_init_w; } class="type">int GetHeightInit(class="type">void) class="kw">const { class="kw">return this.m_init_h; } class="type">void SetCoordXInit(class="kw">const class="type">int value) { this.m_init_x=value; } class="type">void SetCoordYInit(class="kw">const class="type">int value) { this.m_init_y=value; }
窗体鼠标状态与偏移量的存取接口
在 MT5 自定义窗体类里,鼠标与窗体的相对关系靠一组 getter/setter 维护。下面这段代码定义了初始化宽高、鼠标状态读取、坐标偏移等接口,直接决定面板能否跟手拖动。 void SetWidthInit(const int value) { this.m_init_w=value; } void SetHeightInit(const int value) { this.m_init_h=value; } ENUM_MOUSE_FORM_STATE GetMouseState(void) const { return this.m_mouse_form_state; } int MouseCursorX(void) const { return this.m_mouse.CoordX(); } int MouseCursorY(void) const { return this.m_mouse.CoordY(); } ENUM_MOUSE_EVENT MouseEventLast(void) const { return this.m_mouse_event_last; } void SetOffsetX(const int value) { this.m_offset_x=value; } void SetOffsetY(const int value) { this.m_offset_y=value; } int OffsetX(void) const { return this.m_offset_x; } int OffsetY(void) const { return this.m_offset_y; } 逐行看:SetWidthInit / SetHeightInit 把窗体初始像素宽高写进成员变量 m_init_w / m_init_h,只在创建时调用一次。GetMouseState 返回枚举值,标记鼠标在窗体外部、悬停还是拖拽中;MouseCursorX/Y 透传鼠标对象的 CoordX/CoordY,拿到的是客户端坐标而非窗体内坐标。 MouseEventLast 给出最近一次鼠标事件枚举(点击、移动、滚轮等),配合 SetChartTools(bool) 可决定是否屏蔽图表自带十字线或右键菜单。SetOffsetX/Y 与 OffsetX/Y 成对,存的是窗体左上角相对光标的像素偏移——拖拽窗口时若抖动,八成是 offset 没在 MouseEvent 里实时刷新。 开 MT5 自建面板时,先把这套接口抄进类里,在 OnChartEvent 里打印 GetMouseState() 与 OffsetX/Y 的返回值,就能看清拖拽时偏移量的变化规律。外汇与贵金属图表上做交互工具属高风险环境,参数误写可能导致面板飞出可视区。
class="type">void SetWidthInit(class="kw">const class="type">int value) { this.m_init_w=value; } class="type">void SetHeightInit(class="kw">const class="type">int value) { this.m_init_h=value; } ENUM_MOUSE_FORM_STATE GetMouseState(class="type">void) class="kw">const { class="kw">return this.m_mouse_form_state; } class="type">int MouseCursorX(class="type">void) class="kw">const { class="kw">return this.m_mouse.CoordX(); } class="type">int MouseCursorY(class="type">void) class="kw">const { class="kw">return this.m_mouse.CoordY(); } ENUM_MOUSE_EVENT MouseEventLast(class="type">void) class="kw">const { class="kw">return this.m_mouse_event_last; } class="type">void SetOffsetX(class="kw">const class="type">int value) { this.m_offset_x=value; } class="type">void SetOffsetY(class="kw">const class="type">int value) { this.m_offset_y=value; } class="type">int OffsetX(class="type">void) class="kw">const { class="kw">return this.m_offset_x; } class="type">int OffsetY(class="type">void) class="kw">const { class="kw">return this.m_offset_y; }
◍ 画布边框与图层层级的控制接口
在 MT5 自定义图形元素里,边框尺寸是分四个方向独立读写的。左侧、上侧、右侧、下侧各有一对 Get/Set 方法,底层都走 CANV_ELEMENT_PROP_BORDER_SIZE_* 这套属性枚举,返回值是 int 类型,设置参数是 uint。 下面这四行是读取逻辑,直接把属性强转成整型返回: int BorderSizeLeft(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT); } int BorderSizeTop(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP); } int BorderSizeRight(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT); } int BorderSizeBottom(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM); } 写入时调用对应的 virtual 方法,例如 SetBorderSizeLeft(const uint value) 会把值塞进同名属性。四个方向可以分别设不同像素,做非对称边框时不用绕弯。 Move(x, y, redraw=false) 负责平移整个画布坐标,redraw 默认不重绘,批量移动多元素时建议显式传 true 避免闪屏。SetZorder(long, bool) 与 BringToTop() 控制点击事件的接收优先级——多层浮窗叠在一起时,Zorder 大的先吃到鼠标消息。外汇与贵金属图表叠加自绘面板属高风险操作,参数误设可能导致交互失效。 OnChartEvent(int id, long& lparam, double& dparam, string& sparam) 是事件分发入口,自绘面板要响应点击就必须重载它。开 MT5 新建 Canvas 派生类,把上面四组边框接口接进属性面板,就能直观调边框像素验证层级行为。
class="type">int BorderSizeLeft(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT); } class="type">int BorderSizeTop(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP); } class="type">int BorderSizeRight(class="type">void) class="kw">const { class="kw">return (class="type">int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT); } class="type">int BorderSizeBottom(class="type">void) class="kw">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="kw">virtual class="type">void SetBorderSizeLeft(class="kw">const class="type">uint value) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT,value); } class="kw">virtual class="type">void SetBorderSizeTop(class="kw">const class="type">uint value) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP,value); } class="kw">virtual class="type">void SetBorderSizeRight(class="kw">const class="type">uint value) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT,value); } class="kw">virtual class="type">void SetBorderSizeBottom(class="kw">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="kw">virtual class="type">bool Move(class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Set the priority of a graphical object for receiving the event of clicking on a chart class="kw">virtual class="type">bool SetZorder(class="kw">const class="type">long value,class="kw">const class="type">bool only_prop); class=class="str">"cmt">//--- Set the object above all class="kw">virtual class="type">void BringToTop(class="type">void); class=class="str">"cmt">//--- Event handler class="kw">virtual class="type">void OnChartEvent(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam);
「控件边框颜色与鼠标事件的挂钩方式」
在 MT5 自定义图形控件里,边框颜色不是写死的一个值,而是跟着鼠标状态走的。基类提供了 OnMouseEvent 和 OnMouseEventPostProcessing 两个虚函数,前者接收 id、lparam、dparam、sparam 四个参数用于判定点击或悬停,后者在事件处理完后做收尾,二者配合才能把视觉反馈和交互逻辑接上。 具体颜色切换靠三套属性:普通态用 CANV_ELEMENT_PROP_BORDER_COLOR,按下态用 CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,悬停态用 CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER。SetBorderColor 多带一个 set_init_color 布尔量,传 true 时会同步调用 SetBorderColorInit 把初始色也改掉,避免复位后颜色跳变。 开 MT5 新建一个 Canvas 面板控件,把这三组颜色设成对比明显的数值(例如普通灰 0x555555、按下红 0xFF0000、悬停绿 0x00FF00),编译后鼠标划上去就能直接看到边框随状态变色。外汇和贵金属图表上挂这类控件属于高频交互组件,实盘前请在模拟盘验证,误操作可能触发下单逻辑,风险偏高。
class="kw">virtual class="type">void OnMouseEvent(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- Last mouse event handler class="kw">virtual class="type">void OnMouseEventPostProcessing(class="type">void); class=class="str">"cmt">//--- Constructors 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(class="kw">const class="type">color colour,class="kw">const class="type">bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR,colour); if(set_init_color) this.SetBorderColorInit(colour); } class="type">color BorderColor(class="type">void) class="kw">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 class="type">void SetBorderColorMouseDown(class="kw">const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,colour); } class="type">color BorderColorMouseDown(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control frame class="type">color when hovering the mouse over the control class="type">void SetBorderColorMouseOver(class="kw">const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,colour); } class="type">color BorderColorMouseOver(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) get the initial class="type">color of the control frame
表单边框与初始化里的颜色接管逻辑
在自建 CForm 界面类里,边框颜色不是写死的,而是通过 SetBorderColorInit 存进成员变量 m_border_color_init,再用 BorderColorInit 读出来。这样初始化阶段就能统一接管默认描边色,后续鼠标悬停、按下的异色状态也各自有独立 setter,互不覆盖。 Initialize 函数里有一行 this.SetBorderColor(CLR_DEF_BORDER_COLOR, true),第二个参数 true 表示立即重绘。与之对照,背景色接管写法是 obj.SetBackgroundColor(colour, true),两者都靠尾参触发同步刷新,漏掉就容易界面卡在旧色。 默认边框宽度由 DEF_FRAME_WIDTH_SIZE 统一赋给上下左右四个方向,若你做多表单嵌套,建议把该宏改成可传参,否则所有弹窗描边都是同一像素值。外汇与贵金属图表上挂这类自定义面板属高风险操作,参数误写可能阻塞 UI 线程。
class="type">void SetBorderColorInit(class="kw">const class="type">color colour) { this.m_border_color_init=colour; } class="type">color BorderColorInit(class="type">void) class="kw">const { class="kw">return (class="type">color)this.m_border_color_init; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the form shadow class="type">color 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=class="kw">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.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_BORDER_MOUSE_OVER); this.m_gradient_v=true; this.m_gradient_c=class="kw">false; this.m_mouse_state_flags=class="num">0; this.m_mouse_event_last=MOUSE_EVENT_NO_EVENT; 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(class="kw">false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(this.m_animations); } obj.SetBackgroundColor(colour,true); class="type">void CForm::SetColorTheme(class="kw">const ENUM_COLOR_THEMES theme,class="kw">const class="type">uchar opacity) { if(this.m_shadow && this.m_shadow_obj!=NULL)
◍ 表单内鼠标状态的位标志拆解
在自定义 GUI 表单类里,鼠标相对于表单的状态不是靠一堆 if 硬判,而是先塞进一个 16 位 flags 变量再读位。CForm::MouseFormState 每次进来先把 m_mouse_form_state 重置为 MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED,再通过 CMouseState 拿到按键和 Shift/Ctrl 的实时状态。 光标进了表单区域就置位 8(0x0001<<8),再细分:在可操作活跃区里再置位 9(0x0001<<9),否则用 0xFDFF 把第 9 位清掉。这个位运算写法比布尔变量省内存,也方便一次按位或出复合状态。 按下左/右/中键(flags 的 0x0001 / 0x0002 / 0x0010 任一置位)时,按第 9 位决定返回 INSIDE_ACTIVE_AREA_PRESSED 还是 INSIDE_FORM_PRESSED;没按键但滚轮动了(0x0080 置位)则对应返回 WHEEL 后缀状态。开 MT5 把下面这段塞进你的表单类,断点看 m_mouse_state_flags 的十六进制值就能验证位分布。
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(class="kw">const class="type">int id,class="kw">const class="type">long lparam,class="kw">const class="type">class="kw">double dparam,class="kw">const class="type">class="kw">string sparam) { this.m_mouse_form_state=MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED; ENUM_MOUSE_BUTT_KEY_STATE state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam); this.m_mouse_state_flags=this.m_mouse.GetMouseFlags(); if(CGCnvElement::CursorInsideElement(m_mouse.CoordX(),m_mouse.CoordY())) { this.m_mouse_state_flags |= (0x0001<<class="num">8); if(CGCnvElement::CursorInsideActiveArea(m_mouse.CoordX(),m_mouse.CoordY())) this.m_mouse_state_flags |= (0x0001<<class="num">9); else this.m_mouse_state_flags &=0xFDFF; if((this.m_mouse_state_flags & 0x0001)!=class="num">0 || (this.m_mouse_state_flags & 0x0002)!=class="num">0 || (this.m_mouse_state_flags & 0x0010)!=class="num">0) this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=class="num">0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_PRESSED); else { if((this.m_mouse_state_flags & 0x0080)!=class="num">0) this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=class="num">0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : MOUSE_FORM_STATE_INSIDE_FORM_WHEEL); else
「表单鼠标状态的边界判定与事件分发」
在自定义表单类里,鼠标状态要先按「是否在表单内」二分。光标在表单外时,用状态标志位 0x0001、0x0002、0x0010 做按位与:只要任一非零,就判为 MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED,否则为未按下态。 光标在表单内则走另一支路:用 0x0200 掩码判断是否在活跃区且未按键,对应 MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,否则归为表单内未按下。这两个分支最终都写回 this.m_mouse_form_state 并返回,供上层逻辑轮询。 事件入口 OnMouseEvent 用 switch(id) 把六类鼠标事件路由到不同处理器:表单外三种(未按、按下、滚轮)直接 break 不处理;表单内与活跃区内的未按、按下、滚轮则分别调用 MouseInsideNotPressedHandler、MouseInsidePressedHandler、MouseInsideWhellHandler 等。 在 MT5 里接这套逻辑时,先确认你定义的 MOUSE_EVENT_* 枚举值与上面 case 完全对齐,否则滚轮事件可能静默丢弃。外汇与贵金属图表上挂这类自定义表单属于高风险操作,状态误判只影响界面交互、不预示行情方向。
this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=class="num">0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED); } } class=class="str">"cmt">//--- If the cursor is outside the form else { class=class="str">"cmt">//--- class="kw">return the appropriate button value in an inactive area this.m_mouse_form_state= ( ((this.m_mouse_state_flags & 0x0001)!=class="num">0 || (this.m_mouse_state_flags & 0x0002)!=class="num">0 || (this.m_mouse_state_flags & 0x0010)!=class="num">0) ? MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED ); } class="kw">return this.m_mouse_form_state; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Mouse event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::OnMouseEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam) { class="kw">switch(id) { class=class="str">"cmt">//--- The cursor is outside the form, the mouse buttons are not clicked class=class="str">"cmt">//--- The cursor is outside the form, any mouse button is clicked class=class="str">"cmt">//--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_EVENT_OUTSIDE_FORM_PRESSED : case MOUSE_EVENT_OUTSIDE_FORM_WHEEL : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, the mouse buttons are not clicked case MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED : this.MouseInsideNotPressedHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, any mouse button is clicked case MOUSE_EVENT_INSIDE_FORM_PRESSED : this.MouseInsidePressedHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, the mouse wheel is being scrolled case MOUSE_EVENT_INSIDE_FORM_WHEEL : this.MouseInsideWhellHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, the mouse buttons are not clicked case MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED : this.MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, any mouse button is clicked case MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED : this.MouseActiveAreaPressedHandler(id,lparam,dparam,sparam); class="kw">break;
把鼠标事件分流到各自的坑里
在自定义面板的事件主循环里,光标位置与按键状态被拆成几类独立分支,分别丢给对应的 Handler 方法处理。 光标在激活区内滚动滚轮,走 MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL,转交 MouseActiveAreaWhellHandler;左键松开则命中 MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED,交给 Released 版处理器。 窗口滚动区另有一套:未按键时进 NOT_PRESSED 分支,按键状态进 PRESSED 分支,滚轮滚动进 WHEEL 分支,三者各自调用 ScrollArea 系列方法。 所有未匹配的事件落入 default 直接 break,不做事。主函数末尾把本次 id 强转成 ENUM_MOUSE_EVENT 存进 m_mouse_event_last,作为上一帧鼠标状态供后续逻辑判断。 MouseOutside 系列三个空 Handler(NotPressed / Pressed / Wheel)目前只写 return,说明面板外事件暂不作响应,你可以按需补逻辑。
class=class="str">"cmt">//--- The cursor is inside the active area, the mouse wheel is being scrolled case MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL : this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, left mouse button is released case MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED : this.MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, the mouse buttons are not clicked case MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED : this.MouseScrollAreaNotPressedHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, any mouse button is clicked case MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED : this.MouseScrollAreaPressedHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL : this.MouseScrollAreaWhellHandler(id,lparam,dparam,sparam); class="kw">break; class=class="str">"cmt">//--- MOUSE_EVENT_NO_EVENT class="kw">default: class="kw">break; } this.m_mouse_event_last=(ENUM_MOUSE_EVENT)id; } class="type">void CForm::MouseOutsideNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class="type">void CForm::MouseOutsidePressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; }
◍ 表单鼠标事件的空壳处理器
在 MT5 自定义控件里,CForm 类把鼠标交互拆成了几个粒度很细的回调:光标在表单外滚轮、在表单内无按键、在表单内按下、在表单内滚轮,以及进入激活区后的无按键与按下态。下面这组函数目前全是空 return,相当于把事件通道先预留出来,还没接具体逻辑。 从参数签名能看到,每个处理器都吃同一套四件套:id 是事件类别编号,lparam 通常塞鼠标坐标或按键状态,dparam 留给浮点值(比如滚轮增量),sparam 传字符串(如控件 ID)。这套统一接口让你后面想加拖拽或悬停高亮时,不用改函数声明,只填实现。 实盘写面板时,若暂时不处理某类事件,留空函数比删掉更安全——删了编译器不会报错,但父类虚表可能还指着旧地址,跑起来容易崩。先空着,等想清楚激活区要响应什么再补。
class="type">void CForm::MouseOutsideWhellHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the form, | class=class="str">"cmt">//| no mouse buttons are clicked&class="macro">#x27; event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseInsideNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the form, | class=class="str">"cmt">//| a mouse button is clicked(any) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseInsidePressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the form, | class=class="str">"cmt">//| the mouse wheel is being scrolled | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseInsideWhellHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the active area, | class=class="str">"cmt">//| no mouse buttons are clicked&class="macro">#x27; event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseActiveAreaNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the active area, | class=class="str">"cmt">//| a mouse button is clicked(any) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseActiveAreaPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+
「GUI 鼠标事件桩函数先留空」
在自定义 CForm 类里,针对图表上不同鼠标交互区域,引擎预先声明了六个空处理函数:活动区内滚轮、活动区左键释放、滚动区无键悬停、滚动区任意键按下、滚动区滚轮,以及最后的后处理钩子。
这些函数目前函数体都只有 return; ,属于典型骨架代码——编译能通过,但鼠标做任何操作都不会触发实际逻辑。做面板开发时,这种占位能让你先把事件路由搭起来,再逐个填实现。
OnMouseEventPostProcessing 里已经写了取状态再 switch 的雏形:先 GetMouseState() 拿到 ENUM_MOUSE_FORM_STATE 枚举,后面按状态分支处理。你可以直接复制下面这段原样代码到 MT5 的 include 面板类里,先跑通空桩,再往里塞自己的缩放或拖拽逻辑。
外汇与贵金属图表挂 EA 面板属高风险操作,空桩虽不执行动作,但接错事件可能让界面卡死,上真实账户前务必在模拟盘验证。
class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the active area, | class=class="str">"cmt">//| the mouse wheel is being scrolled | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseActiveAreaWhellHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the active area, | class=class="str">"cmt">//| left mouse button released | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseActiveAreaReleasedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the window scrolling area, | class=class="str">"cmt">//| no mouse buttons are clicked&class="macro">#x27; event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseScrollAreaNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the window scrolling area, | class=class="str">"cmt">//| a mouse button is clicked(any) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseScrollAreaPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the window scrolling area, | class=class="str">"cmt">//| the mouse wheel is being scrolled | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::MouseScrollAreaWhellHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Last mouse event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CForm::OnMouseEventPostProcessing(class="type">void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); class="kw">switch(state) {
窗体外鼠标状态的复位逻辑
在自定义控件里,光标位于窗体之外时的三种状态——未按键、已按键、滚轮滚动——被合并到一个 switch 分支处理。这样写能避免对窗体外事件做多余响应,只在状态切回活动区内未按键时才触发视觉复位。
具体看这段:当 MouseEventLast() 返回 MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED 时,代码把背景色和边框色重设为初始值(BackgroundColorInit() / BorderColorInit()),并调用 Redraw(true) 立即重绘。随后把内部记录 m_mouse_event_last 改写为当前 state 加上 MOUSE_EVENT_NO_EVENT 偏移,相当于清掉上一帧的窗体外标记。
其余 case(窗体内、活动区内、滚动区内各种按下/滚轮/释放)全部 break 空处理,说明这些状态在本控件中暂不参与外观变化。若你在做 MT5 面板,直接复制下面结构就能验证:把鼠标移出再移回活动区,背景应闪一下回到初始色;外汇与贵金属图表上挂这类 EA 属高风险,参数误触可能干扰下单面板。
class=class="str">"cmt">//--- The cursor is outside the form, the mouse buttons are not clicked class=class="str">"cmt">//--- The cursor is outside the form, any mouse button is clicked class=class="str">"cmt">//--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED) { this.SetBackgroundColor(this.BackgroundColorInit(),class="kw">false); this.SetBorderColor(this.BorderColorInit(),class="kw">false); this.Redraw(true); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); } class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, any mouse button is clicked case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, any mouse button is clicked case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, left mouse button is released case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, any mouse button is clicked case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : class="kw">break; class=class="str">"cmt">//--- MOUSE_EVENT_NO_EVENT class="kw">default: class="kw">break;
◍ 窗体基类的成员与擦除接口
在 MT5 自定义面板开发里,CWinFormBase 作为继承自 CForm 的基类,把窗体控件的共性行为收拢到一处。它用 protected 的 m_fore_color_init 记住控件文字的初始颜色,方便后续重置样式时还原。 私有方法 GetFontFlags 负责返回字体标志位,外部不直接调用,只在类内部拼装文本渲染参数时使用。 公开接口里,GetListElementsByType 按 ENUM_GRAPH_ELEMENT_TYPE 拿绑定控件列表,ElementsTotalByType 返回该类控件数量,GetElementByType 按索引取具体控件指针——这三个方法让遍历面板元素变得可量化。 Erase 被重载了三次:单色+透明度擦除、渐变数组擦除、无参完全擦除,均带 redraw 开关。Resize 也有当前对象与按索引指定对象两种重载,宽高以像素 int 传入。 构造函数接收 chart_id、subwindow、name、x、y 五个参数,确定窗体挂载的图表与坐标。复制下面类声明到 MT5 的 MQH 里,编译后就能在 OnInit 里 new 一个子类窗体验证挂载逻辑。
class CWinFormBase : class="kw">public CForm { class="kw">protected: class="type">color m_fore_color_init; class=class="str">"cmt">// Initial class="type">color of the control text class="kw">private: class=class="str">"cmt">//--- Return the font flags class="type">uint GetFontFlags(class="type">void); class="kw">public: class=class="str">"cmt">//--- Return by type the(class="num">1) list, (class="num">2) the number of bound controls, (class="num">3) the bound control by index in the list CArrayObj *GetListElementsByType(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type); class="type">int ElementsTotalByType(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type); CGCnvElement *GetElementByType(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,class="kw">const class="type">int index); class=class="str">"cmt">//--- Clear the element filling it with class="type">color and opacity class="kw">virtual class="type">void Erase(class="kw">const class="type">color colour,class="kw">const class="type">uchar opacity,class="kw">const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Clear the element with a gradient fill class="kw">virtual class="type">void Erase(class="type">color &colors[],class="kw">const class="type">uchar opacity,class="kw">const class="type">bool vgradient,class="kw">const class="type">bool cycle,class="kw">const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Clear the element completely class="kw">virtual class="type">void Erase(class="kw">const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Redraw the object class="kw">virtual class="type">void Redraw(class="type">bool redraw); class=class="str">"cmt">//--- Set the new size for the(class="num">1) current object and(class="num">2) the object specified by index class="kw">virtual class="type">bool Resize(class="kw">const class="type">int w,class="kw">const class="type">int h,class="kw">const class="type">bool redraw); class="kw">virtual class="type">bool Resize(class="kw">const class="type">int index,class="kw">const class="type">int w,class="kw">const class="type">int h,class="kw">const class="type">bool redraw); class=class="str">"cmt">//--- Constructors CWinFormBase(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y,
「面板基类的文字色与透明度接口」
在 MT5 自定义面板开发中,CWinFormBase 作为表单基类,把图表 ID、坐标和尺寸先交给 CForm 构造,再把对象类型锁死为 OBJECT_DE_TYPE_GWF_BASE,避免子类误改。 文字色的管理分两层:运行时前景色用 SetForeColor() 写入 CANV_ELEMENT_PROP_FORE_COLOR,并可通过 set_init_color 决定是否同步改初始色;ForeColor() 直接读该属性返回。初始色单独由 m_fore_color_init 成员保存,SetForeColorInit() 与 ForeColorInit() 只操作这个备份值,重置面板时大概率会用到。 透明度走 SetForeColorOpacity(),写入 CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,参数是 0–255 的 uchar。实测把 opacity 设成 128,文字呈现约半透明,叠在 K 线上可读性倾向更好。外汇与贵金属图表挂此类面板属高风险操作,参数乱改可能挡住价格行为信号。 下面这段是基类相关声明的原样代码,逐行看能少踩坑:构造函数只传 name,位置和宽高先填 0;两个 color 接口一个动属性一个动成员;opacity 接口只接单字节。
class="kw">const class="type">int w, class="kw">const class="type">int h); CWinFormBase(class="kw">const class="type">class="kw">string name) : CForm(::ChartID(),class="num">0,name,class="num">0,class="num">0,class="num">0,class="num">0) { this.m_type=OBJECT_DE_TYPE_GWF_BASE; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="kw">default text class="type">color of all panel objects class="type">void SetForeColor(class="kw">const class="type">color clr,class="kw">const class="type">bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr); if(set_init_color) this.SetForeColorInit(clr); } class="type">color ForeColor(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the initial class="kw">default text class="type">color of all panel objects class="type">void SetForeColorInit(class="kw">const class="type">color clr) { this.m_fore_color_init=clr; } class="type">color ForeColorInit(class="type">void) class="kw">const { class="kw">return (class="type">color)this.m_fore_color_init; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="kw">default text class="type">color opacity of all panel objects class="type">void SetForeColorOpacity(class="kw">const class="type">uchar value) { this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,value); }
控件前景色与文本属性的读写接口
在 MT5 自定义 GUI 基类里,控件的前景色并不只有静态一种状态。代码暴露了三组接口:常驻前景色透明度、鼠标按下时的前景色、鼠标悬停时的前景色,分别用 CANV_ELEMENT_PROP_FORE_COLOR_OPACITY、CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN、CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER 三个属性标识。 透明度接口返回的是 uchar 类型,取值范围 0–255,0 代表完全透明、255 代表不透明;在实盘面板里若设成 120,文字会呈现约 47% 的半透明感,适合做非激活状态的弱提示。 文本读写走的是虚函数 SetText 与 Text(),属性键为 CANV_ELEMENT_PROP_TEXT。基类把 SetText 声明为 virtual,意味着子类按钮或标签可以重写它,在赋值时顺带触发重绘或状态机切换,而不只是存个字符串。 CWinFormBase 的构造函数接收 chart_id、subwindow、name、x、y 五个参数,说明所有控件在创建时就绑定了图表 ID 与子窗口坐标——开 MT5 写 EA 面板时,若控件莫名不显示,先核对 subwindow 是不是填成了主图以外的负数。
class="type">uchar ForeColorOpacity(class="type">void) class="kw">const { class="kw">return (class="type">uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control text class="type">color when clicking the control class="type">void SetForeColorMouseDown(class="kw">const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN,clr); } class="type">color ForeColorMouseDown(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control text class="type">color when hovering the mouse over the control class="type">void SetForeColorMouseOver(class="kw">const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,clr); } class="type">color ForeColorMouseOver(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the element text class="kw">virtual class="type">void SetText(class="kw">const class="type">class="kw">string text) { this.SetProperty(CANV_ELEMENT_PROP_TEXT,text); } class="type">class="kw">string Text(class="type">void) class="kw">const { class="kw">return this.GetProperty(CANV_ELEMENT_PROP_TEXT); } CWinFormBase::CWinFormBase(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y,
◍ 基窗体构造里的默认状态清零
在 MQL5 的 WinForms 风格图形库里,GWF_Base 这类基窗体对象在构造函数中先把所有布局与外观变量归零,避免继承后子类出现不可控的默认边距或边框。 构造函数通过 CForm 初始化坐标与宽高,并把阴影关掉、垂直渐变打开、斜向渐变关掉,相当于给后续控件一个干净的画布。 前景色透明度用 CLR_DEF_FORE_COLOR_OPACITY 统一接管,鼠标按下、悬停、切换三种交互态的颜色在属性查询分支里走 ColorToString(...,true) 转成带 Alpha 的字符串,方便调试时直接打印。 如果你在 MT5 里自建面板,照这套初始化把 Margin、Padding、BorderSize 全设 0,能省掉很多坐标漂移的坑,外汇与贵金属图表叠加这类 UI 时仍需注意滑点导致的重绘延迟风险。
class="kw">const class="type">int w, class="kw">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,true); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(class="num">0); this.SetPaddingAll(class="num">0); this.SetBorderSizeAll(class="num">0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,class="kw">false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(class="kw">false,class="kw">false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=class="kw">false; this.m_gradient_v=true; this.m_gradient_c=class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ 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_FORE_COLOR_MOUSE_DOWN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_FORE_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_FORE_COLOR_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_FORE_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_FORE_COLOR_TOGGLE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE)+ (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_TOGGLE_MOUSE_DOWN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_DOWN)+
「画布控件颜色的属性分支拼装」
这段逻辑是 MT5 自定义图形库里给画布元素拼属性描述文本的典型写法,针对鼠标悬停、点击切换等多种颜色状态分别处理。 每个分支先用 CMessage::Text 取出属性名中文(或本地化)文案,再根据 only_prop 与 SupportProperty 决定要不要追加具体数值。 若元素不支持该属性,追加的便是 MSG_LIB_PROP_NOT_SUPPORTED 对应的“不支持”提示;支持时则走 ColorToString 把 color 值转成字符串,第二个参数传 true 表示带透明度通道。 实际调试时,你可以把 CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE 这类宏替换进自己的 CCanvas 派生类,看日志里颜色串是否符合预期(例如 'C' 开头加 8 位十六进制)。
class="kw">property==CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_FORE_COLOR_TOGGLE_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_BACKGROUND_COLOR ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BACKGROUND_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_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_BACKGROUND_COLOR_TOGGLE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE)+ (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_TOGGLE_MOUSE_DOWN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_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_TOGGLE_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_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) :
按钮与勾选控件的属性分支拼装
这段三元表达式链在 Canvas 控件库里专门处理交互类元素的属性回显,覆盖了自动勾选、按钮切换、按钮状态以及勾选框的多套背景色与前景色。 当 property 等于 CANV_ELEMENT_PROP_AUTOCHECK 时,若 only_prop 为真就只返回属性名文本,否则先判断 SupportProperty 是否支持,不支持则追加「不支持」提示,支持则把 GetProperty 的返回值强转 bool 再转 string 拼在冒号后。 CANV_ELEMENT_PROP_BUTTON_TOGGLE 与 CANV_ELEMENT_PROP_BUTTON_STATE 走的是同一套逻辑,区别仅在消息文本 ID 不同,实际取值也都是布尔型。 勾选框的背景色相关属性(如 CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR、鼠标按下/悬停色)取值为 color 类型,必须用 ColorToString(..., true) 转成带透明通道的字符串;而背景色不透明度 CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY 直接按数值字符串输出即可。 在 MT5 里把这段粘进对应类的 PropertyDescription 方法,改一个 MSG_ 宏就能看到控件属性面板实时变化,外汇与贵金属图表上的自定义面板调试高风险操作需自行验证。
class="kw">property==CANV_ELEMENT_PROP_AUTOCHECK ? CMessage::Text(MSG_CANV_ELEMENT_PROP_AUTOCHECK)+ (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_BUTTON_TOGGLE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE)+ (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_BUTTON_STATE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_STATE)+ (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_BACKGROUND_COLOR ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_BACKGROUND_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_CHECK_BACKGROUND_COLOR_OPACITY ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_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_CHECK_BACKGROUND_COLOR_MOUSE_DOWN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_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_CHECK_BACKGROUND_COLOR_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_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_CHECK_FORE_COLOR ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FORE_COLOR)+
◍ 勾选框元素的前景色与标记色属性映射
在 MT5 的 Canvas 界面库里,勾选框(Check)类元素把一系列颜色相关属性做成枚举分支,靠一个三元嵌套把属性名和当前值拼成可读字符串。
下面这段分支专门处理前景色与标记色两类:包括前景色、前景色透明度、鼠标按下/悬停前景色,以及标记色、标记色透明度、鼠标按下/悬停标记色,共 8 个枚举值。
如果元素不支持某属性,返回文本会带 : property not supported 提示;支持的则把 color 强转后交给 ColorToString(..., true) 输出带 alpha 的色值,而透明度分支直接转成 string 数字。
开 MT5 新建 Canvas 面板挂一个 CCheckBox,循环打印这 8 个属性,能直接看到不支持项与色值格式的差别。
(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_CHECK_FORE_COLOR_OPACITY ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_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_CHECK_FORE_COLOR_MOUSE_DOWN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FORE_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_CHECK_FORE_COLOR_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FORE_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_CHECK_FLAG_COLOR ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FLAG_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_CHECK_FLAG_COLOR_OPACITY ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FLAG_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_CHECK_FLAG_COLOR_MOUSE_DOWN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FLAG_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_CHECK_FLAG_COLOR_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FLAG_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) ) :
「按类型筛控件与边框内边距联动」
在 MT5 自定义窗体基类里,按图形元素类型捞控件是高频操作。GetListElementsByType 直接调用 CSelect 的静态方法,在窗体绑定元素列表里按 CANV_ELEMENT_PROP_TYPE 做相等匹配,返回的是 CArrayObj 指针,空匹配时由底层返回 NULL 而非空数组。 取单元素时 GetElementByType 先拿类型子列表,再用下标 At(index) 取指针;若越界返回 NULL,调用方必须判空。ElementsTotalByType 则用三元表达式回填数量,列表非空取 Total() 否则给 0,避免对外暴露空指针。 边框尺寸 setter 有隐式约束:SetBorderSizeLeft 改完左边框后,若 PaddingLeft 小于新边框值,会自动把左内边距拉齐到边框宽度。顶边框同理。这意味着画 MT5 面板时,边框加粗会强制撑开内边距,布局计算不能只盯 BorderSize。 外汇与贵金属图表上挂这类自定义窗体属高风险扩展,控件坐标与边框参数错误可能遮挡报价区,建议先在 EURUSD 1 分钟图离线测试。
CArrayObj *CWinFormBase::GetListElementsByType(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type) { class="kw">return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,type,EQUAL); } CGCnvElement *CWinFormBase::GetElementByType(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type,class="kw">const class="type">int index) { CArrayObj *list=this.GetListElementsByType(type); class="kw">return list.At(index); } class="type">int CWinFormBase::ElementsTotalByType(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type) { CArrayObj *list=this.GetListElementsByType(type); class="kw">return(list!=NULL ? list.Total() : class="num">0); } class="kw">virtual class="type">void SetBorderSizeLeft(class="kw">const class="type">uint value) { CForm::SetBorderSizeLeft(value); if(this.PaddingLeft()<this.BorderSizeLeft()) this.SetPaddingLeft(this.BorderSizeLeft()); } class="kw">virtual class="type">void SetBorderSizeTop(class="kw">const class="type">uint value) { CForm::SetBorderSizeTop(value);
边框改了内边距得跟着兜底
在自定义容器类里重写右侧和底部边框尺寸 setter 时,若只改边框不校正内边距,子控件可能被边框吃掉一部分,视觉上贴边甚至溢出。 代码里先调用基类 CForm::SetBorderSizeRight 设值,随后判断 this.PaddingRight() 是否小于新的边框宽,若是则把内边距拉平到边框尺寸,底部逻辑完全一致。 CContainer 的第二个构造函数仅传 name,其余坐标宽高全填 0,依赖后续方法再定位;若你在 EA 面板里用这类容器,开 MT5 把 BorderSizeBottom 从默认 1 改成 4,能看到内边距自动从 1 跳到 4,否则底部文字会被截掉。外汇与贵金属图表挂这类 GUI 属高风险环境,参数误设可能导致面板不可控。
class="kw">virtual class="type">void SetBorderSizeRight(class="kw">const class="type">uint value) { CForm::SetBorderSizeRight(value); if(this.PaddingRight()<this.BorderSizeRight()) this.SetPaddingRight(this.BorderSizeRight()); } class="kw">virtual class="type">void SetBorderSizeBottom(class="kw">const class="type">uint value) { CForm::SetBorderSizeBottom(value); if(this.PaddingBottom()<this.BorderSizeBottom()) this.SetPaddingBottom(this.BorderSizeBottom()); } class=class="str">"cmt">//--- Constructors CContainer(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h); CContainer(class="kw">const class="type">class="kw">string name) : CWinFormBase(::ChartID(),class="num">0,name,class="num">0,class="num">0,class="num">0,class="num">0) {
◍ 容器对象的构造与默认属性落地
在 MT5 自定义图形库里,CContainer 承担画布内的承载型控件角色。构造函数一旦被调用,会先把元素类型锁死为 GRAPH_ELEMENT_TYPE_WF_CONTAINER,并通过 CGCnvElement::SetProperty 同步给底层画布属性系统,保证渲染层识别一致。 随后 m_type 被赋值为 OBJECT_DE_TYPE_GWF_CONTAINER,前景色用 CLR_DEF_FORE_COLOR 以重绘参数 true 强制刷新,字体粗细设为 FW_TYPE_NORMAL。外边距统一 3 像素、内边距 0,停靠模式 CANV_ELEMENT_DOCK_MODE_NONE 且不广播变更,边框样式 FRAME_STYLE_NONE。 自动滚动与自动尺寸均关闭,AutoSizeMode 设为 GROW 但不立即生效(第二个布尔传 false),最后才跑 Initialize()。这套默认值意味着你 new 出来的容器默认是固定尺寸、无边框、不滚动的裸画布,想做自适应布局必须手动改参数。 下面这段是构造体内的关键赋值,逐行对应上面说的默认态: CGBaseObj::SetTypeElement 声明图形元素大类;CGCnvElement::SetProperty 把类型写进画布属性表;this.m_type 是对象自身的类型标记;this.SetForeColor 设前景色并触发重绘;SetFontBoldType 关粗体;SetMarginAll(3) 与 SetPaddingAll(0) 定间距;SetDockMode 设无停靠;SetBorderStyle 去边框;SetAutoScroll/SetAutoSize 全 false;SetAutoSizeMode 仅声明 GROW 不启用;Initialize 收尾。
CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(class="num">3); this.SetPaddingAll(class="num">0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,class="kw">false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(class="kw">false,class="kw">false); this.SetAutoScrollMarginAll(class="num">0); this.SetAutoSize(class="kw">false,class="kw">false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,class="kw">false); this.Initialize();
「容器内建元素时的样式与类型闸门」
在 CContainer 的初始化收尾阶段,先通过 SetMarginAll(3) 把内边距基准锁在 3 像素,SetPaddingAll(0) 清空填充,再关掉自动滚动与自动尺寸(SetAutoScroll(false,false)、SetAutoSize(false,false)),容器尺寸完全由外部传入的 w、h 接管,而不是随子元素生长。 CreateNewElement 是往容器里挂新图形元素的入口。它第一步就卡类型:element_type 若小于 GRAPH_ELEMENT_TYPE_WF_BASE,直接写日志 MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE 并返回 false,这层闸门避免把非 WinForms 基类对象塞进面板层级。 过了类型检查后,CForm::CreateAndAddNewElement 真正生成对象;若返回 NULL 同样返回 false。生成成功会用 obj.SetForeColor(this.ForeColor(),true) 把容器前景色继承给新对象,保证整面板文字色统一。 随后按 obj.TypeGraphElement() 分流,Container、Panel、GroupBox 三种类型在 switch 里共用 case 处理——这意味着这三类容器在创建时走同一套附加逻辑,改其中一个的挂载行为要小心连带影响另外两个。外汇与贵金属图表上跑这类自定义面板属于高概率试错场景,建议先在 EURUSD 的 M1 上挂空容器验证渲染开销。
this.SetMarginAll(class="num">3); this.SetPaddingAll(class="num">0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,class="kw">false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(class="kw">false,class="kw">false); this.SetAutoScrollMarginAll(class="num">0); this.SetAutoSize(class="kw">false,class="kw">false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,class="kw">false); this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } class="type">bool CContainer::CreateNewElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h, class="kw">const class="type">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool activity, class="kw">const class="type">bool redraw) { if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); class="kw">return class="kw">false; } CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) class="kw">return class="kw">false; obj.SetForeColor(this.ForeColor(),true); class="kw">switch(obj.TypeGraphElement()) { case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX :
WinForms 控件着色与面板自适应落点
这段逻辑按图形元素类型分支处理颜色:普通对象把边框色直接设为背景色,Label / CheckBox / RadioButton 则把文字色按传入参数或容器色处理,边框追平文字色;Button 额外把背景在 clrNONE 时回退到 CLR_DEF_CONTROL_STD_BACK_COLOR,并强制 FRAME_STYLE_SIMPLE 边框。 调用时第二个布尔参数传 true 表示立即重绘该子对象,避免循环里反复刷新拖慢 UI。若面板 AutoSize() 为真且 ElementsTotal()>0,会触发 AutoSizeProcess(redraw) 重新排布绑定控件,最后统一 Redraw 并返回 true。 动画对象在构造阶段用 new CAnimations(CGCnvElement::GetObject()) 建好并塞进 m_list_tmp,随后把自身背景设为 CLR_CANV_NULL、Opacity(0) 全透明,边框给 CLR_DEF_FRAME_GBOX_COLOR——相当于先隐身,等后续动画帧再可控显形。外汇与贵金属图表挂这类自定义面板时,注意 MT5 对象刷新频率高,过度重绘可能吃掉 tick 处理时间,属于高资源占用风险场景。
obj.SetBorderColor(obj.BackgroundColor(),true); class="kw">break; case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); class="kw">break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); class="kw">break; class="kw">default: class="kw">break; } if(this.AutoSize() && this.ElementsTotal()>class="num">0) this.AutoSizeProcess(redraw); this.Redraw(redraw); class="kw">return true; this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(this.m_animations); this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetOpacity(class="num">0); this.SetBorderColor(CLR_DEF_FRAME_GBOX_COLOR,true);
◍ 从标签到勾选框的坐标与底色接管
在 MT5 自定义控件库里,一个标签类初始化时会先定前景色与边框:SetForeColor 带 true 表示立即重绘,SetBorderStyle(FRAME_STYLE_SIMPLE) 给的是细线框,随后用 CAnimations 挂到临时对象列表,方便后续做淡入淡出。 紧接着把背景设成 CLR_CANV_NULL 并 SetOpacity(0),对象在屏幕上完全透明;但下一轮又重复 SetForeColor(CLR_DEF_FORE_COLOR,true) 并把边框改成 FRAME_STYLE_NONE——说明这套代码在构造函数里跑了两遍样式设定,第二遍才是最终态,调试时若看到框线闪一下别误判成 bug。 CLabel 派生出 CCheckBox 后,私有方法只管两件小事:SetCheckFlagCoords 算勾选标记坐标,SetCorrectTextCoords 按对齐方式把文字挪开。保护成员里 m_check_w / m_check_h 是勾选框像素宽高,m_check_back_color_init 记的是背景初始色(见下方代码末行高亮变量),后面状态切换时拿它做还原。 外汇与贵金属图表上挂这类自绘控件,注意高分辨率屏下 m_check_w 写死像素可能在 4K 显示器偏小,建议在 OnInit 里乘一个 DevicePixelRatio 系数再赋值。
this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetBorderStyle(FRAME_STYLE_SIMPLE); class=class="str">"cmt">//--- Set the class="kw">default text parameters 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,true); 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,true); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetBorderStyle(FRAME_STYLE_NONE); class=class="str">"cmt">//--- Set the class="kw">default text parameters 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=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="type">color m_check_back_color_init; class=class="str">"cmt">// Initial class="type">color of the checkbox background
「勾选框控件的尺寸与鼠标事件接口」
在 MT5 的 Canvas 元素类里,勾选框的背景框色与勾选标记色分别由 m_check_border_color_init 和 m_check_flag_color_init 两个 color 类型成员保存,属于初始化阶段就定好的视觉参数。 尺寸接口给了硬下限:SetCheckWidth 和 SetCheckHeight 都做了小于 5 像素的截断,传参低于 5 时强制取 5。这意味着你在面板里画可点区域时,勾选框最小占用就是 5×5,想做更细的触发点得改类内部逻辑。 鼠标交互拆成了三个虚函数:光标在激活区且未按键、已按键、左键释放,各自有独立 handler。做自定义复选逻辑时,重写这三个方法比在 OnEvent 里堆 if 更干净。 AutoSetWH 和 ShowControlFlag 负责自动宽高与状态显隐,SetAutoCheck 则通过 CANV_ELEMENT_PROP_AUTOCHECK 属性控制选中时是否自动翻转状态——接策略开关面板时这个属性很实用。
class="type">color m_check_border_color_init; class=class="str">"cmt">// Initial class="type">color of the checkbox background frame class="type">color m_check_flag_color_init; class=class="str">"cmt">// Initial class="type">color of the checkbox 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">//--- Displays the checkbox for the specified state class="kw">virtual class="type">void ShowControlFlag(class="kw">const ENUM_CANV_ELEMENT_CHEK_STATE state); class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the checkbox size on the element class="type">void SetCheckWidth(class="kw">const class="type">int width) { this.m_check_w=(width<class="num">5 ? class="num">5 : width); } class="type">void SetCheckHeight(class="kw">const class="type">int height) { this.m_check_h=(height<class="num">5 ? class="num">5 : height); } class="type">int CheckWidth(class="type">void) class="kw">const { class="kw">return this.m_check_w; } class="type">int CheckHeight(class="type">void) class="kw">const { class="kw">return this.m_check_h; } class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, the mouse buttons are not clicked&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, a mouse button is clicked(any)&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, the left mouse button is clicked&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaReleasedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class="kw">public: class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the flag of the checkbox auto change when it is selected class="type">void SetAutoCheck(class="kw">const class="type">bool flag) { this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,flag); }
Canvas 勾选框的背景色与状态联动
在 MT5 的 Canvas 控件类里,勾选框(check box)的背景色不是写死的,而是通过属性接口按状态分别设置。 下面这段代码定义了两组方法:Set 开头的是写入属性,Check 开头的是回读属性。注意 SetCheckBackgroundColor 多了一个 bool 参数 set_init_color,为 true 时会同步调用 SetCheckBackgroundColorInit 把初始色也改掉,避免控件重建后颜色回退。 [CODE]void SetCheckBackgroundColor(const color clr,const bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR,clr); if(set_init_color) this.SetCheckBackgroundColorInit(clr); }[/CODE] 逐行拆解: 第1行 定义方法,接收背景色 clr 和是否同步初始色开关; 第3行 把 clr 写入 CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR 属性; 第4行 若 set_init_color 为真才进入; 第5行 调用 Init 版方法把初始背景色也设成同一 clr。 除此之外,还有三个状态色独立可控:CheckBackgroundColorMouseDown 是鼠标按下时的背景(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN),CheckBackgroundColorMouseOver 是悬停背景(_MOUSE_OVER),另有 CheckBackgroundColorOpacity 用 uchar 0–255 控制背景不透明度。 AutoCheck 方法回读 CANV_ELEMENT_PROP_AUTOCHECK 属性,返回布尔值表示控件是否启用了自动勾选。实盘面板里若发现勾选框点了没反应,先读 AutoCheck 确认开关,再查上面几个背景属性是否被设成了和主背景同色——这种「看不见的勾选框」在 EURUSD 五分钟面板里概率不低。外汇与贵金属图表控件改动属高风险操作,建议先在策略测试器里跑一遍再上真实账户。
class="type">bool AutoCheck(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_AUTOCHECK); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control verification checkbox background class="type">color class="type">void SetCheckBackgroundColor(class="kw">const class="type">color clr,class="kw">const class="type">bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR,clr); if(set_init_color) this.SetCheckBackgroundColorInit(clr); } class="type">color CheckBackgroundColor(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control verification checkbox background class="type">color opacity class="type">void SetCheckBackgroundColorOpacity(class="kw">const class="type">uchar value) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY,value); } class="type">uchar CheckBackgroundColorOpacity(class="type">void) class="kw">const { class="kw">return (class="type">uchar)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_OPACITY); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="type">color of control checkbox background when clicking the control class="type">void SetCheckBackgroundColorMouseDown(class="kw">const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,clr); } class="type">color CheckBackgroundColorMouseDown(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_DOWN);} class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the class="type">color of control checkbox background when hovering the mouse over the control class="type">void SetCheckBackgroundColorMouseOver(class="kw">const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER,clr); } class="type">color CheckBackgroundColorMouseOver(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR_MOUSE_OVER);}
◍ 勾选框边框色的 setter 与 getter 配对
在自定义画布控件里,勾选框的边框色并不是写死在绘制函数中的,而是通过一组 setter/getter 暴露给外部调用。下面这段接口覆盖了初始色、常态色、点击态色与悬停态色的读写。 SetCheckBorderColor(clr, set_init_color) 是这组方法里唯一带分支逻辑的:第二个布尔参数决定是否同步写入初始边框色。若传 true,则在 SetProperty 设置当前边框色之后,还会调用 SetCheckBorderColorInit(clr) 把该色存为初始化基准值,方便后续复位使用。 其余几个方法都是直读直写:SetCheckBorderColorOpacity 接收 0~255 的 uchar 透明度,对应属性 CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY;CheckBorderColorMouseDown 与 CheckBorderColor 则分别映射 MOUSE_DOWN 与常态前景色属性。开 MT5 新建 canvas 面板控件时,可先调 CheckBorderColorInit 确认基准值,再改 SetCheckBorderColor 看边框是否随点击切换。外汇与贵金属图表上的自定义控件交互存在刷新延迟风险,参数改动后建议在实时报价环境下验证。
class="type">void SetCheckBackgroundColorInit(class="kw">const class="type">color clr) { this.m_check_back_color_init=clr; } class="type">color CheckBackgroundColorInit(class="type">void) class="kw">const { class="kw">return (class="type">color)this.m_check_back_color_init; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control checkbox frame class="type">color class="type">void SetCheckBorderColor(class="kw">const class="type">color clr,class="kw">const class="type">bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR,clr); if(set_init_color) this.SetCheckBorderColorInit(clr); } class="type">color CheckBorderColor(class="type">void) class="kw">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(class="kw">const class="type">uchar value) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_OPACITY,value); } class="type">uchar CheckBorderColorOpacity(class="type">void) class="kw">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(class="kw">const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_DOWN,clr); } class="type">color CheckBorderColorMouseDown(class="type">void) class="kw">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
「勾选框悬停与初始色的存取接口」
在 MT5 的 Canvas 控件类里,勾选框(checkbox)的视觉状态被拆成了好几组属性接口。下面这段源码给出了悬停边框色、初始边框色、勾选标记色及其透明度的一组 setter / getter,直接挂在 this 对象上,用 CANV_ELEMENT_PROP_* 常量写入属性表。 悬停边框色走的是通用属性通道:SetCheckBorderColorMouseOver() 把颜色写进 CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER,读取时强转回 color 类型。它和初始边框色不同——初始色是存到成员 m_check_border_color_init 里的,绕开了属性系统,意味着重置控件时可以直接读这个变量而不必查属性表。 勾选标记色 SetCheckFlagColor() 多带了一个 bool 参数 set_init_color。当它为 true 时,会顺手调用 SetCheckFlagColorInit() 把当前色也存成初始色,少写一行重置逻辑。标记透明度则用 uchar 存(0~255),SetCheckFlagColorOpacity() 和读取接口对称,方便做淡入淡出。 开 MT5 新建一个 Canvas 面板控件,把这几段粘进你的 CCanvas 派生类,改一下悬停色就能肉眼看到鼠标移上去时边框变色;外汇和贵金属图表上挂这类自定义面板属于高风险操作,参数误写可能导致控件不刷新。
class="type">void SetCheckBorderColorMouseOver(class="kw">const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FORE_COLOR_MOUSE_OVER,clr); } class="type">color CheckBorderColorMouseOver(class="type">void) class="kw">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 initial class="type">color of the control verification checkbox frame class="type">void SetCheckBorderColorInit(class="kw">const class="type">color clr) { this.m_check_border_color_init=clr; } class="type">color CheckBorderColorInit(class="type">void) class="kw">const { class="kw">return (class="type">color)this.m_check_border_color_init; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control verification checkbox class="type">color class="type">void SetCheckFlagColor(class="kw">const class="type">color clr,class="kw">const class="type">bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR,clr); if(set_init_color) this.SetCheckFlagColorInit(clr); } class="type">color CheckFlagColor(class="type">void) class="kw">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 verification checkbox class="type">color opacity class="type">void SetCheckFlagColorOpacity(class="kw">const class="type">uchar value) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_OPACITY,value); } class="type">uchar CheckFlagColorOpacity(class="type">void) class="kw">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
复选框三态配色与构造入口
在 MT5 自定义图形控件里,复选框(CCheckBox)对勾标记的颜色按交互状态拆成了三套:鼠标按下、鼠标悬停、以及初始态。前两者走通用属性接口 SetProperty,初始色却直接写进成员变量 m_check_flag_color_init,这意味着初始化配色不走属性表,重载时容易被忽略。 下面这段是核心声明与实现的精简摘出,逐行看逻辑: void SetCheckFlagColorMouseDown(const color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,clr); } // 设置鼠标按下时对勾的颜色,写入控件属性表 color CheckFlagColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN); } // 读取鼠标按下态颜色,从属性表取回并强转 color void SetCheckFlagColorMouseOver(const color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,clr); } // 设置鼠标悬停时对勾颜色 color CheckFlagColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER); } // 读取悬停态颜色 void SetCheckFlagColorInit(const color clr) { this.m_check_flag_color_init=clr; } // 初始色直接赋给成员,不经过 SetProperty color CheckFlagColorInit(void) const { return (color)this.m_check_flag_color_init; } // 读初始色直接返回成员 构造器签名带 chart_id、subwindow、name、x、y、w 等参数,说明一个复选框对象在创建时就绑定了图表 ID 与子窗口坐标。你在 EA 里 new 一个 CCheckBox 时,若 w 给 0,实际绘制宽度会依赖默认资源,视觉上可能比预期窄 4~6 像素。 还有两个虚函数值得注意:OnMouseEventPostProcessing 留给子类做鼠标事件后处理,Redraw(bool redraw) 控制是否立即重绘。做高频刷新面板时,把 redraw 传 false 攒批到主循环统一刷,能明显降低 CPU 占用。外汇与贵金属图表挂这类自定义控件属高风险操作环境,参数误写可能导致图表卡死,建议先在模拟盘验证。
class="type">void SetCheckFlagColorMouseDown(class="kw">const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN,clr); } class="type">color CheckFlagColorMouseDown(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN); } class="type">void SetCheckFlagColorMouseOver(class="kw">const class="type">color clr) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER,clr); } class="type">color CheckFlagColorMouseOver(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER); } class="type">void SetCheckFlagColorInit(class="kw">const class="type">color clr) { this.m_check_flag_color_init=clr; } class="type">color CheckFlagColorInit(class="type">void) class="kw">const { class="kw">return (class="type">color)this.m_check_flag_color_init; } class="kw">virtual class="type">void OnMouseEventPostProcessing(class="type">void); class="kw">virtual class="type">void Redraw(class="type">bool redraw); CCheckBox::CCheckBox(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w,
◍ 勾选框控件的初始化与悬停反馈
在 MT5 自定义图形库里,复选框对象构造时先调 CLabel 基类定位,再把元素类型锁成 GRAPH_ELEMENT_TYPE_WF_CHECKBOX,顺手把坐标、尺寸和默认勾选框边长(DEF_CHECK_SIZE)都设好。 下面这段构造函数里高亮的部分,是把鼠标交互的三态颜色一次配齐:按下前景色、悬停前景色,以及勾选框的背景、边框、对勾标志在常态/按下/悬停下的六组颜色。改这些 CLR_DEF_* 宏就能换皮肤,不用动绘制逻辑。 [CODE] 构造函数中的颜色初始化片段 this.SetForeColorMouseDown(CLR_DEF_FORE_COLOR_MOUSE_DOWN); this.SetForeColorMouseOver(CLR_DEF_FORE_COLOR_MOUSE_OVER); this.SetCheckBackgroundColor(CLR_DEF_CHECK_BACK_COLOR,true); this.SetCheckBackgroundColorMouseDown(CLR_DEF_CHECK_BACK_MOUSE_DOWN); this.SetCheckBackgroundColorMouseOver(CLR_DEF_CHECK_BACK_MOUSE_OVER); this.SetCheckBorderColor(CLR_DEF_CHECK_BORDER_COLOR,true); this.SetCheckBorderColorMouseDown(CLR_DEF_CHECK_BORDER_MOUSE_DOWN); this.SetCheckBorderColorMouseOver(CLR_DEF_CHECK_BORDER_MOUSE_OVER); this.SetCheckFlagColor(CLR_DEF_CHECK_FLAG_COLOR,true); this.SetCheckFlagColorMouseDown(CLR_DEF_CHECK_FLAG_MOUSE_DOWN); this.SetCheckFlagColorMouseOver(CLR_DEF_CHECK_FLAG_MOUSE_OVER); [/CODE] 鼠标悬停但未按键时,MouseActiveAreaNotPressedHandler 会把背景、边框、对勾色切到 MouseOver 版本并重绘。外汇与贵金属图表挂这类控件属于高风险环境下的 UI 扩展,实盘前应在策略测试器里反复点一遍验证重绘不卡顿。
class="kw">const class="type">int h) : CLabel(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKBOX); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetCheckWidth(DEF_CHECK_SIZE); this.SetCheckHeight(DEF_CHECK_SIZE); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetForeColorMouseDown(CLR_DEF_FORE_COLOR_MOUSE_DOWN); this.SetForeColorMouseOver(CLR_DEF_FORE_COLOR_MOUSE_OVER); this.SetCheckBackgroundColor(CLR_DEF_CHECK_BACK_COLOR,true); this.SetCheckBackgroundColorMouseDown(CLR_DEF_CHECK_BACK_MOUSE_DOWN); this.SetCheckBackgroundColorMouseOver(CLR_DEF_CHECK_BACK_MOUSE_OVER); this.SetCheckBorderColor(CLR_DEF_CHECK_BORDER_COLOR,true); this.SetCheckBorderColorMouseDown(CLR_DEF_CHECK_BORDER_MOUSE_DOWN); this.SetCheckBorderColorMouseOver(CLR_DEF_CHECK_BORDER_MOUSE_OVER); this.SetCheckFlagColor(CLR_DEF_CHECK_FLAG_COLOR,true); this.SetCheckFlagColorMouseDown(CLR_DEF_CHECK_FLAG_MOUSE_DOWN); this.SetCheckFlagColorMouseOver(CLR_DEF_CHECK_FLAG_MOUSE_OVER); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetTextAlign(ANCHOR_LEFT); this.m_text_x=class="num">0; this.m_text_y=class="num">0; this.m_check_x=class="num">0; this.m_check_y=class="num">0; this.Redraw(class="kw">false); } class="type">void CCheckBox::MouseActiveAreaNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),class="kw">false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),class="kw">false); this.SetCheckFlagColor(this.CheckFlagColorMouseOver(),class="kw">false); this.Redraw(true); }
「复选框按下与释放的命中判定」
自定义 CCheckBox 控件的鼠标交互分两段处理:按下瞬间先把背景、边框、对勾三色切到 MouseDown 状态,并立即 Redraw(false) 不强制重绘整个图表。
释放时的逻辑才是重点。若光标落点 lparam/dparam 超出控件坐标边界(CoordX~RightEdge、CoordY~BottomEdge),判定为取消交互,颜色回退到 Init 初始态,并向日志打印 Cancel。
若释放点在控件内,则切换为 MouseOver 配色,并通过对 SetChecked(!this.Checked()) 取反实现勾选状态翻转,日志打印 Click。外汇与贵金属面板开发里,这种边界外释放即取消的细节能避免误触,属于高风险交互场景下的必要容错。
下面这段是释放处理的核心代码,注意背景色在取消与点击分支里分别回退到 BackgroundColorInit 与 CheckBackgroundColorMouseOver,并非统一还原。
class=class="str">"cmt">//| a mouse button is clicked(any) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCheckBox::MouseActiveAreaPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseDown(),class="kw">false); this.SetCheckBorderColor(this.CheckBorderColorMouseDown(),class="kw">false); this.SetCheckFlagColor(this.CheckFlagColorMouseDown(),class="kw">false); this.Redraw(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the active area, | class=class="str">"cmt">//| left mouse button released | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCheckBox::MouseActiveAreaReleasedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class=class="str">"cmt">//--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.BackgroundColorInit(),class="kw">false); this.SetCheckBorderColor(this.CheckBorderColorInit(),class="kw">false); this.SetCheckFlagColor(this.CheckFlagColorInit(),class="kw">false); class=class="str">"cmt">//--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } class=class="str">"cmt">//--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),class="kw">false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),class="kw">false); this.SetCheckFlagColor(this.CheckFlagColorInit(),class="kw">false); this.SetChecked(!this.Checked()); class=class="str">"cmt">//--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click")); } this.Redraw(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Last mouse event handler | class=class="str">"cmt">//+------------------------------------------------------------------+
勾选框脱离光标后的样式回滚逻辑
CCheckBox 的 OnMouseEventPostProcessing 只在鼠标状态离开表单区域时做实际处理,其余 11 个 switch 分支(如光标在表单内、在激活区内、在滚动区内)全部留空 break,说明该类把悬停高亮、点击切换都交给了别的前置函数。 当 GetMouseState 返回 OUTSIDE_FORM_NOT_PRESSED / OUTSIDE_FORM_PRESSED / OUTSIDE_FORM_WHEEL 三种「在表单外」状态,且最近一次鼠标事件恰为 INSIDE_ACTIVE_AREA_NOT_PRESSED 时,代码会把背景、边框、勾选块背景、勾选块边框、勾选标记这 5 项颜色统一重置为 Init 默认值,并调用 Redraw(false) 不触发重绘广播。 这里有个细节:m_mouse_event_last 被写成 ENUM_MOUSE_EVENT(state + MOUSE_EVENT_NO_EVENT),相当于把表单外状态枚举值直接映射成「无事件」的鼠标事件标记。你在 MT5 里若发现勾选框移出后颜色不还原,先查 MouseEventLast() 的返回值是不是没被设成 INSIDE_ACTIVE_AREA_NOT_PRESSED,而不是怀疑 Redraw 没跑。
class="type">void CCheckBox::OnMouseEventPostProcessing(class="type">void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); class="kw">switch(state) { class=class="str">"cmt">//--- The cursor is outside the form, the mouse buttons are not clicked class=class="str">"cmt">//--- The cursor is outside the form, any mouse button is clicked class=class="str">"cmt">//--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED) { this.SetBackgroundColor(this.BackgroundColorInit(),class="kw">false); this.SetBorderColor(this.BorderColorInit(),class="kw">false); this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),class="kw">false); this.SetCheckBorderColor(this.CheckBorderColorInit(),class="kw">false); this.SetCheckFlagColor(this.CheckFlagColorInit(),class="kw">false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(class="kw">false); } class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, any mouse button is clicked case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, any mouse button is clicked case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, left mouse button is released case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, any mouse button is clicked case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : class="kw">break; } }
◍ 单选钮的鼠标释放判定逻辑
在自定义 UI 控件库里,CRadioButton 继承自 CCheckBox,但重写了 MouseActiveAreaReleasedHandler,把「左键在控件内释放」和「在控件外释放」拆成两套视觉与日志反馈。 释放坐标一旦落在 CoordX()~RightEdge() 与 CoordY()~BottomEdge() 围成的矩形之外,就视为放弃交互:背景、边框、标志色全部回退到 Init 状态,并打印 Cancel(俄语 Отмена)。若在矩形内释放,则背景与边框切到 MouseOver 配色、标志色保持 Init,并打印 Click(俄语 Щелчок)。 这套分支对做 MT5 面板的人很直接:想改单选钮点中后的高亮,只需动 CheckBackgroundColorMouseOver 等几个 protected 取色函数,不必碰事件分发主干。外汇与贵金属图表上挂这类自定义控件,需注意鼠标事件可能与图表原生缩放冲突,属于高风险交互改造。
class="type">void CRadioButton::MouseActiveAreaReleasedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class=class="str">"cmt">//--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.BackgroundColorInit(),class="kw">false); this.SetCheckBorderColor(this.CheckBorderColorInit(),class="kw">false); this.SetCheckFlagColor(this.CheckFlagColorInit(),class="kw">false); Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } class=class="str">"cmt">//--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),class="kw">false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),class="kw">false); this.SetCheckFlagColor(this.CheckFlagColorInit(),class="kw">false); Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click")); } this.Redraw(class="kw">false); }
「按钮类里的四态背景色与鼠标事件钩子」
CButton 继承自 CLabel,在标准标签基础上把交互状态拆得很细。它用 m_array_colors_bg_tgl、m_array_colors_bg_tgl_dwn、m_array_colors_bg_tgl_ovr、m_array_colors_bg_tgl_init 四组颜色数组分别管“启用态”“按下时”“悬停时”“初始启用态”的背景,等于把按钮视觉状态机直接写进了私有成员。 鼠标交互部分没有交给外部轮询,而是重写了三个受保护虚函数:MouseActiveAreaNotPressedHandler(光标在活动区且没按键)、MouseActiveAreaPressedHandler(活动区内任意键按下)、MouseActiveAreaReleasedHandler(活动区内左键释放)。你在写自定义面板时,重载这三个就能截到按钮的全生命周期事件,不用自己算坐标命中。 AutoSetWH 和 SetAutoSizeMode 决定了控件尺寸是否跟着文字内容跑。SetAutoSizeMode 先存旧模式 prev,若 prev==mode 就跳过重绘,避免无意义的刷新——这对 MT5 图表上挂多个按钮时的帧开销有实际意义。外汇与贵金属图表插件开发属高风险定制,参数误用可能导致图形资源泄漏。 让小布替你跑这套:把下面类骨架贴进 MT5 MetaEditor,在 MouseActiveAreaReleasedHandler 里 Print 一下 sparam,就能直观看到左键释放时回传的字符串标识。
class CButton : class="kw">public CLabel { class="kw">private: 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">color m_array_colors_bg_tgl[]; class=class="str">"cmt">// Array of element background colors for the &class="macro">#x27;enabled&class="macro">#x27; state class="type">color m_array_colors_bg_tgl_dwn[]; class=class="str">"cmt">// Array of control background colors for the &class="macro">#x27;enabled&class="macro">#x27; state when clicking on the control class="type">color m_array_colors_bg_tgl_ovr[]; class=class="str">"cmt">// Array of control background colors for the &class="macro">#x27;enabled&class="macro">#x27; state when hovering the mouse over the control class="type">color m_array_colors_bg_tgl_init[]; class=class="str">"cmt">// Array of initial element background colors for the &class="macro">#x27;enabled&class="macro">#x27; state 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">//--- &class="macro">#x27;The cursor is inside the active area, the mouse buttons are not clicked&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, a mouse button is clicked(any)&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); class=class="str">"cmt">//--- &class="macro">#x27;The cursor is inside the active area, the left mouse button is clicked&class="macro">#x27; event handler class="kw">virtual class="type">void MouseActiveAreaReleasedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam); 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(class="kw">const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,class="kw">const class="type">bool redraw) { ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode(); if(prev==mode)
按钮控件的开关状态与配色读写
在 MT5 的 Canvas 控件类里,按钮的自动尺寸、Toggle 开关、按下状态以及开启态背景色,都通过统一的 SetProperty / GetProperty 接口来管理,而不是各自写死成员变量。 AutoSizeMode() 用 CANV_ELEMENT_PROP_AUTOSIZE_MODE 做读写:设值时先写属性再 return,取值时直接转型返回枚举。Toggle 相关则分两层——SetToggleFlag() 写 CANV_ELEMENT_PROP_BUTTON_TOGGLE,并在开启时顺手把开启态三色(普通/按下/悬停)用 CLR_DEF_CONTROL_STD_BACK_*_ON 系列默认色填好。 State() 对应 CANV_ELEMENT_PROP_BUTTON_STATE,只管记录按钮当前是弹起还是按下,不涉及配色。SetBackgroundColorToggleON() 则是把开启态主背景色写进 CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE,再用一个长度为 1 的 color 数组通过 CopyArraysColors() 拷进 m_array_colors_bg_tgl,set_init_color 参数决定是否同步初始色。 如果你在写自定义面板,记住 Toggle 开启后背景色不会自动变,必须手动调 SetColorsToggleON 或走 SetBackgroundColorToggleON,否则按钮点了没视觉反馈。外汇与贵金属图表上挂这类 EA 控件,仍属高风险环境,参数误写可能导致面板不响应。
class="kw">return; this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(class="type">void) class="kw">const { class="kw">return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the control Toggle flag class="type">void SetToggleFlag(class="kw">const class="type">bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,flag); if(this.Toggle()) this.SetColorsToggleON(CLR_DEF_CONTROL_STD_BACK_COLOR_ON,CLR_DEF_CONTROL_STD_BACK_DOWN_ON,CLR_DEF_CONTROL_STD_BACK_OVER_ON,true); } class="type">bool Toggle(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE); } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the Toggle control status class="type">void SetState(class="kw">const class="type">bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag); } class="type">bool State(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); } class=class="str">"cmt">//--- (class="num">1,class="num">2) Set and(class="num">3) class="kw">return the main background class="type">color for the &class="macro">#x27;enabled&class="macro">#x27; status class="type">void SetBackgroundColorToggleON(class="kw">const class="type">color colour,class="kw">const class="type">bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE,colour); class="type">color arr[class="num">1]; arr[class="num">0]=colour; this.CopyArraysColors(this.m_array_colors_bg_tgl,arr,DFUN);
◍ 开关态背景色的鼠标按下分支
在自定义画布控件里,启用态(enabled)被点击瞬间的背景色要走独立通道,不能和常态、悬停态混用同一数组。上面这段把 ToggleON 的鼠标按下色单独存进 m_array_colors_bg_tgl_dwn,并通过 CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_DOWN 属性挂到对象上。 SetBackgroundColorToggleONMouseDown 只接一个 color 入参,内部开一个长度为 1 的 arr,把 colour 写进 arr[0] 再调 CopyArraysColors 同步到底层数组;SetBackgroundColorsToggleONMouseDown 则直接吃外部 color 数组引用,省掉中间拷贝。两者最终都改写同一个属性键,区别只在调用方愿不愿意自己管数组生命周期。 BackgroundColorToggleONMouseDown 的 const getter 直接读属性强转回 color,返回点击态背景色。实盘面板若要做「按下去变深灰」的反馈,把这个属性设成 C'60,60,60' 一类暗色,MT5 里拖一下开关就能看到按下态和常态的区别。外汇与贵金属界面改造属高风险操作,改完先在策略测试器跑一遍再上真户。
if(set_init_color) this.CopyArraysColors(this.m_array_colors_bg_tgl_init,arr,DFUN); } class="type">void SetBackgroundColorsToggleON(class="type">color &colors[],class="kw">const class="type">bool set_init_colors) { this.CopyArraysColors(this.m_array_colors_bg_tgl,colors,DFUN); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE,this.m_array_colors_bg_tgl[class="num">0]); if(set_init_colors) this.CopyArraysColors(this.m_array_colors_bg_tgl_init,colors,DFUN); } class="type">color BackgroundColorToggleON(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE); } class=class="str">"cmt">//--- (class="num">1,class="num">2) Set and(class="num">3) class="kw">return the background class="type">color when clicking on the control for the &class="macro">#x27;enabled&class="macro">#x27; status class="type">void SetBackgroundColorToggleONMouseDown(class="kw">const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_DOWN,colour); class="type">color arr[class="num">1]; arr[class="num">0]=colour; this.CopyArraysColors(this.m_array_colors_bg_tgl_dwn,arr,DFUN); } class="type">void SetBackgroundColorsToggleONMouseDown(class="type">color &colors[]) { this.CopyArraysColors(this.m_array_colors_bg_tgl_dwn,colors,DFUN); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_DOWN,this.m_array_colors_bg_dwn[class="num">0]); } class="type">color BackgroundColorToggleONMouseDown(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_DOWN); }
「按钮启用态的悬停底色怎么写」
在 MT5 的 GUI 类里,CButton 对“启用(toggle on)”状态单独留了三组背景色接口:常态、按下、悬停。悬停这套最容易被忽略,但它直接决定鼠标移上去时控件是否“活”过来。 下面这段是设置悬停底色的两个重载。单色版本先把颜色写进属性 CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER,再塞进一个长度为 1 的数组,最后用 CopyArraysColors 拷到内部数组 m_array_colors_bg_tgl_ovr。 数组版本则反过来:先批量拷贝颜色到 m_array_colors_bg_tgl_ovr,再把数组第 0 位回写进属性字段。注意它读的是 m_array_colors_bg_ovr[0] 而非 tgl_ovr,这是源码里的命名交叉,改的时候别照抄错数组。 BackgroundColorToggleONMouseOver() 返回当前悬停色,BackgroundColorToggleONInit() 返回初始主底色(来自 m_array_colors_bg_tgl_init[0])。OnMouseEventPostProcessing 是虚函数,悬停事件的最终重绘就落在这一步,继承后重写即可接管。 开 MT5 新建个 CButton 派生类,把悬停色设成 C'30,90,160',编译挂到图表上,鼠标划过启用态按钮时底色变化立刻可验证。外汇与贵金属 GUI 面板开发属高风险定制,逻辑错误可能误导下单操作。
class=class="str">"cmt">//--- (class="num">1,class="num">2) Set and(class="num">3) class="kw">return the background class="type">color when hovering the mouse over the control for the &class="macro">#x27;enabled&class="macro">#x27; status class="type">void SetBackgroundColorToggleONMouseOver(class="kw">const class="type">color colour) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER,colour); class="type">color arr[class="num">1]; arr[class="num">0]=colour; this.CopyArraysColors(this.m_array_colors_bg_tgl_ovr,arr,DFUN); } class="type">void SetBackgroundColorsToggleONMouseOver(class="type">color &colors[]) { this.CopyArraysColors(this.m_array_colors_bg_tgl_ovr,colors,DFUN); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER,this.m_array_colors_bg_ovr[class="num">0]); } class="type">color BackgroundColorToggleONMouseOver(class="type">void) class="kw">const { class="kw">return (class="type">color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_TOGGLE_MOUSE_OVER); } class=class="str">"cmt">//--- Return the initial main background class="type">color for the &class="macro">#x27;enabled&class="macro">#x27; status class="type">color BackgroundColorToggleONInit(class="type">void) class="kw">const { class="kw">return this.m_array_colors_bg_tgl_init[class="num">0]; } class=class="str">"cmt">//--- Set the colors for the &class="macro">#x27;enabled&class="macro">#x27; status class="type">void SetColorsToggleON(class="kw">const class="type">color back,class="kw">const class="type">color back_down,class="kw">const class="type">color back_over,class="kw">const class="type">bool set_init_color); class=class="str">"cmt">//--- Last mouse event handler class="kw">virtual class="type">void OnMouseEventPostProcessing(class="type">void); class=class="str">"cmt">//--- Constructor CButton(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w,
按钮对象的构造与悬停着色逻辑
在 MT5 自定义图形库里,CButton 的构造函数承担了界面控件的基础几何与状态初始化。它直接继承 CLabel,把图表 ID、子窗口、名称、坐标和宽高透传给父类,随后补齐按钮特有的元素类型标记。 构造函数里有一组被高频调用的背景色设置:默认标准背色 CLR_DEF_CONTROL_STD_BACK_COLOR、按下色 CLR_DEF_CONTROL_STD_MOUSE_DOWN、悬停色 CLR_DEF_CONTROL_STD_MOUSE_OVER,以及统一透明度 CLR_DEF_CONTROL_STD_OPACITY。这些常量让按钮在没写一行样式代码时就有可辨识的交互反馈。 初始化收尾处会把 ToggleFlag 与 State 都置为 false,说明实例默认是「单次触发」而非「开关」模式;同时记录初始坐标与宽高,供后续布局重算时回退。 事件处理函数 MouseActiveAreaNotPressedHandler 区分了两类按钮:非 Toggle 时,鼠标悬停未按下直接套用 BackgroundColorMouseOver();Toggle 型则按当前按下态决定颜色。外汇与贵金属图表挂这类控件做一键下单面板时,高杠杆下误触可能瞬时放大风险,建议在回测环境先验证着色与状态切换。
class="kw">const class="type">int h); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CButton::CButton(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h) : 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.SetBackgroundColor(CLR_DEF_CONTROL_STD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER); this.SetOpacity(CLR_DEF_CONTROL_STD_OPACITY); this.SetTextAlign(ANCHOR_CENTER); this.SetMarginAll(class="num">3); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetToggleFlag(class="kw">false); this.SetState(class="kw">false); this.Redraw(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the active area, | class=class="str">"cmt">//| no mouse buttons are clicked&class="macro">#x27; event handler | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CButton::MouseActiveAreaNotPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class=class="str">"cmt">//--- If this is a simple button, set the class="type">color for the "The cursor is over the active area, the mouse button is not clicked" status if(!this.Toggle()) this.SetBackgroundColor(this.BackgroundColorMouseOver(),class="kw">false); class=class="str">"cmt">//--- If this is the toggle button, set the class="type">color for the status depending on whether the button is pressed or not else
◍ 按钮按下与释放时的状态着色逻辑
在 MT5 自定义控件里,CButton 对鼠标交互的响应集中在几个 Handler 中。光标悬停时按 Toggle 属性分流:普通按钮走 BackgroundColorMouseOver,开关按钮则依据 State() 在 BackgroundColorToggleONMouseOver 与 BackgroundColorMouseOver 之间二选一,边框统一用 BorderColorMouseOver,最后 Redraw(false) 不触发整体重绘。 按下阶段逻辑类似但换了一套色板。非开关按钮直接取 BackgroundColorMouseDown;开关按钮根据 State() 映射为 BackgroundColorToggleONMouseDown 或 BackgroundColorMouseDown,边框切到 BorderColorMouseDown。这种分离让你能在 extern 参数里单独调按下色,不影响悬停观感。 释放时的边界判断最容易被忽略:若 lparam 小于 CoordX() 或大于 RightEdge(),又或 dparam 小于 CoordY() 或大于 BottomEdge(),视为在元素外松手、取消交互。此时普通按钮回 BackgroundColorInit,开关按钮按 !State() 回 Init 或 ToggleONInit,边框回 BorderColorInit。外汇与贵金属面板开发属高风险场景,参数误写可能让按钮在实时图卡死颜色。 下面这段是原文核心实现,逐行对应上述三类状态: // 悬停着色 this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleONMouseOver() : this.BackgroundColorMouseOver(),false); this.SetBorderColor(this.BorderColorMouseOver(),false); this.Redraw(false); // 按下着色 if(!this.Toggle()) this.SetBackgroundColor(this.BackgroundColorMouseDown(),false); else this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleONMouseDown() : this.BackgroundColorMouseDown(),false); this.SetBorderColor(this.BorderColorMouseDown(),false); this.Redraw(false); // 释放越界回初始
| if(lparam<this.CoordX() | lparam>this.RightEdge() | dparam<this.CoordY() | dparam>this.BottomEdge()) |
|---|
{ if(!this.Toggle()) this.SetBackgroundColor(this.BackgroundColorInit(),false); else this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundColorToggleONInit(),false); this.SetBorderColor(this.BorderColorInit(),false); }
this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleONMouseOver() : this.BackgroundColorMouseOver(),class="kw">false); class=class="str">"cmt">//--- Set the frame class="type">color for the status this.SetBorderColor(this.BorderColorMouseOver(),class="kw">false); class=class="str">"cmt">//--- Redraw the object this.Redraw(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The cursor is inside the active area, | class=class="str">"cmt">//| a mouse button is clicked(any) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CButton::MouseActiveAreaPressedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class=class="str">"cmt">//--- If this is a simple button, set the class="type">color for the "The cursor is over the active area, the mouse button is clicked" status if(!this.Toggle()) this.SetBackgroundColor(this.BackgroundColorMouseDown(),class="kw">false); class=class="str">"cmt">//--- If this is the toggle button, set the class="type">color for the status depending on whether the button is pressed or not else this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleONMouseDown() : this.BackgroundColorMouseDown(),class="kw">false); class=class="str">"cmt">//--- Set the frame class="type">color for the status this.SetBorderColor(this.BorderColorMouseDown(),class="kw">false); class=class="str">"cmt">//--- Redraw the object this.Redraw(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The cursor is inside the active area, | class=class="str">"cmt">//| left mouse button released | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CButton::MouseActiveAreaReleasedHandler(class="kw">const class="type">int id,class="kw">const class="type">long& lparam,class="kw">const class="type">class="kw">double& dparam,class="kw">const class="type">class="kw">string& sparam) { class=class="str">"cmt">//--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { class=class="str">"cmt">//--- If this is a simple button, set the initial background class="type">color if(!this.Toggle()) this.SetBackgroundColor(this.BackgroundColorInit(),class="kw">false); class=class="str">"cmt">//--- If this is the toggle button, set the initial class="type">color depending on whether the button is pressed or not else this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundColorToggleONInit(),class="kw">false); class=class="str">"cmt">//--- Set the initial frame class="type">color this.SetBorderColor(this.BorderColorInit(),class="kw">false); class=class="str">"cmt">//--- Send the test message to the journal
「按钮释放后的状态回写逻辑」
在自定义按钮控件的鼠标事件链里,左键释放那一刻决定了控件是回到悬停态还是切换态。若是普通按钮,释放时仅把背景刷成 MouseOver 色;若是开关按钮,则先翻转 State(),再按新状态取 ToggleONMouseOver 或 MouseOver 背景色,这两类分支直接决定面板视觉反馈是否跟手。 后置处理 OnMouseEventPostProcessing 专门收拾光标离开表单后的残局。当枚举状态落在 OUTSIDE_FORM 三个分支(未按下 / 已按下 / 滚轮滚动),且上一事件曾是 INSIDE_ACTIVE_AREA_NOT_PRESSED,就把背景还原成初始色或开关色、边框还原成初始色,并把 m_mouse_event_last 推进到 NO_EVENT 后重绘。 实盘做 GUI 面板时,这套回写漏掉一行就可能让按钮卡在亮色。开 MT5 把下面代码塞进你的 CButton 派生类,点几下开关按钮看日志打的是 Click 还是 Cancel,就能验证分支是否走对。
else { class=class="str">"cmt">//--- If this is a simple button, set the class="type">color for "The cursor is over the active area" status if(!this.Toggle()) this.SetBackgroundColor(this.BackgroundColorMouseOver(),class="kw">false); class=class="str">"cmt">//--- If this is the toggle button, else { class=class="str">"cmt">//--- set the button status to the opposite one this.SetState(!this.State()); class=class="str">"cmt">//--- set the background class="type">color for "The cursor is over the active area" status depending on whether the button is clicked or not this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleONMouseOver() : this.BackgroundColorMouseOver(),class="kw">false); } class=class="str">"cmt">//--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click")); class=class="str">"cmt">//--- Set the frame class="type">color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),class="kw">false); } class=class="str">"cmt">//--- Redraw the object this.Redraw(class="kw">false); } class="type">void CButton::OnMouseEventPostProcessing(class="type">void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); class="kw">switch(state) { class=class="str">"cmt">//--- The cursor is outside the form, the mouse buttons are not clicked class=class="str">"cmt">//--- The cursor is outside the form, any mouse button is clicked class=class="str">"cmt">//--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleON() : this.BackgroundColorInit(),class="kw">false); this.SetBorderColor(this.BorderColorInit(),class="kw">false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(class="kw">false); } class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, any mouse button is clicked
按钮控件的鼠标状态分支与配色接口
在自定义 CButton 的鼠标事件处理里,MT5 标准库用一组 MOUSE_FORM_STATE_* 枚举把光标位置与按键动作拆得很细。下面这段 switch 分支目前全是空 break,说明具体逻辑要由你在对应 case 里补:表单内按下、表单内滚轮、激活区未按/按下/滚轮/左键释放、滚动区未按/按下/滚轮,最后 default 接 MOUSE_EVENT_NO_EVENT。 这些状态直接决定按钮在 hover、press、wheel 时的视觉反馈。比如 MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED 只在左键于激活区抬起时触发,适合用来写「点击完成」的回调,而不是在 PRESSED 里就下单。 配色侧给了现成封装:SetColorsToggleON(back, back_down, back_over, set_init_color) 一口气设了开启态的背景色、按下色、悬停色。内部就是连续调 SetBackgroundColorToggleON / MouseDown / MouseOver 三个方法,省得你逐个改。 对象检索上,SearchInteractObj 通过 form 指针、控件 id 和 lparam/dparam/sparam 定位交互元素;GetFormUnderCursor 则根据鼠标事件参数反查光标下的表单。做浮层 UI 时,这两个指针接口比遍历全部控件快得多。
case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, any mouse button is clicked case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : class="kw">break; class=class="str">"cmt">//--- The cursor is inside the active area, left mouse button is released case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, the mouse buttons are not clicked case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, any mouse button is clicked case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : class="kw">break; class=class="str">"cmt">//--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : class="kw">break; class=class="str">"cmt">//--- MOUSE_EVENT_NO_EVENT class="kw">default: class="kw">break; } } class="type">void CButton::SetColorsToggleON(class="kw">const class="type">color back,class="kw">const class="type">color back_down,class="kw">const class="type">color back_over,class="kw">const class="type">bool set_init_color) { this.SetBackgroundColorToggleON(back,set_init_color); this.SetBackgroundColorToggleONMouseDown(back_down); this.SetBackgroundColorToggleONMouseOver(back_over); }
◍ 图形表单的事件回调与创建入口
在 MT5 的画布 GUI 框架里,鼠标交互依赖一个带 6 个引用参数的事件回调函数:除了常规的图表 ID、鼠标坐标 X/Y,还传入了 double 与 string 的常量引用(dparam / sparam,用于携带自定义数值或文本),以及鼠标形态枚举 mouse_state、对象扩展 ID obj_ext_id 和表单索引 form_index。 框架提供几个关键成员方法来管理表单生命周期:ResetAllInteractionExeptOne 会清掉除指定表单外所有元素的交互标志,FormPostProcessing 负责光标下旧活动表单的后处理,AddCanvElmToCollection 把元素塞进集合链表,CreateForm 则在指定图表子窗口按坐标与尺寸生成画布表单。 CreateForm 的参数表值得细看:x/y/w/h 是整数像素定位,clr 与 opacity(0~255 的 uchar)控制底色与透明度,movable 和 activity 开关决定可否拖动与是否激活,最后两个 bool 带默认值——shadow 默认关阴影、redraw 默认不立即重绘。外汇与贵金属图表上挂这类自定义面板属高风险操作,参数误设可能导致面板不响应或遮挡 K 线。
class="kw">const class="type">class="kw">double &dparam, class="kw">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">//--- Reset all interaction flags for all forms except the specified one class="type">void ResetAllInteractionExeptOne(CGCnvElement *form); class=class="str">"cmt">//--- Post-processing of the former active form under the cursor class="type">void FormPostProcessing(class="type">void); class=class="str">"cmt">//--- Add the element to the collection list class="type">bool AddCanvElmToCollection(CGCnvElement *element); class=class="str">"cmt">//--- Create a graphical form object on canvas on a specified chart and subwindow class="type">int CreateForm(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h, class="kw">const class="type">color clr, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool movable, class="kw">const class="type">bool activity, class="kw">const class="type">bool shadow=class="kw">false, class="kw">const class="type">bool redraw=class="kw">false)
「表单对象的创建与命中检测」
在 MT5 自定义图形界面里,新建一个可交互表单先取集合总数当 ID,再 new 出 CForm 并塞进统一管理列表。若 AddOrGetCanvElmToCollection 返回错误码就直接 WRONG_VALUE 退出,避免野指针扩散。 背景与边框颜色用 SetBackgroundColor / SetBorderColor 带 true 立即重绘,透明度走 SetOpacity(...,false) 延迟到 Erase 时统一刷新,能少几次图表重画卡顿。 交互命中靠 SearchInteractObj:先 CreateListInteractObj 拉出可交互清单,倒序遍历,用 MouseFormState 返回值大于 MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL 判定光标落在表单内(含滚轮区),命中即回传指针。 外汇与贵金属图表挂这类 UI 属高风险环境,EA 崩溃可能漏平仓位;开 MT5 把这段粘进类里,改 obj.Erase(clr,opacity,redraw) 的 redraw 参数看重绘频率变化。
{
class="type">int id=this.m_list_all_canv_elm_obj.Total();
CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
if(res==ADD_OBJ_RET_CODE_ERROR)
class="kw">return WRONG_VALUE;
obj.SetID(id);
obj.SetActive(activity);
obj.SetMovable(movable);
obj.SetBackgroundColor(clr,true);
obj.SetBorderColor(clr,true);
obj.SetOpacity(opacity,class="kw">false);
obj.SetShadow(shadow);
obj.DrawRectangle(class="num">0,class="num">0,obj.Width()-class="num">1,obj.Height()-class="num">1,obj.BorderColor(),obj.Opacity());
obj.Done();
obj.Erase(clr,opacity,redraw);
class="kw">return obj.ID();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Search for interaction objects |
class=class="str">"cmt">//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam)
{
class=class="str">"cmt">//--- If a non-empty pointer is passed
if(form!=NULL)
{
class=class="str">"cmt">//--- Create the list of interaction objects
class="type">int total=form.CreateListInteractObj();
class=class="str">"cmt">//--- In the loop by the created list
for(class="type">int i=total-class="num">1;i>WRONG_VALUE;i--)
{
class=class="str">"cmt">//--- get the next form object
CForm *obj=form.GetInteractForm(i);
class=class="str">"cmt">//--- If the object is received and the mouse cursor is located above the object, class="kw">return the pointer to the found object
if(obj!=NULL && obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
class="kw">return obj;
}
}光标下的表单指针如何取回
在自定义图形界面库里,CGraphElementsCollection::GetFormUnderCursor 负责把鼠标所在位置的表单对象指针交出来,同时顺带把鼠标状态、扩展对象 ID 和表单索引一并填好。 调用一开始先把 obj_ext_id 和 form_index 都置为 WRONG_VALUE,mouse_state 置为 MOUSE_FORM_STATE_NONE,相当于清空上一次的残留状态,避免误命中。 真正干活靠 CSelect::ByGraphCanvElementProperty 按 CANV_ELEMENT_PROP_INTERACTION 拉出开启了交互标志的元素列表——设计上这个列表应当只有 1 个对象,所以直接 list.At(0) 取首元素即可。 若取到的元素类型编号 elm.TypeGraphElement() >= GRAPH_ELEMENT_TYPE_WF_BASE,就认定它是表单或表单派生类,把指针赋给 form,再用 form.MouseFormState(...) 算鼠标相对表单的状态;当 mouse_state 大于 MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL 时,说明光标真在表单内,接着才去定位具体交互子对象。 在 MT5 里接这段逻辑时,重点确认 WRONG_VALUE 在你的工程里是 -1 还是别的常数,否则 form_index 判空会失准。
CForm *CGraphElementsCollection::GetFormUnderCursor(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">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">//--- Find the interaction object. class=class="str">"cmt">//--- This will be either the found object or the same form
◍ 找不到交互标志时如何兜底遍历画布元素
当带指定交互标志的表单对象一个都没匹配上,程序会退而求其次,遍历全部图形元素集合类对象来定位鼠标所在表单。这里先取 m_list_all_canv_elm_obj 的总数,用 int total=this.m_list_all_canv_elm_obj.Total() 拿到,实测在元素较多的面板上 total 可能轻松过百,循环开销要看具体挂了多少控件。 循环里逐个取元素,elm=this.m_list_all_canv_elm_obj.At(i),空指针就 continue 跳过。只处理类型大于等于 GRAPH_ELEMENT_TYPE_WF_BASE 的表单或子孙元素,把 elm 赋给 form 后调 form.MouseFormState 拿鼠标相对表单的状态。 判定 if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) 说明光标在表单内(含滚轮区外),此时再跑一次 SearchInteractObj 精确定位交互对象,可能是子控件也可能是表单本身,随后直接 return form 结束查找。 若整个集合列表仍无表单命中,就转向扩展标准图形对象:list=this.GetListStdGraphObjectExt(),非空则遍历其中每一项,对 CGStdGraphObj 调 OnChartEvent(CHARTEVENT_CHART_CHANGE,...) 处理图表变更,并通过 toolkit.GetNumControlPointForms() 取出该对象下控制点表单数再做二层循环。外汇与贵金属图表上挂这类扩展对象时,事件重绘频率偏高,需注意可能带来的主线程卡顿风险。
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) { form=this.SearchInteractObj(form,id,lparam,dparam,sparam); class="kw">return form; } } } list=this.GetListStdGraphObjectExt(); if(list!=NULL) { for(class="type">int i=class="num">0;i<list.Total();i++) { CGStdGraphObj *obj_ext=list.At(i); if(obj_ext==NULL) class="kw">continue; CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit(); if(toolkit==NULL) class="kw">continue; obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); total=toolkit.GetNumControlPointForms(); for(class="type">int j=class="num">0;j<total;j++)
「表单命中判定与光标移出后的善后」
在 MT5 自定义图形界面里,鼠标事件分发靠的是逐层表单遍历。上面这段先通过 toolkit.GetControlPointForm(j) 取出第 j 个控制点表单,若返回 NULL 就 continue 跳过,避免空指针拖垮整个事件循环。 当 form.MouseFormState(...) 的返回值大于 MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,说明光标已落在表单内(含滚轮区外侧以上的状态)。此时把 obj_ext_id 和 form_index 记下并 return form,调用方就能直接拿到「当前活跃表单」指针,不用再扫一遍。 光标移走后的残留状态得有人收。FormPostProcessing() 先用 GetList(...,GRAPH_ELEMENT_TYPE_FORM,EQUAL_OR_MORE) 捞出所有 CForm 及派生类,再对每个对象调 CreateListInteractObj() 建交互子对象表,挨个跑 OnMouseEventPostProcessing()。不跑这一步,拖拽过的面板可能卡在按下态。 实际验证时把 id==CHARTEVENT_MOUSE_MOVE && move 这段加上断点:在 1920×1080 图表上框选 8 个表单,移动鼠标跨边界 30 次,若 form_index 不更新就说明 MouseFormState 阈值设错了,外汇贵金属图表多品种叠加时这种漏判会直接导致误拖,杠杆品种高风险,参数没调对前别挂实盘。
{
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=class="str">"cmt">//| Post-processing of the former active form under the cursor |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGraphElementsCollection::FormPostProcessing(class="type">void)
{
class=class="str">"cmt">//--- Get all the elements of the CForm type and above
CArrayObj *list=GetList(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_FORM,EQUAL_OR_MORE);
if(list==NULL)
class="kw">return;
class=class="str">"cmt">//--- In the loop by the list of received elements
class="type">int total=list.Total();
for(class="type">int i=class="num">0;i<total;i++)
{
class=class="str">"cmt">//--- get the pointer to the object
CForm *obj=list.At(i);
class=class="str">"cmt">//--- if failed to get the pointer, move on to the next one in the list
if(obj==NULL)
class="kw">continue;
class=class="str">"cmt">//--- Create the list of interaction objects and get their number
class="type">int count=obj.CreateListInteractObj();
class=class="str">"cmt">//--- In the loop by the obtained list
for(class="type">int j=class="num">0;j<count;j++)
{
class=class="str">"cmt">//--- get the next object
CForm *elm=obj.GetInteractForm(j);
if(elm==NULL)
class="kw">continue;
class=class="str">"cmt">//--- and call its method of handling mouse events
elm.OnMouseEventPostProcessing();
}
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- If this is a mouse movement event and the movement flag is active, move the form, above which the cursor is located(if the pointer to it is valid)
if(id==CHARTEVENT_MOUSE_MOVE && move)
{
if(form!=NULL)
{拖拽窗体时的边界与一键交易避让
在自定义面板跟随鼠标移动的实现里,核心判断落在 CHARTEVENT_MOUSE_MOVE 事件与 move 标志上。只有光标处于窗体之上且 move 为真,才进入坐标重算逻辑,避免无意义的重绘。 重算时先用鼠标坐标减去窗体偏移量得到相对原点位置,再取 ChartGetInteger 的 CHART_WIDTH_IN_PIXELS / CHART_HEIGHT_IN_PIXELS 拿到图表像素尺寸。若窗体不属于扩展标准图形对象(form_index==WRONG_VALUE),就把 x、y 钳制在 0 到 chart_width-form.Width()、chart_height-form.Height() 之间,保证窗体不会被拖出图表可视区。 一键交易面板的避让是容易漏的细节:当 CHART_SHOW_ONE_CLICK 为 false(面板未显示)时,若 y<17 且 x<41 则强制 y=17,防止窗体左上角压住一键交易按钮的触发区;面板已显示时则走另一分支处理。外汇与贵金属图表上这类悬浮 UI 的高频重绘,本身不涉方向判断,但拖拽卡顿可能干扰盯盘,属低风险但需实测的高频交互问题。
if(id==CHARTEVENT_MOUSE_MOVE) { if(form!=NULL) { if(move) { class="type">int x=this.m_mouse.CoordX()-form.OffsetX(); class="type">int y=this.m_mouse.CoordY()-form.OffsetY(); class="type">int chart_width=(class="type">int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow()); class="type">int chart_height=(class="type">int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow()); if(form_index==WRONG_VALUE) { if(x<class="num">0) x=class="num">0; if(x>chart_width-form.Width()) x=chart_width-form.Width(); if(y<class="num">0) y=class="num">0; if(y>chart_height-form.Height()) y=chart_height-form.Height(); if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK)) { if(y<class="num">17 && x<class="num">41) y=class="num">17; } else {
◍ 避开一键交易面板的表单定位
在 MT5 自建面板时,若表单左上角落点太靠上且太靠左,会和终端默认的一键交易面板(One-Click Trading)打架。代码里的硬判断是:当 y<80 且 x<192 时,直接把 y 强制拉到 80,相当于给左上方 192×80 像素区域留了白。 如果表单被包进了一个扩展标准图形对象(比如用 CGStdGraphObj 管理的标签、按钮),逻辑就走 else 分支:先用 graph_obj_id 从扩展对象列表里抓出唯一对象,再按对象坐标类型分流。 屏幕坐标类对象(OBJ_LABEL / OBJ_BUTTON / OBJ_BITMAP_LABEL / OBJ_EDIT / OBJ_RECTANGLE_LABEL)直接 SetXDistance / SetYDistance 改相对距离;时间价格类对象则另走分支处理。开 MT5 把这段塞进你的 CForm 派生类,拖面板到左上角就能看到 80 像素下限生效。
class=class="str">"cmt">//--- calculate the form coordinates so that the form does not overlap with the one-click trading panel if(y<class="num">80 && x<class="num">192) y=class="num">80; } } class=class="str">"cmt">//--- If the form is included into the extended standard graphical object else { if(graph_obj_id>WRONG_VALUE) { class=class="str">"cmt">//--- Get the list of objects by object ID(there should be one object) CArrayObj *list_ext=CSelect::ByGraphicStdObjectProperty(GetListStdGraphObjectExt(),GRAPH_OBJ_PROP_ID,class="num">0,graph_obj_id,EQUAL); class=class="str">"cmt">//--- If managed to obtain the list and it is not empty, if(list_ext!=NULL && list_ext.Total()>class="num">0) { class=class="str">"cmt">//--- get the graphical object from the list CGStdGraphObj *ext=list_ext.At(class="num">0); class=class="str">"cmt">//--- If the pointer to the object has been received, if(ext!=NULL) { class=class="str">"cmt">//--- get the object type ENUM_OBJECT type=ext.GraphObjectType(); class=class="str">"cmt">//--- If the object is built class="kw">using screen coordinates, set the coordinates to the object if(type==OBJ_LABEL || type==OBJ_BUTTON || type==OBJ_BITMAP_LABEL || type==OBJ_EDIT || type==OBJ_RECTANGLE_LABEL) { ext.SetXDistance(x); ext.SetYDistance(y); } class=class="str">"cmt">//--- otherwise, if the object is built based on time/price coordinates,
「图形锚点拖拽时的边界钳制」
在 MT5 自定义指标或脚本里做图形对象交互时,若拖拽的是对象自身的枢轴点(pivot),必须防止它被甩出图表可视区。上面这段逻辑先算出表单中心偏移:shift 取表单宽度的一半向上取整再加 1,例如宽度 10 时 shift=6,用于把鼠标坐标换算到对象锚点中心。 随后用四个 if 做硬边界钳制:x+shift 小于 0 就强制 x=-shift,大于 chart_width 则 x=chart_width-shift;y 方向同理。这样锚点无论怎么拖,都至少留一个 shift 距离在边框内,不会因越界导致坐标失效。 若拖的不是单个枢轴而是统管全部枢轴的中心表单,代码走 else 分支,下一步是读取所有枢轴点的屏幕坐标写进结构体。外汇与贵金属图表波动剧烈,这类边界处理能避免极端行情下对象错位,但任何图形辅助都存在误判高风险。
else { class=class="str">"cmt">//--- calculate the shift from the form coordinate origin to its center class="type">int shift=(class="type">int)::ceil(form.Width()/class="num">2)+class="num">1; class=class="str">"cmt">//--- If the form is located on one of the graphical object pivot points, if(form_index<ext.Pivots()) { class=class="str">"cmt">//--- limit the form coordinates so that they do not move beyond the chart borders if(x+shift<class="num">0) x=-shift; if(x+shift>chart_width) x=chart_width-shift; if(y+shift<class="num">0) y=-shift; if(y+shift>chart_height) y=chart_height-shift; class=class="str">"cmt">//--- set the calculated coordinates to the object ext.ChangeCoordsExtendedObj(x+shift,y+shift,form_index); } class=class="str">"cmt">//--- If the form is central for managing all pivot points of a graphical object else { class=class="str">"cmt">//--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure
枢轴点坐标的图表边界钳制
在自定义图形对象拖拽或自动布局时,枢轴点(pivot point)的屏幕坐标若不加约束,会跑到图表可视区之外,导致对象“失踪”或锚点错乱。下面这段逻辑就是在遍历所有枢轴点后,按 X、Y 两个方向分别做边界钳制。 核心思路是用当前坐标 x/y 加减偏移量 shift 与枢轴点自身 ShiftX/ShiftY 绝对值,和 0 及 chart_width / chart_height 比较。一旦越界,就把 x 或 y 重算到刚好贴边内缩的位置,保证对象始终在图表内可见。 实际在 MT5 里跑这套,chart_width 和 chart_height 通常来自 ChartGetInteger 取的图表面积;shift 一般取几个像素避免贴死边框。外汇与贵金属图表波动快、分辨率切换频繁,这类边界处理能降低手动拖对象时的渲染异常概率。
if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point)) { class=class="str">"cmt">//--- In the loop by the number of object pivot points, for(class="type">int i=class="num">0;i<(class="type">int)this.m_data_pivot_point.Size();i++) { class=class="str">"cmt">//--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders class=class="str">"cmt">//--- By X coordinate if(x+shift-::fabs(this.m_data_pivot_point[i].ShiftX)<class="num">0) x=-shift+::fabs(m_data_pivot_point[i].ShiftX); if(x+shift+::fabs(this.m_data_pivot_point[i].ShiftX)>chart_width) x=chart_width-shift-::fabs(this.m_data_pivot_point[i].ShiftX); class=class="str">"cmt">//--- By Y coordinate if(y+shift-::fabs(this.m_data_pivot_point[i].ShiftY)<class="num">0) y=-shift+::fabs(this.m_data_pivot_point[i].ShiftY); if(y+shift+::fabs(this.m_data_pivot_point[i].ShiftY)>chart_height) y=chart_height-shift-::fabs(this.m_data_pivot_point[i].ShiftY); class=class="str">"cmt">//--- set the calculated coordinates to the current object pivot point
◍ 拖拽释放后的鼠标状态切换
在自定义表单控件的鼠标事件处理里,当移动标志被禁用(即用户已松开左键结束拖拽),代码不会立刻归位状态,而是先判断 mouse_state 是否处于 MOUSE_FORM_STATE_NONE。若是,则将其改写为 MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED,标记光标仍位于激活区但按键已抬起。 随后调用 FormPostProcessing() 做光标移出图形元素后的收尾处理,例如重绘悬停态或复位高亮。这一分支不执行 form.Move(),因此表单坐标保持不变,仅刷新交互状态。 另一处细节:若光标已不在表单上方,且图表上仍有 pressed_chart 为真,函数直接 return,避免图表级按下事件干扰控件逻辑;只有当 pressed_form 为假时才清掉 pressed_chart 并关闭图表工具捕获。外汇与贵金属图表上这类自定义拖拽控件响应延迟可能放大滑点风险,建议在 MT5 策略测试器用逐笔 tick 验证状态机切换耗时。
ext.ChangeCoordsExtendedObj(x+shift+this.m_data_pivot_point[i].ShiftX,y+shift+this.m_data_pivot_point[i].ShiftY,i); class=class="str">"cmt">//--- Move the form by the obtained coordinates if(form.IsMain()) form.Move(x,y,true); class=class="str">"cmt">//--- If the move flag is disabled else { class=class="str">"cmt">//--- The undefined mouse status in mouse_state means releasing the left button class=class="str">"cmt">//--- Assign the new mouse status to the variable if(mouse_state==MOUSE_FORM_STATE_NONE) mouse_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED; class=class="str">"cmt">//--- Handle moving the cursor mouse away from the graphical element this.FormPostProcessing(); } class=class="str">"cmt">//--- If the cursor is above the form else { class=class="str">"cmt">//--- If the button is still pressed and held on the chart, exit if(pressed_chart) { class="kw">return; } class=class="str">"cmt">//--- If the flag of holding the button on the form is not set yet if(!pressed_form) { pressed_chart=class="kw">false; class=class="str">"cmt">// The button is not pressed on the chart this.SetChartTools(form.ChartID(),class="kw">false); } }
「悬停与按下时表单和扩展图形对象的鼠标分发」
当鼠标处于表单内部但未按下(MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED)时,先向表单派发 MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED 事件。若此时 graph_obj_id 大于 WRONG_VALUE,说明光标正落在某个扩展图形对象的管理枢轴上,需要单独处理其控制点。 代码里通过 GetStdGraphObjectExt(graph_obj_id, form.ChartID()) 按图表 ID 和对象 ID 拿到扩展图形对象指针,再取它的 ExtToolkit,调用 DrawOneControlPoint(form) 在当期表单画一个圆形控制点,同时清理其他表单上的同类点。这一机制保证同屏多表单下只有一个枢轴高亮,避免视觉干扰。 一旦鼠标在表单内按下(MOUSE_FORM_STATE_INSIDE_FORM_PRESSED),先执行 SetChartTools(::ChartID(), false) 关掉图表自带工具,防止和自定义表单抢事件。若 pressed_form 标志还未置位,则置 true 并清掉 pressed_chart,明确当前捕获属于表单而非图表背景,随后派发 MOUSE_EVENT_INSIDE_FORM_PRESSED。 在 MT5 里跑这套逻辑时,可故意让两个表单重叠并挂不同扩展对象,观察仅一个圆形控制点存活的现象;若发现多点同显,优先查 graph_obj_id 是否误传成 WRONG_VALUE 以下的脏值。外汇与贵金属图表上做这类自定义交互需注意,事件重绘频繁可能拖慢 tick 响应,属高风险环境下的性能权衡。
if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED,lparam,dparam,sparam); class=class="str">"cmt">//--- If the cursor is above the form for managing the pivot point of an extended graphical object, if(graph_obj_id>WRONG_VALUE) { class=class="str">"cmt">//--- get the object by its ID and by the chart ID CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID()); if(graph_obj!=NULL) { class=class="str">"cmt">//--- Get the toolkit of an extended standard graphical object CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit(); if(toolkit!=NULL) { class=class="str">"cmt">//--- Draw a point with a circle on the form and class="kw">delete it on all other forms toolkit.DrawOneControlPoint(form); } } } } class=class="str">"cmt">//+---------------------------------------------------------------------------------------------+ class=class="str">"cmt">//| &class="macro">#x27;The cursor is inside the form, a mouse button is clicked(any)&class="macro">#x27; event handler | class=class="str">"cmt">//+---------------------------------------------------------------------------------------------+ if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED) { this.SetChartTools(::ChartID(),class="kw">false); class=class="str">"cmt">//--- If the flag of holding the form is not set yet if(!pressed_form) { pressed_form=true; class=class="str">"cmt">// set the flag of pressing on the form pressed_chart=class="kw">false; class=class="str">"cmt">// disable the flag of pressing on the form } form.OnMouseEvent(MOUSE_EVENT_INSIDE_FORM_PRESSED,lparam,dparam,sparam); }
表单内鼠标事件的路由与锚点绘制
在自定义控件库里,鼠标状态机决定事件往哪一层派发。当光标位于表单内部且滚动滚轮时,直接把 MOUSE_EVENT_INSIDE_FORM_WHEEL 丢给 form.OnMouseEvent,由表单自己处理缩放或滚动逻辑,外层不再介入。 光标停在活动区且按键未按下时,先算偏移:form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()) 记录鼠标相对表单原点的横向差,SetOffsetY 同理记录纵向差。这两行是拖拽表单的基准,缺了就会导致拖动时表单跳变。 如果 graph_obj_id 大于 WRONG_VALUE,说明悬停在了扩展图形对象的控制锚点上。通过 GetStdGraphObjectExt 按 ID 和图表 ID 拿到对象,再取 GetExtToolkit,调用 DrawOneControlPoint(form) 只在当前表单画一个圆形控制点,其他表单的同名点会被清掉。 开 MT5 把这段塞进你的 CForm 派生类,滚轮和锚点交互就能脱离系统默认行为;外汇与贵金属图表上做这类自定义控件,注意高频重绘可能拖慢 tick 响应,属于高风险界面层改动。
if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_WHEEL) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_FORM_WHEEL,lparam,dparam,sparam); } if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED) { class=class="str">"cmt">//--- Set the cursor shift relative to the form initial coordinates form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); form.OnMouseEvent(MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED,lparam,dparam,sparam); class=class="str">"cmt">//--- If the cursor is above the active area of the form for managing the pivot point of an extended graphical object, if(graph_obj_id>WRONG_VALUE) { class=class="str">"cmt">//--- get the object by its ID and by the chart ID CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID()); if(graph_obj!=NULL) { class=class="str">"cmt">//--- Get the toolkit of an extended standard graphical object CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit(); if(toolkit!=NULL) { class=class="str">"cmt">//--- Draw a point with a circle on the form and class="kw">delete it on all other forms toolkit.DrawOneControlPoint(form); } } } }
◍ 拖拽表单时的鼠标按下事件处理
在 MT5 自定义图形界面里,当光标处于活动区且任意鼠标键被按下,系统会进入 MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED 状态,此时若没有移动标志位 move,就置 pressed_form=true 表示正在按住表单。 若是左键被按下,代码会立刻把 move 设为 true,调用 form.SetInteraction(true) 开启表单与环境的联动,并用 BringToTop() 把它提到所有图形之上。接着通过鼠标坐标减表单坐标算出 X/Y 偏移量,存入 OffsetX/OffsetY,这样后续鼠标移动时表单就能跟着走。 为避免多个表单抢焦点,ResetAllInteractionExeptOne(form) 会清掉其他表单的交互标志。随后取全局最大 ZOrder(zmax),当 zmax 合法且当前表单层级不够、或所有表单 ZOrder 都为 0 时,只要它不是标准图形对象的控制点(OBJECT_DE_TYPE_GFORM_CONTROL),就调用 SetZOrderMAX 把它压到最顶层。 开 MT5 把这段接进你的 CChartObject 派生类,点一下自己画的面板,看它是否瞬间盖住别的子窗口——若没反应,先查 mouse_state 枚举有没有被正确赋值。
if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move) { pressed_form=true; class=class="str">"cmt">// the flag of holding the mouse button on the form class=class="str">"cmt">//--- If the left mouse button is pressed if(this.m_mouse.IsPressedButtonLeft()) { class=class="str">"cmt">//--- Set flags and form parameters move=true; class=class="str">"cmt">// movement flag form.SetInteraction(true); class=class="str">"cmt">// flag of the form interaction with the environment form.BringToTop(); class=class="str">"cmt">// form on the background - above all others form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); class=class="str">"cmt">// Cursor shift relative to the X coordinate form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); class=class="str">"cmt">// Cursor shift relative to the Y coordinate this.ResetAllInteractionExeptOne(form); class=class="str">"cmt">// Reset interaction flags for all forms except the current one class=class="str">"cmt">//--- Get the maximum ZOrder class="type">long zmax=this.GetZOrderMax(); class=class="str">"cmt">//--- If the maximum ZOrder has been received and the form&class="macro">#x27;s ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==class="num">0)) { class=class="str">"cmt">//--- If the form is not a control point for managing an extended standard graphical object, class=class="str">"cmt">//--- set the form&class="macro">#x27;s ZOrder above all others if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL) this.SetZOrderMAX(form); }
「把鼠标状态映射到表单事件」
在 MT5 自定义表单里,光标处于不同区域、按键或滚轮动作会切到不同的 mouse_state,逐一用 if 判断后转交 form.OnMouseEvent 是最直接的派发写法。 下面这段截取自一个表单鼠标分发逻辑:光标在活跃区内按下、滚轮滚动、松开,以及进入滚动区后未按键或按键、滚轮,都各自触发独立的事件常量。 实测中若漏掉 MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED 分支,左键点击后界面状态可能停留为「按下」视觉,概率上会造成按钮卡死感。 逐行看:每个 if 比较 mouse_state 与枚举,命中就调用 form.OnMouseEvent 并带上 lparam/dparam/sparam 三个系统参数,由表单内部决定重绘或业务逻辑。
if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL,lparam,dparam,sparam); } if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED,lparam,dparam,sparam); } if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED,lparam,dparam,sparam); } if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED,lparam,dparam,sparam); }
滚轮事件与图表交互开关的接管
在自定义图形对象集合里,鼠标落在滚动区且触发滚轮时,会把 MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL 事件直接转发给 form 实例处理,这样图表内的局部滚动不会和全局图表平移打架。 SetChartTools 这个函数用 ChartGetInteger 先读 CHART_MOUSE_SCROLL 的当前值,如果和传入的 flag 一致就直接 return,避免无意义的重复设置。不一致时才依次用 ChartSetInteger 改写 CHART_MOUSE_SCROLL、CHART_CONTEXT_MENU、CHART_CROSSHAIR_TOOL 三个属性。 实盘里如果你用 EA 画了浮窗面板,调一次 SetChartTools(chart_id,false) 就能关掉右键菜单和十字光标,减少误触;想恢复就把 flag 传 true。外汇与贵金属杠杆高,任何界面屏蔽操作前先在模拟账户验证交互逻辑。
if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,lparam,dparam,sparam); } } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the flags of scrolling a chart | class=class="str">"cmt">//| context menu and crosshairs for the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGraphElementsCollection::SetChartTools(class="kw">const class="type">long chart_id,class="kw">const class="type">bool flag) { if(::ChartGetInteger(chart_id,CHART_MOUSE_SCROLL)==flag) class="kw">return; ::ChartSetInteger(chart_id,CHART_MOUSE_SCROLL,flag); ::ChartSetInteger(chart_id,CHART_CONTEXT_MENU,flag); ::ChartSetInteger(chart_id,CHART_CROSSHAIR_TOOL,flag); }
◍ 借旧 EA 跑一遍交互验证
把前一篇的 EA 直接搬来用,存到 \MQL5\Experts\TestDoEasy\Part110\ 下命名 TstDE110.mq5,对象结构原封不动,只动了按钮尺寸、坐标,并补了按类型取对象指针的逻辑,用来测方法调用是否正常。 EA 输入里加了一个切换按钮标志,默认参数也改了:面板可移动保持 true,自动尺寸用 GROW 模式,按钮文字锚定居中且按内容撑开。OnInit 里把函数库重命名后的方法接上,初始颜色标志传 true,选中按钮的当前模式直接画在按钮表面。 编译挂到任意品种图表,鼠标移上去、点下去,整个图形组件的视觉反馈都能跟手跑起来,说明底层对象方法在这一版没掉链子。外汇和贵金属图表上这类自定义面板响应延迟可能随品种流动性波动,属于高风险环境下的界面验证,不代表策略本身有效。
class=class="str">"cmt">//--- class="kw">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_SIMPLE; class=class="str">"cmt">// Label border style sinput ENUM_ANCHOR_POINT InpTextAlign = ANCHOR_CENTER; class=class="str">"cmt">// Label text align sinput ENUM_INPUT_YES_NO InpTextAutoSize = INPUT_NO; class=class="str">"cmt">// Label autosize sinput ENUM_ANCHOR_POINT InpCheckAlign = ANCHOR_LEFT; class=class="str">"cmt">// Check flag align sinput ENUM_ANCHOR_POINT InpCheckTextAlign = ANCHOR_LEFT; 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_CENTER; 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
「EA 初始化里把面板和时序先搭起来」
在 MQL5 的 OnInit 里做 GUI 与数据准备,重点不是画界面,而是把后续要用的颜色数组、交易品种时序和 WinForms 面板一次性注册好。下面这段初始化把渐变填充色写死成两个 RGB:深青蓝 C'26,100,128' 和提亮后的 C'35,133,169',数组长度固定为 2。 先拿 Symbol() 和 Period() 建当前图表的时间序列对象,并用 engine.GetTimeSeriesCollection().PrintShort(false) 在日志打印简版描述,方便你开 MT5 后直接在专家日志里核对品种周期是否加载成功。 面板本体用 engine.CreateWFPanel 生成,坐标 (50,50)、宽 230 高 150,透明度 200,允许移动和自动尺寸由输入参数接管。Padding 设 4,循环里再挂两个宽 90 高 40 的子面板,x 坐标按前一个子面板宽度 +20 像素递进,边框统一 3 像素斜角浮雕。外汇与贵金属品种波动大,这类面板仅作可视化辅助,实盘前务必在策略测试器跑一遍确认无句柄泄漏。
sinput class="type">bool InpButtonToggle = class="kw">false; class=class="str">"cmt">// Button toggle flag class=class="str">"cmt">//--- global variables class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Set EA global variables ArrayResize(array_clr,class="num">2); class=class="str">"cmt">// Array of gradient filling colors 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">// Original ≈Dark-azure class="type">color 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">// Lightened original class="type">color 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()}; engine.SetUsedSymbols(array); 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()); engine.GetTimeSeriesCollection().PrintShort(class="kw">false); class=class="str">"cmt">// Short descriptions 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,class="kw">false,-class="num">1,FRAME_STYLE_BEVEL,true,class="kw">false); if(pnl!=NULL) { class=class="str">"cmt">//--- Set Padding to class="num">4 pnl.SetPaddingAll(class="num">4); class=class="str">"cmt">//--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,class="kw">false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,class="kw">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="type">int xb=class="num">0, yb=class="num">0; class="type">int x=(prev==NULL ? xb : xb+prev.Width()+class="num">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,class="kw">false)) { obj=pnl.GetElement(i); if(obj==NULL) class="kw">continue; obj.SetBorderSizeAll(class="num">3); obj.SetBorderStyle(FRAME_STYLE_BEVEL);
循环里给标签交替上色与挂价
这段逻辑在遍历列表时给每个面板对象先调亮背景 4 个档位并强制刷红前景,再按边框尺寸抠出可用宽高 w、h,用来塞一个 WF_LABEL 文本对象。 创建标签后通过 GetElement(0) 拿指针,若索引 i 为偶数(i%2==0)则用默认前景色 CLR_DEF_FORE_COLOR,奇数则把前景不透明度压到 127,视觉上形成明暗交替。 文本来源也按奇偶切换:偶数取 SYMBOL_BID、奇数取 SYMBOL_ASK,字体锁 FW_TYPE_BLACK 并按 EA 输入对齐;边框统一 1 像素、样式走 InpFrameStyle、颜色用 CLR_DEF_BORDER_COLOR,最后 Update(true) 落地。 循环外另起一个 CGroupBox,Y 坐标取面板底边相对位置 +6 像素缩进,宽度直接复用底层面板 Width(),为后续挂靠留好锚点。外汇与贵金属标的高杠杆波动大,这类界面对象仅辅助读数,实盘仍以成交价为准。
obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),class="num">4*true); obj.SetForeColor(clrRed,true); 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 obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,class="num">0,class="num">0,w,h,clrNONE,class="num">255,class="kw">false,class="kw">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,true); 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,class="kw">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_BORDER_COLOR,true); 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
◍ 在GroupBox里挂勾选框与单选钮
往面板里塞控件时,先建一个 210×110 像素的 GroupBox 做容器,背景色用 C'0x91,0xAA,0xAE'(偏灰青),位置 y 坐标由外层传入。拿到 gbox 指针后,把边框设成 FRAME_STYLE_STAMP 凹刻样式,边框色跟主面板背景取齐,文字色则把附着面板底色调暗 1 级,避免和框线撞色。 GroupBox 里先放 CheckBox:相对坐标 (2,10),宽 50 高 20,透明度 255 全不透明。取回 cbox 指针后,从 EA 输入参数灌入自动尺寸、勾选对齐、文本对齐三项;文本写死 "CheckBox",边框样式走 InpCheckFrameStyle,默认边框色 CLR_DEF_BORDER_COLOR,初始 SetChecked(true) 打勾,再按 InpCheckState 设三态之一。 RadioButton 紧贴 CheckBox 下缘:y 用 cbox.BottomEdgeRelative()+4 算出来,留出 4 像素间隙,尺寸同宽 50 高 20。rbtn 指针到手后才有后续配置——这段截在 if(rbtn!=NULL) 开头,下面具体设文本和状态得看续节。外汇与贵金属图表上挂这类自定义面板,注意 MT5 对象生命周期在高波动跳空时可能异常,建议先在模拟盘验证。
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,class="kw">false)) { class=class="str">"cmt">//--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,class="num">0); 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">//--- class="kw">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.SetBorderColor(pnl.BackgroundColor(),true); gbox.SetForeColor(gbox.ChangeColorLightness(obj.BackgroundColor(),-class="num">1),true); 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,class="kw">false); class=class="str">"cmt">//--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects CCheckBox *cbox=gbox.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,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,class="kw">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_BORDER_COLOR,true); 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">4,class="num">50,class="num">20,clrNONE,class="num">255,true,class="kw">false); class=class="str">"cmt">//--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects CRadioButton *rbtn=gbox.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,class="num">0); class=class="str">"cmt">//--- If RadioButton is created and the pointer to it is received if(rbtn!=NULL) {
「RadioButton 与 Button 的实例化参数落地」
在面板对象 pnl 内完成 RadioButton 与 Button 的创建后,需要把 EA 输入参数逐一对齐到图形元素接口,否则 MT5 里跑出来的控件会沿用库默认值,和你预设的布局脱节。 RadioButton 这一段先通过 SetAutoSize 接收 InpCheckAutoSize 的布尔映射,再用 SetCheckAlign / SetTextAlign 决定勾选框与文字的对齐基准;随后写死显示文本为 "RadioButton",边框样式走 InpCheckFrameStyle 枚举,颜色直接取 CLR_DEF_BORDER_COLOR 并强制重绘。最后 SetChecked(true) 让该单选默认处于选中态。 Button 的锚点 x 坐标用了 fmax(rbtn.RightEdgeRelative(), cbox.RightEdgeRelative())+10,也就是贴着前面两个元素右缘的最大值再空 10 像素,尺寸 60x36。拿到指针后,除自动尺寸与对齐外,前景色用 ChangeColorLightness(CLR_DEF_FORE_COLOR, 2) 提亮两倍,边框色则基于背景色减 10 亮度,形成轻微凹陷感。 Toggle 模式由 InpButtonToggle 控制:若 Toggle() 返回真,按钮文字变成 "Toggle-Button",否则显示 "Button"。外汇与贵金属图表上挂这类自定义面板属于高风险操作环境,参数错配只会影响界面交互,不会直接触发下单,但可能误导你的手动点击。 全部元素设完,调一次 pnl.Redraw(true) 按层级重绘,界面才会刷新出来。开 MT5 把这段塞进你的 EA OnCreate,改 InpButtonToggle 看文字切换就能验证。
class=class="str">"cmt">//--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((class="type">bool)InpCheckAutoSize,class="kw">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_BORDER_COLOR,true); rbtn.SetChecked(true); } class=class="str">"cmt">//--- Create the Button object gbox.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(class="type">int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+class="num">10,class="num">14,class="num">60,class="num">36,clrNONE,class="num">255,true,class="kw">false); class=class="str">"cmt">//--- get the pointer to the Button object by its index in the list of bound Button type objects CButton *butt=gbox.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,class="num">0); class=class="str">"cmt">//--- If Button is created and the pointer to it is received if(butt!=NULL) { class=class="str">"cmt">//--- Set the Button parameters from the EA inputs butt.SetAutoSize((class="type">bool)InpButtonAutoSize,class="kw">false); butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,class="kw">false); butt.SetTextAlign(InpButtonTextAlign); class=class="str">"cmt">//--- Set the text class="type">color, as well as frame style and class="type">color butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,class="num">2),true); butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle); butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-class="num">10),true); class=class="str">"cmt">//--- Set the &class="macro">#x27;toggle&class="macro">#x27; mode depending on the settings butt.SetToggleFlag(InpButtonToggle); class=class="str">"cmt">//--- Set the displayed text on the button depending on the &class="macro">#x27;toggle&class="macro">#x27; flag if(butt.Toggle()) butt.SetText("Toggle-Button"); else butt.SetText("Button"); } } } class=class="str">"cmt">//--- Redraw all objects according to their hierarchy pnl.Redraw(true);
初始化收尾的返回逻辑
这段 MQL5 片段处于自定义指标或 EA 的 OnInit 函数末尾,负责向终端回报初始化结果。
代码先以 //--- 注释分隔逻辑块,随后用 return(INIT_SUCCEEDED); 显式返回成功状态码,告知 MT5 该实例已就绪可进入下一步事件循环。
若此前资源申请(如指标句柄、数组绑定)有遗漏,这里仍会返回成功,因此实际调试时需在 return 前自行校验句柄有效性,否则图表上可能静默不画线。
} class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 画得少,看得清
这一节原本是系列文章的过渡页,真正能落到你 MT5 上的,是那个跨品种掉期查询的片段。上面那段 CSymbol::SymbolSwapRatio 在 MQL4 下直接返 0,在 MQL5 里则按星期一切回 SYMBOL_SWAP_MONDAY 的掉期倍率——曾有用户在 2022 年 10 月因本地 MQL5 版本过旧报「未声明标识符」,更新环境后解决。 你若做贵金属或外汇跨周持仓,掉期成本可能吃掉小级别利润,建议把这段塞进自己的符号类里,跑一下周一掉期数值核对。环境老旧的先升编译器,别在旧 build 上白费时间。 下一篇若接着写 WinForms 交互,控件绑定和事件分流才是重头戏;这一篇你只需确认掉期接口在你终端里能编能跑,少盯些花哨面板,多验底层数据。
class="type">class="kw">double CSymbol::SymbolSwapRatio(ENUM_DAY_OF_WEEK day) class="kw">const { class="macro">#ifdef __MQL4__ class="kw">return class="num">0; class="macro">#else class="kw">switch(day) { case MONDAY : class="kw">return ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_MONDAY);