
DoEasyライブラリでのその他のクラス(第70部): チャットオブジェクトコレクショの機能拡張と自動更新
内容
概念
前の記事では、チャートオブジェクトコレクションを作成しました。ターミナルで開かれた各銘柄チャートは、チャートオブジェクトで表されます。各チャートオブジェクトは、ウィンドウ指標オブジェクトを含むウィンドウオブジェクトのセットを備えています。すべてのチャートオブジェクトには、少なくとも1つのウィンドウオブジェクト(メインのチャートウィンドウ)があります。残りのすべての指標ウィンドウは、チャートウィンドウリストに追加したり、チャートウィンドウリストから削除したりできます。オブジェクトセット全体をチャートオブジェクトコレクションに配置しました。
前の記事のチャートオブジェクトコレクションを詳細にテストしたところ、メインチャートウィンドウに新しいウィンドウを追加するときの問題がいくつか明らかになったので、ここで修正します。さらに、チャートオブジェクトの新機能(銘柄チャートウィンドウ内のナビゲーション、ウィンドウスクリーンショットの作成、およびテンプレートの保存とチャートへのアップロード)を追加します。
スケジュールされた改善に加えて、クライアントターミナルのチャートとチャートオブジェクトウィンドウで発生するいくつかのイベント(新規銘柄チャート(チャートオブジェクト)の追加と既存銘柄チャートの削除、チャートオブジェクトからの新規指標ウィンドウの追加と既存指標ウィンドウの削除、チャートウィンドウからの新規指標の追加と既存指標の削除)の自動追跡を実装します。
ライブラリクラスの改善
いつも通り、\MQL5\Include\DoEasy\Data.mqhファイルに新しいメッセージのインデックスを追加します。
MSG_CHART_OBJ_CHART_WINDOW, // Main chart window MSG_CHART_OBJ_CHART_SUBWINDOW, // Chart subwindow MSG_CHART_OBJ_CHART_SUBWINDOWS_NUM, // Subwindows MSG_CHART_OBJ_INDICATORS_MW_NAME_LIST, // Indicators in the main chart window MSG_CHART_OBJ_INDICATORS_SW_NAME_LIST, // Indicators in the chart window MSG_CHART_OBJ_INDICATOR, // Indicator MSG_CHART_OBJ_INDICATORS_TOTAL, // Indicators MSG_CHART_OBJ_WINDOW_N, // Window MSG_CHART_OBJ_INDICATORS_NONE, // No indicators MSG_CHART_OBJ_ERR_FAILED_GET_WIN_OBJ, // Failed to receive the chart window object MSG_CHART_OBJ_SCREENSHOT_CREATED, // Screenshot created MSG_CHART_OBJ_TEMPLATE_SAVED, // Chart template saved MSG_CHART_OBJ_TEMPLATE_APPLIED, // Template applied to chart //--- CChartObjCollection MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION, // Chart collection MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ, // Failed to create a new chart object MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART, // Failed to add a chart object to the collection MSG_CHART_COLLECTION_ERR_CHARTS_MAX, // Cannot open new chart. Number of open charts at maximum }; //+------------------------------------------------------------------+
また、新しく追加したインデックスに対応するメッセージテキストも追加します。
{"Главное окно графика","Main chart window"}, {"Подокно графика","Chart subwindow"}, {"Подокон","Subwindows"}, {"Индикаторы в главном окне графика","Indicators in the main chart window"}, {"Индикаторы в окне графика","Indicators in the chart window"}, {"Индикатор","Indicator"}, {"Индикаторов","Indicators total"}, {"Окно","Window"}, {"Отсутствуют","No indicators"}, {"Не удалось получить объект-окно графика","Failed to get the chart window object"}, {"Скриншот создан","Screenshot created"}, {"Шаблон графика сохранён","Chart template saved"}, {"Шаблон применён к графику","Template applied to the chart"}, //--- CChartObjCollection {"Коллекция чартов","Chart collection"}, {"Не удалось создать новый объект-чарт","Failed to create new chart object"}, {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"}, {"Нельзя открыть новый график, так как количество открытых графиков уже максимальное","You cannot open a new chart, since the number of open charts is already maximum"}, }; //+---------------------------------------------------------------------+
スクリーンショットの作成やテンプレートの処理など、チャートオブジェクトの追加機能を実装するため、スクリーンショットとテンプレート用のストレージフォルダ、スクリーンショットのデフォルトのファイル名拡張子(保存される画像ファイルの形式も示唆)を指定します。スクリーンショットの保存に使用できるファイルの種類は*.gif、*.png、*.bmpです。
これらの新しいマクロ置換を \MQL5\Include\DoEasy\Defines.mqhに追加します。
//--- Data parameters for file operations #define DIRECTORY ("DoEasy\\") // Library directory for storing object folders #define RESOURCE_DIR ("DoEasy\\Resource\\") // Library directory for storing resource folders #define SCREENSHOT_DIR ("DoEasy\\ScreenShots\\") // Library directory for storing screenshot folders #define TEMPLATE_DIR ("DoEasy\\") // Library directory for storing template folders #define FILE_EXT_GIF (".gif") // GIF image file name extension #define FILE_EXT_PNG (".png") // PNG image file name extension #define FILE_EXT_BMP (".bmp") // BMP image file name extension #define SCREENSHOT_FILE_EXT (FILE_EXT_PNG) // Chart screenshot file format (extension: .gif, .png and .bmp can be used) //--- Symbol parameters
スクリーンショットとテンプレートをターミナルに保存するためのフォルダは異なります。
スクリーンショット: (ターミナルデータフォルダー)\MQL5\Files\
テンプレート: (ターミナルデータフォルダー)\ MQL5\Profiles\Templates\
指定されたマクロ置換をファイル名に追加すると、ライブラリファイルの保存がよりターゲットに焦点を合わせたものになります。
スクリーンショットは\MQL5\Files\DoEasy\ScreenShots\に保存され、テンプレートは\MQL5\Profiles\Templates\DoEasy\に保存されます。
スクリーンショットファイルの保存をより便利にするために、 \MQL5\Include\DoEasy\Services\DELib.mqhファイルにサービス関数を実装します。この関数は、起動元のプログラムの名前、関数パラメータに渡されるプレフィックス、およびローカルPC時間で構成されるファイル名を返します。
//+------------------------------------------------------------------+ //| Return the file name (program name+local time) | //+------------------------------------------------------------------+ string FileNameWithTimeLocal(const string time_prefix=NULL) { string name= ( MQLInfoString(MQL_PROGRAM_NAME)+"_"+time_prefix+(time_prefix==NULL ? "" : "_")+ TimeToString(TimeLocal(),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ); ResetLastError(); if(StringReplace(name," ","_")==WRONG_VALUE) CMessage::ToLog(DFUN,GetLastError(),true); if(StringReplace(name,":",".")==WRONG_VALUE) CMessage::ToLog(DFUN,GetLastError(),true); return name; } //+------------------------------------------------------------------+
関数内の文字列は、プログラム名+関数パラメータで渡された値+日付/時間-分/秒の形式のローカルPC時刻で構成されます。次に、すべてのスペースがアンダースコア(_)に置き換えられ、すべてのコロンがピリオド(。)に置き換えられ、結果の文字列が返されます。置換が有効化されていない場合は、操作ログに適切なメッセージを表示します。
この関数は、1秒以内に同じファイル名を返します。つまり、関数が1秒間に数回呼び出されると、その1秒間には常に同じ文字列が返されます。したがって、ここでは、追加のファイルデータを渡して、その識別を一意でより有益なものにすることができる関数入力を実装します。
チャートウィンドウごとにピクセル単位で座標を指定できます。これらの座標は、ChartXYToTimePrice()を使用した時間/価格座標に対応します。また、ChartTimePriceToXY()を使用して逆変換を実行できます。この機能をオブジェクトに追加しましょう。最初の関数はチャートオブジェクトで動作し、2番目の関数はチャートウィンドウオブジェクトで動作します。ChartXYToTimePrice()関数は、カーソルを含むサブウィンドウインデックスを備えています。インデックスは、関数からのリンクによって返されます。これはどのチャートウィンドウでも機能します。カーソルを含むウィンドウインデックスは、関数を呼び出すときに関数パラメータに埋め込む変数に設定されます。逆に、ウィンドウの画面座標から価格/時間座標を取得する必要があるウィンドウのインデックスは、チャートウィンドウの時間と価格をピクセル単位の適切な画面座標に変換するために、手動で2番目の関数に渡す必要があります。関数(つまり、関数を操作するメソッド)をチャートウィンドウオブジェクトに配置して、関数から適切な座標を取得する方が合理的です。
\MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqhのクラスのprivateセクションで、ウィンドウにカーソル座標を格納するための変数を追加します。
//+------------------------------------------------------------------+ //| Chart window object class | //+------------------------------------------------------------------+ class CChartWnd : public CBaseObj { private: CArrayObj m_list_ind; // Indicator list int m_window_num; // Subwindow index int m_wnd_coord_x; // The X coordinate for the time on the chart in the window int m_wnd_coord_y; // The Y coordinates for the price on the chart in the window //--- Return the flag indicating the presence of an indicator from the list in the window bool IsPresentInWindow(const CWndInd *ind); //--- Remove indicators not present in the window from the list void IndicatorsDelete(void); //--- Add new indicators to the list void IndicatorsAdd(void); //--- Set a subwindow index void SetWindowNum(const int num) { this.m_window_num=num; } public:
クラスのpublicセクションで、チャート座標を時間/価格表現からX座標とY座標に変換するメソッドを宣言し、取得済みの座標を変数に返す2つのメソッドとウィンドウ内の相対Y座標を返すメソッドを書きます。
//--- Update data on attached indicators void Refresh(void); //--- Convert the coordinates of a chart from the time/price representation to the X and Y coordinates bool TimePriceToXY(const datetime time,const double price); //--- Return X and Y coordinates of the cursor location in the window int XFromTimePrice(void) const { return this.m_wnd_coord_x; } int YFromTimePrice(void) const { return this.m_wnd_coord_y; } //--- Return the relative Y coordinate of the cursor location in the window int YFromTimePriceRelative(void) const { return this.m_wnd_coord_y-this.YDistance();} }; //+------------------------------------------------------------------+
すべてのウィンドウのY座標の値は、座標の先頭(チャートのメインウィンドウの左上隅)から指定されるため、ウィンドウの上部の境界線を基準にした座標を取得するにはウィンドウの上端からY座標からの距離を減算する必要があります。これは最後のメソッドで行います。
パラメトリッククラスコンストラクタの初期化リストで、デフォルト値を使用して新しい変数を初期化します。
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CChartWnd::CChartWnd(const long chart_id,const int wnd_num) : m_window_num(wnd_num), m_wnd_coord_x(0), m_wnd_coord_y(0) { CBaseObj::SetChartID(chart_id); this.IndicatorsListCreate(); } //+------------------------------------------------------------------+
クラス本体の外で、チャートの座標を時間/価格表現からX軸とY軸の座標に変換するメソッドを実装します。
//+------------------------------------------------------------------+ //| 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.m_window_num,time,price,this.m_wnd_coord_x,this.m_wnd_coord_y)) { //CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } //+------------------------------------------------------------------+
ここでは、必要なすべての値を渡すことにより、ChartTimePriceToXY()関数操作の結果を返すだけです。カーソルがチャートウィンドウ内でチャートフィールドの外側にある場合、操作ログにこれらのメッセージが多すぎるため、ログでエラーメッセージをコメントアウトしました。
このメソッドは、取得した結果を新しく追加された変数に書き込んでそれらを格納しますが、XFromTimePrice()メソッドとYFromTimePrice()メソッドは変数値を返します。したがって、最初にTimePriceToXY()メソッドを呼び出す必要があります。trueを返した後、必要な座標の値を受け取ることができます。
ウィンドウに取り付けられた指標のデータを更新するメソッドを改善しましょう。指標リストが常に再作成されるのを避けるために、最初にウィンドウ内の指標の数をリスト内の指標の数と比較します。変化があった場合, 指標リストを常に再作成します。
//+------------------------------------------------------------------+ //| Update data on attached indicators | //+------------------------------------------------------------------+ void CChartWnd::Refresh(void) { int change=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num)-this.m_list_ind.Total(); if(change!=0) { this.IndicatorsDelete(); this.IndicatorsAdd(); } } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqhでチャートオブジェクトクラスを改善します。前回の記事では、WindowsTotal()メソッドを配置して、環境から値を取得し、1回の呼び出しでオブジェクトのプロパティに設定できるようにしました。しかし、これはコードロジックの構築の明確さと環境への参照の数の点であまり実用的ではないことが判明したので、私はこのアイデアを放棄することにしました。これで、メソッドは単にオブジェクトプロパティ値を返すようになります。
int WindowsTotal(void) const { return (int)this.GetProperty(CHART_PROP_WINDOWS_TOTAL); }
環境からの値は、本当に必要な場合にオブジェクトプロパティに設定されます。
チャートオブジェクトは、本稿でスケジュールされている残りの追加機能(チャートのナビゲート、チャートスクリーンショットの作成、チャートテンプレートの操作、チャートのX座標とY座標の時間と価格の値への変換)を受け取ります。
CSelectクラスファイルをCChartObjクラスファイルにインクルードします。
//+------------------------------------------------------------------+ //| ChartObj.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Objects\BaseObj.mqh" #include "..\..\Services\Select.mqh" #include "ChartWnd.mqh" //+------------------------------------------------------------------+
チャートオブジェクトのリストをプロパティで並べ替えるために使用されます。
クラスのprivateセクションで、チャートのX座標の時間とY座標の価格を格納するための2つの新しいクラスメンバー変数を追加します。
//+------------------------------------------------------------------+ //| Chart object class | //+------------------------------------------------------------------+ class CChartObj : public CBaseObj { private: CArrayObj m_list_wnd; // List of chart window objects long m_long_prop[CHART_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[CHART_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[CHART_PROP_STRING_TOTAL]; // String properties int m_digits; // Symbol's Digits() datetime m_wnd_time_x; // Time for X coordinate on the windowed chart double m_wnd_price_y; // Price for Y coordinate on the windowed chart
クラスのprivateセクションで、スクリーンショットファイルに拡張子がない場合は追加するメソッドを宣言します。
//--- Create the list of chart windows void CreateWindowsList(void); //--- Add an extension to the screenshot file if it is missing string FileNameWithExtention(const string filename); public:
クラス本体の最後で新しいメソッドを宣言します。
//--- Return the flag indicating that the chart object belongs to the program chart bool IsMainChart(void) const { return(this.m_chart_id==CBaseObj::GetMainChartID()); } //--- Return the chart window specified by index CChartWnd *GetWindowByIndex(const int index) const { return this.m_list_wnd.At(index); } //--- Return the window object by its subwindow index CChartWnd *GetWindowByNum(const int win_num) const; //--- Return the window object by the indicator name in it CChartWnd *GetWindowByIndicator(const string ind_name) const; //--- Display data of all indicators of all chart windows in the journal void PrintWndIndicators(void); //--- Display the properties of all chart windows in the journal void PrintWndParameters(void); //--- Shift the chart by the specified number of bars relative to the specified chart position bool Navigate(const int shift,const ENUM_CHART_POSITION position); //--- Shift the chart (1) to the left and (2) to the right by the specified number of bars bool NavigateLeft(const int shift); bool NavigateRight(const int shift); //--- Shift the chart (1) to the beginning and (2) to the end of the history data bool NavigateBegin(void); bool NavigateEnd(void); //--- Create the chart screenshot bool ScreenShot(const string filename,const int width,const int height,const ENUM_ALIGN_MODE align); //--- Create the screenshot of the (1) chart window, (2) 800х600 and (3) 750х562 pixels bool ScreenShotWndSize(const string filename=NULL,const ENUM_ALIGN_MODE align=ALIGN_CENTER); bool ScreenShot800x600(const string filename=NULL,const ENUM_ALIGN_MODE align=ALIGN_CENTER); bool ScreenShot750x562(const string filename=NULL,const ENUM_ALIGN_MODE align=ALIGN_CENTER); //--- Save the chart template with the current settings bool SaveTemplate(const string filename=NULL); //--- Apply the specified template to the chart bool ApplyTemplate(const string filename=NULL); //--- Convert X and Y chart window coordinates into time and price int XYToTimePrice(const long x,const double y); //--- Return (1) time and (2) price from XY coordinates datetime TimeFromXY(void) const { return this.m_wnd_time_x; } double PriceFromXY(void) const { return this.m_wnd_price_y; } }; //+------------------------------------------------------------------+
パラメータコンストラクタの初期化リストで、デフォルト値を使用して新しいクラスメンバー変数を初期化します。
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CChartObj::CChartObj(const long chart_id) : m_wnd_time_x(0),m_wnd_price_y(0) { }
新しいメソッドの実装について考えてみましょう。
以下は、ウィンドウオブジェクトを指標名で返すメソッドです。
//+------------------------------------------------------------------+ //| Return the window object by the indicator name in it | //+------------------------------------------------------------------+ CChartWnd *CChartObj::GetWindowByIndicator(const string ind_name) const { int index=(this.m_program==PROGRAM_INDICATOR && ind_name==NULL ? ::ChartWindowFind() : ::ChartWindowFind(this.m_chart_id,ind_name)); return this.GetWindowByIndex(index); } //+------------------------------------------------------------------+
ライブラリに基づいて起動されたプログラムが指標である場合は、パラメータを指定せずにChartWindowFind() 関数を呼び出して、起動されたウィンドウを指標に通知します。別の指標のウィンドウを見つける必要がある場合、ChartWindowFind()は、ウィンドウインデックスが指標名で見つかるチャートのIDを受け取る必要があります。
したがって、ここでは最初にプログラムのタイプを確認し、これが指標であり、NULLが名前として渡された場合は、パラメータなしでChartWindowFind()関数を呼び出します。これは、カスタムウィンドウを検索するための指標からのリクエストです。
それ以外の場合は、ChartWindowFind()を呼び出します。これは、チャートオブジェクトに属するチャートのIDと、ウィンドウインデックスが検索されるメソッドに渡される指標の短い名前を受け取ります。
その結果、指定された指標が配置されているウィンドウオブジェクトを返すために、検出されたウィンドウインデックスによってチャートオブジェクトに属するウィンドウオブジェクトを返すメソッドを指標とともに使用します。
以下は、指定されたチャート位置に対して指定されたバー数だけチャートをシフトするメソッドです。
//+------------------------------------------------------------------+ //| Move the chart by the specified number of bars | //| relative to the specified chart position | //+------------------------------------------------------------------+ bool CChartObj::Navigate(const int shift,const ENUM_CHART_POSITION position) { ::ResetLastError(); bool res=::ChartNavigate(m_chart_id,position,shift); if(!res) CMessage::ToLog(DFUN,::GetLastError(),true); return res; } //+------------------------------------------------------------------+
このメソッドは、メソッドに渡されたシフトパラメータでChartNavigate()関数を呼び出すだけです。パラメータはバーの数(shift)とシフトが相対して行われるチャートの位置(position)です。関数の実行が失敗した場合、メソッドでは操作ログにエラーメッセージを表示します。ChartNavigate()関数を呼び出した結果を返します。メソッドを呼び出す前に、チャートオブジェクトのチャートの右端への自動スクロールを無効にして、メソッドが正しく機能するようにします。
以下は、指定されたバー数だけチャートを左にシフトするメソッドです。
//+------------------------------------------------------------------+ //| Shift the chart to the left by the specified number of bars | //+------------------------------------------------------------------+ bool CChartObj::NavigateLeft(const int shift) { this.SetAutoscrollOFF(); return this.Navigate(shift,CHART_CURRENT_POS); } //+------------------------------------------------------------------+
ここでは、まずチャートオブジェクトのチャートの右端への自動スクロールを無効化してからNavigate()メソッドの操作結果を戻します。メソッドは、メソッドに渡されたチャートシフト値(バー単位)を受け取ります。
チャートを現在の位置からシフトします。
以下は、指定されたバー数だけチャートを右にシフトするメソッドです。
//+------------------------------------------------------------------+ //| Shift the chart to the right by the specified number of bars | //+------------------------------------------------------------------+ bool CChartObj::NavigateRight(const int shift) { this.SetAutoscrollOFF(); return this.Navigate(-shift,CHART_CURRENT_POS); } //+------------------------------------------------------------------+
ここでは、最初にチャートオブジェクトのチャートの右端への自動スクロールを無効化してからNavigate()メソッドの操作結果を戻します。メソッドは、負のチャートシフト値(バー単位)を受け取ります。
チャートを現在の位置からシフトします。
以下は、チャートを履歴データの最初にシフトするメソッドです。
//+------------------------------------------------------------------+ //| Shift the chart to the beginning of the history data | //+------------------------------------------------------------------+ bool CChartObj::NavigateBegin(void) { this.SetAutoscrollOFF(); return this.Navigate(0,CHART_BEGIN); } //+------------------------------------------------------------------+
ここでは、最初にチャートオブジェクトのチャートの右端への自動スクロールを無効化してからNavigate()メソッドの操作結果を戻します。メソッドは、ゼロチャートシフト値(バー単位)を受け取ります。
チャートを履歴データの最初にシフトします。
以下は、チャートを履歴データの最後(現在時刻)にシフトするメソッドです。
//+------------------------------------------------------------------+ //| Shift the chart to the end of the history data | //+------------------------------------------------------------------+ bool CChartObj::NavigateEnd(void) { this.SetAutoscrollOFF(); return this.Navigate(0,CHART_END); } //+------------------------------------------------------------------+
ここでは、最初にチャートオブジェクトのチャートの右端への自動スクロールを無効化してからNavigate()メソッドの操作結果を戻します。メソッドは、ゼロチャートシフト値(バー単位)を受け取ります。
チャートを履歴データの最後(現在時刻)にシフトします。
以下は、チャートのスクリーンショットを作成するメソッドです。
//+------------------------------------------------------------------+ //| Create the chart screenshot | //+------------------------------------------------------------------+ bool CChartObj::ScreenShot(const string filename,const int width,const int height,const ENUM_ALIGN_MODE align) { ::ResetLastError(); if(!::ChartScreenShot(m_chart_id,filename,width,height,align)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } //+------------------------------------------------------------------+
このメソッドは、取得した画像のスクリーンショットファイル名、幅と高さ、および配置(ENUM_ALIGN_MODE )を受け取ります。後者は、画像の高さが幅を超えたときに垂直スクリーンショットを作成するために必要です。この場合、配置は、画像が配置されるチャートのエッジを指定します。
ここでは、ChartScreenShot()関数を使用してメソッドに渡されたパラメータを含むスクリーンショットを撮るだけです。
スクリーンショットが正常に作成された場合は、trueを返します。
スクリーンショットの作成に失敗した場合は、操作ログに適切なメッセージを表示し、falseを返します。
指定されたサイズのスクリーンショットを作成する3つのメソッドは、追加のメソッドとして使用されます。
- チャートウィンドウに合うスクリーンショット
- スクリーンショット800х600
- スクリーンショット750х562
2番目と3番目のサイズは、理由から選択されています。これらは、MQL5.comフォーラム、記事、マーケットの製品の説明で画像を公開するために必要になることがよくあります。チャートにフィットするスクリーンショットを使用すると、ターミナルでウィンドウの外観とサイズを直接設定し、必要なサイズのスクリーンショットを撮ることができます。
以下は、チャートウィンドウの解像度に合ったチャートスクリーンショットを作成するメソッドです(価格と時間スケールが存在する場合はそれを含む)。
//+------------------------------------------------------------------+ //| Create the chart screenshot fitting the chart window resolution | //+------------------------------------------------------------------+ bool CChartObj::ScreenShotWndSize(const string filename=NULL,const ENUM_ALIGN_MODE align=ALIGN_CENTER) { //--- Create the file name or use the one passed to the method string name= (filename==NULL || filename=="" ? SCREENSHOT_DIR+FileNameWithTimeLocal(this.Symbol()+"_"+TimeframeDescription(this.Timeframe()))+SCREENSHOT_FILE_EXT : this.FileNameWithExtention(filename) ); //--- Get the chart window having the largest number of all windows CChartWnd *wnd=this.GetWindowByNum(this.m_list_wnd.Total()-1); if(wnd==NULL) { ::Print(DFUN,CMessage::Text(MSG_CHART_OBJ_ERR_FAILED_GET_WIN_OBJ),string(this.m_list_wnd.Total()-1)); return false; } //--- Calculate the screenshot width and height considering the size of the price and time scales int width=this.WidthInPixels()+(IsShowPriceScale() ? 56 : 0); int height=wnd.YDistance()+wnd.HeightInPixels()+(this.IsShowDateScale() ? 15 : 0); //--- Create a screenshot and return the result of the ScreenShot() method bool res=this.ScreenShot(name,width,height,align); if(res) ::Print(DFUN,CMessage::Text(MSG_CHART_OBJ_SCREENSHOT_CREATED),": ",name," (",(string)width," x ",(string)height,")"); return res; } //+------------------------------------------------------------------+
メソッドのコードには、ロジックを詳細に説明するコメントが含まれています。つまり、名前の作成時に NULLがメソッドに渡された場合は、デフォルトでDefines.mqhで指定されているライブラリのスクリーンショットファイルへのパス、プログラム名、拡張子で構成されるファイル名を作成します。メソッドに渡されたファイル名が空でない場合は、FileNameWithExtention()メソッド(後述)を使用して、ファイル名に拡張子が含まれているかどうかを確認します(スクリーンショットには、.gif、.png、.bmgの3つの拡張子のいずれかが含まれる場合があります)。ファイル名が存在しない場合は、ファイル名に拡張子を追加します。
チャートに属するすべてのウィンドウを考慮してスクリーンショットのサイズを計算するには、インデックスが最大のウィンドウを見つける必要があります(0 —メインチャートウィンドウ、1、2、3、N — すべてのウィンドウが上から下に開きます)。つまり、一番下のウィンドウの数が最大になります。メインチャートウィンドウの上端からチャート上で開いている最下部のウィンドウの上端までの距離がわかっているので、このウィンドウの高さを追加する必要がある参照ポイントを取得します。したがって、チャート全体の高さ全体を取得します。チャート上のタイムスケールの存在を確認する必要があります。スケールが存在する場合は、計算された高さに15ピクセルを追加します(サイズは試行的に選択されています)。スケールがない場合は、何も追加しません。これが、将来のスクリーンショットの高さを見つける方法です。
スクリーンショットの幅を使用すると少し簡単になります。チャートに価格スケールがある場合は、チャートオブジェクトの幅を取得し、それに56ピクセルを追加します。スケールがない場合は、何も追加しません。価格スケールのサイズも試験的に設定されました。
解像度が異なる画面では、価格と時間スケールのサイズが異なる場合があります。さまざまなモニターとその解像度を試す機会がありませんでした。とにかく、スクリーンショットのサイズに追加されたスケールのサイズは、画像の外観に大きなエラーを引き起こしません。
以下は、800x600ピクセルのチャートスクリーンショットを作成するメソッドです。
//+------------------------------------------------------------------+ //| Create the chart screenshot of 800x600 pixels | //+------------------------------------------------------------------+ bool CChartObj::ScreenShot800x600(const string filename=NULL,const ENUM_ALIGN_MODE align=ALIGN_CENTER) { string name= (filename==NULL || filename=="" ? SCREENSHOT_DIR+FileNameWithTimeLocal(this.Symbol()+"_"+TimeframeDescription(this.Timeframe()))+SCREENSHOT_FILE_EXT : this.FileNameWithExtention(filename) ); int width=800; int height=600; bool res=this.ScreenShot(name,width,height,align); if(res) ::Print(DFUN,CMessage::Text(MSG_CHART_OBJ_SCREENSHOT_CREATED),": ",name," (",(string)width," x ",(string)height,")"); return res; } //+------------------------------------------------------------------+
ここでは、上記の方法よりもすべてが簡単です。ファイル名は前の方法と同じ方法で作成され、ファイルサイズはここにハードコードされています。サイズはScreenShot()メソッドに渡され、その結果が返されます。
以下は、750x562ピクセルのチャートスクリーンショットを作成するメソッドです。
//+------------------------------------------------------------------+ //| Create the chart screenshot of 750x562 pixels | //+------------------------------------------------------------------+ bool CChartObj::ScreenShot750x562(const string filename=NULL,const ENUM_ALIGN_MODE align=ALIGN_CENTER) { string name= (filename==NULL || filename=="" ? SCREENSHOT_DIR+FileNameWithTimeLocal(this.Symbol()+"_"+TimeframeDescription(this.Timeframe()))+SCREENSHOT_FILE_EXT : this.FileNameWithExtention(filename) ); int width=750; int height=562; bool res=this.ScreenShot(name,width,height,align); if(res) ::Print(DFUN,CMessage::Text(MSG_CHART_OBJ_SCREENSHOT_CREATED),": ",name," (",(string)width," x ",(string)height,")"); return res; } //+------------------------------------------------------------------+
このメソッドは、画像サイズを除いて、800×600ピクセルのスクリーンショットを作成するメソッドと似ています。
チャートとそのすべての設定、指標、EAは、後で他のチャートに適用するためのテンプレートとして保存できます。チャートテンプレートを保存するメソッドと、チャートオブジェクトによって記述されたチャートに指定されたテンプレートを適用するメソッドの2つがあります。
以下は、現在の設定でチャートテンプレートを保存するメソッドです。
//+------------------------------------------------------------------+ //| Save the chart template with the current settings | //+------------------------------------------------------------------+ bool CChartObj::SaveTemplate(const string filename=NULL) { ::ResetLastError(); string name= (filename==NULL || filename=="" ? TEMPLATE_DIR+::MQLInfoString(MQL_PROGRAM_NAME) : filename ); if(!::ChartSaveTemplate(this.m_chart_id,name)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } ::Print(DFUN,CMessage::Text(MSG_CHART_OBJ_TEMPLATE_SAVED),": ",this.Symbol()," ",TimeframeDescription(this.Timeframe())); return true; } //+------------------------------------------------------------------+
このメソッドは、テンプレートを保存するときに使用するファイル名を渡します。名前が空の場合(デフォルト)、パス(Defines.mqhで事前に決定されています)とプログラム名で構成されます。
テンプレートファイルが正常に保存されると、テンプレートが保存されたチャート(シンボルと時間枠)を指定する適切なエントリが操作ログに作成され、 true が返されます。テンプレートの保存に失敗した場合、適切なメッセージが操作ログにも送信され、メソッドはfalseを返します。
以下は、指定されたテンプレートをチャートに適用するメソッドです。
//+------------------------------------------------------------------+ //| Apply the specified template to the chart | //+------------------------------------------------------------------+ bool CChartObj::ApplyTemplate(const string filename=NULL) { ::ResetLastError(); string name= (filename==NULL || filename=="" ? TEMPLATE_DIR+::MQLInfoString(MQL_PROGRAM_NAME) : filename ); if(!::ChartApplyTemplate(this.m_chart_id,name)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } ::Print(DFUN,CMessage::Text(MSG_CHART_OBJ_TEMPLATE_APPLIED),": ",this.Symbol()," ",TimeframeDescription(this.Timeframe())); return true; } //+------------------------------------------------------------------+
このメソッドは、チャートテンプレートを保存するメソッドと似ています。メソッドに渡されるか、自動的に作成されるテンプレートファイル名も使用されます。次に、テンプレートがチャートに適用され、操作結果を通知するエントリが操作ログに表示されます。
別のEAを特徴とするテンプレートを、メソッドを呼び出した実行中のEAを使用して現在のチャートに適用すると、現在のEAがメモリからアンロードされ、機能しなくなることに注意してください。テンプレートからの新しいEAに置き換えられます。このメソッドでは、このような衝突の可能性は確認されません。したがって、テンプレートを監視し、現在のEAをチャートに適用されたテンプレートから起動される可能性のあるものに置き換える可能性を確認する必要があります。
以下は、ウィンドウチャートのX座標とY座標を時間と価格の値に変換するメソッドです。
//+------------------------------------------------------------------------------+ //|Convert X and Y coordinates of the chart window into the time and price values| //+------------------------------------------------------------------------------+ int CChartObj::XYToTimePrice(const long x,const double y) { int sub_window=WRONG_VALUE; ::ResetLastError(); if(!::ChartXYToTimePrice(this.m_chart_id,(int)x,(int)y,sub_window,this.m_wnd_time_x,this.m_wnd_price_y)) { //CMessage::ToLog(DFUN,::GetLastError(),true); return WRONG_VALUE; } return sub_window; } //+------------------------------------------------------------------+
ChartXYToTimePrice()関数は、チャートのX座標とY座標を時間と価格の値に変換します。同時に、リンクから渡されたsub_window変数は、サブウィンドウのインデックスを受け取ります。このインデックスには、チャートの XおよびY座標は、返される時間と価格を調整します。
これに基づいて、メソッドはチャートサブウィンドウインデックスを返します。0 — 座標がメインチャートウィンドウにある場合、1、2、3など — 座標が対応するチャートサブウィンドウに該当する場合、-1 — 計算に失敗した場合 座標。メソッドの呼び出し後に取得された値が-1でない場合、TimeFromXY()メソッドとPriceFromXY()メソッドを使用して時間と価格を受け取ることができます。これらのメソッドは、ChartXYToTimePrice()関数によって取得された時間と価格を含む変数を返すだけです。
以下は、スクリーンショットファイルの拡張子が欠落している場合に追加するメソッドです。
//+------------------------------------------------------------------+ //| Add an extension to the screenshot file if it is missing | //+------------------------------------------------------------------+ string CChartObj::FileNameWithExtention(const string filename) { if(::StringFind(filename,FILE_EXT_GIF)>WRONG_VALUE || ::StringFind(filename,FILE_EXT_PNG)>WRONG_VALUE || ::StringFind(filename,FILE_EXT_BMP)>WRONG_VALUE) return filename; return filename+SCREENSHOT_FILE_EXT; } //+------------------------------------------------------------------+
このメソッドは検証済みの文字列を受け取ります。この文字列で、スクリーンショットのファイル拡張子を見つける必要があります。スクリーンショットファイル形式は厳密に定義されており、GIF、PNG、BMPの3つのタイプしか含まれていないため、メソッドに渡される文字列に、そのような拡張子を持つ少なくとも1つのサブ文字列が含まれている場合(つまり、拡張子がすでに設定されている場合)、メソッドは、渡された文字列を変更せずに返します。それ以外の場合、文字列はDefines.mqhでデフォルトで設定されているファイル名拡張子を受け取ります。これは.pngファイルです。変更された文字列が結果として返されます。
チャートに新しいウィンドウを追加するときに検出されたいくつかの問題について:
詳細なテスト中に、ウィンドウに新しい指標をチャートに追加すると、そのウィンドウが表示されることが検出されました(ただし、[ОК]または[キャンセル]はまだクリックしていません)。このウィンドウは、ターミナルでは既存のウィンドウとしてすぐに表示されます。ライブラリはそれを認識してチャートオブジェクトウィンドウリストに追加しますが、指標はウィンドウに表示されなくなります。ただし、新しいウィンドウ指標を追加するウィンドウで[キャンセル]をクリックすると、そのウィンドウはクライアントターミナルチャートウィンドウのリストに表示されなくなります。ライブラリは、次の確認中にリストからウィンドウを削除します。
このような不要なアクションや、クライアントターミナルに存在しない空のチャートウィンドウへの誤ったアクセスを回避するには、ウィンドウをリストに追加する前に、ウィンドウに指標があることを確認する必要があります。指標が表示されている場合は、ウィンドウを追加します。そうでない場合、ウィンドウは開いていますが、チャートに追加されていません。スキップする必要があります。
したがって、チャートウィンドウリスト作成メソッドが改善されます。
//+------------------------------------------------------------------+ //| Create the list of chart windows | //+------------------------------------------------------------------+ void CChartObj::CreateWindowsList(void) { //--- Clear the chart window list this.m_list_wnd.Clear(); //--- Get the total number of chart windows from the environment int total=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL); //--- In the loop by the total number of windows for(int i=0;i<total;i++) { //--- Create a new chart window object CChartWnd *wnd=new CChartWnd(this.m_chart_id,i); if(wnd==NULL) continue; //--- If the window index exceeds 0 (not the main chart window) and it still has no indicator, //--- remove the newly created chart window object and go to the next loop iteration if(wnd.WindowNum()!=0 && wnd.IndicatorsTotal()==0) { delete wnd; continue; } //--- If the object was not added to the list, remove that object this.m_list_wnd.Sort(); if(!this.m_list_wnd.Add(wnd)) delete wnd; } //--- If the number of objects in the list corresponds to the number of windows on the chart, //--- write that value to the chart object property //--- If the number of objects in the list does not correspond to the number of windows on the chart, //--- write the number of objects in the list to the chart object property. int value=int(this.m_list_wnd.Total()==total ? total : this.m_list_wnd.Total()); this.SetProperty(CHART_PROP_WINDOWS_TOTAL,value); } //+------------------------------------------------------------------+
メソッドロジックは、そのリストで詳細に説明されています。まだ作成されていないウィンドウをリストに追加する必要があるかどうかの確認は、強調表示されたコードブロックにあります。つまり、ウィンドウの指標のみを追加する場合、まだ作成されていないウィンドウがある可能性があります。したがって、インデックス0のウィンドウは分析しません。これはメインチャートウィンドウであり、間違いなく存在します。追加されることがあるのは新しい指標のみです。指標のない新しいウィンドウが検出された場合(指標ウィンドウがすでにチャートに追加されている場合は不可能)、これはまだ[OK]をクリックしてチャートに追加していないウィンドウです。そのようなウィンドウをスキップして、ウィンドウリストに追加されないようにします。
ループ全体が完了したら、ウィンドウの数をチャートオブジェクトプロパティに書き込みます。ここで、すべてのウィンドウがリストに正常に追加されたことを確認します。実際の数がリスト内の数と等しい場合、これはすべてのウィンドウが追加されたことを意味します。チャート上のウィンドウの数をオブジェクトプロパティに書き込みます。数値が等しくない場合、リストに存在する数値がプロパティに設定されます(実際の数値と等しくない)ので、次のチェックで再び不等式が示され、次のことが可能になります。 作成したウィンドウをリストに追加します。新しいウィンドウを追加するときに[キャンセル]をクリックすると、チャートとリストのウィンドウの数が等しくなり、変更を処理する必要がなくなります。
これで、ライブラリクラスの改善は終わりです。
チャートオブジェクトとウィンドウのコレクションクラスの自動更新
ここで、開いているチャートの数、チャートのウィンドウの数、およびこれらのウィンドウの指標の数を変更すると、ライブラリが常に関連データを保持しながらこのすべてのデータを自動的に更新することを確認しましょう。
チャートオブジェクトクラスの\MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqhで、Refresh()メソッドを補足して、開いている数の変化だけでなくチェックできるようにします。 チャート上のウィンドウ(チャートオブジェクト内)だけでなく、すでに開いているウィンドウの指標の数も管理します(1つのウィンドウが複数の指標に適合します)。
以下は、チャートオブジェクトとそのウィンドウのリストを更新するメソッドです。
//+------------------------------------------------------------------+ //| Update the chart object and its window list | //+------------------------------------------------------------------+ void CChartObj::Refresh(void) { for(int i=0;i<m_list_wnd.Total();i++) { CChartWnd *wnd=m_list_wnd.At(i); if(wnd==NULL) continue; wnd.Refresh(); } int change=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL)-this.WindowsTotal(); if(change==0) return; this.CreateWindowsList(); } //+------------------------------------------------------------------+
チャートオブジェクトで開いているウィンドウの数の変化を確認する前に、まずすべてのオブジェクトウィンドウのリストに沿って移動し、ループ内の後続の各ウィンドウのupdateメソッドを呼び出す必要があります。チャートウィンドウオブジェクトのRefresh()メソッドは、そこに配置された指標の数の変更をチェックし、変更を登録するときにそれらのリストを再作成します。
チャートオブジェクト\MQL5\Include\DoEasy\Collections\ChartObjCollection.mqhのコレクションクラスファイルで、コレクションリスト内のチャートオブジェクトを更新できず、それに応じてウィンドウそしてそれらの指標を更新できない論理エラーを修正します。
以前は、チャートオブジェクトを更新するブロックは開いているチャートの数の変更をチェックするコードの下にあり、開いているチャートの数に変更がない場合に到達できませんでした。
//+------------------------------------------------------------------+ //| Update the collection list of chart objects | //+------------------------------------------------------------------+ void CChartObjCollection::Refresh(void) { //--- Get the number of open charts in the terminal and int charts_total=this.ChartsTotal(); //--- calculate the difference between the number of open charts in the terminal //--- and chart objects in the collection list. These values are displayed in the chart comment int change=charts_total-this.m_list.Total(); Comment(DFUN,", list total=",DataTotal(),", charts total=",charts_total,", change=",change); //--- If there are no changes, leave if(change==0) return; //--- If a chart is added in the terminal if(change>0) { //--- Find the missing chart object, create and add it to the collection list this.FindAndCreateMissingChartObj(); //--- Get the current chart and return to it since //--- adding a new chart switches the focus to it CChartObj *chart=this.GetChart(GetMainChartID()); if(chart!=NULL) chart.SetBringToTopON(true); } //--- If a chart is removed in the terminal else if(change<0) { //--- Find an extra chart object in the collection list and remove it from the list this.FindAndDeleteExcessChartObj(); } //--- In the loop by the number of chart objects in the list, for(int i=0;i<this.m_list.Total();i++) { //--- get the next chart object and CChartObj *chart=this.m_list.At(i); if(chart==NULL) continue; //--- update it chart.Refresh(); } } //+------------------------------------------------------------------+
解決策は非常に簡単です。クライアントターミナルで開いているチャートの数を確認する前に、チャートオブジェクトの更新コードブロックを上に移動します。
//+------------------------------------------------------------------+ //| Update the collection list of chart objects | //+------------------------------------------------------------------+ void CChartObjCollection::Refresh(void) { //--- In the loop by the number of chart objects in the list, for(int i=0;i<this.m_list.Total();i++) { //--- get the next chart object and CChartObj *chart=this.m_list.At(i); if(chart==NULL) continue; //--- update it chart.Refresh(); } //--- Get the number of open charts in the terminal and int charts_total=this.ChartsTotal(); //--- calculate the difference between the number of open charts in the terminal //--- and chart objects in the collection list. These values are displayed in the chart comment int change=charts_total-this.m_list.Total(); //--- If there are no changes, leave if(change==0) return; //--- If a chart is added in the terminal if(change>0) { //--- Find the missing chart object, create and add it to the collection list this.FindAndCreateMissingChartObj(); //--- Get the current chart and return to it since //--- adding a new chart switches the focus to it CChartObj *chart=this.GetChart(GetMainChartID()); if(chart!=NULL) chart.SetBringToTopON(true); } //--- If a chart is removed in the terminal else if(change<0) { //--- Find an extra chart object in the collection list and remove it from the list this.FindAndDeleteExcessChartObj(); } } //+------------------------------------------------------------------+
ここで、開いているチャートの数の変化を確認する前に(そして、数が変更されていない場合はメソッドを終了する)、まずコレクションリスト内のすべてのチャートオブジェクトをループし、Refresh()メソッドでウィンドウオブジェクトの変更を確認します。 独自のRefresh()メソッドが呼び出され、ウィンドウ内の指標の数を確認します。したがって、最初にウィンドウ内の指標の数とチャート内の指標の数のすべての可能な変更の完全な確認を実行し、その後、開いているチャートの数の変更を確認します。
新しいチャートを開き、既存のチャートを閉じるためのメソッドをチャートオブジェクトコレクションクラスに追加します。
クラスのpublicセクションで、2つの新しいメソッドを宣言します。
//--- Update (1) the chart object collection list and (2) the specified chart object void Refresh(void); void Refresh(const long chart_id); //--- (1) Open a new chart with the specified symbol and period, (2) close the specified chart bool Open(const string symbol,const ENUM_TIMEFRAMES timeframe); bool Close(const long chart_id); }; //+------------------------------------------------------------------+
クラス本体の外側で実装しましょう。
以下は、指定された銘柄と期間で新しいチャートを開くメソッドです。
//+------------------------------------------------------------------+ //| Open a new chart with the specified symbol and period | //+------------------------------------------------------------------+ bool CChartObjCollection::Open(const string symbol,const ENUM_TIMEFRAMES timeframe) { if(this.m_list.Total()==CHARTS_MAX) { ::Print(CMessage::Text(MSG_CHART_COLLECTION_ERR_CHARTS_MAX)," (",(string)CHARTS_MAX,")"); return false; } ::ResetLastError(); long chart_id=::ChartOpen(symbol,timeframe); if(chart_id==0) CMessage::ToLog(::GetLastError(),true); return(chart_id>0); } //+------------------------------------------------------------------+
ここで、コレクション内のチャートオブジェクトの数がしきい値1(CHARTS_MAX)を超えると、新しいチャートを開こうとしても意味がありません。そのことを通知し、falseを返します。次に、それでも新しいチャートを開くことができる場合は、開いたチャートの指定されたパラメータを使用してChartOpen()関数を呼び出します。エラーが発生した場合は、操作ログにエントリを送信します。新しいチャートを開く関数がゼロ以外の値を返したことを示すフラグを返します。
以下は、指定されたチャートを閉じるメソッドです。
//+------------------------------------------------------------------+ //| Close a specified chart | //+------------------------------------------------------------------+ bool CChartObjCollection::Close(const long chart_id) { ::ResetLastError(); bool res=::ChartClose(chart_id); if(!res) CMessage::ToLog(DFUN,::GetLastError(),true); return res; } //+------------------------------------------------------------------+
ここで、 IDで指定されたチャートを閉じる試みが失敗した場合は、適切な操作ログエントリを表示します 。
メソッドはChartClose()関数の結果を返します。
CEngineライブラリのメインオブジェクトの\MQL5\Include\DoEasy\Engine.mqhに、チャートコレクションを管理するためのメソッドを追加します。
銘柄と時間枠でチャートオブジェクトリストを返す 2つのメソッド
//--- Return the list of chart objects by (1) symbol and (2) timeframe CArrayObj *ChartGetChartsList(const string symbol) { return this.m_charts.GetChartsList(symbol); } CArrayObj *ChartGetChartsList(const ENUM_TIMEFRAMES timeframe) { return this.m_charts.GetChartsList(timeframe); }
は、他のクラスの同様のメソッドと一致するように名前が変更され、コードで少し上に移動しました。
//--- Current the chart collection bool ChartCreateCollection(void) { return this.m_charts.CreateCollection(); } //--- Return (1) the chart collection and (2) the list of charts from the chart collection CChartObjCollection *GetChartObjCollection(void) { return &this.m_charts; } CArrayObj *GetListCharts(void) { return this.m_charts.GetList(); } //--- Return the list of chart objects by (1) symbol and (2) timeframe CArrayObj *GetListCharts(const string symbol) { return this.m_charts.GetChartsList(symbol); } CArrayObj *GetListCharts(const ENUM_TIMEFRAMES timeframe) { return this.m_charts.GetChartsList(timeframe); }
最後に開いたチャートのチャートオブジェクトを返すメソッド、コレクションリスト内のチャートオブジェクトの数を返すメソッド、および指定されたチャートを開くメソッドと閉じるメソッドを2つ追加します。
//--- Current the chart collection bool ChartCreateCollection(void) { return this.m_charts.CreateCollection(); } //--- Return (1) the chart collection and (2) the list of charts from the chart collection CChartObjCollection *GetChartObjCollection(void) { return &this.m_charts; } CArrayObj *GetListCharts(void) { return this.m_charts.GetList(); } //--- Return the list of chart objects by (1) symbol and (2) timeframe CArrayObj *GetListCharts(const string symbol) { return this.m_charts.GetChartsList(symbol); } CArrayObj *GetListCharts(const ENUM_TIMEFRAMES timeframe) { return this.m_charts.GetChartsList(timeframe); } //--- Return (1) the specified chart object, (2) the chart object with the program and (3) the chart object of the last open chart CChartObj *ChartGetChartObj(const long chart_id) { return this.m_charts.GetChart(chart_id); } CChartObj *ChartGetMainChart(void) { return this.m_charts.GetChart(this.m_charts.GetMainChartID());} CChartObj *ChartGetLastOpenedChart(void) { return this.m_charts.GetChart(this.GetListCharts().Total()-1);} //--- Return the number of charts in the collection list int ChartsTotal(void) { return this.m_charts.DataTotal(); } //--- Update (1) the chart specified by ID and (2) all charts void ChartRefresh(const long chart_id) { this.m_charts.Refresh(chart_id); } void ChartsRefreshAll(void) { this.m_charts.Refresh(); } //--- (1) Open and (2) close the specified chart bool ChartOpen(const string symbol,const ENUM_TIMEFRAMES timeframe) { return this.m_charts.Open(symbol,timeframe); } bool ChartClose(const long chart_id) { return this.m_charts.Close(chart_id); } //--- Return (1) the buffer collection and (2) the buffer list from the collection
ChartGetLastOpenedChart()メソッドは、チャートオブジェクトコレクションリストにある最後のオブジェクトへのポインタを返すだけですが、 ChartsTotal()メソッドは、チャートオブジェクトコレクションリストのサイズを返します。
ChartOpen()メソッドとChartClose()メソッドはそれに応じてチャートコレクションクラスのOpen()メソッドとClose() メソッドの結果を返します。
本稿で計画した変更と改善はこれですべてです。
検証
テストを実行するには、前の記事のEAを使用して、\MQL5\Experts\TestDoEasy\Part70\にTestDoEasyPart70.mq5として保存します。
EAパネルに次のアイコンが付いた新しいボタンを追加します。
- <と> — それぞれチャートを左右に1バーずつシフトするためのボタン
- <<と>> — それぞれチャートを左右に10バーずつシフトするためのボタン
- |<と>| — それぞれ履歴の最初と最後にチャートを設定するためのボタン
- NとX — 新しい銘柄チャートを開くボタンと最後に開いた銘柄チャートを閉じるボタン
- [O] — EAを使用して現在のチャートのスクリーンショットを作成するためのボタン
新しい機能をテストするロジックは次のとおりです。
- チャートシフトボタンをクリックすると、EAはチャートを左右にそれぞれ1バーと10バーシフトします。
- 履歴の最初と最後にチャートを設定するためのボタンをクリックすると、それに応じてチャートが設定されます。
- チャートを開くボタンをクリックすると、EAは、気配値表示ウィンドウではなく、チャートオブジェクトコレクションリストに設定された銘柄のチャートを、表示される順序で順番に開きます( 気配値表示ウィンドウ)。
チャートテンプレートを保存して適用するために、EAは最後に開いたチャートのテンプレートを保存し、新しいチャートが開かれたときにそれを適用します。つまり、GBPUSDなどの新しいチャートを手動で開き、新しいチャートを開くためのボタンをクリックする前に現在のチャートとは異なる外観に設定すると、後続のすべてのチャートの外観は同じになります。より正確には、事前定義されたチャートの外観を持つテンプレートが、開いた後にそれらに適用されます。
つまり、テストを実行するには、最初に新しいGBPUSDチャートを開き、その外観を構成する必要があります。手動で設定されたGBPUSDチャートに基づいて保存されたテンプレートは、EAによって開かれた他のすべてのチャートに適用されます。 - チャートを閉じるボタンをクリックすると、EAは開いているチャートの最後を閉じます。
- スクリーンショット作成ボタンを順番にクリックすることで、EAはチャートのスクリーンショットを
800х600->750x562->現在のチャートのサイズ->800х600..を繰り返した順番で作成します。
EAの OnInit()ハンドラで、現在のチャートのマウスイベントを追跡する権限を含めるようにします。
//--- Check playing a standard sound by macro substitution and a custom sound by description engine.PlaySoundByDescription(SND_OK); //--- Wait for 600 milliseconds engine.Pause(600); engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","Falling coin 2")); //--- Check the calculation of the cursor coordinates in the chart windows. //--- Allow the current chart to track mouse movement events engine.ChartGetMainChart().SetEventMouseMoveON(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
これにより、プログラムはマウスボタンの移動および押下イベントに関するメッセージを受信できるようになります(CHARTEVENT_MOUSE_MOVE)。
マウス移動イベントを受信したら、 OnChartEvent()ハンドラでチャート上のカーソル座標をピクセル単位で取得し、カーソルが配置されているウィンドウの時間、価格、およびインデックスに変換する必要があります。 作成されたライブラリメソッドを作成し、最後に、新しく取得した時間と価格をチャートのカーソル座標に戻し、これらすべての値をチャートのコメントに表示します。
カーソル移動イベントの処理を記述します。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If working in the tester, exit if(MQLInfoInteger(MQL_TESTER)) return; //--- Handling mouse events if(id==CHARTEVENT_OBJECT_CLICK) { //--- Handle pressing the buttons in the panel if(StringFind(sparam,"BUTT_")>0) PressButtonEvents(sparam); } //--- Handling DoEasy library events if(id>CHARTEVENT_CUSTOM-1) { OnDoEasyEvent(id,lparam,dparam,sparam); } //--- Check ChartXYToTimePrice() if(id==CHARTEVENT_MOUSE_MOVE) { //--- Get the chart object of the current (main) program chart CChartObj *chart=engine.ChartGetMainChart(); if(chart==NULL) return; //--- Get the index of a subwindow the cursor is located at int wnd_num=chart.XYToTimePrice(lparam,dparam); if(wnd_num==WRONG_VALUE) return; //--- Get the calculated cursor location time and price datetime time=chart.TimeFromXY(); double price=chart.PriceFromXY(); //--- Get the window object of the chart the cursor is located in by the subwindow index CChartWnd *wnd=chart.GetWindowByNum(wnd_num); if(wnd==NULL) return; //--- If X and Y coordinates are calculated by time and price (make a reverse conversion), if(wnd.TimePriceToXY(time,price)) { //--- in the comment, show the time, price and index of the window that are calculated by X and Y cursor coordinates, //--- as well as the cursor X and Y coordinates converted back from the time and price Comment ( DFUN,"time: ",TimeToString(time),", price: ",DoubleToString(price,Digits()), ", win num: ",(string)wnd_num,": x: ",(string)wnd.XFromTimePrice(), ", y: ",(string)wnd.YFromTimePrice()," (",(string)wnd.YFromTimePriceRelative(),")") ; } } } //+------------------------------------------------------------------+
マウス移動イベントを処理するロジックは、EAのOnChartEvent()コードで詳細に説明されています。
EAの CreateButtons()関数で、新しいパネルボタンを作成するためのコードを追加します。
//+------------------------------------------------------------------+ //| Create the buttons panel | //+------------------------------------------------------------------+ bool CreateButtons(const int shift_x=20,const int shift_y=0) { int h=18,w=82,offset=2,wpt=14; int cx=offset+shift_x+wpt*2+2,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+3*h+1; int x=cx,y=cy; int shift=0; for(int i=0;i<TOTAL_BUTT;i++) { x=x+(i==7 ? w+2 : 0); if(i==TOTAL_BUTT-6) x=cx; y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-6 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text); return false; } } h=18; offset=2; cx=offset+shift_x; cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+3*h+1; x=cx; y=cy; shift=0; for(int i=0;i<18;i++) { y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name+"_PRICE",((i>6 && i<14) || i>17 ? x+wpt*2+w*2+5 : x),y,wpt,h,"P",(i<4 ? clrGreen : i>6 && i<11 ? clrChocolate : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text+" \"P\""); return false; } if(!ButtonCreate(butt_data[i].name+"_TIME",((i>6 && i<14) || i>17 ? x+wpt*2+w*2+5+wpt+1 : x+wpt+1),y,wpt,h,"T",(i<4 ? clrGreen : i>6 && i<11 ? clrChocolate : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text+" \"T\""); return false; } } //--- Left and Right buttons int xbn=x+wpt*2+w*2+5; int ybn=y+h*3+3; if(!ButtonCreate(prefix+"BUTT_NAVIGATE_LEFT1",xbn,ybn,wpt,h,"<",clrGreen)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_NAVIGATE_LEFT1"); return false; } if(!ButtonCreate(prefix+"BUTT_NAVIGATE_RIGHT1",xbn+wpt+1,ybn,wpt,h,">",clrGreen)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_NAVIGATE_RIGHT1"); return false; } //--- Left 10 and Right 10 buttons ybn=y+h*2+2; if(!ButtonCreate(prefix+"BUTT_NAVIGATE_LEFT10",xbn,ybn,wpt,h,"<<",clrGreen)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_NAVIGATE_LEFT10"); return false; } if(!ButtonCreate(prefix+"BUTT_NAVIGATE_RIGHT10",xbn+wpt+1,ybn,wpt,h,">>",clrGreen)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_NAVIGATE_RIGHT10"); return false; } //--- Home and End buttons ybn=y+h+1; if(!ButtonCreate(prefix+"BUTT_NAVIGATE_HOME",xbn,ybn,wpt,h,"|<",clrGreen)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_NAVIGATE_HOME"); return false; } if(!ButtonCreate(prefix+"BUTT_NAVIGATE_END",xbn+wpt+1,ybn,wpt,h,">|",clrGreen)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_NAVIGATE_END"); return false; } //--- Open and Close buttons ybn=y; if(!ButtonCreate(prefix+"BUTT_CHART_OPEN",xbn,ybn,wpt,h,"N",clrBlue)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_CHART_OPEN"); return false; } if(!ButtonCreate(prefix+"BUTT_CHART_CLOSE",xbn+wpt+1,ybn,wpt,h,"X",clrRed)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_CHART_CLOSE"); return false; } //--- ScreenShot button ybn=y-h-1; if(!ButtonCreate(prefix+"BUTT_CHART_SCREENSHOT",xbn,ybn,wpt*2+offset,h,"[O]",clrBlue)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),prefix+"BUTT_CHART_SCREENSHOT"); return false; } ChartRedraw(0); return true; } //+------------------------------------------------------------------+
ここではすべて簡単です。座標は新しいボタンごとに計算され、ボタンはButtonCreate()関数を使用して作成されます。この関数は、作成されたチャートィカルオブジェクトの名前、座標、幅、高さ、キャプション、色を受け取ります。 ボタンの作成に失敗した場合、アラートがアクティブになり、falseが返されます。OnInit()ハンドラで、パネル作成関数がfalseを返した場合は、INIT_FAILED戻りコードで終了します。
ボタンクリックを処理するためのPressButtonEvents()関数で、新しいボタンクリックの処理を追加します。
//+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { bool comp_magic=true; // Temporary variable selecting the composite magic number with random group IDs string comment=""; //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- Random group 1 and 2 numbers within the range of 0 - 15 group1=(uchar)Rand(); group2=(uchar)Rand(); uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the button of shifting a chart 1 bar to the left is clicked if(button=="BUTT_NAVIGATE_LEFT1") { CChartObj *chart=engine.ChartGetMainChart(); if(chart!=NULL) chart.NavigateLeft(1); } //--- If the button of shifting a chart 1 bar to the right is clicked if(button=="BUTT_NAVIGATE_RIGHT1") { CChartObj *chart=engine.ChartGetMainChart(); if(chart!=NULL) chart.NavigateRight(1); } //--- If the button of shifting a chart 10 bars to the left is clicked if(button=="BUTT_NAVIGATE_LEFT10") { CChartObj *chart=engine.ChartGetMainChart(); if(chart!=NULL) chart.NavigateLeft(10); } //--- If the button of shifting a chart 10 bars to the right is clicked if(button=="BUTT_NAVIGATE_RIGHT10") { CChartObj *chart=engine.ChartGetMainChart(); if(chart!=NULL) chart.NavigateRight(10); } //--- If the button of shifting a chart to the start of history is clicked if(button=="BUTT_NAVIGATE_HOME") { CChartObj *chart=engine.ChartGetMainChart(); if(chart!=NULL) chart.NavigateBegin(); } //--- If the button of shifting a chart to the end of history is clicked if(button=="BUTT_NAVIGATE_END") { CChartObj *chart=engine.ChartGetMainChart(); if(chart!=NULL) chart.NavigateEnd(); } //--- If the new chart open button is pressed if(button=="BUTT_CHART_OPEN") { int total_charts=engine.ChartsTotal(); static int first_index=total_charts; string name=SymbolName(total_charts-first_index,true); if(engine.ChartOpen(name,PERIOD_CURRENT)) { engine.ChartsRefreshAll(); CChartObj *chart=engine.ChartGetMainChart(); if(chart!=NULL) chart.SetBringToTopON(true); } //--- This code block is needed only for the test and only if there is an open GBPUSD chart //--- GBPUSD chart settings should differ from that of charts opened by default CArrayObj *list_gbpusd=engine.GetListCharts("GBPUSD"); if(list_gbpusd!=NULL && list_gbpusd.Total()>0) { CChartObj *chart=list_gbpusd.At(0); if(chart.SaveTemplate()) { chart=engine.ChartGetLastOpenedChart(); if(chart!=NULL) chart.ApplyTemplate(); } } //--- End of the test code block } //--- If the the last chart close button is pressed if(button=="BUTT_CHART_CLOSE") { CArrayObj *list_charts=engine.GetListCharts(); if(list_charts!=NULL) { list_charts.Sort(SORT_BY_CHART_ID); CChartObj *chart=list_charts.At(list_charts.Total()-1); if(chart!=NULL && !chart.IsMainChart()) engine.ChartClose(chart.ID()); } } //--- If the ScreenShot button is pressed if(button=="BUTT_CHART_SCREENSHOT") { static int num=0; if(++num>3) num=1; CChartObj *chart=engine.ChartGetMainChart(); if(chart!=NULL) { switch(num) { case 1 : chart.ScreenShot800x600(); break; case 2 : chart.ScreenShot750x562(); break; default: chart.ScreenShotWndSize(); break; } } } //--- If the 'BUTT_BUY: Open Buy position' is pressed if(button==EnumToString(BUTT_BUY)) { ... ... ... ... ... ... ...
関数全体のコードではなく追加された変更のみを提示しています。
ここでは、新しいボタンをクリックするだけです。ロジックは単純で、説明しても意味がありません。特に、このハンドラがプログラム内のライブラリメソッドを操作する方法の例として機能することを考慮して、独立した分析のために残しておきます。ただし、コードに関連する不明点がある場合は、コメントセクションを使用してください。
新しいテストEAで行う必要のある改善はこれですべてです。
EAをコンパイルし、[現在の銘柄でのみ作業する]および[現在の時間枠でのみ作業する]設定でチャート上で起動します。
EAを起動する前に、GBPUSD銘柄の新しいチャートを開き、default.tplテンプレートを使用するときにデフォルトで開かれるチャートとは異なる外観を構成してください。以下は例です(GBPUSDチャートは事前に開かれています)。
これで、パネルボタンをクリックして新しいライブラリ機能をテストできます。
新しいチャートが開かれるたびに、 EAは以前に構成されたGBPUSD銘柄チャートのテンプレートを保存し、新しく開かれた各チャートにすぐに適用して、適切な操作ログエントリを作成します。
CChartObj::SaveTemplate: Chart template saved: GBPUSD H4 CChartObj::ApplyTemplate: Template applied to the chart: USDCHF H1 CChartObj::SaveTemplate: Chart template saved: GBPUSD H4 CChartObj::ApplyTemplate: Template applied to the chart: GBPUSD H1 CChartObj::SaveTemplate: Chart template saved: GBPUSD H4 CChartObj::ApplyTemplate: Template applied to the chart: EURUSD H1 CChartObj::SaveTemplate: Chart template saved: GBPUSD H4 CChartObj::ApplyTemplate: Template applied to the chart: USDRUB H1 CChartObj::SaveTemplate: Chart template saved: GBPUSD H4 CChartObj::ApplyTemplate: Template applied to the chart: EURJPY H1 CChartObj::SaveTemplate: Chart template saved: GBPUSD H4 CChartObj::ApplyTemplate: Template applied to the chart: EURGBP H1 CChartObjCollection::Close: Wrong chart ID (4101)
チャートを閉じると、 エラーが1つ発生します。ライブラリは、開いているチャートのステータスを0.5秒ごとに更新します。
これはDefines.mqhファイルで設定されます。
//--- Parameters of the chart collection timer #define COLLECTION_CHARTS_PAUSE (500) // Chart collection timer pause in milliseconds #define COLLECTION_CHARTS_COUNTER_STEP (16) // Chart timer counter increment #define COLLECTION_CHARTS_COUNTER_ID (9) // Chart timer counter ID
最後に開いたチャートを1秒に2回より速く閉じるボタンをクリックしたので、すでに閉じた前のチャート(チャートコレクションリストに適切なエントリが残っている)を閉じようとしました。開いているチャート、それらのウィンドウ、および指標の現在の状態を更新する頻度は、指定されたマクロ置換を変更することで調整できます。定数の値を減らすと、環境はより速く更新されます。この場合、更新の頻度が高くなるため、CPUの負荷が増加します。この機能は依然としてチャートの手動制御を目的としているため、ここで「中道」を見つけることが重要です。そのため、更新の頻度はカスタマイズ可能です。存在しないチャートにアクセスしようとしたときにたまに発生するエラーは重要ではありません。次の環境更新が発生し、チャートオブジェクトリストがライブラリ内でクライアントターミナルのチャートオブジェクトステータスと同期されたときに、ボタンをもう一度押すだけです。
次に、現在のチャートのスクリーンショットの作成をテストします。ボタンをクリックするたびに、特定のサイズのチャートのスクリーンショットが作成されます。最初のクリック — 800x600、2番目 — 750x562、3番目 — 現在のチャートサイズです。
異なる解像度で3つのスクリーンショットを作成した後(適切な操作ログエントリを伴う)は、
CChartObj::ScreenShot800x600: Screenshot created: DoEasy\ScreenShots\TestDoEasyPart70_EURUSD_H1_2021.04.13_14.02.25.png (800 x 600) CChartObj::ScreenShot750x562: Screenshot created: DoEasy\ScreenShots\TestDoEasyPart70_EURUSD_H1_2021.04.13_14.02.28.png (750 x 562) CChartObj::ScreenShotWndSize: Screenshot created: DoEasy\ScreenShots\TestDoEasyPart70_EURUSD_H1_2021.04.13_14.02.29.png (726 x 321)
これらのスクリーンショットが保存されているフォルダの内容も表示しました。
スクリーンショットを作成する前に、現在のチャートの2つのウィンドウの異なる領域にカーソルを合わせると、チャートのコメントにサブウィンドウの時間、価格、インデックス、カーソルのピクセル単位のX/Y座標が表示されます。カーソルのY座標には2つの値があります。最初の値は、銘柄メインウィンドウの初期座標のY座標を表示します。表示される2番目の値(括弧内)は、カーソルが置かれているウィンドウの上部境界線を基準にしたY座標を示しています。
ご覧のとおり、本稿で計画されたすべての機能は正しく機能しています。
次の段階
次の記事では、チャートオブジェクトのプロパティとそのウィンドウを変更するイベントの自動追跡を実装します。
ライブラリの現在のバージョンのすべてのファイルは、テストおよびダウンロードできるように、MQL5のテストEAファイルと一緒に以下に添付されています。
質問や提案はコメント欄にお願いします。
*連載のこれまでの記事:
DoEasyライブラリでのその他のクラス(第67部): チャットオブジェクトクラス
DoEasyライブラリでのその他のクラス(第68部): チャットウィンドウオブジェクトクラスとチャートでの指標オブジェクトクラス
DoEasyライブラリでのその他のクラス(第69部): チャットオブジェクトコレクションクラス
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/9293





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