English Русский 中文 Español Deutsch Português
グラフィカルインタフェース  II: 区切り線とコンテキストメニュー要素(チャプター 2)

グラフィカルインタフェース II: 区切り線とコンテキストメニュー要素(チャプター 2)

MetaTrader 5 | 18 3月 2016, 11:54
1 031 0
Anatoli Kazharski
Anatoli Kazharski

内容


 

はじめに

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

前の章では、メニュー項目を作成するためのクラスを書きました。これは独立したコントロールとしてもコンテキストおよびメインメニューの一部としても使用されています。本稿では、独立したインタフェース要素としてではなく他の多くの構成要素の一部として使用することもできる区切り線要素の作成を説明します。その後、コンテキストメニュークラスの開発に必要なものすべての詳細を考察します。それに加え、アプリケーションのすべてのグラフィカル・インターフェース要素へのポインタ格納の基本であるクラスに必要なすべての追加をご紹介します。



区切り線作成クラスの開発

多くの場合、コンテキストメニューでは、異なるタイプのメニュー項目のほかに1つのインターフェース要素が存在します。それが区切り線です。この要素はコンテキストメニュー以外にも存在することがあります。例としてMetaTrader取引端末のステータスバーとMetaEditorコードエディタには縦の区切り線があります。これが、このオブジェクトが他のコントロールに使用されたりグラフィカルインタフェースの別個の要素として使用することができるように別のクラスを作成する理由です。

ボリュームの錯覚を与えるためには、区切り線は、少なくとも2つの部分から構成されなければなりません。1本の線が背景よりも明るく、他方が暗い場合、これは、表面上の溝の視覚効果を作成します。区切り線の作成には2つの方法があります。(1)Objects.mqhファイルに既存する2つのCRectLabel型のプリミティブオブジェクト。または (2) OBJ_BITMAP_LABEL型のオブジェクトの作成と描画キャンパスとしての使用。2番目のオプションを使用してみましょう。標準ライブラリは、描画にはCCanvasクラスを使用するよう推薦します。このクラスには、既に設計の実装を大幅に簡素化し時間を節約する、単純な幾何学図形を描画するために必要なすべてのメソッドが既に含まれています。 

CCanvasクラスは、やはりObjects.mqhファイルに存在するプリミティブオブジェクトと似た使い方ができるように組み込まれなければなりません。これはCCanvasクラスをCChartObjectBmpLabelクラスの継承とすることで簡単にできます。後のプログラムのコンパイル時にはエラーや警告がないようにCCanvas クラスのコードには小規模の変更を導入しなくてはなりません。これは、CChartObjectBmpLabel クラスの基本クラスであるCCanvasクラスとCChartObjectクラス両方にm_chart_id フィールド(変数)があるからです。その結果、コンパイラは、このような名前の変数がすでに存在していると警告を与えます。

図1。そのような名前の変数がすでに存在しているというコンパイラからの警告

図1。コンパイラからの警告

 

実際には、このような警告は重大エラーとはならず、コンパイルはこれにもかかわらず行われます。しかし、プログラムの作業に及ぼされる潜在的な影響が不明であるため、このような状況を避けることが推奨されます。これを原則として想定して従いましょう。そのうえ、CCanvas クラスがCChartObjectBmpLabelクラスから派生するので、Canvas.mqh ファイルの変更はどちらにしても必要です。この方法を使って、簡単に永続化する警告を取り除くことができます。単にm_chart_id変数をCCanvasクラスから削除します。標準ライブラリのクラスに変化を導入するにあたって、次の端末更新の際に標準ライブラリのファイルが更新され、変更が無効化する可能性があることに留意しなければいけません。したがって、CCanvas クラスを変更せずに目標を達成することはできないので、コピーを作成し、私たちのライブラリのファイルが配置されているディレクトリに配置します。

#IncludeフォルダにCanvasフォルダを作成します。СCanvasクラスを含むファイルのコピーを作成し、名前をCustomCanvas.mqhに変え、クラスをCCustomCanvasと名付けます。標準ライブラリのChartObjectsBmpControls.mqhファイルをCustomCanvas.mqhファイルに含み、 CCustomCanvasクラスをCChartObjectBmpLabelクラスの派生クラスにします。そして、m_chart_id変数をCCustomCanvasクラスの本体とコンストラクタから削除します。

//+------------------------------------------------------------------+
//|                                                 CustomCanvas.mqh |
//|                   Copyright 2009-2013, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Files\FileBin.mqh>
#include <Controls\Rect.mqh>
#include <ChartObjects\ChartObjectsBmpControls.mqh>

//...

//+------------------------------------------------------------------+
//| CCustomCanvas クラス                                              |
//| 使用法:動的なリソースを扱うためのクラス                               |
//+------------------------------------------------------------------+
class CCustomCanvas : public CChartObjectBmpLabel
  {
//...

ここでCustomCanvas.mqhファイルをObjects.mqhファイルに含みます。

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Enums.mqh"
#include "Defines.mqh"
#include "..\Canvas\CustomCanvas.mqh"
#include <ChartObjects\ChartObjectsBmpControls.mqh>
#include <ChartObjects\ChartObjectsTxtControls.mqh>

ここでCRectCanvasクラスを作成します。これはCCustomCanvasクラスの派生クラスです。CRectCanvasクラスはObjects.mqhファイルにある他のクラスと似ています。その内容は、前回の記事で考えられました。ここでは、開発中のライブラリの一部となる任意の他のインターフェース要素を描くためにそれを使用することができます。 

CSeparateLineクラス開発に必要なものは全部そろいました。このクラスは区切り線の作成に意図されています。SeparateLine.mqhファイルをControlsフォルダに作成します。Element.mqhWindow.mqhファイルを含みます。そして、次の一連のステップに従います。

1) CSeparateLineクラスを作成する

2)このクラスでは、要素が取り付けられるフォームへのポインタを宣言し、ポインタを格納するためのメソッドを作成する

3) CRectCanvasクラスのインスタンスを作成する

4) 要素の管理に使用なすべての要素のための標準的な視覚的なメソッドを宣言して実装する

//+------------------------------------------------------------------+
//|                                                 SeparateLine.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| 区切り線作成クラス                                                  |
//+------------------------------------------------------------------+
class CSeparateLine : public CElement
  {
private:
   //--- 要素が取り付けられるフォームへのポインタ
   CWindow          *m_wnd;
   //--- 区切り線作成のオブジェクト
   CRectCanvas       m_canvas;
   //---
public:
                     CSeparateLine(void);
                    ~CSeparateLine(void);
   //--- 渡されたフォームのポインタを格納する
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }
   //---
public:
   //--- チャートイベントハンドラ
   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);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CSeparateLine::CSeparateLine(void)
  {
//--- 要素クラスの名前を基本クラスに格納する  
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CSeparateLine::~CSeparateLine(void)
  {
  }
//+------------------------------------------------------------------+

区切り線の外観を設定するために3つのメソッドを作成してみましょう。これらのメソッドは下記を設定するために使用されます。

  • 区切り線の種類 (1) 横 (2) 縦
  • 暗い部分の色
  • 明るい部分の色

種類を指定するために使用される列挙はEnums.mqhファイルに追加されなければなりません。

//+------------------------------------------------------------------+
//| 区切り線の種類の列挙                                                |
//+------------------------------------------------------------------+
enum ENUM_TYPE_SEP_LINE
  {
   H_SEP_LINE =0,
   V_SEP_LINE =1
  };

CSeparateLine クラスに対応する変数やメソッドを追加し、コンストラクタ内のデフォルト値で初期化を行うことができます。

class CSeparateLine : public CElement
  {
private:
   //--- プロパティ
   ENUM_TYPE_SEP_LINE m_type_sep_line;   
   color             m_dark_color;
   color             m_light_color;
   //---
public:
   //--- (1) 線の種類 (2) 線の色
   void              TypeSepLine(const ENUM_TYPE_SEP_LINE type) { m_type_sep_line=type; }
   void              DarkColor(const color clr)                 { m_dark_color=clr;     }
   void              LightColor(const color clr)                { m_light_color=clr;    }
   //---
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CSeparateLine::CSeparateLine(void) : m_type_sep_line(H_SEP_LINE),
                                     m_dark_color(clrBlack),
                                     m_light_color(clrDimGray)
  {
  }

区切り線を作成するメソッドおよびキャンバス上に描画される要素およびメソッドを追加する必要があるだけです。下記のパラメータは要素を作成publicメソッドCreateSeparateLine()に渡される必要があります。これはカスタムアプリケーションで呼び出されます。

  • チャート識別子
  • チャートウィンドウの番号
  • 線のインデックス番号。これは、複数の区切り線が、ループで反復して、コンテキストメニューや他のインタフェース要素で作成されなければならない場合に必要です。この場合、要素識別子のみでは、グラフィカルオブジェクトの一意の名前を作成するのに十分ではありません。
  • 座標
  • サイズ

class CSeparateLine : public CElement
  {
public:
   //--- 区切り線の作成
   bool              CreateSeparateLine(const long chart_id,const int subwin,const int index,
                                        const int x,const int y,const int x_size,const int y_size);
   //---
private:
   //--- 区切り線を描画するキャンバスの作成
   bool              CreateSepLine(void);
   //--- 区切り線の描画
   void              DrawSeparateLine(void);
   //---
  };

CreateSeparateLine()メソッドのコードは原則的に他の似たメソッド(例えばCMenuItemクラスのもの) と変わりません。これが次にCreateSepLine()メソッドのコードを考える理由です。 

このタイプのすべての他のメソッドと同様に、グラフィックオブジェクトの名前は、最初に設定されています。その後、グラフィックオブジェクト(キャンバス)が作成され、その上に描画がされます。OBJ_BITMAP_LABEL型のオブジェクトの作成に CreateBitmapLabel()メソッドが使用され、それがCCustomCanvasクラスに属することには留意されるべきです。このクラスでは、基本クラスのCChartObject::Attach()メソッドがオブジェクト作成の直後に使われるCChartObjectBmpLabel クラスと違って、オブジェクトの作成時にチャートにオブジェクトを取り付けることはできません。これは自分でやることになります。CCustomCanvasクラスはCChartObjectBmpLabelクラスから派生したので、その基本クラスからCChartObject::Attach()メソッドがアクセスできます。チャートに接続されていないオブジェクトの管理は不可能です。

オブジェクトが作成されてチャートに取り付けられ、プロパティが設定された後で、区切り線はカスタムキャンバスにDrawSeparateLine()メソッドで描画できます。これは下記のコードに見られます。その後オブジェクトポインタはCElement基本クラスの配列に格納されます。

//+------------------------------------------------------------------+
//| 区切り線を描画するキャンバスを作成する                                |
//+------------------------------------------------------------------+
bool CSeparateLine::CreateSepLine(void)
  {
//--- オブジェクト名の形成  
   string name=CElement::ProgramName()+"_separate_line_"+(string)CElement::Index()+"__"+(string)CElement::Id();
//--- オブジェクトの作成
   if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
      return(false);
//--- チャートへの取り付け
   if(!m_canvas.Attach(m_chart_id,name,m_subwin,1))
      return(false);
//--- プロパティ
   m_canvas.Background(false);
//--- エッジポイントからのマージン
   m_canvas.XGap(m_x-m_wnd.X());
   m_canvas.YGap(m_y-m_wnd.Y());
//--- 区切り線を描画する
   DrawSeparateLine();
//--- 配列に追加する
   CElement::AddToArray(m_canvas);
   return(true);
  }

DrawSeparateLine()メソッドのコードは簡単です。はじめに、キャンバスのサイズを取得します。CCustomCanvas::Erase()メソッドでキャンバスをクリアします。そして、縦横線のどちらを描画する必要があるかに応じて、プログラムは、対応するコードブロックに移動します。例として、横線の構築について説明します。はじめに線の二点の座標が定義され、その後、最初の線が、キャンバスの上部に描かれます。二本目の線の座標がキャンバスの下部に定義されます。キャンバスの高さが2画素に設定されている場合、線が互いに非常に近接して配置されます。キャンバスの高さを2画素以上に設定することによって、上下の線の間のマージンを手配することができます。変化を表示するには、キャンバスはメソッドの最後でCCustomCanvas::Update()メソッドを使ってレフレッシュされなければなりません。

//+------------------------------------------------------------------+
//|  区切り線を描画する                                                 |
//+------------------------------------------------------------------+
void CSeparateLine::DrawSeparateLine(void)
  {
//--- 線の座標
   int x1=0,x2=0,y1=0,y2=0;
//--- キャンバスサイズ
   int   x_size =m_canvas.X_Size()-1;
   int   y_size =m_canvas.Y_Size()-1;
//--- キャンバスをクリアする
   m_canvas.Erase(::ColorToARGB(clrNONE,0));
//--- 横線
   if(m_type_sep_line==H_SEP_LINE)
     {
      //--- 上の濃い線
      x1=0;
      y1=0;
      x2=x_size;
      y2=0;
      //---
      m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_dark_color));
      //--- 下の薄い線
      x1=0;
      x2=x_size;
      y1=y_size;
      y2=y_size;
      //---
      m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_light_color));
     }
//--- 縦線
   else
     {
      //--- 左の濃い線
      x1=0;
      x2=0;
      y1=0;
      y2=y_size;
      //---
      m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_dark_color));
      //--- 右の薄い線
      x1=x_size;
      y1=0;
      x2=x_size;
      y2=y_size;
      //---
      m_canvas.Line(x1,y1,x2,y2,::ColorToARGB(m_light_color));
     }
//--- キャンバスのレフレッシュ
   m_canvas.Update();
  }

 



区切り線取り付けのテスト

ここで動作をテストできます。前回の記事では、フォームにメニュー項目を添付しました。同じ原理に続いて、区切り線は、独立したインターフェース要素として取り付けることができます。 

フォームに要素を取り付ける工程について簡単におさらいします。

  • 要素クラスが基本にない場合、WndContainer.mqhファイルにファイルが含まれなければなりません。
  • アプリケーションのカスタムクラス(私たちの場合CProgram)はCWndContainer -> CWndEventsから派生するので、その要素のクラスのインスタンスとメソッドの作成は、要素ファイルが含まれた後に使用可能になります。
  • 次に、プログラムのグラフィカル・インターフェースが作成されるメソッドで、要素を作成するためのメソッドを呼び出します。

すべてが正しく行われている場合は、プログラムがコンパイルされてチャートに読み込まれると、結果は次のとおりです。

図2。区切り線要素のテスト

図2。区切り線要素のテスト


CSeparateLine クラスの開発はこれで完了したので、コンテキストメニュー作成クラスの実装に取り掛かります。

 


コンテキストメニュー作成クラスの開発

以前、ライブラリ開発の過程において、(1) コントロールフォー(CWindow)(2) メニュー項目コントロール(CMenuItem) (3) 区切り線要素(CSeparateLine)の3つのインターフェイス要素が作成されました。これらはプリミティブオブジェクトのみから構成されているので、単純型要素に属します。コンテキストメニューは、コントロールの複合体型(コンパウンド)に分類されます。これは、プリミティブオブジェクトだけでなく、他の要素で構成されます。これらの要素の基本クラスはCElementです。 

ContextMenu.mqhファイルをライブラリディレクトリのControlsフォルダで作成します。このファイルにはコンテキストメニューを作成するために使用されるファイルが含まれています。

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

すべてのライブラリ要素のための仮想メソッドの標準セット、フォームポインタとそれを格納するメソッドを持つCContextMenuクラスを作成します。 要素の背景にはOBJ_RECTANGLE_LABEL型のオブジェクトが必要です。これが、このオブジェクトの作成にObject.mqhファイルのCRectLabelクラスを使う理由です。メニュー項目用のCMenuItemクラスは前回の記事で作成されました。コンテキストメニューは普通複数のCMenuItemクラスを含み、その数が前もって知られていないため、クラスインスタンスの動的配列の宣言が必要です。コンテキストメニューの区切り線CSeparateLine)にも同じことが言えます。

//+------------------------------------------------------------------+
//| コンテキストメニュー作成クラス                                       |
//+------------------------------------------------------------------+
class CContextMenu : public CElement
  {
private:
   //--- 要素が取り付けられるフォームへのポインタ
   CWindow          *m_wnd;
   //--- メニュー項目作成のオブジェクト
   CRectLabel        m_area;
   CMenuItem         m_items[];
   CSeparateLine     m_sep_line[];
   //---
public:
                     CContextMenu(void);
                    ~CContextMenu(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      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);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CContextMenu::CContextMenu(void)
  {
//--- 要素クラスの名前を基本クラスに格納する
   CElement::ClassName(CLASS_NAME);
//--- コンテキストメニューはドロップダウン要素
   CElement::m_is_dropdown=true;
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CContextMenu::~CContextMenu(void)
  {
  }
//+------------------------------------------------------------------+

コンテキストメニューの外観を設定するには、対応するフィールドとメソッドが必要です。

class CContextMenu : public CElement
  {
private:
   //--- 背景プロパティ
   int               m_area_zorder;
   color             m_area_color;
   color             m_area_border_color;
   color             m_area_color_hover;
   color             m_area_color_array[];
   //--- メニュー項目プロパティ
   int               m_item_y_size;
   color             m_item_back_color;
   color             m_item_border_color;
   color             m_item_back_color_hover;
   color             m_item_back_color_hover_off;
   color             m_label_color;
   color             m_label_color_hover;
   string            m_right_arrow_file_on;
   string            m_right_arrow_file_off;
   //--- 区切り線プロパティ
   color             m_sepline_dark_color;
   color             m_sepline_light_color;
   //---
public:
   //--- メニュー項目の数
   int               ItemsTotal(void)                         const { return(::ArraySize(m_items));         }
   //--- コンテキストメニューの外観を設定するメソッド
   //    コンテキストメニューの背景色
   void              MenuBackColor(const color clr)                 { m_area_color=clr;                     }
   void              MenuBorderColor(const color clr)               { m_area_border_color=clr;              }
   //--- (1) 高さ (2) 背景色 (3) メニュー項目の枠の色 
   void              ItemYSize(const int y_size)                    { m_item_y_size=y_size;                 }
   void              ItemBackColor(const color clr)                 { m_item_back_color=clr;                }
   void              ItemBorderColor(const color clr)               { m_item_border_color=clr;              }
   //---  (1) 利用可能 および(2) ブロックされたメニュー項目のマウスホバー時の背景色
   void              ItemBackColorHover(const color clr)            { m_item_back_color_hover=clr;          }
   void              ItemBackColorHoverOff(const color clr)         { m_item_back_color_hover_off=clr;      }
   //--- (1) 標準および (2) フォーカスが置かれた時のテキストの色 
   void              LabelColor(const color clr)                    { m_label_color=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              SeparateLineDarkColor(const color clr)         { m_sepline_dark_color=clr;             }
   void              SeparateLineLightColor(const color clr)        { m_sepline_light_color=clr;            }
   //---
  };

メニュー項目とそのコンテキストメニューは接続される必要があります。さもないと、それらの要素を正しく管理することは不可能となります。正確に言うと、コンテキストメニューとその項目は、それが接続されているメニュー項目、つまり1つ前のノード、にアクセスできる必要があります。これはCContextMenuCMenuItem両クラスがCMenuItem型ポインタとポインタを格納および取得するメソッドを持たなければいけないことを意味します。このポインタはまた、プログラムのグラフィカル・インターフェースを作成する順序の正確さを確認するために使用されます。 これは、コンテキストメニュー作成のメソッドが考察されるときに、後で示されます。 

CContextMenuクラスにポインタ、その格納と取得のメソッドを追加します。

class CContextMenu : public CElement
  {
private:
   //--- 1つ前のノードへのポインタ
   CMenuItem        *m_prev_node;
   //---
public:
   //--- 1つ前のノードへのポインタの取得と格納
   CMenuItem        *PrevNodePointer(void)                    const { return(m_prev_node);                  }
   void              PrevNodePointer(CMenuItem &object)             { m_prev_node=::GetPointer(object);     }
   //---
  };


CMenuItemクラスにも同じものが追加されなければなりません。

class CMenuItem : public CElement
  {
private:
   //--- 1つ前のノードへのポインタ
   CMenuItem        *m_prev_node;
   //---
public:
   //--- 1つ前のノードへのポインタの取得と格納
   CMenuItem        *PrevNodePointer(void)                    const { return(m_prev_node);                  }
   void              PrevNodePointer(CMenuItem &object)             { m_prev_node=::GetPointer(object);     }
   //---
  };

グラフィックインターフェースはアプリケーションのカスタムクラス(CProgram)で構築されます。コンテキストメニューの作成時には、コンテキストメニュー内の意図された項目数とすべての項目に共通しないいくつかのパラメータのユニークな値を指定するメソッドが必要になります。CContextMenu::AddItem()メソッドを書きましょう。このメソッドはパラメータとして(1) メニュー項目テキスト (2) 使用できる項目のラベルのアイコンへのパス (3) ブロックされた項目のアイコンへのパス (4) メニュー項目の種類を受け取ります。受け取った値を格納する配列も必要です。これらの配列のサイズはCContextMenu::AddItem()メソッドが呼び出されるごとにひとつづつ増えます。

class CContextMenu : public CElement
  {
private:
   //--- メニュー項目プロパティの配列:
   //    使用可能な項目の(1) テキスト (2) レベル、(3) ブロックされた項目のラベル
   string            m_label_text[];
   string            m_path_bmp_on[];
   string            m_path_bmp_off[];
   //---
public:
   //--- コンテキストメニューの作成前に指定されたプロパティを持つメニュー項目を追加する
   void              AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type);
   //---
  };
//+------------------------------------------------------------------+
//| メニュー項目を追加する                                              |
//+------------------------------------------------------------------+
void CContextMenu::AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type)
  {
//--- 配列サイズを一要素で増やす
   int array_size=::ArraySize(m_items);
   ::ArrayResize(m_items,array_size+1);
   ::ArrayResize(m_label_text,array_size+1);
   ::ArrayResize(m_path_bmp_on,array_size+1);
   ::ArrayResize(m_path_bmp_off,array_size+1);
//--- 受け取ったパラメータの値を格納する
   m_label_text[array_size]   =text;
   m_path_bmp_on[array_size]  =path_bmp_on;
   m_path_bmp_off[array_size] =path_bmp_off;
//--- メニュー項目の種類の設定
   m_items[array_size].TypeMenuItem(type);
  }


コンテキストメニューに区切り線を追加するには、この線が設定される後のメニュー項目のインデックス番号を格納する配列が必要になります。メニュー項目のインデックス番号はCContextMenu::AddSeparateLine()メソッドに渡されます。下記がコードです。

class CContextMenu : public CElement
  {
private:
   //--- その後に区切り線が配置されるメニュー項目のインデックス番号の配列
   int               m_sep_line_index[];
   //---
public:
   //--- コンテキストメニューの作成前に指定された項目の後に区切り線を追加する
   void              AddSeparateLine(const int item_index);
   //---
  };
//+------------------------------------------------------------------+
//| 区切り線を追加する                                                  |
//+------------------------------------------------------------------+
void CContextMenu::AddSeparateLine(const int item_index)
  {
//--- 配列サイズを一要素で増やす
   int array_size=::ArraySize(m_sep_line);
   ::ArrayResize(m_sep_line,array_size+1);
   ::ArrayResize(m_sep_line_index,array_size+1);
//--- インデックス番号を格納する
   m_sep_line_index[array_size]=item_index;
  }


メニュー項目のインデックスを指定した後は、(1) メニュー項目のポインタ (2) 説明(表示されるテキストと (3) 種類を取得するメソッドが必要になります。すべてのメソッドでは、プロパティの値を返す前に、配列のサイズが超えられたかどうかのチェックが行われインデックスが調整されます。これは、渡されたインデックスが配列サイズを超えた場合に最後の項目が呼び出され、インデックスがゼロより小さい場合に最初の項目が呼び出されるように実装されています。 

class CContextMenu : public CElement
  {
public:
   //--- コンテキストメニューから項目ポインタを返す
   CMenuItem        *ItemPointerByIndex(const int index);
   //--- 説明(表示されるテキスト)を返す
   string            DescriptionByIndex(const int index);
   //--- メニュー項目の種類を返す
   ENUM_TYPE_MENU_ITEM TypeMenuItemByIndex(const int index);
   //---
  };
//+------------------------------------------------------------------+
//| メニュー項目ポインタをインデックスで返す                               |
//+------------------------------------------------------------------+
CMenuItem *CContextMenu::ItemPointerByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- コンテキストメニューに項目がない場合、報告する
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "if the context menu has at least one item!");
     }
//--- サイズが超過した場合の調整
   int i=(index>=array_size)?array_size-1 :(index<0)?0 : index;
//--- ポインタを返す
   return(::GetPointer(m_items[i]));
  }
//+------------------------------------------------------------------+
//| 項目名をインデックスで返す                                           |
//+------------------------------------------------------------------+
string CContextMenu::DescriptionByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- コンテキストメニューに項目がない場合、報告する
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "if the context menu has at least one item!");
     }
//--- サイズが超過した場合の調整
   int i=(index>=array_size)?array_size-1 :(index<0)?0 : index;
//--- 項目の説明を返す
   return(m_items[i].LabelText());
  }
//+------------------------------------------------------------------+
//| 項目の種類をインデックスで返す                                       |
//+------------------------------------------------------------------+
ENUM_TYPE_MENU_ITEM CContextMenu::TypeMenuItemByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- コンテキストメニューに項目がない場合、報告する
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "if the context menu has at least one item!");
     }
//--- サイズが超過した場合の調整
   int i=(index>=array_size)?array_size-1 :(index<0)?0 : index;
//--- 項目の種類を返す
   return(m_items[i].TypeMenuItem());
  }

1つのコンテキストメニューに複数のラジオ項目のグループが存在することがあります。どの項目がクリックされたかの混乱を避けるために、すべてのラジオ項目は独自のグループ識別子と、グループのリストに関連したインデックスを持つ必要があります。以下の図は、一般的な指標とコンテキストメニュー要素の識別子の他に、ラジオ項目が独自の識別特性を有することを示しています。 

図3。コンテキストメニュー内の識別子と異なるグループのインデックスの概略図

図3。コンテキストメニュー内の識別子と異なるグループのインデックスの概略図

 

コンテキストメニューを形成する場合、メニュー項目の種類は、メニューがチャートに取り付けられる前に識別されなければなりません。ラジオ項目の場合、属するグループが指定される必要があります。言い換えれば、それぞれのラジオ項目の識別子を決定できるメソッドが必要です。ラジオ項目のデフォルト識別子はゼロに等しいです。このままだと、どのように多くのラジオ項目が追加されたかに関係なく、コンテキストメニューには1つのグループしか存在しません。ラジオ項目の識別子を確立し、それらのどれが現在強調表示されているかを知る必要がある場合があります。また、ラジオ項目を切り替える能力も必要です。 

それに加えて、チェックボックスを操作するためのメソッドが必要となります。チェックボックスの状態を確認し、必要に応じてその状態を変更することができます。これらのメソッドの宣言と実装は以下のコードにあります。

class CContextMenu : public CElement
  {
public:
   //--- チェックボックスの状態の(1)取得と(2) 設定
   bool              CheckBoxStateByIndex(const int index);
   void              CheckBoxStateByIndex(const int index,const bool state);
   //--- インデックスによってラジオ項目のIDを(1) 返す (2) 設定する
   int               RadioItemIdByIndex(const int index);
   void              RadioItemIdByIndex(const int item_index,const int radio_id);
   //--- (1) 選ばれたラジオ項目を返す (2) ラジオ項目を切り替える
   int               SelectedRadioItem(const int radio_id);
   void              SelectedRadioItem(const int radio_index,const int radio_id);
   //---
  };
//+------------------------------------------------------------------+
//| インデックスによってチェックボックスの状態を返す                        |
//+------------------------------------------------------------------+
bool CContextMenu::CheckBoxStateByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- コンテキストメニューに項目がない場合、報告する
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "if the context menu has at least one item!");
     }
//--- サイズが超過した場合の調整
   int i=(index>=array_size)?array_size-1 :(index<0)?0 : index;
//--- 項目の状態を返す
   return(m_items[i].CheckBoxState());
  }
//+------------------------------------------------------------------+
//| インデックスによってチェックボックスの状態を設定する                     |
//+------------------------------------------------------------------+
void CContextMenu::CheckBoxStateByIndex(const int index,const bool state)
  {
//--- 配列サイズの超過を確認する
   int size=::ArraySize(m_items);
   if(size<1 || index<0 || index>=size)
      return;
//--- 状態を設定する
   m_items[index].CheckBoxState(state);
  }
//+------------------------------------------------------------------+
//| インデックスによってラジオ項目IDを返す                                |
//+------------------------------------------------------------------+
int CContextMenu::RadioItemIdByIndex(const int index)
  {
   int array_size=::ArraySize(m_items);
//--- コンテキストメニューに項目がない場合、報告する
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "if the context menu has at least one item!");
     }
//--- サイズが超過した場合の調整
   int i=(index>=array_size)?array_size-1 :(index<0)?0 : index;
//--- 識別子を返す
   return(m_items[i].RadioButtonID());
  }
//+------------------------------------------------------------------+
//| インデックスによってラジオ項目IDを設定する                             |
//+------------------------------------------------------------------+
void CContextMenu::RadioItemIdByIndex(const int index,const int id)
  {
//--- 配列サイズの超過を確認する
   int array_size=::ArraySize(m_items);
   if(array_size<1 || index<0 || index>=array_size)
      return;
//--- 識別子を設定する
   m_items[index].RadioButtonID(id);
  }
//+------------------------------------------------------------------+
//| インデックスによってラジオ項目IDを返す                                |
//+------------------------------------------------------------------+
int CContextMenu::SelectedRadioItem(const int radio_id)
  {
//--- ラジオ項目カウンタ
   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].RadioButtonState())
            break;
         //--- ラジオ項目のカウンタを増加する
         count_radio_id++;
        }
     }
//--- インデックスを返す
   return(count_radio_id);
  }
//+------------------------------------------------------------------+
//| ラジオ項目をインデックスとIDで切り替える                               |
//+------------------------------------------------------------------+
void CContextMenu::SelectedRadioItem(const int radio_index,const int radio_id)
  {
//--- ラジオ項目カウンタ
   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(count_radio_id==radio_index)
            m_items[i].RadioButtonState(true);
         else
            m_items[i].RadioButtonState(false);
         //--- ラジオ項目のカウンタを増加する
         count_radio_id++;
        }
     }
  }

チャートにコンテキストメニューを取り付けるメソッドを作成するための準備がすべて整いました。取り付けにはは3つの段階があります。

  • コンテキストメニュー背景の作成
  • メニュー項目の作成
  • 区切り線の作成

すべての段階はprivateメソッドを必要とします。これらは後に共通のpublicメソッドから呼ばれます。クラス本体で宣言します。

class CContextMenu : public CElement
  {
public:
   //--- コンテキストメニュー作成のメソッド
   bool              CreateContextMenu(const long chart_id,const int window,const int x=0,const int y=0);
   //---
private:
   bool              CreateArea(void);
   bool              CreateItems(void);
   bool              CreateSeparateLine(const int line_number,const int x,const int y);
   //---
  };

コンテキストメニューの背景の高さは、項目と区切り線の数によって異なります。これが、この値 はコンテキストメニューの背景の設定のために設計されているCContextMenu::CreateArea()メソッドで書き換えられるので、アプリケーションクラスでこの値を設定する意味がない理由です。区切り線領域の高さは9画素に等しいこととなり、それらが占める面積を計算するために線の数はこの値で乗算しなければなりません

//+------------------------------------------------------------------+
//| コンテキストメニューの共通の領域を作成する                             |
//+------------------------------------------------------------------+
bool CContextMenu::CreateArea(void)
  {
//--- オブジェクト名の形成
   string name=CElement::ProgramName()+"_contextmenu_bg_"+(string)CElement::Id();
//--- コンテキストメニューの高さの計算は項目と区切り線の数によって異なる
   int items_total =ItemsTotal();
   int sep_y_size  =::ArraySize(m_sep_line)*9;
   m_y_size        =(m_item_y_size*items_total+2)+sep_y_size-(items_total-1);
//--- コンテキストメニューの背景を設定する
   if(!m_area.Create(m_chart_id,name,m_subwin,m_x,m_y,m_x_size,m_y_size))
      return(false);
//--- 優先順位の設定
   m_area.BackColor(m_area_color);
   m_area.Color(m_area_border_color);
   m_area.BorderType(BORDER_FLAT);
   m_area.Corner(m_corner);
   m_area.Selectable(false);
   m_area.Z_Order(m_area_zorder);
   m_area.Tooltip("\n");
//--- エッジポイントからのマージン
   m_area.XGap(m_x-m_wnd.X());
   m_area.YGap(m_y-m_wnd.Y());
//--- 背景寸法
   m_area.XSize(m_x_size);
   m_area.YSize(m_y_size);
//--- オブジェクトポインタを設定する
   CElement::AddToArray(m_area);
   return(true);
  }

区切り線はCContextMenu::CreateSeparateLine()メソッドで設定されます。行番号と座標はパラメータとしてこのメソッドに渡されるべきです。

//+------------------------------------------------------------------+
//| 区切り線を作成する                                                  |
//+------------------------------------------------------------------+
bool CContextMenu::CreateSeparateLine(const int line_number,const int x,const int y)
  {
//--- フォームポインタを格納する
   m_sep_line[line_number].WindowPointer(m_wnd);
//--- 優先順位の設定
   m_sep_line[line_number].TypeSepLine(H_SEP_LINE);
   m_sep_line[line_number].DarkColor(m_sepline_dark_color);
   m_sep_line[line_number].LightColor(m_sepline_light_color);
//--- 区切り線の作成
   if(!m_sep_line[line_number].CreateSeparateLine(m_chart_id,m_subwin,line_number,x,y,m_x_size-10,2))
      return(false);
//--- オブジェクトポインタを設定する
   CElement::AddToArray(m_sep_line[line_number].Object(0));
   return(true);
  }

CContextMenu::CreateSeparateLine()メソッドは、メニュー項目の設定のためにCContextMenu::CreateItems()メソッドで呼ばれます。座標及びこれらの要素を設定する順序は、1つのループ内で定義されます。m_sep_line_index[] 配列は以前に考察されました。コンテキストメニューを形成する際、区切り線が後に配置されるメニュー項目のインデックス番号がこの配列に格納されます。ループの現在の反復数とm_sep_line_index[] 配列内のメニュー項目のインデックス番号を比較すると、区切り線が後に配置される位置を識別することができます。 

コンテキストメニューの項目を設定する前に1つ前のノードへのポインタの格納も必要です。CContextMenu::CreateItems()メソッドのコードは詳細なコメントとともに下記に示されています。

//+------------------------------------------------------------------+
//| メニュー項目のリストを作成する                                       |
//+------------------------------------------------------------------+
bool CContextMenu::CreateItems(void)
  {
   int s =0;     // 区切り線の位置の認識
   int x =m_x+1; // X座標
   int y =m_y+1; // Y座標各メニュー項目のループで計算されます。
//--- 区切り線の数
   int sep_lines_total=::ArraySize(m_sep_line_index);
//---
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Y座標の計算
      y=(i>0)?y+m_item_y_size-1 : y;
      //--- フォームポインタを格納する
      m_items[i].WindowPointer(m_wnd);
      //--- 1つ前のノードへのポインタを追加する
      m_items[i].PrevNodePointer(m_prev_node);
      //--- 優先順位の設定
      m_items[i].XSize(m_x_size-2);
      m_items[i].YSize(m_item_y_size);
      m_items[i].IconFileOn(m_path_bmp_on[i]);
      m_items[i].IconFileOff(m_path_bmp_off[i]);
      m_items[i].AreaBackColor(m_area_color);
      m_items[i].AreaBackColorOff(m_item_back_color_hover_off);
      m_items[i].AreaBorderColor(m_area_color);
      m_items[i].LabelColor(m_label_color);
      m_items[i].LabelColorHover(m_label_color_hover);
      m_items[i].RightArrowFileOn(m_right_arrow_file_on);
      m_items[i].RightArrowFileOff(m_right_arrow_file_off);
      m_items[i].IsDropdown(m_is_dropdown);
      //--- パネルのエッジポイントからのマージン
      m_items[i].XGap(x-m_wnd.X());
      m_items[i].YGap(y-m_wnd.Y());
      //--- メニュー項目の作成
      if(!m_items[i].CreateMenuItem(m_chart_id,m_subwin,i,m_label_text[i],x,y))
         return(false);
      //--- すべての区切り線が設定されている場合は次に移動する
      if(s>=sep_lines_total)
         continue;
      //--- すべてのインデックスが一致した場合、区切り線をこの項目の後に設定する
      if(i==m_sep_line_index[s])
        {
         //--- 座標
         int l_x=x+5;
         y=y+m_item_y_size+2;
         //--- 区切り線の設定
         if(!CreateSeparateLine(s,l_x,y))
            return(false);
         //--- 次の項目のY座標の調整
         y=y-m_item_y_size+7;
         //--- 区切り線カウンタの増加
         s++;
        }
     }
   return(true);
  }

そして外部使用のためにCContextMenu::CreateContextMenu()メソッドの実装が必要です。この段階で、コンテキストメニューが外部メニューの項目や独立した単独のメニュー項目に添付しなければならない場合を考えてみましょう。それは、先に述べたように、コンテキストメニューを作成する前に、1つ前のノードへのポインタを渡さなければならないことを意味します。また、フォームポインタの有無のチェックは、1つ前のノードへのポインタの有無のチェックを行わなければなりません。ライブラリユーザにとって、これは不正確なグラフィカル・インターフェースを形成する可能性を排除する追加的な基準点です。 

別のコントロールまたは作業領域上のクリックによって起動できるように設計されているため、コンテキストメニューは、通常、作成後に隠されています。それぞれの要素のオブジェクトを隠すためにHide()メソッドが設計されています。CContextMenuクラスにも似たメソッドがあります。はじめに、コンテキストメニューのオブジェクトである背景と区切り線がそれによって隠されます。その後すべてのメニュー項目がループで隠されます。同時に、それぞれのCMenuItem::Hide()メソッドがメニュー項目に呼ばれます。この要素はそれぞれのCSeparateLine::Hide()メソッドを持っているので区切り線もまた似たように隠すことができます。しかし、これはグラフィカルオブジェクトからなる設計要素に過ぎずユーザーとの相互作用のためには設計されていないため、作成の瞬間にコンテキストメニューオブジェクトの共通配列に加えられ、対応するスープで非表示になります。

//+------------------------------------------------------------------+
//| コンテキストメニューを隠す                                           |
//+------------------------------------------------------------------+
void CContextMenu::Hide(void)
  {
//--- 要素が隠れている場合は終了する
   if(!CElement::m_is_visible)
      return;
//--- コンテキストメニューのオブジェクトを隠す 
   for(int i=0; i<ObjectsElementTotal(); i++)
      Object(i).Timeframes(OBJ_NO_PERIODS);
//--- メニュー項目を隠す
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].Hide();
//--- フォーカスをゼロにする
   CElement::MouseFocus(false);
//--- 隠れたコントロールの状態を割り当てる
   CElement::m_is_visible=false;
  }

コンテキストメニュー管理のメソッドはすべて同様の方法で構成され、ここでは考察されません。コードはこの記事に添付されたファイルで参照できます。ここでは要素を削除するCContextMenu::Delete()メソッドのコードのみを話し合います。すべてのグラフィックオブジェクトの削除に加えてコンテキストメニューの形成の際に使われた配列のすべては空にされます。これが行われないと、銘柄や時間枠が変更されるたびにメニュー項目のリストが増加します。テストの段階で、これらの行をコメントすることによってこれを試してみることができます。

//+------------------------------------------------------------------+
//| 削除                                                              |
//+------------------------------------------------------------------+
void CContextMenu::Delete(void)
  {
//--- オブジェクトの削除  
   m_area.Delete();
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
      m_items[i].Delete();
//--- 区切り線の削除
   int sep_total=::ArraySize(m_sep_line);
   for(int i=0; i<sep_total; i++)
      m_sep_line[i].Delete();
//--- コントロール配列を空にする
   ::ArrayFree(m_items);
   ::ArrayFree(m_sep_line);
   ::ArrayFree(m_sep_line_index);
   ::ArrayFree(m_label_text);
   ::ArrayFree(m_path_bmp_on);
   ::ArrayFree(m_path_bmp_off);
//--- オブジェクトの配列を空にする
   CElement::FreeObjectsArray();
  }

コンテキストメニュー作成メソッドに戻りますが、座標が1つ前のノードに関連して設定されることを言及しなければなりません。必要に応じてライブラリのユーザーがカスタム座標を設定できるようにアレンジしてみましょう。デフォルト座標はコンテキストメニューを作成するCContextMenu::CreateContextMenu()メソッドで0に設定されます。 座標は、少なくとも一方が指定されていない場合、自動的に1つ前のノードから計算されます。両方の座標が指定されている場合は、自動計算はキャンセルされます。

他のコンテキストメニューから開かれるコンテキストメニューの場合、それらの座標は自動的にメニューが接続されている項目の右側部分から計算されます。メインメニューのメニュー項目に付されたコンテキストメニューの場合、座標計算は項目の下部から行われます。このシステムを管理するためにCContextMenuクラスにはあと1つのフィールドとメソッドが必要です。Enums.mqhファイルに新しい列挙を追加しましょう。

//+------------------------------------------------------------------+
//| メニュー接続の側の列挙                                              |
//+------------------------------------------------------------------+
enum ENUM_FIX_CONTEXT_MENU
  {
   FIX_RIGHT  =0,
   FIX_BOTTOM =1
  };

対応するフィールドと座標計算モード設定のメソッドは、コンテキストメニューのクラスに追加される必要があります。座標はデフォルトでは項目の右側から計算されます。

class CContextMenu : public CElement
  {
private:
   //--- コンテキストメニューの付属の側
   ENUM_FIX_CONTEXT_MENU m_fix_side;
   //---
public:
   //--- コンテキストメニューの付属モードの設定
   void              FixSide(const ENUM_FIX_CONTEXT_MENU side)      { m_fix_side=side;                      }
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CContextMenu::CContextMenu(void) : m_fix_side(FIX_RIGHT)
  {
  }

下記がCContextMenu::CreateContextMenu() メソッドのコードです。要素の作成はそれへのポインタがある場合のみに可能です。このノードのプロパティは前記されたように対応したチェックが完了した後のみに利用可能で、これで自動的に相対座標が計算できますコンテキストメニューを隠すコードは作成コードの後に来るべきです。

//+------------------------------------------------------------------+
//| コンテキストメニューを作成する                                       |
//+------------------------------------------------------------------+
bool CContextMenu::CreateContextMenu(const long chart_id,const int subwin,const int x=0,const int y=0)
  {
//--- フォームポインタがなければ終了する
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Before creating a context menu it has to be passed  "
              "a window object using the WindowPointer(CWindow &object).");
      return(false);
     }
//--- 1つ前のノードへのポインタがなければ終了する 
   if(::CheckPointer(m_prev_node)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Before creating a context menu it has to be passed "
              "the pointer to the previous node using the CContextMenu::PrevNodePointer(CMenuItem &object) method.");
      return(false);
     }
//--- 変数の初期化
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =subwin;
//--- 座標が指定されていない場合
   if(x==0 || y==0)
     {
      m_x =(m_fix_side==FIX_RIGHT)?m_prev_node.X2()-3 : m_prev_node.X()+1;
      m_y =(m_fix_side==FIX_RIGHT)?m_prev_node.Y()-1  : m_prev_node.Y2()-1;
     }
//--- 座標が指定されている場合
   else
     {
      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(!CreateItems())
      return(false);
//--- 要素を隠す
   Hide();
   return(true);
  }

CreateMenuItem()メソッドのCMenuItemクラスに追加される条件での1つ前のノードへのポインタの有無のチェック。ポインタが存在しない場合は、独立したメニュー項目が暗示されていることを意味します。つまりこの項目はコンテキストメニューの一部ではないということです。このような項目は単純型(MI_SIMPLE)の項目かコンテキストメニューを含む項目(MI_HAS_CONTEXT_MENU)です。今すぐこれを理解することは難しいかもしれませんが、本稿の末尾に例を見たときには明らかになると思います。

下記にあるように、このコードをCMenuItem::CreateMenuItem()メソッドのフォームポインタチェックの後に配置します。

//--- 1つ前のノードへのポインタがない場合
//    コンテキストメニューの一部ではない独立したメニュー項目が暗示される
   if(::CheckPointer(m_prev_node)==POINTER_INVALID)
     {
      //--- 設定された種類が一致しなければ終了する
      if(m_type_menu_item!=MI_SIMPLE && m_type_menu_item!=MI_HAS_CONTEXT_MENU)
        {
         ::Print(__FUNCTION__," > The type of the independent menu item can be only MI_SIMPLE or MI_HAS_CONTEXT_MENU,",
                 "that is only with a context menu.\n",
                 __FUNCTION__," > The type of the menu item can be set using the CMenuItem::TypeMenuItem() method");
         return(false);
        }
     }




コンテキストメニュー取り付けのテスト

チャートにコンテキストメニューを取り付けるテストは今直ぐすることができます。下記にあるようにメニューのCContextMenuクラスを持つContextMenu.mqhファイルをライブラリに含みます。

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

CContextMenuクラスインスタンスをCProgramアプリケーションのカスタムクラスで作成し、コンテキストメニュー作成のメソッドを宣言します。フォームのエッジ点からのマージンは、それらが結合しているメニュー項目に相対して計算されるので、指定される必要はありません。

class CProgram : public CWndEvents
  {
private:
   //--- メニュー項目とコンテキストメニュー
   CMenuItem         m_menu_item1;
   CContextMenu      m_mi1_contextmenu1;
   //---
private:
#define MENU_ITEM1_GAP_X (6)
#define MENU_ITEM1_GAP_Y (25)
   bool              CreateMenuItem1(const string item_text);
   bool              CreateMI1ContextMenu1(void);
  };

ここで、コンテキストメニューを作りましょう。項目は、3つの単純型(MI_SIMPLE)のものと2つのチェックボックス型(MI_CHECKBOX)の計5つです。メソッド本体の外側に単純型の項目のラベルアイコンとなるリソースを含めます。利用可能な項目のアイコンには色がありますがブロックされたメニュー項目のアイコンは無色です。 本稿の末尾にあるリンクからのダウンロードが可能です。そして、このメソッドの最初に、フォームとコンテキストメニューの1つ前のノードへのポインタを格納します。これらのアクションがないと、グラフィカルインターフェイスは作成されず、プログラムがチャートから削除されます。そして(1)項目の説明(表示されたテキスト)(2)利用可能と(3)ブロックされた状態のアイコン、と(4)項目の種類 の配列をフォローしてください。. その後、すべての項目の共通プロパティを指定してから、CContextMenu::AddItem()メソッドを使用してループ内でコンテキストメニューのクラスに追加する必要があります。区切り線を2番目の項目(インデックス1)の後に追加します。これらの全ての操作が完了した後、コンテキストメニューは、チャートに取り付けることができます。メソッドの最後に、ベースへの要素ポインタを追加します。メソッドのコードは下記です。

//+------------------------------------------------------------------+
//| コンテキストメニューを作成する                                       |
//+------------------------------------------------------------------+
#resource "\\Images\\Controls\\coins.bmp"
#resource "\\Images\\Controls\\coins_colorless.bmp"
#resource "\\Images\\Controls\\line_chart.bmp"
#resource "\\Images\\Controls\\line_chart_colorless.bmp"
#resource "\\Images\\Controls\\safe.bmp"
#resource "\\Images\\Controls\\safe_colorless.bmp"
//---
bool CProgram::CreateMI1ContextMenu1(void)
  {
//--- コンテキストメニューの5項目
#define CONTEXTMENU_ITEMS1 5
//--- ウィンドウポインタを格納する
   m_mi1_contextmenu1.WindowPointer(m_window);
//--- 1つ前のノードへのポインタを格納する
   m_mi1_contextmenu1.PrevNodePointer(m_menu_item1);
//--- 項目名の配列
   string items_text[CONTEXTMENU_ITEMS1]=
     {
      "ContextMenu 1 Item 1",
      "ContextMenu 1 Item 2",
      "ContextMenu 1 Item 3",
      "ContextMenu 1 Item 4",
      "ContextMenu 1 Item 5"
     };
//--- 使用可能モードのラベル配列
   string items_bmp_on[CONTEXTMENU_ITEMS1]=
     {
      "Images\\Controls\\coins.bmp",
      "Images\\Controls\\line_chart.bmp",
      "Images\\Controls\\safe.bmp",
      "",""
     };
//--- ブロックモードのラベル配列
   string items_bmp_off[CONTEXTMENU_ITEMS1]=
     {
      "Images\\Controls\\coins_colorless.bmp",
      "Images\\Controls\\line_chart_colorless.bmp",
      "Images\\Controls\\safe_colorless.bmp",
      "",""
     };
//--- 項目の種類の配列
   ENUM_TYPE_MENU_ITEM items_type[CONTEXTMENU_ITEMS1]=
     {
      MI_SIMPLE,
      MI_SIMPLE,
      MI_SIMPLE,
      MI_CHECKBOX,
      MI_CHECKBOX
     };
//--- 作成前にプロパティを設定する
   m_mi1_contextmenu1.XSize(160);
   m_mi1_contextmenu1.ItemYSize(24);
   m_mi1_contextmenu1.AreaBackColor(C'240,240,240');
   m_mi1_contextmenu1.AreaBorderColor(clrSilver);
   m_mi1_contextmenu1.ItemBackColorHover(C'240,240,240');
   m_mi1_contextmenu1.ItemBackColorHoverOff(clrLightGray);
   m_mi1_contextmenu1.ItemBorderColor(C'240,240,240');
   m_mi1_contextmenu1.LabelColor(clrBlack);
   m_mi1_contextmenu1.LabelColorHover(clrWhite);
   m_mi1_contextmenu1.RightArrowFileOff("Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp");
   m_mi1_contextmenu1.SeparateLineDarkColor(C'160,160,160');
   m_mi1_contextmenu1.SeparateLineLightColor(clrWhite);
//--- コンテキストメニューに項目を追加する
   for(int i=0; i<CONTEXTMENU_ITEMS1; i++)
      m_mi1_contextmenu1.AddItem(items_text[i],items_bmp_on[i],items_bmp_off[i],items_type[i]);
//--- 2番目の項目の後の区切り線
   m_mi1_contextmenu1.AddSeparateLine(1);
//--- コンテキストメニューを作成する
   if(!m_mi1_contextmenu1.CreateContextMenu(m_chart_id,m_subwin))
      return(false);
//--- ベースにコントロールポインタを追加する
   CWndContainer::AddToElementsArray(0,m_mi1_contextmenu1);
   return(true);
  }

グラフィカル・インターフェース作成の主要メソッドにコンテキストメニュー作成メソッドの呼び出しを追加します。コンテキストメニューは設定時に非表示にされますが、このテストでは表示されます。下記のコードはCProgram::CreateTradePanel() メソッドに追加される行を示します。

//+------------------------------------------------------------------+
//| 取引パネルを作成する                                                |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- コントロールフォームの作成
   if(!CreateWindow("EXPERT PANEL"))
      return(false);
//--- コントロールの作成
//    メニュー項目
   if(!CreateMenuItem1("Menu item"))
      return(false);
   if(!CreateMI1ContextMenu1())
      return(false);
//--- 区切り線
   if(!CreateSepLine())
      return(false);
//--- コンテキストメニューを表示する
   m_mi1_contextmenu1.Show();
//--- チャートの再描画
   m_chart.Redraw();
   return(true);
  }

ファイルをコンパイルしてプログラムをチャートに読み込みます。結果は以下のスクリーンショットのようなはずです。

図4。コンテキストメニュー要素のテスト

図4。コンテキストメニュー要素のテスト


この段階では、マウスカーソルがコンテキストメニューの項目の上にホバーしても、色は変更しません。この機能はコンテキストメニューのCContextMenuクラスで作成できます。またはメニュー項目のCMenuItemクラスに準備されたものを使うこともできます。コンテキストメニューがチャートに取り付けられた後、そのポインタがベースに追加されます。しかし、各メニュー項目へのポインタは、共通の要素配列へのポインタを追加するという現在の実装でのイベント処理のためのCWndEvents クラスで使用できません。複数の要素で構成された複合体(コンパウンド)コントロールについては、CWndContainerクラスで要素ポインタ取得メソッドを作成します。そのためにItemPointerByIndex()メソッドがCContextMenuクラスで実装され、インデックスを指定してメニュー項目のポインタの取得が可能です。

 


すべての要素へのポインタを格納するためのクラスのさらなる開発

コンテキストメニュー操作のためにAddContextMenuElements()メソッドをCWndContainerクラスで実装しましょう。フォームインデックスと要素オブジェクトは、パラメータとして渡されるべきです。メソッドの開始時に渡された要素がコンテキストメニューであるかどうかのチェックが行われます。その場合、メソッドアクセスのためにコンテキストメニュー(CContextMenu)へのポインタが必要です。渡されるオブジェクトは、基本クラスに属している場合(CElement)これはどのようにできるでしょうか。それには、CContextMenu型のポインタを宣言し渡されたオブジェクトのポインタを割り当てるので十分です。これは下記のコードで黄色で強調表示されています。こうすれば、コンテキストメニュー項目へアクセスできます。そして、これらは、フォーム要素の共通配列に反復処理で追加されます。各反復の終わりには、メニュー項目はCChartObject型のオブジェクト配列でそれらを構成するオブジェクトポインタを格納するCWndContainer::AddToObjectsArray()メソッドに渡されます。

//+------------------------------------------------------------------+
//| インターフェースのオブジェクト全てを格納するクラス                      |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- ベースにコンテキストメニュー要素のポインタを格納する
   bool              AddContextMenuElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| ベースにコンテキストメニュー要素のポインタを格納する                    |
//+------------------------------------------------------------------+
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);
     }
//---
   return(true);
  }

これは、要素カウンタの増加の直後にCWndContainer::AddToElementsArray()メソッドで呼び出されます。記事のスペースを節約するために、コードの短縮版のみを表示します。完全なコードは本稿添付のソースコードファイルで確認可能です。

//+------------------------------------------------------------------+
//| 配列へのポインタを追加する                                           |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- ベースにコントロールフォームがない場合
//--- 存在しないフォームへのリクエストの場合
//--- 共通要素配列に追加する
//--- 要素オブジェクトを共通オブジェクト配列に追加する
//--- すべてのフォームの最後の要素のIDを格納する
//--- 要素識別子のカウンタを増加する

//--- ベースにコンテキストメニューオブジェクトのポインタを格納する
   if(AddContextMenuElements(window_index,object))
      return;
  }

CWndContainer::AddToElementsArray()メソッドのコードは複合(コンパウンド)要素を取り扱う似たメソッドと同じように改善されます。

すべてのファイルをコンパイルしてチャート上にプログラムを読み込むと、コンテキストメニューの項目の外観がマウスカーソルがホバーすると変更します。

図5。コンテクストメニュー項目のテスト

図5。コンテクストメニュー項目のテスト


コンテキストメニュー作成クラスの開発がこれで完了です。次のステップは、イベントハンドラとメニュー項目のイベントハンドラを設定することです。これは次の記事で対処します。

 


おわりに

私たちのライブラリは、すでに下記の要素を作成するための3つのクラスが含まれています。

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

次の記事では、メインクラス内と以前作成されたコントロールクラス内でライブラリのイベントハンドラを設定します。

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

第二部の記事(チャプター)のリスト:

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

添付されたファイル |
「穴」のないチャート 「穴」のないチャート
この記事は、バーの見逃しのないチャートの実装を目的にしています。
グラフィカルインタフェース  II:メニュー項目要素(チャプター1) グラフィカルインタフェース II:メニュー項目要素(チャプター1)
シリーズの第二部では、メインメニュー、コンテキストメニューなどのインターフェイス要素の開発の詳細をお話しします。また、要素の描画にもふれ、そのための特別なクラスを作成します。カスタムイベントを含むプログラムイベントの管理なども詳しく話し合われます。
MetaTrader 5にポジション計算のヘッジシステムが追加されました MetaTrader 5にポジション計算のヘッジシステムが追加されました
リテールFXトレーダーの可能性を拡大する為に、プラットフォームに2つ目の計算システムであるヘッジングが追加されました。これからは、シンボルごとに、反対方向のものを含む、多数のポジションを持つことができます。これによって、いわゆる『ロック』を使った取引戦略を実装することができ、つまり、もし価格がトレーダーに反する方向へ向かった場合、反対方向にポジションを開くことができます。
注文の管理ーこれは簡単です 注文の管理ーこれは簡単です
この記事では、オープンポジションや指値注文の様々な管理方法を紹介し、エキスパートアドバイザの作成の簡素化について記述しています。