在 MQL 应用程序中运用 CCanvas 类·综合运用
(3/3)·从像素数组到 UI 控件与指标缓冲,一篇跑通 CCanvas 全链路实战
- 用 CCanvas 在 MT5 里造一个油漆桶
- 用 CCanvas 在图表上画出可控透明度的画布
- 把文字标签钉在图表正中央
- 在图表上画临时标注与坐标轴
- 把坐标数组按画布比例放大
- 用 CCanvas 把几何图元画进图表
- 正文
- 用 CCanvas 在图表上画几何图元
- 用宏打包抗锯齿图元绘制
- 用画布把折线图直接铺到图表上
- 把坐标数组换算成画布像素
- 用宏批量切换线型与线端画法
- 用 CCanvas 把线宽与端点样式画出来
- 用画布把坐标数组拉伸成图表图元
- 用 CCanvas 把坐标点画成平滑线框
- 用 Canvas 把坐标点阵铺到图表像素上
- 用画布接口批量试出平滑曲线手感
- 在图表上用 Canvas 画一个自定义图形标记
- 按钮命中区与鼠标状态位运算
- 用画布坐标判断按钮命中区
- 用鼠标拖选调色盘给画布上色
- 记住这一条就够了
「用 CCanvas 在 MT5 里造一个油漆桶」
想在 MT5 图表上做类图形编辑器的填充,不一定要外接程序。CCanvas 类提供了一组前缀为 Fill 的方法,能以统一颜色填充多边形、三角形、圆和椭圆,对应方法为 FillPolygon、FillTriangle、FillCircle、FillEllipse。在 DrawPrimitivesWu.mq5 基础上改出 DrawPrimitivesFill.mq5,只插入这几个方法并设填充色,就能看到图元被纯色填满,上方仍可叠画普通边框或可调线型的轮廓。 若要做「点哪填哪」的油漆桶,得用指标而非脚本,因为要捕获 OnChartEvent 图表事件。新建 Filling.mq5,全局声明 canvas 变量保证整个指标生命周期可访问;用 14 个按钮数组选色,按下某按钮即写入 fillColor,同时把 oldSelected 指回原位。OnInit 里建画布、画底图、开启鼠标事件,按钮颜色直接取自 CCanvas::GetDefaultColor(0) 开始的调色板。 OnChartEvent 中先判鼠标左键落在哪个按钮边缘坐标,循环比对 colors 数组,命中就存 fillColor 并复位旧按钮然后 return;若点的不是按钮而是图像,则调用 Fill 方法拿 fillColor 填充,再 Update 刷新。跑起来后,用 CCanvas 实现的填充工具行为接近常见图形软件的油漆桶。 下面片段演示画布最基础的创建与擦除:CreateBitmapLabel 在坐标(15,30)生成 300x250 的位图标签,Erase 填白后 Sleep(6000) 留 6 秒观察,再 Destroy 清理。把 Erase 的 ColorToARGB(clrWhite,255) 换成 0xFF98FB98 是实色浅绿,0xCC98FB98 则是约 80% 不透明的同色,改这串十六进制就能直接验证透明通道效果。
class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_NORMALIZE); canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_NORMALIZE); canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_NORMALIZE); canvas.Erase(0xFF98FB98); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_NORMALIZE); canvas.Erase(0xCC98FB98); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { CCanvas canvas;
用 CCanvas 在图表上画出可控透明度的画布
MT5 的 CCanvas 类能在图表上挂一块位图画布,用来画文字、色块或做自定义 HUD。CreateBitmapLabel 的第一个参数是画布 ID,后面四个整型是左上角坐标与宽高——上面例子里固定为 (15,30) 起、300×250 像素,COLOR_FORMAT_ARGB_RAW 表示颜色按 0xAARRGGBB 原始写入。 Erase 填入 0xFF98FB98 这类 16 进制值就是给画布铺底:前两位 FF 是不透明度,98FB98 是浅绿。把 FF 换成 FB、FA 再 Erase,肉眼能看出底色变暗,说明 Alpha 通道确实在生效。 想做淡入淡出效果,就别只 Erase 一次。下面这段用 for 从 255 降到 0 循环调 TransparentLevelSet,每步 Sleep(100),整块画布会在约 25.6 秒里逐渐透明——外汇和贵金属图表叠加这类元素时,注意高波动行情下频繁 Update 可能拖慢终端响应。 若改用 COLOR_FORMAT_XRGB_NOALPHA,0x0098FB98 里前两位被忽略,画布不透明;而 COLOR_FORMAT_ARGB_NORMALIZE 配合 ColorToARGB(clrWhite,255) 才是标准写法。FontSet("Calibri",-210) 设好字体后,TextOut(15,15,"Text",...) 能把字绘到画布上,位置与 Erase 底色互不干扰。 实际量文本尺寸也很直接:FontSet 后调 TextSize(text,width,height),再用 textXDist/textYDist 做边距补偿,就能算出自适应画布大小,避免文字被截断。
class="type">void OnStart() { ChartSetInteger(class="num">0, CHART_COLOR_BACKGROUND, clrWhite); CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_RAW); canvas.Erase(0xFF98FB98); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { ChartSetInteger(class="num">0, CHART_COLOR_BACKGROUND, clrWhite); CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_RAW); canvas.Erase(0xFB98FB98); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { ChartSetInteger(class="num">0, CHART_COLOR_BACKGROUND, clrWhite); CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_RAW); canvas.Erase(0xFA98FB98); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { ChartSetInteger(class="num">0, CHART_COLOR_BACKGROUND, clrWhite); CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_RAW); canvas.Erase(0xFF98FB98); for(class="type">int a = class="num">255; a >= class="num">0; a--) { canvas.TransparentLevelSet((class="type">uchar)a); canvas.Update(true); Sleep(class="num">100); } canvas.Destroy(); } class="type">void OnStart() { CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_XRGB_NOALPHA); canvas.Erase(0x0098FB98); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { CCanvas canvas; canvas.CreateBitmapLabel("canvas", class="num">15, class="num">30, class="num">300, class="num">250, COLOR_FORMAT_ARGB_NORMALIZE); canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.FontSet("Calibri", -class="num">210); canvas.TextOut(class="num">15, class="num">15, "Text", ColorToARGB(clrLightSlateGray, class="num">255)); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { class="type">class="kw">string text = "Text"; class="type">int width, height; class="kw">const class="type">int textXDist = class="num">10, textYDist = class="num">5; class="type">int canvasX, canvasY; CCanvas canvas; canvas.FontSet("Calibri", -class="num">210); canvas.TextSize(text, width, height); }
◍ 把文字标签钉在图表正中央
用 CCanvas 在 MT5 图表上画悬浮文字时,最麻烦的是定位。下面这段代码把标签强行放到图表可视区域的几何中心:先取 CHART_WIDTH_IN_PIXELS 和 CHART_HEIGHT_IN_PIXELS,除以 2 再减去文字宽高的一半,得到的 canvasX / canvasY 就是左上角坐标。 字体先用 FontSet("Calibri", -210) 设定,负号代表磅值换算后的设备单位;TextSize 测出文字像素宽高存进 width / height。注意 textXDist=10、textYDist=5 这两个内边距常量,CreateBitmapLabel 的宽高必须加上它们乘 2,否则文字会被裁掉边。 Erase 用纯白不透明底(alpha=255)清屏,TextOut 以浅石板灰画字,Update(true) 立即刷新。Sleep(6000) 后 Destroy 销毁标签——这套流程跑完,图表中央会闪现 6 秒的「Text」字样,外汇或贵金属图表叠加此类标注时需注意,行情跳动密集期可能遮挡关键 K 线,属高风险界面操作。
class="type">class="kw">string text = "Text"; class="type">int width, height; class="kw">const class="type">int textXDist = class="num">10, textYDist = class="num">5; class="type">int canvasX, canvasY; canvas.FontSet("Calibri", -class="num">210); canvas.TextSize(text, width, height); canvasX = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0) / class="num">2 - width / class="num">2; canvasY = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0) / class="num">2 - height / class="num">2; canvas.CreateBitmapLabel("canvas", canvasX, canvasY, width + textXDist * class="num">2, height + textYDist * class="num">2, COLOR_FORMAT_ARGB_NORMALIZE); canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, class="num">255)); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy();
「在图表上画临时标注与坐标轴」
用 CCanvas 在 MT5 图表中央弹出一个临时文字层,是做自定义 HUD 的入门做法。下面这段把字符串放到图正中,停留 6 秒后销毁,适合用来验证画布坐标体系是否如你所想。 canvasX 和 canvasY 取的是图表像素宽高的一半减去文字宽高的一半,所以文字中心会对齐图表中心;textXDist=10、textYDist=5 是文字相对画布左上角的偏移量。Erase 里用 clrWhite 且 alpha=0,等于清成透明底,不会挡住后面的 K 线。 另一段则演示了坐标轴与折线:取图表宽高较小值除以 15 作为 point 基准单位,画出左下直角坐标的两轴,再把两组数组 plX/plY、pgX/pgY 各乘 point 映射到像素坐标。外汇与贵金属波动快、点差跳空频繁,这类叠加层仅作辅助参考,实盘信号仍须结合风控,杠杆品种高风险。 直接把代码贴进 EA 的 OnStart 跑一遍,改 Sleep(6000) 里的毫秒数就能控制标注存活时长,改 point 分母可缩放整个绘图比例。
canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, class="num">255)); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { class="type">class="kw">string text = "Text"; class="type">int width, height; class="kw">const class="type">int textXDist = class="num">10, textYDist = class="num">5; class="type">int canvasX, canvasY; CCanvas canvas; canvas.FontSet("Calibri", -class="num">210); canvas.TextSize(text, width, height); canvasX = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0) / class="num">2 - width / class="num">2; canvasY = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0) / class="num">2 - height / class="num">2; canvas.CreateBitmapLabel("canvas", canvasX, canvasY, width + textXDist * class="num">2, height + textYDist * class="num">2, COLOR_FORMAT_ARGB_RAW); canvas.Erase(ColorToARGB(clrWhite, class="num">0)); canvas.FontSet("Calibri", -class="num">210); canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, class="num">255)); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy(); } class="type">void OnStart() { CCanvas canvas; class="type">int w, h; class="type">int minSize, point; class="type">uint color_ = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint fillColor = ColorToARGB(clrRed, class="num">255); class="type">int plX[] = {class="num">6, class="num">8, class="num">9, class="num">11, class="num">12, class="num">14}, plY[] = {class="num">14, class="num">12, class="num">13, class="num">11, class="num">12, class="num">10}; class="type">int pgX[] = {class="num">15, class="num">14, class="num">15, class="num">17, class="num">18, class="num">17}, pgY[] = {class="num">9, class="num">7, class="num">5, class="num">5, class="num">7, class="num">9}; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.LineHorizontal(point, w - point, h - point, color_); canvas.LineVertical(point, point, h - point, color_); canvas.Line(point * class="num">2, point * class="num">13, point * class="num">8, point * class="num">9, color_); for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++)
把坐标数组按画布比例放大
在 MT5 的 CCanvas 绘图里,直接写死像素坐标会随窗口大小变形,正确做法是先取图表宽高,再算一个基准单位 point。下面这段把图表宽高读出来,取较小边除以 15 作为缩放步长:窗口若是 600×400,point 就是 26,所有图形会按比例铺在左下区域。 w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0); h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0); minSize = (int)fmin(w, h); point = minSize / 15; 拿到 point 后,折线 plX/plY 和多边形 pgX/pgY 这两组数组都要逐个乘 point 再交给 canvas。注意 Polyline 与 Polygon 接收的是放大后的整型数组,若漏掉循环缩放,画出来会挤在 (0,0) 附近一小块。 实际绘制时还混了 Rectangle、Triangle、Circle、Ellipse、Arc、Pie 等图元,参数也都是「point 倍数」写法,比如 Arc 的圆心在 (15*point, 2*point)、半径 2*point,起止角用 M_PI/180 转弧度。最后 Update(true) 刷新、Sleep(6000) 停留 6 秒、Destroy() 清掉画布——想验证就建个脚本把这段塞进 OnStart,挂 EURUSD 图表看 6 秒图形布局。外汇与贵金属图表叠加自绘图形属高风险辅助手段,图形本身不构成方向判断。
w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; class="type">int plX[] = {class="num">6, class="num">8, class="num">9, class="num">11, class="num">12, class="num">14}, plY[] = {class="num">14, class="num">12, class="num">13, class="num">11, class="num">12, class="num">10}; class="type">int pgX[] = {class="num">15, class="num">14, class="num">15, class="num">17, class="num">18, class="num">17}, pgY[] = {class="num">9, class="num">7, class="num">5, class="num">5, class="num">7, class="num">9}; for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++) plY[i] *= point; canvas.Polyline(plX, plY, color_); for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.Polygon(pgX, pgY, color_); canvas.Rectangle(point * class="num">2, point * class="num">5, point * class="num">7, point, color_); canvas.Triangle(point * class="num">2, point * class="num">11, point * class="num">2, point * class="num">6, point * class="num">7, point * class="num">6, color_); canvas.Circle(point * class="num">10, point * class="num">3, point * class="num">2, color_); canvas.Ellipse(point * class="num">8, point * class="num">9, point * class="num">12, point * class="num">6, color_); canvas.Arc(point * class="num">15, point * class="num">2, point * class="num">2, point, class="num">45.0 * M_PI / class="num">180, class="num">180.0 * M_PI / class="num">180, color_); canvas.Pie(point * class="num">16, point * class="num">3, point * class="num">2, point, class="num">180.0 * M_PI / class="num">180, class="num">402.5 * M_PI / class="num">180, color_, fillColor); canvas.Update(true); Sleep(class="num">6000); canvas.Destroy();
◍ 用 CCanvas 把几何图元画进图表
在 MT5 里做自定义盯盘面板,CCanvas 是最直接的光栅绘图接口。上面的片段演示了如何在一张位图标签上同时铺多边形、矩形、三角、圆、椭圆、弧和扇区——全部坐标都先乘了 point(= minSize/15),这样图形会随窗口尺寸等比缩放,不会在 4K 屏和笔记本上变形。 抗锯齿版(AA 后缀)和普通版要分开看:LineAA、PolylineAA、PolygonAA、TriangleAA 走的是平滑边缘管线,适合做价格通道和形态轮廓;非 AA 的 Rectangle / Circle / Arc / Pie 则更省 CPU,做高频刷新的辅助标记更稳。 注意 Pie 的最后两个参数是弧度制,代码里用 180.0*M_PI/180 到 402.5*M_PI/180 画了一块约 222.5° 的红色扇区,fillColor 单独传参;若你想改扇区开口角度,直接调这两个浮点常数即可,不用动前面坐标数组。 外汇与贵金属图表叠加自绘图形属于高风险辅助手段,图形仅反映历史坐标关系,不预示后续波动。
pgY[i] *= point; canvas.Polygon(pgX, pgY, color_); canvas.Polygon(pgX, pgY, color_); canvas.Rectangle(point * class="num">2, point * class="num">5, point * class="num">7, point, color_); canvas.Triangle(point * class="num">2, point * class="num">11, point * class="num">2, point * class="num">6, point * class="num">7, point * class="num">6, color_); canvas.Circle(point * class="num">10, point * class="num">3, point * class="num">2, color_); canvas.Ellipse(point * class="num">8, point * class="num">9, point * class="num">12, point * class="num">6, color_); canvas.Arc(point * class="num">15, point * class="num">2, point * class="num">2, point, class="num">45.0 * M_PI / class="num">180, class="num">180.0 * M_PI / class="num">180, color_); canvas.Pie(point * class="num">16, point * class="num">3, point * class="num">2, point, class="num">180.0 * M_PI / class="num">180, class="num">402.5 * M_PI / class="num">180, color_, fillColor); class="type">void OnStart() { CCanvas canvas; class="type">int w, h; class="type">int minSize, point; class="type">uint color_ = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint fillColor = ColorToARGB(clrRed, class="num">255); class="type">int plX[] = {class="num">6, class="num">8, class="num">9, class="num">11, class="num">12, class="num">14}, plY[] = {class="num">13, class="num">11, class="num">12, class="num">10, class="num">11, class="num">9}; class="type">int pgX[] = {class="num">15, class="num">14, class="num">15, class="num">17, class="num">18, class="num">17}, pgY[] = {class="num">7, class="num">5, class="num">3, class="num">3, class="num">5, class="num">7}; ENUM_LINE_STYLE lineStyle = STYLE_SOLID; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.LineStyleSet(lineStyle); canvas.LineAA(point * class="num">2, point * class="num">12, point * class="num">8, point * class="num">8, color_); for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++) plY[i] *= point; canvas.PolylineAA(plX, plY, color_); for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.PolygonAA(pgX, pgY, color_); canvas.TriangleAA(point * class="num">2, point * class="num">9, point * class="num">2, point * class="num">4, point * class="num">7, point * class="num">4, color_);
「正文」
canvas.CircleAA(point * <span class="number">16</span>, point * <span class="number">11</span>, point * <span class="number">2</span>, color_); canvas.EllipseAA(point * <span class="number">8</span>, point * <span class="number">4</span>, point * <span class="number">12</span>, point * <span class="number">7</span>, color_);</span> canvas.Update(<span class="macro">true</span>); <span class="functions">Sleep</span>(<span class="number">6000</span>); canvas.Destroy(); } <span class="preprocessor">#property </span><span class="macro">script_show_inputs</span> <span class="comment">//--- input parameters</span> <span class="keyword">input</span> <span class="keyword">int</span> sleep = <span class="number">1000</span>; <span class="preprocessor">#define </span>drawPrimitives canvas.Erase(<span class="functions">ColorToARGB</span>(<span class="macro">clrWhite</span>, <span class="number">255</span>)); \ canvas.LineAA(point * <span class="number">2</span>, point * <span class="number">12</span>, point * <span class="number">8</span>, point * <span class="number">8</span>, color_);
用 CCanvas 在图表上画几何图元
在 MT5 脚本里做可视化标记,CCanvas 是最直接的对象:它能在图表上创建位图标签,然后用一组 AA(抗锯齿)方法画线、面、圆。下面这段把坐标系按图表短边等分成 15 份,每份作为一个 point 单位,所有图元位置都乘 point,保证不同分辨率下比例一致。 实际绘制时,LineAA 画一条从 (2,12) 到 (8,8) 的线段;PolylineAA 吃两个数组 plX、plY,连出 6 个点(原数组 x: 6,8,9,11,12,14 / y: 13,11,12,10,11,9)的折线;PolygonAA 同理用 pgX、pgY 的 6 点围出填充多边形。 TriangleAA 用三个坐标对画三角形,CircleAA 在 (16,11) 以半径 2*point 画圆,EllipseAA 在 (8,4)-(12,7) 的包围盒里画椭圆。每帧末尾 canvas.Update(true) 强制刷新,Sleep 控制节奏。 外汇与贵金属杠杆高、波动剧烈,这类自绘仅作辅助观察,不构成任何方向判断。开 MT5 新建脚本,把下面代码贴进 OnStart 前的绘制段,改 clrDodgerBlue 的 ARGB 透明度(当前 255 全不透明),就能看不同叠加效果。
canvas.LineAA(point * class="num">2, point * class="num">12, point * class="num">8, point * class="num">8, color_); canvas.PolylineAA(plX, plY, color_); canvas.PolygonAA(pgX, pgY, color_); canvas.TriangleAA(point * class="num">2, point * class="num">9, point * class="num">2, point * class="num">4, point * class="num">7, point * class="num">4, color_); canvas.CircleAA(point * class="num">16, point * class="num">11, point * class="num">2, color_); canvas.EllipseAA(point * class="num">8, point * class="num">4, point * class="num">12, point * class="num">7, color_); canvas.Update(true); Sleep(sleep); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CCanvas canvas; class="type">int w, h; class="type">int minSize, point; class="type">uint color_ = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint fillColor = ColorToARGB(clrRed, class="num">255); class="type">int plX[] = {class="num">6, class="num">8, class="num">9, class="num">11, class="num">12, class="num">14}, plY[] = {class="num">13, class="num">11, class="num">12, class="num">10, class="num">11, class="num">9}; class="type">int pgX[] = {class="num">15, class="num">14, class="num">15, class="num">17, class="num">18, class="num">17}, pgY[] = {class="num">7, class="num">5, class="num">3, class="num">3, class="num">5, class="num">7}; class=class="str">"cmt">//ENUM_LINE_STYLE lineStyle; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++) plY[i] *= point;
◍ 用宏打包抗锯齿图元绘制
这段脚本把坐标数组按 point 缩放后,连续切换四种线型重画同一组图元,肉眼能直接比对实线、点线、虚线、点划线在 MT5 画布上的边缘表现。 核心技巧是 #define drawPrimitives 这个宏:它把擦白、画线段、折线、多边形、三角形、圆、椭圆、刷新画布、Sleep(sleep) 全部压成一行调用。改线型只需在前后插 canvas.LineStyleSet(),宏体内不变,代码重复度直接砍掉一大半。 注意宏里 LineWu 起点是 (point*2, point*12) 到 (point*8, point*8),CircleWu 半径用了 point*2,EllipseWu 包围盒从 (point*8, point*4) 到 (point*12, point*7)——这些乘子就是你在 4K 屏和高 DPI 下调清晰度的旋钮。 input int sleep=1000 控制每帧停顿,主循环里另有 Sleep(6000) 做整轮演示间隔;外汇与贵金属图表叠加这类自绘层时波动跳变频繁,脚本仅作渲染验证,实盘叠加须自担高风险。
for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.LineStyleSet(STYLE_SOLID); drawPrimitives canvas.LineStyleSet(STYLE_DOT); drawPrimitives canvas.LineStyleSet(STYLE_DASH); drawPrimitives canvas.LineStyleSet(STYLE_DASHDOTDOT); drawPrimitives Sleep(class="num">6000); canvas.Destroy(); } class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//--- class="kw">input parameters class="kw">input class="type">int sleep = class="num">1000; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, class="num">255)); \ canvas.LineWu(point * class="num">2, point * class="num">12, point * class="num">8, point * class="num">8, color_); \ canvas.PolylineWu(plX, plY, color_); \ canvas.PolygonWu(pgX, pgY, color_); \ canvas.TriangleWu(point * class="num">2, point * class="num">9, point * class="num">2, point * class="num">4, point * class="num">7, point * class="num">4, color_); \ canvas.CircleWu(point * class="num">16, point * class="num">11, point * class="num">2, color_); \ canvas.EllipseWu(point * class="num">8, point * class="num">4, point * class="num">12, point * class="num">7, color_); \ canvas.Update(true); \ Sleep(sleep);
「用画布把折线图直接铺到图表上」
想在 MT5 图表里脱离指标窗口、直接画自定义图形,CCanvas 是最直接的路。下面这段脚本在 OnStart 里先抓当前图表宽高像素,取较小边除以 15 作为单位点距 point,再把两组预设坐标 plX/plY 与 pgX/pgY 整体乘以 point,等于把 6 点折线按屏幕自适应缩放。 代码里 color_ 用 ColorToARGB(clrDodgerBlue,255) 拿到不透明蓝,fillColor 拿不透明红,LineStyleSet(STYLE_SOLID) 后调 drawPrimitives 画实线。被注释掉的 STYLE_DOT / STYLE_DASH / STYLE_DASHDOTDOT 分支说明同一套坐标可快速切换线型重绘,不用改顶点。 脚本尾部 Sleep(6000) 让图形停留 6 秒再 Destroy() 清掉画布;另有一个 input int sleep=1000 参数虽声明却未在 OnStart 主流程使用,属于预留接口。外汇与贵金属图表叠加自绘图形波动剧烈,实盘前务必在模拟盘验证坐标缩放逻辑,避免点距算错导致图形溢出可视区。 开 MT5 新建脚本,把 Canvas.mqh 包含进来,复制下面代码跑一遍,能看到蓝红折线按窗口大小自适应浮现;改 point 的除数 15 为 10,图形会明显放大。
class="type">void OnStart() { CCanvas canvas; class="type">int w, h; class="type">int minSize, point; class="type">uint color_ = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint fillColor = ColorToARGB(clrRed, class="num">255); class="type">int plX[] = {class="num">6, class="num">8, class="num">9, class="num">11, class="num">12, class="num">14}, plY[] = {class="num">13, class="num">11, class="num">12, class="num">10, class="num">11, class="num">9}; class="type">int pgX[] = {class="num">15, class="num">14, class="num">15, class="num">17, class="num">18, class="num">17}, pgY[] = {class="num">7, class="num">5, class="num">3, class="num">3, class="num">5, class="num">7}; class=class="str">"cmt">//ENUM_LINE_STYLE lineStyle; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++) plY[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.LineStyleSet(STYLE_SOLID); drawPrimitives class=class="str">"cmt">//canvas.LineStyleSet(STYLE_DOT); class=class="str">"cmt">//drawPrimitives class=class="str">"cmt">//canvas.LineStyleSet(STYLE_DASH); class=class="str">"cmt">//drawPrimitives class=class="str">"cmt">//canvas.LineStyleSet(STYLE_DASHDOTDOT); class=class="str">"cmt">//drawPrimitives Sleep(class="num">6000); canvas.Destroy(); } class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//--- class="kw">input parameters class="kw">input class="type">int sleep = class="num">1000; class="macro">#include <Canvas\Canvas.mqh>
把坐标数组换算成画布像素
在 MT5 用 CCanvas 画自定义图形时,最容易被忽略的一步是坐标缩放。上面这段代码先把折线 plX/plY 和多边形 pgX/pgY 的「逻辑坐标」各自乘上 point,point 由 minSize/15 得出,意味着整张图被切成了 15 格基准单位。 逻辑坐标写死并不稀奇:plX 是 {6,8,9,11,12,14},pgX 是 {17,18,17,15,14,15,17},乘完 point 才变成真实像素。这样做的好处是换图表尺寸也不用改原始数组,图表窗口从 800 像素变 1200 像素,图形比例依旧稳。 顺带提一句,color_ 用了 ColorToARGB(clrDodgerBlue,255),fillColor 用了 clrRed,线宽 size=3、线帽 LINE_END_ROUND。外汇和贵金属图表叠加这类自绘层属于高风险辅助,信号仅作概率参考,别当成进出场唯一依据。
for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++) plY[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point;
◍ 用宏批量切换线型与线端画法
这段脚本把 Canvas 绘图里反复出现的「设线型→画→换线端→清屏」流程压成了三层宏。最里层 method0 只做一件事:用纯白不透明背景擦掉画布(ColorToARGB(clrWhite,255) 后 Erase)。 method1 在 method0 前后插了三种线端样式:LINE_END_ROUND、LINE_END_BUTT、LINE_END_SQUARE,每切一次就调一次 method0,等于把同一条路径用三种收口方式各画一遍再清掉。 drawPrimitives 再往外扩,依次把线型设为 STYLE_SOLID、STYLE_DOT、STYLE_DASH、STYLE_DASHDOTDOT,每种线型下都跑完整 method1。脚本输入里 beginSize=1、endSize=4 控制线宽起止,sleep 默认 1000 毫秒。 主循环里 pgY[i] *= point 把归一化坐标还原成点数,LineStyleSet(STYLE_SOLID) 后直接 drawPrimitives,最后 Sleep(6000) 停留 6 秒再 Destroy。想看虚线点线差异,把 sleep 调到 2000 以上在 MT5 里跑一遍最直观。
for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.LineStyleSet(STYLE_SOLID); drawPrimitives class=class="str">"cmt">//canvas.LineStyleSet(STYLE_DOT); class=class="str">"cmt">//drawPrimitives class=class="str">"cmt">//canvas.LineStyleSet(STYLE_DASH); class=class="str">"cmt">//drawPrimitives class=class="str">"cmt">//canvas.LineStyleSet(STYLE_DASHDOTDOT); drawPrimitives Sleep(class="num">6000); canvas.Destroy(); } class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//--- class="kw">input parameters class="kw">input class="type">int sleep = class="num">1000; class="kw">input class="type">int beginSize = class="num">1; class="kw">input class="type">int endSize = class="num">4; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define drawPrimitives lineStyle = STYLE_SOLID; \ method1 \ lineStyle = STYLE_DOT; \ method1 \ lineStyle = STYLE_DASH; \ method1 \ lineStyle = STYLE_DASHDOTDOT; \ method1 class="macro">#define method1 endStyle = LINE_END_ROUND; \ method0 \ endStyle = LINE_END_BUTT; \ method0 \ endStyle = LINE_END_SQUARE; \ method0 class="macro">#define method0 canvas.Erase(ColorToARGB(clrWhite, class="num">255));
「用 CCanvas 把线宽与端点样式画出来」
在 MT5 脚本里用 CCanvas 做图形自检,最直观的办法就是把不同线型一次性铺到画布上对比。下面这段从 OnStart 抽出的绘制调用,把竖直、水平、斜线、折线、多边形和文字说明全丢进同一张图,参数里的 size、lineStyle、endStyle 直接决定视觉粗细与收口形态。 canvas.LineThickVertical 画的是从 (point, point) 到 (point, h-point) 的竖线;LineThickHorizontal 同理走底部横向。LineThick(point*2, point*12, point*8, point*8, ...) 拉一条对角短线,坐标都是按 point 倍数算的,point 一般取 10~15 像素才看得清。 PolylineThick 吃两个数组 plX、plY,样例给的是 {6,8,9,11,12,14} 和 {13,11,12,10,11,9},也就是在画布右上区画一段 6 点折线;PolygonThick 用 pgX、pgY 封口填色。TextOut 把当前 size、线型枚举、端点枚举拼成字符串贴在 (point*2, point) 位置,方便你边跑边读。 最后 canvas.Update(true) 强制重绘,Sleep(sleep) 控节奏。外汇与贵金属图表叠加这类自绘层时波动快,高风险,参数没调稳前别挂实盘窗口。
canvas.LineThickVertical(point, point, h - point, color_, size, lineStyle, endStyle); canvas.LineThickHorizontal(point, w - point, h - point, color_, size, lineStyle, endStyle); canvas.LineThick(point * class="num">2, point * class="num">12, point * class="num">8, point * class="num">8, color_, size, lineStyle, endStyle); canvas.PolylineThick(plX, plY, color_, size, lineStyle, endStyle); canvas.PolygonThick(pgX, pgY, color_, size, lineStyle, endStyle); canvas.TextOut(point * class="num">2, point, "Size: " + (class="type">class="kw">string)size + "; Style: " + EnumToString(lineStyle) + "; End Style: " + EnumToString(endStyle) + ";", color_); canvas.Update(true); Sleep(sleep);
用画布把坐标数组拉伸成图表图元
在 MT5 脚本里用 CCanvas 做自定义绘图时,第一步是把逻辑坐标映射到像素。上面这段代码先取图表宽高:w 和 h 分别来自 ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) 和 CHART_HEIGHT_IN_PIXELS,再用 fmin(w,h) 得出 minSize,除以 15 得到 point 作为单位缩放因子。 plX、plY、pgX、pgY 这几组数组存的是相对坐标(例如 plX 初值是 3,5,6,8,9,11),通过四个 for 循环逐个乘上 point,就把它们从“逻辑格子”变成了真实像素偏移。这一步不做,画出来的线会缩在左上角一小块。 高亮段里 lineStyle 设为 STYLE_SOLID、endStyle 为 LINE_END_ROUND、size 从 beginSize 到 endSize 循环递增并调用 drawPrimitives,说明同一图元会按不同线宽重绘多遍,视觉上可能呈现由细到粗的描边动画。 最后 Sleep(6000) 后 canvas.Destroy() 释放资源。外汇与贵金属图表叠加这类自绘层属高风险环境操作,参数误用可能导致图表卡顿,建议在模拟账户先验证。
class="type">int pgX[] = {class="num">17, class="num">18, class="num">17, class="num">15, class="num">14, class="num">15, class="num">17}, pgY[] = {class="num">7, class="num">5, class="num">3, class="num">3, class="num">5, class="num">7, class="num">7}; ENUM_LINE_STYLE lineStyle = STYLE_SOLID; class="type">int size = class="num">3; ENUM_LINE_END endStyle = LINE_END_ROUND; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); canvas.FontSet("Calibri", -class="num">120); for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++) plY[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; for (class="type">int i = beginSize; i <= endSize; i++) { size = i; drawPrimitives } Sleep(class="num">6000); canvas.Destroy(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CCanvas canvas; class="type">int w, h; class="type">int minSize, point; class="type">uint color_ = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint fillColor = ColorToARGB(clrRed, class="num">255); class="type">int plX[] = {class="num">3, class="num">5, class="num">6, class="num">8, class="num">9, class="num">11}, plY[] = {class="num">13, class="num">11, class="num">12, class="num">10, class="num">11, class="num">9}; class="type">int pgX[] = {class="num">14, class="num">15, class="num">14, class="num">12, class="num">11, class="num">12}, pgY[] = {class="num">7, class="num">5, class="num">3, class="num">3, class="num">5, class="num">7}; class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//--- class="kw">input parameters class="kw">input class="type">int sleep = class="num">1000; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用 CCanvas 把坐标点画成平滑线框
在 MT5 脚本里用 CCanvas 做自定义绘图时,最容易被忽略的一步是坐标缩放。上面这段例子先取图表像素宽高,用 fmin(w,h)/15 算出 point,再把 plX、plY、pgX 里写死的网格坐标(如 3、5、6、8、9、11)统一乘 point,等于把 15 格的逻辑画布映射到实际窗口最短边。 宏 drawPrimitives 里连续调了 PolylineSmooth 和 PolygonSmooth:前者吃 plX/plY 画一条 DodgerBlue 平滑折线,后者吃 pgX/pgY 画红色填充多边形,最后 Update(true) 刷新、Sleep 控节奏。注意 pgX 原数组长度是 6(14,15,14,12,11,12),而 pgY 也是 6 个值,配对没问题,但若你改点位数务必两边等长,否则运行时直接数组越界。 外汇和贵金属图表高波动,这种叠加层只作视觉辅助,不改变任何报价;实盘前请在策略测试器里先跑一遍看渲染是否吃 CPU。
class="macro">#define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, class="num">255)); \ canvas.PolylineSmooth(plX, plY, color_, size, lineStyle, endStyle); \ canvas.PolygonSmooth(pgX, pgY, color_, size, lineStyle, endStyle); \ canvas.Update(true); \ Sleep(sleep); class="type">void OnStart() { CCanvas canvas; class="type">int w, h; class="type">int minSize, point; class="type">uint color_ = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint fillColor = ColorToARGB(clrRed, class="num">255); class="type">int plX[] = {class="num">3, class="num">5, class="num">6, class="num">8, class="num">9, class="num">11}, plY[] = {class="num">13, class="num">11, class="num">12, class="num">10, class="num">11, class="num">9}; class="type">int pgX[] = {class="num">14, class="num">15, class="num">14, class="num">12, class="num">11, class="num">12}, pgY[] = {class="num">7, class="num">5, class="num">3, class="num">3, class="num">5, class="num">7}; ENUM_LINE_STYLE lineStyle = STYLE_SOLID; class="type">int size = class="num">3; ENUM_LINE_END endStyle = LINE_END_BUTT; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++) plY[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point;
「用 Canvas 把坐标点阵铺到图表像素上」
在 MT5 脚本里做自定义绘图,第一步是把逻辑坐标映射到实际像素。上面这段代码取了图表宽高像素值,用 fmin(w,h)/15 算出单个 point 基准,再把 plX、plY 两个数组里的每个元素乘以 point,相当于把 0~15 的虚拟网格拉伸到当前窗口最短边上。 输入参数里 lineSize 默认 3、minTension 到 maxTension 从 0.0 走到 1.0、minStep 到 maxStep 从 1.0 到 21.0,这些会直接决定后面曲线张力和采样密度。开 MT5 把 Canvas.mqh 包含进来,改 lineSize 从 3 到 10,能直观看到线宽对图形压迫感的变化。 pgY 数组在另一段循环里也做了同样乘 point 处理,配合 STYLE_SOLID 线型和 6000 毫秒的 Sleep,整套图绘制完会停留 6 秒再 Destroy 清掉。外汇与贵金属图表叠加这类自绘层时需注意,高频刷新可能拖慢报价响应,属高风险操作环境,参数宜小步验证。
for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.LineStyleSet(STYLE_SOLID); drawPrimitives Sleep(class="num">6000); canvas.Destroy(); } class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Canvas\Canvas.mqh> class=class="str">"cmt">//--- class="kw">input parameters class="kw">input class="type">int sleep = class="num">1000; class="kw">input class="type">int lineSize = class="num">3; class="kw">input ENUM_LINE_STYLE lineStyle = STYLE_SOLID; class="kw">input ENUM_LINE_END lineEndStyle = LINE_END_BUTT; class="kw">input class="type">class="kw">double minTension = class="num">0.0; class="kw">input class="type">class="kw">double stepTension = class="num">0.1; class="kw">input class="type">class="kw">double maxTension = class="num">1.0; class="kw">input class="type">class="kw">double minStep = class="num">1.0; class="kw">input class="type">class="kw">double stepStep = class="num">5.0; class="kw">input class="type">class="kw">double maxStep = class="num">21.0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { CCanvas canvas; class="type">int w, h; class="type">int minSize, point; class="type">uint color_ = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint fillColor = ColorToARGB(clrRed, class="num">255); class="type">int plX[] = {class="num">3, class="num">5, class="num">6, class="num">8, class="num">9, class="num">11}, plY[] = {class="num">13, class="num">11, class="num">12, class="num">10, class="num">11, class="num">9}; class="type">int pgX[] = {class="num">14, class="num">15, class="num">14, class="num">12, class="num">11, class="num">12}, pgY[] = {class="num">7, class="num">5, class="num">3, class="num">3, class="num">5, class="num">7}; ENUM_LINE_STYLE style = lineStyle; class="type">int size = lineSize; ENUM_LINE_END endStyle = lineEndStyle; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); canvas.FontSet("Calibri", -class="num">120); for (class="type">int i = class="num">0; i < (class="type">int)plX.Size(); i++) plX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)plY.Size(); i++) plY[i] *= point;
用画布接口批量试出平滑曲线手感
在 MT5 的 CCanvas 里,pgX、pgY 这类多边形顶点数组先乘上 point(由图表短边除以 15 得到),等于把逻辑坐标按当前窗口尺寸自适应缩放,避免写死像素导致 4K 屏和小屏画出来比例失调。 真正调平滑质感的是双层循环:tension 从 minTension 到 maxTension 按 stepTension 递增,step 同理。每换一组参数就 Erase 白底、跑 PolylineSmooth 和 PolygonSmooth、打两行文字标注当前 Size / Style / Tension / Step,然后 Update(true) 加 Sleep 逐帧看效果。 OnStart 里那段静态填充更直白:FillPolygon 用 6 组顶点画红块,FillTriangle 三个点分别是 (4,8)、(4,3)、(9,3) 乘 point,FillCircle 圆心在 (13,12) 半径 2*point,FillEllipse 框 (11,3)-(15,6)。Sleep(6000) 让你有 6 秒肉眼确认图形对不对,再改参数重跑。外汇与贵金属图表叠加这类自绘层时波动快,高风险下别拿它当信号主层。
for(class="type">int i=class="num">0; i<(class="type">int)pgX.Size(); i++) pgX[i] *= point; for(class="type">int i=class="num">0; i<(class="type">int)pgY.Size(); i++) pgY[i] *= point; for(class="type">class="kw">double tension = minTension; tension <= maxTension; tension += stepTension) for(class="type">class="kw">double step = minStep; step <= maxStep; step += stepStep) { canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.PolylineSmooth(plX, plY, color_, size, style, endStyle, tension, step); canvas.PolygonSmooth(pgX, pgY, color_, size, style, endStyle, tension, step); canvas.TextOut(point * class="num">2, point, "Size: " + (class="type">class="kw">string)size + "; Style: " + EnumToString(style) + "; End Style: " + EnumToString(endStyle) + ";", color_); canvas.TextOut(point * class="num">2, point * class="num">2, "Tension: " + DoubleToString(tension, class="num">2) + ";" + " Step: " + DoubleToString(step, class="num">2) + ";", color_); canvas.Update(true); Sleep(sleep); } canvas.Destroy(); } class="type">void OnStart() { CCanvas canvas; class="type">int w, h; class="type">int minSize, point; class="type">uint fillColor = ColorToARGB(clrRed, class="num">255); class="type">int pgX[] = {class="num">4, class="num">5, class="num">7, class="num">8, class="num">7, class="num">5}, pgY[] = {class="num">12, class="num">10, class="num">10, class="num">12, class="num">14, class="num">14}; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); for(class="type">int i=class="num">0; i<(class="type">int)pgX.Size(); i++) pgX[i] *= point; for(class="type">int i=class="num">0; i<(class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.FillPolygon(pgX, pgY, fillColor); canvas.FillTriangle(point * class="num">4, point * class="num">8, point * class="num">4, point * class="num">3, point * class="num">9, point * class="num">3, fillColor); canvas.FillCircle(point * class="num">13, point * class="num">12, point * class="num">2, fillColor); canvas.FillEllipse(point * class="num">11, point * class="num">3, point * class="num">15, point * class="num">6, fillColor); canvas.Update(true); Sleep(class="num">6000);
◍ 在图表上用 Canvas 画一个自定义图形标记
MT5 的 CCanvas 类可以直接在主图创建位图标签,把绘图结果叠加在价格上方,不占用指标子窗口。上面这段代码在 OnInit 里先取图表像素宽高,再按短边 minSize 的 1/15 算出基准单位 point,保证图形在不同屏幕尺寸下比例一致。 具体绘制时,先用 Erase 填白底,再用 Polygon 画一个 6 点多边形(pgX、pgY 各乘 point 后坐标分别为 4/5/7/8/7/5 与 12/10/10/12/14/14),填充色为不透明的 DodgerBlue。随后用 FillTriangle 在左上方画红三角,FillCircle 在右下画红圆,Ellipse 画蓝椭圆,最后 Update(true) 推到图表。 想验证效果,新建一个 indicator_plots 0 的自定义指标,把 Canvas.mqh 和 ChartObjectsTxtControls.mqh 包含进去,挂上 EURUSD 图表即可看到图形。外汇与贵金属杠杆高,这类标记仅作视觉辅助,不改变任何订单逻辑与风险暴露。
class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_plots class="num">0 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Canvas\Canvas.mqh> class="macro">#include <ChartObjects\ChartObjectsTxtControls.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ CCanvas canvas; CChartObjectButton colors[class="num">14]; CChartObjectButton * oldSelected = NULL; class="type">uint fillColor = class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="type">int w, h; class="type">int minSize, point; class="type">uint Color1 = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint Color2 = ColorToARGB(clrRed, class="num">255); class="type">int pgX[] = {class="num">4, class="num">5, class="num">7, class="num">8, class="num">7, class="num">5}, pgY[] = {class="num">12, class="num">10, class="num">10, class="num">12, class="num">14, class="num">14}; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); canvas.FontSet("Calibri", -class="num">120); for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.Polygon(pgX, pgY, Color1); canvas.FillTriangle(point * class="num">4, point * class="num">8, point * class="num">4, point * class="num">3, point * class="num">9, point * class="num">3, Color2); canvas.FillCircle(point * class="num">13, point * class="num">12, point * class="num">2, Color2); canvas.Ellipse(point * class="num">11, point * class="num">3, point * class="num">15, point * class="num">6, Color1); canvas.Update(true); ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, true); for (class="type">int i = class="num">0; i < (class="type">int)colors.Size(); i++) {
「按钮命中区与鼠标状态位运算」
这段逻辑出现在自定义指标的 OnChartEvent 里,专门处理 CHARTEVENT_MOUSE_MOVE 事件下的左键拖拽判定。mouseState 来自 sparam 强转成 uint,用位与 (mouseState & 1) == 1 确认当前鼠标左键处于按下状态,只有按住才进入颜色按钮的命中检测循环。 循环里逐个取 colors 数组里的按钮指针,先拿 X_Distance / Y_Distance 得到按钮左上角坐标,再叠加 X_Size-1 和 Y_Size-1 算出右下边界。right = left + X_Size() - 1、bottom = top + Y_Size() - 1 这两个减 1 是关键,避免边界像素被重复计入相邻按钮。 顺带看初始化收尾:oldSelected 取 colors 最后一个元素指针,失败直接 INIT_FAILED;成功则置 State(1) 并把该按钮背景色经 ColorToARGB(…,255) 写进 fillColor 作为默认填充。OnCalculate 目前只回传 rates_total,没做实际指标缓冲计算,属于占位骨架。 开 MT5 把这段塞进指标工程,故意把 -1 去掉跑一次,能直观看到鼠标移到按钮接缝时两个按钮同时高亮的越界现象。外汇与贵金属图表加载此类自定义对象指标存在脚本异常风险,建议先在模拟盘环境验证。
class="type">uint mouseState = (class="type">uint)sparam; class="type">int x = (class="type">int)lparam, y = (class="type">int)dparam; CChartObjectButton * colorBtn; class="type">int left, right, bottom, top; if (id == CHARTEVENT_MOUSE_MOVE) if ((mouseState & class="num">1) == class="num">1) { for (class="type">int i = class="num">0; i < (class="type">int)colors.Size(); i++) { if ((colorBtn = GetPointer(colors[i])) == NULL) class="kw">return; left = colorBtn.X_Distance(); top = colorBtn.Y_Distance(); right = left + colorBtn.X_Size() - class="num">1; bottom = top + colorBtn.Y_Size() - class="num">1;
用画布坐标判断按钮命中区
在 MT5 自定义面板里,颜色按钮的选中逻辑依赖像素级命中检测。下面这段把鼠标坐标 (x,y) 限制在按钮的 left/right/top/bottom 矩形内,命中后才去取背景色并切换状态,避免误触相邻控件。 命中后先读 colorBtn 的 OBJPROP_BGCOLOR 转成 ARGB(alpha 写死 255),若 oldSelected 为空直接 return;否则把旧按钮 State(0) 取消选中,再把 oldSelected 指向当前按钮并 ChartRedraw,界面才实时刷新。 末尾 canvas.Fill(x, y, fillColor) 与 canvas.Update(true) 是真正落像素的两行——注意 Update 传 true 会强制立即重绘,在 60fps 图表上可能吃掉少量 CPU,外汇与贵金属行情高波动时须留意卡顿风险。 初始化里 w、h 取自 CHART_WIDTH_IN_PIXELS / CHART_HEIGHT_IN_PIXELS,minSize 取两者较小值再除 15 得到 point 基准单位;pgX、pgY 数组乘 point 后变成实际像素多边形,用 Polygon 画六点图形、FillTriangle 与 FillCircle 补红块,证实画布坐标体系完全由 point 缩放驱动。
if (x >= left && x <= right && y >= top && y <= bottom) { fillColor = ColorToARGB((class="type">color)colorBtn.GetInteger(OBJPROP_BGCOLOR), class="num">255); if (oldSelected == NULL) class="kw">return; oldSelected.State(class="num">0); oldSelected = GetPointer(colorBtn); ChartRedraw(); class="kw">return; } } canvas.Fill(x, y, fillColor); canvas.Update(true); } } CCanvas canvas; CChartObjectButton colors[class="num">14]; CChartObjectButton * oldSelected = NULL; class="type">uint fillColor = class="num">0; class="type">int OnInit() { class="type">int w, h; class="type">int minSize, point; class="type">uint Color1 = ColorToARGB(clrDodgerBlue, class="num">255); class="type">uint Color2 = ColorToARGB(clrRed, class="num">255); class="type">int pgX[] = {class="num">4, class="num">5, class="num">7, class="num">8, class="num">7, class="num">5}, pgY[] = {class="num">12, class="num">10, class="num">10, class="num">12, class="num">14, class="num">14}; w = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0); h = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0); minSize = (class="type">int)fmin(w, h); point = minSize / class="num">15; canvas.CreateBitmapLabel("canvas", class="num">0, class="num">0, w, h, COLOR_FORMAT_XRGB_NOALPHA); canvas.FontSet("Calibri", -class="num">120); for (class="type">int i = class="num">0; i < (class="type">int)pgX.Size(); i++) pgX[i] *= point; for (class="type">int i = class="num">0; i < (class="type">int)pgY.Size(); i++) pgY[i] *= point; canvas.Erase(ColorToARGB(clrWhite, class="num">255)); canvas.Polygon(pgX, pgY, Color1); canvas.FillTriangle(point * class="num">4, point * class="num">8, point * class="num">4, point * class="num">3, point * class="num">9, point * class="num">3, Color2); canvas.FillCircle(point * class="num">13, point * class="num">12, point * class="num">2, Color2);
◍ 用鼠标拖选调色盘给画布上色
在 MT5 的 CCanvas 自绘面板里,想实时用鼠标选颜色填充,第一步是打开图表鼠标事件:ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true) 之后,OnChartEvent 才能拿到鼠标坐标和按键状态。 颜色按钮用 CChartObjectButton 数组铺一排,循环中 colors[i].Create(0, "color-"+(string)i, 0, (int)((i+2.8)*point), point, 21, 21) 把每个按钮定在 (i+2.8)*point 的纵向偏移处,宽高固定 21×21 像素,背景色取自 CCanvas::GetDefaultColor(i)。 鼠标左键按住移动时(mouseState & 1 == 1),逐个按钮比对 x/y 是否落在 left~right、top~bottom 矩形内;命中就把该按钮背景色经 ColorToARGB(...,255) 写进 fillColor,同时把上一个 oldSelected 的 State(0) 取消选中,再 ChartRedraw() 刷新。 命中逻辑之外,canvas.Fill(x, y, fillColor) 配合 canvas.Update(true) 会把当前选色刷到鼠标点到的画布位置——外汇和贵金属图表叠加这类自绘层时,需注意高分屏 point 值变化会让 21 像素按钮热区偏移,建议开 MT5 改 point 系数实测命中率。
canvas.Ellipse(point * class="num">11, point * class="num">3, point * class="num">15, point * class="num">6, Color1); canvas.Update(true); ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, true); for (class="type">int i = class="num">0; i < (class="type">int)colors.Size(); i++) { colors[i].Create(class="num">0, "class="type">color-"+(class="type">class="kw">string)i, class="num">0, (class="type">int)((i + class="num">2.8) * point), point, class="num">21, class="num">21); colors[i].SetInteger(OBJPROP_BGCOLOR, ColorToARGB(CCanvas::GetDefaultColor(i), class="num">0)); } if ((oldSelected = GetPointer(colors[(class="type">int)colors.Size()-class="num">1])) == NULL) class="kw">return(INIT_FAILED); oldSelected.State(class="num">1); fillColor = ColorToARGB((class="type">color)oldSelected.GetInteger(OBJPROP_BGCOLOR), class="num">255); class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { class="type">uint mouseState = (class="type">uint)sparam; class="type">int x = (class="type">int)lparam, y = (class="type">int)dparam; CChartObjectButton * colorBtn; class="type">int left, right, bottom, top; if (id == CHARTEVENT_MOUSE_MOVE) if ((mouseState & class="num">1) == class="num">1) { ... } if (id == CHARTEVENT_MOUSE_MOVE) if ((mouseState & class="num">1) == class="num">1) { for (class="type">int i = class="num">0; i < (class="type">int)colors.Size(); i++) { if ((colorBtn = GetPointer(colors[i])) == NULL) class="kw">return; left = colorBtn.X_Distance(); top = colorBtn.Y_Distance(); right = left + colorBtn.X_Size() - class="num">1; bottom = top + colorBtn.Y_Size() - class="num">1; if (x >= left && x <= right && y >= top && y <= bottom) { fillColor = ColorToARGB((class="type">color)colorBtn.GetInteger(OBJPROP_BGCOLOR), class="num">255); if (oldSelected == NULL) class="kw">return; oldSelected.State(class="num">0); oldSelected = GetPointer(colorBtn); ChartRedraw(); class="kw">return; } } canvas.Fill(x, y, fillColor); canvas.Update(true); } }
「记住这一条就够了」
CCanvas 类在 MT5 里负责把图形直接画到图表背景或独立窗口,理解它的坐标体系和资源释放逻辑,比背方法列表更关键。 前面示例里用到的绘制调用,本质上都依赖 Canvas 的像素缓冲;若忘记在析构时释放 CCanvas 对象,EA 反复加载可能拖慢终端。 把本文的绘制原则落到实盘,先开 MT5 加载随附的 MQL5.zip(21.43 KB)跑一遍示例,再改一行线宽参数看重绘差异,比读十遍文档更直接。外汇与贵金属图表叠加自绘元素波动剧烈,高风险,验证时请用模拟盘。