初級から中級まで:オブジェクトイベント(II)
はじめに
前回の「 初級から中級まで:オブジェクトイベント(I)」では、オブジェクトで発生するイベントを中心に、基本的な内容を解説し、具体例を示しました。 しかし、オブジェクトとユーザーとの相互作用によって発生するイベントについては、まだ3種類取り上げる必要があります。 この3種類のうち1つは、MetaTrader 5がこのイベントを実際に発生通知するようにするには、明示的に有効化する必要があります。 そうでなければ、たとえコードがこのイベントを処理できるよう準備されていても、そのイベントが発生したという通知は決して受け取ることができません。
これらのイベントを組み合わせて使用する前に、それぞれの種類と動作を正しく理解しておくことが重要です。そこで、まずは短いセクションに分けて、MetaTrader 5 が残り3種類のイベントをどのような状況で、どのように生成するのかを説明します。 それらを理解したうえで、イベントを組み合わせ、興味深い機能を実現する方法を見ていきましょう。 それでは、始めましょう。
CHARTEVENT_OBJECT_DELETEイベント
このイベントは、CHARTEVENT_OBJECT_CREATEイベントやその他のマウス関連イベントと同様に、アプリケーション側で有効化または無効化する必要があります。 これにより、チャート上で動作しているアプリケーションが、チャート上のオブジェクトが削除された際に通知を受け取れるよう、MetaTrader 5に設定できます。
このイベントは、予防的な目的で利用できると言えます。 これは重要な点です。アプリケーション内で特定のオブジェクト構成を維持したい場合、このイベントを利用すれば、ユーザーが誤って削除操作をおこなった場合や、その他の理由で、重要なオブジェクトや不可欠なオブジェクトをチャートから削除してしまうのを防ぐことができます。
基本的に、このイベントの仕組みは非常にシンプルです。 ただし、コードを記述・実装する際には、いくつか注意すべき点があります。 その理由の一つは、場合によっては配色に関する問題が発生することです。 その結果、アプリケーションがオブジェクトを自動的に再作成しようとした際に、そのオブジェクトを正しく表示することが非常に難しくなる場合があります。
ただし、ここで取り上げるのは、そのような問題ではありません。 本記事は、あらゆるケースに対応できる汎用的な解決策の実装方法を解説することを目的としていません。 そもそも、状況によって必要な対応は異なります。
チャートから誤って削除されたオブジェクトを再作成するための、あらゆる状況に対応できる完全に統合された汎用的な方法は存在しません。 プログラマーであれば、こうした事態に対処するための方法をいくつも考えられるでしょう。 個人的には、小さな構造体を使ってアプリケーションが作成したオブジェクトのプロパティを保存しておく方法を勧めます。 そうしておけば、アプリケーションがオブジェクトを再作成する必要が生じた際に、適切な配色や位置を指定して復元できます。 もちろん、フォントの種類やサイズ、オブジェクト自体のサイズなど、ほかのプロパティについても同様です。これで、私の言いたいことはおわかりいただけたと思います。
さて、簡単な前置きはこのくらいにして、この記事で最初に紹介するコードを見ていきましょう。 コードは以下の通りです。
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. string gl_szObjectName; 09. //+------------------------------------------------------------------+ 10. int OnInit() 11. { 12. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 13. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 14. 15. gl_szObjectName = macro_NameObject; 16. Proc_Object(); 17. 18. return INIT_SUCCEEDED; 19. }; 20. //+------------------------------------------------------------------+ 21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 22. { 23. return rates_total; 24. }; 25. //+------------------------------------------------------------------+ 26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 27. { 28. switch (id) 29. { 30. case CHARTEVENT_MOUSE_MOVE: 31. ObjectSetString(0, gl_szObjectName, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 32. break; 33. case CHARTEVENT_OBJECT_DELETE: 34. if (sparam == gl_szObjectName) Proc_Object(); 35. break; 36. } 37. ChartRedraw(); 38. }; 39. //+------------------------------------------------------------------+ 40. void OnDeinit(const int reason) 41. { 42. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 43. ObjectsDeleteAll(0, def_Prefix); 44. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 45. ChartRedraw(); 46. }; 47. //+------------------------------------------------------------------+ 48. void Proc_Object(void) 49. { 50. ObjectCreate(0, gl_szObjectName, OBJ_LABEL, 0, 0, 0); 51. ObjectSetInteger(0, gl_szObjectName, OBJPROP_SELECTABLE, true); 52. ObjectSetInteger(0, gl_szObjectName, OBJPROP_XDISTANCE, 50); 53. ObjectSetInteger(0, gl_szObjectName, OBJPROP_YDISTANCE, 50); 54. ObjectSetInteger(0, gl_szObjectName, OBJPROP_XSIZE, 150); 55. ObjectSetInteger(0, gl_szObjectName, OBJPROP_COLOR, clrMediumBlue); 56. ObjectSetInteger(0, gl_szObjectName, OBJPROP_FONTSIZE, 20); 57. ObjectSetString(0, gl_szObjectName, OBJPROP_FONT, "Lucida Console"); 58. } 59. //+------------------------------------------------------------------+
コード01
コード01の目的は非常にシンプルです。チャートからオブジェクトを削除すると、何が起こるのかを確認することです。 MetaTrader 5にオブジェクトが削除されたことを通知させるには、イベント通知を有効化し、適切なタイミングで無効化する必要があります。このコードを使えば、その仕組みをより簡単に理解できます。
おそらく、最も難しいのは通知の有効化ではありません。通知を有効にするのは非常に簡単で、13行目の処理を実行するだけです。 通常、削除イベントを見逃さないように、アプリケーションの実行開始時に通知を有効にします。 本当に難しいのは、通知をいつ無効にすべきかを正確に判断することです。 MetaTrader 5には独自のイベント処理の仕組みがあり、通知を無効にするタイミングによっては、一見すると奇妙な結果が生じることがあります。ただし、そのほとんどは実害のないものです。
この仕組みを理解するには、42行目の処理を移動してみてください。 この処理は、チャートからオブジェクトが削除されたときに、イベント通知を無効にする役割を担っています。 ソースコードを実際に動かしながら、42行目と43行目の処理を入れ替えてみましょう。 すると、チャートからインジケータを削除したときに、興味深い結果が得られます。 この実験を通じて、処理を必ずしも任意の順序で実行できるわけではないことがわかるはずです。 また、こうした仕組みを理解することで、通常は説明が難しいさまざまな問題についても理解を深めることができます。
さて、コード01を実行すると、基本的に2種類の操作を確認できます。 まず、最初の例を次のアニメーションで見てみましょう。

アニメーション01
ここでは、オブジェクトを選択し、DELETEキーを押して削除します。 注目してほしいのは、DELETEキーを押した直後に、オブジェクトが再作成されることです。 ただし、情報の更新処理はマウスイベントに関連付けられています。そのため、マウスを動かすなどしてマウスイベントが発生するまでは、情報が正しく表示されないことがあります。 次に、オブジェクト一覧ウィンドウからそのオブジェクトを削除しようとした場合を見てみましょう。 こちらの動作は、次のアニメーションで確認できます。

アニメーション02
アニメーション01と同様に、アニメーション02でも、オブジェクトを削除した直後に、そのオブジェクトが再作成されることがわかります。 ただし、オブジェクト一覧ウィンドウには、その変化がすぐには反映されません。 再作成されたオブジェクトを一覧に再び表示するには、オブジェクト一覧ウィンドウをいったん閉じて、開き直す必要があります。
このように、削除イベントを利用すれば、チャートからオブジェクトが削除されたことを検知できます。 ここからは、さらに2つのケースを検討していきます。 ただし、これらのケースでは、いくつか興味深い問題が生じます。 詳しく説明するために、ここで新しいセクションに進みましょう。
CHARTEVENT_OBJECT_CHANGEイベント
今回取り上げるイベントも、その仕組みを理解すると非常に興味深いものです。 このイベントは、オブジェクトのプロパティが変更されるたびに発生します。そのため、状況によっては、さまざまな便利な機能を実装するために活用できます。
前のセクションでは、削除されたオブジェクトを復元するための、あらゆる状況に対応できる完全に汎用的な仕組みを作ることはできないと説明しました。 これは事実です。 しかし、コードの実装方法を工夫すれば、ほとんどの種類のオブジェクトを、より汎用的な方法で再作成する仕組みを実現できる可能性があります。 ここまでの説明からすると、これはかなり難しい作業に思えるかもしれません。 ですが、焦る必要はありません。比較的シンプルな仕組みでも、優れたアプリケーションを作ることはできます。そして、経験を積むことで、将来的にはさらに良い方法を見つけられるかもしれません。 知識も経験も、積み重ねによって増えていきます。 だからこそ、学び、実際に手を動かして試すことが大切です。 焦らず、一歩ずつ学び続けていきましょう。
このイベントがどれほど興味深いものなのか、そして、実装内容を正確に理解することがなぜ重要なのかを示すために、前のセクションで取り上げたコード01を修正し、今回のイベントが実際にどのように動作するのかを確認してみましょう。 そのためのコードを、以下に示します。
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_Obj 09. { 10. //+----------------+ 11. private: 12. string szName, 13. font; 14. bool Selectable; 15. int x, 16. y, 17. w, 18. fontSize; 19. color cor; 20. //+----------------+ 21. public: 22. //+----------------+ 23. void SetDefault(const string arg) 24. { 25. szName = arg; 26. font = "Lucida Console"; 27. Selectable = true; 28. x = 50; 29. y = 50; 30. w = 150; 31. cor = clrMediumBlue; 32. fontSize = 20; 33. } 34. //+----------------+ 35. void Create(void) 36. { 37. ObjectCreate(0, szName, OBJ_LABEL, 0, 0, 0); 38. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable); 39. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x); 40. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y); 41. ObjectSetInteger(0, szName, OBJPROP_XSIZE, w); 42. ObjectSetInteger(0, szName, OBJPROP_COLOR, cor); 43. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize); 44. ObjectSetString(0, szName, OBJPROP_FONT, font); 45. } 46. //+----------------+ 47. string GetName(void) 48. { 49. return szName; 50. } 51. //+----------------+ 52. }gl_Obj; 53. //+------------------------------------------------------------------+ 54. int OnInit() 55. { 56. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 57. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 58. 59. gl_Obj.SetDefault(macro_NameObject); 60. gl_Obj.Create(); 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. switch (id) 73. { 74. case CHARTEVENT_MOUSE_MOVE: 75. ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 76. break; 77. case CHARTEVENT_OBJECT_DELETE: 78. if (sparam == gl_Obj.GetName()) gl_Obj.Create(); 79. break; 80. case CHARTEVENT_OBJECT_CHANGE: 81. Comment("An event occurred: CHARTEVENT_OBJECT_CHANGE in object ["+sparam+"]"); 82. break; 83. } 84. ChartRedraw(); 85. }; 86. //+------------------------------------------------------------------+ 87. void OnDeinit(const int reason) 88. { 89. Comment(""); 90. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 91. ObjectsDeleteAll(0, def_Prefix); 92. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 93. ChartRedraw(); 94. }; 95. //+------------------------------------------------------------------+
コード02
コード02を実行すると、2つのことを確認できます。 まず、1つ目を次のアニメーションで見てみましょう。

アニメーション03
オブジェクトのプロパティを変更すると、その時点でイベントが発生し、80行目で捕捉されることに注目してください。 イベントの発生元をフィルタリングしていないため、どのオブジェクトのプロパティを変更しても、アニメーション03に示されているように、81行目の処理によってチャートの左上隅にメッセージが表示されます。 しかし、本当に興味深いのは、アニメーション04に示すように、2つ目のイベントが発生したときです。

アニメーション04
「おかしいですね。 なぜオブジェクトのプロパティが変更後の状態で保持されないのでしょうか。 35行目に示したプロシージャを呼び出しているはずです。 コード01の場合は理由がわかります。しかし、今回のケースでは、オブジェクトがチャートから削除されても、アプリケーションによって同じ属性を持つオブジェクトが再作成されるものと思っていました。 アニメーション03で変更した値を初期値に戻すために、SetDefault関数が呼び出されたわけでもありません。 それなのに、元の状態に戻ってしまうのは、少し不思議です。」
読者の皆さんも、こうした現象には注意してください。プログラミングを始めたばかりの人が陥りやすい落とし穴の1つに、コードを書いていても、そのコード自体の内部的な仕組みを十分に理解できていないという問題があります。 これは、多くの場合、どこかからコピーしてきたコードを十分に理解しないまま使用することが原因です。 しかし、新しい実装を試しているときにも、同じことが起こり得ます。一見するとすべてが問題なく動作しているように見えても、テストを進めるうちに、まだ実装されていない細かな部分があることに気づくのです。 いずれにしても、これは間違いというわけではありません。 むしろ、将来的に役立つ可能性のある新しい解決策を試し、その動作を検証するための良い機会です。
では、何が問題なのでしょうか。 重要なのは、MetaTrader 5上でオブジェクトのプロパティを変更しても、そのオブジェクトを作成したときの設定値が更新されるわけではない、という点です。 更新されるのは、その場のオブジェクト側のプロパティだけです。 そのため、オブジェクトが削除され、コード02の80行目で再作成されると、グローバル変数に定義され、引き続き保存されているパラメータやプロパティの値がそのまま使用されます。
つまり、オブジェクトはコード内で指定された設定に従って再作成されるのです。 変更後の設定を再作成時にも反映させるには、アプリケーション側でオブジェクトの設定値を更新し、変更内容を保持できるようにする必要があります。 そのために利用するのが、82行目でおこなっているイベントの捕捉です。 これが、コード02でイベントの発生元をあえてフィルタリングしていない理由です。 まずは、チャート上のオブジェクトのプロパティが変更されたとき、その通知がどのようにアプリケーションに届くのかを理解してほしいのです。
MetaTrader 5から通知されるのはオブジェクトの名前だけであり、具体的にどのプロパティが変更されたのかまでは通知されません。 そのため、変更を反映させたいプロパティを自分で特定し、必要なものを取得する必要があります。 多くのオブジェクトには共通するプロパティがありますが、特定の種類のオブジェクトにしか存在しないプロパティもあります。 そのため、あらゆる種類のオブジェクトに対応できる完全に汎用的な仕組みを実装するのは、非常に難しいのです。
こうした点を踏まえれば、コード02を修正して、 実行中は一部のプロパティの変更を保持できるようにすることも可能です。 ただし、アプリケーションをチャートから削除すると、ユーザーが変更したプロパティの値は失われ、コード内で定義された設定に戻ります。 この問題を解決する方法はいくつかありますが、 その詳しい説明はまた別の機会にしましょう。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_Prefix "Demo" 005. //+------------------------------------------------------------------+ 006. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 007. //+------------------------------------------------------------------+ 008. struct st_Obj 009. { 010. //+----------------+ 011. private: 012. string szName, 013. font; 014. bool Selectable; 015. int x, 016. y, 017. w, 018. fontSize; 019. color cor; 020. //+----------------+ 021. public: 022. //+----------------+ 023. void SetDefault(const string arg) 024. { 025. szName = arg; 026. font = "Lucida Console"; 027. Selectable = true; 028. x = 50; 029. y = 50; 030. w = 150; 031. cor = clrMediumBlue; 032. fontSize = 20; 033. } 034. //+----------------+ 035. void Create(void) 036. { 037. ObjectCreate(0, szName, OBJ_LABEL, 0, 0, 0); 038. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable); 039. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x); 040. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y); 041. ObjectSetInteger(0, szName, OBJPROP_XSIZE, w); 042. ObjectSetInteger(0, szName, OBJPROP_COLOR, cor); 043. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize); 044. ObjectSetString(0, szName, OBJPROP_FONT, font); 045. } 046. //+----------------+ 047. string GetName(void) 048. { 049. return szName; 050. } 051. //+----------------+ 052. void Update(void) 053. { 054. font = ObjectGetString(0, szName, OBJPROP_FONT); 055. Selectable = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE); 056. x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 057. y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 058. w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 059. fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 060. cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 061. } 062. //+----------------+ 063. }gl_Obj; 064. //+------------------------------------------------------------------+ 065. int OnInit() 066. { 067. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 068. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 069. 070. gl_Obj.SetDefault(macro_NameObject); 071. gl_Obj.Create(); 072. 073. return INIT_SUCCEEDED; 074. }; 075. //+------------------------------------------------------------------+ 076. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 077. { 078. return rates_total; 079. }; 080. //+------------------------------------------------------------------+ 081. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 082. { 083. switch (id) 084. { 085. case CHARTEVENT_MOUSE_MOVE: 086. ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 087. break; 088. case CHARTEVENT_OBJECT_DELETE: 089. if (sparam == gl_Obj.GetName()) gl_Obj.Create(); 090. break; 091. case CHARTEVENT_OBJECT_CHANGE: 092. if (sparam == gl_Obj.GetName()) gl_Obj.Update(); 093. break; 094. } 095. ChartRedraw(); 096. }; 097. //+------------------------------------------------------------------+ 098. void OnDeinit(const int reason) 099. { 100. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 101. ObjectsDeleteAll(0, def_Prefix); 102. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 103. ChartRedraw(); 104. }; 105. //+------------------------------------------------------------------+
コード03
コード03では、どのような処理を実装すればよいのかを示しています。 その実行結果は、以下のアニメーションで確認できます。

アニメーション05
このアニメーションで注目してほしいのは、オブジェクトが、数分前にユーザーが設定したプロパティを引き継いで再作成される点です。 このオブジェクトは重要なものとして扱われているため、チャートから削除しても、すぐに再作成されます。 ただし、以前の動作とは異なり、今回はアプリケーション内で定義されたプロパティではなく、ユーザーが設定し、92行目の処理によって保存されたプロパティに基づいて再作成されます。
ここで、今回はイベントのフィルタリングをおこなっている点にも注目してください。 これは、実装している仕組みの性質上、非常に重要です。 次のような状況を考えてみましょう。チャート上に2つのオブジェクトがあり、そのうちの1つのプロパティを変更するとします。 ここまでは何も問題ありません。 しかし、アプリケーションが管理するオブジェクトを削除すると、そのオブジェクトは直ちに再作成されます。 ここで、92行目のフィルタリングを行っていなかったらどうなるでしょうか。アプリケーションが管理しているものとは別のオブジェクトのプロパティを変更した場合、その変更内容が、アプリケーションによって再作成されるオブジェクトにも適用されてしまいます。その結果、チャート上のオブジェクトを使って行っているさまざまな処理に、深刻な混乱が生じる可能性があります。 だからこそ、92行目のフィルタリングが重要なのです。
92行目のフィルタリングを無効にして、同じコード03を実行してみてください。どのような結果になるかを観察してみてください。 この方法が役立つ場面もあるかもしれません。 実際に何が起こるのかを理解し、観察しておけば、いつかその経験が役立つはずです。
学びは、コードを正しく書けたときだけに得られるものではありません。
コードが期待どおりに動作しないときにも、学びは得られます。
この場合、解決策そのものを理解することと同じくらい、その解決策がどのように導き出されたのかを理解することが重要です。
さて、これで、オブジェクトに関連して発生する最後の種類のイベントを検討するための準備が整いました。 今回は、特定のオブジェクトと、それに直接関係するイベントを扱います。そのため、私自身、とても興味深く、学ぶところも多いと考えている小さな実験をします。 MetaTrader 5では標準機能として用意されていない操作を実現してみましょう。 実は、そうした操作も、独自のアプリケーションを作成することで実現できます。 今回の実装の特徴を考えると、非常に興味深い内容になるはずです。
それでは、内容を明確に区切るため、新しいセクションに進みましょう。
CHARTEVENT_OBJECT_ENDEDITイベント
私が興味深いと感じていることの1つに、多くのユーザーが、MetaTrader 5を工夫して操作することで、さまざまな処理を実現できるという事実を十分に理解していない点があります。 なかには、実現不可能だとか、現実的ではないと頑なに主張する人もいます。
さて、今回取り上げる内容には、少し風変わりな側面があります。 一方で、通常の方法でそれを実現しようとすると、あまり合理的とはいえない部分もあります。 しかし、ここで何より大切なのは、焦らずに取り組むことです。 一般的に考えられていることとは異なり、MQL5は単にMetaTrader 5向けのアプリケーションを開発するためだけの言語ではありません。 もちろん、そうした用途にも使われます。 しかし、本質的には、MetaTrader 5をどのように動作させるか、あるいは自分が望むように動作させるにはどうすればよいかを、 一定のルールの範囲内で制御するための言語でもあります。 この点をあらかじめ理解しておかないと、このセクションでこれからおこなうことを理解するのは難しいでしょう。
大多数のユーザー、特に初心者にとって、ここで紹介する内容は、まるで別世界の出来事のように感じられるかもしれません。 人工知能や宇宙人が作ったコードなのではないか、と思う人さえいるでしょう。 しかし、もちろん、そのようなことはありません。 これから紹介する仕組みは、わずかな知識でも実現できます。 必要なのは、豊かな想像力と創造力、そして何よりも、物事を注意深く観察することです。
すでにお気づきかどうかはわかりませんが、OBJ_LABELオブジェクトはOBJ_EDITオブジェクトによく似ています。 大まかにいえば、この2つのオブジェクトの主な違いは、OBJ_EDITではチャート上で直接テキストを編集できるのに対し、OBJ_LABELではそれができないという点です。少なくとも、通常の使い方ではそうなっています。 しかし、本当にそれだけなのでしょうか。
実際には、OBJ_LABELとOBJ_EDITはよく似ているものの、まったく同じ特性を備えているわけではありません。もちろん、その逆も同様です。 ただし、これはあくまでもユーザーの視点から見た場合の話です。 プログラミングの観点から見ると、事情は少し異なります。 適切な方法を考えれば、この2種類のオブジェクトを基本的に同じように扱い、互いに連携させて動作させることも可能なのです。
今回実現したいのは、OBJ_LABELオブジェクトのプロパティウィンドウを開いてテキストを入力することなく、チャート上で直接テキストを編集できるようにする仕組みです。 そんなことは不可能だと思いますか。 それでは、実際にどのように実現できるのかを見ていきましょう。 ここでの目的は、あくまでも学習です。そのため、コードをどのように実装するかという細部にこだわりすぎないでください。 それよりも、これから紹介する仕組みが、なぜ動作するのかを理解することに集中してください。 それが最も重要なことです。
まず、ソースコードを用意する必要があります。 最初からすべてを作り直し、各処理の説明に時間をかけることは避けたいので、前のセクションで取り上げたコードをベースに、変更を加えたものを作成します。 コードを以下に示します。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_Prefix "Demo" 005. //+------------------------------------------------------------------+ 006. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 007. //+------------------------------------------------------------------+ 008. struct st_Obj 009. { 010. //+----------------+ 011. private: 012. string szName, 013. font; 014. bool Selectable; 015. int x, 016. y, 017. w, 018. h, 019. fontSize; 020. color cor, 021. backColor; 022. ENUM_OBJECT actual; 023. //+----------------+ 024. public: 025. //+----------------+ 026. void SetDefault(const string arg) 027. { 028. szName = arg; 029. font = "Lucida Console"; 030. Selectable = true; 031. x = 50; 032. y = 50; 033. w = 150; 034. h = 27; 035. cor = clrMediumBlue; 036. backColor = clrWhite; 037. fontSize = 20; 038. } 039. //+----------------+ 040. void Create(const ENUM_OBJECT type) 041. { 042. actual = type; 043. Recreates(); 044. } 045. //+----------------+ 046. void Recreates(void) 047. { 048. ObjectCreate(0, szName, actual, 0, 0, 0); 049. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false)); 050. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x); 051. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y); 052. ObjectSetInteger(0, szName, OBJPROP_XSIZE, w); 053. ObjectSetInteger(0, szName, OBJPROP_YSIZE, h); 054. ObjectSetInteger(0, szName, OBJPROP_COLOR, cor); 055. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize); 056. ObjectSetString(0, szName, OBJPROP_FONT, font); 057. if (actual == OBJ_EDIT) 058. { 059. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor); 060. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 061. } 062. } 063. //+----------------+ 064. string GetName(void) 065. { 066. return szName; 067. } 068. //+----------------+ 069. void Update(void) 070. { 071. font = ObjectGetString(0, szName, OBJPROP_FONT); 072. Selectable = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE); 073. x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 074. y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 075. w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 076. h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 077. fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 078. cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 079. backColor = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR)); 080. } 081. //+----------------+ 082. }gl_Obj; 083. //+------------------------------------------------------------------+ 084. int OnInit() 085. { 086. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 087. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 088. 089. gl_Obj.SetDefault(macro_NameObject); 090. gl_Obj.Create(OBJ_LABEL); 091. 092. return INIT_SUCCEEDED; 093. }; 094. //+------------------------------------------------------------------+ 095. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 096. { 097. return rates_total; 098. }; 099. //+------------------------------------------------------------------+ 100. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 101. { 102. switch (id) 103. { 104. case CHARTEVENT_MOUSE_MOVE : 105. ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 106. break; 107. case CHARTEVENT_OBJECT_DELETE : 108. if (sparam == gl_Obj.GetName())gl_Obj.Recreates(); 109. break; 110. case CHARTEVENT_OBJECT_CHANGE : 111. if (sparam == gl_Obj.GetName()) gl_Obj.Update(); 112. break; 113. } 114. ChartRedraw(); 115. }; 116. //+------------------------------------------------------------------+ 117. void OnDeinit(const int reason) 118. { 119. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 120. ObjectsDeleteAll(0, def_Prefix); 121. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 122. ChartRedraw(); 123. }; 124. //+------------------------------------------------------------------+
コード04
ここでは、いくつかの変更を加えています。 まず、次の点に注目してください。コード04は、基本的にコード03と同じ仕組みで動作します。 ただし、40行目でオブジェクトを作成するプロシージャを呼び出す際には、作成するオブジェクトの種類を指定する必要があります。 今回は、OBJ_EDITとOBJ_LABELを使用します。 こうした変更を加えることで、新たな機能を実現するための準備が整います。その具体的な内容は、もうすぐ明らかになります。つまり、OBJ_LABELオブジェクトを操作し、チャート上で直接テキストを編集できるようにするのです。
次に、コード04のもう1つの重要なポイントを見てみましょう。 実際にオブジェクトを作成する49行目では、OBJPROP_SELECTABLEプロパティを設定しています。 このプロパティは非常に重要であり、目的に応じて正しく設定する必要があります。 OBJ_LABELオブジェクトの場合、これまでと同様に、後からオブジェクトを選択できるようにするには、このプロパティをtrueに設定します。 一方、OBJ_EDITオブジェクトで同じプロパティをtrueに設定すると、チャート上でテキストを直接編集できなくなります。
前述したように、オブジェクトにはそれぞれ固有の特性があります。そのため、実行したい操作に応じて、各プロパティを適切に設定しなければなりません。 簡単にまとめると、OBJ_EDITオブジェクトでは、OBJPROP_SELECTABLEをtrueに設定すると、テキストを編集できなくなる代わりに、オブジェクトを画面上で移動できるようになります。 反対に、このプロパティをfalseに設定すると、テキストを編集できるようになりますが、オブジェクトを画面上で移動することはできなくなります。
続いて、90行目に注目してください。ここでは、作成するオブジェクトの種類としてOBJ_LABELを指定しています。 しかし、108行目ではCreateを呼び出さず、代わりにオブジェクトを再作成する関数を呼び出しています。 これは、再作成するオブジェクトの型が、すでにわかっているためです。 一方、アプリケーション側では、削除されたオブジェクトの種類を特定できません。この時点でオブジェクトの種類を取得しようとしても、ほとんどの場合、正しい型を取得できないからです。 なぜなら、MetaTrader 5によって、対象のオブジェクトはすでに削除されているためです。
CHARTEVENT_OBJECT_DELETEイベントが発生した時点では、MetaTrader 5はオブジェクトを削除する前に許可を求めることはありません。 その時点で、MetaTrader 5はそのオブジェクトをすでに削除しています。 ここで例外となるのが、型番号が0に対応するOBJ_VLINEオブジェクトを使用する場合です。それ以外の場合、削除後にオブジェクトの型を問い合わせても、正しい型を取得することはできません。 そのオブジェクトがもはや存在しないため、MetaTrader 5はそれを教えることができないのです。
ここまでの内容は、比較的シンプルで理解しやすいものだと思います。 さて、ここからが最も興味深い部分です。コード04をベースに修正を加え、複数のイベントを連携させることで、目的とする動作を実現していきます。
すべてを一度に説明すると複雑になりそうなので、ここでは2つの簡単なステップに分けて進めます。 まず、最初の例を以下のコードで確認しましょう。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_Prefix "Demo" 005. //+------------------------------------------------------------------+ 006. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 007. //+------------------------------------------------------------------+ 008. struct st_Obj 009. { 010. //+----------------+ 011. private: 012. string szName, 013. font, 014. text; 015. bool Selectable; 016. int x, 017. y, 018. w, 019. h, 020. fontSize; 021. color cor, 022. backColor; 023. ENUM_OBJECT actual; 024. //+----------------+ 025. public: 026. //+----------------+ 027. void SetDefault(const string arg) 028. { 029. szName = arg; 030. font = "Lucida Console"; 031. Selectable = true; 032. x = 50; 033. y = 50; 034. w = 250; 035. h = 27; 036. cor = clrMediumBlue; 037. backColor = clrWhite; 038. fontSize = 20; 039. } 040. //+----------------+ 041. void Create(const ENUM_OBJECT type) 042. { 043. actual = type; 044. text = EnumToString(type); 045. Recreates(); 046. } 047. //+----------------+ 048. void Recreates(void) 049. { 050. ObjectCreate(0, szName, actual, 0, 0, 0); 051. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false)); 052. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x); 053. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y); 054. ObjectSetInteger(0, szName, OBJPROP_XSIZE, w); 055. ObjectSetInteger(0, szName, OBJPROP_YSIZE, h); 056. ObjectSetInteger(0, szName, OBJPROP_COLOR, cor); 057. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize); 058. ObjectSetString(0, szName, OBJPROP_FONT, font); 059. ObjectSetString(0, szName, OBJPROP_TEXT, text); 060. if (actual == OBJ_EDIT) 061. { 062. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor); 063. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 064. } 065. } 066. //+----------------+ 067. string GetName(void) 068. { 069. return szName; 070. } 071. //+----------------+ 072. void Update(void) 073. { 074. font = ObjectGetString(0, szName, OBJPROP_FONT); 075. Selectable = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE); 076. x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 077. y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 078. w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 079. h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 080. fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 081. cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 082. backColor = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR)); 083. } 084. //+----------------+ 085. }gl_Obj; 086. //+------------------------------------------------------------------+ 087. int OnInit() 088. { 089. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 090. 091. gl_Obj.SetDefault(macro_NameObject); 092. gl_Obj.Create(OBJ_LABEL); 093. 094. return INIT_SUCCEEDED; 095. }; 096. //+------------------------------------------------------------------+ 097. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 098. { 099. return rates_total; 100. }; 101. //+------------------------------------------------------------------+ 102. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 103. { 104. switch (id) 105. { 106. case CHARTEVENT_OBJECT_CLICK : 107. if (sparam == gl_Obj.GetName()) 108. { 109. string sz0 = ObjectGetString(0, sparam, OBJPROP_TEXT); 110. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_EDIT) 111. break; 112. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 113. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true); 114. ObjectDelete(0, sparam); 115. gl_Obj.Create(OBJ_EDIT); 116. ObjectSetString(0, sparam, OBJPROP_TEXT, sz0); 117. } 118. break; 119. case CHARTEVENT_OBJECT_CREATE : 120. if (sparam == gl_Obj.GetName()) 121. { 122. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false); 123. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 124. } 125. break; 126. case CHARTEVENT_OBJECT_DELETE : 127. if (sparam == gl_Obj.GetName())gl_Obj.Recreates(); 128. break; 129. case CHARTEVENT_OBJECT_CHANGE : 130. if (sparam == gl_Obj.GetName()) gl_Obj.Update(); 131. break; 132. } 133. ChartRedraw(); 134. }; 135. //+------------------------------------------------------------------+ 136. void OnDeinit(const int reason) 137. { 138. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 139. ObjectsDeleteAll(0, def_Prefix); 140. ChartRedraw(); 141. }; 142. //+------------------------------------------------------------------+
コード05
コード05では、非常に奇妙なことが起こります。 これは、次のアニメーションで確認できます。

アニメーション06
アニメーション06をよくご覧ください。 92行目では、OBJ_LABELオブジェクトの作成を要求している点に注目してください。 このため、44行目では、使用しているオブジェクトの型を示すテキストを設定しています。 ここまでは、いつもどおりに動作しています。 しかし、このOBJ_LABELオブジェクトをクリックすると、OBJ_EDITオブジェクトに切り替わります。 これは、106行目で捕捉したイベントを処理するコードによっておこなわれます。 ここでは、最初に作成したオブジェクトに注目してください。
ただし、109行目の処理によって、OBJ_LABELオブジェクトに含まれるテキストが、116行目でOBJ_EDITオブジェクトに転送されます。 これは重要です。この転送をおこなわないと、OBJ_EDITオブジェクトには別のテキストが設定されてしまいます。 それは私たちの望むことではありません。 私たちが本当に実現したいのは、チャート上でOBJ_LABELオブジェクトのテキストを直接変更できるようにすることです。
ここで、次の点に注意してください。CHARTEVENT_OBJECT_CLICKイベントの処理では、OBJ_LABELオブジェクトを削除し、OBJ_EDITオブジェクトを作成します。 しかし、89行目でCHARTEVENT_OBJECT_DELETEイベントの通知を有効にしているため、これを無効にする必要があります。 これは112行目でおこないます。 その直後に、オブジェクトの作成に関する通知を受け取りたいことをMetaTrader 5に伝えるため、別のイベントを有効にします。 これにより、119行目で、アプリケーションによるオブジェクト作成イベントが捕捉されます。 このハンドラでは、作成通知を無効にし、削除通知を再び有効にします。
これにより、126行目のハンドラが再び機能するようになりますが、完全ではありません。 OBJ_EDITオブジェクトを削除すると、そのオブジェクトは再作成されますが、テキストは、削除前に存在していた元のテキストとは一致しなくなるためです。
この小さな修正は、この些細な問題を解決する方法を見つけるための練習問題として残しておきます。 解決するのはとても簡単です。 ただし、OBJ_EDITオブジェクトが作成された時点で存在していたテキストを復元するには、いくつかのデータを処理する必要があることを覚えておいてください。 一方、OBJ_EDITの作成から削除までの間に存在していたテキストを復元するには、はるかに多くの作業が必要になります。 また、テキストをメモリ内で最新の状態に保ち、再現できるようにするため、キーボードイベントとマウスイベントも処理する必要があります。
個人的には、この後半部分はまったく不要で、むしろ面倒だと考えています。 というのも、ユーザーがOBJ_LABELオブジェクトに保存する前にテキストを削除してしまったのであれば、OBJ_EDITオブジェクトにどのようなテキストがあったかは、ユーザー自身に覚えておいてもらえばよいからです(笑)。
さて、いよいよ最後の部分です。ここでは、このセクションで扱ったイベントを実際に使用します。 そのために、コードに最後の変更を加えます。 以下をご覧ください。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_Prefix "Demo" 005. //+------------------------------------------------------------------+ 006. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 007. //+------------------------------------------------------------------+ 008. struct st_Obj 009. { 010. //+----------------+ 011. private: 012. string szName, 013. font, 014. text; 015. bool Selectable; 016. int x, 017. y, 018. w, 019. h, 020. fontSize; 021. color cor, 022. backColor; 023. ENUM_OBJECT actual; 024. //+----------------+ 025. public: 026. //+----------------+ 027. void SetDefault(const string arg) 028. { 029. szName = arg; 030. font = "Lucida Console"; 031. Selectable = true; 032. x = 50; 033. y = 50; 034. w = 250; 035. h = 27; 036. cor = clrMediumBlue; 037. backColor = clrWhite; 038. fontSize = 20; 039. } 040. //+----------------+ 041. void Create(const ENUM_OBJECT type) 042. { 043. actual = type; 044. text = EnumToString(type); 045. Recreates(); 046. } 047. //+----------------+ 048. void Recreates(void) 049. { 050. ObjectCreate(0, szName, actual, 0, 0, 0); 051. ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false)); 052. ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x); 053. ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y); 054. ObjectSetInteger(0, szName, OBJPROP_XSIZE, w); 055. ObjectSetInteger(0, szName, OBJPROP_YSIZE, h); 056. ObjectSetInteger(0, szName, OBJPROP_COLOR, cor); 057. ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize); 058. ObjectSetString(0, szName, OBJPROP_FONT, font); 059. ObjectSetString(0, szName, OBJPROP_TEXT, text); 060. if (actual == OBJ_EDIT) 061. { 062. ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor); 063. ObjectSetInteger(0, szName, OBJPROP_READONLY, false); 064. } 065. } 066. //+----------------+ 067. string GetName(void) 068. { 069. return szName; 070. } 071. //+----------------+ 072. void Update(void) 073. { 074. font = ObjectGetString(0, szName, OBJPROP_FONT); 075. Selectable = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE); 076. x = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE); 077. y = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE); 078. w = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE); 079. h = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE); 080. fontSize = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE); 081. cor = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR); 082. backColor = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR)); 083. text = ObjectGetString(0, szName, OBJPROP_TEXT); 084. } 085. //+----------------+ 086. }gl_Obj; 087. //+------------------------------------------------------------------+ 088. int OnInit() 089. { 090. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 091. 092. gl_Obj.SetDefault(macro_NameObject); 093. gl_Obj.Create(OBJ_LABEL); 094. 095. return INIT_SUCCEEDED; 096. }; 097. //+------------------------------------------------------------------+ 098. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 099. { 100. return rates_total; 101. }; 102. //+------------------------------------------------------------------+ 103. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 104. { 105. //+----------------+ 106. #define macro_SWAP(A) { \ 107. string sz0 = ObjectGetString(0, sparam, OBJPROP_TEXT); \ 108. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); \ 109. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true); \ 110. ObjectDelete(0, sparam); \ 111. gl_Obj.Create(A); \ 112. ObjectSetString(0, sparam, OBJPROP_TEXT, sz0); \ 113. gl_Obj.Update(); \ 114. } 115. //+----------------+ 116. switch (id) 117. { 118. case CHARTEVENT_OBJECT_CLICK : 119. if (sparam == gl_Obj.GetName()) 120. if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) != OBJ_EDIT) 121. macro_SWAP(OBJ_EDIT); 122. break; 123. case CHARTEVENT_OBJECT_CREATE : 124. if (sparam == gl_Obj.GetName()) 125. { 126. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false); 127. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true); 128. } 129. break; 130. case CHARTEVENT_OBJECT_DELETE : 131. if (sparam == gl_Obj.GetName())gl_Obj.Recreates(); 132. break; 133. case CHARTEVENT_OBJECT_CHANGE : 134. if (sparam == gl_Obj.GetName()) gl_Obj.Update(); 135. break; 136. case CHARTEVENT_OBJECT_ENDEDIT : 137. if (sparam == gl_Obj.GetName()) macro_SWAP(OBJ_LABEL) 138. break; 139. } 140. ChartRedraw(); 141. //+----------------+ 142. #undef macro_SWAP 143. //+----------------+ 144. }; 145. //+------------------------------------------------------------------+ 146. void OnDeinit(const int reason) 147. { 148. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false); 149. ObjectsDeleteAll(0, def_Prefix); 150. ChartRedraw(); 151. }; 152. //+------------------------------------------------------------------+
コード06
コード06には、この小さな問題を解決する修正を含め、すでにいくつかの機能が実装されています。 しかし、問題の解決方法がわからないように、意図的にコードの一部を変更しました。 これは、経験の浅いユーザーが、解決方法をすぐには見抜けないようにするためです。
コード06は他のコードと同様にシンプルであり、学習して練習してきた人なら誰でも難なく解決できるはずなので、詳しく説明する必要はないと思います。 ここでは、次の点だけ述べておきます。OBJ_EDITオブジェクトの編集が終了すると、通常はENTERキーが押されたときに、MetaTrader 5はCHARTEVENT_OBJECT_ENDEDITイベントを生成します。 このイベントは136行目で捕捉され、その結果、それまでOBJ_EDIT型だったオブジェクトは再びOBJ_LABELオブジェクトになります。
考えられる結果の一例を以下に示します。

アニメーション07
アニメーション07に惑わされないでください。 ここで実際に何が起きているのか、よく調べて理解するようにしてください。 アニメーション07で目にするものは、巨大な氷山の一角にすぎません。 ここでは、この問題のごく基本的な側面だけを示したからです。 ここで紹介した内容よりも、さらに多くのことができます。
まとめ
コード全体を繰り返し掲載し、その仕組みについては簡単な説明にとどめているため、この記事を少々退屈に感じた方も多いかもしれません。 しかし個人的には、本連載の他の記事ですでに説明した内容を繰り返す必要はないと考えています。 もちろん、多くの人を驚かせ、懐疑的にさせるかもしれないこともおこないました。 それは当然のことであり、学習過程の一部です。
この記事を終え、次の記事に進む前に、読者の皆さんにもう一つアドバイスをしたいと思います。 最後のセクションでは、さまざまな興味深いことができるとわかりました。 よく見ると、少し長いテキストを配置できるように、OBJ_LABELオブジェクトのサイズを変更したことに気づくでしょう。 ここでの助言としては、OBJ_EDITオブジェクト内でテキストがどのように作成されるのかを正確に理解するよう努めることです。 これは、チャート上にテキストを表示する際に、OBJ_LABELオブジェクトのサイズを変更できるようにするために必要です。
また、チャート上でOBJ_LABELオブジェクトを移動させる方法も考えてみてください。 このオブジェクトは、作成された位置に固定されたままだからです。 これは、オブジェクトが選択された際に、CHARTEVENT_OBJECT_CLICKイベントによってOBJ_EDITオブジェクトに変換されるために起こります。 そのため、位置を変更できません。
さらに、他にもできることがありますが、おそらく次の記事で取り上げることになるでしょう。 もし、それがあまり興味深くなく、記事にもあまり貢献しないと判断した場合は、その内容だけを説明します。 いずれにせよ、皆さんはこの記事を読み始めたときよりも、MQL5とMetaTrader 5について少し詳しくなったはずです。
それでは、添付のコードを使って学習と練習に取り組んでください。次の記事でお会いしましょう。
| MQ5ファイル | 説明 |
|---|---|
| コード01 | オブジェクトイベントのデモ |
| コード02 | オブジェクトイベントのデモ |
| コード03 | オブジェクトイベントのデモ |
| コード04 | オブジェクトイベントのデモ |
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/16050
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。