グラフィカルインタフェース I: グラフィカルインタフェースの動画化(チャプター 3)
コンテンツ
はじめに
本稿はグラフィカルインターフェイスに関するシリーズの続きです。シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的を詳細に考慮します。第一部の記事へのリンクの完全なリストは各章の終わりにあります。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
前回の記事では、コントロールフォームクラスの開発に着手しました。本稿では、チャート領域にフォームを移動するためのクラスメソッドを書き入れによってそれを続行します。その後、このインターフェイスコンポーネントをライブラリコアに統合します。また、カーソルが上をホバリングしたときにコントロールフォームの色が変わることを保証します。
グラフィカルインターフェースの管理
コントロールフォームは、チャートに装着されます。今の時点ではそれは全く反応しません。私たちの目標は、フォームとコントロールがユーザーのアクションに反応するようにすることです。これを実現するためには、チャート上のカーソルの位置を追跡する必要があります。任意の時点でプログラムはカーソルの座標を「把握する」必要があります。これをMQLアプリケーションでデフォルトチャートパラメータで実装することは不可能です。カーソルの位置とマウスボタン操作の追跡を有効にする必要があります。今後、開発の過程で、いくつかのチャートプロパティが使用されなければなりません。なので、標準ライブラリのCChartクラスを我々のライブラリのWndEvents.mqhファイルに含んでクラス本体でインスタンスを作成します。
//+------------------------------------------------------------------+ //| WndEvents.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Defines.mqh" #include "WndContainer.mqh" #include <Charts\Chart.mqh> //+------------------------------------------------------------------+ //| イベント処理のクラス | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { protected: CChart m_chart; };
クラスコンストラクタでは、識別子を取得し現在のチャートにてオブジェクトを添付し、カーソル位置の追跡を可能にします。デストラクタでは、チャートからオブジェクトを切り離します(下記のコードで緑色で強調表示)。それ以外の場合は、プログラムがチャートから削除されたときにチャート自体が閉じられます。
//+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CWndEvents::CWndEvents(void) : m_chart_id(0), m_subwin(0), m_indicator_name(""), m_program_name(PROGRAM_NAME) { //--- 現在のチャートのIDを取得する m_chart.Attach(); //--- マウスイベント追跡を有効にする m_chart.EventMouseMove(true); } //+------------------------------------------------------------------+ //| デストラクタ | //+------------------------------------------------------------------+ CWndEvents::~CWndEvents(void) { //--- チャートから切り離す m_chart.Detach(); }
前述したように、各コントロールのクラスは独自のイベントハンドラを有します。ここで、CWindowクラスでフォーム管理に必要なメソッドをいくつか作成します。これらの全てはCWindow::OnEvent()メソッドで呼び出されます。CWindowクラスに追加される機能を決めましょう。
1. カーソルが置かれているウィンドウを定義する必要があります。これは、チャートはメインチャートとインディケータサブウィンドウとの複数の部分で構成することができ、MQLアプリケーションはメインウィンドウ以外のウィンドウに位置するインディケータでありえるからです。
2. MQLアプリケーションがインディケータでチャートのメインウィンドウに配置されていない場合は、Y座標を調整する必要があります。
3. マウスの左ボタンの状態と押されたポイントがチェックされなければなりません。下記の4つの状態があります。
- NOT_PRESSED — ボタンが押されなかった
- PRESSED_OUTSIDE — ボタンがフォーム領域外で押された
- PRESSED_INSIDE_WINDOW — ボタンがフォーム領域内で押された
- PRESSED_INSIDE_HEADER — ボタンがヘッダー領域内で押された
ENUM_WMOUSE_STATE列挙体がEnums.mqhファイルに追加されます。
//+------------------------------------------------------------------+ //| Enums.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| フォームに対したマウスの左ボタンの状態を示す列挙体 | //+------------------------------------------------------------------+ enum ENUM_WMOUSE_STATE { NOT_PRESSED =0, PRESSED_OUTSIDE =1, PRESSED_INSIDE_WINDOW =2, PRESSED_INSIDE_HEADER =3 };
4. カーソルがフォームの領域内またはインターフェイスコンポーネントの領域内にある場合は、チャートのスクロールや取引レベルの管理を無効にする必要があります。
5. カーソルがヘッダの捕捉領域内でマウスの左ボタンが押されると、プログラムは、フォームの座標を更新するモードに入ります。
6. 座標が更新されると、チャート領域を離れるチェックが行われ、対応する調整が行われます。この様なチェックは (1)CChartクラスオブジェクト と (2)チャートサイズ取得のための変数とメソッド を必要とします。
7. 更新された座標系に相対して全てのフォームオブジェクトを移動するメソッドも必要とされます。これにはCWindow::Moving() virtualメソッドがすでに宣言されています。それを実装します。
8. ヘッダー領域のカーソルを識別するため、また変数をゼロにするための補助的なメソッドもいくつか必要となります。
9. マウスフォーカスの追跡は、ウィンドウを構成する全てのオブジェクトで動作します。したがって、そのようなチェックが行われるメソッドを作成してみましょう。
10. チャート上に移動する能力を有するグラフィカルインターフェースは必ずしも必要ではありません。このような理由から、この機能を有効/無効にすることを可能にするメソッドを追加します。
フォームを移動するための機能
上述の機能を実装してみましょう。クラス本体では、必要なメソッドといくつかの変数の宣言を追加します。CChartクラスのファイルを含み、インスタンスを作成します(黄色で強調表示)。
//+------------------------------------------------------------------+ //| Window.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include <Charts\Chart.mqh> //+------------------------------------------------------------------+ //| コントロールフォーム作成クラス | //+------------------------------------------------------------------+ class CWindow : public CElement { private: CChart m_chart; //--- チャート上でウィンドウを移動する可能性 bool m_movable; //--- チャートサイズ int m_chart_width; int m_chart_height; //--- 変位と関連した変数 int m_prev_x; // クリックの際の固定されたXポイント int m_prev_y; // クリックの際の固定されたYポイント int m_size_fixing_x; // X座標から固定Xポイントの距離 int m_size_fixing_y; // Y座標から固定Yポイントの距離 //--- クリックされたポイントを考慮したマウスボタンの状態 ENUM_WMOUSE_STATE m_clamping_area_mouse; //--- public: //--- ウィンドウを移動する可能性 bool Movable(void) const { return(m_movable); } void Movable(const bool flag) { m_movable=flag; } //--- チャートサイズの取得 void SetWindowProperties(void); //--- Y座標を相対的なものに変換する void YToRelative(const int y); //--- ヘッダー領域のカーソルチェック bool CursorInsideCaption(const int x,const int y); //--- 変数のゼロ化 void ZeroPanelVariables(void); //--- マウスフォーカスの確認 void CheckMouseFocus(const int x,const int y,const int subwin); //--- マウスの左ボタンの状態の確認 void CheckMouseButtonState(const int x,const int y,const string state); //--- チャートモードの設定 void SetChartState(const int subwindow_number); //--- フォーム座標の更新 void UpdateWindowXY(const int x,const int y); };
SetWindowProperties()、YToRelative()、CursorInsideCaption()及び ZeroPanelVariables()メソッドのコードは比較的単純で説明を要しません。サブウィンドウ番号(m_subwin)がCChartオブジェクトのメソッドに渡されていることのみは留意されるべきでしょう。これは、MQLプログラムが含まれているサブウインドウの番号でなければなりません。
//+------------------------------------------------------------------+ //| チャートサイズの取得 | //+------------------------------------------------------------------+ void CWindow::SetWindowProperties(void) { //--- チャートウィンドウの幅と高さの取得 m_chart_width =m_chart.WidthInPixels(); m_chart_height =m_chart.HeightInPixels(m_subwin); } //+------------------------------------------------------------------+ //| Y座標を相対的なものに変換する | //+------------------------------------------------------------------+ int CWindow::YToRelative(const int y) { //--- チャートの上部からインディケータサブウィンドウへの距離を取得する int chart_y_distance=m_chart.SubwindowY(m_subwin); //--- Y座標を相対的なものに変換する return(y-chart_y_distance); } //+------------------------------------------------------------------+ //| ウィンドウタイトル領域内のカーソル位置の確認 | //+------------------------------------------------------------------+ bool CWindow::CursorInsideCaption(const int x,const int y) { return(x>m_x && x<X2()-m_right_limit && y>m_y && y<m_caption_bg.Y2()); } //+------------------------------------------------------------------+ //| 変位とマウスの左ボタンに関する変数の | //| ゼロ化 | //+------------------------------------------------------------------+ void CWindow::ZeroPanelVariables(void) { m_prev_x =0; m_prev_y =0; m_size_fixing_x =0; m_size_fixing_y =0; m_clamping_area_mouse =NOT_PRESSED; }
フォームに対するマウスの左ボタンの状態はCWindow::CheckMouseButtonState()メソッドで確認されます。そのため、このメソッドにはカーソルの座標とチャートのイベントモデルの文字列パラメータが渡されます。文字列パラメータは、 CHARTEVENT_MOUSE_MOVEイベントの処理時のマウスの左ボタンの状態を示しています。パラメータはマウスボタンが離された場合に「1」で、マウスボタンが押された場合に「0」の値を持つことを意味します。
ボタンが離されると、全ての補助変数がゼロにされ、メソッドが終了します。ボタンが押されると、チャート領域内のどこでこれが起こったかを定めるチェックがCWindow::CheckMouseButtonState()メソッドで行われます。ボタンが既に記録された状態を有していることが判明した場合、つまり、それがチャート領域で押された場合、プログラムはメソッドを終了します(return)。
//+------------------------------------------------------------------+ //| マウスの左ボタンの状態を確認する | //+------------------------------------------------------------------+ void CWindow::CheckMouseButtonState(const int x,const int y,const string state) { //--- ボタンが離された場合 if(state=="0") { //--- 変数をゼロにする ZeroPanelVariables(); return; } //--- ボタンが押された場合 if(state=="1") { //--- 状態が記録されていたら終了する if(m_clamping_area_mouse!=NOT_PRESSED) return; //--- パネル領域外 if(!CElement::MouseFocus()) m_clamping_area_mouse=PRESSED_OUTSIDE; //--- パネル領域内 else { //--- ヘッダーの領域内の場合 if(CursorInsideCaption(x,y)) { m_clamping_area_mouse=PRESSED_INSIDE_HEADER; return; } //--- ウィンドウの領域内の場合 m_clamping_area_mouse=PRESSED_INSIDE_WINDOW; } } }
オブジェクトの境界を定義するためのメソッドは、プリミティブオブジェクトの全てのクラスのObjects.mqhに含まれています。これらのメソッドは、マウスのフォーカスがオブジェクトに合っているかを迅速かつ容易に認識します。フォームまた全てのフォームオブジェクトのフォーカスはCWindowクラスのCheckMouseFocus()メソッドでチェックできます。このメソッドには、カーソルの現在の座標とカーソルが置かれているサブウィンドウの番号が渡されます。カーソルを含むサブウインドウの数を取得する方法は、後に実証されます。
//+------------------------------------------------------------------+ //| マウスフォーカスの確認 | //+------------------------------------------------------------------+ void CWindow::CheckMouseFocus(const int x,const int y,const int subwin) { //--- カーソルがプログラムウィンドウ領域内にある場合 if(subwin==m_subwin) { //--- 現在フォーム変位モードにない場合 if(m_clamping_area_mouse!=PRESSED_INSIDE_HEADER) { //--- カーソル位置の確認 CElement::MouseFocus(x>m_x && x<X2() && y>m_y && y<Y2()); //--- m_button_rollup.MouseFocus(x>m_button_rollup.X() && x<m_button_rollup.X2() && y>m_button_rollup.Y() && y<m_button_rollup.Y2()); m_button_close.MouseFocus(x>m_button_close.X() && x<m_button_close.X2() && y>m_button_close.Y() && y<m_button_close.Y2()); m_button_unroll.MouseFocus(x>m_button_unroll.X() && x<m_button_unroll.X2() && y>m_button_unroll.Y() && y<m_button_unroll.Y2()); } } else { CElement::MouseFocus(false); } }
フォームのフォーカスのチェックとフォームに対応したマウスボタンの状態の結果によって、チャートのスクロールと取引レベル管理が有効にされるべきか無効にされるべきかがわかります。カーソルがフォーム上にあってこれらのチャートプロパティが無効になっていない場合は、チャートのスクロールはフォーム変位を伴い、取引レベル管理は必要ありませんがフォームの下部で行われます。
//+------------------------------------------------------------------+ //| チャートの状態の設定 | //+------------------------------------------------------------------+ void CWindow::SetChartState(const int subwindow_number) { //--- (カーソルがパネル領域内でマウスボタンが離された)または // マウスボタンがフォーム領域内かヘッダー領域内で押された if((CElement::MouseFocus() && m_clamping_area_mouse==NOT_PRESSED) || m_clamping_area_mouse==PRESSED_INSIDE_WINDOW || m_clamping_area_mouse==PRESSED_INSIDE_HEADER) { //--- スクロールと取引レベル管理を無効にする m_chart.MouseScroll(false); m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,false); } //--- カーソルがウィンドウ領域外の場合管理を有効にする else { m_chart.MouseScroll(true); m_chart.SetInteger(CHART_DRAG_TRADE_LEVELS,true); } }
ウィンドウ座標の更新はCWindow::UpdateWindowXY()メソッドで行われます。はじめに、ウィンドウモードがチェックされます。フォームが固定モードである場合、座標更新の意味がないのでプログラムはメソッドを終了します。次に、マウスボタンが押された場合、現在座標とフォームのエッジ点からカーソルまでの距離が格納され、チャート領域を離れるための制限が算出されてチェックされ、必要に応じて座標の調整が行われます。以下のコードをご覧ください。
//+------------------------------------------------------------------+ //| ウィンドウ座標の更新 | //+------------------------------------------------------------------+ void CWindow::UpdateWindowXY(const int x,const int y) { //--- フォーム固定モードが設定されている場合 if(!m_movable) return; //--- int new_x_point =0; // 新規のX座標 int new_y_point =0; // 新規のY座標 //--- リミット int limit_top =0; int limit_left =0; int limit_bottom =0; int limit_right =0; //--- マウスボタンが押された場合 if((bool)m_clamping_area_mouse) { //--- カーソルの現在のXY座標を格納する if(m_prev_y==0 || m_prev_x==0) { m_prev_y=y; m_prev_x=x; } //--- フォームのエッジポイントからカーソルの距離を格納する if(m_size_fixing_y==0 || m_size_fixing_x==0) { m_size_fixing_y=m_y-m_prev_y; m_size_fixing_x=m_x-m_prev_x; } } //--- リミットを設定する limit_top =y-::fabs(m_size_fixing_y); limit_left =x-::fabs(m_size_fixing_x); limit_bottom =m_y+m_caption_height; limit_right =m_x+m_x_size; //--- 上下左右のチャートの境界が超えられていない場合 if(limit_bottom<m_chart_height && limit_top>=0 && limit_right<m_chart_width && limit_left>=0) { new_y_point =y+m_size_fixing_y; new_x_point =x+m_size_fixing_x; } //--- チャート境界が超えられた場合 else { if(limit_bottom>m_chart_height) // > 下 { new_y_point =m_chart_height-m_caption_height; new_x_point =x+m_size_fixing_x; } if(limit_top<0) // > 上 { new_y_point =0; new_x_point =x+m_size_fixing_x; } if(limit_right>m_chart_width) // > 右 { new_x_point =m_chart_width-m_x_size; new_y_point =y+m_size_fixing_y; } if(limit_left<0) // > 左 { new_x_point =0; new_y_point =y+m_size_fixing_y; } } //--- 変位があった場合座標を更新する if(new_x_point>0 || new_y_point>0) { //--- フォームの座標を調査委する m_x =(new_x_point<=0)?1 : new_x_point; m_y =(new_y_point<=0)?1 : new_y_point; //--- if(new_x_point>0) m_x=(m_x>m_chart_width-m_x_size-1) ?m_chart_width-m_x_size-1 : m_x; if(new_y_point>0) m_y=(m_y>m_chart_height-m_caption_height-1) ?m_chart_height-m_caption_height-2 : m_y; //--- 固定点をゼロにする m_prev_x=0; m_prev_y=0; } }
CWindowクラスのOnEvent()チャートイベントハンドラは、マウス動作イベント(CHARTEVENT_MOUSE_MOVE)の処理の際にサブウィンドウ番号をChartXYToTimePrice()関数で取得します。この関数がtrueを返した場合、依然に作成したCWindow::YToRelative()メソッドを使って相対的なY座標を受け取り、上記のメソッドの全てを順番に回ります。
//+------------------------------------------------------------------+ //| チャートイベントハンドラ | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { int x =(int)lparam; // X軸の座標 int y =(int)dparam; // Y軸の座標 int subwin =WRONG_VALUE; // カーソルが位置するウィンドウの番号 datetime time =NULL; // X座標に対応する時刻 double level =0.0; // Y座標に対応するレベル(価格) int rel_y =0; // 相対的なY座標の識別 //--- カーソル位置を取得する if(!::ChartXYToTimePrice(m_chart_id,x,y,subwin,time,level)) return; //--- 相対的なY座標を取得する rel_y=YToRelative(y); //--- マウスボタンの状態を確認して格納する CheckMouseButtonState(x,rel_y,sparam); //--- マウスフォーカスの確認 CheckMouseFocus(x,rel_y,subwin); //--- チャートの状態を設定する SetChartState(subwin); //--- 管理がウィンドウに委任されている場合はその場所を特定する if(m_clamping_area_mouse==PRESSED_INSIDE_HEADER) { //--- ウィンドウ座標の更新 UpdateWindowXY(x,rel_y); } return; } }
動作をテストするにはCWindow::Moving()メソッドを実装するのみです。このメソッドはUpdateWindowXY()メソッドの直後にクラス内部のイベントハンドラで使用されるように見えるかもしれませんが、これはそうではありません。実装を今他のコントロールがフォームに存在しないうちにすれば、優れた結果が得られます。このロジックに続いて、他のコントロールクラスでも同じことをしなければならないでしょう。結果的に、数多くのコントロールを持ったフォームに移動される際、ある程度の遅延を持って全てのコントロールがフォームと一緒に移動されます。これは望ましくありません。
その理由は、インタフェースコントロールを移動する全てのメソッドが内部ハンドラに実装されている場合、全てのコントロールが種々の条件とチェックによって分割されるからです。それにより遅延が発生します。全てのコントロールが同期移動するためにはMoving()メソッドの間に他の操作が混ざってはなりません。これはCWndContainerクラスから派生し、よってそこに格納された全てのオブジェクトポインタにアクセスできるCWndEventsクラスで実装できます。
CWindow::Moving()メソッドの内容は2つの部分に分けられます。フォームを構成する全てのオブジェクトの座標は初めに格納されからチャート上で更新されます。同じように、各コントロールのMoving()メソッドが後に実装されます。
//+------------------------------------------------------------------+ //| ウィンドウの移動 | //+------------------------------------------------------------------+ void CWindow::Moving(const int x,const int y) { //--- 変数への座標の格納 m_bg.X(x); m_bg.Y(y); m_caption_bg.X(x); m_caption_bg.Y(y); m_icon.X(x+m_icon.XGap()); m_icon.Y(y+m_icon.YGap()); m_label.X(x+m_label.XGap()); m_label.Y(y+m_label.YGap()); m_button_close.X(x+m_button_close.XGap()); m_button_close.Y(y+m_button_close.YGap()); m_button_unroll.X(x+m_button_unroll.XGap()); m_button_unroll.Y(y+m_button_unroll.YGap()); m_button_rollup.X(x+m_button_rollup.XGap()); m_button_rollup.Y(y+m_button_rollup.YGap()); m_button_tooltip.X(x+m_button_tooltip.XGap()); m_button_tooltip.Y(y+m_button_tooltip.YGap()); //--- グラフィックオブジェクトの座標の更新 m_bg.X_Distance(m_bg.X()); m_bg.Y_Distance(m_bg.Y()); m_caption_bg.X_Distance(m_caption_bg.X()); m_caption_bg.Y_Distance(m_caption_bg.Y()); m_icon.X_Distance(m_icon.X()); m_icon.Y_Distance(m_icon.Y()); m_label.X_Distance(m_label.X()); m_label.Y_Distance(m_label.Y()); m_button_close.X_Distance(m_button_close.X()); m_button_close.Y_Distance(m_button_close.Y()); m_button_unroll.X_Distance(m_button_unroll.X()); m_button_unroll.Y_Distance(m_button_unroll.Y()); m_button_rollup.X_Distance(m_button_rollup.X()); m_button_rollup.Y_Distance(m_button_rollup.Y()); m_button_tooltip.X_Distance(m_button_tooltip.X()); m_button_tooltip.Y_Distance(m_button_tooltip.Y()); }
チャート上でのフォームの動きのテスト
イベントは全て、すでに外側だけ作成されたCWndEvents::ChartEvent()メソッドで処理されます。初めに、ウィンドウポインタ配列のサイズのチェックが行われます。それが空の場合、作業を続ける理由がないのでプログラムは作業を中止します(return)。そして、チャートイベントパラメータを参照するクラスフィールドが初期化されます。今のところ、(1)各コントロールのハンドラでのイベントチェックをするCWndEvents::CheckElementsEvents() 及び (2)マウスカーソルの追跡をするCWndEvents::ChartEventMouseMove() の2つのイベント処理関数を用意します。
よってCWndEvents::ChartEvent()メソッドには現在下記のコードがあるべきです。
//+------------------------------------------------------------------+ //| プログラムイベント処理 | //+------------------------------------------------------------------+ void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- 配列が空の場合終了する if(CWndContainer::WindowsTotal()<1) return; //--- イベントパラメータフィールドの初期化 InitChartEventsParams(id,lparam,dparam,sparam); //--- インターフェースコントロールイベントの確認 CheckElementsEvents(); //--- マウス動作のイベント ChartEventMouseMove(); }
現在空のCWndEvents::CheckElementsEvents()及びCWndEvents::ChartEventMouseMove()メソッドを実装します。インタフェースコントロールのチェックイベントが1サイクルで実行されます。そこではベースにあるコントロールのOnEvent()ハンドラを順番に呼び出します。テストファイルには現在ウィンドウが1つだけあるので、CWndEventsでは一時的にウィンドウ配列のゼロインデックスを使用します。これは、マルチウィンドウモードの開発に移行するときに変更する必要があります。
//+------------------------------------------------------------------+ //| コントロールイベントの確認 | //+------------------------------------------------------------------+ void CWndEvents::CheckElementsEvents(void) { int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) m_wnd[0].m_elements[e].OnEvent(m_id,m_lparam,m_dparam,m_sparam); }
チャート上のコントロールの移動が行われるCWndEventsクラスにメソッドを作成しましょう。それをMovingWindow()と名付けます。初めは、このメソッドではフォームが移動されてからそれに接続されている全てのコントロールが移動されます。
class CWndEvents : public CWndContainer { private: //--- ウィンドウの移動 void MovingWindow(void); }; //+------------------------------------------------------------------+ //| ウィンドウの移動 | //+------------------------------------------------------------------+ void CWndEvents::MovingWindow(void) { //--- ウィンドウの移動 int x=m_windows[0].X(); int y=m_windows[0].Y(); m_windows[0].Moving(x,y); //--- コントロールの移動 int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) m_wnd[0].m_elements[e].Moving(x,y); }
ここで、下記のようにコードがCWndEvents::ChartEventMouseMove()メソッドに追加できます。チャートオブジェクトの座標が更新された際は毎回チャートを最描写が必要なことを忘れてはなりません。さもないと変更が反映されません。
//+------------------------------------------------------------------+ //| CHARTEVENT MOUSE MOVE イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventMouseMove(void) { //--- カーソル変位イベント以外の場合は終了する if(m_id!=CHARTEVENT_MOUSE_MOVE) return; //--- ウィンドウの移動 MovingWindow(); //--- チャートを再描画する m_chart.Redraw(); }
ライブラリ開発のプロセスはその最終段階にあり、チャート上のウィンドウ移動をテストすることができます。CWindow::OnEvent()メソッドの本体に、データをチャートの左上の隅で表示するコードを追加します。下記がリアルタイムで表示されます。
- カーソル座標
- 相対的なY座標
- フォーム座標
- カーソルが位置するチャートウィンドウの番号
- MQLプログラムが位置するチャートウィンドウの番号
- マウス左ボタンのモード
このコードはテスト後に削除されるべきです。
::Comment("x: ",x,"\n", "y: ",y,"\n", "rel_y: ",rel_y,"\n", "w.x: ",m_x,"\n", "w.y: ",m_y,"\n", "subwin: ",subwin,"\n", "m_subwin: ",m_subwin,"\n", "clamping mode: ",m_clamping_area_mouse);
プロジェクトファイルをコンパイルしてプログラムをチャートに読み込みます。プログラムがカーソルが置かれているチャートウィンドウで正しく追跡されていることを確認するには、メインチャートウィンドウ上レンダリングしない任意のインディケータをロードします。
私は、プログラムがチャート上に読み込まれた後には、ウィンドウの移動が不可能なことをほぼ確信しています。なぜならCWindowクラスのコンストラクタでm_movable変数が、ユーザーによるチャート上のフォームの移動を無効にするようにfalse値で初期化されているからです。これが、MQLアプリケーションの開発者がコードでフォーム移動の必要性を指定しなければならない理由です。
CreateWindow()メソッドのCProgramクラスに、下のコードにある行を追加し、ファイルをコンパイルしてアプリケーションを再びテストしてみてください。
//+------------------------------------------------------------------+ //| コントロールフォームを作成する | //+------------------------------------------------------------------+ bool CProgram::CreateWindow(const string caption_text) { //--- ウィンドウ配列にウィンドウポインタを追加する CWndContainer::AddWindow(m_window); //--- 座標 int x=1; int y=1; //--- プロパティ m_window.Movable(true); m_window.XSize(200); m_window.YSize(200); //--- フォームの作成 if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- return(true); }
これで、フォームの移動を妨げるものは何もないはずです。
図1。チャート上でのフォームの動きのテスト
時には、チャートウィンドウのサイズが変更されなければならないことがあります。そのような場合には、チャートのCHARTEVENT_CHART_CHANGEプロパティの変更イベントが生成されます。現時点では、これは全く追跡されておらず、フォームが部分的または完全にチャートウィンドウの境界を超える状況が起こり得ます。その回避にはCWindow::OnEvent()チャートイベントハンドラでこのタイプのイベントもチェックされる必要があります。
このイベントはチャートのスクロール時にも発生するので、数多い不要なアクションの実行を回避するために、イベント発生時にマウスの左ボタンがクリックされたかのチェックが必要です。マウスの左ボタンが離されている場合、チャートサイズを取得し、チャートウィンドウの境界が超えられた場合には座標を調整します。下記はCWindow::OnEvent()メソッドで他のイベントの処理後に追加されなければならないコードです。
//+------------------------------------------------------------------+ //| チャートイベントハンドラ | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- チャートプロパティ変更イベント if(id==CHARTEVENT_CHART_CHANGE) { //--- ボタンが離された場合 if(m_clamping_area_mouse==NOT_PRESSED) { //--- チャートウィンドウサイズの取得 SetWindowProperties(); //--- 座標の調整 UpdateWindowXY(m_x,m_y); } return; } }
上記の作業の実体的な結果は、コントロールフォームが移動できるということです。その後、ウィンドウを最小化/最大化するためのボタンの機能を埋め込む必要があります。これらのボタンは、エンドユーザがアイコンやデザイン要素だけではないことを理解するように、マウスカーソルの動きに反応しなければなりません。
カーソルが上をホバリングしたときのインターフェイスコンポーネント外観の変更
全てのコントロールの基本クラスであるCElementクラスの実装については既に考慮しました。そのメンバの一つとして、カーソルのホバー時にオブジェクトの色を変更するCElement::ChangeObjectColor()メソッドが作成されました。ここで、それを作業に使用するためのメカニズムを作成します。このような機能の追加には、タイマーが必要です。これは、MQLアプリケーションの初期設定では無効になっています。設定された目標に応じたタイマーを有効にするかどうかの決定はアプリケーション開発者に任されています。
タイマーを有効にするには、MQL言語には異なる頻度を持つEventSetTimer()ちEventSetMillisecondTimer()の2つの関数があります。1番目の関数では、1秒以上の間隔を設定することができます。カーソルのホバー時のコントロールの外観変更には、1秒間隔は長すぎるので、これは目的に沿いません。変更は、そのような遅延なしですぐに起こるべきです。ミリ秒単位で測定された間隔でのタイマー設定をサポートするEventSetMillisecondTimer()関数が使用されます。MQLレフェレンスによれば、この関数を使用して設定できる最小間隔は10から16ミリ秒です。これは我々の計画を実現するのに十分です。
Defines.mqhファイルに、ライブラリに必要なタイマーステップ定数を追加します。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //--- タイマーステップ(ミリ秒) #define TIMER_STEP_MSC (16)
「これはあまりにも頻繁過ぎないか?」や「プログラムがあまりにも多くのリソースを消費しないか?」のように論理的な質問をされるかもしれません。これらの質問は、このアイデアをテストするときに答えられます。
開発中のライブラリの中心であるCWndEventsクラスでタイマーを有効にします。そのためには、コンストラクタとデストラクタのコードに以下を追加します。
//+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CWndEvents::CWndEvents(void) { //--- タイマーを有効にする if(!::MQLInfoInteger(MQL_TESTER)) ::EventSetMillisecondTimer(TIMER_STEP_MSC); } //+------------------------------------------------------------------+ //| デストラクタ | //+------------------------------------------------------------------+ CWndEvents::~CWndEvents(void) { //--- タイマーを削除する ::EventKillTimer(); }
CWndEventsクラスのコンストラクタでは、タイマーはプログラムがストラテジーテスター外の場合のみに有効にされます。現在、我々のライブラリにはグラフィカルオブジェクトの操作に制限があるので、テスターでは動作しません。EventSetMillisecondTimer()関数を使用しても、テスター内のタイマーの最小間隔は1秒です。デストラクタでは、タイマーはEventKillTimer()関数で無効にされます。
各コントロールのクラスでは、独自のOnEventTimer()メソッドが実装され、そのためにCElement クラスには同名のvirtualメソッドがあります。各コントロールの OnEvent()メソッドが呼び出されるCWndEvents::CheckElementsEvents()メソッドと同じように、プログラムが全てのOnEventTimer()メソッドを呼び出すメソッドを作成しなければなりません。それをCheckElementsEventsTimer()と名付けましょう。
CWndEvents::CheckElementsEventsTimer()メソッドの宣言と実装は下記の通りです。
class CWndEvents : public CWndContainer { private: //--- 全てのコントロールのイベントのタイマーでのチェック void CheckElementsEventsTimer(void); }; //+------------------------------------------------------------------+ //| 全てのコントロールのイベントのタイマーでのチェック | //+------------------------------------------------------------------+ void CWndEvents::CheckElementsEventsTimer(void) { int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) m_wnd[0].m_elements[e].OnEventTimer(); }
このメソッドはCWndEvents::OnTimerEvent()メソッド内で呼び出され、 CWndEvents::ChartEvent()メソッドで行われたように、一番初めにウィンドウ配列サイズのチェックを伴うべきです。
//+------------------------------------------------------------------+ //| タイマー | //+------------------------------------------------------------------+ void CWndEvents::OnTimerEvent(void) { //--- 配列が空の場合終了する if(CWndContainer::WindowsTotal()<1) return; //--- 全てのコントロールのイベントのタイマーでのチェック CheckElementsEventsTimer(); //--- チャートを再描画する m_chart.Redraw(); }
MQLアプリケーション開発者がグラフィカルインターフェースを作成するCProgramクラスでは、メインプログラムファイルと関連するCProgram::OnTimerEvent()メソッドで以下のコードに示すように単に基本クラスのメソッドを呼び出します。
//+------------------------------------------------------------------+ //| タイマー | //+------------------------------------------------------------------+ void CProgram::OnTimerEvent(void) { CWndEvents::OnTimerEvent(); }
これによってOnEventTimer()メソッド内にある全てのコントロールのアクションはプログラムタイマーのイベントのストリームで利用できるようになります。
テストにはまだ全てがそろっていません。我々が現在開発しているライブラリにはコントロールフォームのインターフェイスコンポーネントであるCWindowクラスのみが含まれています。その中に必要な機能を作成して、OnEventTimer()メソッドのローカルバージョンに追加します。
色変更の関数はCWindow::ChangeObjectsColor()と名付けられます。下記はCWindowクラスの宣言と実装です。
class CWindow: public CElement { private: //--- フォームオブジェクトの色の変更 void ChangeObjectsColor(void); }; //+------------------------------------------------------------------+ //| オブジェクト上のカーソルのホバリング時のオブジェクトの色の変更 | //+------------------------------------------------------------------+ void CWindow::ChangeObjectsColor(void) { //--- ボタンアイコンの変更 m_button_rollup.State(m_button_rollup.MouseFocus()); m_button_unroll.State(m_button_unroll.MouseFocus()); m_button_close.State(m_button_close.MouseFocus()); //--- ヘッダーの色の変更 CElement::ChangeObjectColor(m_caption_bg.Name(),CElement::MouseFocus(),OBJPROP_BGCOLOR, m_caption_bg_color,m_caption_bg_color_hover,m_caption_color_bg_array); }
コードを見てわかるようにCWindow::ChangeObjectsColor()メソッドは全く複雑でありません。MouseFocus()メソッドは全てのオブジェクト上のカーソルのフォーカスを返し、カーソルがチャート上で移動している際にCWindow::CheckMouseFocus()メソッドでチェックします。以前、ここでのボタンの役割を持つオブジェクトに2つのアイコンがロードされました。それらはCChartObjectBmpLabel::State()メソッドで状態を設定することによって切り替えられます。CElement::ChangeObjectColor()メソッドはオブジェクトの特定な部分の色を扱います。
この場合、ヘッダーの背景の色ははカーソルがウィンドウ領域内にあるときに変化します。CElement::ChangeObjectColor()メソッドが、順次、それぞれのオブジェクト名を最初のパラメータとして指定して呼び出された場合、ヘッダーフレームの色、またはフレームや他のオブジェクトを含んだ背景の色を変更することもできます。
今残された唯一の事はローカルタイマーでこのメソッドを呼び出すことです。
//+------------------------------------------------------------------+ //| タイマー | //+------------------------------------------------------------------+ void CWindow::OnEventTimer(void) { //--- フォームオブジェクトの色の変更 ChangeObjectsColor(); }
プロジェクトファイルをコンパイルしてプログラムをチャートに読み込みます。カーソルが機能が暗示されているフォーム上のオブジェクトの上にあるときに、オブジェクトの色が変化します。
図2。オブジェクトのマウスカーソルへの反応のためのテスト
おわりに
本稿では、チャート上でのフォームの移動を可能にするいくつかの追加的な変更を紹介しました。フォームのコントロールがマウスカーソルの動きに反応するようになりました。次の記事ではCWindowクラスの開発を続けます。クラスは、フォームのコントロールのクリックを通しての管理を可能にするメソッドによって増幅されます。
シリーズの最初の部分の全ての資料はダウンロードしてテストすることができます。これらのファイルに含まれている資料の使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるるか、本稿へのコメント欄でご質問ください。
第一部の記事(チャプター)のリスト:
- グラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)
- グラフィカルインタフェース I: コントロールフォーム(チャプター 2)
- グラフィカルインタフェース I: グラフィカルインタフェースの動画化(チャプター 3)
- グラフィカルインタフェース I: フォームボタンとインターフェイス要素削除のための関数(チャプター 4)
- グラフィカルインタフェース I: 種々のプログラム及びメタトレーダー4ターミナルでのライブラリのテスト(チャプター 5)
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2127
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索