DoEasyライブラリのグラフィックス(第95部):複合グラフィカルオブジェクトコントロール

Artyom Trishkin | 13 5月, 2022

目次


概念

本稿では、複合グラフィカルオブジェクトの開発を続けます。これらは、いくつかのオブジェクトで構成され、単一のグラフィカルオブジェクトに結合された標準グラフィカルオブジェクトです。ライブラリでは、複合オブジェクトに含まれるグラフィカルオブジェクトは、拡張された標準グラフィカルオブジェクトとして定義されます。このようなオブジェクトには、いくつかの追加のプロパティと機能があり、他のグラフィカルオブジェクトを組み込んだり組み込まれたりすることができます。
複合グラフィカルオブジェクトの概念には、別のオブジェクトに接続されたポイントでオブジェクトを保持し、親オブジェクトを変更または再配置するときにその位置を調整する機能が必要です。
前回の記事では、複合グラフィカルオブジェクトイベントハンドラの作成を開始し、複合グラフィカルオブジェクトの削除の処理を実装し、その再配置ハンドラの開発を開始しました。

今日は、複合グラフィカルオブジェクトの再配置から少し脱線して、複合グラフィカルオブジェクトを特徴とするチャートに変更イベントのハンドラを実装します。さらに、複合グラフィカルオブジェクトを管理するためのコントロールに焦点を当てます。
なぜでしょうか。複合グラフィカルオブジェクトのリアルタイム作成を実装します。従属オブジェクトを基本オブジェクトにドラッグして基本オブジェクトに接続します。基本グラフィカルオブジェクトでは、別のオブジェクトがマウスでドラッグされているかどうかを追跡します。オブジェクト接続メカニズムは、チャートアンカーポイントから一定の距離で有効になります。接続されたオブジェクトのアンカーポイントと基本オブジェクトのアンカーポイントを結ぶ線が視覚的に表示され、ドラッグされたオブジェクトを基本オブジェクトに接続する準備ができたことを示します。これを実現するには、グラフィカルオブジェクトの各アンカーポイントが特定のサイズのフォームオブジェクトを備えている必要があります。フォームオブジェクトの領域に入ると、接続メカニズムがアクティブになり、オブジェクトが対話の準備ができていることを示す線がフォーム自体に表示されます。このようなフォームは、グラフィカルオブジェクトの各ピボットポイントに目に見えない形で存在します。領域のサイズは、フォームのエッジに沿って長方形を描画できるようにすることで、デバッグ目的でのみ表示できます。

さらに、フォームには、マウスカーソルがフォームのアクティブ領域に合わせられたときにのみ表示されるグラフィカルオブジェクトのアンカーポイントが表示されます。マウスクリックで強調表示するのではなくフォーム領域にマウスカーソルを合わせることによって、拡張グラフィカルオブジェクトを移動および変更できます。フォームのアクティブな領域(上の画像の長方形)にカーソルを合わせるとすぐに、ラベルがグラフィカルオブジェクトのアンカーポイント(円の中心にある青い点)に表示されます。マウスでフォームのドラッグを開始すると、グラフィカルオブジェクトの適切なピボットポイントがカーソルに追従し、複合グラフィカルオブジェクトとともにオブジェクト自体を変更します。
マウスボタンが押された状態でマウスカーソルがフォームのアクティブ領域に入った場合、これは(確認された場合)別のグラフィカルオブジェクトをフォームに適用して、あるオブジェクトを別のオブジェクトにバインドするメカニズムをアクティブにすることを意味します。したがって、フォームを使用すると、一度に複数の目的を達成できます。

準備がまだ終わっていないので、ここではオブジェクトの別のオブジェクトへの接続は実装しません。代わりに、フォームを作成してグラフィカルオブジェクトアンカーポイントに接続し、チャートを変更するときにオブジェクトのピボットポイント座標に沿ってフォームを移動するメカニズムを実装します。これには、チャートを再配置するか、表示スケールを変更します。これを行うのは、フォームオブジェクトの座標は画面のピクセル単位であるのに対し、ほとんどのグラフィカルオブジェクトは時間/価格の値で表示されるためです。


ライブラリクラスの改善

\MQL5\Include\DoEasy\Data.mqhに、新しいメッセージインデックスを追加します

//--- CLinkedPivotPoint
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X,                // Not a single pivot point is set for the object along the X axis
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y,                // Not a single pivot point is set for the object along the Y axis
   MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE,             // The object is not attached to the basic graphical object
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_PP_DATA_OBJ,       // Failed to create a data object for the X and Y pivot points
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X,            // Number of base object pivot points for calculating the X coordinate: 
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y,            // Number of base object pivot points for calculating the Y coordinate: 

//--- CGStdGraphObjExtToolkit
   MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA,     // Failed to change the size of the pivot point time data array
   MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA,    // Failed to change the size of the pivot point price data array
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM,   // Failed to create a form object to manage a pivot point
   
  };
//+------------------------------------------------------------------+

新しく追加したインデックスに対応するメッセージテキストも追加します。

//--- CLinkedPivotPoint
   {"Для объекта не установлено ни одной опорной точки по оси X","The object does not have any pivot points set along the x-axis"},
   {"Для объекта не установлено ни одной опорной точки по оси Y","The object does not have any pivot points set along the y-axis"},
   {"Объект не привязан к базовому графическому объекту","The object is not attached to the base graphical object"},
   {"Не удалось создать объект данных опорной точки X и Y.","Failed to create X and Y reference point data object"},
   {"Количество опорных точек базового объекта для расчёта координаты X: ","Number of reference points of the base object to set the X coordinate: "},
   {"Количество опорных точек базового объекта для расчёта координаты Y: ","Number of reference points of the base object to set the Y coordinate: "},
   
//--- CGStdGraphObjExtToolkit
   {"Не удалось изменить размер массива данных времени опорной точки","Failed to resize pivot point time data array"},
   {"Не удалось изменить размер массива данных цены опорной точки","Failed to resize pivot point price data array"},
   {"Не удалось создать объект-форму для контроля опорной точки","Failed to create form object to control pivot point"},
   
  };
//+---------------------------------------------------------------------+


\MQL5\Include\DoEasy\Defines.mqhでマクロ置換

#define CLR_DEFAULT                    (0xFF000000)               // Default symbol background color in the navigator

を、よりわかりやすい

#define CLR_MW_DEFAULT                 (0xFF000000)               // Default symbol background color in the Market Watch

で置き換えます。マクロ置き換え

#define NULL_COLOR                     (0x00FFFFFF)               // Zero for the canvas with the alpha channel

を、よりわかりやすい

#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel

で置き換えます。また、新しいマクロ置換を追加して、ここで作成するフォームオブジェクトのデフォルト値を設定します。

//--- Graphical object parameters
#define PROGRAM_OBJ_MAX_ID             (10000)                    // Maximum value of an ID of a graphical object belonging to a program
#define CTRL_POINT_SIZE                (5)                        // Radius of the control point on the form for managing graphical object pivot points
#define CTRL_FORM_SIZE                 (40)                       // Size of the control point form for managing graphical object pivot points
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+


すべてのファイルの古いマクロ置換名を置き換えます。

単にCtrl+Shift+Hを押し、次の値を入力して、以下に示すチェックボックスをオンにします。


[Replace in Files]をクリックしすると、すべてのライブラリファイルで置き換えが行われます。

NULL_COLOR with CLR_CANV_NULLを同じように置き換えます。

新しいマクロ置換名はそれらの機能をよりわかりやすく示すため、潜在的なエラーの量が減ります(たとえば、私は透明なキャンバスの背景を設定するためにCLR_DEFAULTを入力し、それが機能しない理由を理解するために多くの時間を費やしました)。

さらに、基本グラフィカルオブジェクトの子孫のすべてのクラスファイルにいくつかの小さな変更を加えました(操作ログで短いオブジェクトの説明を表示するメソッドのテキストにコンマを追加するだけです )。

//+------------------------------------------------------------------+
//| Display a short description of the object in the journal         |
//+------------------------------------------------------------------+
void CGStdArrowBuyObj::PrintShort(const bool dash=false,const bool symbol=false)
  {
   ::Print
     (
      (dash ? " - " : "")+this.Header(symbol)," \"",CGBaseObj::Name(),"\": ID ",(string)this.GetProperty(GRAPH_OBJ_PROP_ID,0),
      ", ",::TimeToString(CGBaseObj::TimeCreate(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
     );
  }
//+------------------------------------------------------------------+

これは純粋に「見栄えのする」改善です。
\MQL5\Include\DoEasy\Objects\Graph\Standard\のすべてのファイルに実装されています。


拡張された標準グラフィカルオブジェクトツールキットクラス

拡張グラフィカルオブジェクトを処理するためのツールキットの作成を始めましょう。これは、フォームオブジェクトを作成して操作するために必要なすべてのメソッドを備えたクラスになります。各拡張グラフィカルオブジェクトは、適切なクラスのオブジェクトへのポインタを持つ必要があります。必要に応じて(これが複合グラフィカルオブジェクト内の基本オブジェクトである場合)、クラスオブジェクトは、拡張オブジェクトの作成時に動的に作成され、拡張オブジェクトが削除されると削除されます。

オブジェクトは、基本グラフィカルオブジェクトの必要なパラメータ(座標、タイプ、名前など)を受け取ります。基本オブジェクトの座標が追跡され、フォームオブジェクトの座標がオブジェクト内で調整されます。最終的に、フォームオブジェクトは基本オブジェクトの管理を可能にします。

ただし、まず最初に...

\MQL5\Include\DoEasy\Objects\Graph\で、新しいExtend\フォルダを作成して、 MQL5標準ライブラリを構築する基本CObjectクラスから継承されたCGStdGraphObjExtToolkitクラスのCGStdGraphObjExtToolkit.mqhファイルを含めます

//+------------------------------------------------------------------+
//|                                      CGStdGraphObjExtToolkit.mqh |
//|                                  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 strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Graph\Form.mqh"
//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject
  {
  }

フォームオブジェクトクラスファイルをクラスファイルに含める必要があります

クラスのprivateセクションで、必要なすべての基本オブジェクトプロパティフォームを作成するためのプロパティそれらを保存するためのリストを格納する変数およびフォームオブジェクトを作成するためのメソッドその画面座標を返すためのメソッドを宣言します。

//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject
  {
private:
   long              m_base_chart_id;           // Base graphical object chart ID
   int               m_base_subwindow;          // Base graphical object chart subwindow
   ENUM_OBJECT       m_base_type;               // Base object type
   string            m_base_name;               // Base object name
   int               m_base_pivots;             // Number of base object reference points
   datetime          m_base_time[];             // Time array of base object reference points
   double            m_base_price[];            // Price array of base object reference points
   int               m_base_x;                  // Base object X coordinate
   int               m_base_y;                  // Base object Y coordinate
   int               m_ctrl_form_size;          // Size of forms for managing reference points
   int               m_shift;                   // Shift coordinates for adjusting the form location
   CArrayObj         m_list_forms;              // List of form objects for managing reference points
//--- Create a form object on a base object reference point
   CForm            *CreateNewControlPointForm(const int index);
//--- Return X and Y screen coordinates of the specified reference point of the graphical object
   bool              GetControlPointCoordXY(const int index,int &x,int &y);
public:

1つのグラフィカルオブジェクトに複数のピボットポイントがある場合があるため、配列を使用して価格と時間の座標を格納しましょう。各ポイントの座標は適切な配列セルに格納されます。最初のポイントの座標は配列の0インデックス、2番目はインデックス1、3番目はインデックス2などです。
フォームの座標をシフトすると、オブジェクトのピボットポイントの中心にフォームを正確に配置できます。このシフトは、ポイントサイズの半分をカバーします。フォームサイズが2の倍数の場合は1を足して調整されます(例: 10の場合は1を足して11)。これにより、フォームをグラフィカルオブジェクトのピボットポイントの中心に正確に配置できるため、1ピクセルも他の辺を超えないようになります。
フォームのリストは、作成されたすべてのフォームを保存するためのものです。それらへのアクセスは、ポインタによって許可されます。画面の座標を計算するメソッドにより、フォームが配置される画面の座標を知ることができます。これにより、グラフィカルオブジェクトのピボットポイントに正確に配置できます。

クラスのpublicセクションで、クラスを処理するために必要なすべてのメソッドを宣言します。

public:
//--- Set the parameters of the base object of a composite graphical object
   void              SetBaseObj(const ENUM_OBJECT base_type,const string base_name,
                                const long base_chart_id,const int base_subwindow,
                                const int base_pivots,const int ctrl_form_size,
                                const int base_x,const int base_y,
                                const datetime &base_time[],const double &base_price[]);
//--- Set the base object (1) time, (2) price, (3) time and price coordinates
   void              SetBaseObjTime(const datetime time,const int index);
   void              SetBaseObjPrice(const double price,const int index);
   void              SetBaseObjTimePrice(const datetime time,const double price,const int index);
//--- Set the base object (1) X, (2) Y, (3) X and Y screen coordinates
   void              SetBaseObjCoordX(const int value)                        { this.m_base_x=value;                          }
   void              SetBaseObjCoordY(const int value)                        { this.m_base_y=value;                          }
   void              SetBaseObjCoordXY(const int value_x,const int value_y)   { this.m_base_x=value_x; this.m_base_y=value_y; }
//--- (1) Set and (2) return the size of the form of pivot point management control points
   void              SetControlFormSize(const int size);
   int               GetControlFormSize(void)                          const { return this.m_ctrl_form_size;                }
//--- Return the pointer to the pivot point form by (1) index and (2) name
   CForm            *GetControlPointForm(const int index)                     { return this.m_list_forms.At(index);           }
   CForm            *GetControlPointForm(const string name,int &index);
//--- Return the number of the base object pivot points
   int               GetNumPivotsBaseObj(void)                          const { return this.m_base_pivots;                    }
//--- Create form objects on the base object pivot points
   bool              CreateAllControlPointForm(void);
//--- Remove all form objects from the list
   void              DeleteAllControlPointForm(void);
   
//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructor/destructor
                     CGStdGraphObjExtToolkit(const ENUM_OBJECT base_type,const string base_name,
                                             const long base_chart_id,const int base_subwindow,
                                             const int base_pivots,const int ctrl_form_size,
                                             const int base_x,const int base_y,
                                             const datetime &base_time[],const double &base_price[])
                       {
                        this.m_list_forms.Clear();
                        this.SetBaseObj(base_type,base_name,base_chart_id,base_subwindow,base_pivots,ctrl_form_size,base_x,base_y,base_time,base_price);
                        this.CreateAllControlPointForm();
                       }
                     CGStdGraphObjExtToolkit(){;}
                    ~CGStdGraphObjExtToolkit(){;}
  };
//+------------------------------------------------------------------+

クラスコンストラクタで、フォームオブジェクトのリストをクリアし、必要なすべての基本オブジェクト値(コンストラクタパラメータで渡される)をクラス変数に設定し、基本グラフィカルオブジェクトの各ピボットポイントで基本オブジェクトを管理するフォームオブジェクトを作成します。

以下は、複合グラフィカルオブジェクトの基本オブジェクトのパラメータを設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the base object parameters of the                            |
//| composite graphical object                                       |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObj(const ENUM_OBJECT base_type,const string base_name,
                                         const long base_chart_id,const int base_subwindow,
                                         const int base_pivots,const int ctrl_form_size,
                                         const int base_x,const int base_y,
                                         const datetime &base_time[],const double &base_price[])
  {
   this.m_base_chart_id=base_chart_id;       // Base graphical object chart ID
   this.m_base_subwindow=base_subwindow;     // Base graphical object chart subwindow
   this.m_base_type=base_type;               // Base object type
   this.m_base_name=base_name;               // Base object name
   this.m_base_pivots=base_pivots;           // Number of base object reference points
   this.m_base_x=base_x;                     // Base object X coordinate
   this.m_base_y=base_y;                     // Base object Y coordinate
   this.SetControlFormSize(ctrl_form_size);  // Size of forms for managing reference points
   
   if(this.m_base_type==OBJ_LABEL            || this.m_base_type==OBJ_BUTTON  ||
      this.m_base_type==OBJ_BITMAP_LABEL     || this.m_base_type==OBJ_EDIT    ||
      this.m_base_type==OBJ_RECTANGLE_LABEL  || this.m_base_type==OBJ_CHART)
      return;
   
   if(::ArraySize(base_time)==0)
     {
      CMessage::ToLog(DFUN+"base_time: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      return;
     }
   if(::ArraySize(base_price)==0)
     {
      CMessage::ToLog(DFUN+"base_price: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      return;
     }
   if(::ArrayResize(this.m_base_time,this.m_base_pivots)!=this.m_base_pivots)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA);
      return;
     }
   if(::ArrayResize(this.m_base_price,this.m_base_pivots)!=this.m_base_pivots)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA);
      return;
     }
   for(int i=0;i<this.m_base_pivots;i++)
     {
      this.m_base_time[i]=base_time[i];      // Time (i) of the base object pivot point
      this.m_base_price[i]=base_price[i];    // Price (i) of the base object pivot point
     }
  }
//+------------------------------------------------------------------+

このメソッドは、必要なすべての基本グラフィカルオブジェクトプロパティ値を受け取ります。次に、基本オブジェクトタイプを確認します。これが価格/時間座標に基づかないオブジェクトである場合は、メソッドを終了します。そのようなオブジェクトはまだ処理していません。次に、メソッドに渡される基本オブジェクト座標の配列のサイズを確認します。サイズがゼロの場合(メソッドが空の配列を受け取った場合)、そのことを通知してメソッドを終了します。次に、渡されたものに応じて座標内部配列のサイズを変更します。配列の変更に失敗した場合は、そのことを通知して終了します。最後に、単純に入力配列を要素ごとに内部の配列にコピーします

以下は、ピボットポイントを管理するための参照ポイントのサイズを設定するメソッドです。

//+------------------------------------------------------------------+
//|Set the size of reference points for managing pivot points        |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetControlFormSize(const int size)
  {
   this.m_ctrl_form_size=(size>254 ? 255 : size<5 ? 5 : size%2==0 ? size+1 : size);
   this.m_shift=(int)ceil(m_ctrl_form_size/2)+1;
  }
//+------------------------------------------------------------------+

メソッドは必要なフォームサイズを受け取ります。サイズが254を超える場合は、255に設定します(奇数値)。渡されたサイズが5未満の場合は、5に設定します(これは最小フォームサイズになります) 価値)。それ以外の場合、渡されたサイズが2に等しい場合は、それに1を追加して使用します確認された値のいずれもtrueでない場合は、メソッドに渡されたサイズを使用します
次に、グラフィカルオブジェクトのピボットポイントが正確に中心に配置されるようにフォームを設定するために、座標シフトを計算します。これを実現するには、座標値からシフト値を差し引く必要があります。計算フォームサイズを2つに分割して、最も近い整数値を取得して1を追加します。

以下は、基本オブジェクトの時間座標を設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the time coordinate of the base object                       |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjTime(const datetime time,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_time[index]=time;
  }
//+------------------------------------------------------------------+

メソッドは、ピボットポイント時間とオブジェクトピボットポイントインデックスを受け取ります。インデックスがオブジェクト内のピボットポイントの数を超えている場合は、配列範囲外のリクエストを通知して終了します。その結果、メソッドに渡された時間値は、時間配列のインデックスに対応するセルに設定されます
このメソッドは、オブジェクトが変更されたときにクラスオブジェクトで基本オブジェクトの時間を指定するために必要です。

以下は、基本オブジェクトの価格座標を設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the coordinate of the base object price                      |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjPrice(const double price,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_price[index]=price;
  }
//+------------------------------------------------------------------+

このメソッドは、インデックスで指定された基本オブジェクトのピボットポイントの価格をクラスの価格配列に追加することを除いて、上記で検討したメソッドと同じです。

以下は、基本オブジェクトの時間と価格の座標を設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the time and price coordinates of the base object            |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjTimePrice(const datetime time,const double price,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_time[index]=time;
   this.m_base_price[index]=price;
  }
//+------------------------------------------------------------------+

このメソッドは、価格と時間の両方がクラス配列に設定されていることを除いて、上記の2つのメソッドと同じです。

以下は、グラフィカルオブジェクトの指定されたピボットポイントのX座標とY座標を画面座標で返すメソッドです。

//+------------------------------------------------------------------+
//| Return the X and Y coordinates of the specified pivot point      |
//| of the graphical object in screen coordinates                    |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y)
  {
   switch(this.m_base_type)
     {
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        x=this.m_base_x;
        y=this.m_base_y;
        break;
      default:
        if(!::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y))
          {
           x=0;
           y=0;
           return false;
          }
     }
   return true;
  }
//+------------------------------------------------------------------+

このメソッドは、基本グラフィカルオブジェクトの必要なピボットポイントのインデックスを受け取ります。これは、画面座標(画面の左上隅からのピクセル単位)のピボットポイントであり、フォーム画面座標を受け取る2つの変数(リンクを介して)を受け取る必要があります。オブジェクトがすでに画面座標内にある場合、これらの座標が返されます
オブジェクトが価格/時間座標内にある場合は、 ChartTimePriceToXY()関数を使用してそれらを計算します座標を画面の座標に変換できなかった場合は、座標をゼロに設定し、falseを返します
結果としてtrueを返します

以下は、名前でピボットポイントフォームへのポインタを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the pointer to the pivot point form by name               |
//+------------------------------------------------------------------+
CForm *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index)
  {
   index=WRONG_VALUE;
   for(int i=0;i<this.m_list_forms.Total();i++)
     {
      CForm *form=this.m_list_forms.At(i);
      if(form==NULL)
         continue;
      if(form.Name()==name)
        {
         index=i;
         return form;
        }
     }
   return NULL;
  }
//+------------------------------------------------------------------+

このメソッドは、フォームオブジェクトのリストで検出されたフォームのインデックスを特徴とするリンクを介して、目的のフォームの名前と変数を受け取ります。
フォームオブジェクトのリストによるループで、次のオブジェクトを取得します。その名前が目的の名前と一致する場合は、ループインデックスを変数に書き込み、検出されたオブジェクトへのポインタを返します。ループが完了したら、NULLを返します。フォームが見つからなかったため、インデックスは-1になります。この値は、ループ開始前に設定されています。

以下は、基本オブジェクト参照ポイントにフォームオブジェクトを作成するメソッドです。

//+------------------------------------------------------------------+
//| Create a form object on a base object reference point            |
//+------------------------------------------------------------------+
CForm *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index)
  {
   string name=this.m_base_name+"_TKPP_"+(index<this.m_base_pivots ? (string)index : "X");
   CForm *form=this.GetControlPointForm(index);
   if(form!=NULL)
      return NULL;
   int x=0, y=0;
   if(!this.GetControlPointCoordXY(index,x,y))
      return NULL;
   return new CForm(this.m_base_chart_id,this.m_base_subwindow,name,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize());
  }
//+------------------------------------------------------------------+

このメソッドは、フォームオブジェクトを作成する必要のあるピボットポイントのインデックスを受け取ります。
基本オブジェクト名+「ToolKitPivotPoint」」(_TKPP)の省略形+ピボットポイントインデックスで構成されるフォームオブジェクト名を作成します。インデックスの説明を作成するときは、その値を確認します。基本オブジェクトのピボットポイントの数より少ない場合(計算はゼロから開始)、メソッドに渡されるインデックスの文字列表現を使用します。それ以外の場合は、Xアイコンを使用します。これは何故必要なのでしょうか。後に、従属オブジェクトをそのピボットポイントだけでなくポイント間でも基本オブジェクトに接続できるようになります。さらに、オブジェクト全体を再配置するために、基本オブジェクトラインの中心にコントロールフォームを作成する必要があります。したがって、フォーム名には、ピボットポイントだけでなく、他のポイントにもフォームを作成する機能が含まれている必要があります。
次に、メソッドに渡されたインデックスによってリスト内のフォームの存在を確認します。フォームオブジェクトがリスト内のインデックスにすでに存在する場合(ポインタがNULLと等しくない場合)、NULLを返します
次に、ピボットポイントの座標をインデックスで画面座標に変換し、受信した座標にフォームオブジェクトを作成した結果を返します。フォームの中心をピボットポイントに正確に配置するために、シフト値は両方の座標から引かれます
フォームのアンカーポイントの値を設定するだけでもかまいませんが、ライブラリのルールでは、すべてのフォームのアンカーポイントは左上隅にあって変更されないままであると規定されています。したがって、フォームオブジェクトの配置のシフトは必要に応じて使用されます。

以下は、基本オブジェクトのピボットポイントにフォームオブジェクトを作成するメソッドです。

//+------------------------------------------------------------------+
//| Create form objects on the base object pivot points              |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::CreateAllControlPointForm(void)
  {
   bool res=true;
   //--- In the loop by the number of base object pivot points
   for(int i=0;i<this.m_base_pivots;i++)
     {
      //--- Create a new form object on the current pivot point corresponding to the loop index
      CForm *form=this.CreateNewControlPointForm(i);
      //--- If failed to create the form, inform of that and add 'false' to the final result
      if(form==NULL)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM);
         res &=false;
        }
      //--- If failed to add the form to the list, inform of that, remove the created form and add 'false' to the final result
      if(!this.m_list_forms.Add(form))
        {
         CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
         delete form;
         res &=false;
        }
      //--- Set all the necessary properties for the created form object
      form.SetBelong(GRAPH_OBJ_BELONG_PROGRAM); // Object is created programmatically
      form.SetActive(true);                     // Form object is active
      form.SetMovable(true);                    // Movable object
      form.SetActiveAreaShift(0,0,0,0);         // Object active area - the entire form
      form.SetFlagSelected(false,false);        // Object is not selected
      form.SetFlagSelectable(false,false);      // Object cannot be selected by mouse
      form.Erase(CLR_CANV_NULL,0);              // Fill in the form with transparent color and set the full transparency
      //form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,clrSilver); // Draw an outlining rectangle for visual display of the form location
      form.DrawCircle((int)floor(form.Width()/2),(int)floor(form.Height()/2),CTRL_POINT_SIZE,clrDodgerBlue);   // Draw a circle in the form center
      form.DrawCircleFill((int)floor(form.Width()/2),(int)floor(form.Height()/2),2,clrDodgerBlue);             // Draw a point in the form center
      form.Done();                              // Save the initial form object state (its appearance)
     }
   //--- Redraw the chart for displaying changes (if successful) and return the final result
   if(res)
      ::ChartRedraw(this.m_base_chart_id);
   return res;
  }
//+------------------------------------------------------------------+

ここでのロジック全体は、コードコメントで説明されています。簡単に言うと、基本オブジェクトのピボットポイントの数によるループで、ピボットポイントごとに新しいフォームオブジェクトを作成してフォームオブジェクトのリストに追加して、適切な基本オブジェクトのピボットポイントの座標を各フォームに設定します。各フォームには、中央の円と、これが基本オブジェクトのピボットポイントを管理するオブジェクトであることを指定するポイントがあります。

このようなオブジェクトは、最初は非表示になり、マウスカーソルがフォーム領域に合わせられたときにのみ表示されます。今のところ、チャートの変更中の動作をテストするために、それらを表示することにします。以降の記事では自分の計画に固執して、オブジェクトは最初は非表示で、マウスカーソルがアクティブな領域(つまり、フォームオブジェクト全体のサイズ)にを合わせられたときにのみ表示されるようにします。

以下は、リストからすべてのフォームオブジェクトを削除するメソッドです。

//+------------------------------------------------------------------+
//| Remove all form objects from the list                            |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DeleteAllControlPointForm(void)
  {
   this.m_list_forms.Clear();
  }
//+------------------------------------------------------------------+

Clear()メソッドを使用するだけで、リスト全体が完全にクリアされます。

イベントハンドラで、発生したイベントに従ってフォームオブジェクトイベントを処理します。

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      for(int i=0;i<this.m_list_forms.Total();i++)
        {
         CForm *form=this.m_list_forms.At(i);
         if(form==NULL)
            continue;
         int x=0, y=0;
         if(!this.GetControlPointCoordXY(i,x,y))
            continue;
         form.SetCoordX(x-this.m_shift);
         form.SetCoordY(y-this.m_shift);
         form.Update();
        }
      ::ChartRedraw(this.m_base_chart_id);
     }
  }
//+------------------------------------------------------------------+

現在、チャート変更イベントのみを処理しています。すべてのフォームオブジェクトによるループで、リストから次のフォームを取得します。描画されたピボットポイントに従って画面座標を受信できなかった場合は、次のフォームに進みます。新しい画面座標をフォームに設定し、フォームを更新します。ループが完了したら、変更を表示するためにチャートを再描画します。

拡張された標準グラフィカルオブジェクトのツールキットオブジェクトは標準グラフィカルオブジェクトクラスのオブジェクトに格納されているため、\MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqhでクラスを改善する必要があります。

まず、フォームオブジェクトのファイルと、拡張された標準グラフィカルオブジェクトの新しく作成されたツールキットオブジェクトをファイルに含めます

//+------------------------------------------------------------------+
//|                                                 GStdGraphObj.mqh |
//|                                  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 strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\GBaseObj.mqh"
#include "..\..\..\Services\Properties.mqh"
#include "..\..\Graph\Form.mqh"
#include "..\..\Graph\Extend\CGStdGraphObjExtToolkit.mqh"
//+------------------------------------------------------------------+
//| Class of the dependent object pivot point data                   |
//+------------------------------------------------------------------+

抽象標準グラフィカルオブジェクトのクラスのprivateセクションで、拡張標準グラフィカルオブジェクトのツールキットオブジェクトへのポインタを宣言します

//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   CArrayObj         m_list;                                            // List of subordinate graphical objects
   CProperties      *Prop;                                              // Pointer to the property object
   CLinkedPivotPoint m_linked_pivots;                                   // Linked pivot points
   CGStdGraphObjExtToolkit *ExtToolkit;                                 // Pointer to the extended graphical object toolkit
   int               m_pivots;                                          // Number of object reference points
//--- Read and set (1) the time and (2) the price of the specified object pivot point
   void              SetTimePivot(const int index);
   void              SetPricePivot(const int index);
//--- Read and set (1) color, (2) style, (3) width, (4) value, (5) text of the specified object level
   void              SetLevelColor(const int index);
   void              SetLevelStyle(const int index);
   void              SetLevelWidth(const int index);
   void              SetLevelValue(const int index);
   void              SetLevelText(const int index);
//--- Read and set the BMP file name for the "Bitmap Level" object. Index: 0 - ON, 1 - OFF
   void              SetBMPFile(const int index);

public:

publicセクションで、ツールキットオブジェクトへのポインタを返すメソッドを記述します

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value)     { this.Prop.Curr.SetLong(property,index,value);    }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value)    { this.Prop.Curr.SetDouble(property,index,value);  }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value)    { this.Prop.Curr.SetString(property,index,value);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)          const { return this.Prop.Curr.GetLong(property,index);   }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)           const { return this.Prop.Curr.GetDouble(property,index); }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index)           const { return this.Prop.Curr.GetString(property,index); }
//--- Set object's previous (1) integer, (2) real and (3) string properties
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Prev.SetLong(property,index,value);    }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value){ this.Prop.Prev.SetDouble(property,index,value);  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value){ this.Prop.Prev.SetString(property,index,value);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array
   long              GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)      const { return this.Prop.Prev.GetLong(property,index);   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)       const { return this.Prop.Prev.GetDouble(property,index); }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index)       const { return this.Prop.Prev.GetString(property,index); }
   
//--- Return (1) itself, (2) properties and (3) the change history
   CGStdGraphObj    *GetObject(void)                                       { return &this;            }
   CProperties      *Properties(void)                                      { return this.Prop;        }
   CChangeHistory   *History(void)                                         { return this.Prop.History;}
   CGStdGraphObjExtToolkit *GetExtToolkit(void)                            { return this.ExtToolkit;  }
//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property)  { return true;             }

クラスのpublicセクションで、グラフィカルオブジェクトイベントのハンドラを宣言しますコンストラクタでは、ツールキットオブジェクトへのポインタにデフォルト値のNULLを設定し、クラスデストラクタでは、ポインタの有効性を確認します。まず、ツールキットオブジェクトからすべてのフォームを削除してからオブジェクト自体を削除します

private:
//--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property
   void              SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);

public:
//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; this.ExtToolkit=NULL; }
//--- Destructor
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                        if(this.ExtToolkit!=NULL)
                          {
                           this.ExtToolkit.DeleteAllControlPointForm();
                           delete this.ExtToolkit;
                          }
                       }
protected:
//--- 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 long chart_id, const int pivots,
                                   const string name);
                     
public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+

簡略化されたアクセスとグラフィカルオブジェクトプロパティの設定のためのメソッドのブロックで、グラフィカルオブジェクトのピボットポイントの数を返すメソッドを記述します

public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+
//--- Number of object reference points
   int               Pivots(void)                  const { return this.m_pivots;                                                          }
//--- Object index in the list
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM,0);                            }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,0,number);                                 }


protectedパラメトリックコンストラクタで、グラフィカル要素タイプを確認します。これが拡張グラフィカルオブジェクトの場合は、新しいツールキットオブジェクトを作成し、そのオブジェクトへのポインタを ExtToolkit 変数に保存します。コンストラクタの最後で、ツールキットオブジェクトを初期化します。

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::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 long chart_id,const int pivots,
                             const string name)
  {
//--- Create the property object with the default values
   this.Prop=new CProperties(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL);
   this.ExtToolkit=(elm_type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? new CGStdGraphObjExtToolkit() : NULL);
//--- Set the number of pivot points and object levels
   this.m_pivots=pivots;
   int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS);

//--- Set the property array dimensionalities according to the number of pivot points and levels
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_TIME,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_PRICE,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_BMPFILE,2);
   
//--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits
   this.m_type=obj_type;
   this.SetName(name);
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(elm_type);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetSpecies(species);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save the integer properties inherent in all graphical objects but not present in the current one
   this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID());                // Chart ID
   this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());               // Chart subwindow index
   this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject());            // Graphical object type (ENUM_OBJECT)
   this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement());   // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong());                   // Graphical object affiliation
   this.SetProperty(GRAPH_OBJ_PROP_SPECIES,0,CGBaseObj::Species());                 // Graphical object species
   this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,0);                                      // Graphical object group
   this.SetProperty(GRAPH_OBJ_PROP_ID,0,0);                                         // Object ID
   this.SetProperty(GRAPH_OBJ_PROP_BASE_ID,0,0);                                    // Base object ID
   this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0);                                        // Object index in the list
   this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,false);                         // Flag of storing the change history
   this.SetProperty(GRAPH_OBJ_PROP_BASE_NAME,0,this.Name());                        // Base object name
   
//--- Save the properties inherent in all graphical objects and present in a graphical object
   this.PropertiesRefresh();
   
//--- Save basic properties in the parent object
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME,0);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);

//--- Initialize the extended graphical object toolkit
   if(this.GraphElementType()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
     {
      datetime times[];
      double prices[];
      if(::ArrayResize(times,this.Pivots())!=this.Pivots())
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA);
      if(::ArrayResize(prices,this.Pivots())!=this.Pivots())
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA);
      for(int i=0;i<this.Pivots();i++)
        {
         times[i]=this.Time(i);
         prices[i]=this.Price(i);
        }
      this.ExtToolkit.SetBaseObj(this.TypeGraphObject(),this.Name(),this.ChartID(),this.SubWindow(),this.Pivots(),CTRL_FORM_SIZE,this.XDistance(),this.YDistance(),times,prices);
      this.ExtToolkit.CreateAllControlPointForm();
      this.SetFlagSelected(false,false);
      this.SetFlagSelectable(false,false);
     }

//--- Save the current properties to the previous ones
   this.PropertiesCopyToPrevData();
  }
//+-------------------------------------------------------------------+

ツールキットオブジェクトを初期化するときは、まず時間と価格のプロパティの配列を宣言し、グラフィカルオブジェクトのピボットポイントの数に応じてサイズを調整し、ループインデックスに対応するオブジェクトのピボットポイントから価格と時間の値を設定します。
次に、ツールキットオブジェクトの初期化メソッドを呼び出し、必要なグラフィカルオブジェクトパラメータを、新しく入力された価格と時間のプロパティ配列とともに渡します。初期化後、グラフィカルオブジェクトのピボットポイントにフォームオブジェクトを作成するためのメソッドを呼び出します。最後に、グラフィカルオブジェクトの選択されていないオブジェクトのステータスを設定し、マウスで選択する機能を無効にします。

オブジェクトプロパティの変更を確認するメソッドの拡張標準グラフィカルオブジェクトを処理するコードブロックで、拡張された標準グラフィカルオブジェクトのピボットポイントの位置を変更するときに、コントロールポイント(フォームオブジェクト)を移動するためのコードブロックを新しい画面座標に追加します

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   CGBaseObj::ClearEventsList();
   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;
      for(int j=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(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=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(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      for(int j=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());
           }
        }
     }
   if(changed)
     {
      for(int i=0;i<this.m_list_events.Total();i++)
        {
         CGBaseEvent *event=this.m_list_events.At(i);
         if(event==NULL)
            continue;
         ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
        }
      if(this.AllowChangeHistory())
        {
         int total=HistoryChangesTotal();
         if(this.CreateNewChangeHistoryObj(total<1))
            ::Print
              (
               DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total),
               ": ",this.HistoryChangedObjTimeChangedToString(total-1)
              );
        }
      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- get the data object of its pivot points,
            CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
            if(pp==NULL)
               continue;
            //--- get the number of coordinate points the object is attached to
            int num=pp.GetNumLinkedCoords();
            //--- In the loop by the object coordinate points,
            for(int j=0;j<num;j++)
              {
               //--- get the number of coordinate points of the base object for setting the X coordinate
               int numx=pp.GetBasePivotsNumX(j);
               //--- In the loop by each coordinate point for setting the X coordinate,
               for(int nx=0;nx<numx;nx++)
                 {
                  //--- get the property for setting the X coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyX(j,nx);
                  int modifier_from=pp.GetPropertyModifierX(j,nx);
                  this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
                 }
               //--- Get the number of coordinate points of the base object for setting the Y coordinate
               int numy=pp.GetBasePivotsNumY(j);
               //--- In the loop by each coordinate point for setting the Y coordinate,
               for(int ny=0;ny<numy;ny++)
                 {
                  //--- get the property for setting the Y coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyY(j,ny);
                  int modifier_from=pp.GetPropertyModifierY(j,ny);
                  this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
                 }
              }
            dep.PropertiesCopyToPrevData();
           }
         //--- Move reference control points to new coordinates
         if(ExtToolkit!=NULL)
           {
            for(int i=0;i<this.Pivots();i++)
              {
               ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
              }
            ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
            long   lparam=0;
            double dparam=0;
            string sparam="";
            ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }
      //--- Save the current properties as the previous ones
      this.PropertiesCopyToPrevData();
     }
  }
//+------------------------------------------------------------------+

グラフィカルオブジェクトのピボットポイントの1つまたはオブジェクト全体を移動すると、そのピボットポイントの画面座標が変化するため、ツールキットクラスのフォームオブジェクトも新しい画面座標に移動する必要があります。したがって、まず新しいグラフィカルオブジェクト座標をツールキットオブジェクトに渡します(ループ内で、価格/時間座標のピボットポイントの数とピクセル単位の個別の座標による) 。次に、チャート変更イベントのIDを渡すことにより、ツールキットオブジェクトイベントのハンドラを呼び出します。ツールキットオブジェクトイベントのハンドラですべてのフォームの画面座標を再計算し、グラフィカルオブジェクトの新しい価格と時間の座標に従って新しい場所に再配置します。

下位の標準グラフィカルオブジェクトをリストに追加する方法で、小さなエラーを修正します。下位のグラフィカルオブジェクトではそれ自体のプロパティを変更するため、クリックしたときにこれらのグラフィカルオブジェクトを変更する新しいイベントが生成されないように、新しいプロパティは、すぐに前のプロパティとして保存する必要があります。

//+------------------------------------------------------------------+
//| Add a subordinate standard graphical object to the list          |
//+------------------------------------------------------------------+
bool CGStdGraphObj::AddDependentObj(CGStdGraphObj *obj)
  {
   //--- If the current object is not an extended one, inform of that and return 'false'
   if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
     {
      CMessage::ToLog(MSG_GRAPH_OBJ_NOT_EXT_OBJ);
      return false;
     }
   //--- If failed to add the pointer to the passed object into the list, inform of that and return 'false'
   if(!this.m_list.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_DEP_EXT_OBJ_TO_LIST);
      return false;
     }
   //--- Object added to the list - set its number in the list,
   //--- name and ID of the current object as the base one,
   //--- set the flags of object availability and selection
   //--- and the graphical element type - standard extended graphical object
   obj.SetNumber(this.m_list.Total()-1);
   obj.SetBaseName(this.Name());
   obj.SetBaseObjectID(this.ObjectID());
   obj.SetFlagSelected(false,false);
   obj.SetFlagSelectable(false,false);
   obj.SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED);
   obj.PropertiesCopyToPrevData();
   return true;
  }
//+------------------------------------------------------------------+


以下は、抽象標準グラフィカルオブジェクトイベントのハンドラです。

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(GraphElementType()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
      return;
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      if(ExtToolkit==NULL)
         return;
      for(int i=0;i<this.Pivots();i++)
        {
         ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
        }
      ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
      ExtToolkit.OnChartEvent(id,lparam,dparam,sparam);
     }
  }
//+------------------------------------------------------------------+

今のところ、ハンドラはチャート変更イベントのみを処理します。

オブジェクトが拡張されていない場合は、ハンドラを終了しますチャート変更イベントが検出された場合、拡張標準グラフィカルオブジェクトのツールキットオブジェクトへのポインタの有効性を確認します。ツールキットが作成されていない場合は、終了します。次に、グラフィカルオブジェクトのピボットポイント数のループで、新しいグラフィカルオブジェクトの価格/時間座標をツールキットオブジェクトに設定します。次に、新しい画面座標を設定し、ツールキットオブジェクトイベントのハンドラを呼び出します。ここで、すべてのフォームが、新しくツールキットオブジェクトに渡された価格/時間座標に基づいて新しい画面座標に再配置されます。


拡張された標準グラフィカルオブジェクトをチャートから削除する場合、そのようなオブジェクトがグラフィカルオブジェクト用に作成された場合に備えて、ツールキットオブジェクトのフォームオブジェクトも削除する必要があります。チャートからのグラフィカルオブジェクトの削除は、\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqhのグラフィカル要素のコレクションクラスで処理されます。

拡張グラフィカルオブジェクトの削除を処理するメソッドで、ツールキットオブジェクトからすべてのフォームオブジェクトを削除するためのコードブロックを追加します

//+------------------------------------------------------------------+
//| Handle the removal of extended graphical objects                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteExtendedObj(CGStdGraphObj *obj)
  {
   if(obj==NULL)
      return;
   //--- Save the ID of the graphical object chart and the number of subordinate objects in its list
   long chart_id=obj.ChartID();
   int total=obj.GetNumDependentObj();
   //--- If the list of subordinate objects is not empty (this is the base object)
   if(total>0)
     {
      CGStdGraphObjExtToolkit *toolkit=obj.GetExtToolkit();
      if(toolkit!=NULL)
        {
         toolkit.DeleteAllControlPointForm();
        }
      //--- In the loop, move along all dependent objects and remove them
      for(int n=total-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=obj.GetDependentObj(n);
         if(dep==NULL)
            continue;
         //--- 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);
        }
      //--- Upon the loop completion, update the chart to display the changes and exit the method
      ::ChartRedraw(chart_id);
      return;
     }
   //--- If this is a subordinate object
   else if(obj.BaseObjectID()>0)
     {
      //--- Get the base object name and its ID
      string base_name=obj.BaseName();
      long base_id=obj.BaseObjectID();
      //--- Get the base object from the graphical object collection list
      CGStdGraphObj *base=GetStdGraphObject(base_name,chart_id);
      if(base==NULL)
         return;
      //--- get the number of dependent objects in its list
      int count=base.GetNumDependentObj();
      //--- In the loop, move along all its dependent objects and remove them
      for(int n=count-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=base.GetDependentObj(n);
         //--- 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()))
            continue;
         //--- If failed to delete the graphical object from the chart,
         //--- 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);
            continue;
           }
        }
      //--- 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);
     }
   //--- Update the chart for displaying the changes
   ::ChartRedraw(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    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
      //--- If the event ID corresponds to a user event, the chart ID is received from lparam
      //--- Otherwise, the chart ID is assigned to -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Get the object, whose properties were changed or which was relocated,
      //--- from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,chart_id);
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      if(obj==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         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,
         //--- and send an event with the new name of the object to the control program chart
         if(obj.SetNamePrev(obj.Name()) && obj.SetName(name_new))
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),obj.TimeCreate(),obj.Name());
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj.PropertiesRefresh();
      obj.PropertiesCheckChanged();
     }
//--- Handle chart changes for extended standard objects
   if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE)
     {
      CArrayObj *list=this.GetListStdGraphObjectExt();
      if(list!=NULL)
        {
         for(int i=0;i<list.Total();i++)
           {
            obj=list.At(i);
            if(obj==NULL)
               continue;
            obj.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
        }
     }
  }
//+------------------------------------------------------------------+

ここで、チャート変更イベントが検出された場合は、すべての拡張された標準グラフィカルオブジェクトのリストを取得します。番号によるループで、次のオブジェクトを取得し、そのイベントハンドラを呼び出して、「Chart changed」イベント値を渡します。


テスト

テストを実行するには、前の記事のEAを\MQL5\Experts\TestDoEasy\Part95\でTestDoEasyPart95.mq5として保存します。

唯一の変更点は、基本トレンドラインオブジェクトに付加された価格ラベルオブジェクトの名前がわずかに異なるです。EAイベントハンドラの複合グラフィカルオブジェクトを作成するブロック内の従属オブジェクト名は、名前が拡張グラフィカルオブジェクトタイプに対応するように「Ext」接辞を備えています。

   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      //--- Get the chart click coordinates
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         //--- Get the right point coordinates for a trend line
         datetime time2=iTime(Symbol(),PERIOD_CURRENT,1);
         double price2=iOpen(Symbol(),PERIOD_CURRENT,1);
         
         //--- Create the "Trend line" object
         string name_base="TrendLineExt";
         engine.CreateLineTrend(name_base,0,true,time,price,time2,price2);
         //--- 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());
         
         //--- Create the "Left price label" object
         string name_dep="PriceLeftExt";
         engine.CreatePriceLabelLeft(name_dep,0,false,time,price);
         //--- Get the object from the list of graphical objects by chart name and ID and
         CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line left point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0);
         
         //--- Create the "Right price label" object
         name_dep="PriceRightExt";
         engine.CreatePriceLabelRight(name_dep,0,false,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and
         dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line right point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1);
        }
     }
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);

複合グラフィカルオブジェクトを作成しましょう。フォームオブジェクトは、作成中にそのピボットポイントに設定されます。
これらのフォームオブジェクトの座標は、画面の左上隅からピクセル単位で表示されます。したがって、チャートを移動する場合は、これらの画面座標を再計算して、オブジェクトが適切なグラフィカルオブジェクトのピボットポイントに設定されるようにする必要があります。これがここで確認しようとしていることです。

EAをコンパイルし、チャート上で起動します。


チャートが変わると、オブジェクトが指定された場所に移動することがわかります。ただし、これは遅れて発生します。
グラフィカルオブジェクトを削除すると、適切なフォームオブジェクトも削除されます。

遅れについて何ができるでしょうか?実際、動きをライブで確認する必要はありません。これらの動きは、チャートを移動するときに常に非表示のままになります(イベント応答を処理するために表示されるようになりました)。フォームオブジェクトをマウスでドラッグすると、グラフィカルオブジェクトの線自体が移動します。フォームとのやり取りはすべて、固定チャートで実行されます。特に、チャートは各ループの反復ではなくループの完了後にのみ更新されることを考慮すると、この結果は非常に十分です。負荷を軽減するために、変更を表示し、後でオブジェクトを表示するチャート変更の完了を制御できます(これは、カーソルがフォームオブジェクトのアクティブ領域に合わせられたときに表示される場合にのみ可能です)。

次の段階

次の記事では、複合グラフィカルオブジェクトイベントの作業を続けます。

現在のライブラリバージョン、テストEA、およびMQL5のチャートイベントコントロール指標のすべてのファイルが、テストおよびダウンロードできるように以下に添付されています。質問、コメント、提案はコメント欄にお願いします。

目次に戻る

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

DoEasyライブラリのグラフィックス(第93部): 複合グラフィカルオブジェクトを作成するための機能の準備
DoEasyライブラリのグラフィックス(第94部): 複合グラフィカルオブジェクトの移動と削除