DoEasyライブラリのグラフィックス(第87部): グラフィカルオブジェクトコレクション - プロパティ変更の管理

Artyom Trishkin | 29 11月, 2021

内容


概念

ライブラリベースのプログラムが動作するグラフウィンドウに組み込まれている標準グラフィカルオブジェクトプロパティの変更は、すでに制御できます。いくつかのイベントを追跡するために、前回の記事のイベントモデルを使用することにしました。グラフィカルオブジェクトイベントは、OnChartEvent()ハンドラで処理されます。これによって、コードが大幅に簡素化され(ただし、イベント処理は2つの異なるライブラリコードブロックに配置されるようになりました)、チャート上にグラフィカルオブジェクトを作成するとすぐにクラスオブジェクトプロパティが不完全に入力される問題が修正されました。これについては前の記事で言及しました。

すべてが良好であるように見えますが、現在、他のチャートからグラフィカルオブジェクトイベントを直接受信することはできません。1つのチャートで発生するすべてのイベントは、この特定のチャートで動作するプログラムのOnChartEvent()ハンドラに到着します。つまり、プログラムのないチャートでどのイベントが発生したかを判別するためには、プログラムが起動されたチャートにイベントを送信する必要があります。

EventChartCustom()関数を使用してカスタムイベントをプログラムチャートに送信できます。
この関数では、指定されたチャートのカスタムイベントを生成できます。同時に、指定されたチャートにイベントIDを送信すると、その値に自動的にCHARTEVENT_CUSTOM定数が加算されます。ライブラリでこのようなイベントを受け取った後でこの加算された値を減算するだけで、他のチャートでどのようなイベントが発生したかを判断できます。イベントが発生したチャートを見つけるために、lparamイベントの long型パラメータでチャートIDを指定できます。ここで、lparamに値があることを確認した後(デフォルトでは、グラフィカルオブジェクトの場合はlparamとdparamはゼロに等しい)、イベントが別のチャートから到着したことをすでに判別できます。idパラメータからCHARTEVENT_CUSTOM値を減算( イベントでも渡されます)して、イベントIDを取得します。オブジェクト名が渡されるsparamパラメータを介してイベントが発生したオブジェクトを判別できるため、

他のチャートからプログラムチャートにイベントを送信することができます。イベントはイベントID(id)、チャートはlparamパラメータ、オブジェクト名はsparamパラメータで定義されます。プログラムは単一のチャートで起動されるため、これらのイベントが他のチャートでどのように管理されるかを理解する必要があります。一方、イベントを受信して、他のチャートからライブラリとプログラムチャートに送信します。プログラムはこれらのチャートでも動作し、ライブラリはそれを認識して実行します。

覚えていらっしゃるかもしれませんが、さまざまなチャートのイベントを制御するための小さなクラス(CChartObjectsControl)が損じします。グラフィカルオブジェクトのコレクションクラスでは、前述のクラスパラメータで設定されたクライアントターミナルで開いているすべてのチャートのリストを作成します。さらに、クラスでは、クラスオブジェクトによって管理されるチャート上のグラフィックオブジェクトの数の変化も追跡します。したがって、クラス内で、オブジェクトによって制御されるチャートのプログラムを作成し、そのチャートにプログラムを配置することができます。指標をプログラムとして使用することが可能です。MQL5を使用すると、プログラムリソースに直接カスタム指標を作成し(コンパイル後も指標が不可欠な部分のままになるように)、ターミナルで開いた各チャートの指標ハンドルを作成し、最も快適なことに、新しく作成した指標をプログラムでチャートに配置できます。

指標には描画されたバッファはありません。その唯一の目的は、OnChartEvent()ハンドラで2つのグラフィカルオブジェクトイベント(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 and 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();
  }
//+------------------------------------------------------------------+


すべてのチャートのオブジェクトプロパティの変更に関するメッセージを送信する指標

取り付け先チャートのグラフィックオブジェクトのイベントを追跡する指標を定義します。

指標は次を認識する必要があります。

  1. 起動されたチャートのID(SourseIDと呼びます)
  2. カスタムイベントを送信するチャートのID(DestinationID)
DestinationIDは指標入力で指定する必要がありますが、SourseIDにはさらに作業が必要です。

チャート上で指標を手動で起動する(つまり、ナビゲーターで指標を見つけてチャート銘柄にドラッグする)と、現在のチャートのIDを返すChartID()関数が、指標が起動するチャートのIDを返します。一見したところ、これはまさに私たちが必要としているものです。ただし... 指標がプログラムリソースにある場合(コンパイル時にプログラムコードに組み込まれ、組み込みリソースから起動される)、ChartID()関数は、指標インスタンスではなくてプログラムが起動されるチャートのIDを返します。つまり、指標が「Indicators\」以外のフォルダから起動された場合、プログラムで別のチャートで指標を起動しても、指標が起動されたチャートのIDを見つけることはできません。ここでの解決策は、クライアントターミナルで開かれているすべてのチャートのIDのリストがあるため、指標設定で現在のチャートIDを渡すことです。

エディターナビゲーター(つまり、\MQL5\Indicators\)で、新しいフォルダDoEasy\を作成します。


新しいカスタム指標EventControl.mq5を配置します。



作成時に、long 型の2つの入力を初期値0として指定します。


ウィザードの次のステップで、適切なチェックボックスをオンにして、OnChartEvent()ハンドラを指標コードに含める必要性を設定します。


次のステップで、すべてのフィールドとチェックボックスを空のままにして、[完了]をクリックします。


指標が作成されます。

ここでコンパイルすると、指標に描画されたバッファが1つもないという警告が表示されます。


この警告を回避するには、指標コードに描画されたバッファが不要であることを明示的に指定する必要があります

//+------------------------------------------------------------------+
//|                                                 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);
  }
//+------------------------------------------------------------------+

短縮名には次のものが含まれます。

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()ハンドラで、2つのグラフィカルオブジェクトイベント(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 、残りの2つのデフォルト値(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クラスのprivateセクションで、 3つの新しい変数を宣言します

//+------------------------------------------------------------------+
//| 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:

変数の機能は、その説明から明らかです。

クラスのpublicセクションで、指標を作成してチャートに追加するための2つの新しいメソッドを宣言します

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
  {

この文字列は、チャートイベントを制御するためのコンパイル済み実行可能指標ファイルを含むライブラリリソースを作成します。

クラスのprivateセクションで、4つの新しいメソッドを宣言します

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()メソッドとDeleteGraphObjCtrlObjFromList()メソッドは、指定されたオブジェクトをグラフィカルオブジェクトコレクションリストのリストから削除します。

指定されたチャートのグラフィカルオブジェクトを管理する新しいオブジェクトを作成するメソッドで、指標を作成してチャートに追加するためのコードを追加します。

//+------------------------------------------------------------------+
//| 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が一致する場合、チャートが存在するので、true
を返します。
ループが完了した場合は、指定されたIDのチャートが存在しないためfalseを返します。

以下は、チャートウィンドウの削除を処理するメソッドです。

//+------------------------------------------------------------------+
//| 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を返しますそれ以外の場合はを返します。メソッド操作の結果を返します

グラフィカルオブジェクトコレクションクラスのイベントハンドラで、現在のチャートの操作をIDによるチャートの処理に置き換えます。これを実現するために、イベントのlparam値を管理しましょう。これは、現在のチャートからイベントを受信するとゼロになり、指標からカスタムイベントを受信するとイベントの受信元のチャートのIDと等しくなります。

カスタムイベントからグラフィカルオブジェクトイベントIDを受け取るには、取得したID値からCHARTEVENT_CUSTOMを減算し、IDチェックとともにidxで計算されたイベントIDを確認する必要があります。

lparamがゼロでない場合、イベントは現在のチャート以外から受信されています。それ以外の場合は、現在のチャートから受信されています
これで、コード内の::ChartID() のすべてのインスタンスを取得したchart_idに置き換えるだけで済みます。

//+------------------------------------------------------------------+
//| 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は変更されていません。ターミナルでさらに2つのチャートを事前に開いて、グラフ上でコンパイルして起動するだけです。追加のチャートでオブジェクトを作成および変更すると、グラフィカルオブジェクトで発生するすべてのイベントがライブラリによって記録され、適切なメッセージが操作ログに表示されます。さらに別のチャートを開くと、チャートコントロールオブジェクトが作成され、オブジェクト変更イベントを登録してライブラリに送信する操作ログが表示されます。追加のチャートを削除すると、適切なエントリが操作ログに表示されます。



次の段階

次の記事では、開いているチャート上のグラフィカルオブジェクトで発生するすべてのイベントが制御プログラムチャートに送信されるように、グラフィカルオブジェクトの処理をまとめ始めます(現在、登録されたイベントは操作ログに表示されるだけですが、ライブラリ制御プログラムは表示されません)。

ライブラリの現在のバージョンのすべてのファイルは、テストおよびダウンロードできるように、MQL5のテストEAファイルと一緒に以下に添付されています。

質問や提案はコメント欄にお願いします。

目次に戻る

**連載のこれまでの記事:

DoEasyライブラリのグラフィックス(第83部): 抽象標準グラフィカルオブジェクトのクラス
DoEasyライブラリのグラフィックス(第84部): 抽象標準グラフィカルオブジェクトの子孫クラス
DoEasyライブラリのグラフィックス(第85部): グラフィカルオブジェクトコレクション - 新規作成オブジェクトの追加
DoEasyライブラリのグラフィックス(第86部): グラフィカルオブジェクトコレクション - プロパティ変更の管理