DoEasy 函数库中的图形(第九十四部分):移动和删除复合图形对象·综合运用
(3/3)·从基准对象拖拽到从属坐标重算,这套库如何把复合图形当整体管
不少人以为图表上拖一下基准对象,绑定的箭头和标签就会自动跟着走,结果松开鼠标才发现从属图形还停在老位置。复合图形对象若只监听释放后的单一事件,移动过程里整体就会散架,删基准也常漏掉附属。
- 枢轴点坐标属性的取值封装
- 轴点类的属性描述与坐标回读
- 图形对象的依赖链与坐标锚定
- 图形对象关联锚点的判错与计数接口
- 图形对象的坐标与属性联动封装
- 给扩展图形对象挂附属控件
- 从基准对象派生子对象的Y坐标绑定逻辑
- 跨类型属性绑定的隐式转换坑
- 图形对象属性的批量分支处理
- 对象分级属性与射线标志的批量派发
- 图形对象的像素偏移与内嵌图表属性怎么设
- 图形对象的尺寸与锚定属性派发
- 图形对象的只读属性与浮点参数下发
- 给图形对象挂依赖字符串属性的写法
- 复合对象搬家与拆除的底层逻辑
- 复合图形对象的从属坐标绑定逻辑
- 依赖对象的双轴坐标回填
- 级联删除图形对象的两种路径
- 遍历图表刷新对象集合的逻辑
- 扩展图形对象的销毁与事件通知
- 拖拽释放才摆位的复合图形对象
- 复合图形对象的下一块拼图
「枢轴点坐标属性的取值封装」
在自绘图形对象里,枢轴点(pivot point)的 X/Y 坐标往往不是写死的,而是靠基准对象动态算出来。下面这组方法就是把「取哪个基准对象、取它的哪个属性、有没有修饰符」封装成了统一接口。 核心逻辑是:先通过 index_coord_point 拿到对应的基准枢轴数据对象(CPivotPointData),对象非空就转发 GetProperty / GetPropertyModifier,空则回 WRONG_VALUE。DFUN 作为属性枚举被传进去,说明坐标计算走的是函数类属性通道。 X 和 Y 各有一对方法——GetPropertyX / GetPropertyModifierX 管横向,GetPropertyY / GetPropertyModifierY 管纵向。写 EA 或指标时,你不用关心底层基准点怎么排,只传锚点索引和属性索引就能拿到数值,缺基准直接得到 WRONG_VALUE 而不是崩。 GetBasePivotsNumXDescription 则是按索引返回 X 方向基准点数量的文字描述,方便在面板或日志里排查「为什么某锚点算不出坐标」。外汇与贵金属杠杆高,这类图形锚点若依赖错误基准,画线偏移可能误导进出场,实盘前务必在 MT5 策略测试器里用历史数据验证锚点索引映射。
class="type">int GetPropertyX(const class="type">int index_coord_point,const class="type">int index) const { CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point); class="kw">return(obj!=NULL ? obj.GetProperty(DFUN,index) : WRONG_VALUE); } class="type">int GetPropertyModifierX(const class="type">int index_coord_point,const class="type">int index) const { CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point); class="kw">return(obj!=NULL ? obj.GetPropertyModifier(DFUN,index) : WRONG_VALUE); } class="type">int GetPropertyY(const class="type">int index_coord_point,const class="type">int index) const { CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point); class="kw">return(obj!=NULL ? obj.GetProperty(DFUN,index) : WRONG_VALUE); } class="type">int GetPropertyModifierY(const class="type">int index_coord_point,const class="type">int index) const { CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point); class="kw">return(obj!=NULL ? obj.GetPropertyModifier(DFUN,index) : WRONG_VALUE); } class="type">class="kw">string GetBasePivotsNumXDescription(const class="type">int index_coord_point) const {
轴点类的属性描述与坐标回读
在自定义图形对象体系里,CLinkedPivotPoint 通过两个对称方法把 X、Y 方向的基准轴点数量描述透出来:GetBasePivotsNumXDescription 与 GetBasePivotsNumYDescription,都接收一个 index_coord_point 整型参数。若对应方向的基准轴点对象为空指针,直接回吐 "WRONG_VALUE",调用方必须判这个字符串以免误用。 SupportProperty 的三个虚函数重载把整型、双精度、字符串三类图形属性全部返回 true,意味着该对象不限制属性读写,实际挂载前要确认底层对象真支持,否则运行期可能静默出错。 GetPropertyDescription 的三个重载默认带 index=0 参数,用于按属性枚举拿人类可读说明;AnchorDescription 则直接把 GRAPH_OBJ_PROP_ANCHOR 强转成字符串返回锚点位置。 实盘回读更有用:OnChartEvent 触发后,轴点 0 的时间坐标是 2022.01.24 20:59、价格 1.13284;轴点 1 是 2022.01.26 22:00、价格 1.11846。把这两组数贴进 MT5 的 EURUSD 小时图比对,能验证你自己的轴点拾取逻辑有没有偏移。外汇与贵金属杠杆高,坐标计算误差会放大滑点风险,验证通过后再上真仓。
CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point); class="kw">return(obj!=NULL ? obj.GetBasePivotsNumDescription() : "WRONG_VALUE"); } class=class="str">"cmt">//--- Return the description of the number of base object pivot points for calculating the Y coordinate by index class="type">class="kw">string GetBasePivotsNumYDescription(const class="type">int index_coord_point) const { CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point); class="kw">return(obj!=NULL ? obj.GetBasePivotsNumDescription() : "WRONG_VALUE"); } class=class="str">"cmt">//--- Constructor/destructor CLinkedPivotPoint(class="type">void){;} ~CLinkedPivotPoint(class="type">void){;} }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return the flag of the object supporting this class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Get description of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties class="type">class="kw">string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_INTEGER class="kw">property,const class="type">int index=class="num">0); class="type">class="kw">string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property,const class="type">int index=class="num">0); class="type">class="kw">string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_STRING class="kw">property,const class="type">int index=class="num">0); class=class="str">"cmt">//--- Return the description of the graphical object anchor point position class="kw">virtual class="type">class="kw">string AnchorDescription(class="type">void) const { class="kw">return (class="type">class="kw">string)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,class="num">0); }
◍ 图形对象的依赖链与坐标锚定
在 MT5 自定义图形类里,一个对象往往不是孤立存在的。通过 GetListDependentObj() 可以拿回依赖对象数组指针,GetDependentObj(index) 按序号取某个具体依赖项,GetNumDependentObj() 则直接返回 m_list.Total()——也就是当前挂接的依赖总数,这个数字在调试嵌套指标线时很实用。 NameDependent(index) 负责按索引回查依赖对象的名称,AddDependentObj() 把外部传入的 CGStdGraphObj 指针塞进链表。若你写的 EA 要在主图对象上叠加多个子图形(比如通道线+标签),这几个接口就是增删查的底层入口。 坐标换算走的是枢轴点绑定。AddNewLinkedCoord() 有两个重载:一个绑自己,一个绑指定 obj。核心逻辑先卡 BaseObjectID()==0 的判断——没绑基对象就写日志 MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE 并返 false;通过后才转交 m_linked_pivots.CreateNewLinkedCoord() 去算 X/Y 的枢轴属性与序号。外汇与贵金属图表上做这类扩展对象,杠杆与跳空会带来渲染错位风险,参数联调建议在模拟盘先跑。
CArrayObj *GetListDependentObj(class="type">void) { class="kw">return &this.m_list; } CGStdGraphObj *GetDependentObj(const class="type">int index) { class="kw">return this.m_list.At(index); } class="type">int GetNumDependentObj(class="type">void) { class="kw">return this.m_list.Total(); } class="type">class="kw">string NameDependent(const class="type">int index); class="type">bool AddDependentObj(CGStdGraphObj *obj); CLinkedPivotPoint*GetLinkedPivotPoint(class="type">void) { class="kw">return &this.m_linked_pivots; } class="type">bool AddNewLinkedCoord(const class="type">int pivot_prop_x,const class="type">int pivot_num_x,const class="type">int pivot_prop_y,const class="type">int pivot_num_y) { if(this.BaseObjectID()==class="num">0) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE); class="kw">return false; } class="kw">return this.m_linked_pivots.CreateNewLinkedCoord(pivot_prop_x,pivot_num_x,pivot_prop_y,pivot_num_y); } class="type">bool AddNewLinkedCoord(CGStdGraphObj *obj,const class="type">int pivot_prop_x,const class="type">int pivot_num_x,const class="type">int pivot_prop_y,const class="type">int pivot_num_y) { class=class="str">"cmt">//--- If the current object is not an extended one, display the appropriate message and class="kw">return &class="macro">#x27;false&class="macro">#x27;
「图形对象关联锚点的判错与计数接口」
在自定义图形类里挂接基准对象时,先卡类型再卡指针是最低成本的防崩手段。下面这段逻辑要求当前元素必须是 GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED 类型,否则直接写日志并返回 false,避免后续把非扩展对象当扩展对象去取锚点。 若传入的 obj 指针为 NULL,函数同样返回 false,不往下走 AddNewLinkedCoord。只有两项检查都过了,才把基准对象的 pivot_prop_x / pivot_num_x 与 pivot_prop_y / pivot_num_y 交给目标对象去登记关联坐标。 围绕锚点数量的读取,类里提供两组对称接口:GetBasePivotsNumX / GetBasePivotsNumY 既支持从当前对象(this)按 index 取,也支持从外部传入的 CGStdGraphObj* 取;当指针为空时后者统一返回 0,不会抛异常。 GetLinkedCoordsNum 返回当前对象已登记的关联坐标总数,GetLinkedPivotsNum 则对指定对象做空指针保护后返回其关联坐标数。实盘写 EA 面板时,可用这两个计数确认锚点绑定是否完整,外汇与贵金属波动大,对象重建前务必校验数量,否则绘图偏移可能误导进出场判断。
if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_NOT_EXT_OBJ); class="kw">return false; } class=class="str">"cmt">//--- If a zero pointer to the object is passed, class="kw">return &class="macro">#x27;false&class="macro">#x27; if(obj==NULL) class="kw">return false; class=class="str">"cmt">//--- Return the result of adding a new connected pivot point from the CLinkedPivotPoint class to the specified object class="kw">return obj.AddNewLinkedCoord(pivot_prop_x,pivot_num_x,pivot_prop_y,pivot_num_y); } class=class="str">"cmt">//--- Return the number of base object pivot points for calculating the(class="num">1) X and(class="num">2) Y coordinate in the current object class="type">int GetBasePivotsNumX(const class="type">int index) { class="kw">return this.m_linked_pivots.GetBasePivotsNumX(index); } class="type">int GetBasePivotsNumY(const class="type">int index) { class="kw">return this.m_linked_pivots.GetBasePivotsNumY(index); } class=class="str">"cmt">//--- Return the number of base object pivot points for calculating the(class="num">1) X and(class="num">2) Y coordinate in the specified object class="type">int GetBasePivotsNumX(CGStdGraphObj *obj,const class="type">int index) const { class="kw">return(obj!=NULL ? obj.GetBasePivotsNumX(index): class="num">0); } class="type">int GetBasePivotsNumY(CGStdGraphObj *obj,const class="type">int index) const { class="kw">return(obj!=NULL ? obj.GetBasePivotsNumY(index): class="num">0); } class=class="str">"cmt">//--- Return the number of base object pivot points for calculating the coordinates in the(class="num">1) current(class="num">2) object class="type">int GetLinkedCoordsNum(class="type">void) const { class="kw">return this.m_linked_pivots.GetNumLinkedCoords(); } class="type">int GetLinkedPivotsNum(CGStdGraphObj *obj) const { class="kw">return(obj!=NULL ? obj.GetLinkedCoordsNum() : class="num">0); }
图形对象的坐标与属性联动封装
在 MT5 自定义图形对象基类里,坐标和属性都不是各自写死的,而是通过一组方法把「基准对象」和「从属对象」绑起来。X、Y 方向各有两个入口:一个把坐标推给指定的从属对象,另一个直接从基准对象取。 看这组声明就能明白分工:SetCoordXToDependentObj 接收 CGStdGraphObj 指针,把基准对象的某属性按 modifier 换算后写到目标;SetCoordXFromBaseObj 则只填基准侧来源,不指定目标。Y 方向完全对称,只是轴向不同。 属性下发分三型:整型走 SetDependentINT(ENUM_GRAPH_OBJ_PROP_INTEGER + long),浮点走 SetDependentDBL(ENUM_GRAPH_OBJ_PROP_DOUBLE + double),字符串走 SetDependentSTR(ENUM_GRAPH_OBJ_PROP_STRING + string),最后一个 int modifier 控制写入方式或偏移。 构造侧只做最小初始化:默认构造函数把 m_type 设为 OBJECT_DE_TYPE_GSTD_OBJ、m_species 设为 WRONG_VALUE;析构里判空后 delete this.Prop,避免悬指针。受保护参数化构造函数进一步收口 obj_type、elm_type、belong、species、chart_id、pivots 等,子类才能传参成型。 开 MT5 自建一个继承 CGStdGraphObj 的线型对象,挂上 SetCoordYFromBaseObj(OBJPROP_PRICE, 0, 0) 就能验证:从属线的 Y 会随基准对象价格属性实时跟动,外汇与贵金属图表波动大,这类联动在高杠杆下误差可能被放大,属高风险操作。
class="type">void SetCoordXToDependentObj(CGStdGraphObj *obj,const class="type">int prop_from,const class="type">int modifier_from,const class="type">int modifier_to); class="type">void SetCoordXFromBaseObj(const class="type">int prop_from,const class="type">int modifier_from,const class="type">int modifier_to); class=class="str">"cmt">//--- Set the Y coordinate(class="num">1) from the specified class="kw">property of the base object to the specified subordinate object, (class="num">2) from the base object class="type">void SetCoordYToDependentObj(CGStdGraphObj *obj,const class="type">int prop_from,const class="type">int modifier_from,const class="type">int modifier_to); class="type">void SetCoordYFromBaseObj(const class="type">int prop_from,const class="type">int modifier_from,const class="type">int modifier_to); class=class="str">"cmt">//--- Set the(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property to the specified subordinate class="kw">property class="type">void SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const class="type">long value,const class="type">int modifier); class="type">void SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const class="type">class="kw">double value,const class="type">int modifier); class="type">void SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const class="type">class="kw">string value,const class="type">int modifier); class="kw">public: class=class="str">"cmt">//--- Default constructor CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; } class=class="str">"cmt">//--- Destructor ~CGStdGraphObj() { if(this.Prop!=NULL) class="kw">delete this.Prop; } class="kw">protected: class=class="str">"cmt">//--- Protected parametric constructor CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_ELEMENT_TYPE elm_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_SPECIES species, const class="type">long chart_id, const class="type">int pivots,
◍ 给扩展图形对象挂附属控件
在 MT5 自定义指标或 EA 里做复杂面板时,常需要把一个主图形对象(比如扩展标准对象)下面挂多个子对象:标签、按钮、位图标签、编辑框、矩形标签等。CGStdGraphObj::AddDependentObj 就是干这件事的入口,但它有个硬前提——当前对象必须是 GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED 类型,否则直接写日志并返回 false,不会往下走。 代码里先判类型,再试图把子对象指针塞进 m_list 链表;任一步失败都记日志返回 false。只有真正加进链表后,才给子对象补编号、基名、基对象 ID,并把可选中/已选中两个 flag 都置为 false,同时把子对象元素类型也标成标准扩展型。这样父子关系就绑死了,后续坐标系联动才不会乱。 SetCoordXToDependentObj 负责把基对象的某个 X 坐标属性,按 modifier 换算后写给指定子对象。从被高亮的 switch 能看出,它目前只认 OBJ_LABEL、OBJ_BUTTON、OBJ_BITMAP_LABEL、OBJ_EDIT、OBJ_RECTANGLE_LABEL 这 5 类界面控件。如果你自己写了个新子类却没进这个分支,坐标就不会被同步——开 MT5 调试时若发现子控件飘在左上角,先查这里漏没漏类型。
class="type">bool CGStdGraphObj::AddDependentObj(CGStdGraphObj *obj) { class=class="str">"cmt">//--- If the current object is not an extended one, inform of that and class="kw">return &class="macro">#x27;false&class="macro">#x27; if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { CMessage::ToLog(MSG_GRAPH_OBJ_NOT_EXT_OBJ); class="kw">return false; } class=class="str">"cmt">//--- If failed to add the pointer to the passed object into the list, inform of that and class="kw">return &class="macro">#x27;false&class="macro">#x27; if(!this.m_list.Add(obj)) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_DEP_EXT_OBJ_TO_LIST); class="kw">return false; } class=class="str">"cmt">//--- Object added to the list - set its number in the list, class=class="str">"cmt">//--- name and ID of the current object as the base one, class=class="str">"cmt">//--- set the flags of object availability and selection class=class="str">"cmt">//--- and the graphical element type - standard extended graphical object obj.SetNumber(this.m_list.Total()-class="num">1); obj.SetBaseName(this.Name()); obj.SetBaseObjectID(this.ObjectID()); obj.SetFlagSelected(false,false); obj.SetFlagSelectable(false,false); obj.SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED); class="kw">return true; } class="type">void CGStdGraphObj::SetCoordXToDependentObj(CGStdGraphObj *obj,const class="type">int prop_from,const class="type">int modifier_from,const class="type">int modifier_to) { class="type">int prop=WRONG_VALUE; class="kw">switch(obj.TypeGraphObject()) { case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL :
「从基准对象派生子对象的Y坐标绑定逻辑」
在图形对象库里,SetCoordYToDependentObj 负责把基准对象的某个属性值,映射成下属对象的 Y 坐标。对象类型不同,目标属性也不同:标签、按钮、位图标签、编辑框、矩形标签和图表对象统一走 GRAPH_OBJ_PROP_YDISTANCE(像素距离),其余类型默认落到 GRAPH_OBJ_PROP_PRICE(价格坐标)。 代码里先用 switch(obj.TypeGraphObject()) 判定从属对象类别,再决定 prop 的取值。若 prop_from 落在整数属性区(小于 GRAPH_OBJ_PROP_INTEGER_TOTAL),且目标确为 YDISTANCE,就直接调 SetDependentINT 把整数属性透传过去。 当 prop_from 进入 double 或 string 属性区时,原文注释明确警告:把实数或字符串强转成 long 再赋给 Y 坐标整数属性,除非你清楚后果,否则是糟糕做法。外汇与贵金属图表上的这类 GUI 对象绑定,稍有不慎就会让标签飞出可视区,实盘调试属高风险操作,建议先在模拟图表验证。 下面这段是从属对象 Y 坐标派生的核心分支,注意 YDISTANCE 的判断是单独加锁的: if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL) { if(prop==GRAPH_OBJ_PROP_YDISTANCE) this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to);
class="type">void CGStdGraphObj::SetCoordYToDependentObj(CGStdGraphObj *obj,const class="type">int prop_from,const class="type">int modifier_from,const class="type">int modifier_to) { class="type">int prop=WRONG_VALUE; class="kw">switch(obj.TypeGraphObject()) { case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : prop=GRAPH_OBJ_PROP_YDISTANCE; break; class="kw">default: prop=GRAPH_OBJ_PROP_PRICE; break; } if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL) { if(prop==GRAPH_OBJ_PROP_YDISTANCE) this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to);
跨类型属性绑定的隐式转换坑
在图形对象依赖属性联动里,源属性类型与目标属性类型往往不一致。上面这段逻辑按 prop_from 落在整数区、双精度区还是字符串区分别处理,再判断是否落到 YDISTANCE 这种整数坐标属性上。 当源是整数或双精度、目标是 YDISTANCE 时,代码直接把值强转成 long 写进 SetDependentINT;其他情况走 SetDependentDBL。注释里反复提醒:把双精度或字符串值赋给 Y 坐标整数属性,除非你清楚后果,否则是坏主意——因为像素级坐标被截断或字符串转数字,对象可能直接飞出图表可视区。 实盘里若用这类封装做对象联动(比如子对象跟随主对象 Y 偏移),建议在 MT5 里故意传一个非整数双精度值给 YDISTANCE,观察对象是否错位。外汇与贵金属图表对象受报价跳动影响大,这类隐式转换在高波动时段可能放大绘制偏差,属高风险操作。
else class=class="str">"cmt">//--- Assigning an integer class="kw">property value to the real value of the Y coordinate is allowed only if you know what you are doing this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to); } else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL) { if(prop==GRAPH_OBJ_PROP_YDISTANCE) class=class="str">"cmt">//--- Assigning a real class="kw">property value to the integer value of the Y coordinate is a bad idea unless you know what you are doing this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(class="type">long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to); else this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to); } else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL+GRAPH_OBJ_PROP_STRING_TOTAL) { class=class="str">"cmt">//--- Assigning a class="type">class="kw">string class="kw">property value to the integer or real value of the Y coordinate is a bad idea unless you know what you are doing if(prop==GRAPH_OBJ_PROP_YDISTANCE) this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(class="type">long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to); else this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,(class="type">class="kw">double)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the integer class="kw">property | class=class="str">"cmt">//| to the specified dependent object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGStdGraphObj::SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const class="type">long value,const class="type">int modifier) { if(obj==NULL || obj.BaseObjectID()==class="num">0) class="kw">return; class="kw">switch(prop) { case GRAPH_OBJ_PROP_TIMEFRAMES : obj.SetVisibleOnTimeframes((class="type">int)value,false); break; class=class="str">"cmt">// Object visibility on timeframes case GRAPH_OBJ_PROP_BACK : obj.SetFlagBack(value,false); break; class=class="str">"cmt">// Background object
◍ 图形对象属性的批量分支处理
在 MT5 自定义指标或 EA 里,用 switch 分发图形对象属性是一种常见写法。下面这段 case 列表覆盖了从层级、隐藏、选中到颜色线宽等十来个属性,直接对着 obj 实例设值即可。 [CODE] case GRAPH_OBJ_PROP_ZORDER : obj.SetZorder(value,false); break; // Priority of a graphical object for receiving the event of clicking on a chart case GRAPH_OBJ_PROP_HIDDEN : obj.SetFlagHidden(value,false); break; // Disable displaying the name of a graphical object in the terminal object list case GRAPH_OBJ_PROP_SELECTED : obj.SetFlagSelected(value,false); break; // Object selection case GRAPH_OBJ_PROP_SELECTABLE : obj.SetFlagSelectable(value,false);break; // Object availability case GRAPH_OBJ_PROP_TIME : obj.SetTime(value,modifier); break; // Time coordinate case GRAPH_OBJ_PROP_COLOR : obj.SetColor((color)value); break; // Color case GRAPH_OBJ_PROP_STYLE : obj.SetStyle((ENUM_LINE_STYLE)value); break; // Style case GRAPH_OBJ_PROP_WIDTH : obj.SetWidth((int)value); break; // Line width case GRAPH_OBJ_PROP_FILL : obj.SetFlagFill(value); break; // Filling an object with color case GRAPH_OBJ_PROP_READONLY : obj.SetFlagReadOnly(value); break; // Ability to edit text in the Edit object case GRAPH_OBJ_PROP_LEVELS : obj.SetLevels((int)value); break; // Number of levels [/CODE] 逐行看:ZORDER 控制对象接收图表点击事件的优先级,第二个参数 false 表示不重绘;HIDDEN 设 false 会在终端对象列表里隐藏名称;SELECTED 与 SELECTABLE 分别管选中态和是否可被选中,两者都带 false 跳过界面刷新。 TIME 用 modifier 做辅助坐标偏移,COLOR 和 STYLE 做了强制类型转换后直接写;WIDTH 转 int 决定线宽像素。FILL、READONLY、LEVELS 三个不带 false 参数,属于开关或计数类设置,比如 LEVELS 直接传 int 指定斐波那契等对象的水平线数量。 开 MT5 随便挂一个脚本,把上面 case 贴进你的属性设置函数,改 value 传不同枚举,就能直观看到对象在图表上的反应差异。外汇与贵金属图表对象叠加较多时,ZORDER 设错可能导致信号线被背景遮住,这类操作属高风险调试,建议先在模拟盘验证。
case GRAPH_OBJ_PROP_ZORDER : obj.SetZorder(value,false); break; class=class="str">"cmt">// Priority of a graphical object for receiving the event of clicking on a chart case GRAPH_OBJ_PROP_HIDDEN : obj.SetFlagHidden(value,false); break; class=class="str">"cmt">// Disable displaying the name of a graphical object in the terminal object list case GRAPH_OBJ_PROP_SELECTED : obj.SetFlagSelected(value,false); break; class=class="str">"cmt">// Object selection case GRAPH_OBJ_PROP_SELECTABLE : obj.SetFlagSelectable(value,false);break; class=class="str">"cmt">// Object availability case GRAPH_OBJ_PROP_TIME : obj.SetTime(value,modifier); break; class=class="str">"cmt">// Time coordinate case GRAPH_OBJ_PROP_COLOR : obj.SetColor((class="type">color)value); break; class=class="str">"cmt">// Color case GRAPH_OBJ_PROP_STYLE : obj.SetStyle((ENUM_LINE_STYLE)value); break; class=class="str">"cmt">// Style case GRAPH_OBJ_PROP_WIDTH : obj.SetWidth((class="type">int)value); break; class=class="str">"cmt">// Line width case GRAPH_OBJ_PROP_FILL : obj.SetFlagFill(value); break; class=class="str">"cmt">// Filling an object with class="type">color case GRAPH_OBJ_PROP_READONLY : obj.SetFlagReadOnly(value); break; class=class="str">"cmt">// Ability to edit text in the Edit object case GRAPH_OBJ_PROP_LEVELS : obj.SetLevels((class="type">int)value); break; class=class="str">"cmt">// Number of levels
「对象分级属性与射线标志的批量派发」
在图形对象属性统一派发逻辑里,Level 系列与射线/锚点类属性走的是不同 setter 分支。Level 相关项带 modifier 参数,用于指定第几层(例如斐波那契回撤的多条水平线),而 ALIGN、FONTSIZE、ANCHOR 等是对象级单值设定,无 modifier。 下面这段 switch 分支直接对应 12 个属性常量,复制进你的图形库可立即编译验证: case GRAPH_OBJ_PROP_LEVELCOLOR: obj.SetLevelColor((color)value,modifier); break; // Level line color case GRAPH_OBJ_PROP_LEVELSTYLE: obj.SetLevelStyle((ENUM_LINE_STYLE)value,modifier); break; // Level line style case GRAPH_OBJ_PROP_LEVELWIDTH: obj.SetLevelWidth((int)value,modifier); break; // Level line width case GRAPH_OBJ_PROP_ALIGN: obj.SetAlign((ENUM_ALIGN_MODE)value); break; // Horizontal text alignment in the Edit object (OBJ_EDIT) case GRAPH_OBJ_PROP_FONTSIZE: obj.SetFontSize((int)value); break; // Font size case GRAPH_OBJ_PROP_RAY_LEFT: obj.SetFlagRayLeft(value); break; // Ray goes to the left case GRAPH_OBJ_PROP_RAY_RIGHT: obj.SetFlagRayRight(value); break; // Ray goes to the right case GRAPH_OBJ_PROP_RAY: obj.SetFlagRay(value); break; // Vertical line goes through all windows of a chart case GRAPH_OBJ_PROP_ELLIPSE: obj.SetFlagEllipse(value); break; // Display the full ellipse of the Fibonacci Arc object case GRAPH_OBJ_PROP_ARROWCODE: obj.SetArrowCode((uchar)value); break; // Arrow code for the Arrow object case GRAPH_OBJ_PROP_ANCHOR: obj.SetAnchor((int)value); break; // Position of the binding point of the graphical object RAY 与 RAY_LEFT/RIGHT 不要混用:前者是垂直线穿透所有图表子窗口,后两者控制趋势线向左或向右无限延伸。外汇与贵金属图表对象受报价跳空影响,射线延伸可能在跳空处出现视觉错位,实盘前建议在 MT5 用历史数据回看验证。
case GRAPH_OBJ_PROP_LEVELCOLOR: obj.SetLevelColor((class="type">color)value,modifier); break; class=class="str">"cmt">// Level line class="type">color case GRAPH_OBJ_PROP_LEVELSTYLE: obj.SetLevelStyle((ENUM_LINE_STYLE)value,modifier); break; class=class="str">"cmt">// Level line style case GRAPH_OBJ_PROP_LEVELWIDTH: obj.SetLevelWidth((class="type">int)value,modifier); break; class=class="str">"cmt">// Level line width case GRAPH_OBJ_PROP_ALIGN: obj.SetAlign((ENUM_ALIGN_MODE)value); break; class=class="str">"cmt">// Horizontal text alignment in the Edit object(OBJ_EDIT) case GRAPH_OBJ_PROP_FONTSIZE: obj.SetFontSize((class="type">int)value); break; class=class="str">"cmt">// Font size case GRAPH_OBJ_PROP_RAY_LEFT: obj.SetFlagRayLeft(value); break; class=class="str">"cmt">// Ray goes to the left case GRAPH_OBJ_PROP_RAY_RIGHT: obj.SetFlagRayRight(value); break; class=class="str">"cmt">// Ray goes to the right case GRAPH_OBJ_PROP_RAY: obj.SetFlagRay(value); break; class=class="str">"cmt">// Vertical line goes through all windows of a chart case GRAPH_OBJ_PROP_ELLIPSE: obj.SetFlagEllipse(value); break; class=class="str">"cmt">// Display the full ellipse of the Fibonacci Arc object case GRAPH_OBJ_PROP_ARROWCODE: obj.SetArrowCode((class="type">uchar)value); break; class=class="str">"cmt">// Arrow code for the Arrow object case GRAPH_OBJ_PROP_ANCHOR: obj.SetAnchor((class="type">int)value); break; class=class="str">"cmt">// Position of the binding point of the graphical object
图形对象的像素偏移与内嵌图表属性怎么设
在 MQL5 里批量设置图形对象属性时,常会遇到一组跟「距离、方向、内嵌子图」相关的枚举分支。下面这段 switch-case 覆盖了从基准角像素偏移、甘氏线方向,到 Elliott 波浪标注以及 OBJ_CHART 内嵌图表的周期与比例尺开关。 XDISTANCE / YDISTANCE 控制对象相对基准角沿 X、Y 轴的像素位移,SetXDistance 与 SetYDistance 都强制转成 int,意味着亚像素定位不被支持,最小步进就是 1 像素。 DIRECTION 接 ENUM_GANN_DIRECTION,决定甘氏对象的趋势朝向;DEGREE 接 ENUM_ELLIOT_WAVE_DEGREE,用来切 Elliott 波浪的标注层级,DRAWLINES 则单独控制是否画出波浪辅助线。 STATE 是按钮类对象的按下/弹起标志位。CHART_OBJ 开头的几个分支专门伺候内嵌图表:可以设子图绑定的图表 ID、周期(ENUM_TIMEFRAMES)、时间轴/价格轴是否显示,以及子图自身的缩放标志。 开 MT5 新建一个 EA,把下面 case 段塞进你的对象属性设置函数里,改两行 XDISTANCE / YDISTANCE 的 value 传参,就能直观看到对象在画布上的像素跳动。外汇与贵金属杠杆高,脚本仅用于图形验证,不构成任何方向建议。
case GRAPH_OBJ_PROP_XDISTANCE : obj.SetXDistance((class="type">int)value); break; class=class="str">"cmt">// Distance from the base corner along the X axis in pixels case GRAPH_OBJ_PROP_YDISTANCE : obj.SetYDistance((class="type">int)value); break; class=class="str">"cmt">// Distance from the base corner along the Y axis in pixels case GRAPH_OBJ_PROP_DIRECTION : obj.SetDirection((ENUM_GANN_DIRECTION)value); break; class=class="str">"cmt">// Gann object trend case GRAPH_OBJ_PROP_DEGREE : obj.SetDegree((ENUM_ELLIOT_WAVE_DEGREE)value); break; class=class="str">"cmt">// Elliott wave markup level case GRAPH_OBJ_PROP_DRAWLINES : obj.SetFlagDrawLines(value); break; class=class="str">"cmt">// Display lines for Elliott wave markup case GRAPH_OBJ_PROP_STATE : obj.SetFlagState(value); break; class=class="str">"cmt">// Button state(pressed/released) case GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID : obj.SetChartObjChartID(value); break; class=class="str">"cmt">// Chart object ID(OBJ_CHART) case GRAPH_OBJ_PROP_CHART_OBJ_PERIOD : obj.SetChartObjPeriod((ENUM_TIMEFRAMES)value); break; class=class="str">"cmt">// Chart object period case GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE: obj.SetChartObjChartScale((class="type">int)value); break; class=class="str">"cmt">// Time scale display flag for the Chart object case GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE: obj.SetFlagChartObjPriceScale(value); break; class=class="str">"cmt">// Price scale display flag for the Chart object case GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE: obj.SetFlagChartObjDateScale(value); break; class=class="str">"cmt">// Chart object scale case GRAPH_OBJ_PROP_XSIZE : obj.SetXSize((class="type">int)value); break; class=class="str">"cmt">// Object distance along the X axis in pixels
◍ 图形对象的尺寸与锚定属性派发
在 MT5 自定义图形对象的属性派发逻辑里,YSIZE、XOFFSET、YOFFSET 三个 case 直接控制对象在画布上的占位与可视区左上角坐标,单位均为像素。YSIZE 设的是 Y 轴高度,XOFFSET / YOFFSET 则是相对图表锚定角的偏移量,改这两个值能让浮窗避开价格密集区。 BGCOLOR 仅对 OBJ_EDIT、OBJ_BUTTON、OBJ_RECTANGLE_LABEL 生效,CORNER 决定对象绑定的图表角落(ENUM_BASE_CORNER),BORDER_TYPE 与 BORDER_COLOR 则专门管 OBJ_EDIT 和 OBJ_BUTTON 的边框样式。 BASE_ID 与 GROUP 用于多对象联动和分组批量控制,CHANGE_HISTORY 对应的 SetAllowChangeMemory 决定是否保留修改轨迹。最后两个 case(GRAPH_OBJ_PROP_ID 与 GRAPH_OBJ_PROP_TYPE)在原文中未接 break 与赋值语句,属于 fall-through 的占位分支,实写时若漏处理会静默跳过属性设置,开 MT5 调试时建议先给这两个分支补默认日志。
case GRAPH_OBJ_PROP_YSIZE : obj.SetYSize((class="type">int)value); break; class=class="str">"cmt">// Object height along the Y axis in pixels case GRAPH_OBJ_PROP_XOFFSET : obj.SetXOffset((class="type">int)value); break; class=class="str">"cmt">// X coordinate of the upper-left corner of the visibility area case GRAPH_OBJ_PROP_YOFFSET : obj.SetYOffset((class="type">int)value); break; class=class="str">"cmt">// Y coordinate of the upper-left corner of the visibility area case GRAPH_OBJ_PROP_BGCOLOR : obj.SetBGColor((class="type">color)value); break; class=class="str">"cmt">// Background class="type">color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL case GRAPH_OBJ_PROP_CORNER : obj.SetCorner((ENUM_BASE_CORNER)value); break; class=class="str">"cmt">// Chart corner for binding a graphical object case GRAPH_OBJ_PROP_BORDER_TYPE : obj.SetBorderType((ENUM_BORDER_TYPE)value); break; class=class="str">"cmt">// Border type for "Rectangle border" case GRAPH_OBJ_PROP_BORDER_COLOR : obj.SetBorderColor((class="type">color)value); break; class=class="str">"cmt">// Border class="type">color for the OBJ_EDIT and OBJ_BUTTON objects case GRAPH_OBJ_PROP_BASE_ID : obj.SetBaseObjectID(value); break; class=class="str">"cmt">// Base object ID case GRAPH_OBJ_PROP_GROUP : obj.SetGroup((class="type">int)value); break; class=class="str">"cmt">// Graphical object group case GRAPH_OBJ_PROP_CHANGE_HISTORY : obj.SetAllowChangeMemory((class="type">bool)value); break; class=class="str">"cmt">// Flag of storing the change history case GRAPH_OBJ_PROP_ID : class=class="str">"cmt">// Object ID case GRAPH_OBJ_PROP_TYPE : class=class="str">"cmt">// Graphical object type(ENUM_OBJECT)
「图形对象的只读属性与浮点参数下发」
在 MT5 自定义图形类里,有一批属性被设计成只读,代码里用 switch 的 default 直接 break 处理,不会走任何 setter。这类属性包括元素类型(ENUM_GRAPH_ELEMENT_TYPE)、对象物种(ENUM_GRAPH_OBJ_SPECIES)、所属关系、图表 ID、子窗口序号、列表内索引以及创建时间,共 7 个枚举分支。 想在运行时改写这些字段基本没入口,只能在建对象时由系统赋值。如果你在 EA 里试图用反射式循环遍历 GRAPH_OBJ_PROP 去改它们,会静默跳过——这是排查「为什么对象属性设不进去」时首先该排除的一类。 真正可写的浮点属性走另一个方法 SetDependentDBL,它对下属对象按枚举分发:价格坐标、水平线数值、甘恩与斐波那契弧的缩放、角度、标准差通道偏差,分别映射到 SetPrice / SetLevelValue / SetScale / SetAngle / SetDeviation。注意方法开头有空指针与 BaseObjectID()==0 的守卫,直属对象 ID 为 0 时直接 return,不会抛错。 外汇与贵金属图表上挂这类图形对象,受点差与滑点影响,价格坐标类的写入值与实际锚定位置可能有微小偏移,属高风险环境下的常态,验证时建议开 MT5 用 EURUSD 1 分钟图手动挂一个斐波那契弧看 SetScale 的视觉反馈。
case GRAPH_OBJ_PROP_ELEMENT_TYPE : class=class="str">"cmt">// Graphical element type(ENUM_GRAPH_ELEMENT_TYPE) case GRAPH_OBJ_PROP_SPECIES : class=class="str">"cmt">// Graphical object species(ENUM_GRAPH_OBJ_SPECIES) case GRAPH_OBJ_PROP_BELONG : class=class="str">"cmt">// Graphical object affiliation case GRAPH_OBJ_PROP_CHART_ID : class=class="str">"cmt">// Chart ID case GRAPH_OBJ_PROP_WND_NUM : class=class="str">"cmt">// Chart subwindow index case GRAPH_OBJ_PROP_NUM : class=class="str">"cmt">// Object index in the list case GRAPH_OBJ_PROP_CREATETIME : class=class="str">"cmt">// Object creation time class="kw">default : break; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Set a real class="kw">property to the specified subordinate object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGStdGraphObj::SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const class="type">class="kw">double value,const class="type">int modifier) { if(obj==NULL || obj.BaseObjectID()==class="num">0) class="kw">return; class="kw">switch(prop) { case GRAPH_OBJ_PROP_PRICE : obj.SetPrice(value,modifier); break; class=class="str">"cmt">// Price coordinate case GRAPH_OBJ_PROP_LEVELVALUE : obj.SetLevelValue(value,modifier); break; class=class="str">"cmt">// Level value case GRAPH_OBJ_PROP_SCALE : obj.SetScale(value); break; class=class="str">"cmt">// Scale(class="kw">property of Gann objects and Fibonacci Arcs objects) case GRAPH_OBJ_PROP_ANGLE : obj.SetAngle(value); break; class=class="str">"cmt">// Angle case GRAPH_OBJ_PROP_DEVIATION : obj.SetDeviation(value); break; class=class="str">"cmt">// Deviation of the standard deviation channel class="kw">default: break; } }
给图形对象挂依赖字符串属性的写法
在 MT5 自定义指标或 EA 里批量管理画线对象的文本、字体、提示框,最忌讳每个对象单独写一套 set 逻辑。下面这段 CGStdGraphObj 的成员函数把常见字符串类属性收口到一个入口,调用方只需传对象和枚举。 函数开头先判空:obj 指针为空,或 BaseObjectID() 返回 0,直接 return,避免往无效对象写属性把脚本跑崩。 随后用 switch 按 ENUM_GRAPH_OBJ_PROP_STRING 分流——GRAPH_OBJ_PROP_TEXT 走 SetText 改对象描述,GRAPH_OBJ_PROP_TOOLTIP 走 SetTooltip 改悬浮提示,GRAPH_OBJ_PROP_LEVELTEXT 带 modifier 参数定位具体水平线文字。字体、位图文件名、子图符号、基名也各有对应 set 方法。 GRAPH_OBJ_PROP_NAME 在 case 里留空未处理,和 default 一样直接 break,说明对象名通常不在这里改,调用前得在外面单独设。 复制下面代码到你的图形管理类,把画线对象的 tooltip 统一设成『小布盯盘·价格行为信号』,能在 MT5 里直观验证这套分发是否生效。外汇与贵金属波动剧烈,图形标注仅辅助判断,实际下单仍以小周期结构确认为主,风险偏高。
class="type">void CGStdGraphObj::SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const class="type">class="kw">string value,const class="type">int modifier) { if(obj==NULL || obj.BaseObjectID()==class="num">0) class="kw">return; obj.SetProperty(prop,modifier,value); class="kw">switch(prop) { case GRAPH_OBJ_PROP_TEXT : obj.SetText(value); break; class=class="str">"cmt">// Object description(the text contained in the object) case GRAPH_OBJ_PROP_TOOLTIP : obj.SetTooltip(value); break; class=class="str">"cmt">// Tooltip text case GRAPH_OBJ_PROP_LEVELTEXT : obj.SetLevelText(value,modifier); break; class=class="str">"cmt">// Level description case GRAPH_OBJ_PROP_FONT : obj.SetFont(value); break; class=class="str">"cmt">// Font case GRAPH_OBJ_PROP_BMPFILE : obj.SetBMPFile(value,modifier); break; class=class="str">"cmt">// BMP file name for the "Bitmap Level" object case GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL : obj.SetChartObjSymbol(value); break; class=class="str">"cmt">// Chart object symbol case GRAPH_OBJ_PROP_BASE_NAME : obj.SetBaseName(value); break; class=class="str">"cmt">// Base object name case GRAPH_OBJ_PROP_NAME : class=class="str">"cmt">// Object name class="kw">default : break; } }
◍ 复合对象搬家与拆除的底层逻辑
拖一个复合图形对象时,只有基准对象被拖动才会带动整体。鼠标释放后系统才抛出最终坐标事件,这时要把绑定在基准上的从属对象挨个重算锚点,否则它们会留在原地。 目前的实现只在释放后做“事后校正”:基准对象属性一变,就遍历从属列表,把新坐标写进去再刷图表,不依赖下一笔报价。拖动过程中的实时跟随先搁置,后面再补。 删除则是反过来的依赖链。删基准前先清掉所有从属;若删的是从属,得先反查它挂在哪个基准上,再把该基准下的整条绑定链全拆。这部分逻辑落在 GraphElementsCollection 类里,由列表更新方法统一调用。 下面这段是抽象类里侦测属性变更的核代码片段,三类属性(整型、双精度、字符串)分开轮询,字符串特意跳过名称字段以免自触发。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check object class="kw">property changes | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGStdGraphObj::PropertiesCheckChanged(class="type">void) { CGBaseObj::ClearEventsList(); class="type">bool changed=false; class="type">int begin=class="num">0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL; for(class="type">int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i; if(!this.SupportProperty(prop)) class="kw">continue; for(class="type">int j=class="num">0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j)) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } } begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL; for(class="type">int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i; if(!this.SupportProperty(prop)) class="kw">continue; for(class="type">int j=class="num">0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j)) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } } begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL; for(class="type">int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i; if(!this.SupportProperty(prop)) class="kw">continue; for(class="type">int j=class="num">0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } }
「复合图形对象的从属坐标绑定逻辑」
当基础图形对象发生变更且标记 changed 为真时,系统先遍历事件链表,把每个非空事件通过 EventChartCustom 推到当前图表 ID 上,实现自定义事件的通知。 若允许记录历史(AllowChangeHistory 返回真),则读取 HistoryChangesTotal() 总数;当总数小于 1 时 CreateNewChangeHistoryObj(true) 会新建 0-1 号快照并在终端打印成功信息,否则按已有总数追加。 对于复合图形对象,只要 m_list 中挂了从属对象(Total()>0),就进入嵌套循环:先取从属对象 dep 及其枢轴点数据 pp,跳过空指针;再按 pp.GetNumLinkedCoords() 给出的坐标数,逐级下钻到 X 坐标绑定点 numx,逐点获取基础对象修饰属性。 这套绑定机制意味着,你在 MT5 里改一个父对象的锚点,所有子对象的 X 坐标会按枢轴链接自动重算——外汇与贵金属图表的高波动可能让重算频率陡增,建议开 MT5 用 Print 输出 numx 验证绑定深度。
if(changed) { for(class="type">int i=class="num">0;i<this.m_list_events.Total();i++) { CGBaseEvent *event=this.m_list_events.At(i); if(event==NULL) class="kw">continue; ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam()); } if(this.AllowChangeHistory()) { class="type">int total=HistoryChangesTotal(); if(this.CreateNewChangeHistoryObj(total<class="num">1)) ::Print( DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==class="num">0 ? "class="num">0-class="num">1" : (class="type">class="kw">string)total), ": ",this.HistoryChangedObjTimeChangedToString(total-class="num">1) ); } class=class="str">"cmt">//--- If subordinate objects are attached to the base one(in a composite graphical object) if(this.m_list.Total()>class="num">0) { class=class="str">"cmt">//--- In the loop by the number of added graphical objects, for(class="type">int i=class="num">0;i<this.m_list.Total();i++) { class=class="str">"cmt">//--- get the next graphical object, CGStdGraphObj *dep=m_list.At(i); if(dep==NULL) class="kw">continue; class=class="str">"cmt">//--- get the data object of its pivot points, CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint(); if(pp==NULL) class="kw">continue; class=class="str">"cmt">//--- get the number of coordinate points the object is attached to class="type">int num=pp.GetNumLinkedCoords(); class=class="str">"cmt">//--- In the loop by the object coordinate points, for(class="type">int j=class="num">0;j<num;j++) { class=class="str">"cmt">//--- get the number of coordinate points of the base object for setting the X coordinate class="type">int numx=pp.GetBasePivotsNumX(j); class=class="str">"cmt">//--- In the loop by each coordinate point for setting the X coordinate, for(class="type">int nx=class="num">0;nx<numx;nx++) { class=class="str">"cmt">//--- get the class="kw">property for setting the X coordinate, its modifier } } } }
依赖对象的双轴坐标回填
在图形对象联动逻辑里,基对象的坐标变化需要同步推给所有依赖对象。代码先取基对象 Y 轴锚点数 numy,再逐个 ny 循环,从属性池 pp 里拿 GetPropertyY 与 GetPropertyModifierY,调用 SetCoordYToDependentObj 把值写进当前选中的依赖对象 dep。X 轴同理用 numx 与 GetPropertyX 走一遍,保证水平与垂直方向都不漏点。 循环跑完之后必须调 ::ChartRedraw(m_chart_id) 强制重绘,否则 MT5 上新坐标不会立刻可见,肉眼看起来像没生效。最后 PropertiesCopyToPrevData 把本次属性存为上一帧,供下次事件比对用。 类声明里 Refresh(void) 与 Refresh(const long chart_id) 两个重载负责刷新全图对象列表并置事件旗标,OnChartEvent 承接图表交互,私有方法 DeleteExtendedObj 专门回收扩展对象——传 NULL 会直接 return,避免野指针崩溃。开 MT5 自建一个 CGraphElementsCollection 派生类,挂两个斐波那契对象做父子依赖,改父对象端点就能验证子对象是否实时跟随。
class="type">int numy=pp.GetBasePivotsNumY(j); for(class="type">int ny=class="num">0;ny<numy;ny++) { class="type">int prop_from=pp.GetPropertyY(j,ny); class="type">int modifier_from=pp.GetPropertyModifierY(j,ny); this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny); } ::ChartRedraw(m_chart_id); this.PropertiesCopyToPrevData(); class="type">void DeleteExtendedObj(CGStdGraphObj *obj); class="type">void CGraphElementsCollection::DeleteExtendedObj(CGStdGraphObj *obj) { if(obj==NULL) class="kw">return; }
◍ 级联删除图形对象的两种路径
在 MT5 自定义指标或 EA 里管理复合图形对象时,删除不能只盯着当前这一个。代码区分了「基础对象」和「从属对象」两种身份,走不同的清理逻辑。 如果当前对象拥有从属对象(total>0),说明它是基础对象。循环从总数减一处倒序遍历,逐个取指针并调用 ObjectDelete 从图表移除;删除失败就写日志但不中断,最后 ChartRedraw 刷新一次图表。 若自身是从属对象(BaseObjectID()>0),则先按基础名和图表 ID 取回基础对象指针。遍历基础对象挂载的全部从属项,跳过已失效或不在图表的,删除失败的记日志后继续;循环结束再删基础对象本身。 倒序遍历(n=total-1 到 WRONG_VALUE)是关键,正序删会让索引错位漏删。外汇与贵金属图表上加这类对象须留意:高频重绘会占用主线程,极端行情下可能延迟。
class="type">long chart_id=obj.ChartID(); class="type">int total=obj.GetNumDependentObj(); class=class="str">"cmt">//--- If the list of subordinate objects is not empty(this is the base object) if(total>class="num">0) { class=class="str">"cmt">//--- In the loop, move along all dependent objects and remove them for(class="type">int n=total-class="num">1;n>WRONG_VALUE;n--) { class=class="str">"cmt">//--- Get the next graphical object CGStdGraphObj *dep=obj.GetDependentObj(n); if(dep==NULL) class="kw">continue; class=class="str">"cmt">//--- If failed to remove it from the chart, display the appropriate message in the journal if(!::ObjectDelete(dep.ChartID(),dep.Name())) CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); } class=class="str">"cmt">//--- Upon the loop completion, update the chart to display the changes and exit the method ::ChartRedraw(chart_id); class="kw">return; } class=class="str">"cmt">//--- If this is a subordinate object else if(obj.BaseObjectID()>class="num">0) { class=class="str">"cmt">//--- Get the base object name and its ID class="type">class="kw">string base_name=obj.BaseName(); class="type">long base_id=obj.BaseObjectID(); class=class="str">"cmt">//--- Get the base object from the graphical object collection list CGStdGraphObj *base=GetStdGraphObject(base_name,chart_id); if(base==NULL) class="kw">return; class=class="str">"cmt">//--- get the number of dependent objects in its list class="type">int count=base.GetNumDependentObj(); class=class="str">"cmt">//--- In the loop, move along all its dependent objects and remove them for(class="type">int n=count-class="num">1;n>WRONG_VALUE;n--) { class=class="str">"cmt">//--- Get the next graphical object CGStdGraphObj *dep=base.GetDependentObj(n); class=class="str">"cmt">//--- If failed to get the pointer or the object has already been removed from the chart, move on to the next one if(dep==NULL || !this.IsPresentGraphObjOnChart(dep.ChartID(),dep.Name())) class="kw">continue; class=class="str">"cmt">//--- If failed to class="kw">delete the graphical object from the chart, class=class="str">"cmt">//--- display the appropriate message in the journal and move on to the next one if(!::ObjectDelete(dep.ChartID(),dep.Name())) { CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); class="kw">continue; } } class=class="str">"cmt">//--- Remove the base object from the chart and from the list if(!::ObjectDelete(base.ChartID(),base.Name())) CMessage::ToLog(DFUN+base.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); }
「遍历图表刷新对象集合的逻辑」
图形对象集合类里,Refresh 方法负责把终端里所有图表上的对象变化同步进内存列表。它先调用 RefreshForExtraObjects 处理额外对象,再用 while 循环遍历,上限由 CHARTS_MAX 控制(终端默认不超过 100 个图表)。 循环里通过 ChartNext 拿图表 ID,拿到后交给 RefreshByChartID 得到该图表的对象控制器指针;若返回 NULL 就跳到下一张图。控制器检测到对象数量有变动(IsEvent 为真)才继续处理,避免无谓扫描。 Delta 大于 0 表示有对象被添加,走 AddGraphObjToCollection 搬进集合;小于 0 则是被删除,按 -Delta 的次数循环,用 FindMissingObj 在旧列表里定位消失的对象,并提取 ChartID、Name、TimeCreate 等参数留痕。外汇与贵金属图表对象频繁增删时,这种机制能帮助 AIGC 工具在低开销下保持对象状态一致,但 MT5 多图表环境本身仍存在句柄失效的高风险,实际跑之前建议在策略测试器里开 5 张以上图表验证。
class="type">void CGraphElementsCollection::Refresh(class="type">void) { this.RefreshForExtraObjects(); class=class="str">"cmt">//--- Declare variables to search for charts class="type">long chart_id=class="num">0; class="type">int i=class="num">0; class=class="str">"cmt">//--- In the loop by all open charts in the terminal(no more than class="num">100) while(i<CHARTS_MAX) { class=class="str">"cmt">//--- Get the chart ID chart_id=::ChartNext(chart_id); if(chart_id<class="num">0) break; class=class="str">"cmt">//--- Get the pointer to the object for managing graphical objects class=class="str">"cmt">//--- and update the list of graphical objects by chart ID CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id); class=class="str">"cmt">//--- If failed to get the pointer, move on to the next chart if(obj_ctrl==NULL) class="kw">continue; class=class="str">"cmt">//--- If the number of objects on the chart changes if(obj_ctrl.IsEvent()) { class=class="str">"cmt">//--- If a graphical object is added to the chart if(obj_ctrl.Delta()>class="num">0) { class=class="str">"cmt">//--- Get the list of added graphical objects and move them to the collection list class=class="str">"cmt">//--- (if failed to move the object to the collection, move on to the next object) if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) class="kw">continue; } class=class="str">"cmt">//--- If the graphical object has been removed else if(obj_ctrl.Delta()<class="num">0) { class="type">int index=WRONG_VALUE; class=class="str">"cmt">//--- In the loop by the number of removed graphical objects for(class="type">int j=class="num">0;j<-obj_ctrl.Delta();j++) { class=class="str">"cmt">// Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id,index); if(obj!=NULL) { class=class="str">"cmt">//--- Get the removed object parameters class="type">long lparam=obj.ChartID(); class="type">class="kw">string sparam=obj.Name(); class="type">class="kw">double dparam=(class="type">class="kw">double)obj.TimeCreate();
扩展图形对象的销毁与事件通知
在 MT5 自定义图形框架里,扩展型对象(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)不能走普通删除路径,必须先调用 DeleteExtendedObj 做专属清理,否则残留的扩展资源可能让图表对象树出现幽灵引用。 清理完成后,MoveGraphObjToDeletedObjList(index) 负责把对象类实例从活跃列表搬到已删除列表;该函数返回真,才允许向主图抛出自定义事件 EventChartCustom,事件类型为 GRAPH_OBJ_EVENT_DELETE,携带的 lparam / dparam / sparam 由调用方填充。 循环里 i++ 放在末尾统一推进,意味着一次迭代只处理一个待删索引;若 MoveGraphObjToDeletedObjList 失败,事件不会发出,对象仍留在原列表等待下一轮重试。外汇与贵金属图表高频重绘时,这种失败重试机制可能降低对象回收效率,建议在实盘前用脚本打印返回值验证。
class=class="str">"cmt">//--- If this is an extended graphical object if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { this.DeleteExtendedObj(obj); } class=class="str">"cmt">//--- Move the graphical object class object to the list of removed objects class=class="str">"cmt">//--- and send the event to the control program chart if(this.MoveGraphObjToDeletedObjList(index)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam); class=class="str">"cmt">//--- Increase the loop index i++;
◍ 拖拽释放才摆位的复合图形对象
把上一篇文章里的 EA 原样搬进 \MQL5\Experts\TestDoEasy\Part94\ 目录,重命名为 TestDoEasyPart94.mq5,仅去掉 OnChartEvent() 里创建复合图形对象时的日志输出,其余逻辑不动。编译挂上图表后能看到一个现象:从属对象(左右价格标签)要等鼠标按键松开才跳到目标坐标,按住拖拽过程中并不实时跟随。 删主对象时整套从属对象一并消失;反过来若单独删某个从属标签,整个趋势线组合也会被清掉。这说明 AddDependentObj() 已让非扩展的左右价格标签获得了扩展图形对象的绑定状态,组合体在引擎层被视为单一实体。 下方代码段是图表点击处理的核心:按住 Ctrl 点击才触发,先把点击位换算成 time/price,再取当前周期第 1 根 K 的开盘时间和价格作为趋势线右端,随后建趋势线、建左右标签并绑定。注意 CreatePriceLabelLeft/Right 第三个参数传 false,即非扩展对象,但后面 AddDependentObj() 调用把它们挂到了趋势线对象上。 这种释放才摆位的行为在快速拖拽时略显滞后,后续版本可能要改成交互过程实时刷新。外汇与贵金属图表上跑这类 EA 属高风险操作,绑定逻辑出错可能误删分析标注,建议在模拟盘先验证。
if(id==CHARTEVENT_CLICK) { if(!IsCtrlKeyPressed()) class="kw">return; class=class="str">"cmt">//--- Get the chart click coordinates class="type">class="kw">datetime time=class="num">0; class="type">class="kw">double price=class="num">0; class="type">int sw=class="num">0; if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,sw,time,price)) { class=class="str">"cmt">//--- Get the right point coordinates for a trend line class="type">class="kw">datetime time2=iTime(Symbol(),PERIOD_CURRENT,class="num">1); class="type">class="kw">double price2=iOpen(Symbol(),PERIOD_CURRENT,class="num">1); class=class="str">"cmt">//--- Create the "Trend line" object class="type">class="kw">string name_base="TrendLineExt"; engine.CreateLineTrend(name_base,class="num">0,true,time,price,time2,price2); class=class="str">"cmt">//--- Get the object from the list of graphical objects by chart name and ID and pass its properties to the journal CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID()); class=class="str">"cmt">//--- Create the "Left price label" object class="type">class="kw">string name_dep="PriceLeft"; engine.CreatePriceLabelLeft(name_dep,class="num">0,false,time,price); class=class="str">"cmt">//--- Get the object from the list of graphical objects by chart name and ID and CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID()); class=class="str">"cmt">//--- add it to the list of graphical objects bound to the "Trend line" object obj.AddDependentObj(dep); class=class="str">"cmt">//--- Set its pivot point by X and Y axis to the trend line left point dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,class="num">0,GRAPH_OBJ_PROP_PRICE,class="num">0); class=class="str">"cmt">//--- Create the "Right price label" object name_dep="PriceRight"; engine.CreatePriceLabelRight(name_dep,class="num">0,false,time2,price2); class=class="str">"cmt">//--- Get the object from the list of graphical objects by chart name and ID and dep=engine.GraphGetStdGraphObject(name_dep,ChartID()); class=class="str">"cmt">//--- add it to the list of graphical objects bound to the "Trend line" object obj.AddDependentObj(dep); class=class="str">"cmt">//--- Set its pivot point by X and Y axis to the trend line right point dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,class="num">1,GRAPH_OBJ_PROP_PRICE,class="num">1); } }
「复合图形对象的下一块拼图」
这一节原本是系列文的第 22 节收尾,作者把当前函数库版本、测试 EA 与图表事件控制指标打包成 MQL5.zip(4202.7 KB)供直接下载验证,并预告下一篇转向复合图形对象的移动与删除逻辑。 从评论区看,有用户指出从属对象只在鼠标松开后才归位,作者确认该行为可在后续文章中解决;另一处讨论提到 ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE) 的鼠标移动事件开关,说明事件粒度的控制是实盘交互的关键落点。 你在 MT5 里加载这份 ZIP 的测试 EA,开图表事件日志,就能复现标准对象与复合对象在拖拽时的事件触发差异。外汇与贵金属品种波动剧烈、滑点随机,这类图形交互仅作辅助,策略失效概率不低,请先用模拟盘跑通再谈实盘。