图形界面 X: 升级渲染表格及代码优化 (集成编译 10)·综合运用
表格行焦点重绘的逻辑分支
在自定义画布表格里,焦点行的颜色刷新靠两段逻辑接力:先判断当前是否处于焦点状态,若否,则通过 CheckRowFocus() 拿鼠标所在行索引,立刻 RedrawRow() 并 m_table.Update(),再把该索引存进 m_prev_item_index_focus。 当已经处于焦点时,代码会取鼠标相对表格的 Y 坐标,用 condition=(y>m_rows[idx].m_y && y<=m_rows[idx].m_y2) 验证光标是否还落在同一行。若不满足条件,说明焦点已跨行,再次调用 CheckRowFocus() 与 RedrawRow() 重画。 RedrawRow() 内部用 is_selected_row 参数切换数据源:选中态读 m_selected_item / m_prev_selected_item,悬停态读 m_item_index_focus / m_prev_item_index_focus。当新旧索引都有效时 rows_total 设为 2(重画两行),否则为 1,列数则固定为 m_columns_total-1。 开 MT5 把这段挂到 EA 的画布控件里,移动鼠标看 row 颜色是否只重绘相邻两行,能验证焦点跨行开销被压到最低。外汇与贵金属图表叠加自绘控件时波动刷新频繁,需注意高频 RedrawRow 带来的 CPU 占用风险。
else { class=class="str">"cmt">//--- 检查行上的焦点 if(m_item_index_focus==WRONG_VALUE) { class=class="str">"cmt">//--- 获取焦点所在行的索引 m_item_index_focus=CheckRowFocus(); class=class="str">"cmt">//--- 改变行颜色 RedrawRow(); m_table.Update(); class=class="str">"cmt">//--- 保存为以前的焦点索引 m_prev_item_index_focus=m_item_index_focus; class="kw">return; } class=class="str">"cmt">//--- 获取鼠标光标之下的相对 Y 坐标 class="type">int y=m_mouse.RelativeY(m_table); class=class="str">"cmt">//--- 验证焦点 class="type">bool condition=(y>m_rows[m_item_index_focus].m_y && y<=m_rows[m_item_index_focus].m_y2); class=class="str">"cmt">//--- 如果焦点改变了 if(!condition) { class=class="str">"cmt">//--- 获取焦点所在行的索引 m_item_index_focus=CheckRowFocus(); class=class="str">"cmt">//--- 改变行颜色 RedrawRow(); m_table.Update(); class=class="str">"cmt">//--- 保存为以前的焦点索引 m_prev_item_index_focus=m_item_index_focus; } } class CCanvasTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 根据指定的模式重绘指定的表格行 class="type">void RedrawRow(class="kw">const class="type">bool is_selected_row=class="kw">false); }; class="type">void CCanvasTable::RedrawRow(class="kw">const class="type">bool is_selected_row=class="kw">false) { class=class="str">"cmt">//--- 当前以及前一行的索引 class="type">int item_index =WRONG_VALUE; class="type">int prev_item_index =WRONG_VALUE; class=class="str">"cmt">//--- 相对于指定模式初始化行索引 if(is_selected_row) { item_index =m_selected_item; prev_item_index =m_prev_selected_item; } else { item_index =m_item_index_focus; prev_item_index =m_prev_item_index_focus; } class=class="str">"cmt">//--- 如果没有定义索引, 离开 if(prev_item_index==WRONG_VALUE && item_index==WRONG_VALUE) class="kw">return; class=class="str">"cmt">//--- 将要绘制的行数和列数 class="type">int rows_total =(item_index!=WRONG_VALUE && prev_item_index!=WRONG_VALUE)? class="num">2 : class="num">1; class="type">int columns_total =m_columns_total-class="num">1; class=class="str">"cmt">//--- 坐标 class="type">int x1=class="num">1,x2=m_table_x_size; class="type">int y1[class="num">2]={class="num">0},y2[class="num">2]={class="num">0}; class=class="str">"cmt">//--- 确定序列中的数值数组 class="type">int indexes[class="num">2]; class=class="str">"cmt">//--- 如果 (class="num">1) 鼠标光标向下移动, 或如果 (class="num">2) 首次进入 if(item_index>m_prev_item_index_focus || item_index==WRONG_VALUE) {
「表格行高亮与网格绘制的光标逻辑」
这段绘制逻辑的核心,是根据鼠标光标相对上一帧的位置决定哪两行进入 indexes 数组:光标向下时,indexes[0] 取上一行索引(除非 item_index 非法且 prev 也非法),indexes[1] 取当前行;向上时顺序反转。这样能保证高亮区域始终覆盖光标跨越的相邻两行,而不是单点闪烁。 行背景用 FillRectangle 按 y1/y2 各缩 1 像素绘制,焦点行由 is_item_focus 判定——非 hover 模式下只认 item_index,hover 模式下若光标离开(WRONG_VALUE)则回退到 prev_item_index。外汇与贵金属图表叠加此类自定义表格时,需注意高亮重绘频率,过高可能在 tick 密集时段拖慢 MT5 终端响应。 网格线遍历每行每列,在列右边界 m_x2 处画竖线;图标仅在存在且列对齐为 ALIGN_LEFT 时由 DrawImage 输出。文本绘制前用 TextX 与 TextAlign 取坐标和对齐方式,y 坐标统一加 m_text_y_offset,保证多列文字基线一致。 直接把这段塞进你自己的 CTable 派生类 OnDraw 里,改 m_lights_hover 默认值,就能在 MT5 上直观对比『跟随光标』与『锁定上次位置』两种高亮行为的差异。
indexes[class="num">0]=(item_index==WRONG_VALUE || prev_item_index!=WRONG_VALUE)? prev_item_index : item_index; indexes[class="num">1]=item_index; } class=class="str">"cmt">//--- 如果鼠标光标向上移动 else { indexes[class="num">0]=item_index; indexes[class="num">1]=prev_item_index; } class=class="str">"cmt">//--- 绘制行的背景 for(class="type">int r=class="num">0; r<rows_total; r++) { class=class="str">"cmt">//--- 计算行的上、下边界坐标 y1[r]=m_rows[indexes[r]].m_y+class="num">1; y2[r]=m_rows[indexes[r]].m_y2-class="num">1; class=class="str">"cmt">//--- 确定处于高亮显示模式的焦点所在行 class="type">bool is_item_focus=class="kw">false; if(!m_lights_hover) is_item_focus=(indexes[r]==item_index && item_index!=WRONG_VALUE); else is_item_focus=(item_index==WRONG_VALUE)?(indexes[r]==prev_item_index) :(indexes[r]==item_index); class=class="str">"cmt">//--- 绘制行背景 m_table.FillRectangle(x1,y1[r],x2,y2[r],RowColorCurrent(indexes[r],is_item_focus)); } class=class="str">"cmt">//--- 网格颜色 class="type">uint clr=::ColorToARGB(m_grid_color); class=class="str">"cmt">//--- 绘制边界 for(class="type">int r=class="num">0; r<rows_total; r++) { for(class="type">int c=class="num">0; c<columns_total; c++) m_table.Line(m_columns[c].m_x2,y1[r],m_columns[c].m_x2,y2[r],clr); } class=class="str">"cmt">//--- 绘制图标 for(class="type">int r=class="num">0; r<rows_total; r++) { for(class="type">int c=class="num">0; c<m_columns_total; c++) { class=class="str">"cmt">//--- 绘制图标, 如果 (class="num">1) 它存在于单元格中, 且 (class="num">2) 此列的文本靠左侧对齐 if(ImagesTotal(c,r)>class="num">0 && m_columns[c].m_text_align==ALIGN_LEFT) DrawImage(c,indexes[r]); } } class=class="str">"cmt">//--- 计算坐标 class="type">int x=class="num">0,y=class="num">0; class=class="str">"cmt">//--- 文本对齐模式 class="type">uint text_align=class="num">0; class=class="str">"cmt">//--- 绘制文字 for(class="type">int c=class="num">0; c<m_columns_total; c++) { class=class="str">"cmt">//--- 获取 (class="num">1) 文本的 X 坐标, 和 (class="num">2) 文本对齐模式 x =TextX(c); text_align =TextAlign(c,TA_TOP); class=class="str">"cmt">//--- for(class="type">int r=class="num">0; r<rows_total; r++) { class=class="str">"cmt">//--- (class="num">1) 计算坐标, 并 (class="num">2) 绘制文本 y=m_rows[indexes[r]].m_y+m_text_y_offset; m_table.TextOut(x,y,m_columns[c].m_rows[indexes[r]].m_short_text,TextColor(c,indexes[r]),text_align); } } }
◍ 只刷单元格不刷全表:RedrawCell 与属性改写的用法
表格行级重绘之外,更细粒度的是单元格级重绘。改某个格子的文本、颜色或图标时,没必要重画整张表,私有方法 RedrawCell() 只更新该格内容,边框保持不变;若开了行高亮,它会先按焦点状态算背景色,再依次画背景、图标(仅当分配了且文本左对齐)、文本。 对外改内容用三个公有方法:SetValue() 写文本、TextColor() 写颜色、ChangeImage() 换图标。前两者要传列行索引和值,SetValue() 会把完整串和按格宽截断的短串都存进 CTCell 结构;它们第四个参数 redraw 控制是否立刻重绘,设 false 就等 UpdateTable() 统一刷。ChangeImage() 第三个参数是待切换的图标索引,同样能延后重绘。 UpdateTable() 是唯一公开的全表刷新入口,默认参数 false 表示仅刷新不重绘框架;当多格属性改完再调一次,就能低频统一出图。 实测 5 列 30 行表格在 EA 里以 100 毫秒频率刷所有单元格图标,无用户交互时 CPU 负载不超过 3%。这个开销说明单元格级局部重绘在 MT5 图形界面里是可长期跑的。 下面这段是 RedrawCell 的核心声明与局部重绘逻辑,以及 SetValue 的方法签名,逐行拆一下关键点。
class CCanvasTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 重绘指定的表格单元 class="type">void RedrawCell(class="kw">const class="type">int column_index,class="kw">const class="type">int row_index); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 重绘指定的表格单元 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCanvasTable::RedrawCell(class="kw">const class="type">int column_index,class="kw">const class="type">int row_index) { class=class="str">"cmt">//--- 坐标 class="type">int x1=m_columns[column_index].m_x+class="num">1; class="type">int x2=m_columns[column_index].m_x2-class="num">1; class="type">int y1=m_rows[row_index].m_y+class="num">1; class="type">int y2=m_rows[row_index].m_y2-class="num">1; class=class="str">"cmt">//--- 计算坐标 class="type">int x=class="num">0,y=class="num">0; class=class="str">"cmt">//--- 检查焦点 class="type">bool is_row_focus=class="kw">false; class=class="str">"cmt">//--- 如果启用行高亮显示模式 if(m_lights_hover) { class=class="str">"cmt">//--- (class="num">1) 获取鼠标光标的相对 Y 坐标, 和 (class="num">2) 指定表格行上的焦点 y=m_mouse.RelativeY(m_table); is_row_focus=(y>m_rows[row_index].m_y && y<=m_rows[row_index].m_y2); } class=class="str">"cmt">//--- 绘制单元背景 m_table.FillRectangle(x1,y1,x2,y2,RowColorCurrent(row_index,is_row_focus)); class=class="str">"cmt">//--- 绘制图标, 如果 (class="num">1) 它存在于单元格中, 且 (class="num">2) 此列的文本靠左侧对齐 if(ImagesTotal(column_index,row_index)>class="num">0 && m_columns[column_index].m_text_align==ALIGN_LEFT) DrawImage(column_index,row_index); class=class="str">"cmt">//--- 获取文本对齐模式 class="type">uint text_align=TextAlign(column_index,TA_TOP); class=class="str">"cmt">//--- 绘制文字 for(class="type">int c=class="num">0; c<m_columns_total; c++) { class=class="str">"cmt">//--- 获取文本的 X 坐标 x=TextX(c); class=class="str">"cmt">//--- 停止循环 if(c==column_index) class="kw">break; } class=class="str">"cmt">//--- (class="num">1) 计算 Y 坐标, 并 (class="num">2) 绘制文本 y=y1+m_text_y_offset-class="num">1; m_table.TextOut(x,y,m_columns[column_index].m_rows[row_index].m_short_text,TextColor(column_index,row_index),text_align); } class CCanvasTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 为指定的表格单元设置数值 class="type">void SetValue(class="kw">const class="type">uint column_index,class="kw">const class="type">uint row_index,class="kw">const class="type">class="kw">string value,class="kw">const class="type">bool redraw=class="kw">false);
给表格单元灌数据改颜色换图标
在 MT5 的 CCanvasTable 自定义类里,单元格不是直接画死的,而是通过成员函数往二维结构里写值。SetValue 负责把字符串塞进 m_columns[column_index].m_rows[row_index].m_full_text,同时调用 CorrectingText 生成截断后的 m_short_text,防止文字超出单元格宽度。 TextColor 的逻辑更轻量,只改 m_text_color 这一个属性,不影响文本本身。两个函数都带 redraw 参数,默认 false;如果你在循环里批量改 50 个单元,建议循环结束后再统一重绘一次,而不是每次传 true,能少触发几十次 RedrawCell。 ChangeImage 多了一层防护:先拿 ImagesTotal 看该单元格有没有图标,再比对 image_index 是否越界,最后检查是不是已经选中了同一个图标。若 image_index 等于已有的 m_selected_image 就直接 return,避免无意义的重绘。外汇与贵金属图表叠加这类自定义表格时,注意高频重绘可能拖累 tick 处理性能,属于高风险环境下的细节优化。
class="type">void CCanvasTable::SetValue(class="kw">const class="type">uint column_index,class="kw">const class="type">uint row_index,class="kw">const class="type">class="kw">string value,class="kw">const class="type">bool redraw=class="kw">false) { class=class="str">"cmt">//--- 检查是否超出数组范围 if(!CheckOutOfRange(column_index,row_index)) class="kw">return; class=class="str">"cmt">//--- 将数值存储到数组中 m_columns[column_index].m_rows[row_index].m_full_text=value; class=class="str">"cmt">//--- 调整并存储文本, 如果它不适合单元格 m_columns[column_index].m_rows[row_index].m_short_text=CorrectingText(column_index,row_index); class=class="str">"cmt">//--- 如果已指定, 重绘单元格 if(redraw) RedrawCell(column_index,row_index); } class="type">void CCanvasTable::TextColor(class="kw">const class="type">uint column_index,class="kw">const class="type">uint row_index,class="kw">const class="type">class="kw">color clr,class="kw">const class="type">bool redraw=class="kw">false) { class=class="str">"cmt">//--- 检查是否超出数组范围 if(!CheckOutOfRange(column_index,row_index)) class="kw">return; class=class="str">"cmt">//--- 将文本颜色存储在公共数组中 m_columns[column_index].m_rows[row_index].m_text_color=clr; class=class="str">"cmt">//--- 如果已指定, 重绘单元格 if(redraw) RedrawCell(column_index,row_index); } class="type">void CCanvasTable::ChangeImage(class="kw">const class="type">uint column_index,class="kw">const class="type">uint row_index,class="kw">const class="type">uint image_index,class="kw">const class="type">bool redraw=class="kw">false) { class=class="str">"cmt">//--- 检查是否超出数组范围 if(!CheckOutOfRange(column_index,row_index)) class="kw">return; class=class="str">"cmt">//--- 获取单元格图标的数量 class="type">int images_total=ImagesTotal(column_index,row_index); class=class="str">"cmt">//--- 如果 (class="num">1) 没有图标, 或是 (class="num">2) 超出范围, 离开 if(images_total==WRONG_VALUE || image_index>=(class="type">uint)images_total) class="kw">return; class=class="str">"cmt">//--- 如果指定的图标与已选定的图标匹配, 离开 if(image_index==m_columns[column_index].m_rows[row_index].m_selected_image) class="kw">return; }
「单元格图标索引与表格更新的底层写法」
在自定义表格控件里,单元格选中图标的记录依赖一个强制类型转换:把传入的 image_index 转成 int 再写进 m_selected_image。这一步看着简单,但漏了转换在严格编译模式下会直接报错,开 MT5 写同类 CCanvasTable 派生类时值得照抄。 重绘不是无脑全量跑。代码里用 redraw 参数 gate 住 RedrawCell 和 DrawTable 的调用——只在该布尔量为 true 时才触发绘制函数,否则只更新内存里的数据结构。实盘面板若每帧都重画,CPU 占用会明显抬升,这个开关能省掉不必要的 GDI 开销。 UpdateTable 作为私有方法,对外只暴露 redraw 默认值 false。意味着常规数据刷新不联动画图,要肉眼看到变化得显式传 true。调试时若发现表格数据改了界面没动,先查调用处有没有把 redraw 置真。
class=class="str">"cmt">//--- 保存单元格所选图标的索引 m_columns[column_index].m_rows[row_index].m_selected_image=(class="type">int)image_index; class=class="str">"cmt">//--- 如果已指定, 重绘单元格 if(redraw) RedrawCell(column_index,row_index); } class CCanvasTable : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 更新表格 class="type">void UpdateTable(class="kw">const class="type">bool redraw=class="kw">false); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 更新表格 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CCanvasTable::UpdateTable(class="kw">const class="type">bool redraw=class="kw">false) { class=class="str">"cmt">//--- 如果指定,重绘表格 if(redraw) DrawTable(); class=class="str">"cmt">//--- 更新表格 m_table.Update(); }
◍ 用自绘表格复刻市场观察窗口
MT5 当前版本的图形渲染能力,已经足以在自定义面板里画出和「市场观察」窗口同构的报价表。实测在 MetaQuotes-Demo 服务器上取 25 个可用品种,铺成 5 列 25 行,列定义为 Symbol、Bid、Ask、Spread(!)、Time,就能对齐原生窗口的数据结构。 表格单元首次初始化放在控件创建方法里直接跑,由 CProgram::InitializingTable() 这个辅助方法接管。运行期默认刷新率是 16 毫秒,另写 CProgram::UpdateTable() 负责周期更新:周末(周六、周日)直接 return,否则双重循环扫所有行列,抓取每个品种最近两笔 tick,比完价格方向再写单元格。 图标复用原生逻辑——灰圈表示无变化,上箭头表示买价上行,下箭头表示买价下行。下面这段初始化代码可直接拷进 EA 的 CProgram 类里验证,注意 image_array 路径依赖 EasyAndFastGUI 资源包,缺了图就只显示文字。 别把 16 毫秒当万能刷频 外汇与贵金属报价在高波动时段可能丢 tick,16ms 刷新在普通品种上够用,但遇到点差剧烈扩张时,自绘表里的 Spread 列可能滞后于真实成交价,仅作监控参考而非下单依据。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建应用程序的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CProgram : class="kw">public CWndEvents { class="kw">private: class=class="str">"cmt">//--- 初始化表格 class="type">void InitializingTable(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 初始化表格 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::InitializingTable(class="type">void) { class=class="str">"cmt">//--- 标题数组 class="type">class="kw">string text_headers[COLUMNS1_TOTAL]={"Symbol","Bid","Ask","!","Time"}; class=class="str">"cmt">//--- 品种数组 class="type">class="kw">string text_array[class="num">25]= { "AUDUSD","GBPUSD","EURUSD","USDCAD","USDCHF","USDJPY","NZDUSD","USDSEK","USDHKD","USDMXN", "USDZAR","USDTRY","GBPAUD","AUDCAD","CADCHF","EURAUD","GBPCHF","GBPJPY","NZDJPY","AUDJPY", "EURJPY","EURCHF","EURGBP","AUDCHF","CHFJPY" }; class=class="str">"cmt">//--- 图标数组 class="type">class="kw">string image_array[class="num">3]= { "::Images\\EasyAndFastGUI\\Icons\\bmp16\\circle_gray.bmp", "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp", "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp" }; class=class="str">"cmt">//--- for(class="type">int c=class="num">0; c<COLUMNS1_TOTAL; c++) { class=class="str">"cmt">//--- 设置标题 m_canvas_table.SetHeaderText(c,text_headers[c]); class=class="str">"cmt">//--- for(class="type">int r=class="num">0; r<ROWS1_TOTAL; r++) { class=class="str">"cmt">//--- 设置图标 m_canvas_table.SetImages(c,r,image_array); class=class="str">"cmt">//--- 设置品名 if(c<class="num">1) m_canvas_table.SetValue(c,r,text_array[r]); class=class="str">"cmt">//--- 所有单元的省缺值 else m_canvas_table.SetValue(c,r,"-"); } } } class CProgram : class="kw">public CWndEvents { class="kw">private: class=class="str">"cmt">//--- 初始化表格 class="type">void InitializingTable(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+
用两笔报价判定多空与刷新点差
表格实时刷新的核心在 UpdateTable:先拿服务器时间,遇周六日直接 return,避免非交易时段空跑浪费资源。 逐行列遍历时,对每个品种只取最近 2 笔 tick(CopyTicks 的参数为 COPY_TICKS_ALL、起始 0、数量 2)。若不足两笔则 continue 跳过,保证后面比价逻辑不越界。 方向判定只看 bid:ticks[0].bid 与 ticks[1].bid 相等标 0(不动),大于标 1(买压倾向上行),小于标 2(卖压倾向下行)。注意这里用 ArraySetAsSeries(ticks,true) 把数组翻成时间序列,所以索引 0 是最新、1 是前一笔。 第 3 列单独处理点差,用 SymbolInfoInteger(symbol,SYMBOL_SPREAD) 取整数点值。外汇与贵金属点差随流动性剧烈跳动,周末外盘休市时价差可能瞬间扩大数倍,属高风险特征,实盘需以经纪商实时值为准。
class="type">void CProgram::UpdateTable(class="type">void) { class="type">MqlDateTime check_time; ::TimeToStruct(::TimeTradeServer(),check_time); class=class="str">"cmt">//--- 如果是星期六或星期天, 离开 if(check_time.day_of_week==class="num">0 || check_time.day_of_week==class="num">6) class="kw">return; class=class="str">"cmt">//--- for(class="type">int c=class="num">0; c<m_canvas_table.ColumnsTotal(); c++) { for(class="type">int r=class="num">0; r<m_canvas_table.RowsTotal(); r++) { class=class="str">"cmt">//--- 获取数据的品种 class="type">class="kw">string symbol=m_canvas_table.GetValue(class="num">0,r); class=class="str">"cmt">//--- 获取最后两笔即时报价的数据 class="type">MqlTick ticks[]; if(::CopyTicks(symbol,ticks,COPY_TICKS_ALL,class="num">0,class="num">2)<class="num">2) class="kw">continue; class=class="str">"cmt">//--- 将数组设置为时间序列 ::ArraySetAsSeries(ticks,true); class=class="str">"cmt">//--- 品种列 - 品种。Determine the price direction. if(c==class="num">0) { class="type">int index=class="num">0; class=class="str">"cmt">//--- 如果价格未变化 if(ticks[class="num">0].ask==ticks[class="num">1].ask && ticks[class="num">0].bid==ticks[class="num">1].bid) index=class="num">0; class=class="str">"cmt">//--- 如果供给价格向上变化 else if(ticks[class="num">0].bid>ticks[class="num">1].bid) index=class="num">1; class=class="str">"cmt">//--- 如果供给价格向下变化 else if(ticks[class="num">0].bid<ticks[class="num">1].bid) index=class="num">2; class=class="str">"cmt">//--- 设置相应的图标 m_canvas_table.ChangeImage(c,r,index,true); } else { class=class="str">"cmt">//--- 价格差异列 - 点差 (!) if(c==class="num">3) { class=class="str">"cmt">//--- 获取并设置点差的点数大小 class="type">int spread=(class="type">int)::SymbolInfoInteger(symbol,SYMBOL_SPREAD);
「逐列刷新报价面板的着色逻辑」
这段循环负责把每个交易品种的不同字段写进 Canvas 表格。先取 SYMBOL_DIGITS 决定小数位,再用 DoubleToString 把 bid / ask 格式化成对应精度——黄金 XAUUSD 通常是 2 位,EURUSD 是 5 位,写错位数界面会错位。 bid 列(c==1)和 ask 列(c==2)都做了价格变动检测:拿当前 tick 和上一 tick 比,若新值小于旧值染红、大于旧值染蓝。这样盘口跳动时,交易员扫一眼颜色就知道主动抛压还是主动买盘占优,外汇与贵金属波动剧烈,颜色只是辅助,不代表方向确认。 时间列(c==4)把 SymbolInfoInteger 的 SYMBOL_TIME 和 tick 的 time_msc 拼起来,只截最后 3 位毫秒补在秒后面。例如 TIME_SECONDS 给出 14:23:07,毫秒 482 就显示成 14:23:07.482,方便你核对 MT5 tick 到达的真实密度。
m_canvas_table.SetValue(c,r,class="type">class="kw">string(spread),true); class="kw">continue; } class=class="str">"cmt">//--- 获取小数位数 class="type">int digit=(class="type">int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); class=class="str">"cmt">//--- 供给价列 if(c==class="num">1) { m_canvas_table.SetValue(c,r,::DoubleToString(ticks[class="num">0].bid,digit)); class=class="str">"cmt">//--- 如果价格有变化, 设定与方向对应的颜色 if(ticks[class="num">0].bid!=ticks[class="num">1].bid) m_canvas_table.TextColor(c,r,(ticks[class="num">0].bid<ticks[class="num">1].bid)? clrRed : clrBlue,true); class=class="str">"cmt">//--- class="kw">continue; } class=class="str">"cmt">//--- 采购价列 if(c==class="num">2) { m_canvas_table.SetValue(c,r,::DoubleToString(ticks[class="num">0].ask,digit)); class=class="str">"cmt">//--- 如果价格有变化, 设定与方向对应的颜色 if(ticks[class="num">0].ask!=ticks[class="num">1].ask) m_canvas_table.TextColor(c,r,(ticks[class="num">0].ask<ticks[class="num">1].ask)? clrRed : clrBlue,true); class=class="str">"cmt">//--- class="kw">continue; } class=class="str">"cmt">//--- 最后品种价格的到达时刻列 if(c==class="num">4) { class="type">long time =::SymbolInfoInteger(symbol,SYMBOL_TIME); class="type">class="kw">string time_msc =::IntegerToString(ticks[class="num">0].time_msc); class="type">int length =::StringLen(time_msc); class="type">class="kw">string msc =::StringSubstr(time_msc,length-class="num">3,class="num">3); class="type">class="kw">string str =::TimeToString(time,TIME_MINUTES|TIME_SECONDS)+"."+msc; class=class="str">"cmt">//--- class="type">class="kw">color clr=clrBlack;
◍ 用买卖盘跳动给报价表上色
在 MT5 的自定义报价面板里,光看数字不够直观,得靠颜色把供给端的变动速度显出来。下面这段逻辑就是拿相邻两笔 tick 的 bid 做比较,决定单元格染什么色。 如果最新 tick 的 ask 和 bid 都和上一笔完全相等,说明价格静止,单元格置为黑色 clrBlack;若最新 bid 大于前一笔 bid,意味着供给价格被向上吃掉,染成蓝色 clrBlue;反之 bid 走低则染红 clrRed。 [CODE] //--- 如果价格未变化 if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid) clr=clrBlack; //--- 如果供给价格向上变化 else if(ticks[0].bid>ticks[1].bid) clr=clrBlue; //--- 如果供给价格向下变化 else if(ticks[0].bid<ticks[1].bid) clr=clrRed; //--- 设置数值和文本颜色 m_canvas_table.SetValue(c,r,str); m_canvas_table.TextColor(c,r,clr,true); continue; } } } } //--- 更新表格 m_canvas_table.UpdateTable(); } [/CODE] 逐行拆一下:ticks[0] 是刚到的 tick,ticks[1] 是上一笔;双等号判断买卖盘都未动才黑屏。else if 里只比 bid 不比 ask,是因为供给侧挂单厚度变化最先反映在 bid 上。SetValue 写文本,TextColor 的第三个参数 true 表示立即重绘,continue 跳过后续列直接跑下一格。 最后 m_canvas_table.UpdateTable() 把整张 Canvas 表格推到图表。实盘里外汇和贵金属点差跳动快,这种染色法能让你一眼看出哪一行供给在撤、哪一行在被扫,但高频下红色闪烁可能只是噪音,需结合成交量阈值过滤。
class=class="str">"cmt">//--- 如果价格未变化 if(ticks[class="num">0].ask==ticks[class="num">1].ask && ticks[class="num">0].bid==ticks[class="num">1].bid) clr=clrBlack; class=class="str">"cmt">//--- 如果供给价格向上变化 else if(ticks[class="num">0].bid>ticks[class="num">1].bid) clr=clrBlue; class=class="str">"cmt">//--- 如果供给价格向下变化 else if(ticks[class="num">0].bid<ticks[class="num">1].bid) clr=clrRed; class=class="str">"cmt">//--- 设置数值和文本颜色 m_canvas_table.SetValue(c,r,str); m_canvas_table.TextColor(c,r,clr,true); class="kw">continue; } } } } class=class="str">"cmt">//--- 更新表格 m_canvas_table.UpdateTable(); }
别急着下结论
这套 GUI 函数库当前阶段的结构已在附图里摊开,最新包 easyandfastgui_mql5.zip 体积 578.26 KB,可直接拉到 MT5 里跑一遍看渲染效率。 有用户在 2018 年反馈:把 MetaEditor 的 metaeditor.ini 里 Optimize 参数置 0 后,编译耗时压到 351 毫秒、0 报错 0 警告;但 1755/1816 版客户端未必带这个字段,得用记事本搜文件才找得到。 外汇与贵金属行情波动剧烈、杠杆风险高,这类界面库只解决交互层问题,信号逻辑仍要你自己回测验证。先搭环境编译通,再谈策略不迟。