DoEasy. 控件(第三十部分):动画态滚动条控件(基础篇)
◍ 给滚动条加上动画态捕捉区
这一节把库里的滚动条控件往前推了一步:原本只能静态响应的滑块,现在带上了动画态的捕获区域。捕获区负责接收鼠标命中,动画态意味着滑块在拖拽和悬停时有连续的位置插值,而不是瞬间跳变。 具体落在滚动条控件捕获区域类里,类内部维护捕获矩形的坐标与当前动画进度,每帧根据鼠标偏移重算目标位置并做缓动。对交易者来说,这意味着你在 MT5 自定义面板里拖动量价刻度条时,手感更接近原生终端。 原文示例发布于 2023 年 5 月 2 日,截至归档页面显示阅读量 795、评论 0,属于低讨论但已落地的中段教程。外汇与贵金属自定义面板开发属高风险环境,任何控件改动都应在模拟账户先验证。
「从滚动条按钮到捕获区的对象派生」
上一篇文章里滚动条(ScrollBar)辅助控件已经起了头,这一节要把控件元素和鼠标真正连起来。WinForms 风格的滚动条由两个部分组成:两端的箭头按钮,以及中间的捕获区域(thumb)。箭头按钮已有独立辅助类,捕获区域则准备用简单按钮派生出一个单独类来实现。 关键点在于:捕获区域必须从按钮对象派生,而不是另起炉灶。这样做有两个实在好处——它直接继承按钮已有的属性和事件处理框架,同时因为派生出了独立类型,滚动条滑块的事件就能被准确区分和响应,不会和普通的按钮点击混淆。 为了不在后面反复补基础,这一节先把鼠标状态枚举和事件列表扩好。扩展后,鼠标移动、按下、抬起的细分状态都能被控件识别,后续做滑块拖拽和区域点击时,逻辑层就不用再回头补这些脏活。 对 MT5 自定义控件有兴趣的,可以现在打开 MetaEditor,新建一个从 CButton 派生的类,只重写 OnEvent 里和鼠标捕获相关的分支,验证一下类型独立后事件是否不再串台。外汇与贵金属 GUI 自动化涉及实时行情交互,属于高风险开发场景,任何控件逻辑错误都可能误导下单判断。
滚动条控件的库类改造细节
在 DoEasy 库里给滚动条补了一套底层定义:滚动区域默认宽度设为 11 像素,扣掉四周各 1 像素边框后,活动区剩 9 像素。这个奇数很关键——箭头三角形能以中心轴均匀绘制,不会左右偏一格。 鼠标状态枚举扩到了 8 个方向resize加右侧/底部滚动条悬停,图形元素整数属性从 138 增到 140。箭头画法改成传「底心坐标+相对大小」:大小 1 实际占 1+1+1=3 像素,大小 3 占 3+1+3=7 像素,下限锁 1 但没有上限,调用方自己控尺度。 Form.mqh 里加了区域坐标公开方法,光标落到哪块就置哪 bit 标志;ushort 码位不够时做标志组合。原先在 WinFormBase 调滚动条坐标的代码整段搬进容器类——只有容器才管附着对象溢出后的滚动适配。 ArrowButton 类删了多余的 m_arrow_color 私有变量,直接复用 ForeColor() 控色;四个方向箭头按钮构造里写死绘制大小 3。此前基对象无上层基对象时会踩无效指针崩溃,发送事件消息方法里改成了先探一层再取类型,崩点没了。
class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_TRACK_BACK_COLOR(C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control background class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_TRACK_BORDER_COLOR(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control frame class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_TRACK_FORE_COLOR(C&class="macro">#x27;0x60,0x60,0x60&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control text class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_TRACK_FORE_MOUSE_DOWN(C&class="macro">#x27;0x00,0x00,0x00&class="macro">#x27;)class=class="str">"cmt">// Color of ScrollBar control text when clicking on the control class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_TRACK_FORE_MOUSE_OVER(C&class="macro">#x27;0x00,0x00,0x00&class="macro">#x27;)class=class="str">"cmt">// Color of ScrollBar control text when hovering the mouse over the control class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_THUMB_COLOR(C&class="macro">#x27;0xCD,0xCD,0xCD&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control capture area class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_THUMB_BORDER_COLOR(C&class="macro">#x27;0xCD,0xCD,0xCD&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control capture area frame class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_THUMB_MOUSE_DOWN(C&class="macro">#x27;0x60,0x60,0x60&class="macro">#x27;) class=class="str">"cmt">// Color of ScrollBar control capture area when clicking on the control
◍ ScrollBar 配色与窗体热区常量
做 MT5 自定义控件时,滚动条(ScrollBar)的视觉状态是最容易被忽略的细节。下面这组宏把滑块、按钮在常态、悬停、按下三种鼠标状态下的颜色全部钉死,改一处就能换肤。 滑块悬停捕获区用 0xA6A6A6,按下时文字转白(0xFFFFFF),悬停文字转黑(0x000000);按钮底色常态 0xF0F0F0,悬停 0xDA DA DA,按下 0x60 60 60。直接抄进 EA 面板工程,界面就不会出现系统默认灰的违和感。 两个尺寸常量值得记:默认滚动条宽度 DEF_CONTROL_SCROLL_BAR_WIDTH 是 11 像素,窗体四角拖拽热区 DEF_CONTROL_CORNER_AREA 占 4 像素。ListBox 列间距 X 为 1、行间距 Y 为 0,密排时可以改成 2 和 1 试试。 鼠标相对窗体状态的枚举 ENUM_MOUSE_FORM_STATE 从 NONE=0 起,区分了窗体外未按键、窗体外按键、窗体外滚轮三种情况,这是做拖拽改大小逻辑的判断基底。
class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_THUMB_MOUSE_OVER(C&class="macro">#x27;0xA6,0xA6,0xA6&class="macro">#x27;) class=class="str">"cmt">// Color of ScrollBar control capture area when hovering over the control class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_COLOR(C&class="macro">#x27;0x60,0x60,0x60&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control capture area text class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_MOUSE_DOWN(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)class=class="str">"cmt">// Color of ScrollBar control capture area text when clicking on the control class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_MOUSE_OVER(C&class="macro">#x27;0x00,0x00,0x00&class="macro">#x27;)class=class="str">"cmt">// Color of ScrollBar control capture area text when hovering the mouse over the control class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_BUTT_COLOR(C&class="macro">#x27;0xF0,0xF0,0xF0&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control button class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_BUTT_BORDER_COLOR(C&class="macro">#x27;0xCD,0xCD,0xCD&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control button frame class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_DOWN(C&class="macro">#x27;0x60,0x60,0x60&class="macro">#x27;) class=class="str">"cmt">// Color of ScrollBar control buttons when clicking on the control class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_OVER(C&class="macro">#x27;0xDA,0xDA,0xDA&class="macro">#x27;) class=class="str">"cmt">// Color of ScrollBar control buttons when hovering the mouse over the control class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_COLOR(C&class="macro">#x27;0x60,0x60,0x60&class="macro">#x27;) class=class="str">"cmt">// ScrollBar control button text class="type">color class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_DOWN(C&class="macro">#x27;0xFF,0xFF,0xFF&class="macro">#x27;)class=class="str">"cmt">// Color of ScrollBar control button text when clicking on the control class="macro">#define CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_OVER(C&class="macro">#x27;0x00,0x00,0x00&class="macro">#x27;)class=class="str">"cmt">// Color of ScrollBar control button text when hovering the mouse over the control class="macro">#define DEF_CONTROL_SCROLL_BAR_WIDTH(class="num">11) class=class="str">"cmt">// Default ScrollBar control width class="macro">#define DEF_CONTROL_CORNER_AREA(class="num">4) class=class="str">"cmt">// Number of pixels defining the corner area to resize class="macro">#define DEF_CONTROL_LIST_MARGIN_X(class="num">1) class=class="str">"cmt">// Gap between columns in ListBox controls class="macro">#define DEF_CONTROL_LIST_MARGIN_Y(class="num">0) class=class="str">"cmt">// Gap between rows in ListBox controls class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The list of possible mouse states relative to the form | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_MOUSE_FORM_STATE { MOUSE_FORM_STATE_NONE = class="num">0, class=class="str">"cmt">// Undefined state class=class="str">"cmt">//--- Outside the form MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED, class=class="str">"cmt">// The cursor is outside the form, the mouse buttons are not clicked MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED, class=class="str">"cmt">// The cursor is outside the form, the mouse button(any) is clicked MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL, class=class="str">"cmt">// The cursor is outside the form, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the form
「鼠标在自定义窗体各区域的状态枚举」
给 MT5 写自定义面板时,要区分鼠标落在窗体哪个位置、处于什么动作,靠的是一组状态枚举。下面这段把光标在窗体内部、标题活动区、滚动区、缩放区的细分状态列全了,方便在 OnChartEvent 里做分支。 光标在窗体内部(非标题)分三种:未按键、任意键按下、滚轮滚动。标题活动区更复杂,除了上述三种还多了左键释放态 MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED,拖拽标题栏时靠它判定落点。 右侧与底部滚动区各自有 NOT_PRESSED / PRESSED / WHEEL 三态,共 6 个枚举;顶部与底部、左、右、左上等缩放区同理,每个方向也配齐三态。实际写 GUI 时,用这些枚举做 switch 比手动算坐标边界更稳,能少写约 30% 的命中判断代码。
MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED, class=class="str">"cmt">// The cursor is inside the form, no mouse buttons are clicked MOUSE_FORM_STATE_INSIDE_FORM_PRESSED, class=class="str">"cmt">// The cursor is inside the form, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_FORM_WHEEL, class=class="str">"cmt">// The cursor is inside the form, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window header area MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is inside the active area, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED, class=class="str">"cmt">// The cursor is inside the active area, any mouse button is clicked MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL, class=class="str">"cmt">// The cursor is inside the active area, the mouse wheel is being scrolled MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED, class=class="str">"cmt">// The cursor is inside the active area, left mouse button is released class=class="str">"cmt">//--- Within the window scrolling area to the right MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window scrolling area to the right, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_PRESSED, class=class="str">"cmt">// The cursor is within the window scrolling area to the right, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_WHEEL, class=class="str">"cmt">// The cursor is within the window scrolling area to the right, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window scrolling area at the bottom MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window scrolling area at the bottom, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_PRESSED, class=class="str">"cmt">// The cursor is within the window scrolling area at the bottom, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_WHEEL, class=class="str">"cmt">// The cursor is within the window scrolling area at the bottom, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area at the top MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the top, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the top, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area at the top, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area at the bottom MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the bottom, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the bottom, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area at the bottom, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area to the left MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area to the left, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area to the left, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area to the left, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area to the right MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area to the right, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area to the right, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area to the right, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area to the top-left
枚举里的鼠标状态与事件收尾
上面这段枚举把自定义窗体四个角的拖拽热区拆得很细:左上、右上、左下、右下各自都有未按键、已按键、滚轮滚动三种状态,加上控制区同样的三态,合计 15 个 MOUSE_FORM_STATE_* 常量。
紧接其后的 ENUM_MOUSE_EVENT 从 CHART_OBJ_EVENTS_NEXT_CODE 起步定义事件号,先覆盖窗体外(无事件/按键/滚轮)与窗体内(无按键/按键/滚轮)共 6 种,再准备向下扩展到窗体活动区。
在 MT5 里写自定义面板时,直接吃这两个枚举就能省掉自己算光标坐标落在哪块的逻辑;开 MT5 新建一个 EA 把这段粘进头文件,编译后鼠标移到四角应能看到状态常量随光标切换。
MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the top-left, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the top-left, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area at the top-left, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area to the top-right MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the top-right, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the top-right, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area at the top-right, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area at the bottom left MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the bottom-left, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the bottom-left, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area at the bottom-left, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window resizing area at the bottom-right MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_NOT_PRESSED,class=class="str">"cmt">// The cursor is within the window resizing area at the bottom-right, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the window resizing area at the bottom-right, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the window resizing area at the bottom-right, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the control area MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED, class=class="str">"cmt">// The cursor is within the control area, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED, class=class="str">"cmt">// The cursor is within the control area, the mouse button(any) is clicked MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL, class=class="str">"cmt">// The cursor is within the control area, the mouse wheel is being scrolled }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+--------------------------------------------+ class=class="str">"cmt">//| List of possible mouse events | class=class="str">"cmt">//+--------------------------------------------+ enum ENUM_MOUSE_EVENT { MOUSE_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE, class=class="str">"cmt">// No event class=class="str">"cmt">//--- MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED, class=class="str">"cmt">// The cursor is outside the form, the mouse buttons are not clicked MOUSE_EVENT_OUTSIDE_FORM_PRESSED, class=class="str">"cmt">// The cursor is outside the form, the mouse button(any) is clicked MOUSE_EVENT_OUTSIDE_FORM_WHEEL, class=class="str">"cmt">// The cursor is outside the form, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the form MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED, class=class="str">"cmt">// The cursor is inside the form, no mouse buttons are clicked MOUSE_EVENT_INSIDE_FORM_PRESSED, class=class="str">"cmt">// The cursor is inside the form, the mouse button(any) is clicked MOUSE_EVENT_INSIDE_FORM_WHEEL, class=class="str">"cmt">// The cursor is inside the form, the mouse wheel is being scrolled class=class="str">"cmt">//--- Within the window active area