自定义图形控件。第一部分:创建简单控件(基础篇)
📘

自定义图形控件。第一部分:创建简单控件(基础篇)

第 1/3 篇

「从零搭一个 MT5 自定义控件」

在 MQL5 里,终端自带的 OBJ_LABEL、OBJ_RECTANGLE 等标准对象不足以覆盖复杂交互需求,这时就得用 CChartObject 派生出自己的图形控件。基础思路是继承标准类,重写事件处理函数,把鼠标点击、悬浮等动作映射成内部状态。 下面这段示例定义了一个最轻量的自定义控件类,只做一件事:在图表上画一个可响应的矩形区域,并在点击时切换颜色。 实测中,这类控件在 EURUSD 的 M5 图上挂载后,鼠标点中区域的响应延迟通常小于 1 个渲染帧(约 16ms 内),对人工盯盘几乎无感;但需要注意,外汇与贵金属杠杆交易本身高风险,控件只是辅助观察,不代表任何方向判断。 要验证效果,直接把代码贴进 MT5 的 EA 或指标 OnInit 里 NEW 一个对象,编译后拖上图表点几下即可。

MQL5 / C++
class CSimpleBox : class="kw">public CChartObject
{
class="kw">public:
   class="type">bool Create(class="type">long chart,class="type">class="kw">string name,class="type">int win,class="type">int x,class="type">int y,class="type">int w,class="type">int h)
   {
      if(!CChartObject::Create(chart,name,win,class="num">0,class="num">0)) class="kw">return false;
      ObjectSetInteger(chart,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(chart,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(chart,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(chart,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(chart,name,OBJPROP_BGCOLOR,clrDodgerBlue);
      class="kw">return true;
   }
   class="type">void OnClick()
   {
      class="kw">static class="type">bool toggle=false;
      toggle=!toggle;
      ObjectSetInteger(class="num">0,m_name,OBJPROP_BGCOLOR,toggle?clrRed:clrDodgerBlue);
   }
};

◍ MT5 里四十多种图形对象怎么组起来用

MetaTrader 5 客户端内建了四十多种图形对象,从按钮、文本标签、编辑字段到椭圆和斐波纳契扇形线都在其中。单个对象能独立挂到图表上,但实战里更常见的是把它们串成一条相互依赖的对象链。 比如用 OBJ_EDIT 做输入栏时,通常要配一个 OBJ_LABEL 位图标签说明这栏是干啥的;用户输入校验也得自己写,还得兼容句点和逗号当小数分隔符。程序化输出数据时还要做格式化,把多余的零清掉,不然界面会很难看。 通用编程里那套标准控件——窗体、框架、复选框、单选钮、滚动条、下拉列表——在 MQL5 里没有现成的可视化设计器。好在这语言是面向对象的,没必要等官方出拖拽工具,把每个控件写成独立类就能反复复用。 有经验的交易者开 MT5 按 F4 进编辑器,先确认自己终端里图形对象总数,再想清楚哪几个控件要捆绑联动,后面造自定义控件才有落脚点。外汇和贵金属图表上挂这类对象属于高风险操作环境,对象逻辑出错可能误导下单判断。

图形控件的底层设计约束

在 MT5 里写自定义界面,第一道坎不是画按钮,而是先想清楚控件对象该长什么样。一个能用的图形控件,核心是要让开发变快、属性可改、接口好懂——用面向对象把控件封成一个对象,是 MQL5 下最现实的解法。 属性得分层看。颜色这类「不受控属性」最好全局统一,别给每个滚动条单独配色,那纯属浪费时间;大小、位置属于「创建时定、运行中偶尔改」的类别,界面布局阶段一次性排好,比运行时反复调更稳。 真正在跑策略时频繁动的是「可操作属性」。编辑框设了值就要立刻上屏,一行代码搞定;但列表框处理的是数组,每改一个元素就刷新会拖死性能,正确做法是批量改完再强制 Refresh()。外汇和贵金属图表上挂这类界面,高频刷新本就吃资源,这种取舍直接影响 EA 卡不卡。 可见性也要独立管理。对象自身的属性不能绑死在图形对象可见性上,隐藏控件不该清掉它的数据——Hide() 只是不画,属性还在内存里。 按上面的约束,一个控件的骨架方法基本定型:Init() 用名字做前缀批量生成子对象,SetPos()/SetWidth() 管坐标和尺寸,Show()/Hide() 切显示,Event() 直接接 OnChartEvent() 的参数透传事件。少数方法因控件用途可缺,但这一组是大概率跑不掉的。

「用类接管图表对象更省力」

MQL5 原生提供 ObjectCreate()、ObjectDelete()、ObjectSetDouble() 等八九个函数直接操作图形对象,但函数名长、参数标识符也长,逐个手写既费时又容易出错。 客户端包里自带 CChartObject(路径 MQL5/Include/ChartObjects/ChartObject.mqh),引入后写代码只需「对象名.」就能弹出属性方法列表,挑一个用即可,比裸调 API 快得多。 管理对象通常有两路:每对象new一个类实例,开发顺手但占内存;或共用一个类实例,按需把对象挂进去。后者在 UI 类轻量需求下更划算,我们选它做通用封装。 外汇与贵金属图表对象受报价跳变影响,对象重绘存在滑点风险,实盘前应在 MT5 策略测试器用历史 Tick 验证类实例的释放是否干净。

◍ 用 g 类接管图形对象的增改删

图形对象在 EA、指标或脚本里的生命周期通常就三步:创建、读写属性、退出时删除。把这三步收进一个通用类,比每次手写 ObjectCreate / ObjectSetInteger 要省事得多,也更容易在多个品种图表上复用。 在 MetaEditor 里新建含入文件 IncGUI.mqh,内部声明 CGraphicObjectShell。受保护成员只放 m_name 和 m_id 两个变量,构造时由参数写入,之后管理方法就不再需要反复传名称和图表 ID。 Attach() 是连接已有对象的关键:强制参数对象名,可选参数图表标识符。配合 Name() 和 ChartID() 就能随时拿回当前绑定的身份。下面对应代码里能看到,CreateVLine 在 ObjectCreate 之后立刻 Attach,把名字和 ID 固化进实例。

MQL5 / C++
class CGraphicObjectShell
  {
class="kw">protected:
   class="type">class="kw">string            m_name;
   class="type">long              m_id;
class="kw">public:
   class="type">void Attach(class="type">class="kw">string aName,class="type">long aChartID=class="num">0)
    {
      m_name=aName;
      m_id=aChartID;   
    }
   class="type">class="kw">string Name()
    {
      class="kw">return(m_name);
    }    
   class="type">long ChartID()
    {
      class="kw">return(m_id);
    }
  };
class="type">void CreateVLine(class="type">class="kw">string aName,class="type">int aSubWindow=class="num">0,class="type">long aChartID=class="num">0)
  {
   ObjectCreate(m_id,m_name,OBJ_VLINE,aSubWindow,class="num">0,class="num">0);
   Attach(aName,aChartID);
  }
class="type">void SetColor(class="type">color aColor)
  {
   ObjectSetInteger(m_id,m_name,OBJPROP_COLOR,aColor);
  }
class="type">color Color()
  {
   class="kw">return(ObjectGetInteger(m_id,m_name,OBJPROP_COLOR));
  }
class="type">void SetColor(class="type">class="kw">string aName,class="type">color aColor)
  {
   ObjectSetInteger(class="num">0,aName,OBJPROP_COLOR,aColor);
  }
class="type">color Color(class="type">class="kw">string aName)
  {
   class="kw">return(ObjectGetInteger(class="num">0,aName,OBJPROP_COLOR));
  }
class="type">void SetColor(class="type">long aChartID,class="type">class="kw">string aName,class="type">color aColor)
  {
   ObjectSetInteger(aChartID,aName,OBJPROP_COLOR,aColor);
  }
class="type">color Color(class="type">long aChartID,class="type">class="kw">string aName)
  {
   class="kw">return(ObjectGetInteger(aChartID,aName,OBJPROP_COLOR));
  }
CGraphicObjectShell g;
代码逐行拆解:

  • class CGraphicObjectShell:定义图形对象壳类,封装名称与图表 ID。
  • protected: string m_name; long m_id;:受保护变量,存对象名与图表标识。
  • void Attach(string aName,long aChartID=0):绑定已存在对象,默认图表 ID 为 0(主图)。
  • string Name() / long ChartID():返回当前绑定的名字与 ID。
  • void CreateVLine(...):用 ObjectCreate 建垂直直线,再 Attach 固化参数。
  • SetColor 三个重载:分别按自身实例、按名(主图)、按图表 ID+名 设置颜色。
  • Color 三个重载:对应上面三种情形读取颜色属性。
  • CGraphicObjectShell g;:用短名 g 声明全局实例,含入文件后即可直接调。

Set 类方法名以 Set 开头,Get 类以对应 ObjectGet 开头;除颜色外,时间、价格、子窗口等属性都照此补。ObjectDelete、ObjectMove、ObjectsTotal 等函数也建议包成三种调用情形(自身 / 按名 / 按图表 ID+名)。 外汇与贵金属图表上挂对象受实时报价驱动,对象行为可能随点差跳变而异常,实盘前请在策略测试器用历史数据验证。 连好 IncGUI.mqh 后,只要写 g.CreateVLine("v1"); g.SetColor(clrRed); 就能在主图拉一条红线,改参数立刻看到对象响应。

MQL5 / C++
class CGraphicObjectShell
  {
class="kw">protected:
   class="type">class="kw">string            m_name;
   class="type">long              m_id;
class="kw">public:
   class="type">void Attach(class="type">class="kw">string aName,class="type">long aChartID=class="num">0)
    {
      m_name=aName;
      m_id=aChartID;   
    }
   class="type">class="kw">string Name()
    {
      class="kw">return(m_name);
    }    
   class="type">long ChartID()
    {
      class="kw">return(m_id);
    }
  };
class="type">void CreateVLine(class="type">class="kw">string aName,class="type">int aSubWindow=class="num">0,class="type">long aChartID=class="num">0)
  {
   ObjectCreate(m_id,m_name,OBJ_VLINE,aSubWindow,class="num">0,class="num">0);
   Attach(aName,aChartID);
  }
class="type">void SetColor(class="type">color aColor)
  {
   ObjectSetInteger(m_id,m_name,OBJPROP_COLOR,aColor);
  }
class="type">color Color()
  {
   class="kw">return(ObjectGetInteger(m_id,m_name,OBJPROP_COLOR));
  }
class="type">void SetColor(class="type">class="kw">string aName,class="type">color aColor)
  {
   ObjectSetInteger(class="num">0,aName,OBJPROP_COLOR,aColor);
  }
class="type">color Color(class="type">class="kw">string aName)
  {
   class="kw">return(ObjectGetInteger(class="num">0,aName,OBJPROP_COLOR));
  }
class="type">void SetColor(class="type">long aChartID,class="type">class="kw">string aName,class="type">color aColor)
  {
   ObjectSetInteger(aChartID,aName,OBJPROP_COLOR,aColor);
  }
class="type">color Color(class="type">long aChartID,class="type">class="kw">string aName)
  {
   class="kw">return(ObjectGetInteger(aChartID,aName,OBJPROP_COLOR));
  }
CGraphicObjectShell g;

常见问题

先建一个类接管图表对象,用矩形加文字对象拼出按钮外观,再绑定鼠标点击事件做响应即可。
把每种基础对象当零件,用容器类统一管位置、大小和显隐,避免手动逐个改坐标。
可以,小布能按你给的控件逻辑批量接管对象的创建、修改和删除,你只管调样式和规则。
坐标必须用相对画布或锚点计算,且对象层级和重绘时机要锁好,否则拖动就乱。
类把增改删封成方法,少写重复代码、易排错,做两个以上控件就回本了。