グラフィカルインタフェースX: Easy And Fast (簡単で手早い)ライブラリの更新(ビルド3)
コンテンツ
はじめに
シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的を詳細に考察されます。第一部の記事へのリンクの完全なリストは各章の末尾でみられます。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
本稿ではEasy And Fastライブラリの次のバージョン(バージョン3)を紹介します。特定の欠陥を修正して、新しい機能を追加しました。詳細は本稿で後ほどお話しします。
このバージョンから始まって、ライブラリはMetaTrader 5プラットフォーム専用に開発されます。これはMetaTrader 4の特定の基本的なアーキテクチャーの違いと制限によるものです。しかしながら、以前のプラットフォームバージョンでのライブラリサポートの緊急な必要がある場合はフリーランスサービスで著者または他の開発者宛のリクエストを出していただければ、喜んでサポートします。
更新について
1. 以前の記事でのMQLアプリケーションは、これまで、指標サブウィンドウでグラフィカルインターフェイスを実装する方法を示しました。もちろん、このアプローチは単純なアプリケーションのためには十分です。しかし、「エキスパート」タイプのプログラムのサブウインドウ内でグラフィカルインターフェースを作成する能力があっても良いでしょう。そうすれば、完全にメインチャートウィンドウから分離した、本格的なトレーディングパネルの作成が可能になります。このモードでの作業は容易になります。価格チャートやチャート上の任意の重要なデータは常に、アプリケーションのグラフィカルインターフェースの中で雑然とせずに、表示されたままになります。Easy And Fastライブラリでのこの概念の実装については後ほど説明されます。
タスクは、MQLアプリケーションのメインファイルにプレースホルダ指標を持つリソース(SubWindow.ex5)をインクルードすることによって「サブウィンドウでのエキスパート」モードを有効にすることでした。プレースホルダは、系列計算をしない単なるサブウインドウです。しかし、指標がMQLメソッドを使用してリソースとして含まれている場合かを知る方法は今のところないので、暫定的にEXPERT_IN_SUBWINDOW定数識別子を持つディレクティブをDefines.mqhに加えます。指定されたディレクトリで使用できない指標のハンドルが取得されようとしたときに発生するエラーのロギングの排除が必要です。
デフォルト値はfalseで、モードが無効になっていてグラフィカルインターフェースがメインチャートウィンドウに作成されることが意味されます(以下のコードを参照)。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //--- 「サブウインドウでのエキスパート」モード #define EXPERT_IN_SUBWINDOW false ...
サブウィンドウでグラフィカルインターフェースを作成する場合には、値をtrueにしてプレースホルダー指標を持つリソースをMQLアプリケーションのメインファイルにインクルードします。
//+------------------------------------------------------------------+ //| TestLibrary01.mq5 | //| Copyright 2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2016, MetaQuotes Software Corp." #property link "http://www.mql5.com" //--- 「サブウインドウでのエキスパート」モードの指標のインクルード #resource \\Indicators\\SubWindow.ex5 ...
次にSubWindow.ex5指標がどのようにEAと結びつけられているかを考察します。SubWindow.ex5指標の完全なコードは下に見られます。プログラムプロパティは、指標がチャートのサブウィンドウであり、バッファ及びシリーズ数及び垂直スケールの最大値と最小値がゼロであることを指定します。
指標とエキスパートはメッセージを交換します。ところが、指標は、エキスパートでどちらのグラフィカルインターフェースプロットモードが選択されているかを「知りません」。開発者がサブウインドウが固定された高さだけでなくピクセル単位の高さの値を持たせることを決定した場合は、メッセージを渡す必要があります。メッセージの作成にはあと一つのON_SUBWINDOW_CHANGE_HEIGHTイベント識別子が必要です。この識別子は 、グラフィカルインタフェースを作成するためにこのライブラリを使用するアプリケーションで利用可能となるようにDefines.mqh 内にも配置する必要があります。
EAがどんな場合にサブウィンドウの高さを変更するためのイベントを生成する必要があるかを理解するために:指標の初期化が成功した後、 ::OnCalculate()関数の最初の自動呼び出し中( prev_calculated 引数はゼロ)、EAにメッセージが渡されます。メッセージが SubWindow.ex5指標から来ていることを明確に決定するために、ON_SUBWINDOW_CHANGE_HEIGHTイベント識別子に加えて、プログラム名が文字列パラメータ(sparam)として送られます。メッセージはCWindowクラスのハンドラで追跡されます。条件が満たされた場合には、指標には同じ (1) 識別子(ON_SUBWINDOW_CHANGE_HEIGHT)、(2) イベントのlongパラメータ(lparam)での高さの値、及び (3) エキスパート名を持つ応答が送信されます。受信されたメッセージは::OnChartEvent()関数で処理されます。名前の確認後には、サブウインドウの高さが渡された値に応じて設定されます.。
//+------------------------------------------------------------------+ //| SubWindow.mq5 | //| Copyright 2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2016, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property indicator_separate_window #property indicator_plots 0 #property indicator_buffers 0 #property indicator_minimum 0.0 #property indicator_maximum 0.0 //--- プログラム名 #define PROGRAM_NAME ::MQLInfoString(MQL_PROGRAM_NAME) //--- エキスパートサブウィンドウの高さ変更イベントの識別子 #define ON_SUBWINDOW_CHANGE_HEIGHT (25) //+------------------------------------------------------------------+ //| カスタムインディケータ初期化関数 | //+------------------------------------------------------------------+ int OnInit(void) { //--- 指標の短縮名 ::IndicatorSetString(INDICATOR_SHORTNAME,PROGRAM_NAME); //--- 初期化が完成 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 初期化解除 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| カスタムインディケータ反復処理関数 | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- 初期化が成功した場合 if(prev_calculated<1) //--- メッセージをエキスパートに送信してサブウィンドウサイズを取得する ::EventChartCustom(0,ON_SUBWINDOW_CHANGE_HEIGHT,0,0.0,PROGRAM_NAME); u//--- return(rates_total); } //+------------------------------------------------------------------+ //| ChartEvent関数 | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- エキスパートサブウィンドウの高さ変更イベントの処理 if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT) { //--- エキスパート名からのメッセージのみを受け取る if(sparam==PROGRAM_NAME) return; //--- サブウィンドウの高さを変更する ::IndicatorSetInteger(INDICATOR_HEIGHT,(int)lparam); } } //+------------------------------------------------------------------+
下記のように、CWindowクラスのイベントハンドラにはコードのブロックが追加されるべきです。到着したON_SUBWINDOW_CHANGE_HEIGHT識別子を持つイベントは複数の条件を満たさなければなりません。下記の場合にはプログラムはメソッドを終了します。
- 指標へのメッセージがエキスパートによって送信された
- このプログラムはエキスパートでない
- エキスパートサブウィンドウの高さを固定するモードが設定されていない
//+------------------------------------------------------------------+ //| チャートイベントハンドラ | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- エキスパートサブウィンドウの高さ変更イベントの処理 if(id==CHARTEVENT_CUSTOM+ON_SUBWINDOW_CHANGE_HEIGHT) { //--- メッセージがエキスパートからの場合には終了する if(sparam==PROGRAM_NAME) return; //--- このプログラムがエキスパートでない場合は終了する if(CElement::ProgramType()!=PROGRAM_EXPERT) return; //--- エキスパートサブウィンドウの高さを固定するモードが設定されていない場合は終了する if(!m_height_subwindow_mode) return; //--- サブウィンドウの高さを計算して変更する m_subwindow_height=(m_is_minimized)?m_caption_height+3 : m_bg_full_height+3; ChangeSubwindowHeight(m_subwindow_height); return; } }
これらのすべての条件が満たされた場合、サブウィンドウの高さはウィンドウの現在の状態(最大化/最小化)を考慮して計算されます。 その後CWindow::ChangeSubwindowHeight()メソッドが呼び出されます。このメソッドもわずかに修正されています(以下のコードを参照)。目的は、プログラムのタイプが「エキスパート」の場合にSubWindow.ex5指標へのメッセージを生成することです。
//+------------------------------------------------------------------+ //| インディケータサブウィンドウの縦幅を変更する | //+------------------------------------------------------------------+ void CWindow::ChangeSubwindowHeight(const int height) { //--- グラフィカルインターフェースがサブウインドウにない場合、またはプログラムが「スクリプト」タイプである場合 if(CElement::m_subwin<=0 || CElement::m_program_type==PROGRAM_SCRIPT) return; //--- サブウィンドウの高さの変更が必要な場合 if(height>0) { //--- プログラムが指標タイプの場合 if(CElement::m_program_type==PROGRAM_INDICATOR) { if(!::IndicatorSetInteger(INDICATOR_HEIGHT,height)) ::Print(__FUNCTION__," > Failed to change the height of indicator subwindow!Error code: ",::GetLastError()); } //--- プログラムが「エキスパート」タイプの場合 else { //--- メッセージをSubWindow.ex5指標に送信してウィンドウサイズが変更されなければいけないと伝える ::EventChartCustom(m_chart_id,ON_SUBWINDOW_CHANGE_HEIGHT,(long)height,0,PROGRAM_NAME); } } }
「エキスパート」タイプのMQLアプリケーションのグラフィカルインターフェースが配置されているサブウィンドウの番号を決定、確認して調整するために、ライブラリのエンジン(CWndEventsクラス)にもまた追加が必要です。「サブウインドウでのエキスパート」モードのコードはCWndEvents::DetermineSubwindow()メソッドに追加されます。下記はメソッドの短縮版です。ブロックへのエントリは、プログラムのタイプが「エキスパート」であることを条件に行われます。次に「サブウインドウでのエキスパート」モードが有効になっているかどうかの確認が続きます。有効な場合はSubWindow.ex5指標のハンドルを取得します。これが成功した場合は、初めにチャートウィンドウの現在のサブウィンドウの数が決定されます。取得された値はSubWindow.ex5指標サブウィンドウ番号を定義します。これは::ChartIndicatorAdd()関数で設定されたものです。スbウィンドウの設定が成功した場合は (1) エキスパートサブウィンドウの番号、 (2) 現在のサブウィンドウの数、及び (3) SubWindow.ex5指標の短縮名が格納されます。
//+------------------------------------------------------------------+ //| イベント処理のクラス | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { protected: //--- エキスパートサブウィンドウのハンドル int m_subwindow_handle; //--- エキスパートサブウィンドウの名前 string m_subwindow_shortname; //--- エキスパートサブウィンドウを設定されたあとのチャートのサブウィンドウの数 int m_subwindows_total; }; //+------------------------------------------------------------------+ //| サブウィンドウ番号の検出 | //+------------------------------------------------------------------+ void CWndEvents::DetermineSubwindow(void) { //--- このプログラムが「スクリプト」の場合は終了する //--- 最後のエラーをリセットする //--- プログラムが「エキスパート」タイプの場合 if(PROGRAM_TYPE==PROGRAM_EXPERT) { //---エキスパートののグラフィカルインターフェイスがメインウィンドウに必要とされる場合は終了する if(!EXPERT_IN_SUBWINDOW) return; //--- プレースホルダ指標(空のサブウィンドウ)のハンドルを取得する m_subwindow_handle=iCustom(Symbol(),Period(),"::Indicators\\SubWindow.ex5"); //--- そのような指標が存在しない場合は、ログにエラーを報告する if(m_subwindow_handle==INVALID_HANDLE) ::Print(__FUNCTION__," > Error getting the indicator handle in the directory ::Indicators\\SubWindow.ex5 !"); //--- ハンドルが取得されたならば指標が存在し、リソースとしてアプリケーションに含まれる // よってアプリケーションのグラフィカルインターフェースはサブウィンドウに配置される else { //--- チャートのサブウィンドウの数を取得する int subwindows_total=(int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL); //--- エキスパートのグラフィカルインターフェースのサブウィンドウを設定する if(::ChartIndicatorAdd(m_chart_id,subwindows_total,m_subwindow_handle)) { //--- サブウィンドウ番号と現在のチャートのサブウィンドウの数を格納する m_subwin =subwindows_total; m_subwindows_total =subwindows_total+1; //--- エキスパートサブウィンドウの短縮名を取得し格納する m_subwindow_shortname=::ChartIndicatorName(m_chart_id,m_subwin,0); } //--- サブウィンドウが設定されなかった場合 else ::Print(__FUNCTION__," > Error setting the expert subwindow!Error code: ",::GetLastError()); } u//--- return; } //--- 指標ウィンドウを識別する //--- 番号の識別が失敗したら終了する //--- これがチャートのメインウィンドウでない場合 ... }
チャートサブウィンドウでのグラフィカルインターフェースを正しく動作させるために、他の指標を追加/削除するときにエキスパートサブウィンドウの数が調整されることの確認も必要です。さらに、ユーザーがエキスパートサブウィンドウを削除した場合、エキスパートも削除されて「ツールボックス」ウィンドウの「エキスパート」タブにチャートからの除去の理由を示すログメッセージを残すようにします。このためにCWndEvents::CheckExpertSubwindowNumber()メソッドが実装されています。このメソッドは、プログラムが「エキスパート」タイプであることを条件に実行されます。この条件が満たされると、チャートウィンドウでのサブウィンドウの数が計算されます。サブウィンドウ数が変わっていないことが判明した場合、プログラムはメソッドを終了します。
この場合、エキスパートサブウィンドウをループで見つけることが必要で、見つかったらそのサブウィンドウの数が変更されたかどうかを確認します。サブウィンドウの数は、別のサブウィンドウに存在した指標の追加や削除によって変更されている可能性があります。サブウィンドウの数が変更された場合、その数を保存してメインウィンドウのすべてのコントロールでその値を更新することが必要です。
サブウィンドウが見つからない場合、理由は一つです。それは削除されています。この場合エキスパートもまたチャートから削除されます。
class CWndEvents : public CWndContainer { private: //--- エキスパートサブウィンドウ番号を確認して更新する void CheckExpertSubwindowNumber(void); }; //+------------------------------------------------------------------+ //| エキスパートサブウィンドウ番号を確認して更新す | //+------------------------------------------------------------------+ void CWndEvents::CheckExpertSubwindowNumber(void) { //--- このプログラムがエキスパートでない場合は終了する if(PROGRAM_TYPE!=PROGRAM_EXPERT) return; //--- チャートのサブウィンドウの数を取得する int subwindows_total=(int)::ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL); //--- サブウィンドウの数と指標の数が変更されていない場合は終了する if(subwindows_total==m_subwindows_total) return; //--- 現在のサブウィンドウの数を格納する m_subwindows_total=subwindows_total; //--- エキスパートサブウィンドウの存在を確認するため bool is_subwindow=false; //--- エキスパートサブウィンドウを見つける for(int sw=0; sw<subwindows_total; sw++) { //--- 見つかったらループを中断する if(is_subwindow) break; //--- このウィンドウ/サブウィンドウ内の指標の数 int indicators_total=::ChartIndicatorsTotal(m_chart_id,sw); //--- ウィンドウの全ての指標で反復する for(int i=0; i<indicators_total; i++) { //--- 指標の短縮名を取得する string indicator_name=::ChartIndicatorName(m_chart_id,sw,i); //--- これがエキスパートサブウィンドウでない場合は次に移る if(indicator_name!=m_subwindow_shortname) continue; //--- エキスパートにサブウィンドウがあると印をつける is_subwindow=true; //--- サブウィンドウの数が変更された場合 // 新しい数をメインフォームのすべてのコントロールに格納することが必要 if(sw!=m_subwin) { //--- サブウィンドウの数を格納する m_subwin=sw; //--- インターフェースのメインフォームのすべてのコントロールに格納する int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) m_wnd[0].m_elements[e].SubwindowNumber(m_subwin); } u//--- break; } } //--- エキスパートサブウィンドウが見つからなかった場合はエキスパートを削除する if(!is_subwindow) { ::Print(__FUNCTION__," > Deleting expert subwindow causes the expert to be removed!"); //--- チャートからEAを削除する ::ExpertRemove(); } }
2. ライブラリの以前のバージョンでは、フォームの幅の自動変更を切り替える機能が導入されました。高さのために同様の機能を追加してみましょう。幅と高さの変更は別々のイベントとして処理されるため、フォームサイズ変更イベントに使われるON_WINDOW_CHANGE_SIZE識別子はこのタスクには適していません。したがって、以下のコードが示すように、Defines.mqhファイルはON_WINDOW_CHANGE_SIZEの代わりに2つの別々の識別子を持ちます。対応する識別子の置換は、他のライブラリのファイルでも行われています。
#define ON_WINDOW_CHANGE_XSIZE (3) // X軸に沿ったウィンドウサイズの変更 #define ON_WINDOW_CHANGE_YSIZE (4) // Y軸に沿ったウィンドウサイズの変更
フォームの高さ変更のためにCWindow::ChangeWindowHeight()メソッドが追加されます。フォームサイズの変更後にこのメソッドが呼び出されると、その最後に実行されるアクションに関するメッセージが生成されます。
//+------------------------------------------------------------------+ //| コントロールフォーム作成クラス | //+------------------------------------------------------------------+ class CWindow : public CElement { public: //--- サイズの管理 void ChangeWindowHeight(const int height); }; //+------------------------------------------------------------------+ //| ウィンドウの高さを変更する | //+------------------------------------------------------------------+ void CWindow::ChangeWindowHeight(const int height) { //--- 高さが変更していない場合は終了する if(height==m_bg.YSize()) return; //--- ウィンドウが最小化されている場合は終了する if(m_is_minimized) return; //--- 背景の高さを更新する CElement::YSize(height); m_bg.YSize(height); m_bg.Y_Size(height); m_bg_full_height=height; //--- ウィンドウサイズが変更されたというメッセージ ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElement::Id(),0,""); }
高さの自動変更には、MQLアプリケーションを開発するユーザーはCElement::AutoYResizeMode()メソッドを使って対応するモードを設定するべきです。
//+------------------------------------------------------------------+ //| 基本コントロールクラス | //+------------------------------------------------------------------+ class CElement { protected: //--- コントロールの自動サイズ変更モード bool m_auto_yresize_mode; u//--- public: //--- (1) コントロールの高さの自動変更モード bool AutoYResizeMode(void) const { return(m_auto_yresize_mode); } void AutoYResizeMode(const bool flag) { m_auto_yresize_mode=flag; } };
さて、フォームの自動サイズ変更モードが有効になっていてチャートウィンドウサイズが変更された場合、フォームのイベントハンドラがCHARTEVENT_CHART_CHANGEイベントに基づいてフォームのサイズを調整します これは、指標サブウィンドウとチャートウィンドウの両方で動作します。モードはいずれか、または両方を同時に有効にすることが可能です。
//--- チャートプロパティ変更イベント if(id==CHARTEVENT_CHART_CHANGE) { //--- ボタンが離された場合 if(m_clamping_area_mouse==NOT_PRESSED) { //--- チャートウィンドウサイズの取得 SetWindowProperties(); //--- 座標の調整 UpdateWindowXY(m_x,m_y); } //--- モードが有効な場合は幅を変える if(CElement::AutoXResizeMode()) ChangeWindowWidth(m_chart.WidthInPixels()-2); //--- モードが有効な場合は高さを変える if(CElement::AutoYResizeMode()) ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3); u//--- return; }
3. 高さの自動変更モードはライブラリのすべてのコントロールにも適応します。しかし、現バージョンでは、これは、以下に記載されているコントロールでのみ動作します。
- CTabs – シンプルなタブ
- CIconTabs – 画像付きのタブ
- CCanvasTable – レンダーテーブル
- CLineGraph – 線チャート
以前のCElementクラスへのコントロールの幅の変更のためのCElement::ChangeWidthByRightWindowSide() バーチャルメソッドの追加と同様、対応する高さの自動サイズ変更メソッドを追加しましょう。さらに、以前に追加された、幅の変更時にフォームの右端からのオフセットを設定するメソッド同様、フォームの下端からのオフセットを設定するメソッドを作成してみましょう。
class CElement { protected: //--- コントロール幅/高さの自動変更モードでのフォームの右/下端からのオフセット int m_auto_xresize_right_offset; int m_auto_yresize_bottom_offset; u//--- public: //--- フォームの下端からのオフセットの取得/設定 int AutoYResizeBottomOffset(void) const { return(m_auto_yresize_bottom_offset); } void AutoYResizeBottomOffset(const int offset) { m_auto_yresize_bottom_offset=offset; } u//--- public: //--- ウィンドウの下端で高さを変更する virtual void ChangeHeightByBottomWindowSide(void) {} };
すべてのコントロールは、ユニークな機能とモードを考慮した ChangeWidthByRightWindowSide() メソッドの独自の実装を持っています。例として、以下は、このメソッドのCCanvasTableクラスでのコードを示しています。
//+------------------------------------------------------------------+ //| ウィンドウの下端で高さを変更する | //+------------------------------------------------------------------+ void CCanvasTable::ChangeHeightByBottomWindowSide(void) { //--- フォームの下部でのアンカーモードが有効になっている場合は終了する if(m_anchor_bottom_window_side) return; //--- 座標 int y=0; //--- サイズ int x_size=(m_auto_xresize_mode)?m_wnd.X2()-m_area.X()-m_auto_xresize_right_offset : m_x_size; int y_size=m_wnd.Y2()-m_area.Y()-m_auto_yresize_bottom_offset; //--- サイズが指定されたもの未満の場合は終了する if(y_size<60) return; //--- テーブルの背景の新しいサイズを設定する ChangeMainSize(x_size,y_size); //--- テーブルサイズを計算する CalculateTableSize(); //--- スクロールバーの存在を確認する bool is_scrollh=!(m_table_visible_x_size>=m_table_x_size); bool is_scrollv=!(m_table_visible_y_size>=m_table_y_size); //--- スクロールバーの存在に相対したオフセット int offset=(is_scrollh || (!is_scrollh && !is_scrollv))?0 : 2; //--- 水平スクロールバーの座標を計算して設定する y=m_area.Y2()-m_scrollh.ScrollWidth(); m_scrollh.YDistance(y); //--- 水平スクロールバーを新しいサイズで初期化する m_scrollh.Reinit(m_table_x_size,m_table_visible_x_size); //--- 垂直スクロールバーの高さを計算して変更する m_scrollv.Reinit(m_table_y_size,m_table_visible_y_size-offset); m_scrollv.ChangeYSize((is_scrollh)?m_table_visible_y_size+2 : m_table_visible_y_size); //--- 垂直スクロールバーが必要でない場合 if(!is_scrollv) { //--- 垂直スクロールバーを隠す m_scrollv.Hide(); //--- 水平スクロールバーの幅を変える m_scrollh.ChangeXSize(m_area.XSize()); } else { //--- 垂直スクロールバーを表示する if(CElement::IsVisible()) m_scrollv.Show(); //--- 横スクロールバーの幅を計算して変更する m_scrollh.ChangeXSize(m_area.XSize()-m_scrollv.ScrollWidth()+1); } //--- 水平スクロールバーの可視性を管理する if(CElement::IsVisible()) { if(!is_scrollh) m_scrollh.Hide(); else m_scrollh.Show(); } //--- テーブルのサイズを変える ChangeTableSize(m_table_x_size,m_table_y_size,m_table_visible_x_size,m_table_visible_y_size-offset); //--- テーブルを描く DrawTable(); //--- オブジェクトの位置を更新する Moving(m_wnd.X(),m_wnd.Y()); }
このすべてが機能するためには、一定の追加や変更がCWndEventsクラスになされるべきです。サイズ変更イベント(幅と高さ)はユニークな識別子で生成されるため、その処理のためには2つの別々のメソッドが必要とされます。
//+------------------------------------------------------------------+ //| イベント処理のクラス | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { private: //--- ウィンドウサイズの変更を処理する bool OnWindowChangeXSize(void); bool OnWindowChangeYSize(void); }; //+------------------------------------------------------------------+ //| ON_WINDOW_CHANGE_XSIZEイベント | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowChangeXSize(void) { //--- シグナルが「コントロールのサイズを変える」ための場合 if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_XSIZE) return(false); //--- アクティブウィンドウのインデックス int awi=m_active_window_index; //--- ウィンドウの識別子が一致した場合 if(m_lparam!=m_windows[awi].Id()) return(true); //--- フォーム以外のすべてのコントロールの幅を変える int elements_total=CWndContainer::ElementsTotal(awi); for(int e=0; e<elements_total; e++) { //--- これがウィンドウの場合は次に移る if(m_wnd[awi].m_elements[e].ClassName()=="CWindow") continue; //--- モードが有効な場合は幅を調整する if(m_wnd[awi].m_elements[e].AutoXResizeMode()) m_wnd[awi].m_elements[e].ChangeWidthByRightWindowSide(); } u//--- return(true); } //+------------------------------------------------------------------+ //| ON_WINDOW_CHANGE_YSIZEイベント | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowChangeYSize(void) { //--- シグナルが「コントロールのサイズを変える」ための場合 if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_CHANGE_YSIZE) return(false); //--- アクティブウィンドウのインデックス int awi=m_active_window_index; //--- ウィンドウの識別子が一致した場合 if(m_lparam!=m_windows[awi].Id()) return(true); //--- フォーム以外のすべてのコントロールの幅を変える int elements_total=CWndContainer::ElementsTotal(awi); for(int e=0; e<elements_total; e++) { //--- これがウィンドウの場合は次に移る if(m_wnd[awi].m_elements[e].ClassName()=="CWindow") continue; //--- モードが有効な場合は高さを調整する if(m_wnd[awi].m_elements[e].AutoYResizeMode()) m_wnd[awi].m_elements[e].ChangeHeightByBottomWindowSide(); } u//--- return(true); }
カスタムイベントの処理にはCWndEvents::OnWindowChangeXSize()及びCWndEvents::OnWindowChangeYSize()メソッドが共通のCWndEvents::ChartEventCustom()メソッドで呼び出されます。 下記はメソッドの短縮版です。
//+------------------------------------------------------------------+ //| CHARTEVENT_CUSTOM イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- フォーム最小化のシグナルの場合 //--- フォーム最大化のシグナルの場合 //--- シグナルがX軸に沿ってコントロールのサイズを変えるための場合 if(OnWindowChangeXSize()) return; //--- シグナルがY軸に沿ってコントロールのサイズを変えるための場合 if(OnWindowChangeYSize()) return; //--- 開始メニュー項目の下のコンテキストメニューを非表示にするシグナルの場合 //--- コンテキストメニューをすべて非表示にするシグナルの場合 //--- ダイアログウィンドウを開くシグナルの場合 //--- ダイアログウィンドウを閉じるシグナルの場合 //--- 指定されたフォームの要素の色をすべてゼロにするシグナルの場合 //--- マウスの左クリックの優先順位をリセットするシグナルの場合 //--- マウスの左クリックの優先順位を復元するシグナルの場合 }
チャートウィンドウのサイズを変更する際、その中で指定されたフォームとコントロールのサイズ変更モードが有効になっている場合はそれらも自動的にサイズ変更されます。
下のスクリーンショットは、メインチャートウィンドウで作成されたMQLアプリケーションのグラフィカルインタフェースの例を示します。アプリケーションウィンドウ(CWindow)では、チャートウィンドウの幅と高さの自動サイズ調整モードが設定されています。メインメニュー(CMenuBar)とステータスバー(CStatusBar)コントロールでは、 MQLアプリケーションウィンドウの幅と高さの自動サイズ調整モードが設定されています。タブ(CTabs)とレンダーテーブル(CCanvasTable)コントロールでは、フォームサイズへの幅と高さの自動サイズ調整モードが設定され、MQLアプリケーションの下端からのオフセットが指定されています。
図1 端末ウィンドウの最小サイズ。自動サイズ変更モードが有効にされたMQLアプリケーションのグラフィカルインターフェース。
端末ウィンドウのサイズが変更される際、前述のモードが有効になっている場合にはMQLアプリケーションのグラフィカルインターフェースサイズのもそれに応じて変更されます。
図2 端末ウィンドウのサイズが変更されるると、MQLアプリケーションのグラフィカルインターフェースも変更されます。
4. 多くの場合、可変フォームサイズを持つグラフィカルインターフェースを設計する場合、コントロールは、アプリケーションウィンドウの右側または底部に位置することが必要かもしれません。以前は、単に、コントロールが取り付けられたフォームの左上隅の点に相対したコントロールの座標を指定するオプションがありました。ここでは、フォームに相対してコントロールを位置決めするためのオプションが4つできました。
- 左上
- 右上
- 右下
- 左下
アイディアを実装するために、コントロールの基本クラス(CElement)に、フォームの右と下でのコントロールの位置づけモードの設定/取得のための2つのメソッドを追加しましょう。
class CElement { protected: //--- ウィンドウの右と下にあるコントロールのアンカーポイント bool m_anchor_right_window_side; bool m_anchor_bottom_window_side; u//--- public: //--- ウィンドウの(1) 右端、及び(2) 下端でのコントロールアンカーポイントのモード(取得/設定) bool AnchorRightWindowSide(void) const { return(m_anchor_right_window_side); } void AnchorRightWindowSide(const bool flag) { m_anchor_right_window_side=flag; } bool AnchorBottomWindowSide(void) const { return(m_anchor_bottom_window_side); } void AnchorBottomWindowSide(const bool flag) { m_anchor_bottom_window_side=flag; } };
すべてがこれらのモードに応じて動作するためには、ライブラリコントロールのすべてのクラスが変更されました。ここでは例として、チェックボックス(CCheckBox)コントロールのテキストラベルを作成するメソッドコードをあげます。グラフィックオブジェクトの座標とオフセットを計算する行にご注意ください。
//+------------------------------------------------------------------+ //| チェックボックスのテキストラベルを作成する | //+------------------------------------------------------------------+ bool CCheckBox::CreateLabel(void) { //--- オブジェクト名の形成 string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id(); //--- 座標 int x =(m_anchor_right_window_side)?m_x-m_label_x_gap : m_x+m_label_x_gap; int y =(m_anchor_bottom_window_side)?m_y-m_label_y_gap : m_y+m_label_y_gap; //--- 状態に応じたテキストの色 color label_color=(m_check_button_state) ?m_label_color : m_label_color_off; //--- オブジェクトを設定する if(!m_label.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- プロパティを設定する m_label.Description(m_label_text); m_label.Font(FONT); m_label.FontSize(FONT_SIZE); m_label.Color(label_color); m_label.Corner(m_corner); m_label.Anchor(m_anchor); m_label.Selectable(false); m_label.Z_Order(m_zorder); m_label.Tooltip("\n"); //--- 端からのマージン m_label.XGap((m_anchor_right_window_side)?x : x-m_wnd.X()); m_label.YGap((m_anchor_bottom_window_side)?y : y-m_wnd.Y()); //--- グラデーション配列を初期化する CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array); //--- オブジェクトポインタを格納する CElement::AddToArray(m_label); return(true); }
ライブラリのすべてのコントロールのMoving()メソッドはコントロール位置づけモードを考慮するようになります。例として下記のコードはCCheckBox::Moving()メソッドを示します。
//+------------------------------------------------------------------+ //| コントロールの移動 | //+------------------------------------------------------------------+ void CCheckBox::Moving(const int x,const int y) { //--- 要素が隠れている場合は終了する if(!CElement::IsVisible()) return; //--- 右に固定されている場合 if(m_anchor_right_window_side) { //--- 要素フィールドでの座標の格納 CElement::X(m_wnd.X2()-XGap()); //--- オブジェクトフィールドでの座標の格納 m_area.X(m_wnd.X2()-m_area.XGap()); m_check.X(m_wnd.X2()-m_check.XGap()); m_label.X(m_wnd.X2()-m_label.XGap()); } else { //--- オブジェクトフィールドでの座標の格納 CElement::X(x+XGap()); //--- オブジェクトフィールドでの座標の格納 m_area.X(x+m_area.XGap()); m_check.X(x+m_check.XGap()); m_label.X(x+m_label.XGap()); } //--- 下に固定されている場合 if(m_anchor_bottom_window_side) { //--- 要素フィールドでの座標の格納 CElement::Y(m_wnd.Y2()-YGap()); //--- オブジェクトフィールドでの座標の格納 m_area.Y(m_wnd.Y2()-m_area.YGap()); m_check.Y(m_wnd.Y2()-m_check.YGap()); m_label.Y(m_wnd.Y2()-m_label.YGap()); } else { //--- オブジェクトフィールドでの座標の格納 CElement::Y(y+YGap()); //--- オブジェクトフィールドでの座標の格納 m_area.Y(y+m_area.YGap()); m_check.Y(y+m_check.YGap()); m_label.Y(y+m_label.YGap()); } //--- グラフィックオブジェクトの座標の更新 m_area.X_Distance(m_area.X()); m_area.Y_Distance(m_area.Y()); m_check.X_Distance(m_check.X()); m_check.Y_Distance(m_check.Y()); m_label.X_Distance(m_label.X()); m_label.Y_Distance(m_label.Y()); }
明確化のために、以下の図は位置決めモードとコントロールの自動サイズ変更のすべての可能な組み合わせを概略的に示します。それは抽象的な例で、フォーム(9列目の「Result」)は黒い太枠を持つ 400 x 400画素の白い長方形であらわされ、コントロールは200 x 200画素の灰色の長方形です。コントロールの相対座標とサイズも組み合わせごとに表示されます。ダッシュは(自動サイズ変更モードが有効になっている場合)組み合わせでサイズの設定が必須ではないことを示します。
図3 コントロールの位置と自動サイズ変更と組み合わせたさまざまなオプションの一覧表
5. CTabs及びCIconTabsクラスでのタブの切り替えイベントを生成するためのON_CLICK_TABイベント識別子を追加しました。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ ... #define ON_CLICK_TAB (27) // タブの切り替え
ON_CLICK_TAB識別子を持つイベントはカスタムクラスのハンドラで追跡可能になりました。これはグラフィカルインターフェイスの外観の管理のためにさらに多くの機能を提供します。
6. 座標の強制的更新が全てのコントロールのShow()メソッドに追加しました。例として、下記はCSimpleButtonクラスでのこのメソッドを示します(黄色で強調表示された行)。
//+------------------------------------------------------------------+ //| ボタンを表示する | //+------------------------------------------------------------------+ void CSimpleButton::Show(void) { //--- 要素がすでに表示されている場合は終了する if(CElement::IsVisible()) return; //--- オブジェクトを表示する m_button.Timeframes(OBJ_ALL_PERIODS); //--- 表示の状態 CElement::IsVisible(true); //--- オブジェクトの位置を更新する Moving(m_wnd.X(),m_wnd.Y()); }
場合によっては、MQLアプリケーションのグラフィカルインターフェースの積極的な活用によってそのコントロールの一部が誤って配置される可能性があります。これでこの問題は解決されました。
7. ボタンへのポインタを取得するためのメソッドがCWindowクラスに追加されました。
//+------------------------------------------------------------------+ //| コントロールフォーム作成クラス | //+------------------------------------------------------------------+ class CWindow : public CElement { public: //--- フォームボタンへのポインタを返す CBmpLabel *GetCloseButtonPointer(void) { return(::GetPointer(m_button_close)); } CBmpLabel *GetRollUpButtonPointer(void) { return(::GetPointer(m_button_unroll)); } CBmpLabel *GetUnrollButtonPointer(void) { return(::GetPointer(m_button_rollup)); } CBmpLabel *GetTooltipButtonPointer(void) { return(::GetPointer(m_button_tooltip)); } };
したがって、ライブラリのユーザーは、グラフィカルインターフェースが作成された後、MQLアプリケーションのライフサイクルの任意の時点で、これらのグラフィックオブジェクトのプロパティを変更すうことができます。例えば、ボタンのツールヒントが以前に事前に定義されたデフォルト値であっても、その後独立して設定することができます。 これは、多言語MQLアプリケーションを作成するときに便利かもしれません。
8. CTableクラスの修正。コントロール作成のメインメソッドにチェックを追加しました(以下のコードを参照)。目に見える部分は、もはや共通である必要はありません。テーブルのプロパティを設定するときにユーザーが間違いを犯した場合は、値が自動的に修正されますようになりました。
//+------------------------------------------------------------------+ //| エディットボックステーブルを作成する //+------------------------------------------------------------------+ bool CTable::CreateTable(const long chart_id,const int subwin,const int x,const int y) { //--- フォームポインタがなければ終了する if(!CElement::CheckWindowPointer(::CheckPointer(m_wnd))) return(false); //--- 目に見える部分は、もはや共通である必要はない m_visible_rows_total =(m_visible_rows_total>m_rows_total)?m_rows_total : m_visible_rows_total; m_visible_columns_total =(m_visible_columns_total>m_columns_total)?m_columns_total : m_visible_columns_total; //--- 変数の初期化 m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =subwin; m_x =x; m_y =y; m_x_size =(m_x_size<1 || m_auto_xresize_mode)?(m_anchor_right_window_side)?m_wnd.X2()-m_x+m_x_size-(m_wnd.X2()-m_x)+1-m_auto_xresize_right_offset : m_wnd.X2()-m_x-m_auto_xresize_right_offset : m_x_size; m_y_size =m_row_y_size*m_visible_rows_total-(m_visible_rows_total-1)+2; //--- 端からのマージン CElement::XGap((m_anchor_right_window_side)?m_x : m_x-m_wnd.X()); CElement::YGap((m_anchor_bottom_window_side)?m_y : m_y-m_wnd.Y()); //--- テーブルを作成する if(!CreateArea()) return(false); if(!CreateCells()) return(false); if(!CreateScrollV()) return(false); if(!CreateScrollH()) return(false); //--- ダイアログウィンドウか最小化されたウィンドウの場合は要素を非表示にする if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); u//--- return(true); }
更新版を検証するためのアプリケーション
検証として、本稿に記載されていることのすべてを実証するために、前回の記事のMQLアプリケーションを少し変更してみましょう。「SubWindow」指標のサブウィンドウにEAを作成します。グラフィカルインターフェースのメインウィンドウのサイズは自動的にサブウィンドウサイズに調整されます。サブウインドウの高さは手動で変更できます。これには CWindow::RollUpSubwindowMode()メソッドが呼ばれる時に「false」が渡されるべきです(下記のコードで緑で強調表示)。
以下のコードは、アプリケーションのグラフィカルインターフェースのメインウィンドウ内のボタンのプロパティを管理するアクセス権を取得する方法を示します。この場合、例はツールヒントの設定を示します。
//+------------------------------------------------------------------+ //| コントロールのフォームを作成する | //+------------------------------------------------------------------+ bool CProgram::CreateWindow(const string caption_text) { //--- ウィンドウ配列にウィンドウポインタを追加する CWndContainer::AddWindow(m_window); //--- 座標 int x=1; int y=1; //--- プロパティ m_window.Movable(false); m_window.UseRollButton(); m_window.AutoXResizeMode(true); m_window.AutoYResizeMode(true); m_window.RollUpSubwindowMode(false,false); //--- フォームの作成 if(!m_window.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- ツールヒントを設定する m_window.GetCloseButtonPointer().Tooltip("Close program"); m_window.GetUnrollButtonPointer().Tooltip("Unroll"); m_window.GetRollUpButtonPointer().Tooltip("Roll up"); return(true); }
1番目のタブではすべてのコントロールをフォームの右側に固定します(下のスクリーンショットを参照)。フォームの幅が変更された場合、それらは、その右端から同じ距離に留まります。
図4 1番目のタブのコントロールはフォームの右側に固定されている
以下は「シンプルボタン」(CSimpleButton)を作成するためのコードの例を示しています。コントロールを右側に固定するにはAnchorRightWindowSide()メソッドを呼び出してtrueの値を渡すので十分です。
//+------------------------------------------------------------------+ //| シンプルボタン1を作成する | //+------------------------------------------------------------------+ bool CProgram::CreateSimpleButton1(const int x_gap,const int y_gap,string button_text) { //--- ウィンドウポインタを格納する m_simple_button1.WindowPointer(m_window); //--- 1番目のタブに取り付ける m_tabs.AddToElementsArray(0,m_simple_button1); //--- 座標 int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- 作成前にプロパティを設定する m_simple_button1.ButtonXSize(140); m_simple_button1.BackColor(C'255,140,140'); m_simple_button1.BackColorHover(C'255,180,180'); m_simple_button1.BackColorPressed(C'255,120,120'); m_simple_button1.AnchorRightWindowSide(true); //--- ボタンの作成 if(!m_simple_button1.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y)) return(false); //--- 要素ポインタをベースに追加する CWndContainer::AddToElementsArray(0,m_simple_button1); return(true); }
2番目のタブにはレンダリングテーブル(CCanvasTable)のみが割り当てられ、サブウィンドウ幅と高さが変更するとフォームのサイズに調整されます。
図5 フォームのサイズに調整する折れ線レンダリングテーブル
すべてが意図されたように動作するにはAutoXResizeMode()及びAutoYResizeMode()メソッドを使って縦横サイズの自動変更モードを有効化する必要があります。AutoXResizeRightOffset() 及びAutoYResizeBottomOffset()メソッドを使えば、コントロールの右/下隅からのフォームの右/下隅のオフセットを調整することができます。この場合右隅からのオフセットは1画素、下端からのオフセットは25画素に設定されます(以下のコードを参照)。
//+------------------------------------------------------------------+ //| レンダーテーブルを作成する | //+------------------------------------------------------------------+ bool CProgram::CreateCanvasTable(const int x_gap,const int y_gap) { #define COLUMNS3_TOTAL 15 #define ROWS3_TOTAL 30 //--- フォームへのポインタを格納する m_canvas_table.WindowPointer(m_window); //--- 2番目のタブに取り付ける m_tabs.AddToElementsArray(1,m_canvas_table); //--- 座標 int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- 列の幅の配列 int width[COLUMNS3_TOTAL]; ::ArrayInitialize(width,70); width[0]=100; width[1]=90; //--- 列のテキスト整列の配列 ENUM_ALIGN_MODE align[COLUMNS3_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[0]=ALIGN_RIGHT; align[1]=ALIGN_RIGHT; align[2]=ALIGN_LEFT; //--- 作成前にプロパティを設定する m_canvas_table.XSize(400); m_canvas_table.YSize(200); m_canvas_table.TableSize(COLUMNS3_TOTAL,ROWS3_TOTAL); m_canvas_table.TextAlign(align); m_canvas_table.ColumnsWidth(width); m_canvas_table.GridColor(clrLightGray); m_canvas_table.AutoXResizeMode(true); m_canvas_table.AutoYResizeMode(true); m_canvas_table.AutoXResizeRightOffset(1); m_canvas_table.AutoYResizeBottomOffset(25); //--- テーブルにデータを書き入れる for(int c=0; c<COLUMNS3_TOTAL; c++) for(int r=0; r<ROWS3_TOTAL; r++) m_canvas_table.SetValue(c,r,string(c)+":"+string(r)); //--- コントロールを作成する if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y)) return(false); //--- オブジェクトをオブジェクトグループの共通配列に追加する CWndContainer::AddToElementsArray(0,m_canvas_table); return(true); }
3番目のタブには折れ線グラフ(CLineGraph)が配置され、これもフォームサイズに自動的に調整されます。
図6。フォームのサイズに調整する折れ線グラフコントロール
4、5番目のタブは、他の多くのライブラリコントロールの右端への固定を示します
図7 4番目のタブのコントロール
図8 5番目のタブのコントロール
本稿で紹介されたテストアプリケーションをさらに研究するためには、以下のリンクを使用してダウンロードすることができます。
おわりに
開発の現段階では、以下の図に示されたような結果が得られるはずです。
図9 開発の現段階でのライブラリの構造
次のバージョンでは、ライブラリはより多くのコントロールで拡張されます。これらはMQLアプリケーションを開発するうえで必要となるかもしれません。既存のコントロールは向上し、新機能で補足されます。
以下からEasy And Fastの第3バージョンをダウンロードすることができます。それらのファイルの資料の使用についてご質問がある場合は、当シリーズで詳細をご参照になるか、本稿へのコメント欄でご質問ください。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2723
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索