DoEasy 函数库中的图形(第七十三部分):图形元素的交互窗对象·进阶篇
(2/3)· 从基准对象到交互窗类,解决 MT5 自定义图形无清晰界面的痛点
「鼠标与按键状态在 ushort 里的位布局」
在 MT5 自定义指标或 EA 的图表事件处理里,鼠标按键、Shift/Ctrl 以及光标位置状态被压缩进一个 ushort 变量 m_state_flags 中。理解每一位对应的十进制权重,是后续写 OnChartEvent 交互逻辑的基础。 低位字节(0~7 位)记录输入设备动作:第 0 位左键=1,第 1 位右键=2,第 2 位 Shift=4,第 3 位 Ctrl=8,第 4 位中键=16,第 5、6 位附加鼠标键=32/64,第 7 位滚轮滚动=128。高位字节从 256 起记录光标区域:第 0 位(值 256)表示光标在窗体内,512 表示在活跃区,1024 表示在控件区。 两个设置函数分别负责不同粒度:SetButtonKeyState 接收 id、lparam、dparam、flags 四个参数来写入完整事件上下文;SetButtKeyFlags 仅用 short 类型的 flags 直接覆盖按键状态位。开 MT5 新建脚本,打印 m_state_flags 的按位与结果,就能验证你按下的组合键是否落在预期比特上。
class="type">class="kw">ushort m_state_flags; class=class="str">"cmt">// Status flags class=class="str">"cmt">//--- Set the status of mouse buttons, as well as of Shift and Ctrl keys class="type">void SetButtonKeyState(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">ushort flags); class=class="str">"cmt">//--- Set the mouse buttons and keys status flags class="type">void SetButtKeyFlags(const class="type">short flags); class=class="str">"cmt">//--- Data location in the class="type">class="kw">ushort value of the button status class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// bit | byte | state | dec | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">0 | class="num">0 | left mouse button | class="num">1 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">1 | class="num">0 | right mouse button | class="num">2 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">2 | class="num">0 | SHIFT key | class="num">4 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">3 | class="num">0 | CTRL key | class="num">8 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">4 | class="num">0 | middle mouse button | class="num">16 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">5 | class="num">0 | class="num">1 add. mouse button | class="num">32 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">6 | class="num">0 | class="num">2 add. mouse button | class="num">64 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">7 | class="num">0 | scrolling the wheel | class="num">128 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">0 | class="num">1 | cursor inside the form | class="num">256 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">1 | class="num">1 | cursor inside active area | class="num">512 | class=class="str">"cmt">//----------------------------------------------------------------- class=class="str">"cmt">// class="num">2 | class="num">1 | cursor in the control area | class="num">1024 |
◍ 鼠标状态位与坐标接口的位掩码
鼠标在图表不同区域的停留状态,是用独立位标记分开记录的。滚动区对应 2048,左缘 4096,下缘 8192,右缘 16384,上缘 32768,这些常量直接以 2 的幂递增,方便用按位或一次性合并判断。 类里暴露的 GetMouseFlags 返回 ushort 类型的 m_state_flags,你把上面那些边缘常量跟它做位与,就能知道光标当前贴着哪条边。比如 (flags & 32768) 非零,说明光标在顶部边缘,这种写法比一堆 if 判断更省。 ResetAll 负责清空按钮和按键状态;SetWindowNum 和 SetChartID 分别写入子窗口序号和图表 ID,二者都是单行内联赋值。CoordX、CoordY 给出光标像素坐标,DeltaWheel 返回滚轮增量,都是 const 方法,调用时不改内部状态。 别把正态当圣经:这些位值只在 ChartEvent 回调触发后才更新,若你的 EA 在主循环里轮询却没挂事件钩子,读到的坐标可能是上一帧的旧值。开 MT5 建个脚本打印 GetMouseFlags,移动鼠标到四边就能验证掩码是否按预期翻转。
class="kw">public: class=class="str">"cmt">//--- Reset the states of all buttons and keys class="type">void ResetAll(class="type">void); class=class="str">"cmt">//--- Set(class="num">1) the subwindow index and(class="num">2) the chart ID class="type">void SetWindowNum(const class="type">int wnd_num) { this.m_window_num=wnd_num; } class="type">void SetChartID(const class="type">long id) { this.m_chart_id=id; } class=class="str">"cmt">//--- Return the variable with the mouse status flags class="type">class="kw">ushort GetMouseFlags(class="type">void) { class="kw">return this.m_state_flags; } class=class="str">"cmt">//--- Return(class="num">1-class="num">2) the cursor coordinates, (class="num">3) scroll wheel value, (class="num">4) status of the mouse buttons and Shift/Ctrl keys class="type">int CoordX(class="type">void) const { class="kw">return this.m_coord_x; } class="type">int CoordY(class="type">void) const { class="kw">return this.m_coord_y; } class="type">int DeltaWheel(class="type">void) const { class="kw">return this.m_delta_wheel; } ENUM_MOUSE_BUTT_KEY_STATE ButtKeyState(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string flags);
用状态位掩码区分鼠标键盘组合
在 MT5 自定义指标或 EA 的图表事件类里,鼠标按键与修饰键被压进同一个 m_state_flags 整型字段,靠等值比较来判定当前交互状态。左键对应 1、右键 2、中键 16,侧键 X1/X2 则是 32 与 64,这几个值彼此独占,不会叠加。 Shift 键记 4、Ctrl 记 8,两者同按就是 12(4+8),滚轮单独触发为 128。组合态直接做加法:左键加滚轮得 129,左键加 Shift 得 5,左键加 Ctrl 得 9,左键加 Ctrl+Shift 得 13。 下面这段是类方法原样,逐行看就是返回对应标志位是否相等: bool IsPressedButtonLeft(void) const { return this.m_state_flags==1; } bool IsPressedButtonRight(void) const { return this.m_state_flags==2; } bool IsPressedButtonMiddle(void) const { return this.m_state_flags==16; } bool IsPressedButtonX1(void) const { return this.m_state_flags==32; } bool IsPressedButtonX2(void) const { return this.m_state_flags==64; } bool IsPressedKeyShift(void) const { return this.m_state_flags==4; } bool IsPressedKeyCtrl(void) const { return this.m_state_flags==8; } bool IsPressedKeyCtrlShift(void) const { return this.m_state_flags==12; } bool IsWheel(void) const { return this.m_state_flags==128; } bool IsPressedButtonLeftWheel(void) const { return this.m_state_flags==129; } bool IsPressedButtonLeftShift(void) const { return this.m_state_flags==5; } bool IsPressedButtonLeftCtrl(void) const { return this.m_state_flags==9; } bool IsPressedButtonLeftCtrlShift(void) const { return this.m_state_flags==13; } 实盘写面板交互时,别用位与运算去猜,这些方法是严格等值,所以 m_state_flags 同时只会落在一个预设数字上。外汇与贵金属杠杆高,这类鼠标事件逻辑只管界面响应,和仓位风险无关,但误判组合键可能让脚本乱画对象。
class="type">bool IsPressedButtonLeft(class="type">void) const { class="kw">return this.m_state_flags==class="num">1; } class="type">bool IsPressedButtonRight(class="type">void) const { class="kw">return this.m_state_flags==class="num">2; } class="type">bool IsPressedButtonMiddle(class="type">void) const { class="kw">return this.m_state_flags==class="num">16; } class="type">bool IsPressedButtonX1(class="type">void) const { class="kw">return this.m_state_flags==class="num">32; } class="type">bool IsPressedButtonX2(class="type">void) const { class="kw">return this.m_state_flags==class="num">64; } class="type">bool IsPressedKeyShift(class="type">void) const { class="kw">return this.m_state_flags==class="num">4; } class="type">bool IsPressedKeyCtrl(class="type">void) const { class="kw">return this.m_state_flags==class="num">8; } class="type">bool IsPressedKeyCtrlShift(class="type">void) const { class="kw">return this.m_state_flags==class="num">12; } class="type">bool IsWheel(class="type">void) const { class="kw">return this.m_state_flags==class="num">128; } class="type">bool IsPressedButtonLeftWheel(class="type">void) const { class="kw">return this.m_state_flags==class="num">129; } class="type">bool IsPressedButtonLeftShift(class="type">void) const { class="kw">return this.m_state_flags==class="num">5; } class="type">bool IsPressedButtonLeftCtrl(class="type">void) const { class="kw">return this.m_state_flags==class="num">9; } class="type">bool IsPressedButtonLeftCtrlShift(class="type">void) const { class="kw">return this.m_state_flags==class="num">13; }
「鼠标组合键状态靠位标志判定」
在 MT5 的自定义鼠标状态类里,右键、中键配合 Shift / Ctrl 的按下状态,全部映射成 m_state_flags 的等值比较。比如右键加 Shift 对应标志值 6,右键加 Ctrl 是 10,右键加 Ctrl+Shift 则是 14;中键那一组从 20 到 28 递增,中键加滚轮单独跳到 144。 这些布尔方法都是 const 成员,直接 return this.m_state_flags==数值,没有额外运算。你在 EA 里想区分「中键+Ctrl」和「右键+CtrlShift」这类复合手势,只需比对返回真值,不用自己算位掩码。 构造函数把 m_delta_wheel、m_coord_x、m_coord_y、m_window_num 全初始化为 0,随后调 this.ResetAll() 清掉所有按键态;析构函数为空。ResetAll 内部第一件事就是 this.m_delta_wheel = 0,避免上一帧滚轮增量残留到新一笔行情判定里。 开 MT5 把这段塞进你的鼠标管理类,改一处标志值就能验证某组合键是否触发——外汇和贵金属图表上做手势下单前,先确认这类状态读取不会因残留标志误判,相关品种波动大、杠杆高,误触可能直接吃滑点。
class="type">bool IsPressedButtonRightWheel(class="type">void) const { class="kw">return this.m_state_flags==class="num">130; } class="type">bool IsPressedButtonRightShift(class="type">void) const { class="kw">return this.m_state_flags==class="num">6; } class="type">bool IsPressedButtonRightCtrl(class="type">void) const { class="kw">return this.m_state_flags==class="num">10; } class="type">bool IsPressedButtonRightCtrlShift(class="type">void) const { class="kw">return this.m_state_flags==class="num">14; } class=class="str">"cmt">//--- Return the flag indicating the status of the middle mouse button and(class="num">1) the mouse wheel, (class="num">2) Shift, (class="num">3) Ctrl, (class="num">4) Ctrl+Shift class="type">bool IsPressedButtonMiddleWheel(class="type">void) const { class="kw">return this.m_state_flags==class="num">144; } class="type">bool IsPressedButtonMiddleShift(class="type">void) const { class="kw">return this.m_state_flags==class="num">20; } class="type">bool IsPressedButtonMiddleCtrl(class="type">void) const { class="kw">return this.m_state_flags==class="num">24; } class="type">bool IsPressedButtonMiddleCtrlShift(class="type">void) const { class="kw">return this.m_state_flags==class="num">28; } class=class="str">"cmt">//--- Constructor/destructor CMouseState(); ~CMouseState(); }; CMouseState::CMouseState() : m_delta_wheel(class="num">0),m_coord_x(class="num">0),m_coord_y(class="num">0),m_window_num(class="num">0) { this.ResetAll(); } CMouseState::~CMouseState() { } class="type">void CMouseState::ResetAll(class="type">void) { this.m_delta_wheel = class="num">0;
◍ 鼠标事件里怎么拆出坐标和按键位
在 MT5 自定义指标或 EA 里接管图表交互,第一步是把 OnChartEvent 传进来的 id、lparam、dparam、flags 翻译成可用的鼠标状态。下面这段 CMouseState::SetButtonKeyState 的逻辑,就是先清掉旧状态再按事件类型填值。 左键点击(CHARTEVENT_CLICK / CHARTEVENT_OBJECT_CLICK)时,lparam 和 dparam 直接是 x、y 像素坐标,状态位 0x0001 置 1 表示有点击发生。注意每次进来都先调用 ResetAll() 把 m_state_flags 归零,避免上一次滚轮或移动的残留位干扰判断。 滚轮事件 CHARTEVENT_MOUSE_WHEEL 的 lparam 是打包数据:(short)lparam 取 x 坐标,(short)(lparam>>16) 取 y 坐标,dparam 就是滚动量,最小步长 ±120。按键和 Shift/Ctrl 状态被压在 (short)(lparam>>32) 里,交给 SetButtKeyFlags 解析;同时用 m_state_flags &=0xFF7F 清掉第 7 位旧值,再 |=0x0080 标滚轮事件。 光标移动 CHARTEVENT_MOUSE_MOVE 最简单:坐标来自 lparam/dparam,按键状态直接用入参 flags 调 SetButtKeyFlags。外汇与贵金属图表上做这种交互工具波动大、滑点多,回测和实盘都要先开 MT5 用 Print 把这几类事件的原始参数打出来核对。
class="type">void CMouseState::SetButtonKeyState(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">ushort flags) { class=class="str">"cmt">//--- Reset the values of all mouse status bits this.ResetAll(); class=class="str">"cmt">//--- If a chart or an object is left-clicked if(id==CHARTEVENT_CLICK || id==CHARTEVENT_OBJECT_CLICK) { class=class="str">"cmt">//--- Write the appropriate chart coordinates and set the bit of class="num">0 this.m_coord_x=(class="type">int)lparam; this.m_coord_y=(class="type">int)dparam; this.m_state_flags |=(0x0001); } class=class="str">"cmt">//--- otherwise else { class=class="str">"cmt">//--- in case of a mouse wheel scrolling if(id==CHARTEVENT_MOUSE_WHEEL) { class=class="str">"cmt">//--- get the cursor coordinates and the total scroll value(the minimum of +class="num">120 or -class="num">120) this.m_coord_x=(class="type">int)(class="type">short)lparam; this.m_coord_y=(class="type">int)(class="type">short)(lparam>>class="num">16); this.m_delta_wheel=(class="type">int)dparam; class=class="str">"cmt">//--- Call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys this.SetButtKeyFlags((class="type">short)(lparam>>class="num">32)); class=class="str">"cmt">//--- and set the bit of class="num">8 this.m_state_flags &=0xFF7F; this.m_state_flags |=(0x0001<<class="num">7); } class=class="str">"cmt">//--- If this is a cursor movement, write its coordinates and class=class="str">"cmt">//--- call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys if(id==CHARTEVENT_MOUSE_MOVE) { this.m_coord_x=(class="type">int)lparam; this.m_coord_y=(class="type">int)dparam; this.SetButtKeyFlags(flags); } } } class="type">void CMouseState::SetButtKeyFlags(const class="type">short flags)
把按键位压进状态字
鼠标与修饰键的状态在 MT5 事件里是以位掩码形式一次性送进来的,上面这段把 7 种输入源逐一拆位写进类内成员 m_state_flags。 掩码 0x0001 到 0x0040 分别对应左键、右键、SHIFT、CTRL、中键、第一附加键、第二附加键;只要对应位非 0,就把 0x0001 左移 0~6 位后做按位或,相当于把第 n 个输入源点亮到第 n 个标志位。 ButtKeyState 只是个薄封装:先调 SetButtonKeyState 把原始 flags 转成 ushort 写进状态字,再直接把 m_state_flags 强转为枚举 ENUM_MOUSE_BUTT_KEY_STATE 返回。 在 EA 里若想区分「按住 CTRL 拖拽画线」和「裸左键点击」,直接读返回枚举的位即可,不用再解析 lparam。外汇与贵金属杠杆高,这类交互逻辑只影响下单辅助,不改变仓位风险。
if((flags & 0x0001)!=class="num">0) this.m_state_flags |=(0x0001<<class="num">0); class=class="str">"cmt">//--- Right mouse button status if((flags & 0x0002)!=class="num">0) this.m_state_flags |=(0x0001<<class="num">1); class=class="str">"cmt">//--- SHIFT status if((flags & 0x0004)!=class="num">0) this.m_state_flags |=(0x0001<<class="num">2); class=class="str">"cmt">//--- CTRL status if((flags & 0x0008)!=class="num">0) this.m_state_flags |=(0x0001<<class="num">3); class=class="str">"cmt">//--- Middle mouse button status if((flags & 0x0010)!=class="num">0) this.m_state_flags |=(0x0001<<class="num">4); class=class="str">"cmt">//--- The first additional mouse button status if((flags & 0x0020)!=class="num">0) this.m_state_flags |=(0x0001<<class="num">5); class=class="str">"cmt">//--- The second additional mouse button status if((flags & 0x0040)!=class="num">0) this.m_state_flags |=(0x0001<<class="num">6); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the mouse buttons and Shift/Ctrl keys states | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_MOUSE_BUTT_KEY_STATE CMouseState::ButtKeyState(const class="type">int id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string flags) { this.SetButtonKeyState(id,lparam,dparam,(class="type">class="kw">ushort)flags); class="kw">return (ENUM_MOUSE_BUTT_KEY_STATE)this.m_state_flags; } class=class="str">"cmt">//+------------------------------------------------------------------+