
グラフィカルインタフェースX: 標準チャートコントロール(ビルド4)
コンテンツ
- はじめに
- 標準チャートコントロール作成クラスの開発
- コントロールを検証するためのアプリケーション
- ライブラリエンジンのタイマーとイベントハンドラの最適化
- ツリービューとファイルナビゲータコントロールの最適化
- ファイルナビゲータのフォルダとファイルの新しいアイコン
- おわりに
はじめに
シリーズ初めのグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)稿ではライブラリの目的が詳細に考察されました。各章の末尾では記事へのリンクの完全なリストがみられます。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
開発中のライブラリに欠かせないあと一つのコントロールについて考えてみましょう。取引ターミナルを起動すると、ユーザーに価格チャートが表示されます。より簡単にチャートを管理できるツールを持っていれば便利です。過去のMQL5 クックブック:単一ウィンドウでのマルチ タイム フレームのモニター稿はそのようなツールの1つを実証しました。今回は、コントロールを作成するためのクラスが書かれ、これをカスタムMQLアプリケーションのグラフィカルインターフェイスで簡単に使用できるようになります。上記のリンクで提供されている以前のバージョンとは異なり、この実装ではサブチャートの内容は従来のメインチャートウィンドウのように水平方向にスクロールできます。
さらに、CPU負荷を軽減するために、ライブラリコードは引き続き最適化されます。詳細は本稿で後ほどお話しします。
標準チャートコントロール作成クラスの開発
標準チャートコントロールを作成するCStandardChartクラスの開発を始める前に Object.mqhファイルには追加的なプロパティを持ったCSubChart基本クラス が追加されるべきです(下記のコードを参照)。これは、以前にライブラリコントロールの作成時に使用されるすべてのタイプのグラフィカルオブジェクトで行われていました。CSubChartクラスの基本クラスは標準ライブラリのCChartObjectSubChartクラスです。
//+------------------------------------------------------------------+ //| Objects.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ ... //--- 迅速なナビゲーションのためのファイルでのクラスリスト (Alt+G) ... class CSubChart; //+------------------------------------------------------------------+ // | Subchartオブジェクトの追加的なプロパティを持つクラス| //+------------------------------------------------------------------+ class CSubChart : public CChartObjectSubChart { protected: int m_x; int m_y; int m_x2; int m_y2; int m_x_gap; int m_y_gap; int m_x_size; int m_y_size; bool m_mouse_focus; //--- public: CSubChart(void); ~CSubChart(void); //--- 座標 int X(void) { return(m_x); } void X(const int x) { m_x=x; } int Y(void) { return(m_y); } void Y(const int y) { m_y=y; } int X2(void) { return(m_x+m_x_size); } int Y2(void) { return(m_y+m_y_size); } //--- エッジポイントからのインデント (xy) int XGap(void) { return(m_x_gap); } void XGap(const int x_gap) { m_x_gap=x_gap; } int YGap(void) { return(m_y_gap); } void YGap(const int y_gap) { m_y_gap=y_gap; } //--- サイズ int XSize(void) { return(m_x_size); } void XSize(const int x_size) { m_x_size=x_size; } int YSize(void) { return(m_y_size); } void YSize(const int y_size) { m_y_size=y_size; } //--- フォーカス bool MouseFocus(void) { return(m_mouse_focus); } void MouseFocus(const bool focus) { m_mouse_focus=focus; } }; //+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CSubChart::CSubChart(void) : m_x(0), m_y(0), m_x2(0), m_y2(0), m_x_gap(0), m_y_gap(0), m_x_size(0), m_y_size(0), m_mouse_focus(false) { } //+------------------------------------------------------------------+ //| デストラクタ | //+------------------------------------------------------------------+ CSubChart::~CSubChart(void) { }
CChartObjectSubChartクラスには、サブチャートを作成するメソッドと、最も頻繁に使用されるチャートプロパティを変更するメソッドが含まれています。このリストには、次のようなプロパティを設定および取得するためのメソッドが含まれています。
- 座標と寸法
- 銘柄と時間枠とスケール
- 価格および時間スケールの表示.
//+------------------------------------------------------------------+ //| ChartObjectSubChart.mqh | //| Copyright 2009-2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "ChartObject.mqh" //+------------------------------------------------------------------+ //| CChartObjectSubChartクラス | //| 目的:チャートのSubChartオブジェクトのクラス | //| CChartObjectクラスから派生する | //+------------------------------------------------------------------+ class CChartObjectSubChart : public CChartObject { public: CChartObjectSubChart(void); ~CChartObjectSubChart(void); //--- オブジェクトプロパティへにアクセスするメソッド int X_Distance(void) const; bool X_Distance(const int X) const; int Y_Distance(void) const; bool Y_Distance(const int Y) const; ENUM_BASE_CORNER Corner(void) const; bool Corner(const ENUM_BASE_CORNER corner) const; int X_Size(void) const; bool X_Size(const int size) const; int Y_Size(void) const; bool Y_Size(const int size) const; string Symbol(void) const; bool Symbol(const string symbol) const; int Period(void) const; bool Period(const int period) const; int Scale(void) const; bool Scale(const int scale) const; bool DateScale(void) const; bool DateScale(const bool scale) const; bool PriceScale(void) const; bool PriceScale(const bool scale) const; //--- 時間/価格座標の変更がブロックされている bool Time(const datetime time) const { return(false); } bool Price(const double price) const { return(false); } //--- オブジェクト作成メソッド bool Create(long chart_id,const string name,const int window, const int X,const int Y,const int sizeX,const int sizeY); //--- オブジェクト識別メソッド virtual int Type(void) const { return(OBJ_CHART); } //--- ファイル操作メソッド virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); };
ここでCStandardChart クラスを持つStandardChart.mqhファイルが作成できます。下のコードに示されるように、すべてのライブラリコントロールの標準メソッドをは本コンテンツで指定できます。コントロールには水平スクロールモードがあるため、マウスカーソルにはスクロールモードが有効であることをユーザーに伝えるアイコンが必要です。マウスカーソルが水平方向に移動するとサブチャートのデータがシフトします。アイコンを変えるにはCPointerを含むPointer.mqhクラスをインクルードします。これは以前にグラフィカルインタフェースVIII: ツリービューコントロール(チャプター2)稿で考察されました。マウスポインタのアイコンとしては、メインチャートの水平スクロールでアクティブ化されたもののコピーを使用します(白い輪郭を持った黒い両矢印)。本稿末尾にはこのアイコンの2つのバージョン(黒と青の矢印)が添付されています。
したがって、マウスポインタの列挙(ENUM_MOUSE_POINTER)にあと一つの識別子( MP_X_SCROLL )が追加されました。
//+------------------------------------------------------------------+ //| ポインタタイプの列挙 | //+------------------------------------------------------------------+ enum ENUM_MOUSE_POINTER { MP_CUSTOM =0, MP_X_RESIZE =1, MP_Y_RESIZE =2, MP_XY1_RESIZE =3, MP_XY2_RESIZE =4, MP_X_SCROLL =5 };
さらに Pointer.mqh ファイルにこのタイプのカーソルのアイコンを含むリソースをインクルードし、CPointer::SetPointerBmp>()メソッドのスイッチ構造にはあと一つのcase ブロックが追加されるべきです。
//+------------------------------------------------------------------+ //| Pointer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" //--- リソース ... #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp" //+------------------------------------------------------------------+ //| カーソルタイプに基づいてカーソル画像を設定する | //+------------------------------------------------------------------+ void CPointer::SetPointerBmp(void) { switch(m_type) { case MP_X_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs.bmp"; break; case MP_Y_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs.bmp"; break; case MP_XY1_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs.bmp"; break; case MP_XY2_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs.bmp"; break; case MP_X_SCROLL : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp"; break; } //--- カスタムタイプ(MP_CUSTOM)が示された場合 if(m_file_on=="" || m_file_off=="") ::Print(__FUNCTION__," > Both images must be set for the cursor!"); }
またMoving() メソッドが2つのモードで使用できモードはメソッドの3番目の引数で設定できることにご注目ください。この引数のデフォルト値は false です。つまり、コントロールの移動は取り付けられているフォームが現在移動モードになっている場合にのみ可能です。
//+------------------------------------------------------------------+ //| StandardChart.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "Pointer.mqh" //+------------------------------------------------------------------+ //| 標準チャート作成クラス | //+------------------------------------------------------------------+ class CStandardChart : public CElement { private: //--- 要素が取り付けられるフォームへのポインタ CWindow *m_wnd; //--- public: //--- フォームポインタを格納する void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- チャートイベントハンドラ virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- 要素の移動 virtual void Moving(const int x,const int y,const bool moving_mode=false); //--- (1)表示 (2)非表示 (3)リセット (4)削除 virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- マウスの左クリックの優先順位の(1)設定と(2)リセット virtual void SetZorders(void); virtual void ResetZorders(void); //--- private: //| ウィンドウの右端の幅を変更する | virtual void ChangeWidthByRightWindowSide(void); //--- ウィンドウの下端で高さを変更する virtual void ChangeHeightByBottomWindowSide(void); }; //+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CStandardChart::CStandardChart(void) { //--- 要素クラスの名前を基本クラスに格納する CElement::ClassName(CLASS_NAME); } //+------------------------------------------------------------------+ //| デストラクタ | //+------------------------------------------------------------------+ CStandardChart::~CStandardChart(void) { }
Moving() メソッドの3番目の引数の値が true に設定されている場合は、フォームが移動モードであるかどうかに関係なく、メソッドを呼び出すとコントロールが強制的に移動されます 。場合によっては、これによりCPU負荷が大幅に減少します。
フォームが移動モードかどうかを確認するために ClampingAreaMouse() メソッドが CWindow クラスに追加されました。メソッドからは左マウスボタンが押された領域が戻されます。
//+------------------------------------------------------------------+ //| コントロールフォーム作成クラス | //+------------------------------------------------------------------+ class CWindow : public CElement { public: //--- 左マウスボタンが押された領域を戻す ENUM_MOUSE_STATE ClampingAreaMouse(void) const { return(m_clamping_area_mouse); } };
CWindow::ClampingAreaMouse() メソッドは、フォームに接続されている各コントロールのフォームポインタを介してのみアクセスできます。すべてを上記のように動作させるには、下のコードが示すように、各コントロールの移動Moving() メソッドにコードブロックを挿入する必要があります(黄色で強調表示されている部分を参照)。ここでCSimpleButtonクラスの例(短縮版)を示します。
//+------------------------------------------------------------------+ //| コントロールの移動 | //+------------------------------------------------------------------+ void CSimpleButton::Moving(const int x,const int y,const bool moving_mode=false) { //--- 要素が隠れている場合は終了する if(!CElement::IsVisible()) return; //--- 管理がウィンドウに委任されている場合はその場所を特定する if(!moving_mode) if(m_wnd.ClampingAreaMouse()!=PRESSED_INSIDE_HEADER) return; //--- 右に固定されている場合 //--- 左に固定されている場合 //--- 下に固定されている場合 //--- 上に固定されている場合 //--- グラフィックオブジェクトの座標の更新 m_button.X_Distance(m_button.X()); m_button.Y_Distance(m_button.Y()); }
下記では CSimpleButton::Show() メソッドのコードが実証されMoving()の使用例が見られます。この場合、コントロールの座標を強制的に更新する必要があるため3番目の引数として true が渡されます。Moving() メソッドを使用するすべてのライブラリクラスに適切な変更が加えられました。
//+------------------------------------------------------------------+ //| ボタンを表示する | //+------------------------------------------------------------------+ void CSimpleButton::Show(void) { //--- 要素がすでに表示されている場合は終了する if(CElement::IsVisible()) return; //--- オブジェクトを表示する m_button.Timeframes(OBJ_ALL_PERIODS); //--- 表示の状態 CElement::IsVisible(true); //--- オブジェクトの位置を更新する Moving(m_wnd.X(),m_wnd.Y(),true); }
開発されたライブラリの最適化については本稿の後半で説明します。次に標準チャートコントロールを検討します。
行に配置されたサブチャートの配列を作成できるようにしましょう。そのためには、(1)チャート識別子(2)銘柄(3)タイムフレームなどの特定のプロパティと同様に、チャートを表すオブジェクトの動的配列を宣言する必要があります。標準チャートコントロールを作成する前に CStandardChart::AddSubChart() メソッドを使用する必要があり、チャートの銘柄と時間枠が渡される必要があります。このメソッドの冒頭では、要素を配列に追加して渡された値で初期化する前にCStandardChart :: CheckSymbol ()を使用した銘柄の可用性のチェックがあります。
class CStandardChart : public CElement { private: //--- 要素作成オブジェクト CSubChart m_sub_chart[]; //--- チャートプロパティ long m_sub_chart_id[]; string m_sub_chart_symbol[]; ENUM_TIMEFRAMES m_sub_chart_tf[]; //--- public: //--- 作成前に指定されたプロパティを持つチャートを追加する void AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf); //--- private: //--- 銘柄をチェックする bool CheckSymbol(const string symbol); };
CStandardChart::CheckSymbol()メソッドは初めに気配値表示ウィンドウで指定された銘柄が存在するかどうかを確認します。見つからなかった場合は、銘柄は総合リストで探されます。そこで銘柄が見つかった場合は、銘柄は気配値表示に追加される必要があります。それ以外の場合はこの銘柄でサブチャートを作成することはできません(メインチャートウィンドウの銘柄を持つサブチャートが代わりに作成されます)。
成功した場合CStandardChart::CheckSymbol()メソッドはtrueを戻します。指定された銘柄が見つからなかった場合メソッドは false を返しサブチャートは追加されません(配列のサイズは同じです)。これについてのメッセージが操作ログに表示されます。
//+------------------------------------------------------------------+ //| チャートを追加する | //+------------------------------------------------------------------+ void CStandardChart::AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf) { //--- 銘柄がサーバに存在するかを確認する if(!CheckSymbol(symbol)) { ::Print(__FUNCTION__," > Symbol "+symbol+" is not available on the server!"); return; } //--- 配列サイズを1要素で増やす int array_size=::ArraySize(m_sub_chart); int new_size=array_size+1; ::ArrayResize(m_sub_chart,new_size); ::ArrayResize(m_sub_chart_id,new_size); ::ArrayResize(m_sub_chart_symbol,new_size); ::ArrayResize(m_sub_chart_tf,new_size); //--- 渡されたパラメータの値を格納する m_sub_chart_symbol[array_size] =symbol; m_sub_chart_tf[array_size] =tf; } //+------------------------------------------------------------------+ // | 銘柄の有無を確認する | //+------------------------------------------------------------------+ bool CStandardChart::CheckSymbol(const string symbol) { bool flag=false; //--- 「気配値表示」で銘柄を確認する int symbols_total=::SymbolsTotal(true); for(int i=0; i<symbols_total; i++) { //--- 銘柄が使用可能な場合はサイクルを停止する if(::SymbolName(i,true)==symbol) { flag=true; break; } } //--- 銘柄が「気配値表示」 ウィンドウにない場合は if(!flag) { //--- ... 総合リストで探す symbols_total=::SymbolsTotal(false); for(int i=0; i<symbols_total; i++) { //--- この銘柄があった場合は if(::SymbolName(i,false)==symbol) { //--- ... 「気配値表示」に追加してサイクルを停止する ::SymbolSelect(symbol,true); flag=true; break; } } } //--- 検索結果を戻す return(flag); }
標準チャートコントロールを作成するには、1つのメインパブリックメソッドと2つのプライベートメソッドの3つのメソッドが必要で、プライベートメソッドのうち1つは水平方向のスクロールモードでのマウスカーソルのアイコンを参照します。CStandardChart::SubChartsTotal()パブリックメソッドはサブチャート数を取得するための補助的なメソッドとしてクラスに追加されました。
class CStandardChart : public CElement { private: //--- 要素作成オブジェクト CSubChart m_sub_chart[]; CPointer m_x_scroll; //--- public: //--- 標準チャート作成メソッド bool CreateStandardChart(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateSubChart(void); bool CreateXScrollPointer(void); //--- public: //--- チャート配列のサイズを戻す int SubChartsTotal(void) const { return(::ArraySize(m_sub_chart)); } };
ここで、サブチャートをっ作成するためのCStandardChart::CreateSubCharts()メソッドを考察しましょう。ここで、メソッドの始めには、コントロールを作成する前に配列に追加されたチャートの数の確認があります。何も追加されなかった場合、プログラムは単にメソッドを終了し、関連するメッセージを操作ログに出力します。
チャートが追加された場合は、各オブジェクトの幅が計算されます。コントロールの幅の合計は、開発されたMQLアプリケーションのカスタムクラスでユーザーによって定義される必要があります。複数のチャートを作成する必要がある場合は、各オブジェクトの幅を取得するためにはコントロールの幅をチャートの数で割るだけで十分です。
その後、オブジェクトは反復して作成されます。これはコントロールの配置を考慮に入れます(フォームの辺の1つでのアンカーポイント)。このトピックは前の記事で詳しく説明されているので、ここでは説明しません。
サブチャートが作成されるとその識別子が取得されて配列に格納されそのプロパティが設定されて保存されます。
//+------------------------------------------------------------------+ //| チャートを作成する | //+------------------------------------------------------------------+ bool CStandardChart::CreateSubCharts(void) { //--- チャート数を取得する int sub_charts_total=SubChartsTotal(); //--- グループにチャートがない場合は報告する if(sub_charts_total<1) { ::Print(__FUNCTION__," > This method is to be called, " "if a group contains at least one chart!Use the CStandardChart::AddSubChart() method"); return(false); } //--- 座標とサイズを計算する int x=m_x; int x_size=(sub_charts_total>1)?m_x_size/sub_charts_total : m_x_size; //--- 指定された数のチャートを作成する for(int i=0; i<sub_charts_total; i++) { //--- オブジェクト名の形成 string name=CElement::ProgramName()+"_sub_chart_"+(string)i+"__"+(string)CElement::Id(); //--- X座標の計算 x=(i>0)?(m_anchor_right_window_side)?x-x_size+1 : x+x_size-1 : x; //--- 最後のチャートの幅を調整する if(i+1>=sub_charts_total) x_size=m_x_size-(x_size*(sub_charts_total-1)-(sub_charts_total-1)); //--- ボタンを設定する if(!m_sub_chart[i].Create(m_chart_id,name,m_subwin,x,m_y,x_size,m_y_size)) return(false); //--- 作成されたチャートの識別子を取得して保存する m_sub_chart_id[i]=m_sub_chart[i].GetInteger(OBJPROP_CHART_ID); //--- プロパティを設定する m_sub_chart[i].Symbol(m_sub_chart_symbol[i]); m_sub_chart[i].Period(m_sub_chart_tf[i]); m_sub_chart[i].Z_Order(m_zorder); m_sub_chart[i].Tooltip("\n"); //--- サイズを格納する m_sub_chart[i].XSize(x_size); m_sub_chart[i].YSize(m_y_size); //--- 端からのマージン m_sub_chart[i].XGap((m_anchor_right_window_side)?x : x-m_wnd.X()); m_sub_chart[i].YGap((m_anchor_bottom_window_side)?m_y : m_y-m_wnd.Y()); //--- オブジェクトポインタを格納する CElement::AddToArray(m_sub_chart[i]); } //--- return(true); }
標準チャートコントロールに含まれるサブチャートのプロパティは、作成後いつでも変更することができます。これはCStandardChart :: GetSubChartPointer() メソッドの助けを借りて取得されるポインタを介して行われます。渡された不正なインデックスは配列の範囲を超えないように修正されます。
class CStandardChart : public CElement { public: //--- 指定されたインデックスによってサブチャートへのポインタを戻す CSubChart *GetSubChartPointer(const uint index); }; //+------------------------------------------------------------------+ //| 指定されたインデックスによってチャートへのポインタを戻す | //+------------------------------------------------------------------+ CSubChart *CStandardChart::GetSubChartPointer(const uint index) { uint array_size=::ArraySize(m_sub_chart); //--- チャートがない場合は報告する if(array_size<1) { ::Print(__FUNCTION__," > This method is to be called, " "if a group contains at least one chart!"); } //--- サイズが超過した場合の調整 uint i=(index>=array_size)?array_size-1 : index; //--- ポインタを返す return(::GetPointer(m_sub_chart[i])); }
マウスカーソルのアイコンは、サブチャート内のデータの水平スクロールモードが有効な場合にのみ作成されます。コントロールを作成する前の CStandardChart :: XScrollMode() メソッドを使用した有効化が必要です。
class CStandardChart : public CElement { private: //--- 水平方向のスクロールモード bool m_x_scroll_mode; //--- public: //--- 水平方向のスクロールモード void XScrollMode(const bool mode) { m_x_scroll_mode=mode; } }; //+------------------------------------------------------------------+ //| 水平方向のスクロールのカーソルを作成する | //+------------------------------------------------------------------+ bool CStandardChart::CreateXScrollPointer(void) { //--- 水平方向のスクロールが不必要な場合は終了する if(!m_x_scroll_mode) return(true); //--- プロパティの設定 m_x_scroll.XGap(0); m_x_scroll.YGap(-20); m_x_scroll.Id(CElement::Id()); m_x_scroll.Type(MP_X_SCROLL); //--- 要素の作成 if(!m_x_scroll.CreatePointer(m_chart_id,m_subwin)) return(false); //--- return(true); }
上記を要約します。水平スクロールモードが有効な場合、その操作では、CHARTEVENT_MOUSE_MOVE イベントが発生したときにコントロールのイベントハンドラで呼ばれるCStandardChart :: HorizontalScroll() メソッドが使用されます。メソッドは、マウスの左ボタンが押された場合、それが押されたかまたは水平スクロールの過程でメソッドへの呼び出しが繰り返されているのかどうかによって、マウスカーソルが押された距離をピクセル単位で計算します。ここで
- フォームがロックされます。
- マウスカーソルのアイコンの座標が計算されます。
- アイコンが表示されます。
最後のバーを基準にしてオフセットが実行されるため、サブチャートのデータをシフトする計算値は負の値になりえます。CHART_END 値を持つ::ChartNavigate()メソッド(2番目の引数)から ENUM_CHART_POSITION 列挙体です。シフトの値が正の場合はプログラムはメソッドを終了します。チェックに合格すると、シフトの現在の値が次の反復のために保存されます。次に、すべてのサブチャートの「自動スクロール」( CHART_AUTOSCROLL)と「右端からのチャートシフト」( CHART_SHIFT )が無効にされ、算出された値に応じてシフトが行われます。
マウスの左ボタンが放されると、フォームのロックが解除され、水平スクロール処理を示すマウスカーソルのアイコンが非表示になります。その後、プログラムはメソッドを終了します。
class CStandardChart : public CElement { private: //--- チャートの水平方向のスクロールに関連する変数 int m_prev_x; int m_new_x_point; int m_prev_new_x_point; //--- private: //--- 水平方向のスクロール void HorizontalScroll(void); }; //+------------------------------------------------------------------+ //--- チャートの水平方向のスクロール | //+------------------------------------------------------------------+ void CStandardChart::HorizontalScroll(void) { //--- 水平方向のスクロールが無効な場合は終了する if(!m_x_scroll_mode) return; //--- マウスボタンが押された場合 if(m_mouse.LeftButtonState()) { //--- カーソルの現在のX座標を格納する if(m_prev_x==0) { m_prev_x =m_mouse.X()+m_prev_new_x_point; m_new_x_point =m_prev_new_x_point; } else m_new_x_point=m_prev_x-m_mouse.X(); //--- フォームをブロックする if(!m_wnd.IsLocked()) { m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); } //--- ポインタ座標を更新して表示する int l_x=m_mouse.X()-m_x_scroll.XGap(); int l_y=m_mouse.Y()-m_x_scroll.YGap(); m_x_scroll.Moving(l_x,l_y); //--- ポインタを表示する m_x_scroll.Show(); //--- 可視性フラグを設定する m_x_scroll.IsVisible(true); } else { m_prev_x=0; //--- フォームのブロックを解除する if(m_wnd.IdActivatedElement()==CElement::Id()) { m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } //--- ポインタを非表示にする m_x_scroll.Hide(); //--- 可視性フラグを設定する m_x_scroll.IsVisible(false); return; } //--- 正の値がある場合は終了する if(m_new_x_point>0) return; //--- 現在の位置を格納する m_prev_new_x_point=m_new_x_point; //--- すべてのチャートに適応する int symbols_total=SubChartsTotal(); //--- 右側の境界線からの自動シフトを無効にする for(int i=0; i<symbols_total; i++) { if(::ChartGetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL)) ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false); if(::ChartGetInteger(m_sub_chart_id[i],CHART_SHIFT)) ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false); } //--- 最後のエラーをリセットする ::ResetLastError(); //--- チャートをシフトする for(int i=0; i<symbols_total; i++) if(!::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point)) ::Print(__FUNCTION__," > error: ",::GetLastError()); }
サブチャートデータの水平スクロールモードの補助変数をリセットするためにはCStandardChart::ZeroHorizontalScrollVariables()メソッドが使用されます。プログラムによって最後のバーに移動する必要もあるかもしれません。このためにはCStandardChart::ResetCharts()パブリックメソッドが使われます。
class CStandardChart : public CElement { public: //--- チャートをリセットする void ResetCharts(void); //--- private: //--- 水平方向のスクロールの変数をリセットする void ZeroHorizontalScrollVariables(void); }; //+------------------------------------------------------------------+ //| チャートをリセットする | //+------------------------------------------------------------------+ void CStandardChart::ResetCharts(void) { int sub_charts_total=SubChartsTotal(); for(int i=0; i<sub_charts_total; i++) ::ChartNavigate(m_sub_chart_id[i],CHART_END); //--- チャートの水平方向のスクロールのための変数をリセットする ZeroHorizontalScrollVariables(); } //+------------------------------------------------------------------+ //| 水平方向のスクロールの変数をリセットする | //+------------------------------------------------------------------+ void CStandardChart::ZeroHorizontalScrollVariables(void) { m_prev_x =0; m_new_x_point =0; m_prev_new_x_point =0; }
また「標準チャート」コントロールのサブチャート上でマウスの左ボタンを押すイベントを追跡する必要があるかもしれません。なのでDefines.mqhファイルに新しいON_CLICK_SUB_CHART識別子を追加します。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ ... //--- イベント識別子 ... #define ON_CLICK_SUB_CHART (28) // サブチャートのクリック
サブチャートのクリックを決定するにはCStandardChart :: OnClickSubChart () メソッドを実装します。名前と識別子のチェックが成功した場合(下記のリストを参照)は(1)イベントの ON_CLICK_SUB_CHART 識別子、(2)コントロールの識別子、 (3) サブチャートのインデックス、および(4)銘柄名を持つメッセージが生成されます。
class CStandardChart : public CElement { private: //--- サブチャート押下の処理 bool OnClickSubChart(const string clicked_object); }; //+------------------------------------------------------------------+ //| ボタンの押下の処理 | //+------------------------------------------------------------------+ bool CStandardChart::OnClickSubChart(const string clicked_object) { //--- 押されたのがメニュー項目でなかった場合は終了する if(::StringFind(clicked_object,CElement::ProgramName()+"_sub_chart_",0)<0) return(false); //--- オブジェクト名から識別子とインデックスを取得する int id=CElement::IdFromObjectName(clicked_object); //--- 識別子が一致しない場合は終了する if(id!=CElement::Id()) return(false); //--- インデックスを取得する int group_index=CElement::IndexFromObjectName(clicked_object); //--- それについてシグナルを送信する ::EventChartCustom(m_chart_id,ON_CLICK_SUB_CHART,CElement::Id(),group_index,m_sub_chart_symbol[group_index]); return(true); }
サブチャートをナビゲートするのに、端末によってメインチャートに実装されているのと別の方法が必要であるとします。MetaTrader端末で«Space»または«Enter»キーが押されると、チャートの左下に編集ボックスが表示されます(下記のスクリーンショット参照)。これはコマンドラインの一種で、チャート上のその日付にジャンプするために日付を入力することができます。このコマンドラインを使用して、チャートの銘柄と時間枠を変更することもできます。
図1 チャートの左隅のコマンドライン
ところで、最新の取引ターミナル更新(ビルド1455 )では、コマンドラインを管理するための新機能が追加されました。
…
8. MQL5:新しい CHART_QUICK_NAVIGATIONプロパティは、チャート内のクイックナビゲーションバーを有効/無効にすることができます。プロパティの状態を変更してアクセスする必要がある場合はChartSetInteger及びChartGetInteger関数を使います。
ナビゲーションバーはEnterまたはSpaceを押すと開きます。これにより、チャート上の指定された日付に素早く移動し、銘柄と時間枠を切り替えることができます。MQL5プログラムがEnterキーまたはスペースキーの押下を処理する場合、ターミナルによるこれらのイベントの傍受を避けるために CHART_QUICK_NAVIGATION プロパティを無効にするべきです。クイックナビゲーションバーは、ダブルクリックでも開くことができます。
…
グラフィカルインタフェースでは、すべてをより便利に簡単に行うことができます。 Easy And Fast(簡単かつ手早い)ライブラリにはすでにカレンダーコントロール(CCalendarクラス)が含まれています。メインチャートとサブチャートのナビゲーションは、カレンダーの日付を選択するだけで実装できます。すべてを1つの引数を持つ1つのメソッドに単純化しましょう。この引数の値は、チャートをシフトする必要がある日付になります。メソッドはCStandardChart::SubChartNavigate()と呼ばれ、下記のコードがその現在のバージョンを実証します。
メインチャートの「自動スクロール」と「右端からのチャートシフト」モードはメソッドの冒頭で無効にされます。メソッドに渡された変数が現在の日の始めよりも大きい場合は、最後のバーに移動してメソッドを終了します。日付が少ない場合は、左に移動するバーの数を計算する必要があります。まず、メインチャートの計算が行なわれます。
- 現在の銘柄の開始日から指定された日付までの現在の銘柄および時間枠の使用可能なバーの合計数を取得します。
- チャートに表示されるバーの数を取得します。
- 現在の日の始めからのバーの数+ 追加のインデントの2バーが取得されます。
- 最後のバーからシフトするバーの数を計算します。
その後、メインチャートが左にシフトされ、同じことがサブチャートで繰り返されます。
class CStandardChart : public CElement { public: //--- 指定した日付にジャンプする void SubChartNavigate(const datetime date); }; //+------------------------------------------------------------------+ //| 指定した日付にジャンプする | //+------------------------------------------------------------------+ void CStandardChart::SubChartNavigate(const datetime date) { //--- (1) 現在のチャートの日付と (2) カレンダーで新しく選ばれた日 datetime current_date =::StringToTime(::TimeToString(::TimeCurrent(),TIME_DATE)); datetime selected_date =date; //--- 右側の境界線からの自動シフトを無効にする ::ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,false); ::ChartSetInteger(m_chart_id,CHART_SHIFT,false); //--- カレンダーで選択された日付が現在の日付よりも大きい場合 if(selected_date>=current_date) { //--- すべてのチャートで現在の日付に移動する ::ChartNavigate(m_chart_id,CHART_END); ResetCharts(); return; } //--- 指定日からバーの数を取得する int bars_total =::Bars(::Symbol(),::Period(),selected_date,current_date); int visible_bars =(int)::ChartGetInteger(m_chart_id,CHART_VISIBLE_BARS); long seconds_today =::TimeCurrent()-current_date; int bars_today =int(seconds_today/::PeriodSeconds())+2; //--- すべてのチャートの右の枠からインデントを設定する m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today); ::ChartNavigate(m_chart_id,CHART_END,m_new_x_point); //--- int sub_charts_total=SubChartsTotal(); for(int i=0; i<sub_charts_total; i++) { //--- 右側の境界線からの自動シフトを無効にする ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false); ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false); //--- 指定日からバーの数を取得する bars_total =::Bars(m_sub_chart[i].Symbol(),(ENUM_TIMEFRAMES)m_sub_chart[i].Period(),selected_date,current_date); visible_bars =(int)::ChartGetInteger(m_sub_chart_id[i],CHART_VISIBLE_BARS); bars_today =int(seconds_today/::PeriodSeconds((ENUM_TIMEFRAMES)m_sub_chart[i].Period()))+2; //--- チャートの右側の境界線のインデント m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today); ::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point); } }
標準チャートコントロールを作成するための CStandardChart クラスの開発が終了しました。ここで動作確認のためのアプリケーションを書いてみましょう。
コントロールを検証するためのアプリケーション
検証には、前の記事のエキスパートアドバイザーが使用できます。メインメニューとステータスバーとタブを除いたすべてのコントロールを削除します。各タブにサブチャートの別々のグループがあるようにします。各グループには特定の通貨が含まれます。 したがって、各タブには対応する記述があります。
- 1番目のタブ - EUR (ユーロ)
- 2番目のタブ - GBP(英国ポンド)
- 3番目のタブ - AUD(オーストラリアドル)
- 4番目のタブ - CAD(カナダドル)
- 5番目のタブ - JPY(日本円)
サブチャートは厳密にタブの作業領域に配置され、フォームのサイズが変更されると自動的にサイズ変更されます。タブ内の作業領域の右端はフォームの右端から常に173画素のインデントを持ちます。このスペースには、次のようなプロパティを設定するためのコントロールがあります。
- 時間スケール(Date time)の表示
- 価格スケールの表示(Price scale)
- チャート時間枠の変更(Timeframes)
- カレンダーを介したチャートデータのナビゲート
例としては、単一の標準チャート( CStandardChart )コントロールを作成するためのコードの表示で十分です。チャートの水平スクロールはデフォルトでは無効になっているので、必要に応じて CStandardChart :: XScrollMode() メソッドを使用できます。グループにチャートを追加するにはCStandardChart::AddSubChart()メソッドが使われます。
//+------------------------------------------------------------------+ //| アプリケーション作成のクラス | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { protected: //--- 標準チャート CStandardChart m_sub_chart1; //--- protected: //--- 標準チャート bool CreateSubChart1(const int x_gap,const int y_gap); }; //+------------------------------------------------------------------+ //| 標準チャート1を作成する | //+------------------------------------------------------------------+ bool CProgram::CreateSubChart1(const int x_gap,const int y_gap) { //--- ウィンドウポインタを格納する m_sub_chart1.WindowPointer(m_window); //--- 1番目のタブに取り付ける m_tabs.AddToElementsArray(0,m_sub_chart1); //--- 座標 int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- 作成前にプロパティを設定する m_sub_chart1.XSize(600); m_sub_chart1.YSize(200); m_sub_chart1.AutoXResizeMode(true); m_sub_chart1.AutoYResizeMode(true); m_sub_chart1.AutoXResizeRightOffset(175); m_sub_chart1.AutoYResizeBottomOffset(25); m_sub_chart1.XScrollMode(true); //--- チャートを追加する m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1); m_sub_chart1.AddSubChart("EURGBP",PERIOD_D1); m_sub_chart1.AddSubChart("EURAUD",PERIOD_D1); //--- コントロールを作成する if(!m_sub_chart1.CreateStandardChart(m_chart_id,m_subwin,x,y)) return(false); //--- オブジェクトをオブジェクトグループの共通配列に追加する CWndContainer::AddToElementsArray(0,m_sub_chart1); return(true); }
下のスクリーンショットが最終結果を示します。この例では、サブチャートデータは、メインチャートで実装されているのと同様に、水平方向にスクロールできます。さらに、日付を早送りするなどのサブチャート間のナビゲーションはカレンダーを介して機能します。
図2 標準のチャートコントロールの検証
本稿で紹介されたテストアプリケーションをさらに研究するためには、以下のリンクを使用してダウンロードすることができます。
ライブラリエンジンのタイマーとイベントハンドラの最適化
Easy And Fast(簡単かつ手早い)ライブラリは以前にはWindows 7 x64オペレーティングシステムでのみ検証されてきました。Windows 10 x64にアップグレードした後、CPU使用率が大幅に増加することに気が付きました。ライブラリプロセスは、グラフィカルインタフェースとの対話がない場合のスタンバイモードでもCPUリソースの10%を消費しました。以下のスクリーンショットは、テストMQLアプリケーションをチャートに接続する前後のCPUリソースの消費量を示しています。
図3 テストMQLアプリケーションをチャートに追加する前のCPUリソースの消費
図4 テストMQLアプリケーションをチャートに追加した後のCPUリソースの消費
次のコードに示すように、チャートを16ミリ秒ごとに更新するライブラリエンジンのタイマーに問題があることが判明しました。
//+------------------------------------------------------------------+ //| タイマー | //+------------------------------------------------------------------+ void CWndEvents::OnTimerEvent(void) { //--- 配列が空の場合は終了する if(CWndContainer::WindowsTotal()<1) return; //--- すべての要素のイベントをタイマーで確認する CheckElementsEventsTimer(); //--- チャートを再描画する m_chart.Redraw(); }
マウスカーソルをチャート領域内で動かしてMQLアプリケーションのグラフィカルインタフェースとのアクティブな対話を追加すると、CPU消費量はさらに増加します。タスクは、ライブラリエンジンタイマーの操作を減らし、マウスの移動イベントが発生するたびのチャートの再描画を排除することです。それはどのように行うことができるのでしょうか?
CWndEvents :: ChartEventMouseMove()メソッドから、チャートを再描画する行(赤で強調表示)を削除します。
//+------------------------------------------------------------------+ //| CHARTEVENT MOUSE MOVE イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventMouseMove(void) { //--- カーソル変位イベントではない場合は終了する if(m_id!=CHARTEVENT_MOUSE_MOVE) return; //--- ウィンドウの移動 MovingWindow(); //--- チャートの状態の設定 SetChartState(); //--- チャートを再描画する m_chart.Redraw(); }
ライブラリーエンジンタイマーに関しては、現在の目的は、マウスでホーバーされるとコントロールの色が変わり、異なるコントロール(リスト、表、カレンダーなど)を早送りができることです。したがって、絶えず操作しなければなりません 。リソース節約のために、タイマーはマウスカーソルの移動が開始されるとアクティブになり、動きが終わると操作がほぼすぐにブロックされます。
この概念の実装には CMouse クラスにいくつかの追加が必要です。この追加には、システムタイマーの呼び出しのカウンタと、マウスの移動イベントと呼び出しの差を返すCMouse :: GapBetweenCalls()メソッドが含まれています。
class CMouse { private: //--- 呼び出しカウンタ ulong m_call_counter; //--- public: // ---(1)最後の呼び出し中に格納されたカウンタ値と(2)マウス移動イベントハンドラへの呼び出しを戻す ulong CallCounter(void) const { return(m_call_counter); } ulong GapBetweenCalls(void) const { return(::GetTickCount()-m_call_counter); } }; //+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CMouse::CMouse(void) : m_call_counter(::GetTickCount()) { }
そのロジックはシンプルです。マウスカーソルの移動が開始されると CMouse クラスのイベントハンドラがシステムタイマの現在値を保存します。
//+------------------------------------------------------------------+ //| マウスカーソル移動イベントを処理する | //+------------------------------------------------------------------+ void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- カーソル移動イベントの処理 if(id==CHARTEVENT_MOUSE_MOVE) { //--- 左マウスボタンの座標と状態 m_x =(int)lparam; m_y =(int)dparam; m_left_button_state =(bool)int(sparam); //--- 呼び出しカウンタの値を格納する m_call_counter=::GetTickCount(); //--- カーソル位置を取得する if(!::ChartXYToTimePrice(0,m_x,m_y,m_subwin,m_time,m_level)) return; //--- 相対的なY座標を取得する if(m_subwin>0) m_y=m_y-m_chart.SubwindowY(m_subwin); } }
ライブラリエンジンのタイマ( CWndEvents クラス)には、マウスカーソルが500ミリ秒以上動かされていない場合はチャートを再描画しないという条件が含まれている必要があります。コントロールの早送りオプションが500 ミリ秒の間しか動作しないことを避けるにはマウスの左ボタンをその時点で解除する必要があります。
//+------------------------------------------------------------------+ //| タイマー | //+------------------------------------------------------------------+ void CWndEvents::OnTimerEvent(void) { //--- マウスカーソルが静止していて(呼び出しの差が500ミリ秒を超える)マウスの左ボタンが離された場合は終了する if(m_mouse.GapBetweenCalls()>500 && !m_mouse.LeftButtonState()) return; //--- 配列が空の場合は終了する if(CWndContainer::WindowsTotal()<1) return; //--- すべての要素のイベントをタイマーで確認する CheckElementsEventsTimer(); //--- チャートを再描画する m_chart.Redraw(); }
問題解決です。マウスカーソルが移動するたびに再描画を無効にしても、タイマー内の16ミリ秒の間隔は再描画には十分なので、コントロールを使用してフォームを移動する品質には影響しません。問題は最も単純な方法で解決されましたが、これが唯一の方法ではありません。CPU消費をより効率的に減らすためには他の方法やオプションもあるので、ライブラリコードの最適化については、シリーズの次回の記事でさらに詳しく説明します。
ツリービューとファイルナビゲータコントロールの最適化
多数の要素を含むツリービュー( CTreeView )の初期化には非常に時間がかかることが判明しました。これは、このリストタイプを使用するファイルナビゲータ( CFileNavigator )でも発生しました。この問題を解決するには、要素を配列に追加するときに :: ArrayResize() 関数の3番目のパラメータとして配列のリザーブ(予備)サイズを指定する必要があります。
:: ArrayResize()関数リファレンスからの引用:
…
頻繁なメモリ割り当てでは、物理的なメモリ割り当ての数を減らすために、予備を設定する3番目のパラメータを使用することをお勧めします。以降のArrayResizeの呼び出しは、メモリの物理的な再割り当てにつながることはなく、リザーブされたメモリ内で配列の最初の次元のサイズだけを変更します。3番目のパラメータは、物理的なメモリの割り当て中にのみ使用されることに注意してください。
…
比較のために、ツリービューでの配列の異なる予備サイズの値での検証結果を以下に示します。テストのファイル数は15000を超えています。
図5 予備サイズの値を持つ配列を作成するためのテストの結果
ツリービューの配列の予約サイズを10000 に設定します。CTreeViewおよびCFileNavigator クラスに適切な変更が加えられました。
ファイルナビゲータのフォルダとファイルの新しいアイコン
ファイルナビゲータ( CFileNavigator </ b0>クラス)に、 Windows 10オペレーティングシステムのファイルナビゲータで使用されているものに似たフォルダやファイルの新しいアイコンを追加しました。それらの洗練されたデザインは、開発中のライブラリのグラフィカルインターフェイスに適していますが、必要に応じてカスタムバージョンも使用できます。
図6。ファイルナビゲータのフォルダとファイルの新しいアイコン
これらの画像は本稿末尾にあります。<
おわりに
開発の現段階では、以下の図に示されたような結果が得られるはずです。
図6。開発の現段階でのライブラリの構造
次のグラフィカルインターフェイスの記事ではEasy And Fastライブラリの開発を続けます。ライブラリは、MQLアプリケーションの開発に必要な追加のコントロールとともに拡張されます。既存のコントロールは改善されて新機能で補足されます。
下記ではEasy And Fastライブラリの4番目のバージョン(ビルド4)をダウンロードすることができます。興味のある方は、 記事のコメントやプライベートメッセージを使ってタスク解決法を提案なさることによってこのプロジェクトのより迅速な開発に貢献なされます。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2763




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