グラフィカルインタフェースIV:マルチウィンドウモードと優先度のシステム(チャプター2)
コンテンツ
はじめに
シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的を詳細に考察します。記事へのリンクの完全なリストは各章の末尾でみられます。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
前の章では、グラフィカルインターフェースの情報要素のステータスバーとツールヒントについてお花s時しました。この章では、MQLアプリケーションでのマルチウィンドウインタフェースの作成の可能性をもたらすライブラリの実装を拡張します。それに加えて、グラフィカルオブジェクト上でマウスの左ボタンのクリックのための優先順位のシステムを開発します。これなしでは、コントロールがユーザのアクションに応答しない可能性があります。
マルチウィンドウモード
開発中のライブラリーのグラフィカルインターフェイスのマルチウィンドウモードを考えてみましょう。これまで、ENUM_WINDOW_TYPE列挙はメイン(W_MAIN)およびダイアログ(W_DIALOG)ウィンドウのための2つの識別子を提供しました。使われていたのはシングルウィンドウモードのみでした。いくつかの追加が導入された後、マルチウィンドウモードの有効化は単にベースへの必要な数のコントロールフォームの追加を伴います。
イベント処理メインクラスのCWndEventsで、現在アクティブなウィンドウのインデックスを格納するためのフィールドを作成します。
class CWndEvents : public CWndContainer { protected: //--- アクティブウィンドウのインデックス int m_active_window_index; };
アクティブウィンドウのインデックスがどのように特定されるかを見てみましょう。例えば、ユーザがあるボタンにダイアログウィンドウ(W_DIALOG)を開くタスクを割り当てるとします。ボタンが押されるとON_CLICK_BUTTONカスタムイベントが生成されます。このイベントはカスタムクラスのCProgram::OnEvent()イベントハンドラで追跡できます。これからお見せするフォームのCWindow::Show()メソッドも使われます。ライブラリーの現在の実装では十分ではないので、必要な追加を紹介してします。
カスタムイベントは、ウィンドウが開かれたことを示すCWindow::Show()メソッドから送信されなければならないのでグラフィッカルインターフェースのシステムの更新が必要です。このようなイベントには別の識別子が必要です。それをON_OPEN_DIALOG_BOXと呼んで他のライブラリ識別子が位置するDefines.mqhファイルに加えましょう。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_OPEN_DIALOG_BOX (11) // ダイアログウィンドウを開くイベント
下のコードに示されたようにCWindow::Show()メソッドの最終部に行を追加します。下記はメソッドの短縮版です。イベント開始の明白な同定のために、イベント識別子に加えて要素識別子とプログラム名が送信される必要があります。
//+------------------------------------------------------------------+ //| ウィンドウを表示する | //+------------------------------------------------------------------+ void CWindow::Show(void) { //--- オブジェクトを表示する //--- 表示の状態 //--- フォーカスをゼロにする //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_OPEN_DIALOG_BOX,(long)CElement::Id(),0,m_program_name); }
このイベントはCWndEventsクラスで処理されます。処理のためのメソッドを実装する前にCWindowクラスにあと3つのメソッドを作成する必要があります。そのうち2つは、ダイアログウィンドウが開かれるフォームのインデックスを格納および取得するメソッドで、あと1つはフォームの状態を管理するためのメソッドです。
複数のウィンドウを同時に開くことができるように、1つ前のアクティブウィンドウのインデックスは格納されなければなりません。これが、ダイアログウィンドウを閉じるときにどちらがアクティブ状態に戻される必要があるかを知ることが重要な理由です。
class CWindow : public CElement { private: //--- 1つ前にアクティブだったウィンドウのインデックス int m_prev_active_window_index; //--- public: //--- 1つ前にアクティブだったウィンドウのインデックスの(1)格納と(2)取得 void PrevActiveWindowIndex(const int index) { m_prev_active_window_index=index; } int PrevActiveWindowIndex(void) const { return(m_prev_active_window_index); } };
フォームの状態を管理においては、非アクティブ化されたフォームは異なるヘッダ色を持ち、この色はユーザによって変更することができます。フォームはブロックされるので、要素はマウスカーソルが上にホバーした際に変色しません。それに加え、非アクティブ化の瞬間にカスタムイベントが生成されます。これは、フォームがブロックされていて、焦点とその要素の色をゼロにしなければならないことを伝えます。フォームがブロックされると、要素にフォーカスが追跡されません。ダイアログウィンドウを開く瞬間には、ウィンドウを開いた要素の色はマウスカーソルはまだそれの上にホバーしているようです。
そのようなイベントのためにON_RESET_WINDOW_COLORS識別子がDefines.mqhファイルで作成されます。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_RESET_WINDOW_COLORS (13) // フォーム要素のすべての色をゼロ化する
以下のコードはフォームの状態を管理するメソッドを示します。
class CWindow : public CElement { public: //--- ウィンドウの状態の設定 void State(const bool flag); }; //+------------------------------------------------------------------+ //| ウィンドウの状態の設定 | //+------------------------------------------------------------------+ void CWindow::State(const bool flag) { //--- ウィンドウがブロックされる場合 if(!flag) { //--- 状態を設定する m_is_locked=true; //--- ヘッダの色を設定する m_caption_bg.BackColor(m_caption_bg_color_off); //--- 色をゼロ化するシグナル。リセットは他の要素に対しても行われる。 ::EventChartCustom(m_chart_id,ON_RESET_WINDOW_COLORS,(long)CElement::Id(),0,""); } //--- ウィンドウがブロック解除される場合 else { //--- 状態を設定する m_is_locked=false; //--- ヘッダの色を設定する m_caption_bg.BackColor(m_caption_bg_color); //--- フォーカスをゼロにする CElement::MouseFocus(false); } }
ON_OPEN_DIALOG_BOXイベントの処理に戻りましょう。グラフィカルインターフェースのイベント処理のMaineクラス(CWndEvents)でCWndEvents::OnOpenDialogBox()メソッドを作成します。これは、カスタムイベントを処理する共通メソッドであるCWndEvents::ChartEventCustom() で呼ばれます。
CWndEvents::OnOpenDialogBox() メソッドの初めには、イベント識別子とプログラム名に対しての2つのチェックがあります。それらに合格した場合は、すべてのウィンドウを反復処理してイベントを生成したウィンドウを見つけます。これにはこのメッセージに含まれた要素識別子(lparam)が利用されます。一致する識別子を持っていないフォームは、それらに取り付けられたすべての要素と共にブロックされます。すべてのオブジェクトの優先順位はResetZorders()メソッドの助けを借りてゼロにされ、マウスの左クリックに反応しません。識別子に一致したフォームが見つかった場合、現在アクティブなウィンドウのインデックスを1つ前のアクティブウィンドウの指標として格納します。このフォームをアクティブにし、そのすべてのオブジェクトに対するマウスの左クリックの優先順位を復元します。このウィンドウのインデックスが現在アクティブとして格納します。そして、このフォームのすべての要素を表示可能にしてそれに対するマウスの左クリックの優先順位を復元しますが、すでに表示されているフォーム要素とドロップダウン要素は省略します。
ツールヒントが表示されるときにダイアログウィンドウが開いた場合はツールヒントは閉じられなければなりません。取り付けられているフォームがすでにブロックされているので、それは自動的には閉じられません。このような場合に対応するために、ツールチップのプライベート配列が以前に作成されました。ベースの要素メソッドへのアクセスはイベント処理のメインクラスのCWndEventsで受信されることができます。
class CWndEvents : public CWndContainer { private: //--- ダイアログウィンドウを開く bool OnOpenDialogBox(void); }; //+------------------------------------------------------------------+ //| CHARTEVENT_CUSTOM イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- フォーム最小化のシグナルの場合 //--- フォーム最大化のシグナルの場合 //--- 開始メニュー項目の下のコンテキストメニューを非表示にするシグナルの場合 //--- コンテキストメニューをすべて非表示にするシグナルの場合 //--- ダイアログウィンドウを開くシグナルの場合 if(OnOpenDialogBox()) return; } //+------------------------------------------------------------------+ //| ON_OPEN_DIALOG_BOX event | //+------------------------------------------------------------------+ bool CWndEvents::OnOpenDialogBox(void) { //--- ダイアログウィンドウを開くシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_OPEN_DIALOG_BOX) return(false); //--- メッセージが他のプログラムで送信された場合は終了する if(m_sparam!=m_program_name) return(true); //--- ウィンドウ配列要素を反復処理する int window_total=CWndContainer::WindowsTotal(); for(int w=0; w<window_total; w++) { //--- 識別子が一致した場合 if(m_windows[w].Id()==m_lparam) { //--- ウィンドウのインデックスをウィンドウを開けたフォームに格納する m_windows[w].PrevActiveWindowIndex(m_active_window_index); //--- フォームをアクティブにする m_windows[w].State(true); //--- フォームオブジェクトへの左マウスクリックの優先順位を復元する m_windows[w].SetZorders(); //--- アクティブにされたウィンドウのインデックスを格納する m_active_window_index=w; //--- アクティブにされたウィンドウのすべての要素を表示可能にする int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) { //--- フォームとドロップダウン要素はぬかす if(m_wnd[w].m_elements[e].ClassName()=="CWindow" || m_wnd[w].m_elements[e].IsDropdown()) continue; //--- 要素を表示可能にする m_wnd[w].m_elements[e].Show(); //--- 要素への左マウスクリックの優先順位を復元する m_wnd[w].m_elements[e].SetZorders(); } //--- ツールヒントを非表示にする int tooltips_total=CWndContainer::TooltipsTotal(m_windows[w].PrevActiveWindowIndex()); for(int t=0; t<tooltips_total; t++) m_wnd[m_windows[w].PrevActiveWindowIndex()].m_tooltips[t].FadeOutTooltip(); } //--- 他のフォームはアクティブにされたウィンドウが閉じるまでブロックされる else { //--- フォームをブロックする m_windows[w].State(false); //--- フォームオブジェクトへの左マウスクリックのゼロ優先度 int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) m_wnd[w].m_elements[e].ResetZorders(); } } //--- return(true); }
ここで、本稿ですでに作成されON_RESET_WINDOW_COLORS識別子についてお話します。このイベントを処理するメソッドを書く前に、色のゼロ化の対象となる要素すべての基本クラスとなるCElement にあと1つの標準的な仮想メソッドを追加する必要があります。これをCElement::ResetColors()と呼びましょう。
class CElement { public: //--- 要素の色のゼロ化 virtual void ResetColors(void) {} };
それぞれの要素の特徴を示すResetColors() メソッドは派生クラスで作成されなければなりません。以下のコードはアイコンボタン要素(CIconButton)の例を示します。他のすべての要素のResetColors() メソッドは本稿添付のソースコードファイルで確認可能です。
class CIconButton : public CElement { public: //--- 要素の色のゼロ化 void ResetColors(void); }; //+------------------------------------------------------------------+ //| 色をゼロ化する | //+------------------------------------------------------------------+ void CIconButton::ResetColors(void) { //--- これが、2状態モードであってボタンが押された場合は終了する if(m_two_state && m_button_state) return; //--- 色をゼロ化する m_button.BackColor(m_back_color); //--- フォーカスをゼロにする m_button.MouseFocus(false); CElement::MouseFocus(false); }
よって、基本クラスの仮想メソッドと派生クラスでのそれぞれのバージョンによって、すべての要素の色はライブラリのイベント処理のメインクラス(CWndEvents)から1つのループでゼロ化することができます。
ON_RESET_WINDOW_COLORSイベント処理には CWndEvents::OnResetWindowColors()メソッドを書きます。これは非常にシンプルです。メッセージで受信されたばかりの要素識別子によって非アクティブ化されたフォームを探します。見つかったらそのインデックスを格納します。インデックスが格納された場合は、このフォームのすべての要素の色をゼロ化します。このメソッドの詳細は、以下のコードで見ることができます。
class CWndEvents : public CWndContainer { private: //--- フォームとその要素の色をゼロにする bool OnResetWindowColors(void); }; //+------------------------------------------------------------------+ //| CHARTEVENT_CUSTOM イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- フォーム最小化のシグナルの場合 //--- フォーム最大化のシグナルの場合 //--- 開始メニュー項目の下のコンテキストメニューを非表示にするシグナルの場合 //--- コンテキストメニューをすべて非表示にするシグナルの場合 //--- ダイアログウィンドウを開くシグナルの場合 //--- 指定されたフォームの要素の色をすべてゼロにするシグナルの場合 if(OnResetWindowColors()) return; } //+------------------------------------------------------------------+ //| ON_RESET_WINDOW_COLORS イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnResetWindowColors(void) { //--- ウィンドウの色をゼロにするシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_RESET_WINDOW_COLORS) return(false); //--- メッセージが受信されたフォームのインデックスを特定するため int index=WRONG_VALUE; //--- ウィンドウ配列要素を反復処理する int window_total=CWndContainer::WindowsTotal(); for(int w=0; w<window_total; w++) { //--- 識別子が一致した場合 if(m_windows[w].Id()==m_lparam) { //--- インデックスを格納する index=w; //--- フォームの色をゼロにする m_windows[w].ResetColors(); break; } } //--- インデックスが確認されなかった場合は終了する if(index==WRONG_VALUE) return(true); //--- すべてのフォーム洋装の色をゼロにする int elements_total=CWndContainer::ElementsTotal(index); for(int e=0; e<elements_total; e++) m_wnd[index].m_elements[e].ResetColors(); //--- チャートの再描画 m_chart.Redraw(); return(true); }
ウィンドウを開けることについてはこれで明らかになりました。次に、ウィンドウを閉じて前のアクティブウィンドウを復元するためのメソッドを実装する必要があります。このイベント処理のためにDefines.mqh ファイルにON_CLOSE_DIALOG_BOX 識別子を作成します。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_CLOSE_DIALOG_BOX (12) // ダイアログウィンドウを閉じるイベント
CWindowクラスでは、フォームとプログラムを閉じるにはCWindow::CloseWindow() メソッドが使われます。このメソッドではダイアログウィンドウ(W_DIALOG)を閉じる部分はまだ実装されていません。ダイアログウィンドウを閉じるイベントを生成するもう1つのメソッドを書きましょう。メッセージは(1) イベント識別子に加えて (2) 要素識別子(3) 1つ前にアクティブだったウィンドウのインデックスと (4) ヘッダのテキストを含みます。このメソッドをCWindow::CloseDialogBox()と呼びましょう。これは、後に、ウィンドウが「閉じる」ボタン以外の要素によって閉じられる複雑なコントロールで使用されます。
class CWindow : public CElement { public: //--- ダイアログウィンドウを閉じる void CloseDialogBox(void); }; //+------------------------------------------------------------------+ //| ダイアログウィンドウを閉じる | //+------------------------------------------------------------------+ void CWindow::CloseDialogBox(void) { //--- 表示の状態 CElement::IsVisible(false); //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CLOSE_DIALOG_BOX,CElement::Id(),m_prev_active_window_index,m_caption_text); }
下の短縮したコードでみられるように、CWindowクラスではCWindow::CloseDialogBox()メソッドはCWindow::CloseWindow() メソッドで呼び出されます。完全なコードは本稿添付のソースコードファイルで確認可能です。
//+------------------------------------------------------------------+ //| ダイアログウィンドウかプログラムを閉じる | //+------------------------------------------------------------------+ bool CWindow::CloseWindow(const string pressed_object) { //--- 押されたのが「ウィンドウを閉じる」ボタン出なかった場合 if(pressed_object!=m_button_close.Name()) return(false); //--- これがメインウィンドウの場合 if(m_window_type==W_MAIN) { //--- ... } //--- ダイアログウィンドウの場合 else if(m_window_type==W_DIALOG) { //--- 閉じる CloseDialogBox(); } //--- return(false); }
ON_CLOSE_DIALOG_BOX識別子を持ったメッセージは送信された後、CWndEventsクラスのハンドラで追跡されて処理されるべきです。このためにCWndEvents::OnCloseDialogBox()メソッドを書きましょう。ベースですべてのウィンドウを反復処理し、メッセージ内の識別子と一致する識別子を持つものを探します。そのようなウィンドウが見つかった場合、それは無効にされるべきです。そして、取り付けられているすべての要素とともにそれを非表示にし、メッセージで渡されたインデックスを持つフォームをアクティブにします。その後、現在アクティブなウィンドウのインデックスを格納し、要素に対するマウスの左クリックの優先順位を復元します。
class CWndEvents : public CWndContainer { private: //--- ダイアログウィンドウを閉じる bool OnCloseDialogBox(void); }; //+------------------------------------------------------------------+ //| CHARTEVENT_CUSTOM イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- フォーム最小化のシグナルの場合 //--- フォーム最大化のシグナルの場合 //--- 開始メニュー項目の下のコンテキストメニューを非表示にするシグナルの場合 //--- コンテキストメニューをすべて非表示にするシグナルの場合 //--- ダイアログウィンドウを開くシグナルの場合 //--- ダイアログウィンドウを閉じるシグナルの場合 if(OnCloseDialogBox()) return; //--- 指定されたフォームの要素の色をすべてゼロにするシグナルの場合 } //+------------------------------------------------------------------+ //| ON_CLOSE_DIALOG_BOX イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnCloseDialogBox(void) { //--- ダイアログウィンドウを閉じるシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_CLOSE_DIALOG_BOX) return(false); //--- ウィンドウ配列要素を反復処理する int window_total=CWndContainer::WindowsTotal(); for(int w=0; w<window_total; w++) { //--- 識別子が一致した場合 if(m_windows[w].Id()==m_lparam) { //--- フォームをブロックする m_windows[w].State(false); //--- フォームを非表示にする int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) m_wnd[w].m_elements[e].Hide(); //--- 1つ前のフォームをアクティブにする m_windows[int(m_dparam)].State(true); //--- チャートの再描画 m_chart.Redraw(); break; } } //--- 1つ前のウィンドウのインデックスを設定する m_active_window_index=int(m_dparam); //--- アクティブにされたウィンドウへの左マウスクリックの優先順位を復元する int elements_total=CWndContainer::ElementsTotal(m_active_window_index); for(int e=0; e<elements_total; e++) m_wnd[m_active_window_index].m_elements[e].SetZorders(); //--- return(true); }
これでマルチウィンドウモードのテストのための準備がすべて整いました。
マルチウィンドウモードのテスト
情報インターフェース要素のテストに使われたEAに2つのCWindowクラスインスタンスを作成します。結果は、EAのグラフィカルインターフェイスにおける3つのフォームです。1番目のフォームがメイン(W_MAIN)でほかの2つはダイアログウィンドウ(W_DIALOG)です。メインフォーム上のボタンのいずれかに1つ目のダイアログウィンドウを取り付けます。1つ目のダイアログウィンドウに3つのボタンを作成し、新しく作成されたボタンのいずれかに2つ目のダイアログウィンドウを取り付けます。これによって3つのフォームが同時に開かれ、それらのうちの1つのみがアクティブ(利用可能)になります。
下記のコードは、開発の現段階でアプリケーションのCProgramカスタムクラスに何が追加されるべきかを記します。
class CProgram : public CWndEvents { private: //--- フォーム2 CWindow m_window2; //--- アイコンボタン CIconButton m_icon_button6; CIconButton m_icon_button7; CIconButton m_icon_button8; //--- フォーム3 CWindow m_window3; //--- private: //--- フォーム2 bool CreateWindow2(const string text); //--- アイコンボタン #define ICONBUTTON6_GAP_X (7) #define ICONBUTTON6_GAP_Y (25) bool CreateIconButton6(const string text); #define ICONBUTTON7_GAP_X (7) #define ICONBUTTON7_GAP_Y (50) bool CreateIconButton7(const string text); #define ICONBUTTON8_GAP_X (7) #define ICONBUTTON8_GAP_Y (75) bool CreateIconButton8(const string text); //--- フォーム3 bool CreateWindow3(const string text); };
これらのメソッドの呼び出しは、開発中のアプリケーションのグラフィカルインターフェースを作成するメインメソッドにあります。下記はメソッドの短縮版です。
//+------------------------------------------------------------------+ //| 取引パネルを作成する | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- コントロールのフォーム1 の作成 //--- コントロールの作成 // メインメニュー //--- コンテキストメニュー //--- ステータスバーの作成 //--- アイコンボタン //--- コントロールのフォーム2の作成 if(!CreateWindow2("Icon Button 1")) return(false); //--- アイコンボタン if(!CreateIconButton6("Icon Button 6...")) return(false); if(!CreateIconButton7("Icon Button 7")) return(false); if(!CreateIconButton8("Icon Button 8")) return(false); //--- コントロールのフォーム3 の作成 if(!CreateWindow3("Icon Button 6")) return(false); //--- ツールヒント //--- チャートの再描画 m_chart.Redraw(); return(true); }
一つ目のダイアログウィンドウ(2番目のフォーム)のためのメソッドのみが検討されます。ご存じの通り、ベースにフォームを追加するにはCWndContainer::AddWindow()メソッドが使われます。下記のコードでフォームの座標がどのように定義されているかにご注意ください。プログラムがチャート上に読み込まれたときのデフォルト座標はゼロなので、適切だと判断される座標が設定されます。この例ではそれはx=1でy=20です。その後、フォームは移動可能で、チャートの時間枠やシンボルは切り替えることができます。以下のコードは、フォームが最終位置にとどまることを示しています。チャートがプログラムに最初に読み込まれた時にあった場所にフォームを配置したい場合は、これらの条件を削除します。この例では、プログラムのグラフィカルインターフェースの3フォームすべてが同じ条件を持つことになります。
ダイアログフォームがチャート上で移動できるようにしましょう。ウィンドウタイプはダイアログ(W_DIALOG)であるべきです。そうしないと、グラフィカルインターフェイスの不正な作業が発生します。ウィンドウのアイコンはCWindow::IconFile()メソッドで再定義できます。ダイアログウィンドウの場合、同じアイコンがウィンドウ表示をもたらす要素の1つとして使用できます。
//+------------------------------------------------------------------+ //| コントロールのフォーム 2 の作成 | //+------------------------------------------------------------------+ bool CProgram::CreateWindow2(const string caption_text) { //--- ウィンドウ配列にウィンドウポインタを追加する CWndContainer::AddWindow(m_window2); //--- 座標 int x=(m_window2.X()>0) ?m_window2.X() : 1; int y=(m_window2.Y()>0) ?m_window2.Y() : 20; //--- プロパティ m_window2.Movable(true); m_window2.WindowType(W_DIALOG); m_window2.XSize(160); m_window2.YSize(160); m_window2.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\coins.bmp"); m_window2.CaptionBgColor(clrCornflowerBlue); m_window2.CaptionBgColorHover(C'150,190,240'); //--- フォームの作成 if(!m_window2.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- return(true); }
特定のダイアログウィンドウにコントロールを取り付けるメソッドのいくつかの詳細を思い出してみましょう。例として、このフォーム用に設計されたボタンメソッドの1つについて考えてみましょう。二つのことを強調したいと思います。
覚えていけなければいけないのは次です。
- 要素には自身が取り付けられているフォームへのポインタを受け渡される必要があります。
- 要素ポインタがベースに保存されるとき要素が取り付けられるフォームのインデックスを指定する必要があります。」この場合それはインデックス1です。
//+------------------------------------------------------------------+ //| アイコンボタン6を作成する | //+------------------------------------------------------------------+ bool CProgram::CreateIconButton6(const string button_text) { //--- ウィンドウポインタを格納する m_icon_button6.WindowPointer(m_window2); //--- 座標 int x=m_window2.X()+ICONBUTTON6_GAP_X; int y=m_window2.Y()+ICONBUTTON6_GAP_Y; //--- 作成の前にプロパティを設定する m_icon_button6.TwoState(false); m_icon_button6.ButtonXSize(146); m_icon_button6.ButtonYSize(22); m_icon_button6.LabelColor(clrBlack); m_icon_button6.LabelColorPressed(clrBlack); m_icon_button6.BorderColorOff(clrWhite); m_icon_button6.BackColor(clrLightGray); m_icon_button6.BackColorHover(C'193,218,255'); m_icon_button6.BackColorPressed(C'153,178,215'); m_icon_button6.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp"); m_icon_button6.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp16\\script_colorless.bmp"); //--- コントロールを作成する if(!m_icon_button6.CreateIconButton(m_chart_id,m_subwin,button_text,x,y)) return(false); //--- 要素ポインタをベースに追加する CWndContainer::AddToElementsArray(1,m_icon_button6); return(true); }
ウィンドウ表示の管理はアプリケーションの開発者に任されています。コントロールの押下はCProgramカスタムクラスのイベントハンドラで追跡され、ウィンドウが表示されます。1番目のダイアログウィンドウをEAのMaineウィンドウ(2番目のフォーム)のボタンに割り当て、2番目のダイアログウィンドウの呼び出しを1番目のダイアログウィンドウ(3番目のフォーム)のボタンに割り当てます。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- ボタン押下イベント if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- テキストが一致する if(sparam==m_icon_button1.Text()) { //--- ウィンドウ2を表示する m_window2.Show(); } //--- テキストが一致する if(sparam==m_icon_button6.Text()) { //--- ウィンドウ3を表示する m_window3.Show(); } } }
望ましい結果は、以下のスクリーンショットに示されています。 «Icon Button 1...» と «Icon Button 6...»のボタン名に省略記号に注意してください。これは、ユーザにこの要素を押すとダイアログウィンドウが開くことを知らせる通常の方法です。
図1。マルチウィンドウモードのテスト
複数のフォームが開いているときにシンボルやチャートの時間枠を切り替えると、問題が発生します。ダイアログウィンドウは想定通りに表示されなくなりますが、管理がメインウィンドウに渡されることはありません。フォームは、ユーザのアクションに応答しません。解決法はシンプルです。ご存じのとおりCWndEvents::Destroy()メソッドはカスタムクラスの初期化解除のためにCProgram::OnDeinitEvent() メソッドで呼び出されます。アプリケーションのグラフィカルインターフェースは、このメソッドで削除されます。グラフィカルインターフェイスが削除される瞬間に管理権がメインウィンドウに与えられなければなりません。よってCWndEvents::Destroy()メソッドには追加が必要です。
- メインウィンドウのインデックスをアクティブとして設定します。
- メインウィンドウをアクティブにして、残りを非アクティブ化します。
下記はCWndEvents::Destroy()メソッドのコードの現在のバージョンです。
//+------------------------------------------------------------------+ //| 全てのオブジェクトの削除 | //+------------------------------------------------------------------+ void CWndEvents::Destroy(void) { //--- メインウィンドウのインデックスを設定する m_active_window_index=0; //--- ウィンドウの数を取得する int window_total=CWndContainer::WindowsTotal(); //--- ウィンドウ配列要素を反復処理する for(int w=0; w<window_total; w++) { //--- メインウィンドウをアクティブにする if(m_windows[w].WindowType()==W_MAIN) m_windows[w].State(true); //--- ダイアログウィンドウをブロックする else m_windows[w].State(false); } //--- 要素配列を空にする for(int w=0; w<window_total; w++) { int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) { //--- ポインタが不正の場合、次に移る if(::CheckPointer(m_wnd[w].m_elements[e])==POINTER_INVALID) continue; //--- 要素オブジェクトを削除する m_wnd[w].m_elements[e].Delete(); } //--- 要素配列を空にする ::ArrayFree(m_wnd[w].m_objects); ::ArrayFree(m_wnd[w].m_elements); ::ArrayFree(m_wnd[w].m_context_menus); } //--- フォーム配列を空にする ::ArrayFree(m_wnd); ::ArrayFree(m_windows); }
マルチウィンドウモードの最初のバージョンが実装されました。最初に思えたほど複雑でないことが判明しました。
マウスの左クリックの優先順位のシステムの改善
これまで、インターフェース要素の左マウスクリックの優先順位の管理はON_OPEN_DIALOG_BOXとON_CLOSE_DIALOG_BOX識別子のイベントによって実行されてきました。その理由は次のドロップダウン要素が開発されたとき、この要素のオブジェクトごとに優先順位値を割り当てるのはユーザー次第ということでした。下にあるかもしれない他の要素の優先順位が考慮されました。しかし、このシステムは複雑な複合コントロールの作成においてはぎこちなくわかりにくいものでした。物事を簡単にするために、このようなイベントのために2つ以上の識別子を作成してみましょう。
- ON_ZERO_PRIORITIES – プロパティのゼロ化
- ON_SET_PRIORITIES – プロパティの復元
これらをDefines.mqhファイルに追加します。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_ZERO_PRIORITIES (14) // 左マウスクリックの優先順位のリセット #define ON_SET_PRIORITIES (15) // 左マウスクリックの優先順位の復元
これらの識別子を持つイベントの生成はドロップダウンまたはドロップダウンになりえる要素のクラスに位置する必要があります。開発の現段階では、現在のインタフェースのセットで、コンテキストメニューはそのような要素です。よって、下の短縮版にみえるようにCContextMenuクラスのShow()とHide()メソッドにコードを追加します。
//+------------------------------------------------------------------+ //| コンテキストメニューを表示する //+------------------------------------------------------------------+ void CContextMenu::Show(void) { //--- 要素がすでに表示されている場合は終了する //--- コンテキストメニューのオブジェクトを表示する //--- メニュー項目を表示する //--- 目に見える要素の状態を割り当てる //--- コンテキストメニューの状態 //--- 状態を1つ前のノードに記録する //--- フォームをブロックする //--- マウスの左クリックの優先順位をゼロにするシグナルを送る ::EventChartCustom(m_chart_id,ON_ZERO_PRIORITIES,CElement::Id(),0.0,""); } //+------------------------------------------------------------------+ //| コンテキストメニューを非表示にする | //+------------------------------------------------------------------+ void CContextMenu::Hide(void) { //--- 要素が隠れている場合は終了する //--- コンテキストメニューのオブジェクトを非表示にする //--- メニュー項目を非表示にする //--- フォーカスをゼロにする //--- コンテキストメニューの状態 //--- 状態を1つ前のノードに記録する //--- マウスの左クリックの優先順位を復元するシグナルを送る ::EventChartCustom(m_chart_id,ON_SET_PRIORITIES,0,0.0,""); }
すべてのメッセージを処理するためのメインクラス(CWndEvents)でこれらのメッセージを受信します。そのために、各識別子のための別々の処理メソッドを書きます。これらのメソッドは、カスタムイベントを処理するメインメソッドであるCWndEvents::ChartEventCustom()メソッドで呼び出されます。
class CWndEvents : public CWndContainer { private: //--- マウスの左クリックの優先順位のリセット bool OnZeroPriorities(void); //--- マウスの左クリックの優先順位の復元 bool OnSetPriorities(void); }; //+------------------------------------------------------------------+ //| CHARTEVENT_CUSTOM イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- フォーム最小化のシグナルの場合 //--- フォーム最大化のシグナルの場合 //--- 開始メニュー項目の下のコンテキストメニューを非表示にするシグナルの場合 //--- コンテキストメニューをすべて非表示にするシグナルの場合 //--- ダイアログウィンドウを開くシグナルの場合 //--- ダイアログウィンドウを閉じるシグナルの場合 //--- 指定されたフォームの要素の色をすべてゼロにするシグナルの場合 //--- マウスの左クリックの優先順位をリセットするシグナルの場合 if(OnZeroPriorities()) return; //--- マウスの左クリックの優先順位を復元するシグナルの場合 if(OnSetPriorities()) return; }
CWndEvents::OnZeroPriorities()メソッドでは、すべてのアクティブなウィンドウを反復処理してメッセージに含まれる要素識別子(lparam-パラメータ)を持ったもの、メニュー項目とコンテキストメニュー以外のすべての優先順位をゼロにします。メニュー項目やコンテキストメニューを除外する理由は、複数のコンテキストメニューが同時に(お互いから)開くことが可能だからです。
//+------------------------------------------------------------------+ //| ON_ZERO_PRIORITIES イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnZeroPriorities(void) { //--- マウスの左クリックの優先順位をゼロにするシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_ZERO_PRIORITIES) return(false); //--- int elements_total=CWndContainer::ElementsTotal(m_active_window_index); for(int e=0; e<elements_total; e++) { //--- イベントに渡された識別子を持つもの以外の要素のプロパティをゼロにする if(m_lparam!=m_wnd[m_active_window_index].m_elements[e].Id()) { //--- ... コンテキストメニューもゼロにしない if(m_wnd[m_active_window_index].m_elements[e].ClassName()=="CMenuItem" || m_wnd[m_active_window_index].m_elements[e].ClassName()=="CContextMenu") continue; //--- m_wnd[m_active_window_index].m_elements[e].ResetZorders(); } } //--- return(true); }
受信されたメッセージがON_SET_PRIORITIESイベント識別子を持つ場合、アクティブウィンドウの要素すべてに対する左マウスクリックの優先順位を復元します。
//+------------------------------------------------------------------+ //| ON_SET_PRIORITIES イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnSetPriorities(void) { //--- マウスの左クリックの優先順位を復元するシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_SET_PRIORITIES) return(false); //--- int elements_total=CWndContainer::ElementsTotal(m_active_window_index); for(int e=0; e<elements_total; e++) m_wnd[m_active_window_index].m_elements[e].SetZorders(); //--- return(true); }
おわりに
開発の現段階では、以下のスクリーンショットに示されたような結果が得られるはずです。
図2。開発の現段階でのライブラリの構造
これは、グラフィカルインターフェイスに関するシリーズの第四部の最終章です。第四部の最初の章では、ステータスバーとツールチップ情報インターフェイス要素が実装されました。第二章では、マルチウィンドウモードとマウスの左クリックの優先順位システムが考察されました。
シリーズの最初の部分の全ての資料はダウンロードしてテストすることができます。これらのファイルに含まれている資料の使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるるか、本稿へのコメント欄でご質問ください。
第四部の記事(チャプター)のリストは下記の通りです。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2308
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索