English Русский 中文 Español Deutsch Português
preview
DoEasyライブラリのグラフィックス(第98部):拡張された標準グラフィカルオブジェクトのピボットポイントの移動

DoEasyライブラリのグラフィックス(第98部):拡張された標準グラフィカルオブジェクトのピボットポイントの移動

MetaTrader 5 | 9 6月 2022, 09:42
178 0
Artyom Trishkin
Artyom Trishkin

内容


概念

第93部で、複合グラフィカルオブジェクトの開発を開始しましたが、CCanvas クラスに基づくフォームオブジェクトの機能を改善する必要性に気を取られました。拡張されたされた標準グラフィカルオブジェクトに含まれるグラフィカルオブジェクトのピボットポイントコントロールを作成するために、複合グラフィカルオブジェクトのフォームオブジェクトを使用します。そのため、新しいフォームオブジェクト機能が必要でした。

前回の記事では、CCanvasクラスに基づくオブジェクトの改善を完了しました。今日は、複合グラフィカルオブジェクトで構成される、拡張された標準グラフィカルオブジェクトの開発を続けます。

この記事の目的は、新しいクラスの開発ではありません。代わりに、標準のグラフィカルオブジェクトのピボットポイントを再配置するための便利なツールを作成するために、すでに準備されている機能の改善を検討します。本稿では、複合グラフィカルオブジェクトのプロトタイプの開発について説明します。以前の記事ですでに作成しました。これは通常のトレンドラインであり、最後に追加の価格ラベルオブジェクトがあります。


今日は、トレンドラインの再配置された側に付けられた価格ラベルと一緒にトレンドラインのピボットポイントを移動する問題を扱います。ラインのピボットポイントには、再配置用のフォームオブジェクトが含まれています。オブジェクトをつかんで移動することにより、適切なトレンドライン側も移動します。カーソルがトレンドラインのピボットポイントから離れている間、フォームオブジェクトは表示されません。ただし、カーソルが特定の距離でピボットポイントに近づくと(完全に透明なフォームの領域に入るとき)、円の付いたポイントがフォームに表示されます。


トレンドラインのピボットポイントコントロールはこのようにチャートに表示されます。フォームのサイズはアクティブな領域よりも大きくなっています。フォームのアクティブな領域は、ドラッグしてフォームを移動できる領域です。対照的に、フォーム自体の領域は、マウスボタンとマウスホイールを使用して、フォームとの他の種類の対話を調整するために使用できます。

したがって、マウスカーソルがフォーム内にあるがアクティブ領域の外にある場合は、たとえば、マウスの右ボタンを押したときに複合グラフィカルオブジェクトのコンテキストメニューをアクティブ化するように実装することができます。カーソルがアクティブな領域にある場合は、コンテキストメニューに加えて、このフォームをマウスでつかんで移動できます。この場合、フォームが添付されているラインの終わりも移動します。

もちろん、これは新しい機能を試すために使用される単なるテスト複合グラフィカルオブジェクトです。複合グラフィカルオブジェクトの作成とフォームオブジェクトの処理に必要なすべてのツールが作成されたら、カスタムオブジェクトの開発に使用する標準ライブラリ複合グラフィカルオブジェクトの小さなセットを作成します。それらの作成は、この種の独自のオブジェクトを作成する方法の例と説明として役立ちます。

今のところ、私はライブラリの機能を開発し、カスタムオブジェクトを作成するために必要な「レンガ」を作成しているだけです。私の記事で説明、分析、および実装された手順は、すべてを最初から実装する必要なしに、「そのまま」使用するための基礎として提供します。


ライブラリクラスの改善

\MQL5\Include\DoEasy\Defines.mqhを開き、いくつかの変更を実装します。

ピボットポイント制御フォームには、ポイントと円が含まれます。適用された色は、以前にコードで指定されています。デフォルトの色を特徴とするマクロ置換を追加しましょう。

//--- Pending request type IDs
#define PENDING_REQUEST_ID_TYPE_ERR    (1)                        // Type of a pending request created based on the server return code
#define PENDING_REQUEST_ID_TYPE_REQ    (2)                        // Type of a pending request created by request
//--- Timeseries parameters
#define SERIES_DEFAULT_BARS_COUNT      (1000)                     // Required default amount of timeseries data
#define PAUSE_FOR_SYNC_ATTEMPTS        (16)                       // Amount of pause milliseconds between synchronization attempts
#define ATTEMPTS_FOR_SYNC              (5)                        // Number of attempts to receive synchronization with the server
//--- Tick series parameters
#define TICKSERIES_DEFAULT_DAYS_COUNT  (1)                        // Required number of days for tick data in default series
#define TICKSERIES_MAX_DATA_TOTAL      (200000)                   // Maximum number of stored tick data of a single symbol
//--- Parameters of the DOM snapshot series
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // The default required number of days for DOM snapshots in the series
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Maximum number of stored DOM snapshots of a single symbol
//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
//--- 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_RADIUS              (5)                        // Radius of the control point on the form for managing graphical object pivot points
#define CTRL_POINT_COLOR               (clrDodgerBlue)            // 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_POINT_SIZEマクロ置換の名前をCTRL_POINT_RADIUSに変更します。これは、円のフルサイズではなく、半径であるためです。フォームオブジェクトのアクティブ領域を計算するとき、マクロ置換名は少し誤解を招くものでした。

グラフィカル要素オブジェクトクラスのファイル\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhで、グラフィカル要素オブジェクトを作成するメソッドを少し改善します。残念ながら、CCanvasクラスのCreateBitmapLabel()メソッドを呼び出しても、エラーコードは返されません。したがって、メソッドを呼び出す前に、最後のエラーコードをリセットします。グラフィカルラベルの作成に失敗した場合は、操作ログにエラーコードを表示します。これにより、デバッグがわずかに改善されます。

//+------------------------------------------------------------------+
//| Create the graphical element object                              |
//+------------------------------------------------------------------+
bool CGCnvElement::Create(const long chart_id,     // Chart ID
                          const int wnd_num,       // Chart subwindow
                          const string name,       // Element name
                          const int x,             // X coordinate
                          const int y,             // Y coordinate
                          const int w,             // Width
                          const int h,             // Height
                          const color colour,      // Background color
                          const uchar opacity,     // Opacity
                          const bool redraw=false) // Flag indicating the need to redraw
                         
  {
   ::ResetLastError();
   if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.Erase(CLR_CANV_NULL);
      this.m_canvas.Update(redraw);
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      return true;
     }
   CMessage::ToLog(DFUN,::GetLastError(),true);
   return false;
  }
//+------------------------------------------------------------------+

なぜやらなければならなかったのでしょうか。フォームオブジェクトを作成するとき、グラフィカルリソースを作成できない理由を理解するために多くの時間を費やしました。最後に、作成されたグラフィカルリソースの名前が63文字を超えていることが判明しました。CCanvasクラスのグラフィカルラベルを作成するためのメソッドでエラーが報告された場合、何も検索する必要はありません。代わりに、すぐにエラーコードを取得するので、さまざまなクラスのさまざまなメソッドを呼び出すすべてのループを渡す必要はありません。

ただし、リソース名の長さが63文字を超える場合、この改善は役に立ちません。

ERR_RESOURCE_NAME_IS_TOO_LONG     4018     The resource name exceeds 63 characters

エラーコードを返します。

ERR_RESOURCE_NOT_FOUND    4016    Resource with such a name is not found in EX5

しかし、これはすぐに、グラフィカルリソースがなぜ作成されないのかという質問を引き起こすので、まだ良い方です。


拡張された標準グラフィカルオブジェクトツールキットクラスのファイル、 \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqhを改善してみましょう。

各グラフィカルオブジェクトは、チャート上にオブジェクトを配置するために使用される1つまたは複数のピボットポイントを備えています。各ポイントにはフォームオブジェクトが接続されています。標準グラフィカルオブジェクトには、再配置のための独自のポイントがあり、グラフィカルオブジェクトを選択すると表示されます。ライブラリ内の拡張さえrた標準グラフィカルオブジェクトを管理するつもりはありません。この機能を実装するには、フォームオブジェクトを使用してグラフィカルオブジェクトのピボットポイントを移動する方が便利です。そのようなフォームオブジェクトはコントロールポイントより多く存在する可能性があるため、グラフィカルオブジェクトのピボットポイントの数でフォームオブジェクトの数を定義することはできません。ただし、この数を知っている必要があります。したがって、クラスのpublicセクションに、ピボットポイントを管理するために作成されたフォームオブジェクトの数を返すメソッドを追加し、完全に透過的なフォームオブジェクトにピボットポイントを管理するポイントを描画するメソッドを宣言します。

//--- (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 (1) base object pivot points and (2) newly created form objects for managing reference points
   int               GetNumPivotsBaseObj(void)                          const { return this.m_base_pivots;                    }
   int               GetNumControlPointForms(void)                      const { return this.m_list_forms.Total();             }
//--- Create form objects on the base object pivot points
   bool              CreateAllControlPointForm(void);
//--- Draw a reference point on the form
   void              DrawControlPoint(CForm *form,const uchar opacity,const color clr);
//--- Remove all form objects from the list
   void              DeleteAllControlPointForm(void);


基本オブジェクトのピボットポイントにフォームオブジェクトを作成するメソッドで、作成したオブジェクトの名前を短くします「_TKPP_」を「_CP_」に置き換えます。

//+------------------------------------------------------------------+
//| Create a form object on a base object reference point            |
//+------------------------------------------------------------------+
CForm *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index)
  {
   string name=this.m_base_name+"_CP_"+(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());
  }
//+------------------------------------------------------------------+

ここ(およびテストEAファイル)では、グラフィカルリソースの名前が63文字を超えてオブジェクトが作成されなかったため、作成されたフォームオブジェクトの名前を短くする必要がありました。その理由は、CCanvasクラスの動的リソース作成メソッドにあります。作成されたリソースの名前は、「::」記号+メソッドに渡された名前(および上記のメソッドで指定された名前)+チャートID+システム起動から経過したミリ秒の数+疑似乱数で構成されます。

//+------------------------------------------------------------------+
//| Create dynamic resource                                          |
//+------------------------------------------------------------------+
bool CCanvas::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt)
  {
   Destroy();
//--- prepare data array
   if(width>0 && height>0 && ArrayResize(m_pixels,width*height)>0)
     {
      //--- generate resource name
      m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());
      //--- initialize data with zeros
      ArrayInitialize(m_pixels,0);
      //--- create dynamic resource
      if(ResourceCreate(m_rcname,m_pixels,width,height,0,0,0,clrfmt))
        {
         //--- successfully created
         //--- complete initialization
         m_width =width;
         m_height=height;
         m_format=clrfmt;
         //--- succeed
         return(true);
        }
     }
//--- error - destroy object
   Destroy();
   return(false);
  }
//+------------------------------------------------------------------+

残念ながら、これは、作成されたオブジェクトでわかりやすい名前を選択するのに深刻な制限となります。

基本オブジェクトのピボットポイントにフォームオブジェクトを作成するメソッドでは、フォームの中央に配置されるフォームのアクティブ領域の位置とサイズを指定するために、フォームオブジェクトの両側からインデントを計算します。 そのサイズは、CTRL_POINT_RADIUSマクロ置換で設定された2つの値と等しくなければなりません。半径を扱っているので、2つの半径値を使用し、それらをフォームの幅から減算し(フォームの高さはその幅に等しい)、取得した値を2で割って、フォームのアクティブ領域がその中心に描かれている円に等しくなるようにする必要があります。
SetActiveAreaShift()メソッドで、フォームの端からアクティブ領域の境界線のインデントを指定します。

//+------------------------------------------------------------------+
//| 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
      int x=(int)::floor((form.Width()-CTRL_POINT_RADIUS*2)/2);// Active area shift from the form edge
      form.SetActiveAreaShift(x,x,x,x);                        // Object active area is located in the center of the form, its size is equal to the two CTRL_POINT_RADIUS values
      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.DrawRectangle(x,x,form.Width()-x-1,form.Height()-x-1,clrSilver);// Draw an outlining rectangle for visual display of the form active area location
      this.DrawControlPoint(form,0,CTRL_POINT_COLOR);          // Draw a circle and 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;
  }
//+------------------------------------------------------------------+

フォームを作成するときに、フォームのサイズとデバッグ用のアクティブな領域を表示する長方形を描画できます— これらの文字列はコメント化されています。以下で検討する新しいメソッドを使用して、フォームの中央に完全に透明な円を描画します(ただし、それらを描画することをお勧めしますか?)。

以下は、フォームに参照点を描画するメソッドです。

//+------------------------------------------------------------------+
//| Draw a reference point on the form                               |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DrawControlPoint(CForm *form,const uchar opacity,const color clr)
  {
   if(form==NULL)
      return;
   form.DrawCircle((int)::floor(form.Width()/2),(int)::floor(form.Height()/2),CTRL_POINT_RADIUS,clr,opacity);// Draw a circle at the center of the form
   form.DrawCircleFill((int)::floor(form.Width()/2),(int)::floor(form.Height()/2),2,clr,opacity);            // Draw a point at the center of the form
  }
//+------------------------------------------------------------------+

このメソッドには、上記で検討したメソッドから取得した2つの文字列が含まれています。なぜでしょうか。フォームの中央点はさまざまな時点で円で表示または非表示にする必要があります。これを実現するためには、描画する図形の必要な非透明度と色を指定してメソッドを呼び出します。

不要になったため、マウスカーソル移動の処理をイベントハンドラから削除します。

//+------------------------------------------------------------------+
//| 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);
     }
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      for(int i=0;i<this.m_list_forms.Total();i++)
        {
         CForm *form=this.m_list_forms.At(i);
         if(form==NULL)
            continue;
         form.OnChartEvent(id,lparam,dparam,sparam);
        }
      ::ChartRedraw(this.m_base_chart_id);
     }
  }
//+------------------------------------------------------------------+


\MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqhの抽象標準グラフィカルオブジェクトクラスを改善します。

クラスのpublicセクションで、グラフィカルオブジェクトのピボットポイントとそれにバインドされているオブジェクトの座標を同時に変更できるようにするメソッドを宣言します。

//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects
   CArrayObj        *GetListDependentObj(void)        { return &this.m_list;           }
   CGStdGraphObj    *GetDependentObj(const int index) { return this.m_list.At(index);  }
   int               GetNumDependentObj(void)         { return this.m_list.Total();    }
//--- Return the name of the dependent object by index
   string            NameDependent(const int index);
//--- Add the dependent graphical object to the list
   bool              AddDependentObj(CGStdGraphObj *obj);
//--- Change X and Y coordinates of the current and all dependent objects
   bool              ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false);
//--- Return the object of data on pivot points
   CLinkedPivotPoint*GetLinkedPivotPoint(void)        { return &this.m_linked_pivots;  }

グラフィカルオブジェクトのピボットポイントを管理するためのフォームにアクセスするための3つのメソッドを宣言します。

//--- Return the number of base object pivot points for calculating the coordinates in the (1) current and (2) specified object
   int               GetLinkedCoordsNum(void)               const { return this.m_linked_pivots.GetNumLinkedCoords();      }
   int               GetLinkedPivotsNum(CGStdGraphObj *obj) const { return(obj!=NULL ? obj.GetLinkedCoordsNum() : 0);      }
//--- Return the form for managing an object pivot point
   CForm            *GetControlPointForm(const int index);
//--- Return the number of form objects for managing reference points
   int               GetNumControlPointForms(void);
//--- Redraw the form for managing a reference point of an extended standard graphical object
   void              RedrawControlPointForms(const uchar opacity,const color clr);

private:


画面座標で時間と価格を設定するメソッドを追加します。

//--- Symbol for the Chart object 
   string            ChartObjSymbol(void)          const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0);                    }
   bool              SetChartObjSymbol(const string symbol)
                       {
                        if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,0,symbol);
                        return true;
                       }
//--- Set the time and price by screen coordinates
   bool              SetTimePrice(const int x,const int y,const int modifier)
                       {
                        bool res=true;
                        ENUM_OBJECT type=this.GraphObjectType();
                        if(type==OBJ_LABEL || type==OBJ_BUTTON || type==OBJ_BITMAP_LABEL || type==OBJ_EDIT || type==OBJ_RECTANGLE_LABEL)
                          {
                           res &=this.SetXDistance(x);
                           res &=this.SetYDistance(y);
                          }
                        else
                          {
                           int subwnd=0;
                           datetime time=0;
                           double price=0;
                           if(::ChartXYToTimePrice(this.ChartID(),x,y,subwnd,time,price))
                             {
                              res &=this.SetTime(time,modifier);
                              res &=this.SetPrice(price,modifier);
                             }
                          }
                        return res;
                       }
  
//--- Return the flags indicating object visibility on timeframes

画面のXY座標でグラフィカルオブジェクトを処理できるようにするには、画面座標を時間/価格座標に再計算する必要があります。このメソッドは、まず現在のオブジェクトタイプをチェックします。画面座標に基づいて構築されている場合、画面座標はすぐに変更されます。グラフィカルオブジェクトが時間/価格座標に基づいている場合、まずメソッドに渡された画面座標を時間と価格の値に変換する必要があります。取得した値は、グラフィカルオブジェクトパラメータに設定されます。

以下は、オブジェクトのピボットポイントを管理するためのフォームを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the form for managing an object pivot point               |
//+------------------------------------------------------------------+
CForm *CGStdGraphObj::GetControlPointForm(const int index)
  {
   return(this.ExtToolkit!=NULL ? this.ExtToolkit.GetControlPointForm(index) : NULL);
  }
//+------------------------------------------------------------------+

ここではすべてが簡単です。 拡張標準グラフィカルオブジェクトツールキットのオブジェクトが存在する場合インデックスによってフォームオブジェクトが返されますそれ以外の場合、NULLを返します

以下は、参照点を管理するためのフォームオブジェクトの数を返すメソッドです。

//+------------------------------------------------------------------+
//| Return the number of form objects                                |
//| for managing control points                                      |
//+------------------------------------------------------------------+
int CGStdGraphObj::GetNumControlPointForms(void)
  {
   return(this.ExtToolkit!=NULL ? this.ExtToolkit.GetNumControlPointForms() : 0);
  }
//+------------------------------------------------------------------+

このメソッドは、上記のメソッドと似ています。拡張された標準グラフィカルオブジェクトツールキットのオブジェクトが存在する場合,、フォームオブジェクトの数が返されますそれ以外の場合、0を返します

以下は、拡張された標準グラフィカルオブジェクトの参照点を管理するためにフォームを再描画するメソッドです。

//+------------------------------------------------------------------+
//| Redraw the form for managing the control point                   |
//| of the extended standard graphical object                        |
//+------------------------------------------------------------------+
void CGStdGraphObj::RedrawControlPointForms(const uchar opacity,const color clr)
  {
//--- If the object has no extended standard graphical object toolkit, exit
   if(this.ExtToolkit==NULL)
      return;
//--- Get the number of pivot point handling forms
   int total_form=this.GetNumControlPointForms();
//--- In the loop by the number of pivot point management forms
   for(int i=0;i<total_form;i++)
     {
      //--- get the next form object
      CForm *form=this.ExtToolkit.GetControlPointForm(i);
      if(form==NULL)
         continue;
      //--- draw a point and a circle with the specified non-transparency and color
      this.ExtToolkit.DrawControlPoint(form,opacity,clr);
     }
   
//--- Get the total number of bound graphical objects
   int total_dep=this.GetNumDependentObj();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<total_dep;i++)
     {
      //--- get the next graphical object from the list
      CGStdGraphObj *dep=this.GetDependentObj(i);
      if(dep==NULL)
         continue;
      //--- call the method for it
      dep.RedrawControlPointForms(opacity,clr);
     }
  }
//+------------------------------------------------------------------+

メソッドには詳細なコメントが付いています。簡単に言えば、アイデアは次のとおりです。まず、現在のオブジェクトにバインドされているすべてのフォームオブジェクトを再描画します。ご存知のように、依存するグラフィカルオブジェクトをオブジェクトに接続できます。これらの依存オブジェクトには、独自のフォームオブジェクトもあります。すべての依存オブジェクトによって、ループ内の依存オブジェクトごとにこのメソッドを呼び出しましょう。次に、依存オブジェクトで、それら自身の依存オブジェクトのリストをループしそれらに対して同じメソッドを呼び出します。これは、リンクされているすべてのグラフィカルオブジェクトのすべてのフォームオブジェクトが再描画されるまでおこなわれます。

以下は、現在のオブジェクトとすべての依存オブジェクトのX座標とY座標を変更するメソッドです。

//+----------------------------------------------------------------------+
//| Change X and Y coordinates of the current and all dependent objects  |
//+----------------------------------------------------------------------+
bool CGStdGraphObj::ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false)
  {
//--- Set the new coordinates for the pivot point set in 'modifier'
   if(!this.SetTimePrice(x,y,modifier))
      return false;
//--- If the object is not a composite graphical object
//--- or dependent graphical objects are not attached to the object,
//--- there is nothing we can do here, return 'true'
   if(this.ExtToolkit==NULL || this.m_list.Total()==0)
      return true;
//--- Get the graphical object bound to the 'modifier' point
   CGStdGraphObj *dep=this.GetDependentObj(modifier);
   if(dep==NULL)
      return false;
//--- Get the object of the pivot point data of the attached graphical object
   CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
   if(pp==NULL)
      return false;
//--- 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 to the dependent graphical object attached to the 'modifier' point
         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 to the dependent graphical object attached to the 'modifier' point
         int prop_from=pp.GetPropertyY(j,ny);
         int modifier_from=pp.GetPropertyModifierY(j,ny);
         this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
        }
     }
//--- Save the current properties of the dependent graphical object as the previous ones
   dep.PropertiesCopyToPrevData();
//--- Move the management control point to new coordinates
   this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier);
   this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
//--- Redraw the chart, if the flag is enabled
   if(redraw)
      ::ChartRedraw(m_chart_id);
//--- If all is successful, return 'true'
   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;
   string name=this.Name();
   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,name);
     }
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      if(ExtToolkit!=NULL)
         ExtToolkit.OnChartEvent(id,lparam,dparam,name);
     }
  }
//+------------------------------------------------------------------+


\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqhでグラフィカル要素のコレクションクラスを改善しましょう。

チャート上にカーソルを移動すると、チャートにはオブジェクトが表示されますが、マウスカーソルが合わせられているオブジェクトはイベントハンドラで定義されます。次に、オブジェクトに関連するマウスステータスのハンドラが起動されます。拡張された標準グラフィカルオブジェクトのピボットポイントを処理するためにオブジェクトにカーソルを合わせると、フォーム自体、およびフォームが接続されているインデックスとグラフィカルオブジェクトがハンドラに表示されます。このフォームとグラフィカルオブジェクトをハンドラの外部で再度検索しないようにするには、フォームが接続されているグラフィカルオブジェクトのIDと、カーソルが合わせられているフォームのインデックスを変数に保存する必要があります。このデータにより、リストから必要なオブジェクトをすばやく選択し、これらの変数の値によって、カーソルがフォームの上にあることを理解できます

メソッドの宣言にこれらの変数を挿入 して、カーソルの下にあるフォームへのポインタを返します。

//--- Return the pointer to the form located under the cursor
   CForm            *GetFormUnderCursor(const int id, 
                                        const long &lparam, 
                                        const double &dparam, 
                                        const string &sparam,
                                        ENUM_MOUSE_FORM_STATE &mouse_state,
                                        long &obj_ext_id,
                                        int &form_index);

変数はリンクによってメソッドに渡されます。つまり、メソッド内に必要な値を設定するだけで、後で使用できるような適切な変数に保存されます。

クラスのpublicセクションで、標準および拡張グラフィカルオブジェクトのリストを返す2つのメソッドを宣言します。

//--- Return an (1) existing and (2) removed graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const string name,const long chart_id);
   CGStdGraphObj    *GetStdDelGraphObject(const string name,const long chart_id);
//--- Return the existing (1) extended and (2) standard graphical object by its ID
   CGStdGraphObj    *GetStdGraphObjectExt(const long id,const long chart_id);
   CGStdGraphObj    *GetStdGraphObject(const long id,const long chart_id);
//--- Return the list of (1) chart management objects and (2) removed graphical objects
   CArrayObj        *GetListChartsControl(void)                                                          { return &this.m_list_charts_control;  }
   CArrayObj        *GetListDeletedObj(void)                                                             { return &this.m_list_deleted_obj;     }

これらのメソッドは、IDでグラフィカルオブジェクトへのポインタを受け取るために必要です。これらのメソッドの実装を見てみましょう。

以下は、IDによって既存の拡張された標準グラフィカルオブジェクトを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the existing extended standard                            |
//| graphical object by its ID                                       |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObjectExt(const long id,const long chart_id)
  {
   CArrayObj *list=this.GetListStdGraphObjectExt();
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_ID,0,id,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

ここに、拡張された標準グラフィカルオブジェクトのリストがありますリストには指定されたチャートIDを持つオブジェクトのみが残ります
取得したリストから、指定したオブジェクトIDを持つオブジェクトを選択します取得したリストが有効で空でない場合、リストに含まれている必要なオブジェクトへのポインタを返しますそれ以外の場合、NULLを返します

以下は、IDによって既存の標準グラフィカルオブジェクトを返すメソッドです。

//+------------------------------------------------------------------+
//| Return the existing standard                                     |
//| graphical object by its ID                                       |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::GetStdGraphObject(const long id,const long chart_id)
  {
   CArrayObj *list=this.GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id);
   list=CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_ID,0,id,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

ここでは、チャートIDごとのグラフィカルオブジェクトのリストを受け取ります取得したリストに、指定したオブジェクトIDのオブジェクトを残します
取得したリストが有効で空でない場合、リストに含まれている必要なオブジェクトへのポインタを返します
それ以外の場合、NULLを返します。

カーソルの下にあるフォームへのポインタを返すメソッドを改善しましょう。拡張された標準グラフィカルオブジェクトのIDとフォームが処理するアンカーポイントのインデックスを格納するための2つの新しい変数を追加して初期化し、拡張された標準グラフィカルオブジェクトを処理するためのブロックを挿入します。これらのオブジェクトに添付されている(およびマウスカーソルが合わせられている)フォームの検索は次の通りです。

//+------------------------------------------------------------------+
//| Return the pointer to the form located under the cursor          |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, 
                                                    const long &lparam, 
                                                    const double &dparam, 
                                                    const string &sparam,
                                                    ENUM_MOUSE_FORM_STATE &mouse_state,
                                                    long &obj_ext_id,
                                                    int &form_index)
  {
//--- Set the ID of the extended standard graphical object to -1 
//--- and the index of the anchor point managed by the form to -1
   obj_ext_id=WRONG_VALUE;
   form_index=WRONG_VALUE;
//--- Initialize the mouse status relative to the form
   mouse_state=MOUSE_FORM_STATE_NONE;
//--- Declare the pointers to graphical element collection class objects
   CGCnvElement *elm=NULL;
   CForm *form=NULL;
//--- Get the list of objects the interaction flag is set for (there should be only one object)
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL);
//--- If managed to obtain the list and it is not empty
   if(list!=NULL && list.Total()>0)
     {
      //--- Get the only graphical element there
      elm=list.At(0);
      //--- If the element is a form object
      if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return form;
        }
     }
//--- If there is no a single form object with a specified interaction flag,
//--- in the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next element
      elm=this.m_list_all_canv_elm_obj.At(i);
      if(elm==NULL)
         continue;
      //--- if the obtained element is a form object
      if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return form;
        }
     }
//--- If there is no a single form object from the collection list
//--- Get the list of extended standard graphical objects
   list=this.GetListStdGraphObjectExt();
   if(list!=NULL)
     {
      //--- in the loop by all extended standard graphical objects
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next graphical object,
         CGStdGraphObj *obj_ext=list.At(i);
         if(obj_ext==NULL)
            continue;
         //--- get the object of its toolkit,
         CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit();
         if(toolkit==NULL)
            continue;
         //--- handle the event of changing the chart for the current graphical object
         obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
         //--- Get the total number of form objects created for the current graphical object
         total=toolkit.GetNumControlPointForms();
         //--- In the loop by all form objects
         for(int j=0;j<total;j++)
           {
            //--- get the next form object,
            form=toolkit.GetControlPointForm(j);
            if(form==NULL)
               continue;
            //--- get the mouse status relative to the form
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- If the cursor is inside the form,
            if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
              {
               //--- set the object ID and form index
               //--- and return the pointer to the form
               obj_ext_id=obj_ext.ObjectID();
               form_index=j;
               return form;
              }
           }
        }
     }
//--- Nothing is found - return NULL
   return NULL;
  }
//+------------------------------------------------------------------+

追加されたコードブロックのロジックはすべてコメントで説明されています。簡単に言うと、マウスカーソルが合わせられているフォームオブジェクトを見つける必要があります。まず、コレクションクラスのグラフィカル要素のリストに格納されているフォームオブジェクトを見つけます。単一のフォームが見つからない場合は、フォームを検索するときにすべての拡張標準グラフィカルオブジェクトを渡す必要があります— カーソルをそれらの1つに置くことができます。この場合、フォームが接続されている拡張された標準グラフィカルオブジェクトのIDは、リンクによってメソッドに渡される変数に設定されます。フォームインデックスも設定されているため、フォームによって管理されるピボットポイントとグラフィカルオブジェクトを知ることができます。

次に、イベントハンドラーで、マウスカーソルと拡張された標準グラフィカルオブジェクトのフォームオブジェクトとの相互作用を処理する必要があります。さらに、フォームオブジェクトの再配置に制御を実装して、ワンクリック取引モードのアクティブ化ボタンが配置されている右上隅のチャート領域に入らないようにします。このボタンは常にすべてのオブジェクトの上に表示されているため、ボタンが誤ってクリックされるのを防ぐために、再配置されたフォームがその領域に入らないようにする必要があります。ワンクリックパネルがすでにアクティブになっている場合は、フォームの処理時に不便を引き起こす可能性があるため、フォームがその領域に入らないようにします。

イベントハンドラの改善点と変更点を見てみましょう。

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj_std=NULL;  // Pointer to the standard graphical object
   CGCnvElement  *obj_cnv=NULL;  // Pointer to the graphical element object on canvas
   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_std=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_std==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj_std=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         if(obj_std==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_std.SetNamePrev(obj_std.Name()) && obj_std.SetName(name_new))
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj_std.ChartID(),obj_std.TimeCreate(),obj_std.Name());
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj_std.PropertiesRefresh();
      obj_std.PropertiesCheckChanged();
     }

//--- Handle standard graphical object events in the collection list
   for(int i=0;i<this.m_list_all_graph_obj.Total();i++)
     {
      //--- Get the next graphical object and
      obj_std=this.m_list_all_graph_obj.At(i);
      if(obj_std==NULL)
         continue;
      //--- call its event handler
      obj_std.OnChartEvent((id<CHARTEVENT_CUSTOM ? id : idx),lparam,dparam,sparam);
     }

//--- 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_std=list.At(i);
            if(obj_std==NULL)
               continue;
            obj_std.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
        }
     }
//--- Handling mouse events of graphical objects on canvas
//--- If the event is not a chart change
   else
     {
      //--- Check whether the mouse button is pressed
      bool pressed=(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)==MOUSE_BUTT_KEY_STATE_LEFT ? true : false);
      ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE;
      //--- Declare static variables for the active form and status flags
      static CForm *form=NULL;
      static bool pressed_chart=false;
      static bool pressed_form=false;
      static bool move=false;
      //--- Declare static variables for the form index of managing an extended standard graphical object and its ID
      static int  form_index=WRONG_VALUE;
      static long graph_obj_id=WRONG_VALUE;
      
      //--- If the button is not pressed on the chart and the movement flag is not set, get the form, above which the cursor is located
      if(!pressed_chart && !move)
         form=this.GetFormUnderCursor(id,lparam,dparam,sparam,mouse_state,graph_obj_id,form_index);
      
      //--- If the button is not pressed, reset all flags and enable the chart tools 
      if(!pressed)
        {
         pressed_chart=false;
         pressed_form=false;
         move=false;
         this.SetChartTools(::ChartID(),true);
        }
      
      //--- If this is a mouse movement event and the movement flag is active, move the form, above which the cursor is located (if the pointer to it is valid)
      if(id==CHARTEVENT_MOUSE_MOVE && move)
        {
         if(form!=NULL)
           {
            //--- calculate the cursor movement relative to the form coordinate origin
            int x=this.m_mouse.CoordX()-form.OffsetX();
            int y=this.m_mouse.CoordY()-form.OffsetY();
            //--- get the width and height of the chart the form is located at
            int chart_width=(int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow());
            int chart_height=(int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow());
            //--- If the form is outside the extended standard graphical object
            if(form_index==WRONG_VALUE)
              {
               //--- Adjust the calculated form coordinates if the form is out of the chart range
               if(x<0) x=0;
               if(x>chart_width-form.Width()) x=chart_width-form.Width();
               if(y<0) y=0;
               if(y>chart_height-form.Height()) y=chart_height-form.Height();
               //--- If the chart has no one-click trading panel,
               if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK))
                 {
                  //--- calculate the form coordinate so that the form does not enter the one-click trading button during relocation
                  if(y<17 && x<41)
                     y=17;
                 }
               //--- If the chart has the one-click trading panel enabled,
               else
                 {
                  //--- calculate the form coordinate so that the form does not enter the one-click trading panel area during relocation
                  if(y<80 && x<192)
                     y=80;
                 }
              }
            //--- If the form is included into the extended standard graphical object
            else
              {
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- Get the list of objects by the object ID (there should be one object)
                  CArrayObj *list_ext=CSelect::ByGraphicStdObjectProperty(GetListStdGraphObjectExt(),GRAPH_OBJ_PROP_ID,0,graph_obj_id,EQUAL);
                  //--- If managed to obtain the list and it is not empty,
                  if(list_ext!=NULL && list_ext.Total()>0)
                    {
                     //--- get the graphical object from the list
                     CGStdGraphObj *ext=list_ext.At(0);
                     //--- If the pointer to the object is received,
                     if(ext!=NULL)
                       {
                        //--- get the object type
                        ENUM_OBJECT type=ext.GraphObjectType();
                        //--- If the object is constructed based on screen coordinates, set the coordinates to the object
                        if(type==OBJ_LABEL || type==OBJ_BUTTON || type==OBJ_BITMAP_LABEL || type==OBJ_EDIT || type==OBJ_RECTANGLE_LABEL)
                          {
                           ext.SetXDistance(x);
                           ext.SetYDistance(y);
                          }
                        //--- otherwise, if the object is based on time/price coordinates
                        else
                          {
                           //--- calculate the coordinate shift and limit the coordinates so that they are not out of the chart range
                           int shift=(int)::ceil(form.Width()/2)+1;
                           if(x+shift<0)
                              x=-shift;
                           if(x+shift>chart_width)
                              x=chart_width-shift;
                           if(y+shift<0)
                              y=-shift;
                           if(y+shift>chart_height)
                              y=chart_height-shift;
                           //--- set calculated coordinates in the object
                           ext.ChangeCoordsExtendedObj(x+shift,y+shift,form_index);
                          }
                       }
                    }
                 }
              }
            //--- Move the form by the obtained coordinates
            form.Move(x,y,true);
           }
        }
   
      //--- Display debugging comments on the chart
      Comment
        (
         (form!=NULL ? form.Name()+":" : ""),"\n",
         EnumToString((ENUM_CHART_EVENT)id),"\n",
         EnumToString(this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam)),
         "\n",EnumToString(mouse_state),
         "\npressed=",pressed,", move=",move,(form!=NULL ? ", Interaction="+(string)form.Interaction() : ""),
         "\npressed_chart=",pressed_chart,", pressed_form=",pressed_form,
         "\nform_index=",form_index,", graph_obj_id=",graph_obj_id
        );
      
      //--- If the cursor is not above the form
      if(form==NULL)
        {
         //--- If the mouse button is pressed
         if(pressed)
           {
            //--- If the button is still pressed and held on the form, exit
            if(pressed_form)
              {
               return;
              }
            //--- If the button hold flag is not enabled yet, set the flags and enable chart tools
            if(!pressed_chart)
              {
               pressed_chart=true;  // Button is held on the chart
               pressed_form=false;  // Cursor is not above the form
               move=false;          // movement disabled
               this.SetChartTools(::ChartID(),true);
              }
           }
         //--- If the mouse button is not pressed
         else
           {
            //--- Get the list of extended standard graphical objects
            CArrayObj *list_ext=GetListStdGraphObjectExt();
            //--- In the loop by all extended graphical objects,
            int total=list_ext.Total();
            for(int i=0;i<total;i++)
              {
               //--- get the next graphical object
               CGStdGraphObj *obj=list_ext.At(i);
               if(obj==NULL)
                  continue;
               //--- and redraw it without a point and a circle
               obj.RedrawControlPointForms(0,CTRL_POINT_COLOR);
              }
           }
        }
      //--- If the cursor is above the form
      else
        {
         //--- If the button is still pressed and held on the chart, exit
         if(pressed_chart)
           {
            return;
           }
         
         //--- If the flag of holding the button on the form is not set yet
         if(!pressed_form)
           {
            pressed_chart=false;    // The button is not pressed on the chart
            this.SetChartTools(::ChartID(),false);
            
            //--- 'The cursor is inside the form, no mouse buttons are clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED)
              {
               //--- If the cursor is above the extended graphical object pivot point control form,
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- get the object by the object and chart IDs
                  CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID());
                  if(graph_obj!=NULL)
                    {
                     //--- Get the toolkit of the extended standard graphical object
                     CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit();
                     if(toolkit!=NULL)
                       {
                        //--- Draw a circle with a point on the form
                        toolkit.DrawControlPoint(form,255,CTRL_POINT_COLOR);
                       }
                    }
                 }
              }
            //--- 'The cursor is inside the form, a mouse button is clicked (any)' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED)
              {
               this.SetChartTools(::ChartID(),false);
               //--- If the flag of holding the form is not set yet
               if(!pressed_form)
                 {
                  pressed_form=true;      // set the flag of pressing on the form
                  pressed_chart=false;    // remove the flag of pressing on the chart
                 }
              }
            //--- 'The cursor is inside the form, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_WHEEL)
              {
               
              }
            
            
            //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED)
              {
               //--- Set the cursor shift relative to the form initial coordinates
               form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX());
               form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY());
               //--- If the cursor is above the active area of the extended graphical object pivot point control form
               if(graph_obj_id>WRONG_VALUE)
                 {
                  //--- get the object by the object and chart IDs
                  CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID());
                  if(graph_obj!=NULL)
                    {
                     //--- Get the toolkit of the extended standard graphical object
                     CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit();
                     if(toolkit!=NULL)
                       {
                        //--- Draw a circle with a point on the form
                        toolkit.DrawControlPoint(form,255,CTRL_POINT_COLOR);
                       }
                    }
                 }
              }
            //--- 'The cursor is inside the active area,  any mouse button is clicked' event handler
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move)
              {
               pressed_form=true;   // the flag of holding the mouse button on the form
               //--- If the left mouse button is pressed
               if(this.m_mouse.IsPressedButtonLeft())
                 {
                  //--- Set flags and form parameters
                  move=true;                                            // movement flag
                  form.SetInteraction(true);                            // flag of the form interaction with the environment
                  form.BringToTop();                                    // form on the background - above all others
                  this.ResetAllInteractionExeptOne(form);               // Reset interaction flags for all forms except the current one
                  form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate
                  form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate
                 }
              }
            //--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL)
              {
               
              }
            
            
            //--- 'The cursor is inside the window scrolling area, no mouse buttons are clicked' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED)
              {
               
              }
            //--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED)
              {
               
              }
            //--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler workpiece
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL)
              {
               
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

メソッドのすべての改善には詳細なコメントが付いています。質問がある場合は、下のコメント欄でお気軽にお問い合わせください。

私がここで実装しようとしていた改善はこれですべてです。


検証

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

実装する唯一の変更は、3つのフォームオブジェクトの作成です。

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart98.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"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define        FORMS_TOTAL (3)   // Number of created forms
#define        START_X     (4)   // Initial X coordinate of the shape
#define        START_Y     (4)   // Initial Y coordinate of the shape
#define KEY_LEFT           (188) // Left
#define KEY_RIGHT          (190) // Right
#define KEY_ORIGIN         (191) // Initial properties
//--- input parameters
sinput   bool              InpMovable     =  true;          // Movable forms flag
sinput   ENUM_INPUT_YES_NO InpUseColorBG  =  INPUT_YES;     // Use chart background color to calculate shadow color
sinput   color             InpColorForm3  =  clrCadetBlue;  // Third form shadow color (if not background color) 
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

この点で、作成された各フォームの座標の計算を少し調整してみましょう。
フォームオブジェクトの宣言は、ループの外側に設定されます

最初のフォームは100のY座標で作成されていますが、残りのフォームには前のフォームの下端から20ピクセルのインデントがあります

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//--- Create form objects
   CForm *form=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      //--- When creating an object, pass all the required parameters to it
      form=new CForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30);
      if(form==NULL)
         continue;
      //--- Set activity and moveability flags for the form
      form.SetActive(true);
      form.SetMovable(true);
      //--- Set the form ID and the index in the list of objects
      form.SetID(i);
      form.SetNumber(0);   // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them
      //--- Set the opacity of 200
      form.SetOpacity(245);
      //--- The form background color is set as the first color from the color array
      form.SetColorBackground(array_clr[0]);
      //--- Form outlining frame color
      form.SetColorFrame(clrDarkBlue);
      //--- Draw the shadow drawing flag
      form.SetShadow(false);
      //--- Calculate the shadow color as the chart background color converted to the monochrome one
      color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
      //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units
      //--- Otherwise, use the color specified in the settings for drawing the shadow
      color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
      //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
      //--- Set the shadow opacity to 200, while the blur radius is equal to 4
      form.DrawShadow(3,3,clr,200,4);
      //--- Fill the form background with a vertical gradient
      form.Erase(array_clr,form.Opacity(),true);
      //--- Draw an outlining rectangle at the edges of the form
      form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
      form.Done();
      
      //--- Display the text describing the gradient type and update the form
      //--- Text parameters: the text coordinates and the anchor point in the form center
      //--- Create a new text animation frame with the ID of 0 and display the text on the form
      form.TextOnBG(0,TextByLanguage("Тест 0","Test 0")+string(i+1),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
      //--- Add the form to the list
      if(!engine.GraphAddCanvElmToCollection(form))
         delete form;
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

OnChartEvent()ハンドラで、マウスクリックによって作成されるグラフィカルオブジェクト名の長さを短くして(前の名前は「TrendLineExt」でした)、グラフィカルリソースを作成するときに63文字を超えないようにします。

   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="TLineExt";
         engine.CreateLineTrend(name_base,0,true,time,price,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID
         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);
        }
     }

//--- Handling graphical element collection events

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


ご覧のとおり、フォームはワンクリック取引パネルボタンが配置されている領域には入力されません。また、フォームがアクティブになっている場合はパネル領域にも入力されません。拡張された標準グラフィカルオブジェクトのピボットポイントを処理するためのフォームは、意図したとおりに機能し、チャートの制限を超えません。
ただし、欠点もあります。複合グラフィカルオブジェクトを作成してそのピボットポイントを移動すると、フォームオブジェクトの上に配置されます。場合によっては、これが正しくないことが分かります。たとえば、パネルがある場合、カーソルによってドラッグされた線は、パネルの上に描画されるのではなくパネルの下を通過する必要があります。各フォームを1つずつクリックすると、それらは複合グラフィカルオブジェクトの上に設定され、再配置中にはこれらのフォームの上に描画されません。3つのフォームを部分的に重ねて2番目のフォームにカーソルを合わせると、最初のフォームがアクティブになります。これを修正します。ここでは、相互およびチャート上の他のオブジェクトに対するすべてのフォームの位置の「深さ」を使用する必要があります。


次の段階

次の記事では、複合グラフィカルオブジェクトとその機能に関する作業を続けます。

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

目次に戻る

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

DoEasyライブラリのグラフィックス(第93部):複合グラフィカルオブジェクトを作成するための機能の準備
DoEasyライブラリのグラフィックス(第94部):複合グラフィカルオブジェクトの移動と削除
DoEasyライブラリのグラフィックス(第95部):複合グラフィカルオブジェクトコントロール
DoEasyライブラリのグラフィックス(第96部):フォームオブジェクトのグラフィックとマウスイベントの処理
DoEasyライブラリのグラフィックス(第97部):フォームオブジェクトの移動の独立した処理

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/10521

添付されたファイル |
MQL5.zip (4233.69 KB)
DoEasyライブラリのグラフィックス(第99部):単一のコントロールポイントを使用した拡張グラフィックオブジェクトの移動 DoEasyライブラリのグラフィックス(第99部):単一のコントロールポイントを使用した拡張グラフィックオブジェクトの移動
前回の記事では、コントロールフォームを使用して拡張グラフィックオブジェクトのピボットポイントを移動する機能を実装しました。次に、単一のグラフィックオブジェクトコントロールポイント(フォーム)を使用して複合グラフィックオブジェクトを移動する機能を実装します。
モメンタムによるトレーディングシステムの設計方法を学ぶ モメンタムによるトレーディングシステムの設計方法を学ぶ
前回は、価格の方向性であるトレンドを見極めることの重要性について述べました。この記事では、最も重要な概念と指標の1つであるモメンタム指標を紹介します。このモメンタム指標に基づいたトレーディングシステムの設計方法を紹介します。
RSIによる取引システムの設計方法を学ぶ RSIによる取引システムの設計方法を学ぶ
今回は、取引の世界で最も人気があり、一般的に使用されている指標の1つであるRSIを紹介します。この指標を使用した取引システムの設計方法を学びます。
単一チャート上の複数インジケータ(第05部):MetaTrader 5をRADシステムに変える(I) 単一チャート上の複数インジケータ(第05部):MetaTrader 5をRADシステムに変える(I)
プログラミングはできなくても創造性に富んだ素晴らしいアイデアを持っている人はたくさんいます。しかし、プログラミングの知識がないため、これらのアイデアを実行に移すことができないのです。MetaTrader5のプラットフォームそのものをIDEのように使って、Chart Tradeを作成する方法を一緒に見てみましょう。