初級から中級まで:オブジェクトイベント(III)
はじめに
前回の「初級から中級まで:オブジェクトイベント(II)」では、OBJ_LABELオブジェクトのテキストを、オブジェクトのプロパティダイアログを開いて編集することなく、チャート上で直接編集できる、非常に興味深く、少しユニークなアプリケーションの実装方法を紹介しました。
これは実用面だけでなく学習面から見ても非常に興味深く有用な内容でしたが、実は前回紹介したものよりさらに興味深いことが実現できます。読者の皆さんも実際に試されたうえで、「このアプリケーションは、プログラムで作成したOBJ_LABELオブジェクトだけでなく、任意のOBJ_LABELオブジェクトにも適用できるのではないか」と考えられたかもしれません。そこで今回は、その方法を紹介します。この仕組みは、後ほど取り上げる別の要素と組み合わせることで、非常に魅力的なソリューションとなります。
それでは、いつものように最初のトピックに進みましょう。気が散るものはひとまず脇に置き、本記事で扱う内容に集中してください。今回は、非常に興味深いポイントをいくつか詳しく見ていきます。
すでに優れていたものをさらに改善する
前回の記事では、次のアニメーションのような仕組みを実現できることを紹介しました。

アニメーション01
この仕組みにより、OBJ_LABELオブジェクトに表示されているテキストを、チャート上で直接編集できるようになります。しかし、新しいOBJ_LABELを手動で追加してみると、アニメーション01のような編集はおこなえないことが分かります。これはなぜでしょうか。その理由は、イベントを処理する際に使用しているフィルタにあります。最初は、単純にそのフィルタを取り除けば問題は解決するように思えるかもしれません。確かに、最初のうちはその方法でも動作します。しかし実際に試してみると、アプリケーションの動作がおかしくなり始めることに気付くでしょう。
そこで今回は、前回の記事で作成したソースコードをどのように変更すれば、アニメーション01の仕組みを利用して、任意のOBJ_LABELオブジェクトを編集できるようになるのかを簡単にご説明します。
一見すると複雑そうに思えるかもしれませんが、コードを変更する際にいくつかの注意点を守れば、実際にはそれほど難しいものではありません。必要となる内容の多くは、これまでの記事ですでに説明していますので、今回は本当に重要な部分に絞って解説します。以下をご覧ください。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. switch (id) 17. { 18. case CHARTEVENT_OBJECT_CLICK : 19. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) Swap_ObjLabel_ObjEdit(sparam); 20. break; 21. case CHARTEVENT_OBJECT_ENDEDIT : 22. Swap_ObjLabel_ObjEdit(sparam); 23. break; 24. } 25. ChartRedraw(); 26. }; 27. //+------------------------------------------------------------------+ 28. void Swap_ObjLabel_ObjEdit(const string szName) 29. { 30. struct st_Mem 31. { 32. string font, text; 33. int x, y, w, h, fontSize; 34. color cor; 35. }mem; 36. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_EDIT ? OBJ_LABEL : OBJ_EDIT; 37. 38. mem.font = ObjectGetString(0, szName, OBJPROP_FONT); 39. mem.x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 40. mem.y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 41. mem.w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 42. mem.h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 43. mem.fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 44. mem.cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 45. mem.text = ObjectGetString(0, szName, OBJPROP_TEXT); 46. 47. ObjectDelete(0, szName); 48. ChartRedraw(); 49. ObjectCreate(0, szName, type, 0, 0, 0); 50. 51. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (type != OBJ_EDIT ? true : false)); 52. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, mem.x); 53. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, mem.y); 54. ObjectSetInteger(0, szName, OBJPROP_XSIZE, mem.w); 55. ObjectSetInteger(0, szName, OBJPROP_YSIZE, mem.h); 56. ObjectSetInteger(0, szName, OBJPROP_COLOR, mem.cor); 57. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, mem.fontSize); 58. ObjectSetString(0, szName, OBJPROP_FONT, mem.font); 59. ObjectSetString(0, szName, OBJPROP_TEXT, mem.text); 60. if (type == OBJ_EDIT) 61. { 62. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, clrWhite); 63. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 64. } 65. } 66. //+------------------------------------------------------------------+
コード01
さて、コード01を見てみましょう。このコードのどこが難しいのでしょうか。本連載の記事をここまで読み進め、内容を実際に試しながら学習されてきた方であれば、おそらく次のように答えられるでしょう。「難しいところは何もありません。すべてとてもシンプルで分かりやすいです。」そのとおりです。コード01は非常にシンプルで分かりやすいため、その動作について改めて説明する必要はないと考えています。とはいえ、このコードには小さな問題があります。もちろん、複雑なものではありません。その修正方法を紹介する前に、コード01を任意のチャートに適用すると何が起こるのかを確認してみましょう。まずは、以下に示す最初の手順から始めます。

アニメーション02
アニメーション02では、チャートにコード01だけを適用しています。この時点では何も起こりません。これは、このコードの目的が新しいオブジェクトを作成することではなく、すでに存在しているオブジェクトを操作することだからです。続いて、複数のOBJ_LABELオブジェクトを追加します。その方法は次のとおりです。

図01
この図は、目的のオブジェクトをチャートへ追加する方法を示しています。必要な数だけ追加してください。ここでは、既存の問題点を示すことが目的ですので、OBJ_LABELは2個だけ追加します。すると、チャートは次のようになります。

図02
ここから、すべてが腑に落ちる部分に入ります。チャート上でアプリケーションが動作しており、対象となる型のオブジェクトが存在している状態では、これまでと同じ操作はできなくなります。これは、OBJ_LABELオブジェクトをクリックすると、そのオブジェクトがOBJ_EDITオブジェクトへ変換され、表示されているテキストをチャート上で直接編集できるようになるためです。しかも、この一連の操作はOBJ_LABELオブジェクトのプロパティダイアログを開くことなくおこなえます。

アニメーション03
アニメーション03から分かるように、前回の記事で説明したとおり、これらのオブジェクトは実際に編集できます。さらに重要なのは、この仕組みが任意のOBJ_LABELオブジェクトに対して利用できるようになったことです。それだけではありません。少し方法は異なりますが、さらに多くのことが可能になります。そのためには、オブジェクトリストを開きます。ここから、色、フォント、フォントサイズ、さらにはオブジェクト名などの各種プロパティを変更できます。
もっとも、この方法で変更するのは副次的なプロパティです。いずれにしても、OBJ_EDITとOBJ_LABELは同じプロパティを使用します。これは、コード01の28行目にあるプロシージャが、それらのプロパティを自動的に引き継ぐためです。
では、この節の冒頭で触れた問題点とは何でしょうか。実は、それほど深刻な問題ではありません。選択と選択解除の処理に関する小さな不具合です。コード01のアプリケーションでは、多くの場合、チャート上にはOBJ_EDITオブジェクトが1つだけ存在すれば十分です。状況によっては、複数のOBJ_EDITオブジェクトが同時に存在しても、あまり意味がありません。しかし、選択・選択解除の処理に問題があるため、次のような状態になってしまいます。

アニメーション04
最初はOBJ_LABELが2つだけだったにもかかわらず、その後OBJ_EDITも2つ作成されてしまうことが分かります。これは修正可能です。もちろん、この動作が完全に誤っているというわけではありません。しかし、目的がOBJ_LABELのテキストをチャート上で直接編集できるようにすることであるなら、アニメーション04のような状態になる必然性はありません。
この問題の修正は非常に簡単です。必要なのは、イベント処理のコードを少し変更するだけです。本記事の例では、以下に示すようにコードを修正すれば十分です。
. . . 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. static string szOldEdit = ""; 17. 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) 22. { 23. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 24. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 25. } 26. break; 27. case CHARTEVENT_OBJECT_ENDEDIT : 28. Swap_ObjLabel_ObjEdit(sparam); 29. szOldEdit = ""; 30. break; 31. } 32. ChartRedraw(); 33. }; 34. //+------------------------------------------------------------------+ . . .
コード02
この解決方法の適用がいかに簡単であるかに注目してください。必要なのは、コード02の断片で示されている箇所を変更するだけです。その結果は、以下のアニメーション05で確認できます。

アニメーション05
「なるほど。これで、単に正しいように見えるという理由だけで、誰もが繰り返し使っている方法をそのまま真似する前に、内容をより深く学び、実際に練習することがどれほど重要なのか理解できたと思います。ところで、このように丁寧に作成方法を説明してくださったアプリケーションについて、1つ質問があります。オブジェクトリストウィンドウを開かなくても、ほかのパラメータを制御する方法はないのでしょうか。なぜ聞くのかというと、どうやらチャート上でオブジェクトの位置を変更する方法がないように思えるからです。オブジェクトを配置し、アプリケーションがチャート上で動作している状態になると、そのオブジェクトを移動することはほとんど不可能になります。
このような状況を処理する方法はあるのでしょうか。現在示されている操作方法は非常に興味深いと思いますが、この点だけは少し気になっています。」
はい、読者の皆さん。この問題を解決する方法はあります。だからこそ、いくつかのことを正しく理解することが重要なのです。常に、使用された具体的なコードではなく、その概念そのものを理解するようにしてください。なぜなら、コードは変更できますが、概念は変更できないからです。
ここで示しているアプリケーションがチャート上で実行されている状態でも、オブジェクトの位置を制御する方法はいくつかあります。最も簡単な方法の1つは、OBJ_EDITオブジェクトの動作方法を変更することです。
前回の記事では、OBJ_EDITオブジェクトの内容を編集するためには、そのプロパティの1つに特定の値を設定する必要があると説明しました。同じプロパティに別の値が設定されている場合、編集は無効になります。「しかし、なぜこのことが重要なのでしょうか。目的はチャート上でオブジェクトを移動できる仕組みを作ることではなかったでしょうか。特定のプロパティと、それがOBJ_EDITオブジェクトに与える影響についての情報が、どうしてオブジェクトを移動する問題の解決に役立つのでしょうか。よく分かりません。もう少し詳しく説明していただけますか。」
もちろんです。私が何を意図しているのか、さらに詳しく説明しましょう。ご記憶のとおり、前回と前々回の記事では、必要な場合にコードがオブジェクトイベントを捕捉する方法を説明しました。ここで、オブジェクトをどのように移動させるべきかを私たち自身がすべて管理するという、余計な作業を増やしたくありません。そこで、その役割をMetaTrader 5に任せます。この部分は簡単に理解できます。おそらく難しい部分、そしてオブジェクトの仕組みを十分に理解することが重要になる理由は、次の点です。OBJ_EDITオブジェクトが選択されている状態で編集できない場合、オブジェクトの移動処理をMetaTrader 5に任せることができます。
初心者プログラマーの大部分が最も理解するのに苦労する点は、ここです。コード01にコード02の断片を追加した後でも、コード01は依然としてOBJ_LABELオブジェクトをOBJ_EDITオブジェクトへ変換します。これは、後で表示されるテキストを編集するためです。しかし、そのOBJ_EDITオブジェクトをもう一度クリックした場合はどうなるでしょうか。現在扱っているコードの内容だけを見ると、答えは「何も起こらない」です。しかし、少し考えてみてください。オブジェクトの移動が完了すると、MetaTrader 5はイベントを生成します。そして、ユーザーがオブジェクトのドラッグを終え、それが新しい位置に配置されたことをアプリケーションへ通知します。
したがって、小さな戦略を適用することで、OBJEDITオブジェクトを、MetaTrader 5 が移動を管理できる状態に切り替えることができます。これにより、ユーザーはチャート上でオブジェクトを移動できるようになります。ユーザーがオブジェクトのドラッグを終えた時点で、編集処理を終了することもできますし、単純にOBJ_EDITオブジェクトを再びOBJ_LABELオブジェクトへ変換することもできます。どちらの方法が目的に適しているかは、読者の皆さん自身が判断してください。
このように説明すると、少し複雑に感じるかもしれません。しかし、考え方をコードに変換すると、物事ははるかに理解しやすくなります。その前に、読者の皆さんに1つお見せしたいことがあります。これは単純な例ですが、非常に重要な1つの詳細を、より明確に理解する助けになります。
前回の記事で、OBJ_EDITオブジェクトのOBJPROP_SELECTABLEプロパティをfalseに設定する必要があると説明しました。しかし、その理由については説明しませんでした。場合によっては、OBJPROP_SELECTABLEをtrueに設定した状態でもOBJ_EDITオブジェクトのテキストを編集できることがあります。ただし、なぜそのようなことが可能なのかまで説明すると、内容は間違いなくかなり複雑になります。そこで、実際の動作を確認するために、以下のコードをご覧ください。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 15. { 16. static string szOldEdit = ""; 17. 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_LABEL) 22. { 23. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 24. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 25. } 26. break; 27. case CHARTEVENT_OBJECT_ENDEDIT : 28. Swap_ObjLabel_ObjEdit(sparam); 29. szOldEdit = ""; 30. break; 31. } 32. ChartRedraw(); 33. }; 34. //+------------------------------------------------------------------+ 35. void Swap_ObjLabel_ObjEdit(const string szName, bool Selectable = true) 36. { 37. struct st_Mem 38. { 39. string font, text; 40. int x, y, w, h, fontSize; 41. color cor; 42. }mem; 43. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_EDIT ? OBJ_LABEL : OBJ_EDIT; 44. 45. mem.font = ObjectGetString(0, szName, OBJPROP_FONT); 46. mem.x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 47. mem.y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 48. mem.w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 49. mem.h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 50. mem.fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 51. mem.cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 52. mem.text = ObjectGetString(0, szName, OBJPROP_TEXT); 53. 54. ObjectDelete(0, szName); 55. ChartRedraw(); 56. ObjectCreate(0, szName, type, 0, 0, 0); 57. 58. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable); 59. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, mem.x); 60. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, mem.y); 61. ObjectSetInteger(0, szName, OBJPROP_XSIZE, mem.w); 62. ObjectSetInteger(0, szName, OBJPROP_YSIZE, mem.h); 63. ObjectSetInteger(0, szName, OBJPROP_COLOR, mem.cor); 64. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, mem.fontSize); 65. ObjectSetString(0, szName, OBJPROP_FONT, mem.font); 66. ObjectSetString(0, szName, OBJPROP_TEXT, mem.text); 67. if (type == OBJ_EDIT) 68. { 69. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, clrWhite); 70. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 71. } 72. } 73. //+------------------------------------------------------------------+
コード03
コード03はコード02と似ています。コード02では設定全体の一部だけを示しましたが、今回はほぼ完全な形で示しています。この点は35行目で確認できます。第2引数に指定されている値が使用される場所は1か所だけです。それが58行目です。ここで注意してください。皆さんがコード内で実装する設定の種類によっては、この部分が少し分かりにくくなる可能性があります。現在の状態では、コード03は次のような動作になります。

アニメーション06
「ちょっと待ってください。このコードはコード02のようには動作しません。これは一体どういうことでしょうか。たった1つのプロパティを変更しただけで、本当にコードの動作がこのように変化するのでしょうか。これは非常に不思議です。しかし、オブジェクトの選択と同時に編集を許可することはできないのでしょうか。」場合によっては可能です、読者の皆さん。しかし、いくつかの問題については、特定のプロパティがどのように動作するのか、内部の動作まで説明する必要があるため、説明がかなり難しくなります。そのため、皆さんを混乱させないように、私は別の方法で説明することにします。
ここまでで、皆さんはおそらく次の点に気付かれたと思います。OBJ_EDITオブジェクトに対してOBJPROP_SELECTABLEプロパティが有効になっている場合、チャート上で直接テキストを編集することはできないと考える必要があります。しかし、その代わりに、オブジェクトを移動することは完全に可能になります。
では、これらの事実を踏まえて、期待した動作になるようにコード03を変更してみましょう。これは、以下に示すようにコードを修正することで実現できます。
. . . 18. switch (id) 19. { 20. case CHARTEVENT_OBJECT_CLICK : 21. { 22. ENUM_OBJECT type = (ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE); 23. if (type == OBJ_EDIT) ObjectSetInteger(0, sparam, OBJPROP_SELECTABLE, false); 24. else if (type == OBJ_LABEL) 25. { 26. if (szOldEdit != "") Swap_ObjLabel_ObjEdit(szOldEdit); 27. Swap_ObjLabel_ObjEdit(szOldEdit = sparam); 28. ObjectSetInteger(0, sparam, OBJPROP_SELECTED, true); 29. } 30. } 31. break; . . .
コード04
この場合、コード04ではコード03で実際に変更する必要がある部分だけに注目します。つまり、修正が必要だった断片だけを示します。そして、この断片をコード03、または必要であればコード02に適用すると、結果は次のようになります。

アニメーション07
「まったく、なんて突拍子もなく、あり得ないような解決方法なのでしょう。こんな方法を思いつくなんて、正気とは思えません。OBJ_LABELオブジェクトをチャート上で直接移動したり編集したりする問題を解決するために、このような方法を考え出すことなど不可能だったはずです。」確かに、非常に奇妙なアイデアです。
しかし、その奇妙さはひとまず置いておくとして、今ご紹介した内容は面白いと思いませんか。結局のところ、少しの努力だけで、チャート上のOBJ_LABELオブジェクトを直接編集する方法を作り出すことができたのです。もちろん、この解決方法が100%完璧というわけではありません。なぜなら、このアプリケーションの動作に慣れるためには、しばらく使用してみる必要があるからです。アニメーション07で示されているように、これは決められたタイミングで実行する必要がある一連の操作になります。そのため、24行目のチェックを通過すると、OBJ_LABELオブジェクトをOBJ_EDITオブジェクトへ変換します。しかし、オブジェクトを移動できるようにする必要があるため、28行目ではオブジェクトの選択を有効にします。これにより、チャート上でOBJ_EDITオブジェクトを移動できるようになります。
同じオブジェクトをもう一度クリックすると、23行目のチェックによって選択が無効になります。その結果、次のクリックではMetaTrader 5はOBJ_EDITオブジェクト自体を選択するのではなく、テキストを選択するようになります。これにより、テキストを変更できるようになります。この結果は十分に良いものですが、さらに改善することも可能です。しかし、ここでの目的はアプリケーションを完成させることではありません。目的は、何が可能なのか、そしてそれをどのように実現できるのかを示すことです。
チャート上でサイズを直接管理する
ここまで見てきた内容は、控えめに言っても非常に楽しく、興味深いものだったと言えるでしょう。しかし、これから見ていく内容は、それ以上に興味深いものになります。では、始めましょう。ここで述べておきたいのは、皆さんの創造力や、さまざまな要素をどのように組み合わせられるかを想像する力によって、実現できる可能性の数は増えたり減ったりするということです。これは単純な理由によるものです。私たちはすでに、MetaTrader 5とアプリケーションがどのように相互作用するのかを理解しています。そのため、ある種類の結果を得たり、アプリケーションがどのように動作するべきかという特定のモデルを作成したりできるようになっています。
私の考えでは、MetaTrader 5を単なる取引のためだけではなく、別のものを作成するために利用することは非常に興味深いことです。たとえば、グラフィックエディタ、テキストエディタ、あるいはパズルや昔のアーケードゲームのようなシンプルなゲームを作成するための小さなプラットフォームなどです。
多くの人は、そのようなことは不可能だと考えるかもしれません。なぜなら、一見すると、そのような機能を実現するために必要な仕組みを私たちは持っていないように思えるからです。しかし、多くの人がMetaTrader 5の可能性について考えていることや、知っているつもりになっていることとは異なり、実際には、このような機能をプラットフォーム上で実装するための可能性は存在します。重要なのは、想像力と必要な知識を持つことです。さらに重要なのは、正しいコンセプトを理解し、それらを自分たちのためにどのように活用するかを知ることです。もしそれができれば、単調で面白みのない価格チャートを見るだけに制限されることなく、MetaTrader 5上で動作する多くの興味深いものを作成できるようになります。もしかすると、3D要素などを利用して、価格チャートを表示する方法そのものを、少し変わった形で作り出すことさえできるかもしれません。
しかし現時点では、おそらく皆さんはまだそこまで想像できる段階には達していないでしょう。つまり、そのようなことを実現するためには、現在の私たちはまだその基礎部分にいるということです。そして、皆さんが想像するかもしれないこととは異なり、MQL5プログラミングという観点でも、私たちはまだ基本的な段階にいます。むしろ、初心者レベルと言ってもよいでしょう。それでも、この基本レベルだけで、すでにこれほど多くのことを実現できています。
さて、それでは次に、別の非常に簡単に実現できる内容を見ていきましょう。ただし、これまでの記事を追いかけてこなかった場合には、少し理解するのが難しく感じられるかもしれません。今回はいくつかの技術を利用するため、一見すると少し複雑に見える可能性があります。しかし、私たちが実現できる可能性を、基本レベルの範囲だけに制限したくはありません。何かを実現できるのであれば、それを実現してみましょう。たとえ、その仕組みや使用されているコンセプトを本当に理解するまでに時間がかかったとしてもです。
今回のテーマの目的は、マウスを使用してチャート上のオブジェクトサイズを変更する方法を作成することです。ただし、ここではマウスイベントを直接使用せずに実現することを目指します。難しそうに聞こえませんか。考え方としては、これまでと同様にMetaTrader 5に特定のイベントの管理を任せながら、そのイベントを私たちが制御し、特定の結果を発生させるように利用します。
これは本当に非常に難しい作業のように思えます。まるで、施設から抜け出してきた狂人が、自分以外のすべての人間を同じ考え方に変えようとしているような、そんな人物だけが思いつくような方法に感じるかもしれません。しかし、実際には非常に簡単です。必要なのは、ただ続けていくことです。もしかすると、それよりもさらに簡単かもしれません。ただし、このテーマは非常に広範囲にわたり、正しく説明し理解するためには1本以上の記事が必要になります。そこで、まずは次の事実から始めましょう。これまでのコードでおこなってきたこととは異なり、今回はすでに、皆さん自身、あるいは他のユーザーが、さまざまな種類のオブジェクトのサイズを操作できるようにします。
ただし、最初の段階ではすべての種類のオブジェクトに対応するわけではありません。それがアプリケーションによって作成されたものなのか、ユーザーによってチャートへ追加されたものなのかは関係ありません。すべて同じ方法で扱います。もちろん、いくつかの制限はあります。しかし、それらについてはコードを実装していく過程で確認していきます。それではまず、以下のコードを見てみましょう。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 11. 12. return INIT_SUCCEEDED; 13. }; 14. //+------------------------------------------------------------------+ 15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 16. { 17. return rates_total; 18. }; 19. //+------------------------------------------------------------------+ 20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 21. { 22. switch (id) 23. { 24. case CHARTEVENT_KEYDOWN : 25. break; 26. case CHARTEVENT_OBJECT_CLICK : 27. break; 28. case CHARTEVENT_OBJECT_DRAG : 29. break; 30. } 31. ChartRedraw(); 32. }; 33. //+------------------------------------------------------------------+ 34. void OnDeinit(const int reason) 35. { 36. ObjectsDeleteAll(0, def_Prefix); 37. ChartRedraw(); 38. }; 39. //+------------------------------------------------------------------+
コード05
コード05は基本テンプレートになります。ここで注目していただきたいのは、コード05では3つのイベントを処理対象として指定している点です。これは、ここで「fantastic」と「fabulous」を合わせた「fantabulous」のような、やたら大げさなアプリケーションを作成しようとしているわけではないからです。今回おこなうことは、もっと単純で分かりやすいものです。つまり、チャート上で任意のオブジェクトのサイズを変更し、必要であれば位置も変更できるようにすることです。ただし、位置については、実装する価値が本当にあるのか、現在も検討しています。
「分かりました。ここではあなたが進行役ということですね。それでは、次に何をするのでしょうか。」さて、読者の皆さん。次のステップは、CHARTEVENT_OBJECT_CLICKイベントを使用して、これから操作対象となるオブジェクトの情報を取得することです。基本的に、私たちが扱う情報はこの1つだけです。それはオブジェクト名です。なぜなら、私たちにとって本当に重要な情報はこれだけであり、それ以外のすべての情報は、この名前から取得できるからです。そこで、ここではコード全体を再び表示する必要がないように、OnChartEvent関数に注目します。最初の変更点は、以下のようになります。
. . . 19. //+------------------------------------------------------------------+ 20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 21. { 22. static string szNameObj = NULL; 23. 24. switch (id) 25. { 26. case CHARTEVENT_KEYDOWN : 27. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) szNameObj = NULL; 28. break; 29. case CHARTEVENT_OBJECT_CLICK : 30. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 31. if (szNameObj != NULL); 32. if (szNameObj != sparam); else szNameObj = NULL; 33. break; 34. case CHARTEVENT_OBJECT_DRAG : 35. break; 36. } 37. ChartRedraw(); 38. }; 39. //+------------------------------------------------------------------+ . . .
コード06
「まだ、これから何をしようとしているのか分かりません。短いコードを共有していただけますか。少し不安になってきました。」さて、読者の皆さん。ご要望どおりにしましょう(笑)。ただし、その前に、コード06の断片に組み込まれているロジックに注目してください。このロジックを理解することは、次に実行する処理を予測するうえで非常に重要です。したがって、次のステップは以下のコードで示します。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. struct st_BoxResize 09. { 10. private : 11. ushort x, y, w, h; 12. bool isBack; 13. string szObjects[4]; 14. //+----------------+ 15. void CreateEdge(const char what) 16. { 17. ushort p1 = x, p2 = y, p3 = w, p4 = h; 18. 19. switch (what) 20. { 21. case 0: p4 = 1; break; 22. case 1: p3 = 1; break; 23. case 2: p2 = y + h; p4 = 1; break; 24. case 3: p1 = x + w; p3 = 1; break; 25. } 26. szObjects[what] = macro_NameObject; 27. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 28. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 29. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 30. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 31. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 32. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 33. } 34. //+----------------+ 35. public : 36. void CreateBoxResize(const string szArg, const uchar adjust = 2) 37. { 38. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 39. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 40. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 41. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 42. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 43. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 44. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 45. for (uchar c = 0; c < 4; c++) 46. CreateEdge(c); 47. } 48. //+----------------+ 49. void RemoveBoxResize(const string szArg) 50. { 51. ObjectSetInteger(0, szArg, OBJPROP_BACK, isBack); 52. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 53. ObjectsDeleteAll(0, def_Prefix); 54. } 55. //+----------------+ 56. }gl_RZ; 57. //+------------------------------------------------------------------+ 58. int OnInit() 59. { 60. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 61. 62. return INIT_SUCCEEDED; 63. }; 64. //+------------------------------------------------------------------+ 65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 66. { 67. return rates_total; 68. }; 69. //+------------------------------------------------------------------+ 70. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 71. { 72. static string szNameObj = NULL; 73. 74. switch (id) 75. { 76. case CHARTEVENT_KEYDOWN : 77. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 78. { 79. gl_RZ.RemoveBoxResize(szNameObj); 80. szNameObj = NULL; 81. } 82. break; 83. case CHARTEVENT_OBJECT_CLICK : 84. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 85. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 86. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 87. break; 88. case CHARTEVENT_OBJECT_DRAG : 89. break; 90. } 91. ChartRedraw(); 92. }; 93. //+------------------------------------------------------------------+ 94. void OnDeinit(const int reason) 95. { 96. ObjectsDeleteAll(0, def_Prefix); 97. ChartRedraw(); 98. }; 99. //+------------------------------------------------------------------+
コード07
「わあ!やめてください。そこまで大げさにする必要はありません。」いや、皆さんが求めたものです。これがその結果です。一見すると非常に複雑に見えるかもしれませんが、このコード自体は非常に単純で、基本的なものです。これを理解するために必要なのは、どのように処理がおこなわれているのかを落ち着いて確認することだけです。いずれにしても、ここで何をしているのかを簡単に説明します。なぜなら、これは後で実行する処理の最初の部分になるからです。
08行目では、選択したオブジェクトの周囲に枠を形成することを目的とした構造体を作成しています。ここで注意してください、読者の皆さん。この枠を作成する対象となるオブジェクトは、直交座標を持っている必要があります。価格スケールに関連付けられた座標を持つオブジェクトについては、現時点では対象としていません。MetaTrader 5では、この種類のオブジェクトはそれほど多くありません。そして、その中で関心を持つ可能性があるものは、OBJ_TEXTオブジェクトとOBJ_BITMAPオブジェクトの2つだけです。そのため、現時点ではこれらを扱う理由はないと考えています。ただし、それ以外のオブジェクトも私たちにとって重要になります。
OnChartEventイベントハンドラでは、構造体内部にある複数の関数を呼び出していることに注目してください。しかし、その構造自体には内部的なprivate関数が含まれています。これが、選択したオブジェクトを囲む線を作成する役割を担当しています。
今回は、比較的明るい色を使用しています。これは28行目で定義されています。必要であれば、より適していると思う別の色へ変更することもできます。ただし、どのように動作しているのかを理解するための実験でない限り、ほかの部分のコードは変更しないようにしてください。次に、38行目では、選択されたオブジェクトが前面に表示されているのか、それとも背景オブジェクトなのかを記録しています。これは私たちにとって重要です。なぜなら、どのような場合でも、対象オブジェクトを背景へ送り、選択状態を解除することで、そのオブジェクトが選択状態のままにならないようにするためです。
なぜこれをおこなうのでしょうか。次に実行する処理の邪魔にならないようにするためです。覚えておいてください。オブジェクトが選択されている場合、チャートウィンドウ上で、その特定のポイントを使用してオブジェクトを移動できます。これはMetaTrader 5が直接処理している動作です。しかし、今回の場合、その動作は必要ありません。なぜなら、私たち自身が制御を引き取り、管理するからです。まずは、このコードがどのように動作しているのかを完全に理解する必要があります。ただし、この記事を終える前に、コード07がどのようなことを実現できるのかを確認してみましょう。これは、次のアニメーションで示します。その前に、まずチャート上に存在するオブジェクトを確認してください。特にオブジェクト名に注目してください。これは、イベントハンドラがそれぞれのオブジェクトをどのように処理するのかを理解するうえで重要です。これは図03で確認できます。

図03
それでは、デモンストレーションアニメーションに移りましょう。

アニメーション08
まとめ
この記事では、OBJ_LABELオブジェクトとOBJ_EDITオブジェクトを組み合わせて使用し、チャート上で直接テキストを編集できるようにする方法を説明しました。さらに、最初は不可能だったとしても、オブジェクトを移動できるようにするためのロジックを追加する方法についても確認しました。これは、OBJ_LABELオブジェクトからOBJ_EDITオブジェクトへ変換し、再び戻すという処理をおこなった場合、オブジェクト移動の実装という点で行き詰まってしまうためです。
また、次回の記事でさらに詳しく学習する内容の始まりについても確認しました。しかし、今回おこなった内容を皆さんに十分理解していただきたいと考えています。なぜなら、後で必要になるからです。そのため、少し先の内容へ進み、私の考えでは、MQL5を使用してMetaTrader 5を制御することについて、皆さんにさまざまなアイデアを与えるものを示すことにしました。
今回はここまでです。それでは、次回の記事でお会いしましょう。引き続き学習を頑張ってください。また近いうちにお会いしましょう。
| MQ5ファイル | 説明 |
|---|---|
| コード01 | オブジェクトイベントのデモンストレーション |
| コード02 | オブジェクトイベントのデモンストレーション |
| コード03 | オブジェクトイベントのデモンストレーション |
| コード04 | オブジェクトイベントのデモンストレーション |
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/16075
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
市場シミュレーション(第23回):ポジション表示(I)
MQL5カスタムシンボル:3Dバー用シンボルの作成
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
因果推論とアルゴリズム取引における回帰モデルの活用
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索