
DoEasyライブラリのグラフィックス(第99部):単一のコントロールポイントを使用した拡張グラフィックオブジェクトの移動
内容
概念
前の記事では、コントロールフォームを使用して拡張グラフィックオブジェクトのピボットポイントを移動する機能を実装しました。ただし、そのようなグラフィックオブジェクト全体を移動する機能はまだありません。標準グラフィックオブジェクトは、その中心点を移動することによって完全に移動できます。同様に、ここでは、グラフィックオブジェクトを管理するための単一の中心点を作成し、その点を移動することで、グラフィックオブジェクト全体(ピボットポイントではなく)を移動できるようにします。テストを実行するためには、両端に価格ラベルオブジェクトを備えたトレンドラインで構成される複合グラフィックオブジェクトを選択しました。これを念頭に置いて、作業全体は、端を再配置するための2つのピボットポイントとグラフィックオブジェクト全体を移動するための1つの中心点(オブジェクトの端を変更するための2つの点と、オブジェクトを移動するための中心点)を持つグラフィックオブジェクトに対しておこなわれます。後で、3つ以上のコントロールポイントを持つグラフィックオブジェクトのコントロールポイントを使用して同じフォームを作成します。
さらに、基本的なロジックをわかりやすくするために、グラフィックオブジェクトのピボットポイントを個別のメソッドに分割するという観点から、画面座標を計算するためのコードを少し最適化します。結局のところ、メインの計算ブロックにそのようなメソッドのかさばったコード全体を設定して理解するのが難しくなるよりも、特定の値を返すメソッドを呼び出すコードを読む方がはるかに簡単です(一方、内部には別のメソッドがあって、何かを計算します)。
実装されたすべてが常に意図したとおりに機能するわけではありません。本稿の目的は、必要な結果を得るためのコードの開発と作成のプロセスを説明することです。「最終的にすべてがどうなるか」についての味気ない記事を読むよりも、機能の計画から実装までのほとんどすべてをおこなう方がはるかに興味深いと思います。
画面座標を取得するChartTimePriceToXY()関数が返すのはチャートの表示部分の座標のみなので、チャートの制限外のラインポイントの画面座標を計算することはできません。表示されているチャートの左側を超えて配置されているスクリーンタイムピクセルのX座標を要求すると、この関数は常に0を返すため、複合グラフィックオブジェクトを画面に沿って移動し、その左側の部分が画面の左側の境界を超えた場合、オブジェクトの左側のピボットポイントはチャートピクセルの座標0のままになります。これによって、グラフィックオブジェクトが歪みます。グラフィックオブジェクトの右側とチャート画面の右側(および上部と下部)にも同じことが当てはまります。したがって、チャートの表示部分を超えた移動に対する制限を複合グラフィックオブジェクトに導入します。これにより、移動時にグラフィックオブジェクトのいずれかの側面が画面の端に「ぶつかった」場合に、グラフィックオブジェクトの歪みを防ぐことができます。
ライブラリクラスの改善
拡張グラフィックオブジェクトのピボットポイントを管理するためのコントロールポイントを表示するために使用されるオブジェクトフォームは、ライブラリオブジェクトの中で重要なオブジェクトであるのにグラフィックオブジェクトコレクションに含まれていないため、このようなフォームの新しいタイプを定義する必要があります。すべての重要なライブラリオブジェクトには独自のライブラリオブジェクト型の名前があるため、現在アクティブなオブジェクトを定義できます。ライブラリの拡張グラフィックオブジェクト内で、ピボットポイントを管理するためのフォームオブジェクトのタイプを定義しましょう。
\MQL5\Include\DoEasy\Defines.mqhで、ライブラリオブジェクト型の列挙に新しい型を追加します。
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type OBJECT_DE_TYPE_GFRAME_TEXT, // "Single text animation frame" object type OBJECT_DE_TYPE_GFRAME_QUAD, // "Single rectangular animation frame" object type OBJECT_DE_TYPE_GFRAME_GEOMETRY, // "Single geometric animation frame" object type OBJECT_DE_TYPE_GANIMATIONS, // "Animations" object type //--- Managing graphical objects ... ... ... }
\MQL5\Include\DoEasy\Data.mqhに、ライブラリの新しいメッセージインデックスを追加します。
MSG_LIB_SYS_REQUEST_OUTSIDE_LONG_ARRAY, // Request outside long array MSG_LIB_SYS_REQUEST_OUTSIDE_DOUBLE_ARRAY, // Request outside double array MSG_LIB_SYS_REQUEST_OUTSIDE_STRING_ARRAY, // Request outside string array MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY, // Request outside the array MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY, // Failed to convert graphical object coordinates to screen ones MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY, // Failed to convert time/price coordinates to screen ones
また、新しく追加されたインデックスに対応するテキストも追加します。
{"Запрос за пределами long-массива","Data requested outside the long-array"}, {"Запрос за пределами double-массива","Data requested outside the double-array"}, {"Запрос за пределами string-массива","Data requested outside the string-array"}, {"Запрос за пределами массива","Data requested outside the array"}, {"Не удалось преобразовать координаты графического объекта в экранные","Failed to convert graphics object coordinates to screen coordinates"}, {"Не удалось преобразовать координаты время/цена в экранные","Failed to convert time/price coordinates to screen coordinates"},
グラフィックオブジェクトを移動する機能を開発しているときに時間/価格座標を画面座標に変換するエラーが検出された場合、そのようなエラーはロジックエラーの検索からチェーンを除外するために報告されます。
ChartTimePriceToXY() 関数は、座標変換エラーを引き起こす可能性がありますが、 \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqhのチャートウィンドウオブジェクトクラスでも使用されています。操作ログでの座標変換エラーメッセージの表示をTimePriceToXY()メソッドに追加しましょう。
//+------------------------------------------------------------------+ //| Convert chart coordinates from the time/price representation | //| to X and Y coordinates | //+------------------------------------------------------------------+ bool CChartWnd::TimePriceToXY(const datetime time,const double price) { ::ResetLastError(); if(!::ChartTimePriceToXY(this.m_chart_id,this.WindowNum(),time,price,this.m_wnd_coord_x,this.m_wnd_coord_y)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY); CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } //+------------------------------------------------------------------+
まず、「時間/価格座標を画面座標に変換できませんでした」というメッセージを表示 し、続いて エラーの説明とエラーコードを表示します。
拡張された標準グラフィックオブジェクトのピボットポイントを管理するためのコントロールポイントの新しいライブラリオブジェクト型を宣言したので、フォームオブジェクトクラスから継承されたそのようなオブジェクトのクラスを作成します。クラス内には、そのようなオブジェクトの処理を簡素化するためのいくつかの変数とメソッドを追加します。
\MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqhにある拡張標準グラフィックオブジェクトのツールキットに設定します。
//+------------------------------------------------------------------+ //| CGStdGraphObjExtToolkit.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, 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" //+------------------------------------------------------------------+ //| Class of the form for managing pivot points of a graphical object| //+------------------------------------------------------------------+ class CFormControl : public CForm { private: bool m_drawn; // Flag indicating that the pivot point is drawn on the form int m_pivot_point; // Pivot point managed by the form public: //--- (1) Return and (2) set the drawn point flag bool IsControlAlreadyDrawn(void) const { return this.m_drawn; } void SetControlPointDrawnFlag(const bool flag) { this.m_drawn=flag; } //--- (1) Return and (2) set the pivot point managed by the form int GraphObjPivotPoint(void) const { return this.m_pivot_point; } void SetGraphObjPivotPoint(const int index) { this.m_pivot_point=index; } //--- Constructor CFormControl(void) { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL; } CFormControl(const long chart_id,const int subwindow,const string name,const int pivot_point,const int x,const int y,const int w,const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL; this.m_pivot_point=pivot_point; } }; //+------------------------------------------------------------------+ //| Extended standard graphical | //| object toolkit class | //+------------------------------------------------------------------+ class CGStdGraphObjExtToolkit : public CObject
m_drawn privateクラスメンバー変数は、ポイントがフォームにすでに描画されていることを通知するフラグを格納するためのものです。なぜそのような変数が必要なのでしょうか。グラフィックオブジェクトのコントロールポイントを管理するためにコントロールフォームのアクティブゾーンからマウスカーソルを削除した場合、フォームに描画されたポイントを削除する必要があります。現在、マウスカーソルがフォームのアクティブな領域に合わせられていない場合、描画されたポイントはそのようなすべてのフォームで常に削除されます。フォームの再描画フラグを最初に確認できるのに、なぜこのようにすべてのフォームを絶えず再描画することでシステムに負担をかける必要があるのでしょうか。フラグがポイントが描画または削除されたことを通知します。将来的には、そのようなポイントを描画するための視覚効果を(他のものに加えて)開発する予定です。したがって、視覚効果ハンドラを実行した直後にフラグを設定する方が、描画はすでに完了していることを定義するよりも優れています。
クラスのprivateメンバー変数m_pivot_pointは、フォームによって管理されるピボットポイントインデックスを格納するためのものです。グラフィックオブジェクトにはいくつかのコントロールポイントがあります。たとえば、トレンドラインには3つのポイントがあります。ラインの終点の位置を個別に変更するためのラインの2つの終点と、オブジェクト全体を移動するための中心点です。フォームオブジェクトに格納されているインデックスは、ラインのピボットポイントのインデックス(0と1 - 線の端に沿った点、2 - 中心点)に対応します。他のグラフィックオブジェクトのコントロールポイントは完全に異なる場合がありますが、すべてのインデックスは、オブジェクト全体を移動するためのオブジェクトピボットポイント+追加の1つ(すべての場合ではありませんが、これについては後続の記事で説明します)に対応します。
クラスのpublicメソッドは上記の変数の値を設定/返すために使用されます。クラスには2つのコンストラクタもあります。デフォルトのコンストラクタでは、本稿で追加された新しいOBJECT_DE_TYPE_GFORM_CONTROLのタイプ がオブジェクトタイプに設定されます。
パラメトリックコンストラクタは、親クラスコンストラクタに渡されるすべての値に加えて、作成されたフォームによって管理されるグラフィックオブジェクトのピボットポイントのインデックスである追加の変数を渡します。
これで、CGStdGraphObjExtToolkitクラスのすべてのピボットポイントコントロールフォームがCFormControl型になるため、CFormフォームオブジェクトの型をCFormControlに置き換え、グラフィックオブジェクトのピボットポイントを管理するためのコントロールフォームを処理するための新しいメソッドを追加する必要があります。
//+------------------------------------------------------------------+ //| 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 CFormControl *CreateNewControlPointForm(const int index); //--- Return X and Y (1) screen coordinates of the specified reference point of the graphical object bool GetControlPointCoordXY(const int index,int &x,int &y); //--- Set the parameters of a form object for managing pivot points void SetControlFormParams(CFormControl *form,const int index); 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 CFormControl *GetControlPointForm(const int index) { return this.m_list_forms.At(index); } CFormControl *GetControlPointForm(const string name,int &index); //--- Return the number of (1) base object pivot points and (2) newly created form objects for managing control 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); //--- (1) Draw a control point on the form, (2) draw a control point on the form and delete it on all other forms void DrawControlPoint(CFormControl *form,const uchar opacity,const color clr); void DrawOneControlPoint(CFormControl *form,const uchar opacity=255,const color clr=CTRL_POINT_COLOR); //--- (1) Draw using a default color, (remove) a control point on the form void DrawControlPoint(CFormControl *form) { this.DrawControlPoint(form,255,CTRL_POINT_COLOR);} void ClearControlPoint(CFormControl *form) { this.DrawControlPoint(form,0,CTRL_POINT_COLOR); } //--- 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(){;} }; //+------------------------------------------------------------------+
グラフィックオブジェクトの指定されたピボットポイントのX座標とY座標を画面座標で返すGetControlPointCoordXY()メソッドを改善します。
以前は、このメソッドは指定されたグラフィックオブジェクトのピボットポイントの計算された座標を返すだけでしたが、グラフィックオブジェクトのピボットポイントの数と中央のピボットポイントの位置が異なる可能性があることを考慮する必要があります。switch()でさまざまな型のオブジェクトの計算を行いましょう。さらに、取得するピボットポイントの座標(オブジェクトの端に沿って配置されている座標または中央の座標)を考慮する必要があります。メソッドに渡されるピボットポイントのインデックスがグラフィックオブジェクトのピボットポイントの総数よりも少ない場合、ピボットポイントの座標が要求されます。それ以外の場合は、中央のピボットポイントの座標が要求されます。
今のところ、端に2つのピボットポイントと1つの中心点を持つグラフィックオブジェクトに対してのみ、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) { //--- Declare form objects, from which we are to receive their screen coordinates CFormControl *form0=NULL, *form1=NULL; //--- Set X and Y to zero - these values will be received in case of a failure x=0; y=0; //--- Depending on the graphical object type switch(this.m_base_type) { //--- Objects drawn using screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : case OBJ_EVENT : //--- Write object screen coordinates and return 'true' x=this.m_base_x; y=this.m_base_y; return true; //--- Lines (vertical and horizontal) case OBJ_VLINE : break; case OBJ_HLINE : break; //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate coordinates for forms on the line pivot points if(index<this.m_base_pivots) return(::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y) ? true : false); //--- Calculate the coordinates for the central form located between the line pivot points else { form0=this.GetControlPointForm(0); form1=this.GetControlPointForm(1); if(form0==NULL || form1==NULL) return false; x=(form0.CoordX()+this.m_shift+form1.CoordX()+this.m_shift)/2; y=(form0.CoordY()+this.m_shift+form1.CoordY()+this.m_shift)/2; return true; } //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- Graphical objects with time/price coordinates case OBJ_TEXT : break; case OBJ_BITMAP : break; //--- default : break; } return false; } //+------------------------------------------------------------------+
ピボットポイントの計算は、ラインピボットポイント座標の m_base_timeおよびm_base_price配列で設定された値を使用して実行されます。中心点の座標を計算するには、線の端のピボットポイントに接続されたフォームオブジェクトの座標を使用します。座標計算が成功した場合、メソッドはすぐにtrueを返します。それ以外の場合はすべて、false を返すか、breakを適用してswitchのcaseのコードの実行を停止し、メソッドの最後に移動してfalseを返します。
ピボットポイントフォームへのポインタを名前で返すメソッドで、CFormをCFormControlに置き換えます。
//+------------------------------------------------------------------+ //| Return the pointer to the pivot point form by name | //+------------------------------------------------------------------+ CFormControl *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index) { index=WRONG_VALUE; for(int i=0;i<this.m_list_forms.Total();i++) { CFormControl *form=this.m_list_forms.At(i); if(form==NULL) continue; if(form.Name()==name) { index=i; return form; } } return NULL; } //+------------------------------------------------------------------+
基本オブジェクトのピボットポイントにフォームオブジェクトを作成するメソッドで、CFormをCFormControlに置き換え、正常に作成されたフォームオブジェクトのパラメーターを設定します。
//+------------------------------------------------------------------+ //| Create a form object on a base object reference point | //+------------------------------------------------------------------+ CFormControl *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index) { string name=this.m_base_name+"_CP_"+(index<this.m_base_pivots ? (string)index : "X"); CFormControl *form=this.GetControlPointForm(index); if(form!=NULL) return NULL; int x=0, y=0; if(!this.GetControlPointCoordXY(index,x,y)) return NULL; form=new CFormControl(this.m_base_chart_id,this.m_base_subwindow,name,index,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize()); //--- Set all the necessary properties for the created form object if(form!=NULL) this.SetControlFormParams(form,index); return form; } //+------------------------------------------------------------------+
基本オブジェクトのピボットポイントにフォームオブジェクトを作成するメソッドでは 、CFormをCFormControlに置き換え、作成されたフォームオブジェクトのパラメータを設定するための文字列を削除します。これは、上記のメソッドでオブジェクトを作成するときにパラメータがすぐに設定されるためです。
//+------------------------------------------------------------------+ //| 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 CFormControl *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; } } //--- Redraw the chart for displaying changes (if successful) and return the final result if(res) ::ChartRedraw(this.m_base_chart_id); return res; } //+------------------------------------------------------------------+
これで、基本オブジェクトのピボットポイントの数に1を加えた数のループが実行されます。つまり、作成されたフォームの数が、グラフィックオブジェクトのピボットポイントの数を1つ上回っています。最後のフォームは中心のもので、グラフィックオブジェクト全体を移動するためのものです。
以下は、ピボットポイントを管理するためのフォームオブジェクトのパラメータを設定するメソッドです。
//+------------------------------------------------------------------+ //| Set the parameters of a form object for managing pivot points | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::SetControlFormParams(CFormControl *form,const int index) { 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);// Shift the active area from the form edge form.SetActiveAreaShift(x,x,x,x); // Object active area is located at the center of the form, its size is equal to 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 form.SetID(index+1); // Set the form ID form.SetControlPointDrawnFlag(false); // Set the flag that the pivot point is not drawn on the form form.Done(); // Save the initial form object state (its appearance) } //+------------------------------------------------------------------+
ここには、上記で検討したメソッドから転送されたコードの文字列があります。それに、フォームに描かれたポイントとフォームIDのフラグがあります。
フォームにコントロールポイントを描画する方法では、計算の重複を避けるために、フォームの中心の計算を別の文字列に再配置します。メソッドが完了したら、フォームに描画ポイントのフラグを設定します。
//+------------------------------------------------------------------+ //| Draw a control point on the form | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::DrawControlPoint(CFormControl *form,const uchar opacity,const color clr) { if(form==NULL) return; int c=int(::floor(form.Width()/2)); // Form center (coordinates) form.DrawCircle(c,c,CTRL_POINT_RADIUS,clr,opacity); // Draw a circle in the form center form.DrawCircleFill(c,c,2,clr,opacity); // Draw a circle in the form center form.SetControlPointDrawnFlag(opacity>0 ? true : false); // Set the flag that the pivot point is drawn on the form } //+------------------------------------------------------------------+
現在、グラフィックオブジェクトのピボットポイントを管理するためにフォームにマウスを合わせると、その上にポイントが表示され、カーソルがフォームを離れた後にのみ削除されます。ただし、オブジェクトのすべてのコントロールポイントを近づけて、グラフィックオブジェクトの端に作成されたフォームと中央のフォームが互いに重なり始めると、1つのフォームから遠ざけたカーソルが近くにありる別のフォームに移動します。したがって、すべてのオブジェクトフォームのすべてのポイントが表示されるように作成することができます。
フォームをつかんで動かし始めると、オブジェクトのピボットポイントも動き始めます。エラーが原因で表示されているフォームは、再配置が開始された前の場所に残ります。この動作は正しくありません。したがって、1つのグラフィックオブジェクトフォームオブジェクトにポイントを描画すると同時に同じオブジェクトの他のフォームオブジェクトのポイントを削除するメソッドが必要です。
以下は、フォームにコントロールポイントを描画し、残りのすべてのフォームからコントロールポイントを削除するメソッドです。
//+------------------------------------------------------------------+ //| Draw a control point on the form, | //| remove it on all other forms | //+------------------------------------------------------------------+ void CGStdGraphObjExtToolkit::DrawOneControlPoint(CFormControl *form,const uchar opacity=255,const color clr=CTRL_POINT_COLOR) { this.DrawControlPoint(form,opacity,clr); for(int i=0;i<this.GetNumControlPointForms();i++) { CFormControl *ctrl=this.GetControlPointForm(i); if(ctrl==NULL || ctrl.ID()==form.ID()) continue; this.ClearControlPoint(ctrl); } } //+------------------------------------------------------------------+
ここで、メソッドはカーソルが合わせられているフォームへのポインタを受け取ります。フォームに点を描きます。次に、すべてのオブジェクトフォームによるループで、フォームを選択し、フォームがメソッドに渡されていない場合は、フォームからポイントを削除します。
イベントハンドラでCFormフォーム型をCFormControlに置き換えます。
//+------------------------------------------------------------------+ //| 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++) { CFormControl *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にある抽象標準グラフィックオブジェクトのクラスでメソッドコードの最適化を改善してみましょう。さまざまなメソッドに同様のコードフラグメントがあるため、このようなコードブロックを個別のメソッドとして実装して必要に応じて呼び出して、コードを読みやすくするのが道理にかなっています。
クラスのpublicとprivateセクションで、繰り返しコードフラグメントを含む新しいメソッドを宣言します。
//--- 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); //--- Set X and Y coordinates into the appropriate pivot points of a specified subordinate object bool SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj); //--- Return the pivot point data object CLinkedPivotPoint*GetLinkedPivotPoint(void) { return &this.m_linked_pivots; }
...
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 X and Y coordinates into the appropriate pivot point of a specified subordinate object void SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj,CLinkedPivotPoint *pivot_point,const int index); //--- 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
オブジェクトプロパティの変更をチェックするメソッドで、指定されたコードブロックを削除します (コードは別のメソッドに移動されます)。
//--- 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); }
削除されたブロックの代わりに新しいメソッドの呼び出しを追加します。
//--- 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; //--- Set X and Y coordinates to all pivot points of a subordinate object and //--- save the current properties of a subordinate graphical object as the previous ones if(this.SetCoordsXYtoDependentObj(dep)) dep.PropertiesCopyToPrevData(); } //--- Move reference control points to new coordinates if(this.ExtToolkit!=NULL) { for(int i=0;i<this.Pivots();i++) { this.ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i); } this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); long lparam=0; double dparam=0; string sparam=""; this.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); }
現在のポイント削除ロジックでは、カーソルがどのフォームにも設定されていない場合にはそのような各フォームが常に再描画されるため(これは最適ではなく、リソースを大量に消費します)、ポイントを実際に削除する必要があり、拡張された標準グラフィックオブジェクトのコントロールポイントを管理するためのフォームを再描画するメソッドにポイントがまだ存在する場合にのみ、ポイント削除フォームを再描画する必要があることを確認するチェックを実装しましょう。また、フォームオブジェクトタイプを新しいものに置き換えます。
//+------------------------------------------------------------------+ //| Redraw the form for managing a control point | //| of an extended standard graphical object | //+------------------------------------------------------------------+ void CGStdGraphObj::RedrawControlPointForms(const uchar opacity,const color clr) { //--- Leave if the object has no toolkit of an extended standard graphical object if(this.ExtToolkit==NULL) return; //--- Get the number of pivot point management 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 CFormControl *form=this.ExtToolkit.GetControlPointForm(i); if(form==NULL) continue; //--- Draw a point and a circle with a specified non-transparency and color //--- If a point should be completely transparent (deleted) //--- and the form still has the point, delete the point, if(opacity==0 && form.IsControlAlreadyDrawn()) this.ExtToolkit.DrawControlPoint(form,0,clr); //--- otherwise, draw the point with a specified non-transparency and color else 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 new coordinates for the pivot point specified in 'modifier' if(!this.SetTimePrice(x,y,modifier)) return false; //--- If the object is not a composite graphical object //--- or if subordinate graphical objects are not attached to the object, //--- there is nothing else to 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 pivot point data of the bound 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 subordinate 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 subordinate 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 a subordinate graphical object as the previous ones dep.PropertiesCopyToPrevData(); //--- Move a reference control point to new coordinates this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier); this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); //--- If the flag is active, redraw the chart if(redraw) ::ChartRedraw(m_chart_id); //--- All is successful - return 'true' return true; } //+------------------------------------------------------------------+
これで、メソッドははるかに簡潔になります。
//+----------------------------------------------------------------------+ //| 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 new coordinates for the pivot point specified in 'modifier' if(!this.SetTimePrice(x,y,modifier)) return false; //--- If the object is a composite graphical object, //--- and subordinate graphical objects are attached to the object if(this.ExtToolkit!=NULL && this.m_list.Total()>0) { //--- Get the graphical object bound to the 'modifier' point CGStdGraphObj *dep=this.GetDependentObj(modifier); if(dep==NULL) return false; //--- Set X and Y coordinates to all pivot points of a subordinate object and //--- save the current properties of a subordinate graphical object as the previous ones if(this.SetCoordsXYtoDependentObj(dep)) dep.PropertiesCopyToPrevData(); } //--- Move a reference control point to new coordinates this.ExtToolkit.SetBaseObjTimePrice(this.Time(modifier),this.Price(modifier),modifier); this.ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance()); //--- If the flag is active, redraw the chart if(redraw) ::ChartRedraw(m_chart_id); //--- All is successful - return 'true' return true; } //+------------------------------------------------------------------+
X座標とY座標を指定した従属オブジェクトの適切なピボットポイントに設定するメソッドを実装します。
//+------------------------------------------------------------------+ //| Set X and Y coordinates to the associated pivot point | //| of a specified subordinate object by index | //+------------------------------------------------------------------+ void CGStdGraphObj::SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj,CLinkedPivotPoint *pivot_point,const int index) { //--- get the number of coordinate points of the base object for setting the X coordinate int numx=pivot_point.GetBasePivotsNumX(index); //--- 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 subordinate graphical object attached to the 'index' point int prop_from=pivot_point.GetPropertyX(index,nx); int modifier_from=pivot_point.GetPropertyModifierX(index,nx); this.SetCoordXToDependentObj(dependent_obj,prop_from,modifier_from,nx); } //--- Get the number of coordinate points of the base object for setting the Y coordinate int numy=pivot_point.GetBasePivotsNumY(index); //--- 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 subordinate graphical object attached to the 'index' point int prop_from=pivot_point.GetPropertyY(index,ny); int modifier_from=pivot_point.GetPropertyModifierY(index,ny); this.SetCoordYToDependentObj(dependent_obj,prop_from,modifier_from,ny); } } //+------------------------------------------------------------------+
実際、これらはまさにクラスメソッドから削除されたコードブロックです。コードロジックは以前の記事で検討しており、コードコメントにも説明されているので、説明は必要ないと思います。
指定された従属オブジェクトの関連するピボットポイントにX座標とY座標を設定するメソッドを実装します 。
//+------------------------------------------------------------------+ //| Set X and Y coordinates to associated pivot points | //| of the specified subordinate object | //+------------------------------------------------------------------+ bool CGStdGraphObj::SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj) { //--- Get the object of pivot point data of the bound graphical object CLinkedPivotPoint *pp=dependent_obj.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, //--- set X and Y to all pivot points of a subordinate object for(int j=0;j<num;j++) this.SetCoordsXYtoDependentObj(dependent_obj,pp,j); return true; } //+------------------------------------------------------------------+
このメソッドでは、従属オブジェクトのすべてのピボットポイントに座標を設定できます。他のグラフィックオブジェクトが複合グラフィックオブジェクトに追加された場合、メソッドはそれらに指定された座標を設定します。
\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqhでグラフィカル要素のコレクションクラスを改善しましょう。
ChartTimePriceToXY()標準関数はXとYの2つの座標を同時に返すため、それらを格納するためにprivateセクションに構造体を作成します。この構造体では座標に加えて、中心点を基準にした座標シフトも格納します。グラフィックオブジェクトには複数のピボットポイントがある場合があるため、グラフィックオブジェクトの各ピボットポイントの座標を保存するために、作成された構造体型で配列を宣言します。この場合、各配列セルは、「時間/価格」座標から変換されたXおよびY画面座標と、グラフィックオブジェクトの中心点に対するピボットポイント座標のシフトを特徴とします。
クラスのprivateセクションで、構造体を作成して必要な配列を宣言します。
//+------------------------------------------------------------------+ //| Collection of graphical objects | //+------------------------------------------------------------------+ #resource "\\"+PATH_TO_EVENT_CTRL_IND; // Indicator for controlling graphical object events packed into the program resources class CGraphElementsCollection : public CBaseObj { private: //--- Pivot point data structure struct SDataPivotPoint { public: int X; // Pivot point X coordinate int Y; // Pivot point Y coordinate int ShiftX; // Pivot point X coordinate shift from the central one int ShiftY; // Pivot point Y coordinate shift from the central one }; SDataPivotPoint m_data_pivot_point[]; // Pivot point data structure array CArrayObj m_list_charts_control; // List of chart management objects CListObj m_list_all_canv_elm_obj; // List of all graphical elements on canvas CListObj m_list_all_graph_obj; // List of all graphical objects CArrayObj m_list_deleted_obj; // List of removed graphical objects CMouseState m_mouse; // "Mouse status" class object bool m_is_graph_obj_event; // Event flag in the list of graphical objects int m_total_objects; // Number of graphical objects int m_delta_graph_obj; // Difference in the number of graphical objects compared to the previous check
クラスのprivateセクションで、グラフィックオブジェクトの各ピボットポイントの画面座標を返すメソッドを宣言します。
private: //--- Find an object present in the collection but not on a chart CGStdGraphObj *FindMissingObj(const long chart_id); CGStdGraphObj *FindMissingObj(const long chart_id,int &index); //--- Find the graphical object present on a chart but not in the collection string FindExtraObj(const long chart_id); //--- Remove the graphical object class object from the graphical object collection list: (1) specified object, (2) by chart ID bool DeleteGraphObjFromList(CGStdGraphObj *obj); void DeleteGraphObjectsFromList(const long chart_id); //--- Move the graphical object class object to the list of removed graphical objects: (1) specified object, (2) by index bool MoveGraphObjToDeletedObjList(CGStdGraphObj *obj); bool MoveGraphObjToDeletedObjList(const int index); //--- Move all objects by chart ID to the list of removed graphical objects void MoveGraphObjectsToDeletedObjList(const long chart_id); //--- Remove the object of managing charts from the list bool DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj); //--- Set the flags of scrolling the chart with the mouse, context menu and crosshairs tool for the specified chart void SetChartTools(const long chart_id,const bool flag); //--- Return the screen coordinates of each pivot point of the graphical object bool GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]); public:
クラス本体の外にメソッドを実装します。
//+------------------------------------------------------------------+ //| Return screen coordinates | //| of each graphical object pivot point | //+------------------------------------------------------------------+ bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]) { //--- If failed to increase the array of structures to match the number of pivot points, //--- inform of that in the journal and return 'false' if(::ArrayResize(array_pivots,obj.Pivots())!=obj.Pivots()) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); return false; } //--- In the loop by the number of graphical object pivot points for(int i=0;i<obj.Pivots();i++) { //--- Convert the object coordinates into screen ones. If failed, inform of that and return 'false' if(!::ChartTimePriceToXY(obj.ChartID(),obj.SubWindow(),obj.Time(i),obj.Price(i),array_pivots[i].X,array_pivots[i].Y)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY); return false; } } //--- Depending on the graphical object type switch(obj.TypeGraphObject()) { //--- One pivot point in screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : break; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : break; case OBJ_EVENT : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate the shifts of all pivot points from the central one and write them to the structure array array_pivots[0].ShiftX=(array_pivots[1].X-array_pivots[0].X)/2; array_pivots[0].ShiftY=(array_pivots[1].Y-array_pivots[0].Y)/2; array_pivots[1].ShiftX=(array_pivots[0].X-array_pivots[1].X)/2; array_pivots[1].ShiftY=(array_pivots[0].Y-array_pivots[1].Y)/2; return true; //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- Graphical objects with time/price coordinates case OBJ_TEXT : break; case OBJ_BITMAP : break; //--- default: break; } return false; } //+------------------------------------------------------------------+
今のところ、2つのピボットポイントと1つの中央のピボットポイントを持つグラフィックオブジェクトの画面座標のみが構造体に設定されています。
メソッドは、ピボットポイント座標を構造体配列に設定する必要があるグラフィックオブジェクトへのポインタを受け取ります。このオブジェクトは、リンクによってメソッドに渡されます。座標変換が成功した場合、メソッドは、グラフィックオブジェクトの各ピボットポイントの画面座標を含む完全に塗りつぶされた構造体配列とともにtrueを返します。失敗した場合、メソッドはfalseを返します。
クラスイベントハンドラでは、オブジェクト管理フォームシフトを処理して、これが中心点である場合にオブジェクト全体が移動されるようにする必要があります。これを実現するには、(オブジェクトをドラッグするために使用される)中央のエッジフォームに対するエッジフォームのシフトを計算し、計算されて構造体に設定されたシフト値によって両方のピボットポイントを再配置する必要があるため、そのすべてのピボットポイントは、マウスによってドラッグされた中央のピボットポイントと同じ値だけシフトします。
中央のコントロールポイント(フォーム)移動イベントのそのような処理を追加しましょう。
//+------------------------------------------------------------------+ //| 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 index of the form for 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 not within an 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 one-click trading panel is not present on the chart, if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK)) { //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel button if(y<17 && x<41) y=17; } //--- If the one-click trading panel is on the chart, else { //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel 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 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 has been received, if(ext!=NULL) { //--- get the object type ENUM_OBJECT type=ext.GraphObjectType(); //--- If the object is built using 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 built based on time/price coordinates, else { //--- calculate the coordinate shift int shift=(int)::ceil(form.Width()/2)+1; //--- If the form is located on one of the graphical object pivot points, if(form_index<ext.Pivots()) { //--- limit the form coordinates so that they do not move beyond the chart borders 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 the calculated coordinates to the object ext.ChangeCoordsExtendedObj(x+shift,y+shift,form_index); } //--- If the form is central for managing all pivot points of a graphical object else { //--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point)) { //--- In the loop by the number of object pivot points, for(int i=0;i<(int)this.m_data_pivot_point.Size();i++) { //--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders if(x+shift-this.m_data_pivot_point[i].ShiftX<0) x=-shift+m_data_pivot_point[i].ShiftX; if(x+shift+this.m_data_pivot_point[i].ShiftX>chart_width) x=chart_width-shift-this.m_data_pivot_point[i].ShiftX; if(y+shift+this.m_data_pivot_point[i].ShiftY<0) y=-shift-this.m_data_pivot_point[i].ShiftY; if(y+shift-this.m_data_pivot_point[i].ShiftY>chart_height) y=chart_height-shift+this.m_data_pivot_point[i].ShiftY; //--- set the calculated coordinates to the current object pivot point ext.ChangeCoordsExtendedObj(x+shift-this.m_data_pivot_point[i].ShiftX,y+shift-this.m_data_pivot_point[i].ShiftY,i); } } } } } } } } //--- 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 form for managing the pivot point of an extended graphical object, if(graph_obj_id>WRONG_VALUE) { //--- get the object by its ID and by the chart ID CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID()); if(graph_obj!=NULL) { //--- Get the toolkit of an extended standard graphical object CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit(); if(toolkit!=NULL) { //--- Draw a point with a circle on the form and delete it on all other forms toolkit.DrawOneControlPoint(form); } } } } //--- '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; // disable the flag of pressing on the form } } //--- '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 form for managing the pivot point of an extended graphical object, if(graph_obj_id>WRONG_VALUE) { //--- get the object by its ID and by the chart ID CGStdGraphObj *graph_obj=this.GetStdGraphObjectExt(graph_obj_id,form.ChartID()); if(graph_obj!=NULL) { //--- Get the toolkit of an extended standard graphical object CGStdGraphObjExtToolkit *toolkit=graph_obj.GetExtToolkit(); if(toolkit!=NULL) { //--- Draw a point with a circle on the form and delete it on all other forms toolkit.DrawOneControlPoint(form); } } } } //--- '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\Part99\にTestDoEasyPart99.mq5として保存します。
EAを変更する必要はありません。現時点では、すべての変更はライブラリクラスでのみおこなわれます。
EAをコンパイルし、チャート上で起動します。
ご覧のとおり、作成されたフォームで複合グラフィックオブジェクトを移動すると、チャートの制限を超えるピボットポイントに関連するすべての制限が正しく機能します。ただし、ピボットポイントの位置を初期位置に対して「逆」にすると、ピボットポイントがチャートの制限を超えた場合にオブジェクトの「構成」が歪んでしまいます。これは、ピボットポイントがチャートの右、左、上、または下の端を超えることについての制限と依存関係の計算が正しくないことを意味します。
ピボットポイントのシフトは中央点を基準にして計算されるため、これは驚くべきことではありません。つまり、1つめのポイントには正のシフトがあり、2番目のポイントには負のシフトがあります。中央のピボットポイントを基準にしてピボットポイントの位置を変更すると、制限の計算にエラーが発生します。これは次の記事で修正します。
次の段階
次の記事では、複合グラフィックオブジェクトの作業を続けます。
**連載のこれまでの記事:
DoEasyライブラリのグラフィックス(第93部):複合グラフィックオブジェクトを作成するための機能の準備
DoEasyライブラリのグラフィックス(第94部):複合グラフィックオブジェクトの移動と削除
DoEasyライブラリのグラフィックス(第95部):複合グラフィックオブジェクトコントロール
DoEasyライブラリのグラフィックス(第96部):フォームオブジェクトのグラフィックとマウスイベントの処理
DoEasyライブラリのグラフィックス(第97部):フォームオブジェクトの移動の独立した処理
DoEasyライブラリのグラフィックス(第98部):拡張された標準グラフィックオブジェクトのピボットポイントの移動
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/10584




- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索