カスタムグラフィックコントロールパート1:簡単なコントロールを作成する

Dmitry Fedoseev | 2 10月, 2015

導入

MQL5言語では、プログラムでコントロールされたグラフィックオブジェクト:ボタン、テキストラベル、編集フィールド、ビットマップラベル(図1)、分析用各種グラフィックツール(図2)、が開発者向けに幅広く提供されています。

図1グラフィックオブジェクト:ボタン、テキストラベル、編集フィールド、ビットマップラベル

図2分析用グラフィックオブジェクト:楕円、フィボナッチファン、フィボナッチエクスパンション

メタトレーダー5クライアントターミナル全体には、40を超えるグラフィックオブジェクトがあります。これらは個別にも用いることができますが、それよりも、相互に接続した一連のオブジェクトとして多く用いられます。例えば、編集フィールド(OBJ_EDIT)は同時に、編集フィールドの機能を示すために、ビットマップラベル(OBJ_LABEL)と一緒にたいへん良く用いられます。

編集フィールドを使う際にはしばしば、ユーザによるデータ入力が正しいかどうかチェックすると同時に、ドットやカンマを10進数セパレータとして使う可能性もあわせ持つ必要があります。

データのプログラム出力を使う際には、データをフォーマットする必要があります。例えば、不必要なゼロを削除しなければなりません。したがって、編集フィールド、ビットマップラベル、さらにいくつかの他の機能的特徴を含む単一のオブジェクトがあれば、より簡単に使うことができるでしょう。

今日、プログラミングの世界には、あらゆるアプリケーションで用いられるグラフィックコントロールのセット:フォーム(すべてのコントロールエレメントが配置されるアプリケーションインターフェースの土台)、フレーム(1つの機能的役割を持つエレメントのセットのグループ化、また分割を可能にする)、ボタン、編集フィールド、ラベル、チェックボックス、ラジオボタン、縦横のスクロールバー、リスト、ドロップダウンリスト、メニュー行、メニュータブ、があります(図3)。

図3最も一般的な標準コントロールを含むフォーム

MQL5で上記エレメントを表す方法は、他のプログラミング言語と似ています(ボタンや編集フィールド)。他のコントロールを幅広くストックしておくと便利でしょう。

いくつかの開発環境ではプログラマー向けに、カスタムコントロール作成用の特別なツールが提供されています。MQL5には、そのような特徴はありません。しかし、MQL5はオブジェクト指向言語であるためそれを必要としません。すべてを、個別にプログラムされたオブジェクトの形で扱うことができます。

カスタムコントロール作成の原則と方法を、本稿ではさらに議論していきます。それらにもとづいて、プログラミング技術のある誰もが、アプリケーションで繰り返し用いられる必要なコントロールセットを作成することができるのです。


1 グラフィックコントロールとは何か

1.1 一般的な必要条件と原則

グラフィックコントロールはアプリケーション開発をより容易にする必要があり、それでこそ役に立つといえます。そのために、以下の必要条件を満たしていなければなりません:

  1. 開発時に、コントロールを素早く作成できなければなりません。この課題は、プログラミングへのオブジェクト指向アプローチにより解決されます。1つのグラフィックコントロールは、1つのプログラムされたオブジェクトとして表されます。
  2. コントロールは、フレキシブルでなければなりません。言い換えれば、サイズ、位置、色などのプロパティを変更できる必要があります。
  3. コントロールは、使いやすくなければなりません-必要なプロパティやメソッドのみを持ち、それらの目的が、エレメントの目的やメソッド名から分かりやすくなければなりません。それでは、コントロールのプロパティを分類してみましょう:
    1. コントロールされていないプロパティ。このようなプロパティは、カラースキームを含んでいます。アプリケーションに用いられるすべてのコントロールは、一般的なスタイルを持っている必要があります。そのため、各コントロールごとに色の設定を行うと面倒になります。
      加えて、いくつかのコントロールに使われている色を探すのはたいへん困難な作業で、そのようなことに時間を費やしたくありません。スクロールバーの例を見てみます。この興味深い作業に直面したことがあるウェブ開発者もおられるかもしれません。
    2. コントロール作成時に設定されるか、またはほとんど変更されないプロパティ。例えばコントロールのサイズ。アプリケーションに使われるすべてのコントロールをバランス良く、使いやすいように配置することは、インターフェース作成段階で行われる特別かつ困難な作業です。
      関連して、コントロールのサイズは通常、プログラム実行中には変更されません。しかし、時にそのようなプロパティを変更する必要があるかもしれません。そのため、プログラム実行中にこれらのプロパティを変更できるようにしなければなりません。
    3. 主操作用プロパティ。プログラムから頻繁に変更されるプロパティ。コントロールの目的を形成するプロパティ。これらのプロパティは2つに分類されます:
      1. コントロールの自動アップデートや表示のためのプロパティ。例えば、編集。プログラムで値が設定されると、その変更はスクリーン上に表示されなければなりません。プログラミングにおいては、コード1行で実行されるはずです。
      2. 表示の再読み込みが強制的に必要なプロパティ。例えば、リスト。リストはデータ配列を扱うことを意味するため、リストの1つのエレメントを作業した後にはリフレッシュされるべきではありません。そこで、リストのすべてのエレメントの作業後に強制的にアップデートを実行するほうが、より好ましいことになります。このアプローチにより、アプリケーションのパフォーマンスがたいへん向上します。
  4. コントロールをすばやくシンプルに、隠したり表示したりする可能性。コントロールを可視化することで、表示中のプロパティの設定を繰り返し要求すべきではありません;オブジェクトのプロパティは、コントロールのグラフィックオブジェクトの視認性とは独立してあるべきです。言い換えれば、プログラムされたオブジェクトはコントロールのすべてのプロパティを含むべきですが、それをグラフィックオブジェクトのプログラムに用いるべきではありません。
  5. コントロールに対応してアウトプットを行うため、コントロールに含まれる個々のグラフィックオブジェクトごとに、イベントが処理されなければなりません。

1.2コントロールと要求されるメソッドの利用方法

上記の条件を考慮して、グラフィックインターフェースについての作成スキーム、および、プログラムされたオブジェクトに必要とされるプロパティとメソッドのセットに関して、以下のように定めています:

  1. コントロールの初期化、およびほとんど変更されないプロパティの同時設定。このメソッドはInit()と呼ばれます;いくつかのパラメータが含まれます。第1の必須事項-コントロール名このパラメータは、コントロールに含まれるすべてのグラフィックオブジェクトの名称に、プリフィックスとして用いられます。加えて、コントロールのサイズを設定するパラメータやその他のパラメータを(コントロールの目的に応じて)含むことができます。
  2. コントロールはチャート上に固定して配置することができますが、同時に、移動させることもできます。したがってここでは、座標決定の別のメソッド:SetPosLeft()-X座標の設定、SetPosTop()-Y座標の決定、を用います。これらのメソッドは個々に、1つのパラメータを持っている必要があります。両方の座標を変更する必要があることがたいへん多く、そのため、X座標とY座標を同時に変更する2つのパラメータを含むSetPos()メソッドがあると便利です。
    1つのコントロールの位置を計算するために、他のコントロールのサイズや位置についての情報を取得する必要があるかもしれません。このために次のようなメソッドを用います:Width()-幅、Height()-高さ、Left()-X座標、Top()-Y座標。操作の段階においてコントロールの座標が計算されて、それらを設定するメソッドが呼び出されます。
  3. アプリケーションの作成直後、もしくは操作の別の段階においては、コントロールを可視化する必要があります。このためには、Show()メソッドが用いられます。コントロールを隠すには、Hide()メソッドが用いられます。
  4. 前にも述べたように、プログラムの操作中にコントロールのサイズを変更する必要があるかもしれません。そのため、プログラムされたオブジェクトはサイズ変更のためのメソッド-SetWidth()および/またはSetHeght()、を別途持つ必要があります。これらはほとんど変更されることのないプロパティであるため、変更を有効にするには、Refresh()メソッドを呼び出して表示をアップデートします。
  5. 個々のグラフィックオブジェクトのイベントは、コントロールの特定イベントに対応する値を返すEvent()メソッドで処理されます。Event()メソッドは、OnChartEvent()関数から呼び出されなければなりません;このメソッドは、OnChartEvent()関数と同じパラメータセットを持っています。

したがって、プログラムされたオブジェクトに求められるメソッドのセットは以下になります。

コントロール本体やその目的によっては、他のメソッドが存在することに(もしくはリストアップしたいくつかのメソッドが不要と)なります。

本稿の後半では、上に述べた原則の実装を試みます-ユーザがテキストや数字を入力するためのコントロールを作成する予定です。

その前に、グラフィックオブジェクトを素早く手軽に作成するツールを提供すしなければなりません。


2グラフィックオブジェクトで素早く手軽に作業するには

グラフィックオブジェクトで作業するために、MQL5は以下の基本関数を提供しています:ObjectCreate()ObjectDelete()ObjectSetDouble()ObjectSetInteger()ObjectSetString()ObjectGetDouble()ObjectGetInteger()ObjectGetString()。これらの関数を直接用いることができますが、プログラミングの処理が大変面倒で、時間もかかります。関数はロングネームを備えています;ロングネームを持つ識別子が、関数に渡されなければなりません。

グラフィックオブジェクトによる作業の利便性を高めるために、メタトレーダー5クライアントターミナルパッケージに含まれる既成のクラス(MQL5/Include/ChartObjects/ChartObject.mqhファイルにあるCChartObjectクラス)を用いるか、もしくは独自のクラスを書いて必要なすべてのメソッドを与えることができます。

冗談のようですが、プログラミングへのこのようなアプローチとは、キーを1つ押した後にドットを1回押すことから成り立っています。オブジェクト名を特定した後でドットを押すと、プロパティとメソッドのリストが開きます;そこで、リストから必要な項目をただ選択するだけです(図4)。

図4オブジェクトのプロパティとメソッドのリスト

補助クラスを使ってグラフィックオブジェクトを管理する2つの変数:

  1. 各グラフィックオブジェクトには、個別のクラスインスタンスが生成されます。たいへん便利ですが、消費メモリの観点では節約的な方法ではありません。この変数用に、グラフィックオブジェクトのタイプ別に特別なクラスを書くほうが好ましいです。しかし、このアプローチはたいへん面倒なため、最適ではありません。エキスパートアドバイザのプログラミングとは異なり、ユーザインタフェースを作成する際には最大パフォーマンスについての厳密な要求はありません。
  2. クラスインスタンスを1つ用います。グラフィックオブジェクトを管理する必要があれば、クラスにオブジェクトを追加します。ここでは、第2の変数を使ってみます。

グラフィックオブジェクトを管理する第2のタイプに最適な、汎用クラスを作成してみましょう。


3 グラフィックオブジェクトを管理するための汎用クラス

プログラミングでは、グラフィックオブジェクトを使う作業は3つの段階:作成、プロパティの読み込み/設定、アプリケーション操作後の削除、で構成されます。

そこでまず、グラフィックオブジェクトを管理するクラスにはそれらを作成するメソッドが含まれなければなりません。グラフィックオブジェクト作成用の必須パラメータは、名称です;したがってオブジェクト作成メソッドは、作成されたオブジェクトの名前を指定する必須パラメータを1つ含みます。

通常グラフィックオブジェクトは、プログラム(エキスパートアドバイザ、インディケータ、あるいはスクリプト)が動作するチャート上に作成されます。レアなケースではサブウィンドウ、また、さらにレアケースですが、ターミナルの他のチャートウィンドウということもあります。したがって、第2のオプションパラメータとしてサブウィンドウの数を指定するパラメータが必要です;第3は、チャートの識別子です。

デフォルトでは、いずれのオプションパラメータも0となっています(価格チャートが「自身」のチャート上にあることを示します)。ドキュメンテーションにあるグラフィックオブジェクトのタイプリストをチェックします。各タイプにCreateメソッドを追加します。

その前に、ファイルを作成する必要があります。このため、メタエディタを開いて新たにインクルードファイルを作成し、このファイルをIncGUI.mqhと呼ぶことにします。開いているファイルに、保護されたセクションとパブリックセクションを持つCGraphicObjectShellクラスを作成します。保護されたセクションには、オブジェクト名とチャート識別子のための変数を宣言します。

オブジェクト作成メソッドでは、これらの変数にメソッドによってパラメータとして渡される値が割り当てられるため、オブジェクトの作成後に、名称とチャート識別子を特定しなくてもそれらを管理できるようになっています。したがって、グラフィックオブジェクトを管理する第1変数用のクラスを用いることもできます。

(あらゆるグラフィックオブジェクトを管理する)第2変数のクラスを用いることができるようにするには、グラフィックオブジェクトを添付するメソッド(Attach()メソッド)を与えます。このメソッドは1つの必須パラメータ-グラフィックオブジェクト名、と1つのオプションパラメータ-チャート識別子、を含みます。おそらく、添付されたグラフィックオブジェクトの名称と識別子を確認する必要があるでしょう。そのためには、次のメソッド:Name()およびChartID()、をオブジェクトに追加します。

結果として、次のようなクラスの「ワークピース」が得られます:

class CGraphicObjectShell
  {
protected:
   string            m_name;
   long              m_id;
public:
   void Attach(string aName,long aChartID=0)
     {
      m_name=aName;
      m_id=aChartID;
     }
   string Name()
     {
      return(m_name);
     }    
   long ChartID()
     {
      return(m_id);
     }
  };

上で述べたグラフィックオブジェクト作成メソッドを追加します。これらのメソッド名は「Create」で始まります。

読み飛ばして構いませんが、本稿に添付のIncGUI.mqhファイルには、既成のCGraphicObjectShellクラスが含まれています。

例として、縦線のグラフィックオブジェクト(OBJ_VLINE)の作成メソッドをここでは見てみましょう:

void CreateVLine(string aName,int aSubWindow=0,long aChartID=0)
  {
   ObjectCreate(m_id,m_name,OBJ_VLINE,aSubWindow,0,0);
   Attach(aName,aChartID);
  }

さて、ユーザガイドにあるグラフィックオブジェクトのプロパティリストを開きましょう;ObjectSetDouble()、ObjectSetInteger()および ObjectSetString()の各関数を用いて、各プロパティの値を設定するメソッドを書いてみます。メソッド名は「Set」で始まります。次に、ObjectGetDouble()関数とObjectGetInteger()関数を用いて、プロパティを読み込むメソッドを記述します。ObjectGetString()

一例として、色を設定して取得するメソッドをここに示します:

void SetColor(color aColor)
  {
   ObjectSetInteger(m_id,m_name,OBJPROP_COLOR,aColor);
  }
color Color()
  {
   return(ObjectGetInteger(m_id,m_name,OBJPROP_COLOR));
  }

さて、グラフィックオブジェクトで作業するために最低限必要なツールは揃ったようですが、これらがすべてではありません。

グラフィックオブジェクトで作業する際には、時として、1つのオブジェクトを1アクションのみで実行する必要があるかもしれません。この場合、オブジェクトに対してAttach()メソッドを実行し、メインオブジェクトに戻ってから、Attach()を再度実行するのは不便です。

もう2つ、クラスのプロパティを設定/取得するすべてのメソッドの変数を加えてみましょう。

第1の変数-「自身」のチャート上の名前による:

void SetColor(string aName,color aColor)
  {
   ObjectSetInteger(0,aName,OBJPROP_COLOR,aColor);
  }
color Color(string aName)
  {
   return(ObjectGetInteger(0,aName,OBJPROP_COLOR));
  }

第2の変数-チャート名と識別子による:

void SetColor(long aChartID,string aName,color aColor)
  {
   ObjectSetInteger(aChartID,aName,OBJPROP_COLOR,aColor);
  }
color Color(long aChartID,string aName)
  {
   return(ObjectGetInteger(aChartID,aName,OBJPROP_COLOR));
  }

ObjectGet... およびObjectSet...関数に加えて、グラフィックオブジェクトで作業するための他の関数:ObjectDelete()ObjectMove()ObjectFind()ObjectGetTimeByValue()ObjectGetValueByTime()ObjectsTotal()、があります。これらも、それぞれを呼び出すための3つの変数とともにクラスに追加することができます。

最後に、このファイルでは、CGraphicObjectShellクラスを、単純なショートネーム「g」として宣言します。

CGraphicObjectShell g;

これで、グラフィックオブジェクトで作業を開始するために、IncGUI.mqhファイルに接続する準備が整いました;したがって、「g」クラスを使った作業ができます。これを使えば、すべての利用可能なグラフィックオブジェクトの管理を容易に行えます。


4コントロールのためのワークピース

グラフィックオブジェクトで素早く作業するためのクラスがありますが、それよりも簡単にコントロールを作成することができます。すべてのコントロールを、4つのグラフィックオブジェクトにもとづいて作成することができます:

  1. 長方形ラベル(OBJ_RECTANGLE_LABEL)、
  2. テキストラベル(OBJ_LABEL)、
  3. 編集フィールド(OBJ_EDIT)、
  4. ボタン(OBJ_BUTTON)。

グラフィックオブジェクトの作成後には、多くのプロパティ:座標、サイズ、色、フォントサイズなど、を設定しなければなりません。これを早く行うために、別のクラスを作成してこれにCWorkPiece(ワークピース)と名前を付け、パラメータの形で渡されるプロパティとともにグラフィックオブジェクトの作成メソッドを提供してみましょう。

コントロールが機能するためには、チャートイベントを扱う必要があります。他のチャートのイベントは利用できないため、自身のチャートでのみ作業を行うことになります-CWorkPieceクラスメソッドのパラメータにはチャート識別子がありません。いずれの場合も、0(チャート自身)が用いられます。

サブウィンドウを指定するパラメータを使うと、価格チャートとサブウィンドウの両方でコントロールを作成することができます。グラフィックオブジェクトは、左上領域にのみ固定されます;相対的に、その他のいずれの領域に再配置する必要があれば、チャートサイズを考慮しながら、コントロール全体の座標を再計算するほうがずっと簡単です。チャートサイズの変更をコントロールするために、CHARTEVENT_CHART_CHANGEイベントを扱うことができます。

多くのコントロールのベースとして、「長方形ラベル」オブジェクトを使います;このオブジェクトの作成メソッドをCWorkPieceクラスに追加していきます;このメソッドはCanvas()と呼ばれています:

void Canvas(string aName="Canvas",
             int aSubWindow=0,
             int aLeft=100,
             int aTop=100,
             int aWidth=300,
             int aHeight=150,
             color aColorBg=clrIvory,
             int aColorBorder=clrDimGray)
  {
   g.CreateRectangleLabel(aName,aSubWindow); // Creation of rectangle label
   g.SetXDistance(aLeft);                    // Setting of the X coordinate
   g.SetYDistanse(aTop);                        // Setting of the Y coordinate
   g.SetXSize(aWidth);                          // Setting of width
   g.SetYSize(aHeight);                         // Setting of height
   g.SetBgColor(aColorBg);                   // Setting of background color
   g.SetColor(aColorBorder);                 // Setting of border color
   g.SetCorner(CORNER_LEFT_UPPER);             // Setting of a anchor point
   g.SetBorderType(BORDER_FLAT);             // Setting of border type
   g.SetTimeFrames(OBJ_ALL_PERIODS);            // Setting visibility at all timeframes
   g.SetSelected(false);                        // Disabling selection
   g.SetSelectable(false);                   // Disabling of selection possibility
   g.SetWidth(1);                               // Setting of border width
   g.SetStyle(STYLE_SOLID);                  // Setting of border style
  }

注目:このメソッドは14行のコードで構成されています;オブジェクトを作成するたびにこれらすべてを記述するとしたら、たいへん煩雑でしょう。実際には、1行だけ記述すれば十分です。メソッドのすべてのパラメータ:位置、サイズ、色など、はオプショナルで、また使用頻度順にリストされています。

Canvas()メソッドと同様、テキストラベル、ボタン、そして編集フィールドの作成メソッド:Label()、Button()およびEdit()、を記述します。既成のCWorkPieceクラスは、IncGUI.mqhファイルとして本稿に添付されています。上記のメソッドに加えて、クラスにはいくつかの他のメソッド:Frame()およびDeleteFrame()-フレーム作成、削除メソッド(図5)、が含まれます。フレームは、左上領域にキャプションが付いた長方形ラベルです。

フレームは、コントロールをフォームの形にグループ化するために考案されています。

図5キャプション付きフレーム。

CWorkPieceクラスのすべてのメソッドのリストが、本稿に添付されています。

CGraphicObjectShellクラスと同様、CWorkPieceクラスをショートネーム「w」を使って宣言すると、IncGUI.mqhファイルに接続した後すぐに用いることができるようになります。

CWorkPiece w;

すべての補助ツールの準備が整いました。これで本稿の主題-カスタムコントロール作成、に進むことができます。


5「編集」コントロールの作成

まず、用語を混同しないようにするため、グラフィックオブジェクトOBJ_EDITをテキストフィールドとして、OBJ_LABELオブジェクトをラベルとして、そして作成されたコントロールを編集フィールドとして呼びます。作成されたコントロールは2つのグラフィックオブジェクト:編集フィールド(OBJ_EDIT)とテキストラベル(OBJ_LABEL)、から構成されます。

コントロールは2つの操作モード:テキストデータ入力と数値データ入力、をサポートします。数値データ入力モードでは入力値の範囲に制約があり、またコンマとドットが十進数のセパレータとして認められます。編集フィールドの値をプログラム出力する際には、小数位は特定した数にしたがってフォーマットされます。

したがって、コントロールの初期化では、操作モード:テキストあるいは数値、の指定が必要になります;モードはaDigitsパラメータを用いて指定されます。0以上の値には、小数位の数を特定して数値モードが設定され、負数はテキストモードの設定になります。

デフォルトでは、値の許容範囲は-DBL_MAXからDBL_MAX(変数の2倍の値の範囲全体)です。必要があれば、SetMin()メソッドとSetMax()メソッドを呼び出して他の範囲を設定することができます。サイズパラメータに関して、コントロールでは幅のみが設定されます。編集フィールドがバランスよく見えるために、高さと幅を対応させて設定する必要があります。

フォントサイズの変更には、対応するグラフィックオブジェクトの高さの変更が必要となります。このため、その他すべてのコントロールの配置変更が必要になります;しかし、誰もこれをしようと思わないはずです。すべてのコントロールと対応する編集フィールドに対しては、一貫したフォントサイズの使用がサポートされています。しかし、コントロールのクラスには、他のエレメントの座標計算を簡略化するため、高さを返すメソッド1つが備えられています。

色に関する4つのパラメータ:background color、text color、caption colorおよびwarning color、があります(例えば、不正確な値が入力された場合などにテキストフィールドの背景色を変えて、ユーザの注意を促すことができます)。

前に述べたように、サブウィンドウ内のコントロールがサポートされています。コントロールが動作するのに必要とされる主なパラメータに加えて、他のパラメータであるタグも用いていきます;これは、クラスのインスタンスに格納される単純なテキスト値です。タグは、便利な補助ツールです。

このクラスは、CInputBoxと呼ばれます。したがって、(プライベート領域には)以下のようにクラスの変数セットが備えられていることになります:

string m_NameEdit;    // Name of the Edit object
string m_NameLabel;   // Name of the Label object
int m_Left;           // X coordinate
int m_Top;            // Y coordinate
int m_Width;           // Width
int m_Height;          // Height
bool m_Visible;        // Visibility flag of the control
int m_Digits;          // Number of decimal places for the double number; -1 set the text mode
string m_Caption;      // Caption
string m_Value;        // Value
double m_ValueMin;     // Minimum value
double m_ValueMax;     // Maximum value
color m_BgColor;       // Background color
color m_TxtColor;     // Text color
color m_LblColor;      // Caption color
color m_WarningColor; // Warning font color
bool m_Warning;        // Flag of warning
int m_SubWindow;       // Subwindow
string m_Tag;           // Tag

コントロールを用いる際に、第1に呼び出されるメソッドはInit()です。

このメソッドで、以前に決定したすべてのパラメータの値を用意します:

// The initialization method
void Init(string aName="CInputBox",
           int aWidth=50,
           int aDigits=-1,
           string aCaption="CInputBox")
 { 
   m_NameEdit=aName+"_E";  // Preparing the name of the text field
   m_NameLabel=aName+"_L"; // Preparing the caption name
   m_Left=0;                 // X coordinate
   m_Top=0;                  // Y coordinate
   m_Width=aWidth;          // Width
   m_Height=15;             // Height
   m_Visible=false;         // Visibility
   m_Digits=aDigits;       // The mode of operation and the number of decimal places
   m_Caption=aCaption;     // Caption text
   m_Value="";              // Value in the text mode
   if(aDigits>=0)m_Value=DoubleToString(0,m_Digits); // Value in the numeric mode
   m_ValueMin=-DBL_MAX;                   // Minimal value
   m_ValueMax=DBL_MAX;                  // Maximal value
   m_BgColor=ClrScheme.Color(0);       // Background color of the text field
   m_TxtColor=ClrScheme.Color(1);      // Color of text and frame of the text field
   m_LblColor=ClrScheme.Color(2);      // Caption color
   m_WarningColor=ClrScheme.Color(3); // Warning color
   m_Warning=false;                      // Mode: warning, normal
   m_SubWindow=0; // Number of subwindow
   m_Tag=""; // Tag
 }

コントロールがテキストモードで動作する場合はm_Value変数には「」が割り当てられ、数値モードで動作する場合は-0に指定された小数位の数を付けて表記した値となります。色のパラメータはデフォルトに指定されます;カラースキームについては最後に扱います。

コントロールの座標を決定する変数は、コントロールがまだ可視化されていないためゼロに設定されます。Init()メソッドの呼び出し後(コントロールをチャート上の固定ポジションに億場合は)、SetPos()メソッドで座標を設定します:

// Setting the X and Y coordinates
void SetPos(int aLeft,int aTop)
{ 
   m_Left=aLeft;
   m_Top=aTop;
}

その後、コントロールを可視化することができます(the Show()メソッド):

// Enable visibility on the previously specified position
void Show()
{ 
   m_Visible=true; // Registration of visibility
   Create();       // Creation of graphical objects
   ChartRedraw();   // Refreshing of the chart
}

Create()関数はShow()メソッドから呼び出されます;これは(プライベート領域に)グラフィックオブジェクトを作成し、チャートを再読み込みします(ChartRedraw())。Create()関数のコードを以下に示します:

// The function of creation of graphical objects
void Create(){ 
   color m_ctmp=m_BgColor;  // Normal background color
      if(m_Warning){ // The warning method is set
         m_ctmp=m_WarningColor; // The text field will be color in the warning color
      }
    // Creation of the text field
   w.Edit(m_NameEdit,m_SubWindow,m_Left,m_Top,m_Width,m_Height,m_Value,m_ctmp,m_TxtColor,7,"Arial"); 
      if(m_Caption!=""){ // There is a caption
          // Creation of caption
         w.Label(m_NameLabel,m_SubWindow,m_Left+m_Width+1,m_Top+2,m_Caption,m_LblColor,7,"Arial"); 
      } 
}  

Create()関数において、m_Warningの値にしたがってグラフィックオブジェクトを作成する場合、テキストフィールドには、対応する背景色が割り当てられます。m_Caption変数が値を持つ場合は、キャプションが作成されます(キャプションなしでコントロールを作成することもできます)。

移動可能なコントロールを作成しようとする場合、Show()メソッドの第2変数を用いて、座標を特定してください。このメソッドでは座標を設定し、Show()メソッドの第1変数が呼び出されます:

// Setting the X and Y coordinates
void SetPos(int aLeft,int aTop){ 
   m_Left=aLeft;
   m_Top=aTop;
}

コントロールが表示された後で、それを場合によっては非表示にする必要があるかもしれません。

このためには、Hide()メソッドを用います:

// Hiding (deletion of graphical objects)
void Hide()
{ 
   m_Visible=false; // Registration of the invisible state
   Delete();        // Deletion of graphical objects
   ChartRedraw();    // Refreshing of the chart
}  

Hide()メソッドはDelete()関数を呼び出してグラフィックオブジェクトを削除し、その後ChartRedraw()関数を呼び出してチャートを再度読み込みます。Delete()関数はプライベート領域に存在します:

// The function of deletion of graphical objects
void Delete()
{ 
   ObjectDelete(0,m_NameEdit);  // Deletion of the text field
   ObjectDelete(0,m_NameLabel); // Deletion of caption
}  

コントロールの表示を変えずに値を設定するメソッド(SetPos())がすでにあることから、ロジカルに考えると、コントロールを強制的に再読み込みするメソッド-Refresh()メソッド、を作成するのは当然の成り行きです:

// Refreshing of displaying (deletion and creation)
void Refresh()
{ 
   if(m_Visible)
   {   // Visibility enabled
      Delete();     // Deletion of graphical object
      Create();     // Creation of graphical objects
      ChartRedraw(); // Redrawing of the chart 
   }            
}  

このコントロールはたいへんシンプルなため、再読み込みにはこのシンプルなメソッド-削除と作成、を用いることになります。たくさんの編集フィールドから成るリストのように、より複雑なコントロールの場合であれば、もっとスマートなアプローチが必要でしょう。

したがって、これをコントロールの配置により行います。では次に、値の設定-SetValue()メソッド、に進みましょう。このコントロールは2つのモードで動作することができるため、SetValue()メソッドには2つの変数:文字列と倍精度型、があります。テキストモードでは、値はそのまま用いられます:

// Setting a text value
void SetValue(string aValue)
{ 
   m_Value=aValue; // Assigning a value to variable to store it
      if(m_Visible)
      { // The visibility of the control is enabled
          // Assigning the text field to the object for managing graphical objects
         g.Attach(m_NameEdit); 
         g.SetText(m_Value); // Setting the value for the text field
         ChartRedraw();        // Redrawing the chart
      }
}

得られた引数がm_Value variable変数に割り当てられ、コントロールが可視化されると、テキストフィールドに表示されます。

数値モードでは、得られた引数がm_Digitsの値にしたがってノーマライズされ、最大値および最小値(m_MaxValue, m_MinValue)にしたがって補正され文字列に変換された後で、最初のメソッドSetValue()が呼び出されます。

// Setting a number value
void SetValue(double aValue)
{ 
   if(m_Digits>=0)
   {  // In the numeric mode
       // Normalization of the number according to the specified accuracy
      aValue=NormalizeDouble(aValue,m_Digits);
      // "Alignment" of the value according to the minimal acceptable value
      aValue=MathMax(aValue,m_ValueMin); 
       // "Alignment" of the value according to the maximal acceptable value
      aValue=MathMin(aValue,m_ValueMax); 
       // Setting the obtained value as a string
      SetValue(DoubleToString(aValue,m_Digits)); 
   }
   else
   { // In the text mode
      SetValue((string)aValue); // Assigning the value to the variable to store it as is
   }            
}

値を取得する2つのメソッド:文字列値の取得と倍精度値の取得、を記述してみましょう:

// Getting a text value
string ValueStrind()
{ 
   return(m_Value);
}

// Getting a numeric value
double ValueDouble()
{ 
   return(StringToDouble(m_Value));
}

コントロールに設定される値は、認められている最大値および最小値にしたがって補正されます;それらを取得し設定するメソッドを追加してみましょう:

// Setting the maximal acceptable value
void SetMaxValue(double aValue)
{ 
   m_ValueMax=aValue; // Registration of the new maximal accepted value
      if(m_Digits>=0)
     { // The control works in the numeric mode
         if(StringToDouble(m_Value)>m_ValueMax)
         { /* The current value of the control is greater than the new maximal acceptable value*/
            SetValue(m_ValueMax); // Setting the new value that is equal to the maximal accepted value
         }
      }         
}

// Setting the minimal acceptable value
void SetMinValue(double aValue)
{ 
   m_ValueMin=aValue; // Registration of the new minimal acceptable value     
      if(m_Digits>=0)
      { // The control works in the numeric mode
         if(StringToDouble(m_Value)<m_ValueMin)
         { /* The current value of the control is less than the new minimal acceptable value*/
            SetValue(m_ValueMin); // Setting the new value that is equal to the minimum acceptable value
         }
      }
}

// Getting the maximal accepted value
double MaxValue()
{ 
   return(m_ValueMax); 
}

// Getting the minimal accepted value
double MinValue()
{ 
   return(m_ValueMin);
}

コントロールが数値モードで動作する場合、新たに最大値および最小値の設定が実行される際に、現在の値のチェックと(必要があれば)補正が行われます。

では、ユーザによる値の入力:Event()メソッド、を扱ってみましょう。ユーザによるデータ入力のチェックは、CHARTEVENT_OBJECT_ENDEDITイベントを用いて実行されます。テキストモードで動作する際、ユーザにより特定された値がm_Valueに等しくない場合には、新たな値がm_Valueに割り当てられると同時に、Event()メソッドから返されるm_event変数に値1が割り当てられます。

数値モードで動作する際は、m_Valueの以前の値をm_OldValue変数に記憶し、コンマをドットに変換し、文字列を数字に変換してSetValue()関数に渡します。その後、m_Valueとm_OldValueが等しくない場合はイベントを「生成」します(m_event variableに値1が設定されます)。

// Handling of events
int Event(const int id,
           const long & lparam,
           const double & dparam,
           const string & sparam)
{ 
   bool m_event=0; // Variable for an event of this control
      if(id==CHARTEVENT_OBJECT_ENDEDIT)
      { // There has been an event of end of editing the text field
         if(sparam==m_NameEdit)
         { // The text field with the name m_NameEdit has been modified
            if(m_Digits<0)
            { // In the text mode
               g.Attach(m_NameEdit); // Assigning the text field for controlling it
                  if(g.Text()!=m_Value)
                  { // New value in the text field
                     m_Value=g.Text(); // Assigning the value to the variable to store it
                     m_event=1;         // There has been an event
                  }
            }
            else
            { // In the numeric mode
               string m_OldValue=m_Value; // The variable with the previous value of the control
               g.Attach(m_NameEdit);      // Attaching the text field for controlling it
               string m_stmp=g.Text();     // Getting text specified by a user in the text field
               StringReplace(m_stmp,",",".");       // Replacing comma with a dot
               double m_dtmp=StringToDouble(m_stmp); // Conversion to a number
               SetValue(m_dtmp);                     // Setting the new numeric value
                     // Comparing the new value with the previous one
                  if(StringToDouble(m_Value)!=StringToDouble(m_OldValue))
                  { 
                     m_event=1; // There has been an event 
                  }
            }
         }
      }               
   return(m_event); // Return the event. 0 - there is no event, 1 - there is an event
}

サブウィンドウ内でのコントロール動作サポートこれを行うには、CHARTEVENT_CHART_CHANGEイベントの際にOnChartEvent()から呼び出されるSetSubWindow()メソッドを追加してください。価格チャート上のみでコントロールを用いる予定であれば、このメソッドを呼び出す必要はありません。

m_SubWindow変数はすでに宣言されていますが、デフォルトでは0に等しく、コントロールのグラフィックオブジェクト作成においては、「w」クラスのEdit()メソッドおよびLabel()メソッドに渡されます。サブウィンドウの数はSetSubWindowName()メソッドに渡されます;この数字が変更されると、m_SubWindow変数の値が変わりRefresh()メソッドを実行します。

// Setting a subwindow by number
void SetSubWindow(int aNumber)
{ 
   int m_itmp=(int)MathMax(aNumber,0); /* If the number is negative, 0 will be used - the price chart*/
      if(m_itmp!=m_SubWindow)
      { /* The specified number doesn't correspond the number where the control is located*/
         m_SubWindow=m_itmp; // Registration of the new number of subwindow
         Refresh(); // Recreation of the graphical objects
      }
}

おそらくは、関数名の代わりにサブウィンドウ名を渡すほうがより便利になるでしょう。SetSubWindow()メソッドのもう1つの変数を追加します:

// Setting a subwindow by name
void SetSubWindow(string aName)
{ 
   SetSubWindow(ChartWindowFind(0,aName)); // Determination of the number of the subwindow by its name and setting the subwindow by number
}

本稿の冒頭で述べたコンセプトにしたがって、コントロールクラスに含まれていない他のメソッドを加えます。

コントロールの両座標を同時に設定することができるSetPos()メソッドを備えるとすぐに、座標の設定を別々に行うメソッドを追加します:

// Setting the X coordinate
void SetPosLeft(int aLeft)
{ 
   m_Left=aLeft;
}      

// Setting the Y coordinate
void SetPosTop(int aTop)
{ 
   m_Top=aTop;
}  

幅を設定するメソッド:

// Setting the width
void SetWidth(int aWidth)
{ 
   m_Width=aWidth;
}

座標とそのサイズを取得するメソッド:

// Getting the X coordinate
int Left()
{ 
   return(m_Left);
}

// Getting the Y coordinate
int Top()
{ 
   return(m_Top);
}

// Getting the width
int Width()
{ 
   return(m_Width);
}

// Getting the height
int Height()
{
   return(m_Height); 
}

タグを用いるメソッド:

// Setting the tag
void SetTag(string aValue)
{ 
   m_Tag=aValue;
}

// Getting the tag
string Tag()
{ 
   return(m_Tag);
}  

警告メソッド:

// Setting the warning mode
void SetWarning(bool aValue)
{ 
      if(m_Visible)
      { // Visibility is enabled
         if(aValue)
         { // We need to turn on the warning mode
            if(!m_Warning)
            { // The warning mode has not been enabled
               g.Attach(m_NameEdit);         // Attaching the text field for controlling
               g.SetBgColor(m_WarningColor); // Setting the warning color of text in the text field
            }
         }
         else
         { // We need to disable the warning mode
            if(m_Warning)
            { // The warning mode is enabled
               g.Attach(m_NameEdit);    // Attach the text field for controlling 
               g.SetBgColor(m_BgColor); // Setting the normal font color                
            }
         }
      }
   m_Warning=aValue; // Registration of the current mode
}

// Getting the warning mode
bool Warning()
{ 
   return(m_Warning);
}

警告モードの設定時にコントロールが可視化されている場合には、SetWarningメソッドに渡されるパラメータの値がチェックされます;その値がコントロールの現状に対応していない場合は、テキストフィールドの背景色が変わります。

コントロールが非表示の場合には、テキストフィールドに対応する色は設定せずに設定モードが登録されます。

残っているプロパティはあと1つ-m_Digits、です。その値を取得して設定するメソッドを追加してみましょう:

// Setting the number of decimal places
void SetDigits(int aValue)
{ 
   m_Digits=aValue; // Registration of the new value
      if(m_Digits>=0)
      { // The numeric mode
         SetValue(ValueDouble()); // Resetting of the current value
      }
}  

// Getting the m_Digits value
int Digits()
{ 
   return(m_Digits);
}  

これで、最も興味深いパートはおしまいです。ここからは、最も美しいパートに移ります。


6カラースキーム

カラースキームは、CСolorSchemesクラスの変数として格納されます。

このクラスはあらかじめ、IncGUI.mqhファイル内でClrSchemeという名前で宣言されます。カラースキームを設定するには、パラメータとしてカラースキームの番号を特定してSetScheme()メソッドを呼び出します。SetScheme()メソッドが呼び出されない場合、カラースキーム番号として0が用いられます。

色を取得するには、カラースキームから番号を指定してColor()メソッドを用います。プライベートおよびパブリック領域で、CСolor Schemesクラスを記述してみましょう。プライベート領域では、m_ShemeIndex変数を宣言してカラースキームインデックスを格納します。パブリック領域では、SetScheme()メソッドを記述します:

// Setting the color scheme number
void SetScheme(int aShemeIndex)
{ 
   m_ShemeIndex=aShemeIndex;
}

Color()メソッドこのメソッドでは、二次元配列:一次元にはカラースキーム番号;二次元にはスキーム内の色番号、が宣言されます。特定されたカラースキームの番号にしたがって、メソッドパラメータで指定する番号により色を返します。

color Color(int aColorIndex)
{
   color m_Color[3][4];  // The first dimension - the color scheme number, the second one - the number of the color in the color scheme
   // default
   m_Color[0][0]=clrSnow;
   m_Color[0][1]=clrDimGray;
   m_Color[0][2]=clrDimGray;
   m_Color[0][3]=clrPink;
   // yellow-black
   m_Color[1][0]=clrLightYellow;
   m_Color[1][1]=clrBrown;
   m_Color[1][2]=clrBrown;
   m_Color[1][3]=clrPink;
   // blue
   m_Color[2][0]=clrAliceBlue;
   m_Color[2][1]=clrNavy;
   m_Color[2][2]=clrNavy;
   m_Color[2][3]=clrPink;
   return(m_Color[m_ShemeIndex][aColorIndex]); // Returning a value according to the scheme number and the number of color in the scheme
}

ここではカラースキームに4つの色が含まれ、うち2つは同じ値です。さらに、他のコントロールを作成する際にはもっと色が必要になるかもしれません。

スキームにふさわしい色を簡単に探す、もしくは新しい色の追加を決定するために、クラスには色を見ることができるメソッド-Show()メソッド、が含まれています(図6)。逆に、色見本をチャートから削除するメソッドであるHide()も含まれます。

図6Show()メソッドでカラースキームを見る

本稿には、ColorSchemesView.mq5が添付されています。これ(ColorSchemesView.mq5)は、カラースキームを見るためのエキスパートアドバイザです。

CInputBoxクラス内のInit()メソッドを、若干修正してみましょう。 色の設定を、ClrSchemeクラス内の色に置き換えます。

m_BgColor=ClrScheme.Color(0);       // Background color of the text field
m_TxtColor=ClrScheme.Color(1);      // Font color and frame color of the text field
m_LblColor=ClrScheme.Color(2);     // Caption color
m_WarningColor=ClrScheme.Color(3); // Warning color

これで1つのコントロールの作成が終わりました;これで、他のコントロールを開発するためのベースが用意できたことになります。


7コントロールの利用

エキスパートアドバイザを作成してGUITestと名前を付けます;IncGUI.mqhファイルに接続します:

#include <IncGUI.mqh>
CInputBoxクラスをibという名前で宣言します:
CInputBox ib;

EAのOnInit()で、ibオブジェクトのInit()メソッドを呼び出します:

ib.Init("InpytBox",50,4,"input");

コントロールを可視化し、位置を設定します:

ib.Show(10,20);

EAのOnDeinit()関数でコントロールを削除します:

ib.Hide(); 

コンパイルし、エキスパートアドバイザをチャートに添付します。これで、コントロールを見られるようになりました(図7)。

図7InputBoxコントロール

エキスパートアドバイザにカラースキームの変更ができるよう、機能を追加します。

現時点では、3つのカラースキームを持っています。カラースキームを選択するために、番号付けと外部変数の作成を行います:

enum eColorScheme
  {
   DefaultScheme=0,
   YellowBrownScheme=1,
   BlueScheme=2
  };

input eColorScheme ColorScheme=DefaultScheme;

エキスパートアドバイザのOnInit()関数の開始直後に、カラースキームの設定を追加します:

ClrScheme.SetScheme(ColorScheme);

これで、EAのプロパティウインドウ内で、3つのカラースキームから1つを選択することができます(図8)。



図8別のカラースキーム

新しい値を指定するイベントを操作するには、EAのOnChartEvent()関数に以下のコードを追加します:

if(ib.Event(id,lparam,dparam,sparam)==1)
  {
   Alert("Entered value "+ib.ValueStrind());
  }

これで、編集フィールドに新しい値が追加されると、指定された値を知らせるメッセージウィンドウが開きます。

エキスパートアドバイザで、サブウィンドウ内でコントロールを作成することができるようにします。

まず、テストインディケータとしてTestSubWindowを作成します(TestSubWindow.mq5ファイルに添付されています)。MQL5ウィザードでこのインディケータを作成する際は、別のサブウィンドウで動作するように指定します。以下のコードを、EAのOnChartEvent()関数に追加します:

if(CHARTEVENT_CHART_CHANGE)
  {
   ip.SetSubWindow("TestSubWindow");
  }

これで、インディケータが価格チャート上にない場合に、コントロールが価格チャート上に作成されるようになりました。チャートにインディケータを添付する場合、コントロールはサブウィンドウに移動します(図9)。インディケータを削除する場合、コントロールは価格チャートに戻ります。

図9サブウィンドウ内のコントロール


結論

作業の結果、以下のクラスを含むインクルードファイルIncGUI.mqhを作成しました:CGraphicObjectShell(グラフィックオブジェクトの作成と管理)、CWorkPiece(パラメータでプロパティを設定して、いくつかのグラフィックオブジェクトを素早く作成)、CColorSchemes(カラースキームの設定と現在のカラースキームの色を取得)、およびコントロールのクラス-CInputBox。

このファイルではすでに、CGraphicObjectShell、CWorkPieceおよびCColorSchemesの各クラスは、「g」、「w」および「ClrScheme」という名前で宣言されています、つまり、IncGUI.mqhファイルに接続後、すぐにこれらを用いることができます。

CInputBoxクラスの使用方法をおさらいします:

  1. IncGUI.mqhファイルに接続します。
  2. CInputBoxタイプのクラスを宣言します。
  3. Init()メソッドを呼び出します。
  4. SetPos()メソッドで座標を設定します;必要があれば、Show()を用いて可視化をオンに設定します。第2の変数:Show()を座標指定とともに用いて、可視化をオンに設定します。
  5. 必要があれば、もしくはエキスパートアドバイザの作業後に、Hide()メソッドを使ってコントロールを非表示にします。
  6. Event()メソッドの呼び出しをOnChartEvent()関数に追加します。
  7. サブウィンドウにコントロールを作成する必要がある場合には、CHARTEVENT_CHART_CHANGEイベントの発生時にOnChartEvent()関数にSetSubWindow()メソッドの呼び出しを加えます。
  8. カラースキームを用いるには、Init()メソッドの呼び出し前に、ClrSchemeクラスのSetScheme()メソッドを呼び出します。


添付