
グラフィカルインタフェース II:メニュー項目要素(チャプター1)
コンテンツ
はじめに
最初のシリーズの章では、グラフィカルインタフェースを作成するためのライブラリの主な構造開発の完全な工程をお話しました。そこでは、グラフィカルインタフェースの第一及び主要素であるコントロールフォームが作成されました。シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的を詳細に考察します。第一部の記事へのリンクの完全なリストは各章の終わりにあります。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
開発の現段階では、いくつかのクラスはまだ未完成です。ライブラリが新しいコントロールで改善されたため、CWndContainer クラスにいくつかの新機能が導入される必要があります。このクラスは要素とその成分となるオブジェクトのポインタを格納するベースです。追加はCWndEventsクラスにも必要です。チャートのイベントとコントロールによって生成されたイベントがそこで処理されるからです。
現在のCWindowコントロールフォームクラスもまた最終的なものではありません。以前の記事で述べたマルチウィンドウモードがまだ実装されていません。このモードは、本シリーズの第四部で実現します。それに加え、コンテキストメニューの作成が実証されます。これは、メインメニューの一部ですが、このシリーズの他の記事で考察されるコントロールのいくつかの一部とすることもできます。
内容をよりよく理解するために、一貫してすべてのアクションを実行することをお勧めします。 記事の省スペースのために、同じタイプに属して異なるクラスで繰り返されるメソッドのコードは省略されます. そのようなメソッドでは、本稿に添付されたファイルを参照して必要なコードを見つけ、本稿の内容を研究し続けなさって下さい。<
プログラムのメインメニュー
メインメニューを有していないプログラムを見つけることは困難です。MetaTrader 端末もまた、このインタフェース要素をを持っています(下のスクリーンショットを参照)。通常、メニューは、プログラムウィンドウの左上部に位置し、いくつかの項目で構成されています。メニュー項目上でマウスを左クリックすると、プログラムのオプションを持つドロップダウンリストが表示されます。
図1。MetaTrader 5 端末のメインメニュー
このドロップダウンリストはコンテキストメニューと呼ばれ、いくつかのタイプのアイテムを含めることができます。そのいくつかを詳しく考察していきましょう。
- ボタンこれは、コンテキストメニューで、最も単純な要素です。通常、左クリックは、プログラムの拡張機能設定または情報提供のためのウィンドウを開きます。また、非常に単純な機能がある場合もあります。ボタンの項目がクリックされた後、プログラム・インターフェースの外観の何かが変更されます。
- チェックボックス型の2つの状態を持つ項目この要素は プロセスを有効にするか、プログラムインターフェースを開く(可視化)のに使用することができます。これが発生すると、この項目はその外観を変更し、状態をアプリケーションユーザに示します。
- 項目グループこのグループでは一つの項目だけを有効にすることができます。このタイプのコントロールは、ラジオボタンやスイッチと呼ばれています。本稿ではこれをラジオ項目と呼びます。
- コンテキストメニューを呼び出す項目メインプログラムファイルから呼び出されたコンテキストメニューは、コンテキストメニューを含む複数の項目を持つことができます。項目のクリック後、コンテキストメニューは右側に表示されます。
MetaEditorコードエディタにもメインメニューが含まれています。
図2。MetaEditorコードエディタのメインメニュー
ここで、このような複雑なインタフェース要素を構成するために必要なクラスを特定する必要があります。すべてを1つのクラスに収集することは勉強目的としては非現実的で、その後の使用もかなり困難なことは明らかです。したがって、複合体全体が単純な部分から組み立てられている方法での実現が理にかなっています。部分を決めましょう。
メインメニューとコンテキストメニューは、いくつかの項目で構成されています。同じクラスがこれらの両方のメニュー項目として使用きます。多くの場合、コンテキストメニューにはメニュー項目をカテゴリに分類するのに役立つ区切り線が含まれています。したがって、すでにこのようなインターフェイス要素を作成するためのコードが少なくとも4つのクラスを必要としていることがわかります。
- メニュー項目この要素の作成にはCMenuItemクラスを開発します。
- 区切り線にはCSeparateLineクラスが作成されます。
- コンテキストメニューCContextMenuクラスこのインターフェース要素はCMenuItemクラスオブジェクトから組み立てられます。
- メインメニューCMenuBarクラスコンテキストメニューと同様に、構成部品は、メニュー項目(CMenuItem)となります。
主なタスクが定義されました。上記から、メインメニューとコンテキストメニューを作成するための最も重要な部分はメニュー項目であることは明らかです。よってCMenuItemクラスの作成を続けます。
メニュー項目作成クラスの開発
他のライブラリファイルが配置されているControlsフォルダに、CMenuItem派生クラスを持つMenuItem.mqhファイルを作成します。 そこですぐにすべてのコントロールで使われる仮想メソッドを宣言することができます。基本クラスは前稿で考察されたCElementクラスです。コンパイラの興味深い特性を強調表示する必要があるため、これらのメソッドは今は実装せずに記事の後部で考察されます。
//+------------------------------------------------------------------+ //| MenuItem.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" //+------------------------------------------------------------------+ //| メニュー項目作成クラス | //+------------------------------------------------------------------+ class CMenuItem : public CElement { //--- public: CMenuItem(void); ~CMenuItem(void); //--- public: //--- チャートイベントハンドラ virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- タイマー virtual void OnEventTimer(void); //--- 要素の移動 virtual void Moving(const int x,const int y); //--- (1)表示 (2)非表示 (3)リセット (4)削除 virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- マウスの左クリックの優先順位の(1)設定と(2)リセット virtual void SetZorders(void); virtual void ResetZorders(void); }; //+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CMenuItem::CMenuItem(void) { } //+------------------------------------------------------------------+ //| デストラクタ | //+------------------------------------------------------------------+ CMenuItem::~CMenuItem(void) { } //+------------------------------------------------------------------+
メニュー項目の要素を構成するためにどのグラフィカルオブジェクトが使用されるのですか?通常、メインメニューには、カーソルがその上をホバリングしたときに背景色および/またはフォントの色を変更するキャプションがあります。コンテキストメニュー項目には通常アイコンがあります。項目に独自のコンテキストメニューが含まれている場合、項目の領域の右側部分に、取り付けられたメニューがあることをユーザーに示す右向きの矢印があります。これは、メニュー項目は、何に属しどのようなタスクを果たすかに応じていくつかの異なるタイプであることを意味します。可能な構成部品のすべてを列挙してみましょう。
- 背景
- ラベル
- キャプション。
- コンテキストメニューの存在を示します。
図3。メニュー項目のコントロールの構成部品
CMenuItemクラスに、 Element.mqhクラスファイルで利用可能なグラフィカルオブジェクトのインスタンス、およびグラフィカルオブジェクト作成メソッドの宣言を追加します。チャートへメニュー項目を添付するときはメニューのインデックス番号がメソッドに渡されなければなりません。この番号は、メニュー構成に使用されるグラフィックオブジェクトの名前の形成に使用されます。これはまた、クリック時のリストや項目の識別のために使用されます。
class CMenuItem : public CElement { private: //--- メニュー項目作成のオブジェクト CRectLabel m_area; CBmpLabel m_icon; CLabel m_label; CBmpLabel m_arrow; //--- public: //--- メニュー項目作成メソッド bool CreateMenuItem(const long chart_id,const int window,const int index_number,const string label_text,const int x,const int y); //--- private: bool CreateArea(void); bool CreateIcon(void); bool CreateLabel(void); bool CreateArrow(void); //--- };
メニュー項目は、いくつかの異なるタイプでありえるので、作成前にタイプを指定することができなければなりません。メニュー項目タイプの列挙(ENUM_TYPE_MENU_ITEM)が必要です。
これを、ライブラリのすべての列挙が格納されているEnums.mqhファイルに追加します。ENUM_TYPE_MENU_ITEM列挙は以前に考察されたオプションを持たなければいけません。
- MI_SIMPLE — 単純な項目
- MI_HAS_CONTEXT_MENU — コンテキストメニューを含む項目
- MI_CHECKBOX — チェックボックス項目
- MI_RADIOBUTTON — ラジオ項目グループに属する項目
//+------------------------------------------------------------------+ //| メニュー項目タイプの列挙 | //+------------------------------------------------------------------+ enum ENUM_TYPE_MENU_ITEM { MI_SIMPLE =0, MI_HAS_CONTEXT_MENU =1, MI_CHECKBOX =2, MI_RADIOBUTTON =3 };
CMenuItemクラスに、メニュー項目タイプを取得するための対応するフィールドとメソッドを追加します。
class CMenuItem : public CElement { private: //--- メニュー項目プロパティ ENUM_TYPE_MENU_ITEM m_type_menu_item; //--- public: //--- タイプの設定と取得 void TypeMenuItem(const ENUM_TYPE_MENU_ITEM type) { m_type_menu_item=type; } ENUM_TYPE_MENU_ITEM TypeMenuItem(void) const { return(m_type_menu_item); } //--- };
単純なメニュー項目であるMI_SIMPLEタイプがデフォルトで設定されます。:
//+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CMenuItem::CMenuItem(void) : m_type_menu_item(MI_SIMPLE) { }
メニュー項目のすべてのグラフィカルオブジェクトの外観を設定するメソッドは、前稿でフォームに行ったように作成する必要があります。これらのメソッドにはいくつかのユニークな特性があります。このコントロールで必要となるすべてのものをリストアップしてみましょう。
- 背景色の変更
- メニュー項目が使用できないとき、つまり現在項目の機能が使用ができないためにブロックされたときの背景色
- 背景フレーム色の変更
- すべてのオブジェクトのためのマウスの左クリック優先順位は、背景クリックを例外として、一般的で0に等しくなければならない
- ラベルとコンテキストメニューの存在を示すためのアイコンの再定義
- 下記を例とするテキストラベルのプロパティを管理するための方法:
- メニュー項目の背景のゼロ座標からのマージン
- フォントの色
- カーソルが上をホバリングされているテキストの色
- 項目がブロックされているときのテキストの色
- メニュー項目の状態を追跡するための変数
- 一般的な状態(使用可能/ブロック)
- チェックボックスの状態
- ラジオポイントの状態
- 項目に付属している場合のコンテキストメニュー状態
- ラジオ項目グループの識別子。これは、コンテキストメニューが複数のラジオ項目グループを含むことができるので必要です。識別子は、ラジオポイントが所属するグループを正確に理解することができます。
- メニュー項目のインデックス番号
上記のすべてをCMenuItemクラスに追加します。
class CMenuItem : public CElement { private: //--- 背景プロパティ int m_area_zorder; color m_area_border_color; color m_area_color; color m_area_color_off; color m_area_color_hover; //--- ラベルプロパティ string m_icon_file_on; string m_icon_file_off; //--- テキストラベルプロパティ string m_label_text; int m_label_x_gap; int m_label_y_gap; color m_label_color; color m_label_color_off; color m_label_color_hover; //--- コンテキストメニュー表示のプロパティ string m_right_arrow_file_on; string m_right_arrow_file_off; //--- 一般的なクリック優先順位 int m_zorder; //--- 使用可能/ブロック bool m_item_state; //--- チェックボックスの状態 bool m_checkbox_state; //--- ラジオボタンの状態と識別子 bool m_radiobutton_state; int m_radiobutton_id; //--- コンテキストメニューの状態 bool m_context_menu_state; //--- public: //--- 背景メソッド void AreaBackColor(const color clr) { m_area_color=clr; } void AreaBackColorOff(const color clr) { m_area_color_off=clr; } void AreaBorderColor(const color clr) { m_area_border_color=clr; } //--- ラベルメソッド void IconFileOn(const string file_path) { m_icon_file_on=file_path; } void IconFileOff(const string file_path) { m_icon_file_off=file_path; } //--- テキストラベルメソッド string LabelText(void) const { return(m_label.Description()); } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } void LabelColor(const color clr) { m_label_color=clr; } void LabelColorOff(const color clr) { m_label_color_off=clr; } void LabelColorHover(const color clr) { m_label_color_hover=clr; } //--- コンテキストメニューの存在を示すメソッド void RightArrowFileOn(const string file_path) { m_right_arrow_file_on=file_path; } void RightArrowFileOff(const string file_path) { m_right_arrow_file_off=file_path; } //--- 共通の (1) 項目の状態 (2) チェックボックスの状態 void ItemState(const bool state); bool ItemState(void) const { return(m_item_state); } void CheckBoxState(const bool flag) { m_checkbox_state=flag; } bool CheckBoxState(void) const { return(m_checkbox_state); } //--- ラジオ項目識別子 void RadioButtonID(const int id) { m_radiobutton_id=id; } int RadioButtonID(void) const { return(m_radiobutton_id); } //--- ラジオ項目の状態 void RadioButtonState(const bool flag) { m_radiobutton_state=flag; } bool RadioButtonState(void) const { return(m_radiobutton_state); } //--- この項目に付属するコンテキストメニューの状態 bool ContextMenuState(void) const { return(m_context_menu_state); } void ContextMenuState(const bool flag) { m_context_menu_state=flag; } //--- };
前回の記事では、ライブラリが、ユーザがどうするべきかわからない状況になるのを避けるように編成されていると説明されました。アクションのシーケンスは、時間の経過とともに忘れられることがあります。コントロールを作成する前に、添付されるフォームのポインタが渡される必要があります。この操作を行わないと、プログラムはフォームへのコントロールの取り付けを完了せず、操作ログに関連メッセージを出力します。このメッセージは、ユーザに、自分のどのコントロールに関する行動でエラーが発生したかが明らかであるように形成されなければなりません。
フォームクラス(CWindow)を含むクラスを含み、フォーム型のポインタを宣言します。各コントロールのクラスでフォームへのポインタを格納するためには、対応するメソッドが必要になります。これをWindowPointer()と名付けます。唯一のパラメータとしてCWindow型のオブジェクトのリンクを受け取ります。そのタスクは、渡されたオブジェクトへのポインタを格納することです。GetPointer() 関数はオブジェクトポインタの取得に使われます。作成する各コントロールのクラスで同じことを行う必要があります。
//+------------------------------------------------------------------+ //| MenuItem.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| メニュー項目作成クラス | //+------------------------------------------------------------------+ class CMenuItem : public CElement { private: //--- コントロールが接続されるフォームへのポインタ CWindow *m_wnd; //--- public: //--- 渡されたフォームのポインタを格納する void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- };
ここで、メニュー項目を作成するCMenuItem::CreateMenuItem()メソッドを考察しましょう。メソッドの最初でCheckPointer() 関数を使って要素が接続されるフォームポインタの有効性を確認します。ポインタが有効でない場合は、プログラムは、操作ログへのメッセージを出力し、メソッドからfalseを返します。ポインタが存在する場合、その要素の変数が初期化されます。
前回の記事では、すべてのインターフェイス要素が独自の識別子を持つと確立し、それが形成される方法を詳細に検討しました。繰り返しを避けるために、簡単におさらいします。
メインフォームの作成時に、メインフォームのポインタが要素ベース(CWndContainerクラス)に追加されたとき、識別子はCWndContainer::AddWindow()メソッドでインターフェース要素のカウンタによって定義されます。カウンタの値は、ベースに各要素を追加する際にフォームクラスに格納されています。フォームポインタがすべての要素で義務付けられているように、最後のコントロールの識別子番号はフォームポインタを介してそれぞれの新しく作成されたインターフェイス要素で利用可能です。要素を作成する前に、その識別子は下のコードのように形成 (黄色で強調表示)されます。
その後、フォームのエッジ点からマージンを計算され要素のために保存されます。フォームポインタからのフォーム座標へのアクセスは可能で、距離の計算もできます。同様のマージンは、要素のオブジェクトごとに計算されます。その後、すべての要素オブジェクトが作成され、最後にウィンドウの状態がチェックされます。ウィンドウが最小化された場合要素も隠される必要があります。
//+------------------------------------------------------------------+ //| メニュー項目要素を作成する | //+------------------------------------------------------------------+ bool CMenuItem::CreateMenuItem(const long chart_id,const int subwin,const int index_number,const string label_text,const int x,const int y) { //--- フォームへのポインタがない場合終了する if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a menu item, the class has to be passed " "the window pointer: CMenuItem::WindowPointer(CWindow &object)"); return(false); } //--- 変数の初期化 m_id =m_wnd.LastId()+1; m_index =index_number; m_chart_id =chart_id; m_subwin =subwin; m_label_text =label_text; m_x =x; m_y =y; //--- エッジポイントからのマージン CElement::XGap(m_x-m_wnd.X()); CElement::YGap(m_y-m_wnd.Y()); //--- メニュー項目の作成 if(!CreateArea()) return(false); if(!CreateIcon()) return(false); if(!CreateLabel()) return(false); if(!CreateArrow()) return(false); //--- ウィンドウが最小化されている場合、作成後要素を隠す if(m_wnd.IsMinimized()) Hide(); //--- return(true); }
要素を隠すCMenuItem::Hide()メソッドでは、すべてのオブジェクトを隠し、変数のいくつかを0にして色のリセットをしなければなりません。
//+------------------------------------------------------------------+ //| メニュー項目を隠す | //+------------------------------------------------------------------+ void CMenuItem::Hide(void) { //--- 要素が隠れている場合は終了する if(!CElement::m_is_visible) return; //--- 全てのオブジェクトを非表示にする for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_NO_PERIODS); //--- 変数のゼロ化 m_context_menu_state=false; CElement::m_is_visible=false; CElement::MouseFocus(false); //--- 色をリセットする m_area.BackColor(m_area_color); m_arrow.State(false); }
メニュー項目の要素のグラフィカルオブジェクトの名前を形成することは CWindowクラスフォームの場合より少し複雑になります。メニュー項目が、メインメニューやコンテキストメニューの一部ではない独立した要素として作成された場合(例は下記)、これは一意の識別子を持っているのでその後まったく問題が生じません。 この要素が他の同様のオブジェクトを含むグループで作成される場合、1つの識別子は十分ではありません。これは、さもないとすべての単一型グラフィカルオブジェクトの名前が同じになり、結果として一つだけの要素がチャートに表示されるからです。
メニュー項目が作成されると、この項目のインデックス番号はCMenuItem::CreateMenuItem()メソッドに渡されます。その後m_index_numberクラスのフィールドに格納され、これがのちに要素のそれぞれのグラフィックオブジェクトの名前を形成するのに使われます。メニュー項目オブジェクト名の構成部分は、以下のとおりです。
- プログラム名
- 要素の所属
- 要素の部分への所属
- 要素のインデックス番号
- 要素の識別子
背景、テキストラベル、コンテキストメニューの存在の指標を作成する方法はCWindowクラスにあるものと主要な違いを持っていないため、本稿に送付されたMenuItem.mqhファイルでコードを研究できます。ここでは、アイコンを作成するCMenuItem::CreateIcon()メソッドを例として実証します。このメソッドでは、メニューアイテムのタイプがチェックされ、その結果に応じて、該当するアイコンが定義されています。
単純なメニュー項目(MI_SIMPLE)とコンテキストメニューを含む項目(MI_HAS_CONTEXT_MENU)はアイコンを持てません。ユーザーによって定義されていない場合、これはエラーではなくアイコンは必要でないので、プログラムはメソッドが trueを返します。チェックボックスやラジオポイント型のメニュー項目に対しては、アイコンが定義されていない場合デフォルトのものが使用されます。ライブラリのコードで使用されるアイコンは本稿に添付されています。
CMenuItem::CreateIcon()メソッドのコードは下記です。
//+------------------------------------------------------------------+ //| 項目ラベルを作成する | //+------------------------------------------------------------------+ #resource "\\Images\\Controls\\CheckBoxOn_min_gray.bmp" #resource "\\Images\\Controls\\CheckBoxOn_min_white.bmp" //--- bool CMenuItem::CreateIcon(void) { //--- 単純な項目とコンテキストメニューを含む項目の場合 if(m_type_menu_item==MI_SIMPLE || m_type_menu_item==MI_HAS_CONTEXT_MENU) { //--- ラベルが必要ない場合(アイコンが定義されない)、終了する if(m_icon_file_on=="" || m_icon_file_off=="") return(true); } //--- チェックボックスの場合 else if(m_type_menu_item==MI_CHECKBOX) { //--- アイコンが定義されていない場合は、デフォルトを設定する if(m_icon_file_on=="") m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp"; if(m_icon_file_off=="") m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp"; } //--- ラジオ項目の場合 else if(m_type_menu_item==MI_RADIOBUTTON) { //--- アイコンが定義されていない場合は、デフォルトを設定する if(m_icon_file_on=="") m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp"; if(m_icon_file_off=="") m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp"; } //--- オブジェクト名の形成 string name=CElement::ProgramName()+"_menuitem_icon_"+(string)CElement::Index()+"__"+(string)CElement::Id(); //--- オブジェクト座標 int x =m_x+7; int y =m_y+4; //--- ラベルを設定する if(!m_icon.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- 優先順位の設定 m_icon.BmpFileOn("::"+m_icon_file_on); m_icon.BmpFileOff("::"+m_icon_file_off); m_icon.State(m_item_state); m_icon.Corner(m_corner); m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor); m_icon.Selectable(false); m_icon.Z_Order(m_zorder); m_icon.Tooltip("\n"); //--- エッジポイントからのマージン m_icon.XGap(x-m_wnd.X()); m_icon.YGap(y-m_wnd.Y()); //--- オブジェクトポインタを格納する CElement::AddToArray(m_icon); return(true); }
要素を隠すCMenuItem::Hide()メソッドは以前に表示されました。ここでは、要素を表示するCMenuItem::Show()メソッドを実装します。メニュー項目がチェックボックスやラジオポイント型である場合には、このメソッドは現在の状態(有効/無効)を考慮しなければなりません。
//+------------------------------------------------------------------+ //| メニュー項目を表示する | //+------------------------------------------------------------------+ void CMenuItem::Show(void) { //--- 要素がすでに表示されている場合は終了する if(CElement::m_is_visible) return; //--- オブジェクトを表示する for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_ALL_PERIODS); //--- チェックボックスの場合、状態を考慮する if(m_type_menu_item==MI_CHECKBOX) m_icon.Timeframes((m_checkbox_state)?OBJ_ALL_PERIODS : OBJ_NO_PERIODS); //--- ラジオ項目の場合、状態を考慮する else if(m_type_menu_item==MI_RADIOBUTTON) m_icon.Timeframes((m_radiobutton_state)?OBJ_ALL_PERIODS : OBJ_NO_PERIODS); //--- 変数のゼロ化 CElement::m_is_visible=true; CElement::MouseFocus(false); }
インタフェース要素を作成するためのすべてのメソッドが実装されると、チャートへのインタフェースの付着をテストすることができます。CMenuItemクラスが使用できるようにWndContainer.mqhファイルに含みます。
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Window.mqh" #include "MenuItem.mqh"
前回の記事でテストされたEAが使用できます。CProgram クラスでCMenuItem クラスのインスタンスと、メニュー項目を作成するCProgram::CreateMenuItem1()メソッドを作成します。作成された各要素のフォームのエッジ点からのマージンは、マクロに便利です。要素が多い場合、これは、一つのメソッド実装からもう一つに移動するよりもそれらの互いに相対した位置を調整するための、より便利で迅速な方法です。
//+------------------------------------------------------------------+ //| アプリケーション作成のクラス | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: //--- ウィンドウ CWindow m_window; //--- メニュー項目 CMenuItem m_menu_item1; public: CProgram(); ~CProgram(); //--- 初期化/初期化解除 void OnInitEvent(void); void OnDeinitEvent(const int reason); //--- タイマー void OnTimerEvent(void); //--- protected: //--- チャートイベントハンドラ virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- public: //--- 取引パネルを作成する bool CreateTradePanel(void); //--- private: //--- フォームの作成 bool CreateWindow(const string text); //--- メニュー項目の作成 #define MENU_ITEM1_GAP_X (6) #define MENU_ITEM1_GAP_Y (25) bool CreateMenuItem1(const string item_text); };
要素が機能の完全なセットでどのように見えるかを確認するテストのために、コンテキストメニューが含まれているアイコンを持つメニュー項目を作成してみましょう。色のついたアイコンと無色のアイコンと2つのアイコンが用意されます。無色のアイコンは項目がブロックされているときに使われます。
下記はCProgram::CreateMenuItem1()メソッドのコードです。含まれたリソース(アイコン)は本稿の末尾にあります。要素が取り付けられるフォームへのポインタは、メソッドの開始時に要素のクラスに格納されます。その後座標が計算され、必要な要素プロパティが設定され、メニュー項目の要素がチャートに取り付けられます。
//+------------------------------------------------------------------+ //| メニュー項目を作成する | //+------------------------------------------------------------------+ #resource "\\Images\\Controls\\bar_chart.bmp" #resource "\\Images\\Controls\\bar_chart_colorless.bmp" //--- bool CProgram::CreateMenuItem1(string item_text) { //--- ウィンドウポインタを格納する m_menu_item1.WindowPointer(m_window); //--- 座標 int x=m_window.X()+MENU_ITEM1_GAP_X; int y=m_window.Y()+MENU_ITEM1_GAP_Y; //--- 作成前にプロパティを設定する m_menu_item1.XSize(193); m_menu_item1.YSize(24); m_menu_item1.TypeMenuItem(MI_HAS_CONTEXT_MENU); m_menu_item1.IconFileOn("Images\\Controls\\bar_chart.bmp"); m_menu_item1.IconFileOff("Images\\Controls\\bar_chart_colorless.bmp"); m_menu_item1.LabelColor(clrWhite); m_menu_item1.LabelColorHover(clrWhite); //--- メニュー項目の作成 if(!m_menu_item1.CreateMenuItem(m_chart_id,m_subwin,0,item_text,x,y)) return(false); //--- return(true); }
メニュー項目を作成するための方法の呼び出しがCProgram::CreateTradePanel()メソッドに追加できます。
//+------------------------------------------------------------------+ //| 取引パネルを作成する | //+------------------------------------------------------------------+ bool CProgram::CreateTradePanel(void) { //--- コントロールフォームの作成 if(!CreateWindow("EXPERT PANEL")) return(false); //--- コントロールの作成 // メニュー項目 if(!CreateMenuItem1("Menu item")) return(false); //--- チャートの再描画 m_chart.Redraw(); return(true); }
注意深い読者は「要素ポインタがどのようにベースに入るのだろう」と聞くかもしれません。実装がまだで、要素ポインタがまだベースに格納されていないので、これは良い質問です。以前、コンパイラの興味深い特性を実証するだろうと述べました。その準備が整いました。要素を管理しそのイベントを処理するためのメソッドは、現在 CMenuItem クラスで実装されており、ベースにはポインタはありません。CMenuItem クラスのコンパイルはエラーにつながりませんでした。EAのファイルをコンパイルしてみると、実装が必要とされているメソッドをを指定するエラーメッセージが表示されます(下のスクリーンショットを参照)。
図4。メソッドの実装が存在しないことについてのメッセージ
ファイルがCMenuItemクラスとコンパイルされた時、開発の現在のレベルでは実装のないメソッドは呼び出されないので、エラーは発生しませんでした。現時点では要素のベースのみがフォームポインタを持っています。上のスクリーンショットにあるメソッドはフォームを対象にして(CWindow)CWndEventsクラスのCheckElementsEvents()、MovingWindow()、CheckElementsEventsTimer()および Destroy()メソッドのループで呼ばれているるだけに見えます。要素ポインタはCElement型の配列に格納されています。つまり呼び出しは、これらのメソッドがvirtualとして宣言されているCElement基本クラスで行われます。
ベースのWndContainer.mqhファイルではWindow.mqhとMenuItem.mqhファイルの要素が含まれこれらのクラスはCElementクラスから派生するので、コンパイラーはすべての派生クラスを認識し、直接呼出しがない場合でも、呼び出されたメソッドの実装を要求します。必要なメソッドをCMenuItemクラスに作成しましょう。CWndContainerクラスのベースにコントロールポイントを追加できるようにするメソッドも作成します。.
この時点で、CMenuItem::ChangeObjectsColor()メソッドは、マウスカーソルがホバーしたときに要素オブジェクトの色を変えるのに実装できます。このメソッドでは、メニュー項目の要素の種類及び状態(使用可能/ブロック)が考慮されなければなりません。後者が有効の場合、メソッドの開始時に、このアイテムがコンテキストメニューを含むかどうかのチェックがなければなりません。コンテキストメニューが有効になっている場合、管理がそれに渡されることが理由です。また、呼び出し側の項目の色も記録されなければなりません。
後々、メニュー項目のフォーカス色をリセットするメソッドも必要なのでCMenuItem::ResetColors()メソッドを作成しましょう。
class CMenuItem : public CElement { public: //--- コントロールオブジェクトの色の変化 void ChangeObjectsColor(void); //--- 色をリセットする void ResetColors(void); //--- }; //+------------------------------------------------------------------+ //| カーソルが上をホバ-したときのオブジェクトの色の変更 | //+------------------------------------------------------------------+ void CMenuItem::ChangeObjectsColor(void) { //--- この項目には有効なコンテキストメニューがあるので、終了する if(m_type_menu_item==MI_HAS_CONTEXT_MENU && m_context_menu_state) return; //--- 単純な項目とコンテキストメニューを含む項目のためのコードブロック if(m_type_menu_item==MI_HAS_CONTEXT_MENU || m_type_menu_item==MI_SIMPLE) { //--- フォーカスがある場合 if(CElement::MouseFocus()) { m_icon.State(m_item_state); m_area.BackColor((m_item_state)?m_area_color_hover : m_area_color_off); m_label.Color((m_item_state)?m_label_color_hover : m_label_color_off); if(m_item_state) m_arrow.State(true); } //--- フォーカスのない場合 else { m_arrow.State(false); m_area.BackColor(m_area_color); m_label.Color((m_item_state)?m_label_color : m_label_color_off); } } //--- チェックボックスとラジオ項目のためのコードブロック else if(m_type_menu_item==MI_CHECKBOX || m_type_menu_item==MI_RADIOBUTTON) { m_icon.State(CElement::MouseFocus()); m_area.BackColor((CElement::MouseFocus())?m_area_color_hover : m_area_color); m_label.Color((CElement::MouseFocus())?m_label_color_hover : m_label_color); } } //+------------------------------------------------------------------+ //| 項目の色をリセットする | //+------------------------------------------------------------------+ void CMenuItem::ResetColors(void) { CElement::MouseFocus(false); m_area.BackColor(m_area_color); m_label.Color(m_label_color); }
オブジェクトの移動および削除はCWindowクラス同様の方法で実装されているので、ここで繰り返す必要はありません。コードはこの記事に添付されたファイルで参照できます。これが、CMenuItem::OnEvent()とCMenuItem::OnEventTimer()メソッドに最小限の必要なコード(下記のコードを参照) を追加してライブラリとEAファイルをコンパイルする理由です。メソッド実装の必要性を報告するエラーは生じません。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CMenuItem::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()); return; } } //+------------------------------------------------------------------+ //| タイマー | //+------------------------------------------------------------------+ void CMenuItem::OnEventTimer(void) { //--- フォームオブジェクトの色の変更 ChangeObjectsColor(); }
メニュー項目取り付けのテスト
チャートにEAを読み込むと、下のスクリーンショットのように、メニュー項目の要素を持つフォームが表示されます。要素の背景のデフォルトの色は、フォームの背景の色と一致するので、ここでは見ることはできません。必要に応じて、フォームやメニュー項目の要素の両方のオブジェクトの多くのプロパティを変更するクラス機能を使用することができます。
図5。チャートにメニュー項目を取り付けるテスト
要素はチャートに取り付けられていますが、フォームとともに移動せず、マウスカーソルをホバーしても外観は変更されません。ベースに要素のポインタを追加するためのメソッドを作成してみましょう。これは、一つのループで全ての要素の特性がそれらのそれぞれを呼び出すことによって管理することができる方法です。
このメソッドをCWndContainer::AddToElementsArray()と名付けます。これには(1)要素オブジェクトが取り付けられるフォームのインデックス番号(2)ベースにポインタが保存されなければならない要素オブジェクトの2つのパラメータがあります。メソッドの開始時に、ベースが1つのフォームを含んでいるか複数のフォームを含んでいるかのチェックがあります。1つも存在しない場合、要素はフォームに付加する前に最初にベースに追加しなければならないとのメッセージが操作ログに出力されます。それから、配列サイズの超過の確認があります。
問題がない場合は、(1)ポインタは取り付けられるフォームの配列に追加され、(2)要素オブジェクトはオブジェクトの共通の配列に追加され、(3)この要素の最後の識別子であるすべてのフォームで格納され(4)要素数が1だけ増加します。 これは、メソッドの最終版ではなく、また後で戻ってきます。現在のバージョンのコードを以下に提示されています。
//+------------------------------------------------------------------+ //| 配列へのポインタを追加する | //+------------------------------------------------------------------+ void CWndContainer::AddToElementsArray(const int window_index,CElement &object) { //--- ベースにコントロールフォームがない場合 if(ArraySize(m_windows)<1) { Print(__FUNCTION__," > Before creating a control, create a form " "and add it to the base using the CWndContainer::AddWindow(CWindow &object) method."); return; } //--- 存在しないフォームへのリクエストの場合 if(window_index>=ArraySize(m_windows)) { Print(PREVENTING_OUT_OF_RANGE," window_index: ",window_index,"; ArraySize(m_windows): ",ArraySize(m_windows)); return; } //--- 共通要素配列に追加する int size=ArraySize(m_wnd[window_index].m_elements); ArrayResize(m_wnd[window_index].m_elements,size+1); m_wnd[window_index].m_elements[size]=GetPointer(object); //--- 要素オブジェクトを共通オブジェクト配列に追加する AddToObjectsArray(window_index,object); //--- すべてのフォームの最後の要素のIDを格納する int windows_total=ArraySize(m_windows); for(int w=0; w<windows_total; w++) m_windows[w].LastId(m_counter_element_id); //--- 要素識別子のカウンタを増加する m_counter_element_id++; }
CWndContainer::AddToElementsArray()メソッドの現在のバージョンはすでに、先に開発したEAの検証に十分です。CProgram::CreateMenuItem()メソッドで要素を作成した後の最後に、下のメソッドの短縮版にある1行のコードを追加します。
//+------------------------------------------------------------------+ //| メニュー項目を作成する | //+------------------------------------------------------------------+ bool CProgram::CreateMenuItem(string item_text) { //--- ウィンドウポインタを格納する //--- 座標 //--- 作成前にプロパティを設定する //--- メニュー項目の作成 //--- 要素ポインタをベースに追加する CWndContainer::AddToElementsArray(0,m_menu_item); return(true); }
変更が導入されたファイルがコンパイルされてEAがチャートに読み込まれると、フォームが移動したときに、メニュー項目の要素がそれと一緒に移動され、マウスカーソルがホバーしたときにそのすべてのオブジェクトはその外観を変更します。
図6。グラフィカルインターフェイスの一部としてのメニュー項目の要素のテスト
ライブラリのメインクラスの更なる開発
フォームが最小化されている場合、期待と違ってメニュー項目が隠されることはありません。フォームに取り付けられた要素を隠すのはどのように実装することができますか?チャートイベントの管理は現在CWndEventsクラスで行われます。このクラスはその基本クラスであるCWndContainerの要素のベースにアクセスできます。最小化または最大化ボタンが押されたという表示はありますか?MQLには、そのような場合にカスタムイベントを生成するEventChartCustom() 関数があります。
フォームを最小化するボタンがクリックされると、プログラムは CWindowクラスのOnEvent()ハンドラでCHARTEVENT_OBJECT_CLICKイベントをトラックし、イベントの文字列パラメータ(sparam)をグラフィックオブジェクトの名前に対して検証してCWindow::RollUp()メソッドを呼び出します。これが、メッセージがイベントストリームのキューに送信され、CWndEventsクラスハンドラで受け取られる時です。CWndEventsクラスはすべての要素にアクセスできるので、各要素の CElement::Hide()メソッドは反復して呼び出すことができます。フォーム最大化のイベントでも同様の操作が行われCElement::Show()メソッドを使ってフォーム要素が表示されます。
カスタムイベントのそれぞれにはユニークな識別子が必要です。フォーム最小化/最大化の識別子をDefines.mqhファイルに追加します。
//--- イベント #define ON_WINDOW_UNROLL (1) // フォーム最大化 #define ON_WINDOW_ROLLUP (2) // フォーム最小化
CWindow::RollUp() およびCWindow::Unroll()メソッドの最後で、渡されたEventChartCustom() 関数を呼び出します。
- チャート識別子
- カスタムイベント識別子
- 3番目のパラメータとしての要素識別子(lparam)。
- 4番目のパラメータとしてのプログラムが位置するチャートサブウィンドウ番号(dparam)。
3、4番目のパラメータは、イベントが別の要素やプログラムによって送信された場合の追加的なチェックに必要とされます。
追加される行のみを示したCWindow::RollUp()とCWindow::Unroll()メソッドの短縮版が下記です(コメントはそのままにされています)。
//+------------------------------------------------------------------+ //| ウィンドウを最小化する | //+------------------------------------------------------------------+ void CWindow::RollUp(void) { //--- ボタンを変更する //--- サイズを設定して格納する //--- ボタンを無効にする //--- フォームの最小化された状態 //--- 固定された高さを持ったインディケータがありサブウインドウが最小化モードにがある場合、 // インディケータサブウィンドウのサイズを設定する //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_WINDOW_ROLLUP,CElement::Id(),m_subwin,""); } //+------------------------------------------------------------------+ //| ウィンドウを最大化する | //+------------------------------------------------------------------+ void CWindow::Unroll(void) { //--- ボタンを変更する //--- サイズを設定して格納する //--- ボタンを無効にする //--- フォームの最大化された状態 //--- 固定された高さを持ったインディケータがありサブウインドウが最小化モードにがある場合、 // インディケータサブウィンドウのサイズを設定する //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_WINDOW_UNROLL,CElement::Id(),m_subwin,""); }
ここで、これらのカスタムイベントの処理のためにCWndEvents クラスにメソッドを作成します。これに、識別子用のマクロと同じように名前を付けます。CWndEventsクラスのメソッドの宣言と実装のためのコードは次のとおりです。
class CWndEvents : public CWndContainer { private: //--- フォームを最大化/最小化する bool OnWindowRollUp(void); bool OnWindowUnroll(void); //--- }; //+------------------------------------------------------------------+ //| ON_WINDOW_ROLLUP イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowRollUp(void) { //--- フォーム最小化のシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_ROLLUP) return(false); //--- ウィンドウ識別子とサブウィンドウ番号が一致する場合 if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin) { int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) { //--- フォーム以外のすべての要素を隠す if(m_wnd[0].m_elements[e].ClassName()!="CWindow") m_wnd[0].m_elements[e].Hide(); } } //--- return(true); } //+------------------------------------------------------------------+ //| ON_WINDOW_UNROLL イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowUnroll(void) { //--- フォーム最大化のシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL) return(false); //--- ウィンドウ識別子とサブウィンドウ番号が一致する場合 if(m_lparam==m_windows[0].Id() && (int)m_dparam==m_subwin) { int elements_total=CWndContainer::ElementsTotal(0); for(int e=0; e<elements_total; e++) { //--- フォームとドロップダウン以外の要素のすべてを // 表示する if(m_wnd[0].m_elements[e].ClassName()!="CWindow") if(!m_wnd[0].m_elements[e].IsDropdown()) m_wnd[0].m_elements[e].Show(); } } //--- return(true); }
フォームクラスのCWindow::Show()メソッドはまだ実装されていません。このメソッドは反復してすべての要素で呼び出されるので、上にあるように、プログラムがCWindowクラスにアクセスしない場合にも実装は必須です。
CWindow::Show()メソッドのコード
//+------------------------------------------------------------------+ //| ウィンドウを表示する | //+------------------------------------------------------------------+ void CWindow::Show(void) { //--- オブジェクトを表示する for(int i=0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_ALL_PERIODS); //--- 表示の状態 CElement::m_is_visible=true; //--- フォーカスをゼロにする CElement::MouseFocus(false); m_button_close.MouseFocus(false); m_button_close.State(false); }
これらのメソッドはCWndEvents::ChartEventCustom() メソッドで呼ばれます。
//+------------------------------------------------------------------+ //| CHARTEVENT_CUSTOM イベント | //+------------------------------------------------------------------+ void CWndEvents::ChartEventCustom(void) { //--- フォーム最小化のシグナルの場合 if(OnWindowRollUp()) return; //--- フォーム最大化のシグナルの場合 if(OnWindowUnroll()) return; }
今から、カスタムイベントを処理するすべてのメソッドはCWndEvents::ChartEventCustom()メソッドに配置されます。
CWndEvents::ChartEventCustom()の呼び出しは、チャートイベントが処理されるメソッドの前に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); //--- カスタムイベント ChartEventCustom(); //--- インターフェース要素のイベントのチェック CheckElementsEvents(); //--- マウス動作のイベント ChartEventMouseMove(); //--- チャートプロパティ変更イベント ChartEventChartChange(); }
変更されたファイルとメインプログラムファイルをコンパイルした後、チャートに読み込みます。フォームが最小化されるとメニュー項目が隠され、最大化されると表示されます。
CMenuItemクラスの主要な部分の開発はこれで完成しました。後はこのコントロールのイベントハンドラを設定するだけです。これには、コンテキストメニュー作成クラスを実装し、すべての変更を一貫して完全にテストすることができるようになった後に戻ってきます。その前に、コンテキストメニューの一部である別のインターフェイス要素の区切り線を開発します。
おわりに
本稿では、メニュー項目コントロールを作成する工程の詳細を説明してきました。コントロールのフォームクラス(CWindow)とイベント処理のメインクラス(CWndEvents).にも必要な追加点を導入しました。次回の記事では、区切り線とコンテキストメニューを作成するためのクラスを作成します。
以下の現在の開発段階でのライブラリファイルと記事で考察されたプログラム(EA、インディケータ、スクリプト)の写真やファイルのアーカイブはMetaTrader 4およびMetaTrader 5ターミナルのテストのためにダウンロードできます。これらのファイルに含まれている資料の使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるるか、本稿へのコメント欄でご質問ください。
第二部の記事(チャプター)のリスト:
- グラフィカルインタフェース II:メニュー項目要素(チャプター1)
- グラフィカルインタフェース II: 区切り線とコンテキストメニュー要素(チャプター 2)
- グラフィカルインタフェース II:ライブラリのイベントハンドラの設定(チャプター3)
- グラフィカルインタフェース II:メインメニュー要素(チャプターー4)
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2200



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