DoEasy 函数库中的图形(第八十七部分):图形对象集合 - 在所有打开的图表上管理对象属性修改
内容
概述
我们已经能够控制在图表窗口中构建的、基于函数库程序操作的标准图形对象属性的更改。 为了跟踪一些事件,我决定采用上一篇文章中的事件模型。 图形对象事件将在 OnChartEvent() 响应程序中处理。 这大大简化了代码(尽管事件响应处理现在位于两个不同的函数库代码块之中),并修复了在图表上创建图形对象时,类对象属性填充不完整的问题。 我在上一篇文章中已提到了这一点。
一切似乎都还不错,但现在我们无法直接接收来自其它图表里的图形对象事件。 在一个图表上发生的所有事件都会到达该特定图表上工作的 OnChartEvent() 响应程序。 这意味着为了判定在没有程序的图表上发生了什么事件,我们需要向启动程序的图表发送一个事件。
我们可以调用 EventChartCustom() 函数将自定义事件发送到程序图表。
该函数可以为其中指定的图表生成自定义事件。 与此同时,将事件 ID 发送到指定的图表,该函数会自动在其值中加上 CHARTEVENT_CUSTOM 常量。 函数库在收到这样的事件后,我们只需要从其值中减去这个附加值就可以判定在其它图表上发生了什么样的事件。 为了查找发生事件的图表,我们可以在 lparam 事件的 long 参数中指定图表 ID。 现在,我们看到 lparam 有一个值后(默认情况下,对于图形对象,lparam 和 dparam 等于 0),我们已经可以判定来自另一个图表的事件已经到达 — 从 id 参数中减去 CHARTEVENT_CUSTOM 值( 也在事件中传递),并得到事件 ID。 发生事件的对象可以通过 sparam 参数判定,因为在其中传递的是对象名称。
由此,我们能够将事件从其它图表发送到程序图表。 事件可由事件 ID (id) 定义,图表由 lparam 参数定义,而对象名称 — 由 sparam 参数定义。 现在我们需要弄清楚如何在其它图表上管理这些事件,因为程序是在某个图表上启动的,而我们应该接收来自其它图表的事件,并将它们发送到函数库和程序图表。 程序也应该能够处理这些图表,且函数库应该知道它,并能够运行它。
您可能还记得,我们有一个小类来控制不同图表上的事件 (CChartObjectsControl)。 在图形对象的集合类中,我们创建了客户端所有打开图表的列表,并设置在所提到的类参数当中。 此外,该类还跟踪由该类对象管理的图表上图形对象数量的变化。 相应地,在类内部,我们能够创建一个控制图表对象的程序,并将该程序放置在图表上。 它可以使用指标作为程序。 MQL5 允许直接在程序资源中创建自定义指标(以便指标在编译后依然作为其不可或缺的一部分),为终端中打开的每个图表创建一个指标句柄,最令人愉快的是,能够以编程方式将新创建的指标放置在图表当中。
该指标将不会绘制缓冲区。 它的唯一目的就是在 OnChartEvent() 响应程序中跟踪两个图形对象事件(CHARTEVENT_OBJECT_CHANGE 和 CHARTEVENT_OBJECT_DRAG),并把它们当成我们需要在函数库中定义和处理的自定义事件发送给程序图表。
在实现这一点之前,我想提请注意,在许多函数库文件中,用于指定函数库对象属性循环开始的局部变量的名称已更改。 变量名称 “beg”(“begin” 的缩写)对于说英语的用户看来不正确... 因此,我决定在所有文件中将其替换为 “begin”。
以下是函数库文件,其中变量已重命名:
DataTick.mqh, Symbol.mqh, Bar.mqh, PendRequest.mqh, Order.mqh, MQLSignal.mqh, IndicatorDE.mqh, DataInd.mqh, Buffer.mqh, GCnvElement.mqh, Event.mqh, ChartWnd.mqh, ChartObj.mqh, MarketBookOrd.mqh, Account.mqh 和 GStdGraphObj.mqh。
改进库类
在 \MQL5\Include\DoEasy\Data.mqh 里,加入新的消息索引:
MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT_UPPER, // Anchor point at the upper left corner MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT, // Anchor point at the left center MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT_LOWER, // Anchor point at the lower left corner MSG_GRAPH_OBJ_TEXT_ANCHOR_LOWER, // Anchor point at the bottom center MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_LOWER, // Anchor point at the lower right corner MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT, // Anchor point at the right center MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_UPPER, // Anchor point at the upper right corner MSG_GRAPH_OBJ_TEXT_ANCHOR_UPPER, // Anchor point at the upper center MSG_GRAPH_OBJ_TEXT_ANCHOR_CENTER, // Anchor point at the very center of the object //--- CGraphElementsCollection MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST, // Failed to get the list of newly added objects MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST, // Failed to remove a graphical object from the list MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR, // Indicator for controlling and sending events created MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR, // Failed to create the indicator for controlling and sending events MSG_GRAPH_OBJ_CLOSED_CHARTS, // Chart window closed: MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS, // Objects removed together with charts: }; //+------------------------------------------------------------------+
及与新添加的索引相对应的消息文本:
{"Точка привязки в левом верхнем углу","Anchor point at the upper left corner"}, {"Точка привязки слева по центру","Anchor point to the left in the center"}, {"Точка привязки в левом нижнем углу","Anchor point at the lower left corner"}, {"Точка привязки снизу по центру","Anchor point below in the center"}, {"Точка привязки в правом нижнем углу","Anchor point at the lower right corner"}, {"Точка привязки справа по центру","Anchor point to the right in the center"}, {"Точка привязки в правом верхнем углу","Anchor point at the upper right corner"}, {"Точка привязки сверху по центру","Anchor point above in the center"}, {"Точка привязки строго по центру объекта","Anchor point strictly in the center of the object"}, //--- 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: "}, }; //+---------------------------------------------------------------------+
抽象标准图形对象类所在的 \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh 文件中的 Symbol() 方法返回 Chart 图形对象符号:
//--- Symbol for the Chart object string Symbol(void) const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL); } void SetChartObjSymbol(const string symbol) { if(::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol)) this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,symbol); }
鉴于处理 Chart 图形对象的所有方法在其名称中都带有 ChartObj 前缀,为了保持一致性,重命名该方法:
//--- Symbol for the Chart object string ChartObjSymbol(void) const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL); } void SetChartObjSymbol(const string symbol) { if(::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol)) this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,symbol); } //--- Return the flags indicating object visibility on timeframes
此处我们可以看到我一开始就已经提过的已重命名变量:
//+------------------------------------------------------------------+ //| Compare CGStdGraphObj objects by all properties | //+------------------------------------------------------------------+ bool CGStdGraphObj::IsEqual(CGStdGraphObj *compared_obj) const { int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } return true; } //+------------------------------------------------------------------+ //| Display object properties in the journal | //+------------------------------------------------------------------+ void CGStdGraphObj::Print(const bool full_prop=false,const bool dash=false) { ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") ============="); int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.Header(),") =============\n"); } //+------------------------------------------------------------------+
...
//+------------------------------------------------------------------+ //| Check object property changes | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCheckChanged(void) { bool changed=false; int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i; if(!this.SupportProperty(prop)) continue; if(this.GetProperty(prop)!=this.GetPropertyPrev(prop)) { changed=true; ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop)); } } if(changed) PropertiesCopyToPrevData(); } //+------------------------------------------------------------------+
指标发送有关所有图表上对象属性更改的消息
我们需定义指标的参数,用于跟踪所要附加到图表的图形对象事件。
指标应该知道:
- 启动它的图表 ID(我们称之为 SourseID),和
- 它应该将自定义事件发送到 (DestinationID) 的目标图表 ID。
如果我们简单地手动启动图表上的指标(即在导航器中找到它,并将其拖拽到交易品种所在图表上),ChartID() 函数,返回当前图表的 ID,返回启动指标的图表 ID。 乍一看,这正是我们所需要的。 但是... 如果指标位于程序资源当中(它在编译期间内置于程序代码中,并从内置资源启动),则 ChartID() 函数返回启动程序(而非指标实例)的图表 ID。 换言之,如果指标是从 Indicators\ 以外的文件夹启动的,我们就无法以编程方式来排查在不同图表上启动的指标,也无法找出启动指标的图表 ID。 此处的解决方案是在指标设置中传递当前图表 ID,因为我们有全部客户端打开的图表 ID 列表。
在编辑器的导航栏(即在 \MQL5\Indicators\ 中),创建一个新文件夹 DoEasy\
并创建新的指标文件 EventControl.mq5。
创建时,指定两个 long 类型的输入,初始值为 0:
在向导的下一步中,通过勾选相应的复选框,在指标代码中设置 OnChartEvent() 响应程序所需的包含文件:
在下一步中,将所有字段和复选框留空,然后单击完成:
指标已创建。
如果我们现在编译它,我们会收到一个警告,指出指标没有定义至少一个绘制缓冲区:
为避免此警告,我们需要明确指定我们不需要在指标代码中绘制缓冲区:
//+------------------------------------------------------------------+ //| EventControl.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property indicator_chart_window #property indicator_plots 0 //--- input parameters input long InpChartSRC = 0; input long InpChartDST = 0; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+
在逆反初始化函数库类时,我们需要从所有打开的图表中删除已启动的指标。 由于我们可以通过其短名称找到指标,因此我们需要在指标的 OnInit()处理程序中显式设置名称,以便从图表中查找并删除指标:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator shortname IndicatorSetString(INDICATOR_SHORTNAME,"EventSend_From#"+(string)InpChartSRC+"_To#"+(string)InpChartDST); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
短名称将包含:
- 指标名: "EventSend",
- 发送图表 ID 消息的形式为: "_From#"+(string)InpChartSRC 和
- 发送图表 ID 消息的形式: "_To#"+(string)InpChartDST。
在 OnCalculate() 响应程序中,我们什么也不做 — 简单地返回图表柱线的数量:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { return rates_total; } //+------------------------------------------------------------------+
在指标的 OnChartEvent() 响应程序中,跟踪两个图形对象事件(CHARTEVENT_OBJECT_CHANGE 和 CHARTEVENT_OBJECT_DRAG)。 如果检测到它们,则向控制程序图表发送自定义事件:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG) { EventChartCustom(InpChartDST,(ushort)id,InpChartSRC,dparam,sparam); } } //+------------------------------------------------------------------+
在消息本身中,指定事件发送的目的图表,事件 ID(EventChartCustom() 函数自动将 CHARTEVENT_CUSTOM 值加到事件值上),发送事件所来自的图表 ID,其余两个默认值 — dparam 将等于零,而 sparam 设为发生更改的图形对象名称。
编译指标,并将其保留在其文件夹中。 我们会从函数库访问它。 我们只在编译函数库时需要它,而函数库会把它放在资源中,且在以后只访问存储在资源中的指标实例。
分发已编译程序时,无需提供指标的源代码或已编译文件,因为在编译程序时指标代码被嵌入程序编码当中,并且程序在必要时会访问嵌入的编码。
指标文件可在文后附件中找到。
现在我们需要创建一个资源,可执行指标代码将存储在其中。
在 \MQL5\Include\DoEasy\Defines.mqh 里,定义指定指标可执行文件路径的宏替换:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2021, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "DataSND.mqh" #include "DataIMG.mqh" #include "Data.mqh" #ifdef __MQL4__ #include "ToMQL4.mqh" #endif //+------------------------------------------------------------------+ //| Resources | //+------------------------------------------------------------------+ #define PATH_TO_EVENT_CTRL_IND "Indicators\\DoEasy\\EventControl.ex5" //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+
使用此宏替换,我们能获得函数库资源中编译的指标文件的路径。
我们在同一文件中添加可能的图形对象事件列表:
//+------------------------------------------------------------------+ //| Data for handling graphical elements | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of possible graphical object events | //+------------------------------------------------------------------+ enum ENUM_GRAPH_OBJ_EVENT { GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,// No event GRAPH_OBJ_EVENT_CREATE, // "Creating a new graphical object" event GRAPH_OBJ_EVENT_CHANGE, // "Changing graphical object properties" event GRAPH_OBJ_EVENT_MOVE, // "Moving graphical object" event GRAPH_OBJ_EVENT_RENAME, // "Renaming graphical object" event GRAPH_OBJ_EVENT_DELETE, // "Removing graphical object" event }; #define GRAPH_OBJ_EVENTS_NEXT_CODE (GRAPH_OBJ_EVENT_DELETE+1) // The code of the next event after the last graphical object event code //+------------------------------------------------------------------+ //| List of anchoring methods | //| (horizontal and vertical text alignment) | //+------------------------------------------------------------------+
此枚举包含事件的初步列表,在创建控制标准图形对象事件的统一功能(我会在不久的将来介绍)时会被发送给程序。
处理有关对象属性更改事件的指标信号
打开新图表窗口时,函数库自动创建管理 CChartObjectsControl 图表对象类的实例,并将其保存到图形对象集合类中的图表管理对象列表之中。
用于管理图表对象的对象在其属性中保存所托管图表的 ID。 相应地,在创建图表窗口图形对象时,我们可以创建一个指标,来控制同一对象中的图形对象事件。 由此,我们能够将我们的新指标放在每个新打开的图表上(或者放在第一次启动期间的现有图表上)。 指标会跟踪图形对象事件,并将其发送给控制程序图表。
在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 的 CChartObjectsControl 类的私密部分中,声明三个新变量:
//+------------------------------------------------------------------+ //| Chart object management class | //+------------------------------------------------------------------+ class CChartObjectsControl : public CObject { private: CArrayObj m_list_new_graph_obj; // List of added graphical objects ENUM_TIMEFRAMES m_chart_timeframe; // Chart timeframe long m_chart_id; // Chart ID long m_chart_id_main; // Control program chart ID string m_chart_symbol; // Chart symbol bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_last_objects; // Number of graphical objects during the previous check int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check int m_handle_ind; // Event controller indicator handle string m_name_ind; // Short name of the event controller indicator //--- Return the name of the last graphical object added to the chart string LastAddedGraphObjName(void); //--- Set the permission to track mouse events and graphical objects void SetMouseEvent(void); public:
变量的作用从它们的描述中即可清楚了解。v
在类的公开部分,声明两个新方法,用于创建指标,并将其加载到图表中:
public: //--- Return the variable values ENUM_TIMEFRAMES Timeframe(void) const { return this.m_chart_timeframe; } long ChartID(void) const { return this.m_chart_id; } string Symbol(void) const { return this.m_chart_symbol; } bool IsEvent(void) const { return this.m_is_graph_obj_event; } int TotalObjects(void) const { return this.m_total_objects; } int Delta(void) const { return this.m_delta_graph_obj; } //--- Create a new standard graphical object CGStdGraphObj *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name); //--- Return the list of newly added objects CArrayObj *GetListNewAddedObj(void) { return &this.m_list_new_graph_obj;} //--- Create the event control indicator bool CreateEventControlInd(const long chart_id_main); //--- Add the event control indicator to the chart bool AddEventControlInd(void); //--- Check the chart objects void Refresh(void); //--- Constructors
在类构造函数中,为新变量设置默认值,并添加类析构函数,删除图表上的指标和指标句柄,同时释放指标的计算部分:
//--- Check the chart objects void Refresh(void); //--- Constructors CChartObjectsControl(void) { this.m_list_new_graph_obj.Clear(); this.m_list_new_graph_obj.Sort(); this.m_chart_id=::ChartID(); this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id); this.m_chart_symbol=::ChartSymbol(this.m_chart_id); this.m_chart_id_main=::ChartID(); this.m_is_graph_obj_event=false; this.m_total_objects=0; this.m_last_objects=0; this.m_delta_graph_obj=0; this.m_name_ind=""; this.m_handle_ind=INVALID_HANDLE; this.SetMouseEvent(); } CChartObjectsControl(const long chart_id) { this.m_list_new_graph_obj.Clear(); this.m_list_new_graph_obj.Sort(); this.m_chart_id=chart_id; this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id); this.m_chart_symbol=::ChartSymbol(this.m_chart_id); this.m_chart_id_main=::ChartID(); this.m_is_graph_obj_event=false; this.m_total_objects=0; this.m_last_objects=0; this.m_delta_graph_obj=0; this.m_name_ind=""; this.m_handle_ind=INVALID_HANDLE; this.SetMouseEvent(); } //--- Destructor ~CChartObjectsControl() { ::ChartIndicatorDelete(this.ChartID(),0,this.m_name_ind); ::IndicatorRelease(this.m_handle_ind); } //--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property) virtual int Compare(const CObject *node,const int mode=0) const { const CChartObjectsControl *obj_compared=node; return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0); } //--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+
在类主体之外实现所声明的方法。
该方法创建事件控制指标:
//+------------------------------------------------------------------+ //| CChartObjectsControl: Create the event control indicator | //+------------------------------------------------------------------+ bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main) { this.m_chart_id_main=chart_id_main; string name="::"+PATH_TO_EVENT_CTRL_IND; ::ResetLastError(); this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main); if(this.m_handle_ind==INVALID_HANDLE) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR); CMessage::ToLog(DFUN,::GetLastError(),true); return false; } this.m_name_ind="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main; Print ( DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ", CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\"" ); return true; } //+------------------------------------------------------------------+
在此,我们用方法参数传入的值设置控制程序 ID,设置资源中指向指标的路径,并基于图表控制类的品种和时间帧创建指标句柄。
在指标输入里传递需由类对象控制的图表ID,和控制程序 ID。
如果创建指标失败,则在终端日志中发通知,指示误索引和描述,并返回 false。
如果指标句柄创建成功,在类析构函数中指定需从图表中删除的指标短名,在日志里显示有关在图表上创建指标消息,并返回 true。
请注意,在函数库资源中指定指向指标的路径时,在路径字符串之前指定上下文解析符号 “:”。
与之对比,在创建资源时,我们是用 “\\” 进行设置。
该方法将事件控制指标加载到图表:
//+------------------------------------------------------------------+ //|CChartObjectsControl: Add the event control indicator to the chart| //+------------------------------------------------------------------+ bool CChartObjectsControl::AddEventControlInd(void) { if(this.m_handle_ind==INVALID_HANDLE) return false; return ::ChartIndicatorAdd(this.ChartID(),0,this.m_handle_ind); } //+------------------------------------------------------------------+
此处,我们要检查指标句柄。 如果无效,则返回 false。 否则,返回加载指标到图表的操作结果。
在定义图形对象集合类之前,设置存储指标的资源路径:
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ #resource "\\"+PATH_TO_EVENT_CTRL_IND; // Indicator for controlling graphical object events packed into the program resources class CGraphElementsCollection : public CBaseObj {
该代码创建函数库资源,包含控制图表事件的已编译可执行指标文件。
在类的私密部分,声明四个新方法:
class CGraphElementsCollection : public CBaseObj { private: CArrayObj m_list_charts_control; // List of chart management objects CListObj m_list_all_canv_elm_obj; // List of all graphical elements on canvas CListObj m_list_all_graph_obj; // List of all graphical objects bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check //--- Return the flag indicating the graphical element class object presence in the collection list of graphical elements bool IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj); //--- Return the flag indicating the presence of the graphical object class in the graphical object collection list bool IsPresentGraphObjInList(const long chart_id,const string name); //--- Return the flag indicating the presence of a graphical object on a chart by name bool IsPresentGraphObjOnChart(const long chart_id,const string name); //--- Return the pointer to the object of managing objects of the specified chart CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id); //--- Create a new object of managing graphical objects of a specified chart and add it to the list CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id); //--- Update the list of graphical objects by chart ID CChartObjectsControl *RefreshByChartID(const long chart_id); //--- Check if the chart window is present bool IsPresentChartWindow(const long chart_id); //--- Handle removing the chart window void RefreshForExtraObjects(void); //--- Return the first free ID of the graphical (1) object and (2) element on canvas long GetFreeGraphObjID(void); long GetFreeCanvElmID(void); //--- Add a graphical object to the collection bool AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control); //--- Find an object present in the collection but not on a chart CGStdGraphObj *FindMissingObj(const long chart_id); //--- Find the graphical object present on a chart but not in the collection string FindExtraObj(const long chart_id); //--- Remove the graphical object from the graphical object collection list: (1) specified object, (2) by chart ID bool DeleteGraphObjFromList(CGStdGraphObj *obj); void DeleteGraphObjectsFromList(const long chart_id); //--- Remove the object of managing charts from the list bool DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj); public:
为了从类清单中删除不必要的图表控件对象,并从集合列表中删除描述远程图形对象的对象,我们需要知道哪个图表已被删除。 IsPresentChartWindow() 方法即用于此操作。 RefreshForExtraObjects() 方法处理集合类列表中是否存在不必要的对象,而 DeleteGraphObjectsFromList() 和 DeleteGraphObjectRobJFromList() 方法将从图形对象集合列表中删除指定的对象。
在创建管理指定图表图形对象的新对象的方法中,添加创建指标并将其添加到图表的代码:
//+------------------------------------------------------------------+ //| Create a new graphical object management object | //| for a specified and add it to the list | //+------------------------------------------------------------------+ CChartObjectsControl *CGraphElementsCollection::CreateChartObjectCtrlObj(const long chart_id) { //--- Create a new object for managing chart objects by ID CChartObjectsControl *obj=new CChartObjectsControl(chart_id); //--- If the object is not created, inform of the error and return NULL if(obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ),(string)chart_id); return NULL; } //--- If failed to add the object to the list, inform of the error, remove the object and return NULL if(!this.m_list_charts_control.Add(obj)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); delete obj; return NULL; } if(obj.ChartID()!=CBaseObj::GetMainChartID() && obj.CreateEventControlInd(CBaseObj::GetMainChartID())) obj.AddEventControlInd(); //--- Return the pointer to the object that was created and added to the list return obj; } //+------------------------------------------------------------------+
在此,我们确保控制对象不是为程序当前运行所在的图表而创建,且针对图表管理对象控制的图表创建指标成功。 如果全部正确,则将新创建的指标加载到图表上。
在更新所有图形对象列表的方法中,首先处理终端中的关闭图表,以便把已删除图表从对应的图表管理对象里剔除,还有描述图形对象的类对象,这些图形对象在关闭图表后在并无必要保存在集合列表中,故需与图表一并删除:
//+------------------------------------------------------------------+ //| Update the list of all graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { this.RefreshForExtraObjects(); //--- Declare variables to search for charts long chart_id=0; int i=0; //--- In the loop by all open charts in the terminal (no more than 100) while(i<CHARTS_MAX) { //--- Get the chart ID chart_id=::ChartNext(chart_id); if(chart_id<0) break; //--- Get the pointer to the object for managing graphical objects //--- and update the list of graphical objects by chart ID CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id); //--- If failed to get the pointer, move on to the next chart if(obj_ctrl==NULL) continue; //--- If the number of objects on the chart changes if(obj_ctrl.IsEvent()) { //--- If a graphical object is added to the chart if(obj_ctrl.Delta()>0) { //--- Get the list of added graphical objects and move them to the collection list //--- (if failed to move the object to the collection, move on to the next object) if(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; } //--- If the graphical object has been removed else if(obj_ctrl.Delta()<0) { // Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id); if(obj!=NULL) { //--- Display a short description of a detected object deleted from a chart in the journal obj.PrintShort(); //--- Remove the class object of a removed graphical object from the collection list if(!this.DeleteGraphObjFromList(obj)) CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST); } } } //--- Increase the loop index i++; } } //+------------------------------------------------------------------+
该方法检查图表窗口是否存在:
//+------------------------------------------------------------------+ //| Check if the chart window is present | //+------------------------------------------------------------------+ bool CGraphElementsCollection::IsPresentChartWindow(const long chart_id) { long chart=0; int i=0; //--- In the loop by all open charts in the terminal (no more than 100) while(i<CHARTS_MAX) { //--- Get the chart ID chart=::ChartNext(chart); if(chart<0) break; if(chart==chart_id) return true; //--- Increase the loop index i++; } return false; } //+------------------------------------------------------------------+
在此,我们获取下一个图表的 ID,并用传递给方法的 ID 在循环中与所有已打开的图表进行比较。
如果 ID 匹配,则该图表存在。 返回 true。
循环完毕,返回 false — 没有匹配指定 ID 的图表。
该方法处理图表窗口的删除:
//+------------------------------------------------------------------+ //| Handle removing the chart window | //+------------------------------------------------------------------+ void CGraphElementsCollection::RefreshForExtraObjects(void) { for(int i=this.m_list_charts_control.Total()-1;i>WRONG_VALUE;i--) { CChartObjectsControl *obj_ctrl=this.m_list_charts_control.At(i); if(obj_ctrl==NULL) continue; if(!this.IsPresentChartWindow(obj_ctrl.ChartID())) { long chart_id=obj_ctrl.ChartID(); int total_ctrl=m_list_charts_control.Total(); this.DeleteGraphObjCtrlObjFromList(obj_ctrl); int total_obj=m_list_all_graph_obj.Total(); this.DeleteGraphObjectsFromList(chart_id); int del_ctrl=total_ctrl-m_list_charts_control.Total(); int del_obj=total_obj-m_list_all_graph_obj.Total(); Print ( DFUN,CMessage::Text(MSG_GRAPH_OBJ_CLOSED_CHARTS),(string)del_ctrl,". ", CMessage::Text(MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS),(string)del_obj ); } } } //+------------------------------------------------------------------+
此处,在循环中遍历图表管理对象列表,获取下一个对象。 如果终端不包含与对象对应的图表,则表示该图表已被关闭。
故此,我们获得已关闭图表的 ID,以及先前打开图表的总数,就可从列表中删除与已闭合图表对应的控制对象。
在删除图表之前,获取终端中存在的图形对象总数,并从曾经存在过的集合列表中移除当前已关闭的图形对象类对象。
接下来,计算已关闭图表的数量,和已删除图表的数量,并在日志里显示有关已删除图表和图表上图形对象数量的消息。
稍后,此消息将替换为创建事件,并将其发送到控制程序图表。
该方法依据图表 ID 从图形对象集合列表中删除图形对象:
//+------------------------------------------------------------------+ //| Remove a graphical object by a chart ID | //| from the graphical object collection list | //+------------------------------------------------------------------+ void CGraphElementsCollection::DeleteGraphObjectsFromList(const long chart_id) { CArrayObj *list=CSelect::ByGraphicStdObjectProperty(GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL); if(list==NULL) return; for(int i=list.Total();i>WRONG_VALUE;i--) { CGStdGraphObj *obj=list.At(i); if(obj==NULL) continue; this.DeleteGraphObjFromList(obj); } } //+------------------------------------------------------------------+
在此,我们会收到含有指定图表 ID 的图形对象列表。 在循环中遍历获取的列表,获取下一个对象,将其从集合列表中删除。
该方法从列表中删除图表管理对象:
//+------------------------------------------------------------------+ //| Remove the object of managing charts from the list | //+------------------------------------------------------------------+ bool CGraphElementsCollection::DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj) { this.m_list_charts_control.Sort(); int index=this.m_list_charts_control.Search(obj); return(index==WRONG_VALUE ? false : m_list_charts_control.Delete(index)); } //+------------------------------------------------------------------+
在此,我们为图表管理对象列表设置排序列表标志,并使用 Search() 方法查找列表中指定对象的索引。 如果对象不在列表中,则返回 false。 否则,返回方法 Delete() 操作的结果。
在图形对象集合类事件的响应程序中,以其 ID 替代当前正在工作的图表。 为了实现这一点,我们来管理lparam 中的事件值。 当从当前图表接收事件时,它为零;且等于来自指标当中接收的自定义事件到达的图表 ID。
为了从自定义事件中接收图形对象事件 ID,我们需要从获得的 ID 值中减去 CHARTEVENT_CUSTOM,并在检查 ID 的同时检查 idx 中所计算的事件 ID。
如果 lparam 不为零,则接受的事件不是来自当前图表。 否则,它就是接收自当前图表。
现在,只剩下在代码中用获得的图表 id替换 ::ChartID() 的所有实例:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj=NULL; ushort idx=ushort(id-CHARTEVENT_CUSTOM); if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG) { //--- Get the chart ID. If lparam is zero, //--- the event is from the current chart, //--- otherwise, this is a custom event from an indicator long chart_id=(lparam==0 ? ::ChartID() : lparam); //--- If the object, whose properties were changed or which was relocated, //--- is successfully received from the collection list by its name set in sparam obj=this.GetStdGraphObject(sparam,chart_id); if(obj!=NULL) { //--- Update the properties of the obtained object //--- and check their change obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } //--- If failed to get the object by its name, it is not on the list, //--- which means its name has been changed else { //--- Let's search the list for the object that is not on the chart obj=this.FindMissingObj(chart_id); if(obj==NULL) return; //--- Get the name of the renamed graphical object on the chart, which is not in the collection list string name_new=this.FindExtraObj(chart_id); //--- Set a new name for the collection list object, which does not correspond to any graphical object on the chart, //--- update the chart properties and check their change obj.SetName(name_new); obj.PropertiesRefresh(); obj.PropertiesCheckChanged(); } } } //+------------------------------------------------------------------+
这些就是我们目前所需的改进。 我们来测试一下得到的结果。
测试
为了执行测试,我们取用上一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part87\,命名为 TestDoEasyPart87.mq5。
EA 无需修改。 简单地编译后,在图表上启动它,同时预先在终端中打开另外两个图表。 在其它图表上创建和更改对象时,图形对象发生的所有事件都由函数库记录,并在日志中显示相应的消息。 当打开另一个图表时,会为其创建图表控件对象,同时还会创建指标、注册对象更改事件,并将其发送到函数库。 删除其它图表时,日志中将显示相应的条目:
下一步是什么?
在下一篇文章中,我将开始处理图形对象,以便将打开图表上图形对象发生的所有事件发送到控制程序图表(目前,已注册事件仅显示在日志中,但函数库控制程序尚未意识到这些)。
在评论中留下您的问题、意见和建议。
*该系列的前几篇文章:
DoEasy 函数库中的图形(第八十三部分):抽象标准图形对象类
DoEasy 函数库中的图形(第八十四部分):抽象标准图形对象的衍生后代类
DoEasy 函数库中的图形(第八十五部分):图形对象集合 - 添加新创建的对象
DoEasy 函数库中的图形(第八十六部分):图形对象集合 - 管理属性修改
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10038