
グラフィカルインタフェース II:ライブラリのイベントハンドラの設定(チャプター3)
コンテンツ
- はじめに
- 要素のプライベート配列
- チャートの状態の管理
- 外部および内部使用のための識別子
- コンテキストメニュークラスの充実
- メニュー項目クラスの充実
- グラフィカルインターフェイスのイベント処理の主要クラスの充実
- イベントハンドラの選考検証
- いくつかのコンテキストメニューの検証と微調整
- アプリケーションのカスタムクラスでのメッセージの受信の検証
- おわりに
はじめに
シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的を詳細に考察します。第一部の記事へのリンクの完全なリストは各章の末尾でみられます。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
以前の記事には、メインメニューの構成部分を作成するためのクラスの実装が含まれています。各コントロールのクラスの開発には、主要な基本クラスと作成されたコントロールのクラスでのイベントハンドラの前もった微調整が必要です。本稿では以下の質問が考慮されます。
- それぞれの意味あるコントロールのためのプライベート配列。
- ベースへの要素ポインタの追加。これらの要素は、複合体型(コンパウンド)の要素の構成部分です。
- マウスカーソルの位置に応じたチャートの状態の管理。
- 内部および外部の使用のためのライブラリイベントの識別子。
それに加え、アプリケーションのカスタムクラスのハンドラでメッセージを受信する過程が示されます。
要素のプライベート配列
少し実験をしてみましょう。マウスカーソルがフォーム領域の外側になる領域のコンテキストメニュー項目のいずれかを左クリックしてください。チャートのスクロールが無効にされておらず、コントロールの上にマウスを移動すると使用できることがわかります。これは、機能的エラーで、あってはなりません。マウスカーソルが指しているコントロールにかかわりなく、チャートのスクロールと取引レベルモードの移動がその時点で無効になっているようにします。
初めに、下のコードにあるようにコンテキストメニューハンドラに要素でのフォーカスの追跡を追加しましょう。コンテキストメニューが表示されていない場合、続ける必要がありません。このアプローチに従って時間を節約します。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- 要素が隠れている場合は終了する if(!CElement::m_is_visible) return; //--- フォーカスを取得する int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); } }
ライブラリ開発の現時点では、要素のベース(正確にはCWndContainerクラス)は要素ポインタの共通配列であるm_elements[] が含まれます。これは要素配列のWindowElements構造体の一部です。この配列は、アクションがすべてまたは大部分のコントロールに適用されなければならない場合のすべてに適しています。アクションを要素の特定のグループにのみ適用する必要がある場合、このアプローチはあまりにも多くのリソースを必要とするのでやりすぎです。例えば、取り付けられているフォームの境界を超えることができるサイズを持つコントロールのグループを考えてみましょう。ドロップダウンリストとコンテキストメニューがこのグループに属します。このような要素は、種類ごとに別々の配列に格納されなければなりません。これによって、より効率的かつ容易な管理が可能になります。
コンテキストメニューの配列をWindowElements構造体に追加してサイズ取得のメソッドを作成します。
//+------------------------------------------------------------------+ //| インターフェースオブジェクト格納クラス | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- 要素配列の構造体 struct WindowElements { //--- 全てのオブジェクトの共通配列 CChartObject *m_objects[]; //--- 全要素の共通配列 CElement *m_elements[]; //--- 要素のプライベート配列 // コンテキストメニュー配列 CContextMenu *m_context_menus[]; }; //--- 各ウィンドウの要素配列の配列 WindowElements m_wnd[]; //--- public: //--- コンテキストメニューの数 int ContextMenusTotal(const int window_index); //--- }; //+------------------------------------------------------------------+ //| 指定されたウィンドウインデックスによってコンテキストメニューの数を返します。 //+------------------------------------------------------------------+ int CWndContainer::ContextMenusTotal(const int window_index) { if(window_index>=::ArraySize(m_wnd)) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return(::ArraySize(m_wnd[window_index].m_context_menus)); }
アプリケーションのカスタムクラス(私たちの場合CProgram)でコントロールを作成するときは、このコントロールのポインタをベースに追加するのにCWndContainer::AddToElementsArray() メソッドを使用します。このメソッドでは、共通の配列内のすべての複合体(コンパウンド)要素へのポインタを取得/格納するためのメソッドが使用されます。似たようなCWndContainer::AddContextMenuElements() メソッドは先にコンテキストメニューのために作成されました。すべての似たメソッドは、必要に応じて要素のプライベート配列にポインタを配布することができます。
このアクションが複数回繰り返され異なるオブジェクトタイプに適用されるので、リンクによって渡された配列への要素のポインタを追加するためのテンプレートメソッドが必要です。<
class CWndContainer { protected: //--- リンクによって渡された配列への要素のポインタを追加するテンプレートメソッド template<typename T1,typename T2> void AddToRefArray(T1 &object,T2 &ref_array[]); //--- }; //+------------------------------------------------------------------+ //| リンク(T2)で渡された配列にポインタ(T1)を格納する | //+------------------------------------------------------------------+ template<typename T1,typename T2> void CWndContainer::AddToRefArray(T1 &object,T2 &array[]) { int size=::ArraySize(array); ::ArrayResize(array,size+1); array[size]=object; }
ここで、下記にあるように、コンテキストメニューのポインターはCWndContainer::AddContextMenuElements()メソッドの終わりでプライベート配列に収納できます(黄色で強調表示)。他のコントロールの全部にも同じことをしましょう。
//+------------------------------------------------------------------+ //| ベースにコンテキストメニューのポインタを格納する | //+------------------------------------------------------------------+ bool CWndContainer::AddContextMenuElements(const int window_index,CElement &object) { //--- コンテキストメニューでない場合は終了する if(object.ClassName()!="CContextMenu") return(false); //--- コンテキストメニューのポインタを取得する CContextMenu *cm=::GetPointer(object); //--- ベースにオブジェクトポインタを格納する int items_total=cm.ItemsTotal(); for(int i=0; i<items_total; i++) { //--- 配列要素の増加 int size=::ArraySize(m_wnd[window_index].m_elements); ::ArrayResize(m_wnd[window_index].m_elements,size+1); //--- メニュー項目ポインタの取得 CMenuItem *mi=cm.ItemPointerByIndex(i); //--- ポインタを配列に格納する m_wnd[window_index].m_elements[size]=mi; //--- 共通配列にメニュー項目のオブジェクトすべてへのポインタを追加する AddToObjectsArray(window_index,mi); } //--- ポインタをプライベート配列に追加する AddToRefArray(cm,m_wnd[window_index].m_context_menus); return(true); }
チャートの状態の管理
そして、コントロールの上のマウスカーソルのフォーカスを確認するメソッドがCWndEventsクラスに追加されなければなりません。このようなチェックは、フォームやドロップダウンリストのために実施されます。フォームとコンテキストメニューは、すでにプライベート配列を持っています。よってCWndEvents::SetChartState() メソッドを作成しましょう。下記がその宣言と実装です。
class CWndEvents : public CWndContainer { private: //--- チャートの状態の設定 void SetChartState(void); }; //+------------------------------------------------------------------+ // チャートの状態を設定する //+------------------------------------------------------------------+ void CWndEvents::SetChartState(void) { //--- 管理が無効にされたときのイベントの認識 bool condition=false; //--- ウィンドウを確認する int windows_total=CWndContainer::WindowsTotal(); for(int i=0; i<windows_total; i++) { //--- これが非表示の場合、次のフォームに移る if(!m_windows[i].IsVisible()) continue; //--- フォームの内部ハンドラの確認条件 m_windows[i].OnEvent(m_id,m_lparam,m_dparam,m_sparam); //--- フォーカスがある場合、記憶する if(m_windows[i].MouseFocus()) { condition=true; break; } } //--- コンテキストメニューのフォーカスを確認する if(!condition) { int context_menus_total=CWndContainer::ContextMenusTotal(0); for(int i=0; i<context_menus_total; i++) { if(m_wnd[0].m_context_menus[i].MouseFocus()) { condition=true; break; } } } //--- if(condition) { //--- スクロールと取引レベル管理を無効にする 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); } }
このメソッドは、後にいくつかの追加で充実されますが、現在のタスクにはすでに適しています。下記のようにCWndEvents::ChartEventMouseMove() メソッドでの呼び出しが必要です。
//+------------------------------------------------------------------+ //| CHARTEVENT MOUSE MOVE イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventMouseMove(void) { //--- カーソル変位イベントではない場合は終了する if(m_id!=CHARTEVENT_MOUSE_MOVE) return; //--- ウィンドウの移動 MovingWindow(); //--- チャートの状態の設定 SetChartState(); //--- チャートを再描画する m_chart.Redraw(); }
ファイルを全部コンパイルしてEAを検証します。フォームの境界を超えたコンテキストメニューエリアで左クリックすると、チャートのスクロールと取引レベルの管理が無効になっていることがわかります。<チャートへ要素を取り付けるテストが成功しました。今から、コンテキストメニューはユーザーリクエストによってのみ表示されます。アプリケーションクラスのCProgram::CreateTradePanel() メソッドから表示を削除します(下記のコードを参照)。
m_contextmenu.Show(); // <<< この行は削除されるべき
外部および内部使用のための識別子
ここで、メニュー項目の左クリックの処理に移ります。
次のタスクは、メニュー項目のクリックによってコンテキストメニューが(ある場合には)表示されるようにすることです。もう一度クリックするとコンテキストメニューが隠れます。この処理はメニュー項目のCMenuItemクラスとコンテキストメニューのCContextMenuクラスの両方に存在します。コンテキストメニューは、付属する項目(前のノード)にアクセスできますが、コンテキストメニューが含まれているメニュー項目は直接コンテキストメニューにアクセスできません。コンテキストメニューのポインタはCMenuItem クラスで作成できません。これはContextMenu.mqhファイルがMenuItem.mqhファイルに含まれるとコンパイルエラーが発生するからです。これがコンテキストメニューの表示の処理がCContextMenu クラスで行われる理由です。CMenuItemクラスでのハンドラは補助的です。それは、クリックされたメニュー項目に関する特定の常用をコンテキストメニューに送信することによって、カスタムイベントを生成します。そのうえ、クリックがMetaTrader ターミナルやMetaEditorコードエディタのようにコンテキストメニューエリアの外の場合はコンテキストメニューを隠す必要があります。これはコンテキストメニューの標準的なふるまいです。
この機能を実装するには、追加的なカスタムイベントの識別子が必要です。そのうちのいくつかは、ライブラリクラス内部の使用のために設計され、いくつかはカスタムアプリケーションクラスの外部処理のために設計されます。私たちの場合これはCProgramです。
内部使用イベント:
- ON_CLICK_MENU_ITEM — メニュー項目のクリック
- ON_HIDE_CONTEXTMENUS — すべてのコンテキストメニューを隠すシグナル
- ON_HIDE_BACK_CONTEXTMENUS — 現在のメニュー項目のコンテキストメニューを隠すシグナルこの詳細は後にお話します。
外部使用には、コンテキストメニューの項目がクリックされたとプログラムに伝えるON_CLICK_CONTEXTMENU_ITEM識別子を作成します。
識別子にDefines.mqhファイルで一意の番号を割り当てます。
#define ON_CLICK_MENU_ITEM (4) // メニュー項目のクリック #define ON_CLICK_CONTEXTMENU_ITEM (5) // コンテキストメニューのメニュー項目のクリック #define ON_HIDE_CONTEXTMENUS (6) // コンテキストメニューをすべて隠す #define ON_HIDE_BACK_CONTEXTMENUS (7) // 現在のメニュー項目のコンテキストメニューを隠す
コンテキストメニュークラスの充実
次のフィールドとメソッドは、コンテキストメニューのCContextMenuクラスに追加する必要があります。
- コンテキストメニューの状態の設定と取得
- メニュー項目のクリックイベントの処理
- メニュー項目名からの識別子とインデックスの取得これが、インデックスと識別子が様々な要素を構成するオブジェクトの名前の一部である理由であることはすでにわかっています。
以下のコードは、詳細なコメントをもって上に記載されているすべてのものの宣言と実装提示します。
class CContextMenu : public CElement { private: //--- コンテキストメニューの状態 bool m_contextmenu_state; public: //--- コンテキストメニューの状態の(1) 取得と (2) 設定 bool ContextMenuState(void) const { return(m_context_menu_state); } void ContextMenuState(const bool flag) { m_context_menu_state=flag; } //--- private: //--- このコンテキストメニューが属する項目のクリックの処理 bool OnClickMenuItem(const string clicked_object); //--- メニュー項目名からの(1) 識別子と (2) インデックスの取得 int IdFromObjectName(const string object_name); int IndexFromObjectName(const string object_name); }; //+------------------------------------------------------------------+ //| メニュー項目のクリックの処理 | //+------------------------------------------------------------------+ bool CContextMenu::OnClickMenuItem(const string clicked_object) { //---コンテキストメニューがすでに空いている場合は終了する if(m_contextmenu_state) return(true); //--- クリックがメニュー項目に対してでない場合は終了する if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0) return(false); //--- オブジェクト名から識別子とインデックスを取得する int id =IdFromObjectName(clicked_object); int index =IndexFromObjectName(clicked_object); //--- クリックがこのコンテキストメニューが属するメニュー項目に対してでない場合は終了する if(id!=m_prev_node.Id() || index!=m_prev_node.Index()) return(false); //--- コンテキストメニューを表示する Show(); return(true); } //+------------------------------------------------------------------+ //| オブジェクト名から識別子を抽出する | //+------------------------------------------------------------------+ int CContextMenu::IdFromObjectName(const string object_name) { //--- オブジェクト名から識別子を取得する int length =::StringLen(object_name); int pos =::StringFind(object_name,"__",0); string id =::StringSubstr(object_name,pos+2,length-1); //--- return((int)id); } //+------------------------------------------------------------------+ //| オブジェクト名からインデックスを抽出する | //+------------------------------------------------------------------+ int CContextMenu::IndexFromObjectName(const string object_name) { ushort u_sep=0; string result[]; int array_size=0; //--- 区切り文字のコードを取得する u_sep=::StringGetCharacter("_",0); //--- 文字列を分ける ::StringSplit(object_name,u_sep,result); array_size=::ArraySize(result)-1; //--- 配列サイズの超過を確認する if(array_size-2<0) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- return((int)result[array_size-2]); }
ここでCHARTEVENT_OBJECT_CLICK() イベント発生時に、コンテキストメニューの CContextMenu::OnEventイベントハンドラにCContextMenu::OnClickMenuItem() メソッドの呼び出しを加えるだけです。
//--- オブジェクトのマウス左クリックイベントの処理 if(id==CHARTEVENT_OBJECT_CLICK) { if(OnClickMenuItem(sparam)) return; }
メニュー項目クラスの充実
プログラムがメニュー項目上でのマウスの左クリックを検出すると、文字列パラメータがCContextMenu::OnClickMenuItem() <B0>に渡されます。文字列パラメータには、メニュー項目の背景である長方形のラベルグラフィックオブジェクトの名前が含まれています。覚えていらっしゃるように、背景クリックの優先順位は、ほぼすべてのコントロールの他の要素オブジェクトのクリックよりも高くなります。これは、クリックが他の要素オブジェクトによって傍受されてプログラムの予期しない動作を引き起こすことがないよう保証します。例えば、メニュー項目のラベルがその背景よりも高い優先度を持っている場合、ラベル領域のクリックはアイコンの変更につながりかねません。ここで、状態に対応して定義された2つのラベルアイコンがあることを思い出しましょう。理由はOBJ_BITMAP_LABEL型のオブジェクトはデフォルトでこのように動作するからです。
CContextMenu::OnClickMenuItem() メソッドの初めには、コンテキストメニューの状態のチェックが行われます。すでに有効な場合、続ける理由はありません。そして、クリックされたオブジェクトの名前がチェックされます。これは自分のプログラムのオブジェクトであってメニュー項目である場合、続けます。メニュー項目の識別子とインデックスが、オブジェクト名から抽出されます。これらのタスクのために、すべての必要なパラメータがMQL言語の文字列関数を使用してオブジェクト名から抽出されるメソッドがすでに指定されています。メニュー項目の識別子は二重ダッシュの文字列を区切り記号として使用して抽出されます。インデックスを抽出するには、行が、要素オブジェクトパラメータの区切り文字であるアンダースコア記号(_)により部分に分割されます。
OnClickMenuItem()メソッドをCMenuItemクラスで作成します。そのコードは、コンテキストメニューのために書かれたものとは異なります。以下はこのメソッドの宣言と実装です。このメソッドでは、オブジェクト名からパラメータを抽出する必要がありません。渡されたオブジェクトの名前と背景名を比較するので十分です。次に、メニュー項目の現在の状態がチェックされます。それがブロックされている場合には、さらなるアクションは必要とされません。その後、項目がコンテキストメニューを含む場合、有効化または無効化要素のステータスが割り当てられています。この状態より前にコンテキストメニューの状態が有効になった場合、イベント処理のメインモジュールが 後に開かれたすべてのコンテキストメニューを閉じるためのシグナルを送信します。これは、依存性を持ついくつかのコンテキストメニューが同時に開くときに適用可能です。このような例は、本稿で後に詳しく説明されます。ON_HIDE_BACK_CONTEXTMENUSイベント識別子に加え、あと一つのパラメータとしてメニュー項目識別子が渡されます。これは、ループがどちらのコンテキストメニューで停止することができるかを識別するために使用されます。
class CMenuItem : public CElement { //--- メニュー項目クリックの処理 bool OnClickMenuItem(const string clicked_object); //--- }; //+------------------------------------------------------------------+ //| メニュー項目のクリックの処理 | //+------------------------------------------------------------------+ bool CMenuItem::OnClickMenuItem(const string clicked_object) { //--- オブジェクト名でチェックする if(m_area.Name()!=clicked_object) return(false); //--- 項目がアクティブにされていない場合終了する if(!m_item_state) return(false); //--- この項目にコンテキストメニューが含まれる場合 if(m_type_menu_item==MI_HAS_CONTEXT_MENU) { //--- この項目のドロップダウンメニューがアクティブにされていない場合 if(!m_context_menu_state) { m_context_menu_state=true; } else { m_context_menu_state=false; //--- この項目の下にあるコンテキストメニューを閉じるためにシグナルを送る ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,""); } return(true); } //--- この項目はコンテキストメニューを含まないが、コンテキストメニュー自体の一部である場合 else { } //--- return(true); }
グラフィカルインターフェイスのイベント処理の主要クラスの充実
これはCMenuItem::OnClickMenuItem()メソッドの最終版ではありません。これには後ほど戻ってきて、さらに追加を導入します。現在、その主なタスクはコンテキストメニューを隠すメッセージをCWndEventsクラスでのカスタムイベント処理する主要モジュールに送信することです。そのクラスで ON_HIDE_BACK_CONTEXTMENUSイベントで行われるメソッドへのアクセスを作成してみましょう。CWndEvents::OnHideBackContextMenus()と名付けます。本メソッドのコードは下記です。
class CWndEvents : public CWndContainer { private: //--- 開始項目のすべてのコンテキストメニューを非表示にする bool OnHideBackContextMenus(void); }; //+------------------------------------------------------------------+ //| ON_HIDE_BACK_CONTEXTMENUSイベント | //+------------------------------------------------------------------+ bool CWndEvents::OnHideBackContextMenus(void) { //--- 開始項目のすべてのコンテキストメニューを非表示にするシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_BACK_CONTEXTMENUS) return(false); //--- 最後に呼び出されたものからすべてのメニューを反復処理する int context_menus_total=CWndContainer::ContextMenusTotal(0); for(int i=context_menus_total-1; i>=0; i--) { //--- コンテキストメニューと1つ前のノードへのポインタ CContextMenu *cm=m_wnd[0].m_context_menus[i]; CMenuItem *mi=cm.PrevNodePointer(); //--- 開始シグナルについた場合 if(mi.Id()==m_lparam) { //--- コンテキストメニューがフォーカスされていない場合、隠す if(!cm.MouseFocus()) cm.Hide(); //--- ループを停止する break; } else { //--- コンテキストメニューを隠す cm.Hide(); } } //--- return(true); }
CWndEvents::OnHideBackContextMenus()メソッドは下に見られえるようにカスタムイベントを処理するメソッドで呼び出される必要があります。
//+------------------------------------------------------------------+ //| CHARTEVENT_CUSTOM イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- フォーム最小化のシグナルの場合 if(OnWindowRollUp()) return; //--- フォーム最大化のシグナルの場合 if(OnWindowUnroll()) return; //--- シグナルが開始項目の下にコンテキストメニューを非表示にするためのものである場合 if(OnHideBackContextMenus()) return; }
イベントハンドラの選考検証
すべての変更が導入された後、すべてのファイルをコンパイルし、テストのためのチャートにプログラムを読み込みます。さて、フォーム上の独立したメニュー項目がクリックされたとき、そのコンテキストメニューは前に隠されていた場合には表示され、表示されていた場合は隠されます。<加えて、コンテキストメニューが開いているときは、メニュー項目の背景色は変更されません。言い換えると、以下のスクリーンショットのようにマウスカーソルがその領域から削除された場合も再び変更されません。
図1。コンテキストメニュー表示と非表示のテスト
ユーザのコンテキストメニューとの相互作用の調節を続けます。ほとんどのアプリケーションで、1つまたは複数のコンテキストメニューが(他のコンテキストメニューから)開いている場合、マウスクリックはその境界の外で起こると、それらはすぐに閉じられます。ここでは、同じ動作を複製します。
この機能を完全にテストできるようにするために、私たちのEAのインターフェイスに別のコンテキストメニューを追加してみましょう。現在のコンテキストメニューの3項目にコンテキストメニューを取り付けます。これには、items_type[] 配列で一番目のコンテキストメニューを作成するCProgram::CreateContextMenu1() メソッドで、3項目にMI_HAS_CONTEXT_MENU型を割り当てます。
//--- 項目の種類の配列 ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS]= { MI_SIMPLE, MI_SIMPLE, MI_HAS_CONTEXT_MENU, MI_CHECKBOX, MI_CHECKBOX };
次に、2番目のコンテキストメニューのメソッドを作成しましょう。CContextMenuクラスの2番目のインスタンスをCProgramクラスに追加し CreateContextMenu2()メソッドを宣言します。
class CProgram : public CWndEvents { private: //--- メニュー項目とコンテキストメニュー CMenuItem m_menu_item1; CContextMenu m_mi1_contextmenu1; CContextMenu m_mi1_contextmenu2; //--- private: #define MENU_ITEM1_GAP_X (6) #define MENU_ITEM1_GAP_Y (25) bool CreateMenuItem1(const string item_text); bool CreateMI1ContextMenu1(void); bool CreateMI1ContextMenu2(void); };
2番目のコンテキストメニューは6つの項目を含みます。それぞれ3項目を含むラジオ項目(MI_RADIOBUTTON)の2グループです。下記がこのメソッドのコードです。このメソッドと最初のコンテキストメニューを作成するメソッドの違いは何ですか?<2番目のコンテキストメニューが取り付けられる一番目のコンテキストメニューの3番目の項目のポインタの取得の方法をご覧ください。そのためのCContextMenu::ItemPointerByIndex() メソッドは先に作成されました。ラジオ項目にはデフォルトのアイコンを使用するので、配列は必要ありません。CContextMenu::AddItem()メソッドで、アイコンパスの代わりに空の値を受け渡します。一番目と二番目のラジオ項目を視覚的に分離するためにここでは区切り線が必要です。よって、これをリストの3番目の(2)項目の後に設定します。
ラジオ項目の各グループは、独自の一意の識別子を持たなければならないこと前述され模式的に示されました。このパラメータのデフォルト値は0です。そのため、2番目のグループの各ラジオ項目(ループの3から6)に1に等しい識別子を割り当てます。 CContextMenuクラスはすでに識別子を設定するCContextMenu::RadioItemIdByIndex()メソッドを含みます。
CContextMenu::SelectedRadioItem() メソッドを使用して、各グループのどちらのラジオ項目が初めに強調表示する必要があるかを指定してみましょう。下記のコードでは、1番目のグループでは2番目のラジオ項目(インデックス1)が強調表示され、2番目のグループでは3番目のラジオ項目(インデックス2)が強調表示されています。
//+------------------------------------------------------------------+ //| コンテキストメニュー2を作成する | //+------------------------------------------------------------------+ bool CProgram::CreateMI1ContextMenu2(void) { //--- コンテキストメニューの6項目 #define CONTEXTMENU_ITEMS2 6 //--- ウィンドウポインタを格納する m_mi1_contextmenu2.WindowPointer(m_window); //--- 1つ前のノードへのポインタを格納する m_mi1_contextmenu2.PrevNodePointer(m_mi1_contextmenu1.ItemPointerByIndex(2)); //--- 項目名の配列 string items_text[CONTEXTMENU_ITEMS2]= { "ContextMenu 2 Item 1", "ContextMenu 2 Item 2", "ContextMenu 2 Item 3", "ContextMenu 2 Item 4", "ContextMenu 2 Item 5", "ContextMenu 2 Item 6" }; //--- 作成前にプロパティを設定する m_mi1_contextmenu2.XSize(160); m_mi1_contextmenu2.ItemYSize(24); m_mi1_contextmenu2.AreaBackColor(C'240,240,240'); m_mi1_contextmenu2.AreaBorderColor(clrSilver); m_mi1_contextmenu2.ItemBackColorHover(C'240,240,240'); m_mi1_contextmenu2.ItemBackColorHoverOff(clrLightGray); m_mi1_contextmenu2.ItemBorderColor(C'240,240,240'); m_mi1_contextmenu2.LabelColor(clrBlack); m_mi1_contextmenu2.LabelColorHover(clrWhite); m_mi1_contextmenu2.SeparateLineDarkColor(C'160,160,160'); m_mi1_contextmenu2.SeparateLineLightColor(clrWhite); //--- コンテキストメニューに項目を追加する for(int i=0; i<CONTEXTMENU_ITEMS2; i++) m_mi1_contextmenu2.AddItem(items_text[i],"","",MI_RADIOBUTTON); //--- 3番目の項目の後の区切り線 m_mi1_contextmenu2.AddSeparateLine(2); //--- 2番目のグル^ぷに一意の識別子 (1) を設定する for(int i=3; i<6; i++) m_mi1_contextmenu2.RadioItemIdByIndex(i,1); //--- 両グループでのラジオ項目の選択 m_mi1_contextmenu2.SelectedRadioItem(1,0); m_mi1_contextmenu2.SelectedRadioItem(2,1); //--- コンテキストメニューを作成する if(!m_mi1_contextmenu2.CreateContextMenu(m_chart_id,m_subwin)) return(false); //--- 要素ポインタをベースに追加する CWndContainer::AddToElementsArray(0,m_mi1_contextmenu2); return(true); }
CProgram::CreateContextMenu2()メソッドの呼び出しはCProgram::CreateTradePanel()メソッドに位置します。
いくつかのコンテキストメニューの検証と微調整
EAファイルをコンパイルし、チャートに読み鋳込んだ結果は以下のように表示されます。
図2。コンテキストメニューのテスト
項目をクリックしたときに両方のコンテキストメニューが開いている場合は、最初のメニューが表示され、両方のメニューが閉じられます。この動作は、上記で考慮されたCWndEvents::OnHideBackContextMenus()メソッドの基本となります。フォームヘッダーのチャートをクリックした場合は、コンテキストメニューが閉じられません。これは後で治します。
マウスカーソルの位置(フォーカス)はコンテキストメニュークラス(CContextMenu)のOnEvent()イベントハンドラで定義されていますそのため、メインイベントハンドラ(CWndEventsクラス内)内で開いているすべてのコンテキストメニューを閉じるためのシグナルはそこにも送信されます。このタスクは、次の解決策を持っています。
1. マウスの移動イベント(CHARTEVENT_MOUSE_MOVE)が起こると、文字列パラメータsparamは左マウスボタンの状態を含みます。
2. マウスのフォーカスが識別された後に、コンテキストメニューの現在の状態とマウスの左ボタンのチェックを行います。コンテキストメニューがアクティブになっていてボタンが押された場合は、現在のカーソル位置がこのコンテキストメニューと前のノードに関連して識別される次のチェックに移動します。
3. カーソルがそれらのいずれかの領域にある場合は、すべてのコンテキストメニューを閉じるためのシグナルを送信する必要はありません。カーソルがこれらの要素の領域の外にある場合、後に開かれたコンテキストメニューの存在を確認する必要があります。
4. これには、独自のコンテキストメニューを持つ項目が含まれているかどうかを識別するために、このコンテキストメニューのリストを反復処理します。そのような項目がある場合は、そのコンテキストメニューが有効になっているかどうかを確認します。コンテキストメニューがアクティブにされている場合、カーソルはその領域にあっても大丈夫です。これは、この要素のすべてのコンテキストメニューを閉じるためのシグナルを送信する必要がないことを意味します。シグナルを送信するための条件がそろう前に現在のコンテキストメニューが最後に開かれ他場合、それは間違いなくカーソルがすべてのアクティブにされたコンテキストメニューの領域外にあることを意味します。
5. ON_HIDE_CONTEXTMENUSカスタムイベントがここで生成できます。
見ることができるように、重要なのは、マウスカーソルが(マウスの左ボタンが押された場合)最後のアクティブなコンテキストメニューの領域またそれが呼ばれた項目の領域の外側ある場合にのみ、すべてのコンテキストメニューが閉じられるということです。
この論理は、以下のコードで説明されます。CContextMenu::CheckHideContextMenus()メソッドはそのためのものです。
class CContextMenu : public CElement { private: //--- すべてのコンテキストメニューを閉じる条件をチェックする void CheckHideContextMenus(void); //--- }; //+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- 要素が隠れている場合は終了する if(!CElement::m_is_visible) return; //--- フォーカスを取得する int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- コンテキストメニューが有効でマウスの左ボタンが押された場合 if(m_context_menu_state && sparam=="1") { //--- すべてのコンテキストメニューを閉じる条件をチェックする CheckHideContextMenus(); return; } //--- return; } } //+------------------------------------------------------------------+ //| すべてのコンテキストメニューを閉じる条件をチェックする | //+------------------------------------------------------------------+ void CContextMenu::CheckHideContextMenus(void) { //--- カーソルがコンテキストメニューエリアまたは前のノードの領域内にある場合終了する if(CElement::MouseFocus() || m_prev_node.MouseFocus()) return; //--- カーソルがこれらの要素の領域の外にある場合 // そのアクティブにされたオープンコンテキストメニューがあるかのチェックが必要 //--- そのためにコンテキストメニュー項目のリストを反復処理して // コンテキストメニューを含むメニュー項目を識別する int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- そのようなアイテムがある場合は、そのコンテキストメニューが開いている場合は確認する // 開いている場合、この要素からすべてのコンテキストメニューを閉じるためのシグナルは送信しない // なぜならカーソルが次のものの領域にあル可能性があるので、これをチェックしなければならない if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU) if(m_items[i].ContextMenuState()) return; } //--- すべてのコンテキストメニューを隠すシグナルを送信する ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,""); }
ここでON_HIDE_CONTEXTMENUSイベントは開発中のライブラリのCWndEventsクラスのメインハンドラで受信されなければならない 。メソッドを書いてOnHideContextMenus()と名付けましょう。それは、現在はコンテキストメニューのプライベート配列を反復処理して隠すだけなのでかなり単純です。
CWndEvents::OnHideContextMenus()メソッドの宣言と実装は以下のコードにあります。
class CWndEvents : public CWndContainer { private: //--- コンテキストメニューを全部隠す bool OnHideContextMenus(void); }; //+------------------------------------------------------------------+ //| ON_HIDE_CONTEXTMENUS イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnHideContextMenus(void) { //--- すべてのコンテキストメニューを隠すシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_HIDE_CONTEXTMENUS) return(false); //--- int cm_total=CWndContainer::ContextMenusTotal(0); for(int i=0; i<cm_total; i++) m_wnd[0].m_context_menus[i].Hide(); //--- return(true); }
ライブラリファイルをコンパイルし、テストのためのチャートにEAを読み込むと、マウスクリックがその区域の外で発生した場合、アクティブにされているコンテキストメニューが非表示になることがわかります。
あと一つの顕著な設計上の欠陥を排除する必要があります。以下のスクリーンショットを見てみましょう。これは、マウスカーソルが一番目のコンテキストメニューの領域で二番目のコンテキストメニューが呼び出されるメニュー項目の領域外にある状況を示します。通常、このような場合、カーソルが現在置かれている者の後に開かれたすべてのコンテキストメニューは閉じられます。コードを書きましょう。
図3。このような状況では、右のすべてのコンテキストメニューを非表示にする必要があります。
次のメソッドをCContextMenu::CheckHideBackContextMenus()と名付けます。その論理は前の段落で説明されたので、すぐにその実装に進むことができます(以下のコードを参照)。条件がそろえばON_HIDE_BACK_CONTEXTMENUSイベントが生成されます。
class CContextMenu : public CElement { private: //--- このコンテキストメニューの後に開かれたすべてのコンテキストメニューを閉じるための条件チェック void CheckHideBackContextMenus(void); //--- }; //+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_MOUSE_MOVE) { //--- 要素が隠れている場合は終了する if(!CElement::m_is_visible) return; //--- フォーカスを取得する int x=(int)lparam; int y=(int)dparam; CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- コンテキストメニューが有効でマウスの左ボタンが押された場合 if(m_context_menu_state && sparam=="1") { //--- すべてのコンテキストメニューを閉じる条件をチェックする CheckHideContextMenus(); return; } //--- このコンテキストメニューの後に開かれたすべてのコンテキストメニューを閉じるための条件チェック CheckHideBackContextMenus(); return; } } //+------------------------------------------------------------------+ //| このコンテキストメニューの後に開かれたすべての | //| コンテキストメニューを閉じるための条件チェック | //+------------------------------------------------------------------+ void CContextMenu::CheckHideBackContextMenus(void) { //--- すべてのメニュー項目を反復処理する int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- 項目がコンテキストメニューを含み有効になっている場合 if(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU && m_items[i].ContextMenuState()) { //--- 焦点がコンテキストメニューにあるがこの項目にない場合 if(CElement::MouseFocus() && !m_items[i].MouseFocus()) //--- このコンテキストメニューの後に開いたすべてのコンテキストメニューを隠すシグナルを送信する ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,""); } } }
ON_HIDE_BACK_CONTEXTMENUSイベントを処理するOnHideBackContextMenus()メソッドは以前にCWndEventsクラスで書かれたので、プロジェクトファイルはコンパイルされてEAがテストできます。すべてが正しく行われた場合、コンテキストメニューは、プログラムの要件に応じて、マウスカーソルの移動に反応します。
最も難しい部分はこれで終わりですが、仕事はまだ終わっていません。イベントハンドラは、コンテキストメニュー項目のいずれかがクリックされたときにパラメータの値を持つメッセージがアプリケーションのカスタムクラス(CProgram)に送信されるように設定する必要があります。これらのパラメータによって、クリックされたメニュー項目を正確に識別できるようになります。アプリケーションの開発者は、これによってメニュー項目に特定の機能を割り当てることができます。また、チェックボックスやコンテキストメニューのラジオ項目の状態の切り替えの設定がまだされていません。
クラスのOnClickMenuItem()メソッドの、メニュー項目がコンテキストメニューを含まないがコンテキストメニューの一部である場合のブロックはまだ書かれていません。ON_CLICK_MENU_ITEMカスタムイベントはここから送信されます。このメッセージには次の追加的なパラメータが含まれます。
- 共同リストのインデックス
- 要素の識別子
- 下記から形成される行:
- プログラム名
- チェックボックスかラジオ項目の識別。
- ラジオ項目の場合、ラジオ項目の識別子が含まれます。
お分かりのようにEventChartCustom()関数は不十分で、正確な識別に必要な数のパラメータを持った文字列が常に形成されます。グラフィックオブジェクトの名前と同様に、パラメータは、アンダースコア「_」によって分割されます。
チェックボックスとラジオ項目の状態も同じブロックで変更されます。下記はCMenuItem::OnClickMenuItem() メソッドの短縮版です。elseブロックに追加されるコードのみが示されています。
//+------------------------------------------------------------------+ //| 要素ヘッダのクリック | //+------------------------------------------------------------------+ bool CMenuItem::OnClickMenuItem(const string clicked_object) { //--- オブジェクト名でチェックする //--- 項目がアクティブにされていない場合終了する //--- この項目にコンテキストメニューが含まれる場合 //... //--- この項目はコンテキストメニューを含まないが、コンテキストメニュー自体の一部である場合 else { //--- プログラム名を持つメッセージプレフィックス string message=CElement::ProgramName(); //--- チェックボックスの場合、状態を変える if(m_type_menu_item==MI_CHECKBOX) { m_checkbox_state=(m_checkbox_state)?false : true; m_icon.Timeframes((m_checkbox_state)?OBJ_NO_PERIODS : OBJ_ALL_PERIODS); //--- メッセージにこれがチェックボックスだということを追加する message+="_checkbox"; } //--- ラジオ項目の場合、状態を変える else if(m_type_menu_item==MI_RADIOBUTTON) { m_radiobutton_state=(m_radiobutton_state)?false : true; m_icon.Timeframes((m_radiobutton_state)?OBJ_NO_PERIODS : OBJ_ALL_PERIODS); //--- メッセージにこれがラジオ項目だということを追加する message+="_radioitem_"+(string)m_radiobutton_id; } //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CLICK_MENU_ITEM,m_index,CElement::Id(),message); } //--- return(true); }
ON_CLICK_MENU_ITEM識別子を持ったカスタムイベントはコンテキストメニュークラス(CContextMenu)のために意図されています。ラジオ項目がクリックされた場合、イベントの文字列パラメータから識別子を抽出するための追加のメソッドと、このラジオ項目が属するグループに関連したインデックスを取得するメソッドが必要になります。本メソッドのコードは下記です。
文字列パラメータからの識別子の抽出は渡された文字列の構造に依存するため CContextMenu::RadioIdFromMessage() メソッドは、文字列の形式と配列サイズ超過についての追加的なチェックを含みます。
ラジオ項目のインデックスを一般的なインデックスによって返すCContextMenu::RadioIndexByItemIndex() メソッドの初めに、ラジオ項目の識別子を一般的なインデックスによって取得します。以前に書かれたCContextMenu::RadioItemIdByIndex()メソッドを使います。その後、ループでラジオ項目を数えます。受け渡されたインデックスに等しい一般的なインデックスを持つラジオ項目が見つかったら、カウンタの値を格納してループを終了します。これは、カウンタの最後の値が返されなければならないインデックスであることを意味します。
class CContextMenu : public CElement { private: //--- ラジオ項目メッセージからの(1) 識別子と (2) インデックスの取得 int RadioIdFromMessage(const string message); int RadioIndexByItemIndex(const int index); //--- }; //+------------------------------------------------------------------+ //| ラジオ項目の識別子をメッセージから抽出する | //+------------------------------------------------------------------+ int CContextMenu::RadioIdFromMessage(const string message) { ushort u_sep=0; string result[]; int array_size=0; //--- 区切り文字のコードを取得する u_sep=::StringGetCharacter("_",0); //--- 文字列を分ける ::StringSplit(message,u_sep,result); array_size=::ArraySize(result); //--- メッセージ構造が期待されるものと異なる場合 if(array_size!=3) { ::Print(__FUNCTION__," > Wrong structure in the message for the radio item!message: ",message); return(WRONG_VALUE); } //--- 配列サイズ超過の防止 if(array_size<3) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- ラジオ項目の識別子を返す return((int)result[2]); } //+------------------------------------------------------------------+ //| 一般的なインデックスでのラジオ項目のインデックスを返す | //+------------------------------------------------------------------+ int CContextMenu::RadioIndexByItemIndex(const int index) { int radio_index =0; //--- 一般的なインデックスでのラジオ項目のインデックスを取得する int radio_id =RadioItemIdByIndex(index); //--- 必要なグループからの項目カウンタ int count_radio_id=0; //--- リストを反復処理する int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) { //--- ラジオ項目でない場合、次に移る if(m_items[i].TypeMenuItem()!=MI_RADIOBUTTON) continue; //--- 識別子が一致した場合 if(m_items[i].RadioButtonID()==radio_id) { //--- インデックスが一致した場合 // 現在のカウンタ値を格納し、ループを完成 if(m_items[i].Index()==index) { radio_index=count_radio_id; break; } //--- カウンタの増加 count_radio_id++; } } //--- インデックスを返す return(radio_index); }
ここでメニュー項目のON_CLICK_MENU_ITEM カスタムイベントを処理するCContextMenu::ReceiveMessageFromMenuItem()メソッドを作成しましょう。イベントのパラメータとして、識別子、インデックスと文字列のメッセージがこのメソッドに渡される必要があります。このメッセージが自分のプログラムから受信されたかどうかと識別子が一致するかどうかの条件は、このメソッドの開始時にチェックされます。チェックが肯定的でこのメッセージがラジオ項目から送信された場合、スイッチが識別子とインデックスで必要な項目によって定義されているグループで行われます。識別子およびインデックスは上記で作成されたメソッドを利用して得ることができます。
プログラム名と識別子の比較のチェックが成功し他場合、メッセージを発信したメニュー項目の種類にかかわらずON_CLICK_CONTEXTMENU_ITEMカスタムメッセージが送信されます。それはカスタムアプリケーションのCProgramクラスに向けられています。メッセージとともに次のパラメータが送信されます。(1) 識別子、(2) コンテキストメニューのリストの一般的なインデックス(3)項目の表示テキスト。
メソッドの終わりでは、最初のチェックに関係なく(1)コンテキストメニューが隠され(2)フォームのブロックが解除され(3)すべてのコンテキストメニューを閉じるためのシグナルが送信されます。<
class CContextMenu : public CElement { private: //--- メニュー項目から処理のためのメッセージを受信する void ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item); //--- }; //+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- ON_CLICK_MENU_ITEM イベント処理 if(id==CHARTEVENT_CUSTOM+ON_CLICK_MENU_ITEM) { int item_id =int(dparam); int item_index =int(lparam); string item_message =sparam; //--- メニュー項目から処理のためのメッセージを受信する ReceiveMessageFromMenuItem(item_id,item_index,item_message); return; } } //+------------------------------------------------------------------+ //--- メニュー項目から処理のためのメッセージを受信する //+------------------------------------------------------------------+ void CContextMenu::ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item) { //--- Iメッセージがこのプログラムから受信され要素のIDが一致した場合 if(::StringFind(message_item,CElement::ProgramName(),0)>-1 && id_item==CElement::Id()) { //--- ラジオ項目自体がクリックされた場合 if(::StringFind(message_item,"radioitem",0)>-1) { //--- 渡されたメッセージからラジオ項目IDを取得する int radio_id=RadioIdFromMessage(message_item); //--- 一般的なインデックスでのラジオ項目のインデックスを取得 int radio_index=RadioIndexByItemIndex(index_item); //--- ラジオ項目を切り替える SelectedRadioItem(radio_index,radio_id); } //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,index_item,id_item,DescriptionByIndex(index_item)); } //--- コンテキストメニューを隠す Hide(); //--- フォームのブロックを解除する m_wnd.IsLocked(false); //--- すべてのコンテキストメニューを隠すシグナルを送信する ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,""); }
アプリケーションのカスタムクラスでのメッセージの受信の検証
ここでCProgramクラスでのメッセージ受信をテストできます。そのためには、下記にあるようにコードを追加します。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { if(id==CHARTEVENT_CUSTOM+ON_CLICK_CONTEXTMENU_ITEM) { ::Print(__FUNCTION__," > index: ",lparam,"; id: ",int(dparam),"; description: ",sparam); } }
ファイルをコンパイルしてEAをチャートに読み込みます。メニュー項目がクリックされると、これらの項目のパラメータを持つメッセージがEAの操作ログに印刷されます。
2015.10.23 20:16:27.389 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 4; id: 2; description: ContextMenu 1 Item 5 2015.10.23 20:16:10.895 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 0; id: 3; description: ContextMenu 2 Item 1 2015.10.23 19:27:58.520 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 5; id: 3; description: ContextMenu 2 Item 6 2015.10.23 19:27:26.739 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 2; id: 3; description: ContextMenu 2 Item 3 2015.10.23 19:27:23.351 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 3; id: 3; description: ContextMenu 2 Item 4 2015.10.23 19:27:19.822 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 4; id: 2; description: ContextMenu 1 Item 5 2015.10.23 19:27:15.550 TestLibrary (USDCAD,D1) CProgram::OnEvent > index: 1; id: 2; description: ContextMenu 1 Item 2
これでCContextMenuクラスのコンテキストメニュー作成のための主要な部分の開発が終わりました。後に、検証が問題を明らかにするときいくつかの追加が必要になりますが、その際にこれに戻ります。要するに、学びを容易にするために、この自然な成り行きに従います。
おわりに
本稿では、以前の記事で作成した要素のクラスを改善しました。これでメインメニュー要素を開発するための準備がすべて整いました。これは次の記事で代書します。.
以下の現在の開発段階でのライブラリファイルと記事で考察されたプログラム(EA、インディケータ、スクリプト)の写真やファイルのアーカイブはMetaTrader 4およびMetaTrader 5ターミナルのテストのためにダウンロードできます。これらのファイルに含まれている資料の使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるるか、本稿へのコメント欄でご質問ください。
第二部の記事(チャプター)のリストは下記の通りです。
- グラフィカルインタフェース II:メニュー項目要素(チャプター1)
- グラフィカルインタフェース II: 区切り線とコンテキストメニュー要素(チャプター 2)
- グラフィカルインタフェース II:ライブラリのイベントハンドラの設定(チャプター3)
- グラフィカルインタフェース II:メインメニュー要素(チャプターー4)
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2204




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