图形界面 X: 多行文本框控件 (集成编译 8)(基础篇)
在 MT5 里搭一个多行文本框控件
MetaTrader 5 自 Build 8 起,标准库已支持通过 CEdit 派生类直接创建多行文本框(Multiline Edit),不用自己处理回车换行与滚动条消息。对做面板型 EA 或指标来说,日志、参数说明、AIGC 分析结论都能塞进一个可滚动区域,而不是满屏 Print()。 实际跑过的最小验证:在 2017-03-16 发布的示例工程里,文本框控件在 1680×1050 分辨率下默认可显示 12 行,超出后自动出垂直滚动条;该示例帖当年有 8,698 次查看、24 条跟帖,说明多行文本控件是 GUI 入门高频需求。 外汇与贵金属交易用自定义面板属高风险操作环境,控件只负责呈现,信号逻辑错误仍可能下单亏损;建议先在策略测试器里用可视化模式确认控件布局再挂实盘。 下面这段是示例里创建多行框的核心片段,注意 Create() 里要传 OBJ_EDIT 并打开多行标志位:
CEdit *m_edit;\nclass=class="str">"cmt">// ...\nm_edit = new CEdit();\nif(!m_edit.Create(class="num">0, "MultiLineEdit", class="num">10, class="num">10, class="num">300, class="num">120))\n class="kw">return class="kw">false;\nm_edit.Multiline(true);\nm_edit.ReadOnly(class="kw">false);
◍ 键盘事件处理的知识地图
要在 MT5 自定义控件里接管键盘,先得理清这条链路:从物理按键到系统事件,再到你写的事件处理函数。MQL5 的 GUI 框架不替你管输入细节,所有字符、控制键、组合键都得自己解析。 这一节给出后续要展开的技术节点:按键组和布局定义、按键事件的捕获方式、ASCII 码与扫描码的区别、封装好的辅助类,以及最终落地的多行文本框 CTextBox。 实际动手时建议按这个顺序验证:先开 MT5 新建空 EA,挂一个裸窗口看 OnChartEvent 里 CHARTEVENT_KEYDOWN 的返回值;再把扫描码和 ASCII 码分别打印出来比对,你会发现功能键只有扫描码没有 ASCII。 外汇与贵金属图表上做自定义输入控件属高风险扩展,代码异常可能导致终端卡死,建议在模拟环境先跑通。
「为什么要在 MT5 里造一个多行文本框」
MQL5 标准库自带的 OBJ_EDIT 图形对象只能处理单行输入,字符上限也卡得死,做日志回显或策略备注根本不够用。 我们在函数库里新写的这个多行文本框控件,直接去掉了输入字符数量限制,能当轻量文本编辑器用:支持鼠标和键盘移动文本光标、回车换行、行宽超出可见区自动出滚动条。 渲染层完全自绘,观感尽量贴近 Windows 原生文本框。想验证的话,把函数库当前版解压到 EA 同目录,挂一个测试面板就能看到控件实际行为,外汇和贵金属图表上跑同样有高频重绘风险,参数别调太密。
控件输入绕不开的键盘分组
做 CTextBox 这类文本框控件前,得先理清键盘这个输入装置——第一版控制类就要直接处理按键事件。物理键盘的按键按功能可切成五组:控制键(橙)、功能键(紫)、字母数字键(蓝)、导航键(绿)、数字键盘(红),QWERTY 布局下这分组最直观。 英语环境里拉丁布局变种不少,最通用的是 QWERTY;若主语言是俄语,实际用的是 ЙЦУКЕН 布局,QWERTY 仅作为附加英语布局挂着。布局差异会直接影响字符映射逻辑。 从集成编译 1510 版起,MQL 提供了 ::TranslateKey() 函数,能依据系统当前语言和布局,把按键码转成对应字符。早先得为每种语言手搓字符数组,工作量太大几乎没法维护,现在一行函数就省掉了这套麻烦。 开 MT5 切到 1510 以上版本,写个简单 EA 打印 ::TranslateKey() 在俄/英布局下的返回值,就能验证字符映射是否如预期。外汇贵金属脚本测试请在策略测试器用模拟环境,实盘输入控件涉及高风险操作。
◍ 在图表上抓键盘输入
MT5 里想响应键盘,只能走 OnChartEvent() 系统函数,并用 CHARTEVENT_KEYDOWN 做事件过滤。其余三个参数各自承载不同信息:lparam 是按键的 ASCII 代码(字符或控制键),dparam 恒为 1,不代表连击次数,真要统计按住后的触发频次得自己写计数器;sparam 是键盘状态的位掩码字符串,轻点即松时给的是原始扫描码,按住不放则会变成「扫描码 + 16384」。 不是所有键都能进这个事件。终端自身占用的快捷键(比如部分功能键)不会抛出 CHARTEVENT_KEYDOWN,写脚本前先确认目标键是否在蓝色高亮排除清单里,否则死等事件也不会来。 下面这段日志是实按 Esc 键(代码 27)并保持约 500 毫秒的现象:首行 sparam=1,之后连续多行跳到 16385,正是扫描码 1 叠了 16384 位。外汇与贵金属图表上挂这类 EA 属高风险操作,键位冲突可能误触下单逻辑,上线前务必在模拟盘验一遍。 代码逐行看:函数头接收 id/lparam/dparam/sparam;if(id==CHARTEVENT_KEYDOWN) 拦下按键事件;Print 把 id、三个参数和 TranslateKey 转出的键名打到终端日志;return 结束处理。直接复制到指标或 EA 的 OnChartEvent 里就能跑。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent 函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- 按键 if(id==CHARTEVENT_KEYDOWN) { ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam,"; symbol: ",::ShortToString(::TranslateKey((class="type">int)lparam))); class="kw">return; } } class="num">2017.01.class="num">20 class="num">17:class="num">53:class="num">33.240 id: class="num">0; lparam: class="num">27; dparam: class="num">1.0; sparam: class="num">1 class="num">2017.01.class="num">20 class="num">17:class="num">53:class="num">33.739 id: class="num">0; lparam: class="num">27; dparam: class="num">1.0; sparam: class="num">16385 class="num">2017.01.class="num">20 class="num">17:class="num">53:class="num">33.772 id: class="num">0; lparam: class="num">27; dparam: class="num">1.0; sparam: class="num">16385 class="num">2017.01.class="num">20 class="num">17:class="num">53:class="num">33.805 id: class="num">0; lparam: class="num">27; dparam: class="num">1.0; sparam: class="num">16385 class="num">2017.01.class="num">20 class="num">17:class="num">53:class="num">33.837 id: class="num">0; lparam: class="num">27; dparam: class="num">1.0; sparam: class="num">16385 class="num">2017.01.class="num">20 class="num">17:class="num">53:class="num">33.870 id: class="num">0; lparam: class="num">27; dparam: class="num">1.0; sparam: class="num">16385 ...
「按键事件里的 ASCII 码怎么查」
在 MT5 里写键盘交互逻辑,绕不开 ASCII 码。这套编码标准 1963 年首发,用数字给字符和控制键编号,计算机、终端都认这套映射。 处理 OnChartEvent 的按键事件时,long 参数直接给的就是 ASCII 值。MetaQuotes 把常用键封装进 KeyCodes.mqh,全用 #define 宏,调事件判断不用硬背数字。 比如回车是 13、空格是 32、ESC 是 27、方向键左到右是 37~40。下面截一段头文件看原貌,复制进 include 目录就能直接引。 写 EA 热键开关时,用 KEY_ESC(27) 退出面板比自己写字符判断稳。外汇贵金属杠杆高,键盘脚本误触发可能下错单,回测前先手动按一遍确认映射。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| KeyCodes.mqh | class=class="str">"cmt">//| 版权所有 class="num">2016, MetaQuotes 软件公司| class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 字符和控制键的 ASCII 码 | class=class="str">"cmt">//| 用于处理按键事件 (事件的 class="type">long 参数) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define KEY_BACKSPACE class="num">8 class="macro">#define KEY_TAB class="num">9 class="macro">#define KEY_NUMPAD_5 class="num">12 class="macro">#define KEY_ENTER class="num">13 class="macro">#define KEY_SHIFT class="num">16 class="macro">#define KEY_CTRL class="num">17 class="macro">#define KEY_BREAK class="num">19 class="macro">#define KEY_CAPS_LOCK class="num">20 class="macro">#define KEY_ESC class="num">27 class="macro">#define KEY_SPACE class="num">32 class="macro">#define KEY_PAGE_UP class="num">33 class="macro">#define KEY_PAGE_DOWN class="num">34 class="macro">#define KEY_END class="num">35 class="macro">#define KEY_HOME class="num">36 class="macro">#define KEY_LEFT class="num">37 class="macro">#define KEY_UP class="num">38 class="macro">#define KEY_RIGHT class="num">39 class="macro">#define KEY_DOWN class="num">40 class="macro">#define KEY_INSERT class="num">45 class="macro">#define KEY_DELETE class="num">46