DoEasy 函数库中的图形(第八十九部分):标准图形对象编程。 基本功能·进阶篇
📐

DoEasy 函数库中的图形(第八十九部分):标准图形对象编程。 基本功能·进阶篇

(2/3)· 从零补齐标准图形对象的创建与修改能力,并修正多轴点跟踪的逻辑漏洞

偏理论 第 2/3 篇

不少自建 EA 在图表上只能被动跟踪标准图形对象,一旦想主动画一条带多级水平的斐波那契或江恩线就得手写一堆重复代码。更隐蔽的是,当对象轴点超过两个或级别数变多时,旧版数组追加逻辑会直接卡死属性修改——这种坑往往要到实盘才暴露。

长整型数据单元的批量增容逻辑

在 MQL5 里给自定义列表批量补长整型数据,核心落在 Increase() 这个方法。它先记下当前元素数 size_prev,再循环 new 出 CDataUnitLong 对象往里塞,失败就记日志、删对象、把结果位标记为 false 并 continue 跳到下一只。 下面这段是增容失败分支的原文片段,注意 data==NULL 的判断必须紧跟 new 之后,否则 MT5 里可能静默泄漏内存: 数据对象创建不出来时,程序走 Print 打点并返回 0,调用方拿到的 added count 就是 0,不会误以为塞进了元素。实盘跑 EA 时若日志频繁出现 MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ,倾向说明内存压力或句柄耗尽,外汇和贵金属品种在高波动时段更易触发,属高风险场景。

MQL5 / C++
data.Value=value;
class=class="str">"cmt">//--- if failed to add the object to the list
if(!this.Add(data))
  {
   class=class="str">"cmt">//--- display the appropriate message, remove the object and add &class="macro">#x27;class="kw">false&class="macro">#x27; to the variable value
   class=class="str">"cmt">//--- and move on to the loop next iteration
   CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
   class="kw">delete data;
   res &=class="kw">false;
   class="kw">continue;
  }
}
class=class="str">"cmt">//--- Return the total result of adding the specified number of objects to the list
class="kw">return res;
class=class="str">"cmt">//--- Increase the number of data cells by the specified value, class="kw">return the number of added elements
class="type">int Increase(class="kw">const class="type">int total,class="kw">const class="type">long value=class="num">0)
  {
   class=class="str">"cmt">//--- Save the current array size
   class="type">int size_prev=this.Total();
   class=class="str">"cmt">//--- Create a new class="type">long data object
   CDataUnitLong *data=new CDataUnitLong();
   class=class="str">"cmt">//--- If failed to create an object, inform of that and class="kw">return zero
   if(data==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ));

◍ 列表扩容与报错枚举的底层写法

这段代码片段展示了图形对象集合类里两个关键方法的收尾逻辑,以及一组报错枚举的定义。 Increase 方法先通过 int size_prev=this.Total(); 存下当前数组长度,再调用 this.AddQuantity(DFUN,total,value) 批量追加元素,最后用 return this.Total()-size_prev; 把实际新增数量吐出来。调用方拿这个返回值就能判断扩容是否如预期——比如传 total=5 却只返回 3,说明对象池里可能已有重复实例被跳过。 前文那个未命名方法逻辑类似,只是多了一行 data.Value=value; 在新建对象上挂值,再走 AddQuantity 并返回差值。两个方法都不直接碰底层数组,而是委托给 AddQuantity,保持集合操作的一致性。 报错枚举部分列出了 CGraphElementsCollection 与 GStdGraphObj 两组的错误码:前者覆盖对象已存在、控件创建失败、获取失败等(MSG_GRAPH_ELM_COLLECTION_ERR_OBJ_ALREADY_EXISTS 等),后者针对标准图形对象的类实例创建、子窗口查找等(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ 等)。在 MT5 里接这些枚举做多语言报错提示,能省掉大量硬编码中文的维护成本。 外汇与贵金属图表上跑这类自定义图形集合时,扩容失败往往意味着子窗口索引越界或对象名冲突,属于高风险操作,建议先在策略测试器用 EURUSD 1 分钟数据跑通再上实盘。

MQL5 / C++
      class="kw">return class="num">0;
      }
      class=class="str">"cmt">//--- Set the specified value to a newly created object
      data.Value=value;
      class=class="str">"cmt">//--- Add the specified number of object instances to the list
      class=class="str">"cmt">//--- and class="kw">return the difference between the obtained and previous array size
      this.AddQuantity(DFUN,total,data);
      class="kw">return this.Total()-size_prev;
      }
class=class="str">"cmt">//--- Increase the number of data cells by the specified value, class="kw">return the number of added elements
   class="type">int       Increase(class="kw">const class="type">int total,class="kw">const class="type">long value=class="num">0)
      {
      class=class="str">"cmt">//--- Save the current array size
      class="type">int size_prev=this.Total();
      class=class="str">"cmt">//--- Add the specified number of object instances to the list
      class=class="str">"cmt">//--- and class="kw">return the difference between the obtained and previous array size
      this.AddQuantity(DFUN,total,value);
      class="kw">return this.Total()-size_prev;
      }
class=class="str">"cmt">//--- CGraphElementsCollection
   MSG_GRAPH_ELM_COLLECTION_ERR_OBJ_ALREADY_EXISTS,   class=class="str">"cmt">// Error. A chart control object already exists with chart id 
   MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ,class=class="str">"cmt">// Failed to create chart control object with chart ID 
   MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ,   class=class="str">"cmt">// Failed to get chart control object with chart ID 
   MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS,class=class="str">"cmt">// Such graphical object already exists: 

class=class="str">"cmt">//--- GStdGraphObj
   MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ,     class=class="str">"cmt">// Failed to create the class object for a graphical object 
   MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ, class=class="str">"cmt">// Failed to create a graphical object 
   MSG_GRAPH_STD_OBJ_ERR_NOT_FIND_SUBWINDOW,          class=class="str">"cmt">// Failed to find the chart subwindow
   MSG_GRAPH_OBJ_TEXT_BMP_FILE_STATE_ON,              class=class="str">"cmt">// On state

「图形对象错误码与后台标志的落地写法」

在 MT5 自定义图形库里,对象生命周期的错误提示是被集中成枚举和双语字符串表管理的。上面这段截取自内部消息枚举,覆盖了图表控制对象重复创建、图形对象类实例化失败、属性越界等典型故障点,例如 MSG_DATA_PROP_OBJ_OUT_OF_PROP_RANGE 直接对应「传入属性超出对象属性范围」这一类调用错误。 俄英双字符串表把同一错误码映射成两种语言,运行时按终端语言返回。像 Failed to get chart control object with chart id Such a graphic object already exists: 都是直接在集合类抛错时拼 id 用的,调试时能在专家日志里看到具体图表标识。 设置图形对象为后台绘制,靠的是下面这个 SetFlagBack 方法,进来先清错误码再走对象属性写入。 ::ResetLastError() 这一步很关键,不然前一次 OBJPROP_BACK 写入失败的错误会污染后续判断。外汇和贵金属图表上挂这类对象时,注意后台对象会吃掉鼠标事件,高频刷新场景下可能拖累报价响应。

MQL5 / C++
  MSG_GRAPH_OBJ_TEXT_BMP_FILE_STATE_OFF,                   class=class="str">"cmt">// Off state

class=class="str">"cmt">//--- CDataPropObj
  MSG_DATA_PROP_OBJ_OUT_OF_PROP_RANGE,                      class=class="str">"cmt">// Passed class="kw">property is out of object class="kw">property range
class=class="str">"cmt">//--- CGraphElementsCollection
  MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,                  class=class="str">"cmt">// Failed to get the list of newly added objects
  MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,                class=class="str">"cmt">// Failed to remove a graphical object from the list

  MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR,                 class=class="str">"cmt">// Indicator for controlling and sending events created
  MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR,          class=class="str">"cmt">// Failed to create the indicator for controlling and sending events
  MSG_GRAPH_OBJ_CLOSED_CHARTS,                              class=class="str">"cmt">// Chart windows closed:
  MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,                  class=class="str">"cmt">// Objects removed together with charts:

  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- CGraphElementsCollection
  {"Ошибка. Уже существует объект управления чартами с идентификатором чарта ","Error. A chart control object already exists with chart id "},
  {"Не удалось создать объект управления чартами с идентификатором чарта ","Failed to create chart control object with chart id "},
  {"Не удалось получить объект управления чартами с идентификатором чарта ","Failed to get chart control object with chart id "},
  {"Такой графический объект уже существует: ","Such a graphic object already exists: "},

class=class="str">"cmt">//--- GStdGraphObj
  {"Не удалось создать объект класса для графического объекта ","Failed to create class object for graphic object"},
  {"Не удалось создать графический объект ","Failed to create graphic object "},
  {"Не удалось найти подокно графика","Could not find chart subwindow"},
  {"Состояние \"On\"","State \"On\""},
  {"Состояние \"Off\"","State \"Off\""},

class=class="str">"cmt">//--- CDataPropObj
  {"Переданное свойство находится за пределами диапазона свойств объекта","The passed class="kw">property is outside the range of the object&class="macro">#x27;s properties"},

class=class="str">"cmt">//--- CGraphElementsCollection
  {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
  {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},

  {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"},
  {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"},
  {"Закрыто окон графиков: ","Closed chart windows: "},
  {"С ними удалено объектов: ","Objects removed with them: "},

  };
class=class="str">"cmt">//+---------------------------------------------------------------------+
class=class="str">"cmt">//--- Set the "Background object" flag
  class="type">bool                SetFlagBack(class="kw">const class="type">bool flag)
                    {
                    ::ResetLastError();

对象层级与选中状态的代码级切换

在 MT5 自定义图形类里,控制对象是否置于背景、是否被选中、是否允许被选中,都靠 ObjectSetInteger 改对应的整型属性。这三个 Set 方法把 OBJPROP_BACK、OBJPROP_SELECTED、OBJPROP_SELECTABLE 分别封装成了可复用的成员函数。 每个方法进来先调 ResetLastError 清掉旧错误码,再尝试写属性;成功就把本地成员变量同步成传入的 flag 并返回 true,失败则通过 CMessage::ToLog 把 GetLastError 打进日志并返回 false。 实盘里若你用 EA 批量画线又频繁切换选中态,这类封装能避免图表对象卡在『不可选』状态导致手动点不动。外汇与贵金属杠杆高,自动化改对象属性前建议在策略测试器用 2023 年全年 M1 数据跑一遍,确认无 4106(对象不存在)类报错再上真仓。

MQL5 / C++
if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_BACK,flag))
  {
   this.m_back=flag;
   class="kw">return true;
  }
else
   CMessage::ToLog(DFUN,::GetLastError(),true);
 class="kw">return class="kw">false;
 }
class=class="str">"cmt">//--- Set the "Object selection" flag
  class="type">bool       SetFlagSelected(class="kw">const class="type">bool flag)
    {
     ::ResetLastError();
     if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTED,flag))
       {
        this.m_selected=flag;
        class="kw">return true;
       }
     else
        CMessage::ToLog(DFUN,::GetLastError(),true);
     class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- Set the "Object selection" flag
  class="type">bool       SetFlagSelectable(class="kw">const class="type">bool flag)
    {
     ::ResetLastError();
     if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag))
       {
        this.m_selectable=flag;

◍ 图形对象的可选性与隐藏标志封装

在 MT5 自定义图形对象类里,SetFlagHidden 方法负责切换对象在终端列表中的可选/隐藏状态。它内部调用 ObjectSetInteger 并传入 OBJPROP_SELECTABLE,成功就把成员变量 m_hidden 同步为新值并返回 true,失败则通过 CMessage::ToLog 把 GetLastError 的错误码打进日志并返回 false。 这段代码没有用 OBJPROP_HIDDEN,而是借 OBJPROP_SELECTABLE 控制对象是否能被鼠标选中及在对象列表中显示名称——实际效果接近“隐藏”,但语义上是可选性开关。 在 CGStdGraphObj 的私有嵌套类 CDataPropObj 中,用三个整型 m_total_int、m_total_dbl、m_total_str 分别记录整数、实数、字符串参数的数量,配合 CArrayObj 类型的 m_list 管理属性对象。你在写自己的指标或 EA 图形层时,可以直接照搬这个计数+列表的结构来跟踪动态属性。 外汇与贵金属图表上挂这类对象时需注意:对象属性变更不会自动重绘,若切换隐藏标志后界面没刷新,可能只是渲染时机问题,属正常高风险环境下的显示延迟。

MQL5 / C++
      class="kw">return true;
            }
            else
               CMessage::ToLog(DFUN,::GetLastError(),true);
            class="kw">return class="kw">false;
            }
class=class="str">"cmt">//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag
   class="type">bool       SetFlagHidden(class="kw">const class="type">bool flag)
            {
            ::ResetLastError();
            if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag))
              {
               this.m_hidden=flag;
               class="kw">return true;
              }
            else
               CMessage::ToLog(DFUN,::GetLastError(),true);
            class="kw">return class="kw">false;
            }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| The class of the abstract standard graphical object               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CGStdGraphObj : class="kw">public CGBaseObj
  {
class="kw">private:
  class=class="str">"cmt">//--- Object class="kw">property class
  class CDataPropObj
    {
  class="kw">private:
      CArrayObj         m_list;      class=class="str">"cmt">// list of class="kw">property objects
      class="type">int               m_total_int; class=class="str">"cmt">// Number of integer parameters
      class="type">int               m_total_dbl; class=class="str">"cmt">// Number of real parameters
      class="type">int               m_total_str; class=class="str">"cmt">// Number of class="type">class="kw">string parameters
      class=class="str">"cmt">//--- Return the index of the array the(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are actually located at

「属性索引偏移与存取接口的实现细节」

图形对象属性在底层被拆成整型、双精度、字符串三类容器,类内部用 m_total_int 与 m_total_dbl 记录前两类数量,以此把枚举值映射成数组下标。IndexProp 的两个重载就是干这件事:整型属性直接拿枚举值减 m_total_int,双精度与字符串则要继续扣掉 m_total_dbl,避免三类数据在统一列表里踩地址。 GetList 返回 m_list 裸指针,而 Long()、Double()、String() 分别取列表第 0、1、2 号元素,对应三种维度的属性数组。你在 MT5 里若想批量改一个对象的颜色或价格水平,直接拿 Double() 返回的指针去 Set 比反复调 ObjectSetDouble 更省开销。 Set 的三个重载表面对称,实则暗藏差异:整型版本把 property 原样丢给 Long().Set,而双精度与字符串版本先过一道 IndexProp 做下标平移。若你抄这段代码,漏掉 IndexProp 这步,字符串属性就会写进错误的偏移位,对象大概率不响应。外汇与贵金属图表对象受报价跳动影响,这类底层封装出错时盘面表现可能很隐蔽,实盘前务必用脚本打印属性值核对。

MQL5 / C++
class="type">int IndexProp(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-this.m_total_int; }
class="type">int IndexProp(ENUM_GRAPH_OBJ_PROP_STRING class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-this.m_total_int-this.m_total_dbl; }
 class="kw">public:
 class=class="str">"cmt">//--- Return the pointer to(class="num">1) the list of class="kw">property objects, as well as to the object of(class="num">2) integer, (class="num">3) real and(class="num">4) class="type">class="kw">string properties
 CArrayObj *GetList(class="type">void) { class="kw">return &this.m_list; }
 CXDimArrayLong *Long() class="kw">const { class="kw">return this.m_list.At(class="num">0); }
 CXDimArrayDouble *Double() class="kw">const { class="kw">return this.m_list.At(class="num">1); }
 CXDimArrayString *String() class="kw">const { class="kw">return this.m_list.At(class="num">2); }
 class=class="str">"cmt">//--- Set object&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
 class="type">void Set(ENUM_GRAPH_OBJ_PROP_INTEGER class="kw">property,class="type">int index,class="type">long value) { this.Long().Set(class="kw">property,index,value); }
 class="type">void Set(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property,class="type">int index,class="type">class="kw">double value) { this.Double().Set(this.IndexProp(class="kw">property),index,value); }
 class="type">void Set(ENUM_GRAPH_OBJ_PROP_STRING class="kw">property,class="type">int index,class="type">class="kw">string value) { this.String().Set(this.IndexProp(class="kw">property),index,value); }
把对象属性监控交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到标准图形对象的轴点与级别变动,你专注策略逻辑而非底层数组维护。

常见问题

早期版本只实现了对象的跟踪与部分参数监听,缺少从代码直接 new 出标准图形并写入图表的基础封装,本篇补齐了这一层。
可以,小布盯盘的品种页内置了图形对象轴点及级别变动的监控视图,省去你手动打印终端日志来比对。
上一版动态数组在末尾追加指针时复用了同一对象指针却按数量循环写入,导致多轴点分支被错误截断,本篇重写了该追加方法。
水平线构造仅用价格故去掉了时间属性标志,垂直线仅用时间故清空了实数型属性支持,二者在基类里做了针对性裁剪。