MQL5のインタラクティブGUIで取引チャートを改善する(第2回):移動可能なGUI (II)
はじめに
連載第2回へようこそ。最初の回では、シンプルで移動可能なダッシュボードの作り方について説明しました。この第2回では、同じ目的を達成することを目的としていますが、より効率的な方法で、本格的なEA/指標アプリケーションに適しています。
例えば、画面上に2つの移動可能なダッシュボードを表示したい場合、既存のコードを複製し、それぞれ異なる名前で6つのグローバル変数を追加作成する必要があります。さて、もし移動可能なダッシュボードが3つ必要だとしたら、コードの複雑さは大幅に増し、管理はかなり難しくなるでしょう。より合理的なアプローチが必要なのは明らかです。
幸いなことに、.mqhファイルを使えばこのプロセスを簡略化できます。
この記事で取り上げる内容は以下の通りです。
クラスの概念について
深く掘り下げる前に、まずクラスの概念を理解することが不可欠です。クラスは、さらに掘り下げていくと驚くほど高度で複雑なものになる可能性がありますが、当面は基本的なものだけを取り上げます。これらの基本概念を理解し、効果的に活用することは、より複雑な細部に進む前の重要なステップです。
では、クラスとはいったい何なのでしょうか。
簡単に言えば、クラスは複雑なデータ型であり、intやstringなどに似ていますが、もう少し複雑です。
クラスを定義する方法は数多くありますが、基本的にはクラスはコードのクラスタと見なすことができます。どのようなコードかと尋ねられるかもしれません。通常、メソッドと呼ばれる関数と変数の集まりです。この定義がやや曖昧、あるいはやや不正確だという意見もあるかもしれません。しかし、ここで忘れてはならないのは、私たちは学生時代のように試験のために詰め込んでいるわけではないということです。私たちの第一の目的は、クラスの力を利用してコーディングをより管理しやすく、効率的にすることであり、そのためには厳密な定義は重要ではありません。
要するに、クラスは関数と変数の集合体であり、それを活用することで有利になります。
さて、この理解は当然4つの根本的な疑問につながります。
- どこで作るのか
- どのように宣言するのか
- どのように書くのか
- どのように使うのか
ところで、そもそもなぜクラスが必要なのかと頭を悩ませているのなら、答えは至極簡単です。クラスはコーディングプロセスを簡素化し、コード管理を簡単にします。
- どこで作るのか
.mq5であれ.mqhであれ、クラスを作成するファイルタイプは柔軟に選択できます。しかし、通常は.mqhファイルを分けて使います。
.mq5と.mqhでのクラス作成の違いは注目に値します。.mqhファイルでクラスを開発した場合、それを.mq5にインポートする必要があります。これは、EA/指標の作成が.mq5ファイルに限定されているためです。しかし、.mq5ファイル内で直接クラスを確立すれば、インポート処理は必要ありません。
コードの管理性を高めるため、一般的に.mqhファイルを分けることが好まれています。インポート処理は簡単で、1行のコードが必要なだけです。話を進める便宜上、別の.mqhファイルを使うことにします。
- どのように宣言するのか
クラスの宣言は簡単です。以下は、単純な空のクラスを宣言する方法の例です。
class YourClassName { };
上記のコードスニペットでは、YourClassNameはプレースホルダーなので、クラスに割り当てたい実際の名前に置き換えてください。
- 何を書けばいいのか
これを理解するために、関数の話に移る前に、まず変数について説明します。
2つの変数を宣言するとします。1つはint型、もう1つはbool型です。これは次のような方法でできます。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class YourClassName { int var1; bool var2; }; //+------------------------------------------------------------------+
重要なのは、クラス宣言の中でこれらの変数に直接値を代入することはできないということです。例えば、以下のコードはエラーになります。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class YourClassName { int var1 = "int var"; bool var2 = true; }; //+------------------------------------------------------------------+
次のエラーが表示されます。
'=' - illegal assignment use
'=' - illegal assignment useロジックが変数の初期値に依存するシナリオでは、コンストラクタと呼ばれるものを利用できます。さらに、デストラクタと呼ばれるものもあり、これは基本的にコンストラクタと対になる役割を果たします。
コンストラクタとデストラクタは、明示的に宣言するしないにかかわらず、常にクラスに付随する特別な関数です。宣言しなければ、暗黙のうちに空の関数とみなされます。コンストラクタはクラスのインスタンスが宣言されたときに実行され、デストラクタはクラスのインスタンスがスコープ外に出たときに実行されます。MQL5では、クラスのインスタンスを明示的に削除する方法がないことに注意することが重要です。
例えば、このコードのOnInit()関数はコンストラクタとして動作し、OnDeinit()関数はデストラクタとして動作します。ここでのクラスは、シンプルさを保つために背景に隠されています。このパターンは、常にデフォルトクラスを含むJavaを含む多くの言語で一般的です。
「インスタンス」が何を意味するかについては、次のステップ(インスタンスの使い方)で説明します。とりあえず、コンストラクタを使って変数に初期値を代入できることを理解しておきましょう。デストラクタは今回の議論には関係ありませんが、連載の後半で必ず取り上げます。コンストラクタを使用することを強くお勧めします。
プログラマーの中には、コンパイラの警告を無視して、変数を明示的に定義しなければ暗黙のうちにデフォルトに設定されると思い込んでいる人もいるかもしれませんが、このやり方は完全に正しいわけでも完全に間違っているわけでもありません。
この仮定は、多くのプログラミング言語では(MQL4でも)、明示的な定義のない変数は、コードに矛盾を生じさせることなくデフォルトで何らかの値になるという事実に由来します。しかしMQL5では、変数を明示的に定義しないとコードに矛盾が生じる可能性があります。
ここでは、最も一般的に使用されるデータ型のデフォルト値を示します。
種類 宣言するコード デフォルト値(推定) int int test; 0 double double test; 0.0 bool bool test; false string string test; NULL datetime datetime test; 1970.01.01 00:00:00 注: 推定デフォルト値は、初期化されていない変数を出力したときに表示される値です。同じことをif文でチェックすると、期待通りの動作をするものもあれば、そうでないものもあり、問題になる可能性があります。
テストスクリプトは次のようなものです。
void OnStart() { type test; if(test == presumedDefaultValue) { Alert("Yes, This is the default value of the test variable."); } else { Alert("No, This is NOT the default value of the test variable."); } }
typeを変数タイプに、presumedDefaultValueをデフォルトと予想される値に置き換えてください。
ここで、boolとstringについては、すべてが完璧に動作し、「Yes, This is default value of the test variable. 」というアラートがトリガーされることがわかるでしょう。しかし、int、double、datetimeの場合は、それほど単純ではありません。「No, This is NOT the default value of the test variable.」というアラートが表示されます。 この予期せぬ結果は、論理的な問題を引き起こす可能性があります。
コンストラクタの重要性を理解したところで、コンストラクタの作り方を見てみましょう。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class name { private: public: int var1; bool var2; name(); ~name(); }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ name::name() { var1 = 0; var2 = true; } //+------------------------------------------------------------------+
ここでは、クラスにpublicとprivateのフィールドを含めていますが、privateは現在空です。コンストラクタを他のファイルで使用するには、publicアクセス修飾子を付けて宣言することが非常に重要です。これを説明するために、アクセス修飾子(アクセス指定子とも呼ばれる)について説明します。
アクセス修飾子は、コンパイラが変数や構造体のメンバ、クラスにアクセスする方法を定義します。
アクセス修飾子 詳細 public 変数またはクラスメソッドへの無制限のアクセスを許可
private このクラスのメソッド、および継承クラスのメソッドからのアクセスを許可し、それ以外のアクセスは不可能
protected 同じクラスのメソッドからのみ、変数やクラスメソッドへのアクセスを許可します。
virtual クラスメソッドにのみ適用され(構造体のメソッドには適用されない)、このメソッドをクラスの仮想関数の表に置くようにコンパイラに指示 この記事ではpublicとprivateのみを説明します。それ以外は使用しません。
この記事では、public修飾子とprivate修飾子に焦点を当て、他の修飾子については後編に譲ることにします。publicとは、基本的に、publicとして定義された変数や関数は、異なる.mq5ファイルや.mqhファイルを含め、どこでも使用/変更(変数の場合)できることを意味します。一方、privateは現在のクラスで定義された関数内でのみアクセスを許可します。
そもそもなぜそれらが必要なのかという疑問があるなら、データの隠蔽、抽象化、保守性、再利用性など、その理由は多くあります。
コンストラクタのコードはこのように定義しました。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ name::name() { var1 = 0; var2 = true; } //+------------------------------------------------------------------+
コンストラクタ/デストラクタは戻り値を持ちません。
コンストラクタ/デストラクタには戻り値がないことに注意してください。ここでは、初期化されていない変数に値を代入しているだけです。bool変数(つまり、この場合はvar2)に初期値としてtrueを設定する必要がある場合があるからです。
別の方法もあります。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ name::name() : var1(0), var2(true) { } //+------------------------------------------------------------------+
このコンストラクタは本文を持たず、初期化されていない変数の値を望む値に初期化するだけです。どちらの方法でも良いですが、一般的には2番目の方法を使う方が良いです。例えば、変数の宣言時にconstを使って変更不可にした場合、初期化されていない変数に値を代入する最初の方法は機能しませんが、2番目の方法は機能します。最初の方法では値を代入し、2番目の方法では値を初期化しているからです。
あるいは、メンバーリストにもそう書くことができます。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class name { private: public: int var1; bool var2; name() : var1(0), var2(true) {} ~name(); }; //+------------------------------------------------------------------+
変数、コンストラクタ、デストラクタの宣言方法を説明したので、関数の作り方を理解するのは簡単でしょう。入力を文字列変数として受け取り、その変数を単純に表示するfunctionName()という名前の関数を作りたい場合、次のようになります。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class className { public: void functionName(string printThis) }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void className::functionName(string printThis) { Print(printThis); } //+------------------------------------------------------------------+
この関数をどこでも使えるように、classNameというクラスのメンバリストでpublicアクセス修飾子をつけて宣言します。次に、関数本体を書きます。クラス内での関数の宣言、つまり
functionName(string printThis)
は、関数本体を書くときに正確に一致しなければなりません。
これでMQL5でのクラスの書き方についての基本的な紹介は終わりです。
- どのように使うのか
これをよりよく理解するために、フォルダ構造を見てみましょう。
- テストプロジェクト
- mainFile.mq5
- includeFile.mqh
まず、includeFile.mqhに書いたクラスコードを見てみましょう。
この例では、コンストラクタ、デストラクタ、3つの変数(privateが1つ、publicが2つ)、public関数を含む classNameという名前のクラスを宣言しています。
- コンストラクタ:変数var0、var1、var2をそれぞれ10、0、trueに初期化します。
- Destructor:現在は空なので何もしません。
- var0:このprivate整数変数は、10で初期化され、関数(functionName)で使用されます。
- var1:このpublic整数変数は、0で初期化され、関数(functionName)でも使用されます。
- functionName:このvoid関数は、整数printThisNumberを受け取り、printThisNumber、var0、var1の合計を表示します。
次にmainFile.mq5を見てみましょう。
#include "includeFile.mqh" className classInstance; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { classInstance.functionName(5)//This line will print (5+10+0) = 15 classInstance.var1 = 50;//This will change the value of var1 to 50 classInstance.functionName(5)//Now, this line will print (5+10+50) = 65 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
まず、includeFile.mqhファイルをmainFile.mq5にインクルードします。そして、次のようにしてクラスのインスタンスを作成します。
className classInstance;
このインスタンスによって、クラスの変数や関数を変更したり使用したりすることができます。このプロセスはインスタンス化と呼ばれますが、ここでは深い定義は省きます。
インスタンスは好きなだけ作成でき、それらはすべて互いに独立しています。.mqhファイルの検索に「<>」の代わりに「""」を使ったのは、「<>」がincludeフォルダ内の.mqhファイルを探すのに対し、「""」はカレントディレクトリ内の.mqhファイルを探すからです。
このウォークスルーでは、クラスの使い方をしっかりと理解することができます。 - テストプロジェクト
.mqhファイルを使って同じダッシュボードを作成する
同じようなダッシュボードを、今回は.mqhファイルを使ってゼロから作る旅に出ましょう。必要であれば、以前のコードの一部を拝借します。コードファイルを効率的に整理するために、「Movable Dashboard MQL5」という適切な名前の新しいフォルダを作成します。
次に、2 つの新しいファイルを生成します。Movable_Dashboard_MQL5.mq5ファイル(これが主要な.mq5ファイルとなる)と、ダッシュボードを移動可能にするコードを格納するGUI_Movable.mqhファイルです。これらのファイルに適切な名前を付けることは、複数のファイルを簡単に管理するために非常に重要です。
まず、メインの.mq5ファイル(Movable_Dashboard_MQL5.mq5)のOnInit()内のObject Createメソッドを使用して、200x200の白いダッシュボードを作成します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //Set the name of the rectangle as "TestRectangle" string name = "TestRectangle"; //Create a Rectangle Label Object at (time1, price1)=(0,0) ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100); //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100); //Set XSize to 200px i.e. Width of Rectangle Label ObjectSetInteger(0, name, OBJPROP_XSIZE, 200); //Set YSize to 200px i.e. Height of Rectangle Label ObjectSetInteger(0, name, OBJPROP_YSIZE, 200); ChartRedraw(); //--- return(INIT_SUCCEEDED); }
結果:
図1 ダッシュボードの基本画像
なぜ.mqhファイル(GUI_Movable.mqh)ではなく、メインの.mq5ファイル(Movable_Dashboard_MQL5.mq5)にダッシュボードを作成しているのか疑問に思うかもしれません。この決断は主に単純化のためであり、具体的な目標に左右されます。次のセクションではそのアプローチを採用します。
.mqhファイル(GUI_Movable.mqh)に注目してみましょう。これは現在次のようになります。
//+------------------------------------------------------------------+ //| Class GUI_Movable | //+------------------------------------------------------------------+ class GUI_Movable { }; //+------------------------------------------------------------------+
ここでは、コンストラクタとデストラクタを明示的に定義することなく、単にクラスを宣言しています。
では、私たちの目的は何なのでしょうか。私たちは、このコードをメインファイルに実装できるように調整することで、ダッシュボードを移動可能にすることを目指しています。
さて、どうすればそれを達成できるでしょうか。前回の.mq5ファイル(Movable_Dashboard_MQL5.mq5)でダッシュボードを移動可能にするために必要なコードはこれだけです。
//Declare some global variable that will be used in the OnChartEvent() function int previousMouseState = 0; int mlbDownX = 0; int mlbDownY = 0; int mlbDownXDistance = 0; int mlbDownYDistance = 0; bool movingState = false; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case if(id == CHARTEVENT_MOUSE_MOVE) { //define X, Y, XDistance, YDistance, XSize, YSize int X = (int)lparam; int Y = (int)dparam; int MouseState = (int)sparam; string name = "TestRectangle"; int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit() int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit() int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit() int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit() if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click { mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard { movingState = true; //If yes the set movingState to True } } if(movingState)//if movingState is true, Update the Dashboard position { ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX) ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY) ChartRedraw(0); //Redraw Chart } if(MouseState == 0)//Check if MLB is not pressed { movingState = false;//set movingState again to false ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again } previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value } } //+------------------------------------------------------------------+
では、次のように話を進めましょう。
- GUI_Movableクラスのコードを書く
- メインの.mq5ファイルにクラスのインスタンスを作成する
- そのインスタンスに名前をつける
- GUI_Movableクラスのメソッドを使用してダッシュボードを移動可能にする
これらの手順は、最初は難しく見えるかもしれませんが、練習すれば直感的に理解できるようになります。
- GUI_Movableクラスのコードを書く
クラスの構成要素を計画する必要があります。内訳は次の通りです。
- private修飾子を持つ6つの変数(previousMouseState、mlbDownX、mlbDownY、mlbDownXDistance、mlbDownYDistance、movingState)を宣言します。これらの変数には5つの整数と1つのブール値が含まれます。
- ダッシュボードの名前を格納する7番目のpublic変数を宣言します。メインの.mq5ファイルから変更する必要があるので、この変数をpublicにする必要があります。
- .mqhファイルのOnChartEvent関数を利用する方法を見つける必要があります。宣言した変数はすべてそこにあり、OnChartEvent関数内でこれらの変数が必要だからです。
-
まず、クラス内で6つのprivate変数を宣言しましょう。そのうち5つは整数、1つはブール値です。これらの値を初期化するためにコンストラクタを使用します。
//+------------------------------------------------------------------+ //| Class GUI_Movable | //+------------------------------------------------------------------+ class GUI_Movable { private: int previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance; bool movingState; public: GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false) {}; }; //+------------------------------------------------------------------+
初期値としてint型は0、bool型はfalseが必要なので、コンストラクタを使って初期化しています。
-
次に、ダッシュボードの名前を格納するpublic変数を宣言します。この変数はメインの.mq5ファイルからアクセスできる必要があります。
public: string Name;
初期値はもちろんNULLですが、形式的にNULLに初期化し、コンストラクタを次のように変更します(文字列は矛盾を起こさないので形式的)。
GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
-
このステップは少し複雑に見えるかもしれませんが、理解すれば簡単です。
OnEventという名前のpublic関数を作ります。この関数はid、lparam、dparam、sparamという入力を受け付けます。OnChartEvent()は何も返さない(void)ので、OnEvent()もvoidにします。
OnEvent関数は、OnChartEvent()がおこなうように設計されているすべてのことをおこないますが、.mqhファイル内でおこないます。メインファイルの実際のOnChartEvent()関数でOnEvent()を使うことにします。
.mqhファイルとmainファイルの両方でOnChartEvent()を宣言することによるエラーを避けるために、OnEvent()という名前の関数を別に作成しました。こう宣言します。
public: string Name; void OnEvent(int id, long lparam, double dparam, string sparam);
では、関数コードを書いてみましょう。これは、オリジナルのOnChartEvent()がおこなうように設計されていたすべてのことをおこないます。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam) { } //+------------------------------------------------------------------+
この関数はグローバルスコープに置きました。さて、ここに同じコードを書くだけで、クラスで宣言された変数にアクセスできるようになります。
完全な関数は次のようになります。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam) { //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case if(id == CHARTEVENT_MOUSE_MOVE) { //define X, Y, XDistance, YDistance, XSize, YSize int X = (int)lparam; int Y = (int)dparam; int MouseState = (int)sparam; string name = Name; int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit() int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit() int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit() int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit() if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click { mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard { movingState = true; //If yes the set movingState to True } } if(movingState)//if movingState is true, Update the Dashboard position { ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX) ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY) ChartRedraw(0); //Redraw Chart } if(MouseState == 0)//Check if MLB is not pressed { movingState = false;//set movingState again to false ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again } previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value } } //+------------------------------------------------------------------+
唯一
string name = "TestRectangle";
を
string name = Name;
に変えます。もちろん、メインの.mq5ファイルで設定したName変数を使う必要があるからです。
-
メインの.mq5ファイルにクラスのインスタンスを作成する
これは次のように簡単にできます。
#include "GUI_Movable.mqh" GUI_Movable Dashboard;
ここでは、ファイルの場所を指定するために「<>」の代わりに「""」を選択して.mqhファイルを含めています。「<>」はincludeフォルダ内の.mqhファイルを探しますが、「""」はカレントディレクトリ(この場合は 「Movable Dashboard MQL5」という名前のフォルダ)内の.mqhファイルを探します。次に、GUI_Movableクラスのインスタンスを宣言し、便利な名前「Dashboard」を割り当てます。この名前によって、.mqhファイルに書いたコードを利用することができます。
-
そのインスタンスに名前をつける
これはOnInit()関数の中で簡単に実行できます。OnInit()関数は次のようになります。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //Set the name of the rectangle as "TestRectangle" string name = "TestRectangle"; //Create a Rectangle Label Object at (time1, price1)=(0,0) ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100); //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100); //Set XSize to 200px i.e. Width of Rectangle Label ObjectSetInteger(0, name, OBJPROP_XSIZE, 200); //Set YSize to 200px i.e. Height of Rectangle Label ObjectSetInteger(0, name, OBJPROP_YSIZE, 200); //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //Give dashboard's name to the class instance Dashboard.Name = name; //--- return(INIT_SUCCEEDED); }
最終的には//Give dashboard's name to the class instance Dashboard.Name = name;
を使用して、GUI_MovableクラスのDashboardインスタンスにName変数を割り当てます。これは後にインスタンス内のOnEvent()関数で利用されます。CHART_EVENT_MOUSE_MOVEプロパティをtrueに設定することを忘れないでください。これにより、マウスイベントの検出が可能になります。後でコンストラクタでこのステップを繰り返すことになるが、今は単純にしておきます。
-
ダッシュボードを移動可能にするには、GUI_Movableクラスのメソッドを使用します:
少々複雑な名前ですが、このステップは簡単です。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { Dashboard.OnEvent(id, lparam, dparam, sparam); }
このフェーズでは、OnChartEvent()の機能を.mqhファイルで利用するために、OnChartEvent()内にOnEvent()関数を配置します。
最終的に、これが完全なコードです。
フォルダ構造:
- Movable Dashboard MQL5
- Movable_Dashboard_MQL5.mq5
- GUI_Movable.mqh
- Movable_Dashboard_MQL5.mq5
#include "GUI_Movable.mqh" GUI_Movable Dashboard; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //Set the name of the rectangle as "TestRectangle" string name = "TestRectangle"; //Create a Rectangle Label Object at (time1, price1)=(0,0) ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100); //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100); //Set XSize to 200px i.e. Width of Rectangle Label ObjectSetInteger(0, name, OBJPROP_XSIZE, 200); //Set YSize to 200px i.e. Height of Rectangle Label ObjectSetInteger(0, name, OBJPROP_YSIZE, 200); //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //Give dashboard's name to the class instance Dashboard.Name = name; //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { Dashboard.OnEvent(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+
- GUI_Movable.mqh
//+------------------------------------------------------------------+ //| Class GUI_Movable | //+------------------------------------------------------------------+ class GUI_Movable { private: int previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance; bool movingState; public: GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {}; string Name; void OnEvent(int id, long lparam, double dparam, string sparam); }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam) { //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case if(id == CHARTEVENT_MOUSE_MOVE) { //define X, Y, XDistance, YDistance, XSize, YSize int X = (int)lparam; int Y = (int)dparam; int MouseState = (int)sparam; string name = Name; int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit() int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit() int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit() int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit() if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click { mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard { movingState = true; //If yes the set movingState to True } } if(movingState)//if movingState is true, Update the Dashboard position { ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX) ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY) ChartRedraw(0); //Redraw Chart } if(MouseState == 0)//Check if MLB is not pressed { movingState = false;//set movingState again to false ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again } previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value } } //+------------------------------------------------------------------+
まず.mqhファイルをコンパイルし、次に.mq5ファイルをコンパイルします。これでチャートに添付できる.ex5ファイルが作成されます。
これらのステップで、第1回で達成したことをより効率的なコードで再現しました。第1回と第2回では、メインの.mq5ファイルで使用されているコードの量が大きく異なることに注目してください。そして何より素晴らしいのは、ここからさらに良くなっていくということです。
結果:
図2:シンプルな移動可能ダッシュボード
.mqhファイルを使用して同じチャートに2つのダッシュボードを設定する
さて、メインの.mq5ファイルでObjectCreateを使ってダッシュボードを作成する代わりに、.mqhファイルでこれをおこないます。そうすれば、物事がいかにシンプルになるかがわかるでしょう。
.mqhファイルに加える変更を掘り下げてみましょう。
-
文字列変数Nameの修飾子をpublicからprivateに変更する必要があります。Nameはメインファイルでは必要ありません。.mqhファイルだけです。よってprivateにします。これは次のようにできます。
変更前:
//+------------------------------------------------------------------+ //| Class GUI_Movable | //+------------------------------------------------------------------+ class GUI_Movable { private: int previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance; bool movingState; public: GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {}; string Name; void OnEvent(int id, long lparam, double dparam, string sparam); };
変更後:
//+------------------------------------------------------------------+ //| Class GUI_Movable | //+------------------------------------------------------------------+ class GUI_Movable { private: int previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance; bool movingState; string Name; public: GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {}; void OnEvent(int id, long lparam, double dparam, string sparam); };
変数修飾子をpublicからprivateに変更した
string Name;
の場所を変えただけです。
-
次に、CreateDashboard()という名前のpublicメソッドを追加します。このメソッドが受け取る入力は、name (string)、xDis (int)、yDis (int)、xSize (int)、ySize (int)です。
まず、これをクラスのメンバーリストに追加します。
public: void OnEvent(int id, long lparam, double dparam, string sparam); void CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);
では、この関数をグローバルスペースで定義し、メインファイルから以下のようにコードをコピーしてみましょう。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) { //Create a Rectangle Label Object at (time1, price1)=(0,0) ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis); //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis); //Set XSize to 200px i.e. Width of Rectangle Label ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize); //Set YSize to 200px i.e. Height of Rectangle Label ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize); //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //Give dashboard's name to the class instance Name = name; //Redraw Chart ChartRedraw(); } //+------------------------------------------------------------------+
続いて、.mq5ファイルを修正する必要があります。
#include "GUI_Movable.mqh" GUI_Movable Dashboard1; GUI_Movable Dashboard2; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Dashboard1.CreateDashboard("Dashboard1", 100, 100, 200, 200); Dashboard2.CreateDashboard("Dashboard2", 100, 350, 200, 200); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { Dashboard1.OnEvent(id, lparam, dparam, sparam); Dashboard2.OnEvent(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+
それを噛み砕いて説明します。
まず、GUI_Movableクラスの定義を含む GUI_Movable.mqh ファイルをインクルードします。このクラスは、移動可能なダッシュボードに関連するイベントを作成し、処理するためのメソッドを持っています。
次に、GUI_Movableクラスの2つのインスタンス、Dashboard1とDashboard2を宣言します。これらのインスタンスは、このプログラムで作成し制御する2つのダッシュボードを表しています。
エキスパートアドバイザー(EA)の起動時に自動的に呼び出されるOnInit()関数で、2つのインスタンスに対してCreateDashboard()メソッドを呼び出して2つのダッシュボードを作成します。このメソッドには、ダッシュボードの名前とその位置とサイズ(ピクセル単位)をパラメータとして渡します。その後、この関数は、初期化が成功したことを示すINIT_SUCCEEDEDを返します。
OnChartEvent()関数は、チャート上でイベント(マウスのクリックや移動など)が発生するたびにトリガーされます。この関数では、2つのダッシュボードインスタンスのOnEvent()メソッドを呼び出し、受け取ったすべてのパラメータを渡します。これにより、GUI_MovableクラスのOnEvent()メソッドで定義されたロジックに従って、各ダッシュボードが独立してイベントを処理できるようになります。
ご覧のように、この方法は同じ機能を維持しながらシンプルでクリーンです。これにより、本格的なEA/指標で非常に有用なコードになります。
次は、.mqhファイルの完全なコードです。
//+------------------------------------------------------------------+ //| Class GUI_Movable | //+------------------------------------------------------------------+ class GUI_Movable { private: int previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance; bool movingState; string Name; public: GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {}; void OnEvent(int id, long lparam, double dparam, string sparam); void CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize); }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam) { //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case if(id == CHARTEVENT_MOUSE_MOVE) { //define X, Y, XDistance, YDistance, XSize, YSize int X = (int)lparam; int Y = (int)dparam; int MouseState = (int)sparam; string name = Name; int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit() int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit() int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit() int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit() if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click { mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard { movingState = true; //If yes the set movingState to True } } if(movingState)//if movingState is true, Update the Dashboard position { ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX) ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY) ChartRedraw(0); //Redraw Chart } if(MouseState == 0)//Check if MLB is not pressed { movingState = false;//set movingState again to false ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again } previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) { //Create a Rectangle Label Object at (time1, price1)=(0,0) ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis); //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis); //Set XSize to 200px i.e. Width of Rectangle Label ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize); //Set YSize to 200px i.e. Height of Rectangle Label ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize); //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //Give dashboard's name to the class instance Name = name; //Redraw Chart ChartRedraw(); } //+------------------------------------------------------------------+
結果:
図3:同じチャート上の2つの移動可能ダッシュボード
結論
既存のダッシュボードを移動可能にする場合、そのプロセスは非常に簡単です。「.mqh ファイルを使用して同じダッシュボードを作成する」セクションを参照すると、既存の EA/指標のわずか数行のコードで、任意のダッシュボードを移動可能なダッシュボードに変換できることがわかります。必要なのは、GUI_Movable.mqhファイルをインクルードしてクラスのインスタンスを作成し、ダッシュボードの名前をこのインスタンスに割り当てることだけです。これらの簡単なステップで、ダッシュボードがインタラクティブになり、マウスで簡単に移動できるようになります。
この第2回を終えて、ダッシュボードを移動可能にしてインタラクティブ性を高める方法を学ぶことができました。既存のEA/指標に適用することも、ゼロから新しいEA/指標を構築することもできます。
これは長い記事で、クラスの概念を説明し理解するのは難しいかもしれないが、この知識は読者コーディングの旅に大いに役立つと信じています。
この記事が少しでも参考になったことを心から願っています。次回またお会いできるのを楽しみにしています。
ハッピーコーディング、ハッピートレーディング!
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/12880
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索