键盘事件
MQL 程序可通过在 OnChartEvent函数中处理 CHARTEVENT_KEYDOWN 事件来接收来自终端的按键消息。
需要注意的是,仅当图表处于活动状态且获得输入焦点时,才会生成此类事件。
在 Windows 中,焦点指的是当前与用户交互的特定窗口在逻辑和视觉上的选中状态。通常,可通过鼠标点击或特殊键盘快捷键(例如 Tab、Ctrl+Tab)切换焦点,这会使被选中的窗口高亮显示。例如,输入字段将会出现文本光标,列表中的当前行会以另一种颜色标记等等。
在终端中也能观察到类似的视觉效果,尤其是当 Market Watch窗口、Data Window 窗口或专家日志获得焦点时。但图表窗口的情况略有不同。从外观上往往难以判断显示前台显示的图表是否已获得输入焦点。如前所述,可以通过以下方式确保切换焦点:点击所需图表(图表区域,而非窗口标题或边框)或使用快捷键:
- Alt+W:打开图表列表窗口,从中选择图表。
- Ctrl+F6:切换至下一个图表(按窗口列表顺序,通常对应选项卡顺序)。
- Crtl+Shift+F6:切换至上一个图表。
MetaTrader 5 的完整快捷键列表可在 文档中查阅。请注意,部分快捷键组合不符合 Microsoft 的常规建议(例如 F10 用于打开报价窗口,而非激活主菜单)。
CHARTEVENT_KEYDOWN 事件参数包含以下信息:
- lparam - 按下按键的代码
- dparam - 按住按键期间生成的击键次数
- sparam - 描述键盘按键状态的位掩码(已转换为字符串)
位 |
说明 |
---|---|
0-7 |
按键扫描码(取决于硬件和 OEM) |
8 |
扩展键盘键特性 |
9-12 |
供 Windows 系统服务使用(请勿使用) |
13 |
Alt键状态(1 - 按下,0 - 释放),不可用(见下文) |
14 |
按键之前的状态(1 - 按下,0 - 释放) |
15 |
按键状态变化(1 表示释放,0 表示按下) |
Alt键状态不可用,因为会被终端拦截,因此,此位始终为 0。由于该事件的触发机制,第 15 位始终为 0:仅按键按下事件会传递给 MQL 程序,按键释放事件不会传递。
扩展键盘的特性(第 8 位)将进行设置,例如数字小键盘按键(笔记本电脑通常通过 Fn键激活),如 NumLock、ScrollLock、右侧 Ctrl(区别于左侧主Ctrl)。更多相关内容,请参阅 Windows 文档。
首次按下任何非系统按键时,第 14 位为 0。如果持续按住按键,在后续自动生成的重复事件中,该位将为 1。
以下结构有助于确保位说明的准确性。
struct KeyState
|
在 MQL 程序中,可按如下方式使用。
void OnChartEvent(const int id,
|
从实际应用角度来看,使用宏从按键掩码中提取位特性会更为便捷。
#define KEY_SCANCODE(SPARAM) ((uchar)(((ushort)SPARAM) & 0xFF))
|
可以在图表上运行 EventAll.mq5指标(该指标位于 事件相关图表特性 章节),观察按下特定按键时日志中显示的参数值。
请注意,lparam中的代码为虚拟键盘键码。其列表可在 MetaTrader 5 安装目录下的MQL5/Include/VirtualKeys.mqh文件中查看。例如,部分代码如下:
#define VK_SPACE 0x20
|
这些代码被称为虚拟键码,原因在于,相同功能的按键在不同键盘上的位置可能不同,甚至可能通过组合辅助键实现(如笔记本电脑的Fn键)。此外,虚拟性还体现在另一方面,同一按键可能生成不同符号或控制动作。例如,同一按键在不同的语言布局中可能代表不同的字母。此外,每个字母键根据 CapsLock的模式和 Shift 键的状态,可生成大写或小写字母。
为此,MQL5 API 提供了专门的 TranslateKey函数,用于将虚拟键码转换为相应字符。
short TranslateKey(int key)
该函数会根据当前输入语言和控制键的状态,基于传入的虚拟键码返回对应的 Unicode 字符。
如果发生错误,将返回值 -1。如果键码与正确字符不匹配,例如尝试获取 Shift键的字符时,可能会发生错误。
需要注意,MQL 程序除了能获取按键码外,还可通过控制键和模式 检查键盘状态 。顺便提一下,作为参数传递给TerminalInfoInteger函数的 TERMINAL_KEYSTATE_XXX 形式的常量,是基于 1000 + 虚拟键码的原则。例如,TERMINAL_KEYSTATE_UP 为 1038,因为 VK_UP 是 38 (0x26)。
在设计对按键做出反应的算法时,请记住,终端可能会拦截许多组合键,因为它们被保留用于执行特定操作(上文已提供文档链接)。特别要注意,按下空格键会打开一个用于沿时间轴快速导航的字段。MQL5 API 允许你对这种内置键盘处理进行部分控制,并且在必要时可将其禁用。请参阅 鼠标和键盘控制章节。
简单的无缓冲指标EventTranslateKey.mq5可用于演示该函数的功能。在其针对 CHARTEVENT_KEYDOWN 事件的 OnChartEvent处理程序中,将调用 TranslateKey 获取有效Unicode 字符。如果成功,该符号将被添加到图表注释中显示的消息字符串中。按下 Enter,将在文本中插入换行符,按下 Backspace, 则会删除文本末尾的最后一个字符。
#include <VirtualKeys.mqh>
|
你可以尝试输入不同大小写形式和不同语言的字符。
注意事项:该函数返回有符号的short,主要是为了能够返回错误码 -1。“宽字符”(双字节字符)的类型被视为无符号整数,即ushort。如果将接收变量声明为 ushort,使用 -1 进行检查(例如 c!=-1)将引发“符号不匹配”的编译器警告(需要显式类型转换),而另一种检查方式 (c >= 0) 通常是错误的,因为其结果始终等于 true。
为了能够在消息中插入单词间的空格,在 OnInit处理程序中预先禁用了由空格键触发的快速导航功能。
void OnInit()
|
作为使用键盘事件的一个完整示例,考虑以下应用程序任务。终端用户知道,无需打开设置对话框,使用鼠标即可交互式地更改主图表窗口的刻度,只需在价格刻度上按下鼠标按钮,然后在不松开的情况下向上/向下移动。遗憾的是,此方法不适用于子窗口。
子窗口始终自动缩放以适应所有内容,要更改比例,必须打开对话框并手动输入数值。当子窗口中的指标显示“异常值”时,即过大的单个读数,会干扰对其他正常(中等)尺寸数据的分析,此时就需要进行这种操作。此外,有时为了处理更精细的细节,简单地放大图像即可。
为解决此问题并允许用户通过键盘操作调整子窗口比例,我们实现了SubScalermq5指标。该指标没有缓冲区且不显示任何内容。
SubScaler 必须作为子窗口中的首个指标,或更严格地说,必须在你需要控制刻度的工作指标添加到子窗口之前,先将其添加到该子窗口中。要使 SubScaler成为首个指标,应将其放置于图表(主窗口)上,从而创建新的子窗口,之后可在该子窗口中添加从属指标。
在工作指标的设置对话框中,务必启用 Inherit scale选项(位于 Scale 选项卡上)。
当两个指标同时在子窗口中运行时,你可以使用Up/Down箭头键进行放大/缩小操作。如果按住 Shift键,垂直轴上的当前可见数值范围将向上或向下移动。
放大意味着聚焦细节(“镜头拉近”),因此部分数据可能超出窗口显示范围。缩小则表示整体画面变小(“镜头拉远”)。
输入参数设置如下:
- 初始最大值 (Initial maximum) - 初次放置在图表上时数据的上限,默认值为 +1000。
- 初始最小值 (Initial minimum) - 初次放置在图表上时数据的下限,默认值为 -1000。
- 缩放系数 (Scaling factor ) - 按键时缩放比例的变化步长,取值范围为 [0.01 ...0.5],默认值为 0.1。
由于 SubScaler无法预知后续添加到子窗口中的任意第三方指标的有效数值范围,因此必须由用户设置最小值和最大值。
当启动新的终端会话后恢复图表,或加载 tpl 模板时,SubScaler会自动恢复上次保存的缩放状态。
现在让我们分析 SubScaler的具体实现。
上述设置通过对应的输入变量进行设置:
input double FixedMaximum = 1000; // Initial Maximum
|
此外,Disabled变量允许临时禁用特定指标实例的键盘响应,以便在不同子窗口中逐个设置不同的比例。
由于 MQL5 中的输入变量是只读的,我们不得不额外声明一个变量ScaleFactor,用于将输入值校正到允许范围 [0.01 ...0.5] 内。
double ScaleFactor; |
当前子窗口编号 (w) 和其中的指标数量 (n) 存储在全局变量中,这些变量均在 OnInit 处理程序中初始化。
int w = -1, n = -1;
|
在OnChartEvent函数中,我们处理两种类型的事件:图表变更事件和键盘事件。CHARTEVENT_CHART_CHANGE 事件用于追踪子窗口中后续指标(即需要缩放的工作指标)的添加情况。同时,我们会请求子窗口数值的当前范围(CHART_PRICE_MIN 和 CHART_PRICE_MAX),并判断该范围是否为无效范围,即最大值和最小值都等于零的情况。如果出现这种情况,则需应用输入参数中指定的初始限制(FixedMinimum和 FixedMaximum)。
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
当接收到键盘按下事件时,将调用主 Scale函数,该函数不仅接收 lparam,还会通过引用 TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT) 获取 Shift 键的状态。
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
在 Scale函数内部,首先将当前数值范围存入 min 和 max 变量中。
void Scale(const long cmd, const int shift)
|
然后,根据当前是否按下Shift键,执行缩放或平移操作,即向上或向下移动可见数值范围。在这两种情况下,修改操作均以给定步长(乘数)ScaleFactor相对于 min 和max 限值执行,并分别将结果赋值给指标特性 INDICATOR_MINIMUM 和 INDICATOR_MAXIMUM。由于从属指标设置了“继承比例 (Inherit scale)”选项,因此该设置也会成为其工作设置。
if((shift &0x10000000) ==0)// Shift is not pressed - scalechange
|
每次修改后都会调用 ChartRedraw以更新图表。
让我们看看 SubScaler如何与标准成交量指标配合使用(其他任何指标,包括自定义指标,都以相同方式控制)。
SubScaler 指标在两个子窗口中设置的不同刻度
在此处两个子窗口中,两个 SubScaler实例分别为成交量指标应用了不同的垂直刻度。