English Русский 中文 Español Deutsch Português
グラフィカルインタフェース  II:メインメニュー要素(チャプターー4)

グラフィカルインタフェース II:メインメニュー要素(チャプターー4)

MetaTrader 5 | 25 4月 2016, 10:23
803 0
Anatoli Kazharski
Anatoli Kazharski

コンテンツ

 


はじめに

シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的が詳しく考察されました。記事への完全なリンクのリストは各章の末尾でみられます。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。    

これは、グラフィカルインターフェイスに関するシリーズの第二部の最終章です。ここでは、メインメニューの作成を検討します。このコントロールの開発とユーザの行動に正確な反応するライブラリクラスのハンドラ設定が実証されます。また、メインメニューの項目にコンテキストメニューを取り付ける方法についても説明します。さらに、現在非アクティブである要素のブロックについて話します。

 


メインメニュー作成クラスを開発

プログラムのメインメニューを構築するすべての要素を作成するクラスは、前の章で開発されました。あるのは以下のクラスです。

  • CMenuItem – メニュー項目
  • CSeparateLine – 区切り線
  • CContextMenu – コンテキストメニュー

すべての要素ファイルが含まれているControlsフォルダにMenuBar.mqhファイルを作成します。このファイルに、基本クラスを含むファイル、フォームクラスのファイルとそれを構成する全ての複合体要素のファイルを含見ます。

//+------------------------------------------------------------------+
//|                                                      MenuBar.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "MenuItem.mqh"
#include "ContextMenu.mqh"

メインメニューの基本的なオブジェクトは、背景とメニュー項目です。コンテキストメニューは、ポインタを介してメインメニューの項目に取り付けられます。

図1。メインメニューの基本的な部分

図1。メインメニューの基本的な部分


必要なクラスのインスタンス、ポインタおよび要素の標準的な仮想メソッドを持ったCMenuBarクラスの初期フォームは、以下のコードに示されています。

//+------------------------------------------------------------------+
//| メインメニュー作成クラス                                 |
//+------------------------------------------------------------------+
class CMenuBar : public CElement
  {
private:
   //--- 要素が取り付けられるフォームへのポインタ
   CWindow          *m_wnd;
   //--- メニュー項目作成のオブジェクト
   CRectLabel        m_area;
   CMenuItem         m_items[];
   //--- コンテキストメニューポインタ配列
   CContextMenu     *m_contextmenus[];
   //---
public:
                     CMenuBar(void);
                    ~CMenuBar(void);
   //--- フォームポインタを格納する
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }

   //--- チャートイベントハンドラ
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- 要素の移動
   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);
   //---
  };

他のインターフェイス要素と同様に、メインメニューには外観を設定する可能性がなければなりません。フィールドやメソッドは、この目的のために特別に作成され、設定することができます。

  • メインメニューの背景プロパティ
  • メインメニューの一般的なプロパティ

また、メインメニューの現在の状態を設定/取得するためのメソッドが必要となります。すべてのフィールドは、コンストラクタでデフォルト値に初期化される必要があります。メインメニューのデフォルトの高さは22画素です。メニュー項目の高さはメインメニューの背景の高さと相対して自動的に計算されます。この値は、しかし、基本クラスのCElement::YSize()メソッドによって要素がチャートに取り付けられる前に変更できます。メインメニューの背景の幅は取り付けられているフォームの幅と同じです。これが、チャートに要素を取り付ける際にこのパラメータの計算を自動的に行う理由です。

class CMenuBar : public CElement
  {
private:
   //--- 背景プロパティ
   int               m_area_zorder;
   color             m_area_color;
   color             m_area_color_hover;
   color             m_area_border_color;
   //--- メニュー項目の一般的なプロパティ
   int               m_item_y_size;
   color             m_item_color;
   color             m_item_color_hover;
   color             m_item_border_color;
   int               m_label_x_gap;
   int               m_label_y_gap;
   color             m_label_color;
   color             m_label_color_hover;
   //--- メインメニューの状態
   bool              m_menubar_state;
   //---
public:
   //--- メインメニューの(1)背景と(2)背景フレームの色
   void              MenuBackColor(const color clr)       { m_area_color=clr;                    }
   void              MenuBorderColor(const color clr)     { m_area_border_color=clr;             }
   //--- (1)背景色、(2)カーソルがその上をホバリングしたときの背景色(3)メインメニュー項目のフレーム色
   void              ItemBackColor(const color clr)       { m_item_color=clr;                    }
   void              ItemBackColorHover(const color clr)  { m_item_color_hover=clr;              }
   void              ItemBorderColor(const color clr)     { m_item_border_color=clr;             }
   //--- メニュー項目の背景のエッジ点からのテキストラベルの余白
   void              LabelXGap(const int x_gap)           { m_label_x_gap=x_gap;                 }
   void              LabelYGap(const int y_gap)           { m_label_y_gap=y_gap;                 }
   //--- (1) 標準および (2) フォーカスが置かれた時のテキストの色
   void              LabelColor(const color clr)          { m_label_color=clr;                   }
   void              LabelColorHover(const color clr)     { m_label_color_hover=clr;             }
   //--- メインメニューの状態
   void              State(const bool state);
   bool              State(void)                    const { return(m_menubar_state);             }
   //---
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                   |
//+------------------------------------------------------------------+
CMenuBar::CMenuBar(void) : m_menubar_state(false),
                           m_area_zorder(0),
                           m_area_color(C'240,240,240'),
                           m_area_border_color(clrSilver),
                           m_item_color(C'240,240,240'),
                           m_item_color_hover(C'51,153,255'),
                           m_item_border_color(C'240,240,240'),
                           m_label_x_gap(15),
                           m_label_y_gap(3),
                           m_label_color(clrBlack),
                           m_label_color_hover(clrWhite)
  {
//--- 要素クラスの名前を基本クラスに格納する
   CElement::ClassName(CLASS_NAME);
//--- メインメニューのデフォルトの高さ
   m_y_size=22;
  }
//+------------------------------------------------------------------+
//| メインメニューの状態の設定                               |
//+------------------------------------------------------------------+
void CMenuBar::State(const bool state)
  {
   if(state)
      m_menubar_state=true;
   else
     {
      m_menubar_state=false;
      //--- メインメニューの項目を反復処理して
      //    無効化されたコンテキストメニューの状態を設定する
      int items_total=ItemsTotal();
      for(int i=0; i<items_total; i++)
         m_items[i].ContextMenuState(false);
      //--- フォームのブロックを解除する
      m_wnd.IsLocked(false);
     }
  }

各メニュー項目のユニークなプロパティを設定するには配列が必要です。ユニークなプロパティトは下記です。

  • メニュー項目の幅
  • 表示されたテキスト

これらのプロパティは、チャートにメインメニューを取り付ける前に、すべての項目を追加した瞬間に形成され設定されます。これには、先に CContextMenuクラスで作成されたものと似ているCMenuBar::AddItem()メソッドが使われます。それらの間の唯一の違いは渡された(設定された)パラメータです。 

メインメニューのすべての項目にコンテキストメニューを取り付けるCMenuBar::AddContextMenuPointer()メソッドを作成しましょう。メインメニュー項目のインデックスとコンテキストメニューのオブジェクトはこのメソッドに渡されるべきです。<コンテキストメニューのオブジェクトのポインタはm_contextmenus[]配列に格納されます。

class CMenuBar : public CElement
  {
private:
   //--- メニュー項目のユニークなプロパティは下記です。
   //    (1) 幅 (2) テキスト
   int               m_width[];
   string            m_label_text[];
   //---
public:
   //--- メインメニューの作成前に指定されたプロパティを持つメニューアイテムを追加する
   void              AddItem(const int width,const string text);
   //--- メインメニューの指定された項目に渡されたコンテキストメニューを取り付ける
   void              AddContextMenuPointer(const int index,CContextMenu &object);
   //---
  };
//+------------------------------------------------------------------+
//| メニュー項目を追加する                                                |
//+------------------------------------------------------------------+
void CMenuBar::AddItem(const int width,const string text)
  {
//--- 配列サイズを一要素で増やす  
   int array_size=::ArraySize(m_items);
   ::ArrayResize(m_items,array_size+1);
   ::ArrayResize(m_contextmenus,array_size+1);
   ::ArrayResize(m_width,array_size+1);
   ::ArrayResize(m_label_text,array_size+1);
//--- 受け取ったパラメータの値を格納する
   m_width[array_size]      =width;
   m_label_text[array_size] =text;
  }
//+------------------------------------------------------------------+
//| コンテキストメニューポインタを追加する                                    |
//+------------------------------------------------------------------+
void CMenuBar::AddContextMenuPointer(const int index,CContextMenu &object)
  {
//--- 配列サイズの超過を確認する
   int size=::ArraySize(m_contextmenus);
   if(size<1 || index<0 || index>=size)
      return;
//--- ポインタを格納する
   m_contextmenus[index]=::GetPointer(object);
  }

また、メインメニュー項目へのポインタと項目に取り付けられたコンテキストメニューの1つを指すポインタを取得するためのメソッドが必要になります。これらのメソッドはそれぞれ、配列の範囲を超えた場合に備えて配列サイズおよびインデックス調整のチェックを含みます。それに加えて、メインメニューとコンテキストメニューの項目に対する反復処理はよく行われます。これが、配列のサイズを取得するためのメソッドが必要とされる理由です。

class CMenuBar : public CElement
  {
public:
   //--- (1) 指定されたメニュー項目のポインタの取得 (2)  指定されたコンテキストメニューのポインタの取得 
   CMenuItem        *ItemPointerByIndex(const int index);
   CContextMenu     *ContextMenuPointerByIndex(const int index);

   //--- (1) メニュー項目と (2) コンテキストメニューの数
   int               ItemsTotal(void)               const { return(::ArraySize(m_items));        }
   int               ContextMenusTotal(void)        const { return(::ArraySize(m_contextmenus)); }
   //---
  };
//+------------------------------------------------------------------+
//| メニュー項目ポインタをインデックスで返す                        |
//+------------------------------------------------------------------+
CMenuItem *CMenuBar::ItemPointerByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- メインメニューに項目が一つもない場合には報告する
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
      "when the main menu contains at least one item!");
     }
//--- サイズが超過した場合の調整
   int i=(index>=array_size)?array_size-1 : (index<0)?0 : index;
//--- ポインタを返す
   return(::GetPointer(m_items[i]));
  }
//+------------------------------------------------------------------+
//| コンテキストメニューポインタをインデックスで返す                   |
//+------------------------------------------------------------------+
CContextMenu *CMenuBar::ContextMenuPointerByIndex(const int index)
  {
   int array_size=::ArraySize(m_contextmenus);
//--- メインメニューに項目が一つもない場合には報告する
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
      "when the main menu contains at least one item!");
     }
//--- サイズが超過した場合の調整
   int i=(index>=array_size)?array_size-1 : (index<0)?0 : index;
//--- ポインタを返す
   return(::GetPointer(m_contextmenus[i]));
  }

基本的に、メインメニューを構築する工程はCContextMenuクラスのコンテキストメニューの作成から違いがありません。実際には、メインメニューの作成は、メインメニューが前のノードへのポインタを必要としないので、少し単純です。区切り線も含まれていません。記事のスペースを節約するためにこれらのメソッドのコードは考慮しません。それは本稿末尾に添付したMenuBar.mqhファイルでご覧いただけます。

以前、コンテキストメニュー項目がCWndContainerクラスですべての要素のベースにとどくために、,特別なAddContextMenuElements()メソッドが書かれました。これは、要素をベースに追加するCWndContainer::AddToElementsArray()メインメソッドで呼ばれます。メインメニュー項目にも同じメソッドが必要です。さもないとメニュー項目はフォームとともに移動せずマウスカーソルがホバーしても色を変えません。 

下記は、主要要素の複合体要素がベースに、また、メイン要素ポインタがプライベート配列に到達するために実行するアクションの短いリストです。

  • 要素クラスのファイルをWndContainer.mqhファイルに含めます。
  • 要素配列をWindowElements構造体に追加します。
  • プライベート配列でのメインメニューの数を取得するためのメソッドを追加します。
  • 他の主要要素の一部である要素のポインタを格納するプライベートメソッドを宣言して実装します。
  • 適切なクラスで主要要素ポインタをベースに追加するために書かれた共通メソッドに新しいメソッドの呼び出しを配置します。

下記はWndContainer.mqhファイルに追加されるべきコードを示した短縮版です。

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "MenuBar.mqh"
//+------------------------------------------------------------------+
//| インターフェースオブジェクト格納クラス                          |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- 要素配列の構造体
   struct WindowElements
     {
      //--- メインメニュー配列
      CMenuBar         *m_menu_bars[];
     };
   //---
public:
   //--- メインメニューの数
   int               MenuBarsTotal(const int window_index);
   //---
private:
   //--- ベースにメインメニュー要素へのポインタを格納する
   bool              AddMenuBarElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| 指定されたウィンドウインデックスでのメインメニューの番号を返す  |
//+------------------------------------------------------------------+
int CWndContainer::MenuBarsTotal(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_menu_bars));
  }
//+------------------------------------------------------------------+
//| 配列へのポインタを追加する                              |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- ベースにコントロールフォームがない場合
//--- 存在しないフォームへのリクエストの場合
//--- 共通要素配列に追加する
//--- 要素オブジェクトを共通オブジェクト配列に追加する
//--- すべてのフォームの最後の要素のIDを格納する
//--- 要素識別子のカウンタを増加する
//--- ベースにコンテキストメニューオブジェクトのポインタを格納する
//--- ベースにメインメニューオブジェクトのポインタを格納する
   if(AddMenuBarElements(window_index,object))
      return;
  }
//+------------------------------------------------------------------+
//| ベースにメインメニューオブジェクトのポインタを格納する         |
//+------------------------------------------------------------------+
bool CWndContainer::AddMenuBarElements(const int window_index,CElement &object)
  {
//--- メインメニューでない場合には終了する
   if(object.ClassName()!="CMenuBar")
      return(false);
//--- メインメニューポインタを取得する
   CMenuBar *mb=::GetPointer(object);
//--- ベースにオブジェクトポインタを格納する
   int items_total=mb.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=mb.ItemPointerByIndex(i);
      //--- ポインタを配列に格納する
      m_wnd[window_index].m_elements[size]=mi;
      //--- 共通配列にメニュー項目のオブジェクトすべてへのポインタを追加する
      AddToObjectsArray(window_index,mi);
     }
//--- ポインタをプライベート配列に追加する
   AddToRefArray(mb,m_wnd[window_index].m_menu_bars);
   return(true);
  }

 

 

メインメニュー設定の検証

現時点でメインメニューの設定がテストできます。この要素は3つの項目から作られます。各要素の幅と表示テキストは設定され、残りのプロパティはデフォルトのままです。

下記のコードに見られるように、メインメニューを作成するコードをCProgramアプリケーションのクラスに追加します。すでにフォームに取り付けられている他の要素の座標を調整します。最後に、CProgram::CreateMenuBar()メソッドをグラフィカルインターフェースを作成する新しいメソッドに配置します。

class CProgram : public CWndEvents
  {
private:
   //--- メインメニュー
   CMenuBar          m_menubar;
   //---
private:
   //---
#define MENUBAR_GAP_X    (1)
#define MENUBAR_GAP_Y    (20)
   bool              CreateMenuBar(void);
   //---
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (45)
   //---
#define SEP_LINE_GAP_X   (6)
#define SEP_LINE_GAP_Y   (75)
   //---
  };
//+------------------------------------------------------------------+
//| 取引パネルを作成する                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- コントロールフォームの作成
//--- コントロールの作成
//    メインメニュー
   if(!CreateMenuBar())
      return(false);
//--- メニュー項目
//--- 区切り線
//--- チャートの再描画
   return(true);
  }
//+------------------------------------------------------------------+
//| メインメニューを作成する                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateMenuBar(void)
  {
//--- メインメニューの3項目
#define MENUBAR_TOTAL 3
//--- ウィンドウポインタを格納する
   m_menubar.WindowPointer(m_window);
//--- 座標
   int x=m_window.X()+MENUBAR_GAP_X;
   int y=m_window.Y()+MENUBAR_GAP_Y;
//--- 各項目のユニークなプロパティの配列
   int    width[MENUBAR_TOTAL] ={50,55,53};
   string text[MENUBAR_TOTAL]  ={"File","View","Help"};
//--- メインメニューに項目を追加する
   for(int i=0; i<MENUBAR_TOTAL; i++)
      m_menubar.AddItem(width[i],text[i]);
//--- コントロールを作成する
   if(!m_menubar.CreateMenuBar(m_chart_id,m_subwin,x,y))
      return(false);
//--- オブジェクトをオブジェクトグループの共通配列に追加する
   CWndContainer::AddToElementsArray(0,m_menubar);
   return(true);
  }

ファイルをコンパイルしてEAをチャートに読み込みます。結果は以下のスクリーンショットのようなはずです。メインメニューはフォームとともに移動しマウスカーソルがホバーすると色を変えます。

図2。メインメニュー要素のテスト

図2。メインメニュー要素のテスト

 

 


非アクティブ要素のブロック

コンテキストメニューを作成してメインメニューの項目に取り付ける前に、私たちのライブラリーには、要素の一つがアクティブにされた際にフォームや他の要素をブロックする機能が必要です。どうしてでしょうか?ここでアクティブにされた要素とは、他の要素を介して可視化され(呼ばれ)、必要とされていない場合に隠されるものです。例として、ドロップダウンリスト、コンテキストメニュー、カレンダーなどがこのグループに属します。MetaTrader取引ターミナルでコンテクストメニューかドロップダウンリストをアクティブにしてみてください。これらの要素がアクティブにされるとターミナル全体で他のコントロールが使用できなくなることがわかると思います。これは、例えば、マウスカーソルがそれらの上をホバーしても色がかわらないことで明確です。このようなブロックの理由は、カーソルが現在ドロップダウンリストによって隠されている要素に反応するという状況を除外するためです。

この現象を再現するためにやらなければならないことは、アクティブにされたコントロールが属するフォームをブロックすることです。フォーム上の他の要素はポインタを経由してアクセスすることができるので、その状態は常に取得することができます。ここでの原理は非常に簡単です。フォームがブロックされている場合、その要素の色を変更するためのメソッドが呼び出される必要はないということです。

以下のコードに示すように、フォームを作成するにはCWindowくらすにフォームの状態(ブロックされている/いない)を設定/取得するための特別なフィールドやメソッドを追加します。コンストラクタのm_is_lockedフィールドはfalse値で初期化されます。つまり、デフォルトではフォームはブロックされてはいけません。フォームとその構成要素の色はフォームがブロックされていないときのみに変化するという 条件が追加されます。

class CWindow : public CElement
  {
private:
   //--- ブロックされたウィンドウの状態
   bool              m_is_locked;
   //---
public:
   //--- ブロックされたウィンドウの状態
   bool              IsLocked(void)                                    const { return(m_is_locked);                }
   void              IsLocked(const bool flag)                               { m_is_locked=flag;                   }
   //---
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                   |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_is_locked(false)
  {
  }
//+------------------------------------------------------------------+
//| タイマー                                                            |
//+------------------------------------------------------------------+
void CWindow::OnEventTimer(void)
  {
//--- ウィンドウがブロックされていない場合
   if(!m_is_locked)
     {
      //--- フォームオブジェクトの色の変更
      ChangeObjectsColor();
     }
  }

他のコントロールについては、現在、機能をテストできる唯一の要素はメニュー項目です。マウスカーソルがホバーしたときのメニュー項目の色の変更は、それが取り付けられているフォームの状態に依存します。よって、下記のコードにあるようにCMenuItemクラスではそのチェックも必要です。

//+------------------------------------------------------------------+
//| タイマー                                                            |
//+------------------------------------------------------------------+
void CMenuItem::OnEventTimer(void)
  {
//--- ウィンドウが利用可能な場合
   if(!m_wnd.IsLocked())
     {
      //--- コンテキストメニューが無効な場合
      if(!m_context_menu_state)
         //--- フォームオブジェクトの色の変更
         ChangeObjectsColor();
     }
  }

フォームCContextMenu::Show() メソッドでコンテキストメニューが表示された時点でブロックされなければなりません。

//+------------------------------------------------------------------+
//| コンテキストメニューを表示する                                             |
//+------------------------------------------------------------------+
void CContextMenu::Show(void)
  {
//--- 要素がすでに表示されている場合は終了する
//--- コンテキストメニューのオブジェクトを表示する
//--- メニュー項目を表示する
//--- 目に見える要素の状態を割り当てる
//--- コンテキストメニューの状態
//--- 状態を1つ前のノードに記録する
//--- フォームをブロックする
   m_wnd.IsLocked(true);
  }

ブロック解除にはCWindow::IsLocked()メソッドをCContextMenu::Hide()メソッドで呼び出すのが十分に思えるかもしれません。このオプションは、いくつかのコンテキストメニューが同時に開くことがであるので適していません。すべてが同時に閉じられるわけではありません。開かれたコンテキストメニューが全部一緒に閉じる例を思い出してみましょう。それにはいくつかの条件が満たされる必要があります。例は、CContextMenu::CheckHideContextMenus()メソッドで条件がチェックされた後で、すべてのコンテキストメニューを閉じるシグナルが送信されるときです。二番目の例はON_CLICK_MENU_ITEMイベントが処理されているときのCContextMenu::ReceiveMessageFromMenuItem()メソッドです。 

これらのメソッドにフォームのブロック解除を追加します。下記はこれらのメソッドの短縮版です。コメントは、黄色で追加されたコードを加える場所を識別するのに役立ちます。

//+------------------------------------------------------------------+
//| すべてのコンテキストメニューを閉じる条件をチェックする                    |
//+------------------------------------------------------------------+
void CContextMenu::CheckHideContextMenus(void)
  {
//--- カーソルがコンテキストメニューエリアまたは前のノードの領域内にある場合終了する
//--- カーソルがこれらの要素の領域の外にある場合
//    そのアクティブにされたオープンコンテキストメニューがあるかのチェックが必要
//--- そのためにコンテキストメニュー項目のリストを反復処理して
//    コンテキストメニューを含むメニュー項目を識別する
//--- フォームのブロックを解除する
   m_wnd.IsLocked(false);
//--- すべてのコンテキストメニューを隠すシグナルを送信する
  }
//+------------------------------------------------------------------+
//--- メニュー項目から処理のためのメッセージを受信する
//+------------------------------------------------------------------+
void CContextMenu::ReceiveMessageFromMenuItem(const int id_item,const int index_item,const string message_item)
  {
//--- Iメッセージがこのプログラムから受信され要素のIDが一致した場合
//--- コンテキストメニューを隠す
//--- フォームのブロックを解除する
   m_wnd.IsLocked(false);
  }

この段階で、すべてのファイルがコンパイルされ、以前テストしたEAがチャート上に読み込まれた場合、コンテキストメニューが開くとすぐに、はすべてが期待とまったく異なった動作していることがわかります。アクティブにされたコンテキストメニューでのメニュー項目を含めたすべてのメニュー項目の色の変更が防止されています。こんなはずではありません。このような場合には、コンテキストメニューには、その項目の色を変更するための独自のメソッドを持つ必要があります。CContextMenu クラスにそのようなメソッドを作り、下記のコードで見られるようにタイマーで呼び出します

class CContextMenu : public CElement
  {
public:
   //--- カーソルが上をホバリングしたときメニュー項目の色を変更する
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| タイマー                                                            |
//+------------------------------------------------------------------+
void CContextMenu::OnEventTimer(void)
  {
//--- カーソルが上をホバリングしたときメニュー項目の色を変更する
   ChangeObjectsColor();
  }
//+------------------------------------------------------------------+
//| カーソルが上をホバ-したときのオブジェクトの色の変更   |
//+------------------------------------------------------------------+
void CContextMenu::ChangeObjectsColor(void)
  {
//--- コンテキストメニューが無効ならば終了する
   if(!m_context_menu_state)
      return;
//--- すべてのメニュー項目を反復処理する
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- メニュー項目の色を変更する
      m_items[i].ChangeObjectsColor();
     }
  }

これで、すべてが、設計されたように動作するはずです。

図3。フォームと現在アクティブなもの以外のすべてのコントロールをブロックするテスト

図3。フォームと現在アクティブなもの以外のすべてのコントロールをブロックするテスト

 

 


メインメニューとのコミュニケーション法

メインメニューを作成するCMenuBarクラスの開発を続けましょう。残りの部分は要素の管理です。他のプログラムの例で、メインメニューがどのように機能するかを詳細に検討してみましょう。プログラムが読み込まれた後、メインメニューはデフォルトで無効になっています。この状態では、マウスカーソルがホバーするとメインメニュー項目は単に強調表示されます。項目のいずれかがクリックされると、メインメニューがアクティブになり、クリックされた項目のコンテキストメニューが表示されます。メインメニューがこのようにアクティブにされると、マウスカーソルがメニュー項目に沿って移動している場合、コンテキストメニューはマウスカーソルがホバーしている項目に応じて自動的に変わります。

ライブラリにこのような動作を実装する前に、3つのコンテキストメニューを作成してメインメニューの項目に取り付けましょう。記事のスペースを節約するために、そのうち一つの短縮版のみを考慮します。EAの完全なファイルは本稿末尾にあります。 

以下のコードには重要な行のみが含まれています。どのように以前のノードへのポインタがコンテキストメニューに渡されているかとコンテキストメニューポインタがメインメニューに格納されているかにご注意下さい。項目下部からの座標計算はメインメニューのコンテキストメニューのプロパティが設定されているときに設定されます。他のすべての点で、コンテキストメニューの作成は、以前考慮されたものと変わりません。

//+------------------------------------------------------------------+
//| コンテキストメニューを作成する                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateMBContextMenu1(void)
  {
//--- コンテキストメニューの3項目
//--- ウィンドウポインタを格納する
   m_mb_contextmenu1.WindowPointer(m_window);
//--- 1つ前のノードへのポインタを格納する
   m_mb_contextmenu1.PrevNodePointer(m_menubar.ItemPointerByIndex(0));
//--- コンテキストメニューを指定されたメニュー項目に取り付ける
   m_menubar.AddContextMenuPointer(0,m_mb_contextmenu1);
//--- 項目名の配列
//--- 使用可能モードのラベル配列
//--- ブロックモードのラベル配列
//--- 項目の種類の配列
//--- 作成前にプロパティを設定する
   m_mb_contextmenu1.FixSide(FIX_BOTTOM);
//--- コンテキストメニューに項目を追加する
//--- 2番目の項目の後の区切り線
//--- 2番目の項目を非アクティブにする
//--- コンテキストメニューを作成する
   if(!m_mb_contextmenu1.CreateContextMenu(m_chart_id,m_subwin))
      return(false);
//--- オブジェクトをオブジェクトグループの共通配列に追加する
   CWndContainer::AddToElementsArray(0,m_mb_contextmenu1);
   return(true);
  }

ここで、メインメニューのСMenuBarクラスでイベントハンドラを設定しましょう。メニュー項目クリックの処理から始めます。その前にCMenuItem CContextMenuクラスと同じように、 OnClickMenuItem()メソッドと、クリックされたオブジェクトの名前からインデックスとメニュー項目識別子を抽出するメソッドが必要です。 

識別メソッドはCContextMenuクラスの場合と同じです。ですがメニュー項目クリックの処理の違いはСMenuBar クラスにあります。識別子のチェックには、オブジェクト名から得られたインデックスによるコンテキストメニューのポインタの正しさのチェックが続きます。ポインタが存在しない場合、すべての開いたコンテキストメニューを閉じるためのシグナルが送信されます。ポインタがある場合、すべてのコンテキストメニューを閉じるためのシグナルは項目のクリックが現在のコンテキストメニューを閉じるためだった場合にのみ送信されます。  

class CMenuBar : public CElement
  {
private:
   //--- メニュー項目クリックの処理
   bool              OnClickMenuItem(const string clicked_object);
   //--- メニュー項目名からの(1) 識別子と (2) インデックスの取得
   int               IdFromObjectName(const string object_name);
   int               IndexFromObjectName(const string object_name);
   //---
  };
//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- メインメニュー項目のマウス左クリックの処理
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickMenuItem(sparam))
         return;
     }
  }
//+------------------------------------------------------------------+
//| メインメニュー項目のクリック                                  |
//+------------------------------------------------------------------+
bool CMenuBar::OnClickMenuItem(const string clicked_object)
  {
//--- クリックがメニュー項目に対してでない場合は終了する
   if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0)
      return(false);
//--- オブジェクト名から識別子とインデックスを取得する
   int id    =IdFromObjectName(clicked_object);
   int index =IndexFromObjectName(clicked_object);
//--- 識別子が一致しない場合は終了する
   if(id!=CElement::Id())
      return(false);
//--- コンテキストメニューポインタがある場合
   if(CheckPointer(m_contextmenus[index])!=POINTER_INVALID)
     {
      //--- メインメニューの状態は、コンテキストメニューの視認性に依存する
      m_menubar_state=(m_contextmenus[index].ContextMenuState())?false : true;
      //--- フォームの状態を設定する
      m_wnd.IsLocked(m_menubar_state);
      //--- メインメニューが無効の場合
      if(!m_menubar_state)
         //--- すべてのコンテキストメニューを隠すシグナルを送信する
         ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
     }
//--- コンテキストメニューポインタがない場合
   else
     {
      //--- すべてのコンテキストメニューを隠すシグナルを送信する
      ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,"");
     }
//---
   return(true);
  }

覚えているようにON_HIDE_CONTEXTMENUSイベントの処理はCWndEventsクラスで行われます。CWndEvents::OnHideContextMenus()メソッドにあと一つのサイクルを追加し、ベースに存在するメインメニューをスイッチオフする必要があります。

//+------------------------------------------------------------------+
//| 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();
//---メインメニューを無効にする
   int menu_bars_total=CWndContainer::MenuBarsTotal(0);
   for(int i=0; i<menu_bars_total; i++)
      m_wnd[0].m_menu_bars[i].State(false);
//---
   return(true);
  }

すべてのファイルをコンパイルし、チャート上にEAを読み込むと、コンテキストメニューはメインメニュー項目がクリックされたときに開いて2回目のクリックの後に閉じます。

図4。メインメニューからのコンテキストメニューの呼び出しのテスト

図4。メインメニューからのコンテキストメニューの呼び出しのテスト

 

ここでは、メインメニューがアクティブなときマウスカーソルを移動させることによってコンテキストメニューを切り替えるメソッドを実装します。この実装の仕方はWindows アプリケーションに似ています。これには、アクティブにされたときにメニュー項目を強調表示するメソッドと補助的な、アクティブなメインメニューのアクティブな(フォーカスされている)項目を定義するメソッドが必要です。 

class CMenuBar : public CElement
  {
public:
   //--- マウスカーソルが上をホバ-したときに色を変更する
   void              ChangeObjectsColor(void);
   //---
private:
   //--- メインメニューのアクティブな項目を返す
   int               ActiveItemIndex(void);
   //---
  };
//+------------------------------------------------------------------+
//| カーソルが上をホバ-したときのオブジェクトの色の変更   |
//+------------------------------------------------------------------+
void CMenuBar::ChangeObjectsColor(void)
  {
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].ChangeObjectsColor();
  }
//+------------------------------------------------------------------+
//| アクティブにされたメニュー項目のインデックスを返す                     |
//+------------------------------------------------------------------+
int CMenuBar::ActiveItemIndex(void)
  {
   int active_item_index=WRONG_VALUE;
//---
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- 項目がフォーカスされている場合
      if(m_items[i].MouseFocus())
        {
         //--- インデックスを格納しループを停止する
         active_item_index=i;
         break;
        }
     }
//---
   return(active_item_index);
  }

加えて、メインメニューがアクティブなとき、カーソルをホバーしてコンテキストメニューの切り替えをするメソッドを作成します。このメソッドをSwitchContextMenuByFocus()と名付けます。メインメニューのアクティブな項目のインデックスは、このメソッドに渡されます。このインデックスは、表示されるコンテキストメニューを定義することができます。他のすべてのコンテキストメニューは隠されます。同時に、メインメニュー項目から呼び出されれた開いているコンテキストメニューがあるかのチェックが行われます。あった場合ON_HIDE_BACK_CONTEXTMENUSカスタムイベントが生成されます。このイベントの詳細はすでに本稿で検討されました。

コンテキストメニューが隠されると、2つのメインメニュー項目が同時に強調表示されるのを避けるためにメニュー項目の色のリセットが必要です。

class CMenuBar : public CElement
  {
private:
   //--- カーソルをホバーしてメインメニューのコンテキストメニューをスイッチする
   void              SwitchContextMenuByFocus(const int active_item_index);
   //---
  };
//+------------------------------------------------------------------+
//| カーソルをホバーしてメインメニューのコンテキストメニューをスイッチする
  |
//+------------------------------------------------------------------+
void CMenuBar::SwitchContextMenuByFocus(const int active_item_index)
  {
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- この項目がコンテキストメニューを持っていない場合、次の項目メニューに移動する
      if(::CheckPointer(m_contextmenus[i])==POINTER_INVALID)
         continue;
      //--- 指定されたメニュー項目が見つかったら、そのコンテキストメニューを表示する
      if(i==active_item_index)
         m_contextmenus[i].Show();
      //--- 他のコンテキストメニューを隠す
      else
        {
         CContextMenu *cm=m_contextmenus[i];
         //--- 他のコンテキストメニューをから開かれたコンテキストメニューを隠す
         //    現在のコンテキストメニューの項目を反復処理してあるかどうかを見る
         int cm_items_total=cm.ItemsTotal();
         for(int c=0; c<cm_items_total; c++)
           {
            CMenuItem *mi=cm.ItemPointerByIndex(c);
            //--- ポインタが不正な場合次のメニュー項目に移る
            if(::CheckPointer(mi)==POINTER_INVALID)
               continue;
            //--- コンテキストメニューがない場合次のメニュー項目に移る
            if(mi.TypeMenuItem()!=MI_HAS_CONTEXT_MENU)
               continue;
            //--- コンテクストメニューがあってアクティブな場合
            if(mi.ContextMenuState())
              {
               //--- このコンテクストメニューから開かれたすべてのコンテクストメニューを閉じるシグナルを送信する
               ::EventChartCustom(m_chart_id,ON_HIDE_BACK_CONTEXTMENUS,CElement::Id(),0,"");
               break;
              }
           }
         //--- メインメニューのコンテクストメニューを隠す
         m_contextmenus[i].Hide();
         //--- メニュー項目の色をリセットする
         m_items[i].ResetColors();
        }
     }
  }

ここで、いま作ったばかりの新しいメソッドを CMenuBarクラスのCHARTEVENT_MOUSE_MOVEイベントハンドラに追加してマウスカーソルの動きのイベントをチェックします。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- メインメニューがアクティブにされていない場合は終了する
      if(!m_menubar_state)
         return;
      //--- メインメニューのアクティブにされた項目のインデックスを取得する
      int active_item_index=ActiveItemIndex();
      if(active_item_index==WRONG_VALUE)
         return;
      //--- フォーカスが変わったら色を変える
      ChangeObjectsColor();
      //--- メインメニューのアクティブ項目によってコンテキストメニューを変える
      SwitchContextMenuByFocus(active_item_index);
      return;
     }
  }

 


メインメニューの最終テスト

ここで、本稿で行ったすべてをテストすることができます。フォームにいくつかの独立したメニュー項目を追加します。メインメニューにあと一つの内部コンテキストメニューを追加し、以下のスクリーンショットが示すように、3番目のコンテキストメニューの2番目の項目に取り付けます。

ファイルをコンパイルしてEAをチャートに読み込みます。下のスクリーンショットと同じ結果を見るには、本稿末尾に添付されたファイルを読み込むことができます。

図5。メインメニューの一般的なテスト

図5。メインメニューの一般的なテスト


本稿末尾に添付されたファイルには、上記のスクリーンショットのEAと同様のグラフィカルインターフェースを使用してテストできるインディケータも含まれています。MetaTrader 4取引プラットフォームでテストできるバージョンもあります。

 

 


おわりに

これはシリーズの第二部の最後の記事です。大くがカバーされましたが、グラフィカルインタフェースを開発するためのライブラリのほぼすべての構成要素を見てきました。ライブラリの現在の構造は、以下の図に示されます。詳細な説明は、第一部の最終章に記載されています。

図6。開発の現段階でのライブラリの構造

図6。開発の現段階でのライブラリの構造


ここまでたどり着いたなら、良いニュースは、最も難しい部分はもう終わったということです。このシリーズの第一二部では、グラフィカルインタフェースの開発に関する最も複雑なトピックを検討しました。これからの記事は、コントロールの作成に主に専念されます。記事は大幅に簡素化され、クラスはみな同じようなものです。

以下の現在の開発段階でのライブラリファイルと記事で考察されたプログラム(EA、インディケータ、スクリプト)の写真やファイルのアーカイブはMetaTrader 4およびMetaTrader 5ターミナルのテストのためにダウンロードできます。これらのファイルに含まれている資料の使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるるか、本稿へのコメント欄でご質問ください。 

第二部の記事(チャプター)のリストは下記の通りです。


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2207

添付されたファイル |
大きなプログラムでの注文に関する考察 大きなプログラムでの注文に関する考察
複雑なプログラムでの注文に関する考察の一般的な原則を研究していきます。
グラフィカルインタフェース  II:ライブラリのイベントハンドラの設定(チャプター3) グラフィカルインタフェース II:ライブラリのイベントハンドラの設定(チャプター3)
以前の記事には、メインメニューの構成部分を作成するためのクラスの実装が含まれています。ここで、主要な基本クラスと作成されたコントロールのクラスでイベントハンドラを細かく見ることにします。また、マウスカーソルの位置に応じたチャートの状態の管理にも特別な注意が払われます。
グラフィカルインタフェース III: シンプルなボタンと多機能ボタン(チャプター 1) グラフィカルインタフェース III: シンプルなボタンと多機能ボタン(チャプター 1)
ボタンについて考えましょう。ここでは、簡単なボタン、拡張機能を持ったボタン(アイコンボタンとスプリットボタン)、また相互接続されたボタン(ボタングループとラジオボタン)を作成するためのいくつかのクラスの例を説明していきます。そのうえ、それらの能力を拡大するためのコントロールのために既存クラスにいくつかの追加を導入します。
ユニットテストの助けを借りたコードのクオリティー向上 ユニットテストの助けを借りたコードのクオリティー向上
シンプルなプログラムにさえ信じられないようなエラーがよくあるものです。「どうしてそんなふうになったんだろう?」というのが、そのようなエラーが明らかになったとき最初に思うことです「どうしたらエラーを出さずにすむのだろう?」というのは、あまり頻繁に心に浮かばない第2の問いです。完璧に欠点のないコードを作成するのは不可能です。特に大規模なプロジェクトでは。ですが、ちょうどよいタイミングでエラーを検出するために技術を利用できるものです。本稿では、一般的なユニットテスト方法の助けを借りてコードのクオリティーを向上する方法を説明します。