グラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)

Anatoli Kazharski | 22 2月, 2016

コンテンツ

 

はじめに

本稿は、グラフィカルインタフェースの開発に関するあと一つのシリーズの幕開けとなります。現在、MQLアプリケーション内での高品質なグラフィカルインタフェースを迅速かつ簡単に作成するためのコードライブラリは1つも存在しません。ここで、グラフィカルインタフェースとは私たちになじみ深いオペレーティングシステムで使用されるグラフィカルインタフェースのことですす。

このプロジェクトの目標は、エンドユーザに機会を提供し、このライブラリの助けを借りる方法を示すことです。出来るだけ簡単に学べるようにし、さらなる開発への余地を残しました。

Dmitry Fedoseevの記事でシェアされたコードライブラリには言及する価値があります。

Dmitryによって提案されたライブラリと標準ライブラリにはそれぞれの利点がありますが、またいくつかの欠点もあります。Dmitryは、取扱説明書の形での詳細な説明と、標準ライブラリより広範囲のユーザーインターフェイス要素を提供してきました。しかし、私は、個人的には、その機能の一部と実装制御機構には賛成できません。このことについて何人かのフォーラムメンバーがコメントで興味深いアイデアを共有し、ここではそれらのいくつかが考慮されました。

標準ライブラリは、詳細な説明を備えていません、そして、私の意見では、その構造がより良く設計されていますが、十分に考え通されていない部分があります。それに加えて、標準ライブラリは、グラフィカルインタフェースの作成上で必要になる場合のあるコントロールの多くを含んでいません。近代化と品質向上についての質問が発生した場合、どちらの実装も一から再構築する必要があると考えざるを得ません。過去の記事では、私は全ての利点を収集し全ての欠点を解消する試みを行いました。

以前、私はコントロールに関する2つの簡単な入門記事を書きました。

これらは、手順的なスタイルで書かれており、MQLを理解するという目的を果たしています。今回は、オブジェクト指向の形で実現されるであろうかなり大規模なプロジェクトの例をもって、より複雑な構造を示そうと思います。

読者はこれらの記事から下記が得られるでしょう。

私はこの記事のシリーズで使用されるナレーションメソッドを「理想的なシーケンスの未遂模倣」と呼びました。多くの場合、実際の生活の大規模なプロジェクトの開発の過程で、シーケンシャルなアクションの順序と思考のラインは、より混沌であり、多くの実験、試行錯誤で構成されています。ここでは、これら全ての複雑性を抜かします。この規模のプロジェクトに初めてかかわる皆さんには、このライブラリの研究、及びその開発の過程のよりよい理解のために全てのアクションを繰り返すことが推奨されます。この記事シリーズは、質問の大半の答えが全て既存しており、プロジェクトに必要な全ての部分が作成されたおりに、思考の流れを理想的な順序で提示する機会を与えます。

 

コントロールのリスト

このライブラリはどのようなものなのでしょうか?このコードのライブラリのオブジェクト指向プログラミング構造はどのようであるべきですか?初めに何をすべきですか?実際には、質問はまだまだあります。便利なMQLアプリケーションを作成するために必要なコントロールとインターフェース要素を定義してみましょう。誰もが独自の要件を持っており、アイデアの規模は人から人へと変化します。ボタンとチェックボックスが十分な人もいます。色々な種類のデータセットの選択とコントロールを備えたマルチウィンドウインターフェイスを考える人もいます。

この記事シリーズで説明した実装は、取引プラットフォーム MetaTrader 4 及び MetaTrader 5 の最終製品として使用することができることに言及したいと思います。理想と比較すれば、当然、改善の予知があることは明らかでしょう。この記事シリーズが公開された後、MQLでグラフィカルインタフェースを作成するためのライブラリの理想的な実装についての個人的な考えを共有しようと思います。

ライブラリの初期バージョンには、以下のリストにあるインターフェイス要素が含まれます。 そのうちのいくつかは複数のメソッドで実施され、各々が特定のタスクのために設計された別個のコードクラスとして提示されます。すなわち、異なる要素は異なるタスクに適しています。

上記のリストには、自らの機能のために同リストから他の要素を含むコントロールが含まれています。上で、コンボボックス型のコントロールはリストビューコントロールを含み、リストビューは縦スクロールを含みます。テーブル型には縦横のスクロールバーも含まれています。ドロップダウンカレンダーはまた、別のコントロールとして使用できるカレンダーを含みます。各要素の作成の詳細はプロジェクト構造を定義した後に考慮されます。

 

プリミティブオブジェクトとしての標準ライブラリの基本クラス

標準ライブラリからはいくつかのコードクラスが使用されることになります。それらは全てのコントロールの構成要素となるベースのグラフィックプリミティブに属します。これらのクラスで、グラフィカルオブジェクトを速やかに作成/削除し、そのプロパティを取得/変更することができます。

図形要素との作業のためのクラスファイルは下記に配置されています。

これらのクラスの包括的な説明と使用例はCreate Your Own Market Watch Using The Standard Library Classesの記事にあるので、ここでは詳細にはふれません。このクラスグループのベースクラスはCObjectだということだけを言っておきます。CChartObjectクラスはそこから派生されています。このクラスは、全てのグラフィカルオブジェクトに適用可能な共通のメソッドを含みます。他のクラスは全てCChartObjectクラスから派生し、全てのグラフィックオブジェクトの独自のプロパティの管理メソッドを含みます。

グラフィカルオブジェクトに属する標準ライブラリクラスの相互接続の一般的な構造は、以下のように示すことができます。青い矢印は基本クラスと派生クラスの関係を表します。

図1。標準ライブラリクラスの相互関係の一般的な構造

図1。標準ライブラリのクラスの相互接続の一般的な構造

ライブラリの構造は、ライブラリが著作されながら記事で同様のダイアグラム上で表示されます。簡略化のために、以下に示すように短縮バージョンを使用します。提示された図の最後の要素は、上記の図の任意の(...)グラフィカルなオブジェクトでありえることが意味されます。

図2。標準ライブラリのグラフィックオブジェクトの構造の短縮版

図2。標準ライブラリのグラフィックオブジェクトの構造の短縮版

グラフィカルインターフェースの構築には、これらのクラスのほんの一部が必要です。

これらに共有することは、チャートがスクロールされたとき、チャートと一緒に動かずに時間軸に添付されていることです。その後、これらのプリミティブのそれぞれで、しばしば参照されるプロパティを保管するための派生クラスの作成が必要となります。

これらのクラスはまた、プロパティの値を取得と保持のための対応するメソッドを含む必要があります。現実的に、これらのプロパティの値の取得には、一つ上のレベルで基本クラスの助けを借りることができます。しかしながら、このアプローチは資源の面では効率的でありません。

 

追加的なメソッドを持つプリミティブオブジェクトの派生クラス

<data folder>\MQL5\Include ディレクトリ(MetaTrader 4 では <data folder>\MQL4\Include) にEasyAndFastGUIフォルダを作成します。このフォルダには全てのライブラリファイルが配置されます。データフォルダを検索するには、MetaTraderかMetaEditorのいずれかのメインメニューで「ファイル > データフォルダーを開く」を選びます。EasyAndFastGUIフォルダで、グラフィカルインターフェース作成に属するファイルの全てはControlsサブフォルダに保存されます。Controls サブフォルダにObjects.mqhファイルを作成します。ここには、前述した派生クラスが含まれます。

Objects.mqhファイルの頭に、標準ライブラリの必要なファイルを含みます。

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

上記の全てのクラスのオブジェクトが同じ型になるので、1つだけコードを表示します。実際に、これらのメソッドの全ては一行にはまります。ファイル内のスペースを節約して最大に圧縮された形式で情報を提示するために、このようなメソッドはクラス本体内に直接配置されます。

変数の初期化は全て、クラスコンストラクタで行われます。それらの一部は、コンストラクタの初期化リストで行われます(クラス本体の前に)。現実的には、ライブラリ全体の開発時を通して、派生クラスと基本クラスのフィールド(変数)の初期化シーケンスが必要とされた場合は一回もなかったので、それは重要ではありません。これが、クラス本体内で全ての変数、またはコメントを必要とする変数のみを初期化することができる理由です。

//+------------------------------------------------------------------+
//| 長方形ラベルオブジェクトの追加プロパティを持つクラス  |
//+------------------------------------------------------------------+
class CRectLabel : public CChartObjectRectLabel
  {
protected:
   int               m_x;
   int               m_y;
   int               m_x2;
   int               m_y2;
   int               m_x_gap;
   int               m_y_gap;
   int               m_x_size;
   int               m_y_size;
   bool              m_mouse_focus;
public:
                     CRectLabel(void);
                    ~CRectLabel(void);
   //--- 座標
   int               X(void)                      { return(m_x);           }
   void              X(const int x)               { m_x=x;                 }
   int               Y(void)                      { return(m_y);           }
   void              Y(const int y)               { m_y=y;                 }
   int               X2(void)                     { return(m_x+m_x_size);  }
   int               Y2(void)                     { return(m_y+m_y_size);  }
   //--- エッジポイントからのインデント (xy)
   int               XGap(void)                   { return(m_x_gap);       }
   void              XGap(const int x_gap)        { m_x_gap=x_gap;         }
   int               YGap(void)                   { return(m_y_gap);       }
   void              YGap(const int y_gap)        { m_y_gap=y_gap;         }
   //--- サイズ
   int               XSize(void)                  { return(m_x_size);      }
   void              XSize(const int x_size)      { m_x_size=x_size;       }
   int               YSize(void)                  { return(m_y_size);      }
   void              YSize(const int y_size)      { m_y_size=y_size;       }
   //--- フォーカス
   bool              MouseFocus(void)             { return(m_mouse_focus); }
   void              MouseFocus(const bool focus) { m_mouse_focus=focus;   }
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                   |
//+------------------------------------------------------------------+
CRectLabel::CRectLabel(void) : m_x(0),
                               m_y(0),
                               m_x2(0),
                               m_y2(0),
                               m_x_gap(0),
                               m_y_gap(0),
                               m_x_size(0),
                               m_y_size(0),
                               m_mouse_focus(false)
  {
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CRectLabel::~CRectLabel(void)
  {
  }

1つのファイルには複数のクラスが含まれます。クラス間の迅速なナビゲーションのために、ファイルの先頭の標準ライブラリのインクルードの直後に、実質的には本体のないクラスのリストであるファイル内容を書き込みます。

//--- 迅速なナビゲーションのためのファイルでのクラスリスト (Alt+G)
class CRectLabel;
class CEdit;
class CLabel;
class CBmpLabel;
class CButton;

リスト内のクラスの名前にカーソルを置いてAlt+Gを押すと関数やメソッド間を移動することができ、同様の方法で、ファイルのクラスに移動もできます。

現在の段階では、下の図のようなスキームを示すことができます。ここで、青い枠を持った長方形は上記で説明されたObjects.mqhファイルです。青い枠は、このファイル内の全てのクラスが、最後の青矢印の元となっているCChartObject…長方形によって表されるクラスのいずれかから派生することを意味します。

図3。プリミティブオブジェクトのための派生クラスを作成することによる構造の拡大

図3。プリミティブオブジェクトのための派生クラスを作成することによる構造の拡大

グラフィカルプリミティブについての質問が答えられました。ほぼ全てのコントロールは複数の単純なオブジェクトで構成されているため、ここでそれらのオブジェクトがグループとして存在するときの管理法を決定する必要があります。全てのコントロールはユニークですが、共通のプロパティのセットを持っています。この共通のセットを持つCElement基本クラスを作成しましょう。

 

全てのコントロールの基本クラス

CElementクラスはElement.mqhファイルに位置し、Objects.mqhファイルが #include コマンドによってそれに含まれます。

//+------------------------------------------------------------------+
//|                                                      Element.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Objects.mqh"
//+------------------------------------------------------------------+
//| 基本コントロールクラス                                               |
//+------------------------------------------------------------------+
class CElement
  {
public:
                     CElement(void);
                    ~CElement(void);
  };

記事内のスペースを節約するために、クラス本体内の別々のグループでこのクラスのメソッドを表示します。本稿の最後では、全てのメソッドの説明を含むクラスの完全なバージョンをダウンロードすることができます。このアプローチは他の全てのクラスでとられます。

Objects.mqhファイル内のクラスはCElement基本クラスではなく、このクラスのオブジェクトにふくまれていないことには留意するべきです。それらは後にCElementクラスから派生したクラスのオブジェクトとして使用され、基本クラスにはオブジェクトポインターの配列として保存されます。Objects.mqhファイルがElement.mqhファイルに含まれた場合、将来的に他のファイルには含まなくて済みます。結果としてObjects.mqhElement.mqhの2つのファイルを含む代わりに、Element.mqh1つだけを含めばよいわけです。

Controls フォルダに、#defineディレクティブでプログラム全体に共通したプロパティを保存するためのファイルを作成します。

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- クラス名
#define CLASS_NAME ::StringSubstr(__FUNCTION__,0,::StringFind(__FUNCTION__,"::"))
//--- プログラム名
#define PROGRAM_NAME ::MQLInfoString(MQL_PROGRAM_NAME)
//--- プログラムタイプ
#define PROGRAM_TYPE (ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)
//--- 配列サイズ超過の防止
#define PREVENTING_OUT_OF_RANGE __FUNCTION__," > Prevention of exceeding the array size."

//--- フォント
#define FONT      ("Calibri")
#define FONT_SIZE (8)

上記のコード内の関数の前には二重コロンがある前にあることにご注意ください。これらは必須ではなく、なくても全てが正常に動作します。しかし、プログラミングでは、言語のシステム関数の前にコロンを置くのは良いマナーです。これによって、関数がシステム関数であることがより明白になります。

Defines.mqhファイルをObjects.mqhファイルに含み、互いに含まれたファイルで使用できるようにします(Defines.mqh -> Objects.mqh -> Element.mqh)。

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

ここで、プロパティコントロールが共有しているものとは何かを定義する必要があります。全てのコントロールは、他の全ての類似のモジュールから独立して機能する別々のプログラムモジュールです。コントロールのいくつかはグループに収集されます。つまり、より複雑(コンパウンド)なコントロールではコントロールがメインプログラムモジュール及び他のコントロールにメッセージを送信する場合があります。同じチャート上で複数のMQLアプリケーションが実行されている場合があるので、時折、メッセージが自分のプログラムのコントロールから受信されたかどうかを定義する必要性があります。

下記もまた定義される必要があります。

class CElement
  {
protected:
   //--- (1) クラス名 (2) プログラム名 (3) プログラムの種類
   string            m_class_name;
   string            m_program_name;
   ENUM_PROGRAM_TYPE m_program_type;
   //--- コントロールの状態
   bool              m_is_visible;
   bool              m_is_dropdown;
   int               m_is_object_tabs;
   //--- フォーカス
   bool              m_mouse_focus;
   //---
public:
                     CElement(void);
                    ~CElement(void);
   //--- (1) クラス名の取得と設定 (2) プログラム名の取得 
   //    (3) プログラムの種類の取得
   string            ClassName(void)                    const { return(m_class_name);           }
   void              ClassName(const string class_name)       { m_class_name=class_name;        }
   string            ProgramName(void)                  const { return(m_program_name);         }
   ENUM_PROGRAM_TYPE ProgramType(void)                  const { return(m_program_type);         }
   //--- コントロールの状態
   void              IsVisible(const bool flag)               { m_is_visible=flag;              }
   bool              IsVisible(void)                    const { return(m_is_visible);           }
   void              IsDropdown(const bool flag)              { m_is_dropdown=flag;             }
   bool              IsDropdown(void)                   const { return(m_is_dropdown);          }
   void              IsObjectTabs(const int index)            { m_is_object_tabs=index;         }
   int               IsObjectTabs(void)                 const { return(m_is_object_tabs);       }
   //--- フォーカス
   bool              MouseFocus(void)                   const { return(m_mouse_focus);          }
   void              MouseFocus(const bool focus)             { m_mouse_focus=focus;            }
  };

プログラムインターフェースには、ボタンやチェックボックスなどの同種のコントロールが複数ある場合があるので、それぞれに固有の番号または識別子(id)が必要です。同時に、コントロール要素が他のコントロールの配列から構成されている場合、それぞれにインデックス番号が必要です。

class CElement
  {
protected:
   //--- コントロールの識別子とインデックス
   int               m_id;
   int               m_index;
   //---
public:
   //--- コントロール識別子の設定と取得
   void              Id(const int id)                         { m_id=id;                      }
   int               Id(void)                           const { return(m_id);                 }
   //--- コントロールインデックスの設定と取得
   void              Index(const int index)                   { m_index=index;                }
   int               Index(void)                        const { return(m_index);              }
  };

前述したように、コントロールグラフィカル要素の全てのオブジェクトは、これらのオブジェクトへのポインタとしてCChartObject型の配列に格納されます。そのために、オブジェクトポインタが正常に作成された後に配列に挿入するメソッドが必要になります。更に (1) 指定されたインデックスを持つポインタを配列から取得する、 (2) オブジェクト配列のサイズを取得する、 (3) 配列バッファを空にすることも必要です。

class CElement
  {
protected:
   //--- このコントロールのオブジェクトの全てへのポインタを含む配列
   CChartObject     *m_objects[];
   //---
public:
   //--- 指定されたインデックスによるオブジェクトポインタの取得
   CChartObject     *Object(const int index);
   //--- (1) コントロールオブジェクトの数の取得 (2) オブジェクト配列を空にする
   int               ObjectsElementTotal(void)          const { return(::ArraySize(m_objects)); }
   void              FreeObjectsArray(void)                   { ::ArrayFree(m_objects);         }
   //---
protected:
   //--- プリミティブオブジェクトポインタを共通配列に追加するメソッド
   void              AddToArray(CChartObject &object);
  };
//+------------------------------------------------------------------+
//| インデックスによってコントロールオブジェクトポインタを返す               |
//+------------------------------------------------------------------+
CChartObject *CElement::Object(const int index)
  {
   int array_size=::ArraySize(m_objects);
//--- オブジェクト配列のサイズを確認
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > No ("+m_class_name+") objects in this element!");
      return(NULL);
     }
//--- サイズが超過した場合の修正
   int i=(index>=array_size)?array_size-1 : (index<0)?0 : index;
//--- オブジェクトポインタを返す
   return(m_objects[i]);
  }
//+------------------------------------------------------------------+
//| オブジェクトポインタを配列に追加する                                  |
//+------------------------------------------------------------------+
void CElement::AddToArray(CChartObject &object)
  {
   int size=ObjectsElementTotal();
   ::ArrayResize(m_objects,size+1);
   m_objects[size]=::GetPointer(object);
  }

全てのコントロールは、チャートイベントと独自のタイマーの独自のイベントハンドラを持つことになります。各コントロールは一意のためCElement クラスではこれらのメソッドはvirtualになります。この問題は全てのオブジェクト(コントロール)コンテナとなるクラスを開発するときに詳細に議論されます。下記のメソッドもvirtualとなります。

class CElement
  {
public:
   //--- チャートイベントハンドラ
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {}
   //--- タイマー
   virtual void      OnEventTimer(void) {}
   //--- コントロールの移動
   virtual void      Moving(const int x,const int y) {}
   //--- (1)表示 (2)非表示 (3)リセット (4)削除
   virtual void      Show(void) {}
   virtual void      Hide(void) {}
   virtual void      Reset(void) {}
   virtual void      Delete(void) {}
   //--- マウスの左クリックの優先順位の(1)設定と(2)リセット
   virtual void      SetZorders(void) {}
   virtual void      ResetZorders(void) {}
  };

Objects.mqhファイルとCElementファイルにあるグラフィカルプリミティブのクラスの両方に、オブジェクトの境界線を取得することができるプロパティとメソッドがあることはもう見てきました。したがって、マウスのカーソルがコントロールの領域内にあるか、またそれが個別に各プリミティブオブジェクト上にあるかどうかを確認する機会です。これは何のために必要なのでしょうか。それは、プログラムのグラフィカルインタフェースをユーザにとって非常に直感的にするためです。

カーソルがインターフェースの要素の上にあるときは、その背景や枠の色が変化し、インターフェイスのこの要素がクリック可能でマウスクリックに反応することを示します。この機能をCElementクラスで実装する場合、色の操作に使用するメソッドが必要です。それらのいずれかで色の配列が定義されると、2色のみがこのメソッドに渡される必要があります。グラデーションがそれらから計算されます。計算は、チャートにオブジェクトを取り付ける瞬間に一度だけオブジェクトごとに行われます。2番目のメソッドでは、色の操作は既存する色配列でのみ行われ、リソースが大幅に節約されます。

グラデーション計算メソッドの作成は可能ですが、ここでは既存のクラスをコードベースからダウンロードします。このプロジェクトでは、多数のコードベースからのメソッドが他のクラスで使用されています。Dmitry Fedoseevは独自の色操作のクラス(IncColors) をコードベースに追加しましたが、ここでは私が少し修正したバージョンの使用が推薦されています。それは本稿の終わりでダウンロードできます(Colors.mqh)。

このクラス(CColors)には多数の一般的なメソッドがあります。私が導入した唯一の変更は、メソッド名がクラス本体にある場合やメソッド自体がクラス本体外にある場合の迅速なナビゲーションの可能性の追加です。これにより、必要なメソッドをより迅速かつ効率的に見つけて、Alt+Gキーを使用してコンテンツに/からメソッドを移動できるようになります。このファイルはEasyAndFastGUIフォルダに位置します。インターフェースライブラリのファイルは..\EasyAndFastGUI\ControlsディレクトリのElement.mqhファイルに含まれます。このファイルは1レベル上のディレクトリ内に配置されているので、以下に示すように含まれる必要があります。

//+------------------------------------------------------------------+
//|                                                      Element.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Objects.mqh"
#include "..\Colors.mqh"

CColorsクラスオブジェクトをCElementクラスに含み、(1)グラデーションの色数を指定する変数やメソッド、(2)グラデーション配列を初期化して指定されたオブジェクトの色を変更するためのメソッドを追加します。

class CElement
  {
protected:
   //--- 色操作のためのクラスインスタンス
   CColors           m_clr;
   //--- グラデーションの色数
   int               m_gradient_colors_total;
   //---
public:
   //--- グラデーションサイズの設定
   void              GradientColorsTotal(const int total)     { m_gradient_colors_total=total;  }
   //---
protected:
   //--- 配列グラデーションの初期化
   void              InitColorArray(const color outer_color,const color hover_color,color &color_array[]);
   //--- オブジェクトの色の変更
   void              ChangeObjectColor(const string name,const bool mouse_focus,const ENUM_OBJECT_PROPERTY_INTEGER property,
                                       const color outer_color,const color hover_color,const color &color_array[]);
  };

グラデーション配列の初期化にはCColorsクラスのGradient() メソッドを使用します。(1)グラデーションを計算するために使用される色の配列 、(2)グラデーションの色を順序に含む配列、 (3)グラデーションのステップの想定数(リクエストされた配列サイズ)は、パラメータとしてこのメソッドに渡されるべきです。

//+------------------------------------------------------------------+
//| グラデーション配列の初期化                                  |
//+------------------------------------------------------------------+
void CElement::InitColorArray(const color outer_color,const color hover_color,color &color_array[])
  {
//--- グラデーション色の配列
   color colors[2];
   colors[0]=outer_color;
   colors[1]=hover_color;
//--- 色の配列の作成
   m_clr.Gradient(colors,color_array,m_gradient_colors_total);
  }

オブジェクトの色を変更するメソッドでは、下記を指定するためのパラメータがあります。

更にはChangeObjectColor() メソッドの下のコメントをご覧ください。

//+------------------------------------------------------------------+
//| オブジェクト上のカーソルのホバリング時のオブジェクトの色の変更     |
//+------------------------------------------------------------------+
void CElement::ChangeObjectColor(const string name,const bool mouse_focus,const ENUM_OBJECT_PROPERTY_INTEGER property,
                                 const color outer_color,const color hover_color,const color &color_array[])
  {
   if(::ArraySize(color_array)<1)
      return;
//--- 現在のオブジェクトの色の取得
   color current_color=(color)::ObjectGetInteger(m_chart_id,name,property);
//--- カーソルがオブジェクトの上にある場合
   if(mouse_focus)
     {
      //--- 指定された色があったら戻る
      if(current_color==hover_color)
         return;
      //--- 最初から最後まで移動
      for(int i=0; i<m_gradient_colors_total; i++)
        {
         //--- 色が一致しない場合次に行く
         if(color_array[i]!=current_color)
            continue;
         //---
         color new_color=(i+1==m_gradient_colors_total)?color_array[i] : color_array[i+1];
         //--- 色を変える
         ::ObjectSetInteger(m_chart_id,name,property,new_color);
         break;
        }
     }
//--- カーソルがオブジェクトの領域外にある
   else
     {
      //--- 指定された色があったら戻る
      if(current_color==outer_color)
         return;
      //--- 最後から最初まで移動
      for(int i=m_gradient_colors_total-1; i>=0; i--)
        {
         //--- 色が一致しない場合次に行く
         if(color_array[i]!=current_color)
            continue;
         //---
         color new_color=(i-1<0)?color_array[i] : color_array[i-1];
         //--- 色を変える
         ::ObjectSetInteger(m_chart_id,name,property,new_color);
         break;
        }
     }
  }

全てのコントロールの他の一般的なプロパティは、オブジェクトのアンカーポイントとチャートコーナーです。

class CElement
  {
protected:
   //--- オブジェクトのチャートコーナーとアンカーポイント
   ENUM_BASE_CORNER  m_corner;
   ENUM_ANCHOR_POINT m_anchor;
  }

CElementクラスの作成が完成しました。本稿の最後では、このクラスの完全なバージョンをダウンロードすることができます。現在、ライブラリの構造は下の図に示されている通りです。黄色の矢印がファイルが含まれていることを意味すると仮定しましょう。もしクラスが含まれている場合、後者は含み側のファイルのクラスの基本クラスではありません。上記のCElementCColorsの関係で見られるように、それはクラスに含まれたオブジェクトとして使われます。

図4。色操作のためにCColorsクラスを含みます。

図4。CElementコントロールのベースクラス

 

グラフィカルインタフェースを使用したアプリケーションのための基本クラス

インターフェース要素の作成を開始する前に、オブジェクトの関係を実装するメソッドを定義する必要があります。そのスキームは、オブジェクトの格納のみでなく分類も含む1つのクラスから、各オブジェクトにアクセスする方法を設定する必要があります。したがって、このコンテナ内にどのようなオブジェクトがどのくらい多く存在しているかを見つけるだけでなく、それらを管理することになります。

全ての機能を1クラスに配置すると、クラスが混みすぎてあまり便利ではありません。このようなクラス(オブジェクト)は、あまりにも多くのタスクを持っておりオブジェクト指向プログラミングのアンチパターンであるので「神のオブジェクト」と呼ばれています。プロジェクトの構造が成長するにつれて、変更や追加を導入することは困難になります。したがって、オブジェクトは、イベントが処理されるクラスとは別に保存されます。オブジェクトはCWndContainer基本クラスに含まれ、イベントはCWndEvents派生クラスで対応されます。

CWndContainerCWndEventsクラスを作成しましょう。本稿の冒頭に記載されている全てのコントロールを作成するために、これらのクラスに必要な機能を追加していきます。今のところ、プロジェクトの一般的な構造を決定します。

Controlsフォルダで、WndContainer.mqhWndEvents.mqhファイルを作成します。まだコントロールを作成していないのでCWndContainerクラスは空となります。

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| インターフェースのオブジェクト全てを格納するクラス                   |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
                     CWndContainer(void);
                    ~CWndContainer(void);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                   |
//+------------------------------------------------------------------+
CWndContainer::CWndContainer(void)
  {
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CWndContainer::~CWndContainer(void)
  {
  }
//+------------------------------------------------------------------+

CWndEventsクラスはCWndContainerクラスから派生するので、WndContainer.mqhファイルはWndEvents.mqhファイルに含まれる必要があります。

//+------------------------------------------------------------------+
//|                                                    WndEvents.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "WndContainer.mqh"
//+------------------------------------------------------------------+
//| イベント処理のクラス                                         |
//+------------------------------------------------------------------+
class CWndEvents : public CWndContainer
  {
protected:
                     CWndEvents(void);
                    ~CWndEvents(void);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                   |
//+------------------------------------------------------------------+
CWndEvents::CWndEvents(void)
  {
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CWndEvents::~CWndEvents(void)
  {
  }
//+------------------------------------------------------------------+

CWndContainer及びCWndEvents クラスはグラフィカルインターフェースを必要とするMQLアプリケーションの基本クラスになります。

開発しながらこのライブラリをさらにテストするために、EAを作成します。これは別のフォルダに作成されなければなりません。メインプログラムの他にProgram.mqhインクルードファイルがありここに我々のプログラム(CProgram)が位置するからです。このクラスはCWndEventsクラスから派生します。

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\Controls\WndEvents.mqh>
//+------------------------------------------------------------------+
//| 取引パネル作成クラス                            |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
public:
                     CProgram(void);
                    ~CProgram(void);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                   |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CProgram::~CProgram(void)
  {
  }
//+------------------------------------------------------------------+

後にメインプログラムファイルに呼び出される、イベント処理のためのメソッドがMQLアプリケーションイベントの主な関数ハンドラー内に必要になります。

class CProgram : public CWndEvents
  {
public:
   //--- 初期化/初期化解除
   void              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- タイマー
   void              OnTimerEvent(void);
   //---
protected:
   //--- チャートのVirtualイベントハンドラ
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
  };

チャートのタイマーやイベントハンドラは、基本クラスのCWndEventsのより高いレベルでも作成する必要があります。

class CWndEvents : public CWndContainer
  {
protected:
   //--- タイマー
   void              OnTimerEvent(void);
   //--- チャートのVirtualイベントハンドラ
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {}
  };

上記のコードではCWndEvents基本クラスとCProgram派生クラスの両方で、チャートのOnEventハンドラがvirtualで宣言されていることにご注意下さい。CWndEventsクラスでは、このクラスはただの {} です。これによって、必要な場合にイベントの流れを基本クラスから派生クラスに導くことができます。 これらのクラスでのVirtualのOnEvent() メソッドは内部使用のために意図されています。メインプログラムからの呼び出しにはCWndEventsの別のメソッドが使用されます。それをChartEvent()と名付けます。また、コードをより明確で読みやすくするために、各種のメインイベントのためのヘルパーメソッドを作成します。

カスタムイベントのチェックを含むヘルパーメソッドとともに、コントロールのイベントをチェックするためのメソッドが必要とされます。これをCheckElementsEvents()と名付けます。これは下記で緑で強調されています。

class CWndEvents : public CWndContainer
  {
public:
   //--- チャートのイベントハンドラ
   void              ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //---
private:
   void              ChartEventCustom(void);
   void              ChartEventClick(void);
   void              ChartEventMouseMove(void);
   void              ChartEventObjectClick(void);
   void              ChartEventEndEdit(void);
   void              ChartEventChartChange(void);
   //--- controlのイベントチェック
   void              CheckElementsEvents(void);
  };

ヘルパーメソッドはChartEvent() メソッドの内側及びCWndEventsクラス内のみで使用されます。それらに同じパラメータを渡すのを避けるために、同様の変数をクラスのメンバーとして作成し、ChartEvent()メソッドの非常に最初に使用される初期化メソッドも作成してみましょう。これらはクラス内でのみ使用されるので、プライベート(private)セクションに配置されます。

class CWndEvents : public CWndContainer
  {
private:
   //--- イベントパラメータ
   int               m_id;
   long              m_lparam;
   double            m_dparam;
   string            m_sparam;
   //--- イベントパラメータの初期化
   void              InitChartEventsParams(const int id,const long lparam,const double dparam,const string sparam);
  };
//+------------------------------------------------------------------+
//| イベント変数の初期化                                |
//+------------------------------------------------------------------+
void CWndEvents::InitChartEventsParams(const int id,const long lparam,const double dparam,const string sparam)
  {
   m_id     =id;
   m_lparam =lparam;
   m_dparam =dparam;
   m_sparam =sparam;
  }

ここで、メインプログラムファイルで (1)CProgramクラスを含むファイルを含み、(2)インスタンスを作成し、(3)メインプログラム関数と連結します。

//+------------------------------------------------------------------+
//|                                                  TestLibrary.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2015, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//--- 取引パネルクラスを含む
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                  |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   program.OnInitEvent();
//--- 初期化が完成
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   program.OnDeinitEvent(reason);
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
  }
//+------------------------------------------------------------------+
//| タイマー関数                                                   |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
   program.OnTimerEvent();
  }
//+------------------------------------------------------------------+
//| 取引関数                                                   |
//+------------------------------------------------------------------+
void OnTrade(void)
  {
  }
//+------------------------------------------------------------------+
//| ChartEvent関数                                            |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   program.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

必要な場合、OnTick()やOnTrade()などの他のイベントハンドラメソッドもCProgram クラスに作成できます。

 

ライブラリとプログラムクラスのイベントハンドラのテスト

CProgramクラスのvirtualのOnEvent()メソッドがChartEvent()メソッドでCWndEvents基本クラスで呼び出しできることは先に述べました。動作確認が望まれるので、今、このメカニズムをテストすることができます。そのためには、下記のようにCWndEvents::ChartEvent()メソッドでCProgram::OnEvent()メソッドを呼び出します。

//+------------------------------------------------------------------+
//| プログラムイベントの処理                                          |
//+------------------------------------------------------------------+
void CWndEvents::ChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   OnEvent(id,lparam,dparam,sparam);
  }

その後CProgram::OnEvent()メソッドに下記のコードを書きます。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_CLICK)
     {
      ::Comment("x: ",lparam,"; y: ",(int)dparam);
     }
  }

ファイルをコンパイルしてEAをチャートに読み込みます。チャート上を左クリックした場合、マウスキーを解除する瞬間のカーソルの座標が左上隅に表示されます。テストの後では最後の2つのリストで強調表示されたコードはCWndEvents::ChartEvent()とCProgram::OnEvent()メソッドから削除できます。

 

おわりに

これまで話してきたことの要約を図として表示してみましょう。

図5。プロジェクトに保存ポインタとイベント処理のためのクラスを含みます。

図5。プロジェクトに保存ポインタとイベント処理のためのクラスを含みます。

現在、スキームは2つの独立した部分から構成されます。それらを連結するには、まず、ユーザインターフェースの主要要素の作成が必要です。主要要素はフォームまたはウィンドウで全てのコントロールがそれに連結されます。そこでそのようなクラスを書いてCWindowと名付けます。CElementクラスはCWindowクラスの基本クラスなのでElement.mqhクラスファイルをWindow.mqhと連結します。

パートIの資料はダウンロードされ、それがどのように動作するかをテストすることができます。ファイル資料の使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるか、本稿へのコメント欄でご質問ください。

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