初級から中級まで:オブジェクトイベント(I)
はじめに
前回の「初級から中級まで:オブジェクト(IV)」では、MetaTrader 5で利用可能なオブジェクトを変更し、別のオブジェクトを作成する方法を紹介しました。これは、非常に興味深い目的のためにおこなったものであり、それ自体にも面白い要素があります。その記事では、読者の皆さんに小さな課題も出しましたが、その解答については公開しません。新しいアイデアを学び、それを実装していく中で、必ず発生する問題を自分自身で解決できるようになるために、記事の内容を実際に練習し、研究してほしいからです。
この課題自体は非常に簡単に解決できます。また、その問題が学習目的で使用しているコードの利用を妨げるものでもありません。しかし、自分自身で問題を解決し、さまざまな状況を調査・分析しながら、最適な解決方法を見つけることができれば、学んでいる内容に対してより大きな自信を持てるようになりますし、自分自身の成長をより強く実感できるでしょう。そのため、この記事では、グラフィカルオブジェクトの操作に関連する、別のテーマについて説明します。ある意味ではこれまで扱ってきた内容と関連していますが、少し異なる側面を持っています。
おそらく皆さんは、私たちがこのテーマにかなり多くの注意を払っていることに気付いているでしょう。人によっては「そこまで必要なのか」と感じるほどかもしれません。しかし、MQL5が持つ可能性をさらに深く理解するためには、いくつかの基本的なことを理解する必要があります。これらすべては、より大きな目的のために必要です。つまり、特定の目的に合わせて設計された、より複雑なアプリケーションを作成することです。少なくともMetaTrader 5という環境においては、他のどのプログラムにも存在しないようなアプリケーションを作成することが目標になります。
それでは、新しいテーマへ進みましょう。これにより、この記事の中心となる内容について説明を始めることができます。
オブジェクトイベント
ここまでの記事では、ユーザーがMetaTrader 5を操作した際に発生するイベントについて説明し、それらをチャートレベルで処理する方法を紹介してきました。たとえば、キーを押した場合や、マウスを使用して何らかの操作をした場合です。チャート上へオブジェクトを配置することも、そのような操作の1つです。この操作は主にマウスによっておこなわれますが、それに関連した別のイベントも発生します。
これらのイベントによって、MetaTrader 5はさらに別のイベントを発生させます。今回扱うのは、まさにオブジェクトに関連したイベントです。そのため、これらのイベントは通常とは異なる方法で処理されます。少なくとも、それが期待される動作です。MetaTrader 5は、チャート上のオブジェクトに対しておこなわれた操作を、プログラマー、そして最終的にはアプリケーション自身が処理できるようにするため、専用のイベントを生成します。
これらのイベントは、マウスクリック、移動、オブジェクトの作成、削除、さらにはオブジェクトのプロパティ変更によって発生します。ただし、すべてのイベントが発生するわけではありません。一部のイベントは有効化する必要があります。理由は非常に単純で、MetaTrader 5では初期状態で無効になっているということです。これはマウスイベントの場合とほぼ同じ仕組みです。アプリケーションによっては、そのようなイベント通知を必要としない場合があります。そのため、MetaTrader 5はデフォルトではそれらを無視します。その結果、より高速な環境が実現され、パフォーマンス低下やプラットフォーム自体の安定性問題が発生する可能性も低くなります。
しかし、場合によっては、そのようなイベントが必要になることがあります。その場合、イベントをどのように処理するかを理解する必要があります。なぜなら、ユーザーがグラフィカルオブジェクトへ変更を加えたことによって、修正や調整が必要になる場合があるからです。
グラフィカルオブジェクトの変更を手動で監視するという方法は、読者の皆さんが望むものではないでしょう。CPUリソースを大量に消費するだけでなく、コードも不必要なほど複雑になります。しかし、MetaTrader 5がチャート上の変更情報を提供してくれるため、配置されたすべてのオブジェクトを個別に監視する作業を大幅に減らすことができます。これにより、ユーザーが情報表示を目的としたオブジェクトを、いつ、どこで、なぜ変更したのかを正確に把握できます。
この時点では、MetaTrader 5がオブジェクト変更を通知するために生成するイベントが、どれほど重要なのかまだ十分に理解できていないかもしれません。さらに、悪意のあるユーザーが特定のオブジェクトのプロパティを変更することで、アプリケーションの動作を妨げたり、不具合を引き起こしたりする可能性についても、まだ十分に認識していないかもしれません。だからこそ、オブジェクトイベントについて説明する必要があります。これは、アプリケーションの問題を未然に防ぐため、またはアプリケーションの動作中に発生する障害の原因を理解するために、非常に役立つテーマです。
アプリケーションを更新することで、障害発生のリスクを減らすこともできます。しかし同時に、本当に重要な問題を見落としてしまう可能性もあります。
MetaTrader 5がオブジェクト操作によって生成するイベントは、アプリケーションの種類によって異なる意味として解釈される場合があります。そのため、ここでは固定された実装モデルやプロトコルを作成することは避けます。読者の皆さんには、これから使用する概念そのものを理解していただきたいと思います。また、すべての操作を監視する必要がある場合もあれば、オブジェクトに関する特定の操作を完全に無視できる場合もあることを理解してください。そのオブジェクトが、アプリケーション自身によってチャートへ配置されたものなのか、それともユーザーが手動で追加したものなのかは関係ありません。すべてには適切なタイミングがあります。したがって、まずコードそのものではなく、概念を理解することから始めてください。コード自体は大きく変わり得るからです。
さて、始める前に1つ明確にしておく必要があります。一般的に、MetaTrader 5が標準で提供しているオブジェクト処理方法は、より特殊な目的を持つアプリケーションを作成する場合、必ずしも最適とは限りません。特定の目標を達成するために独自の機能を実装する場合、MetaTrader 5が標準で使用しているルールに加えて、追加のルールを設定する必要があります。
そして、アプリケーション側で設定したルールを正しく機能させるために、MetaTrader 5が提供している組み込みメカニズムを利用します。この仕組みによって、特定のオブジェクトに何が起こったのかを知ることができます。そのため、ユーザーがどのオブジェクトに対して何をするかを常に監視する必要がなくなります。ユーザーが新しいオブジェクトを追加した場合、変更した場合、あるいは削除した場合でも、MetaTrader 5が通知してくれるため、必要な処理を実行できます。
この考え方を理解することは非常に重要です。場合によっては、コードそのものを理解することよりも重要です。もちろん、すべてのイベントを有効化する必要があるわけではありません。必要なイベントだけを有効化し、不要なものは無効化します。どのタイミングで有効化し、どのタイミングで無効化するべきかは、実践を通じて学ぶ必要があります。しかし、まずはどこからか始めなければなりません。そこで、できるだけ簡単かつ段階的に進めていきます。まずは、以下に示すコードから始めましょう。
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. #include <Tutorial\File 01.mqh> 009. //+------------------------------------------------------------------+ 010. input double user01 = 1.5; //Stop-Target Relationship 011. input bool user02 = true; //Extend lines to the right 012. //+------------------------------------------------------------------+ 013. st_Cross gl_Cross; 014. //+------------------------------------------------------------------+ 015. int OnInit() 016. { 017. gl_Cross.Init(); 018. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 019. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 020. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 021. 022. return INIT_SUCCEEDED; 023. }; 024. //+------------------------------------------------------------------+ 025. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 026. { 027. return rates_total; 028. }; 029. //+------------------------------------------------------------------+ 030. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 031. { 032. #define macro_CLEAN_EVENT { \ 033. isPaint = ""; \ 034. gl_Cross.Hide(); \ 035. bMouseL = false; \ 036. ChartSetInteger(0, CHART_MOUSE_SCROLL, true); \ 037. } 038. 039. st_TimePrice tp; 040. static string isPaint = ""; 041. static bool bMouseL = false; 042. 043. switch (id) 044. { 045. case CHARTEVENT_KEYDOWN: 046. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) macro_CLEAN_EVENT; 047. break; 048. case CHARTEVENT_MOUSE_MOVE: 049. tp = gl_Cross.Move((ushort)lparam, (ushort)dparam); 050. if (((uchar)sparam & MOUSE_LEFT) != 0) 051. { 052. if (isPaint != "") 053. { 054. bMouseL = true; 055. if (!ObjectGetInteger(0, isPaint, OBJPROP_TIME)) ObjectMove(0, isPaint, 0, tp.Time, tp.Price); 056. ObjectMove(0, isPaint, 1, tp.Time, tp.Price); 057. } 058. }else if (bMouseL) macro_CLEAN_EVENT; 059. if ((((uchar)sparam & MOUSE_MIDDLE) != 0) && (isPaint == "")) 060. { 061. ObjectCreate(0, isPaint = macro_NameObject, OBJ_FIBO, 0, 0, 0); 062. ChartSetInteger(0, CHART_MOUSE_SCROLL, false); 063. Modifier_OBJ_FIBO(isPaint); 064. gl_Cross.Show(); 065. } 066. break; 067. case CHARTEVENT_MOUSE_WHEEL: 068. break; 069. case CHARTEVENT_OBJECT_CLICK: 070. Comment(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d Y: %03d Object: %s", (ushort)lparam, (ushort)dparam, sparam)); 071. break; 072. case CHARTEVENT_OBJECT_DRAG : 073. Comment(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam)); 074. break; 075. } 076. ChartRedraw(); 077. 078. #undef macro_CLEAN_EVENT 079. }; 080. //+------------------------------------------------------------------+ 081. void OnDeinit(const int reason) 082. { 083. Comment(""); 084. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 085. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 086. 087. gl_Cross.Hide(); 088. 089. if (reason == REASON_REMOVE) 090. ObjectsDeleteAll(0, def_Prefix); 091. }; 092. //+------------------------------------------------------------------+ 093. void Modifier_OBJ_FIBO(const string szNameObj) 094. { 095. #define macro_Mod_OBJ_FIBO(txt, pos, cor, width, style) { \ 096. ObjectSetDouble(0, szNameObj, OBJPROP_LEVELVALUE, levels, pos); \ 097. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELCOLOR, levels, cor); \ 098. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELWIDTH, levels, width); \ 099. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELSTYLE, levels, style); \ 100. ObjectSetString(0, szNameObj, OBJPROP_LEVELTEXT, levels, txt); \ 101. levels++; \ 102. } 103. 104. int levels = 0; 105. 106. ObjectSetInteger(0, szNameObj, OBJPROP_SELECTABLE, false); 107. ObjectSetInteger(0, szNameObj, OBJPROP_COLOR, clrNONE); 108. ObjectSetInteger(0, szNameObj, OBJPROP_RAY_RIGHT, user02); 109. 110. macro_Mod_OBJ_FIBO("Stop", 0, clrRed, 2, STYLE_SOLID); 111. macro_Mod_OBJ_FIBO("Enter", 1, clrBlue, 2, STYLE_DASH); 112. macro_Mod_OBJ_FIBO("Partial", 1 + (user01 / 2), clrYellowGreen, 1, STYLE_DASHDOTDOT); 113. macro_Mod_OBJ_FIBO("Take", 1 + user01, clrGreen, 2, STYLE_SOLID); 114. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELS, levels); 115. 116. #undef macro_Mod_OBJ_FIBO 117. } 118. //+------------------------------------------------------------------+
コード01
ここにあるのは、前回の記事で確認し、説明し、提供したコードです。ただし、実際には完全に同じというわけではありません。なぜなら、ここではソースコードに2つの新しい要素を追加しているからです。これは意図的におこなっています。すでに学んだ内容を土台として利用することで、非常に複雑なアプリケーションが突然完成するものではなく、少しずつ、段階を踏んで構築されていくことを理解していただきたいからです。もちろん、ここでは特定のアプリケーションを作成すること自体が目的ではありません。私たちの目的は、MQL5プログラミングに関連する要素を説明し、MetaTrader 5プラットフォームそのものを制御する方法を示すことです。
コード01をコンパイルし、チャートへ追加すると、観察する価値のある非常に興味深い動作を確認できます。このアプリケーションは、現在、私たちに有用な情報を提供してくれます。ここで覚えておいてください。このデータは、もともとチャート上の操作に応じてMetaTrader 5が生成したものです。以下のアニメーションでは、どのチャート上でこのアプリケーションを使用した場合でも、どのような動作になるかを確認できます。

アニメーション01
アニメーション01の左上部分に注目してください。そこには、チャート上に存在するオブジェクトでイベントが発生したことを知らせるメッセージが表示されています。特にCHARTEVENT_OBJECT_CLICKイベントの場合、単にオブジェクト名やマウス位置だけでなく、どの種類のイベントが発生したのかも通知されていることに注目してください。この種のイベントには幅広い活用の余地があります。実際、この2つを観察するだけでも多くの応用が考えられます。
しかし、今回は非常に単純で分かりやすい内容を扱っていますので、コード01で何が起きているのかを細かく説明する必要はないと考えています。落ち着いてコードを確認し、これまでの記事で学んだ知識を適用すれば、このアプリケーションで何が起きているのかを正確に理解できるでしょう。ただし、1つ覚えておいてください。このアプリケーションは、自分自身が作成していないオブジェクトであっても監視することができます。
これこそが、私が読者の皆さんに理解していただきたいポイントです。現在分析しているのは、MetaTrader 5の標準的な動作です。つまり、MetaTrader 5がオブジェクトとどのようにやり取りし、変更が発生した際にどのように通知するのかを理解することが目的です。
もちろん、コード01へさらに新しい要素を追加していくことも可能ですが、しません。最終的には、本来の目的が分かりにくくなってしまうからです。私たちの目的は、皆さんへ知識を伝え、私自身が経験してきたプログラミングの考え方を共有することです。つまり、プラットフォームに制限されるのではなく、プラットフォームを自分の目的のために動かせるようになることです。そこで、新しい種類のアプリケーションを作成してみましょう。もちろん、そのためにはMQL5を利用してMetaTrader 5を制御する方法を理解していることが前提になります。
そのためには、今回のテーマで扱っている内容の本質を理解する必要があります。私の考えでは、ここで説明している内容は、今後学ぶすべての基礎となるものです。この部分を理解しないまま先へ進むと、これからおこなう内容を理解することはかなり難しくなります
重複オブジェクトの防止
私が本当に気になるものの1つに、悪名高いCTRL+CとCTRL+Vがあります。しかし、MetaTrader 5では、一般的なアプリケーションと同じようには動作しません。なぜなら、オブジェクトを選択して、そのオブジェクトに対して利用可能な操作メニューを開いても、MetaTrader 5ではデフォルトでオブジェクトに対するCTRL+CとCTRL+Vが存在しないからです。多くの人にとっては完全に自然で当たり前に感じることが、別の人にとっては少し不思議で分かりにくく感じられるかもしれません。これは以下の図で確認できます。

図01
確かに、少し奇妙に感じるかもしれません。しかし、MetaTrader 5ではCTRL+CとCTRL+Vを別の方法で利用できます。そして正直に言えば、MetaTrader 5でグラフィカルオブジェクトを複製するためにCTRL+CとCTRL+Vを使う方法を知っているユーザーは、非常に少ないと思います。
この方法を理解するには、他のいくつかの内容について説明する必要があります。しかし、それをここでおこなうと、この記事は非常につまらないものになってしまいますし、私自身もあまり興味深い内容になるとは思いません。実際、多くのユーザーは現在のプラットフォームの状態に十分満足しています。それなら、なぜ変更する必要があるのでしょうか。そう思いませんか。
しかし、後ほど説明する内容のためには、MetaTrader 5の任意のオブジェクトでCTRL+CとCTRL+Vを使用する方法を紹介する価値があります。なぜなら、おそらく皆さんはこの方法をまだ知らないと思うからです。また、これは偶然この記事に出会い、学習を始めた方々への一種のボーナス情報にもなります。それでは、以下のアニメーションで、任意のオブジェクトに対してCTRL+CとCTRL+Vを実行する方法を確認しましょう。

アニメーション02
ご覧のとおり、これは非常にシンプルで簡単、そして少し楽しい操作です。しかし、多くの人は、チャート上の任意のオブジェクトをコピーするには、オブジェクトをドラッグするときにCTRLキーを押し続けるだけでよいことを知りません。この操作によって、オブジェクトのコピーが作成されます。ただし、実際には完全に同一のコピーではありません。この理由については、すぐ後で説明します。そして、完全なコピーではないことは幸いでもあります。なぜなら、もし完全な複製であれば、これから説明する別の可能な処理が難しくなってしまうからです。
さて、アニメーション02で示したように、この操作をおこなうと、チャート上にすでに存在していたオブジェクトの複製が作成されます。しかし、場合によっては、これはまったく望んでいない動作です。より正確に言えば、特定の種類のオブジェクトについては、チャート上に複製を作成したくない場合があります。なぜなら、そのようなオブジェクトには情報が含まれている可能性があり、それが複製されることで、オペレーターやユーザーを混乱させる可能性があるからです。
特に、それらのオブジェクトがアプリケーションから情報を受け取る場合、この問題はさらに重要になります。その場合、見た目が同じ2つのオブジェクトに、矛盾した情報が表示される可能性があります。これをより明確に理解していただくため、そして私が何を説明しているのかを正しく理解していただくために、小さなテストを実行してみましょう。以下に示すコードを使用して確認します。
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_NameObj; 09. //+------------------------------------------------------------------+ 10. int OnInit() 11. { 12. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 13. 14. ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0); 15. ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true); 16. ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50); 17. ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50); 18. ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150); 19. ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue); 20. ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20); 21. ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console"); 22. 23. return INIT_SUCCEEDED; 24. }; 25. //+------------------------------------------------------------------+ 26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 27. { 28. return rates_total; 29. }; 30. //+------------------------------------------------------------------+ 31. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 32. { 33. switch (id) 34. { 35. case CHARTEVENT_MOUSE_MOVE: 36. ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 37. break; 38. } 39. ChartRedraw(); 40. }; 41. //+------------------------------------------------------------------+ 42. void OnDeinit(const int reason) 43. { 44. ObjectsDeleteAll(0, def_Prefix); 45. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 46. ChartRedraw(); 47. }; 48. //+------------------------------------------------------------------+
コード02
コード02は、私がオブジェクトの複製について話す際に、私の意図を理解するための一例となります。これは非常に単純なものであり、これまで私の記事を読み続けてくださっている読者の皆さんであれば、内容を容易に理解できると思いますので、ここでは何をしているコードなのかについては説明しません。その代わりに、前の段落で説明した考え方を実際に適用した場合に、どのような結果が得られるのかだけを示します。その結果は、以下のアニメーション03で確認できます。

アニメーション03
ここで、イベントが処理される速度に関係する問題に直面します。今回はマウスイベントを扱っているため、処理速度は非常に高速です。そのため、最終的には何か奇妙なことが起きていることに気付き始めます。しかし、5分ごと、あるいは1時間ごとに発生するようなイベントについて考えてみてください。そのような場合、異常に気付くことは非常に難しくなります。これで、私がどこへ向かおうとしているのか、少し見えてきたのではないでしょうか。おそらく、チャート上でオブジェクトを複製する方法を知らなかった場合、アニメーション03で見られるような状況が発生することは、ほとんどありませんでした。
しかし現在、MetaTrader 5でオブジェクトの複製を作成することが、いかに簡単であるかを理解しました。そのため、このような状況は頻繁に発生する可能性があります。ある時点で、これは深刻な問題につながる可能性があります。特に、複製されたオブジェクトが目的のオブジェクトの前面に表示されている場合です。このようなケースは、一般的に想像される以上に多く発生し、その結果、さらに混乱を招くことになります。
しかし、オブジェクトの複製を防ぐこと、あるいは少なくともそれによって発生する競合の可能性を低減することは可能です。なぜなら、一部の種類のオブジェクトは、その設定方法によって、他のオブジェクトを隠すことができるからです。幸いなことに、ここで作成されたオブジェクトは完全に無害であり、あくまで学習目的のためだけに使用されています。
さて、ここで読者の皆さんに理解していただきたい重要な点があります。このようにオブジェクトが複製されると、MetaTrader 5は新しいオブジェクトに対して名前を生成します。この名前は一種の標準的な命名規則に従っており、3つの要素で構成されています。最初の部分は、チャートで使用している時間足を示します。2番目の部分は、使用されているオブジェクトの種類を示します。そして3番目の部分は、ランダムに生成された番号です。この3番目の部分については無視します。なぜなら、その目的は同じ種類のオブジェクト同士で名前が衝突することを防ぐためだからです。
では、これは私たちに何を教えてくれるのでしょうか。そして、この知識をどのように利用できるのでしょうか。ここからが、最も興味深い部分です。コード01を使用し、オブジェクトの複製が作成される過程を観察することで確認できる、いくつかの特徴に基づいて、オブジェクトの複製が作成されたかどうかを判断することができます。もちろん、これは完全に正確な科学というわけではありません。なぜなら、この処理をどのように実装するか、そしてMQL5の標準機能だけでどこまで実現できるかについて、いくつかの制約や問題が存在するためです。
その問題の一つについては、すぐに明らかになります。しかし、その前に、さらに興味深いことをおこなうことができます。それは、ユーザーがオブジェクトを操作した際に、MetaTrader 5自身が生成するイベントを分析するための機能を実装することです。その結果、コード02は、以下のようになります。
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. enum eBtnMouse { 09. MOUSE_LEFT = 0x01, 10. MOUSE_RIGHT = 0x02, 11. MOUSE_KEY_SHIFT = 0x04, 12. MOUSE_KEY_CTRL = 0x08, 13. MOUSE_MIDDLE = 0x10, 14. MOUSE_EXTRA_1 = 0x20, 15. MOUSE_EXTRA_2 = 0x40 16. }; 17. //+------------------------------------------------------------------+ 18. string gl_NameObj; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 23. 24. ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0); 25. ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true); 26. ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50); 27. ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50); 28. ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150); 29. ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue); 30. ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20); 31. ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console"); 32. 33. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true); 34. 35. return INIT_SUCCEEDED; 36. }; 37. //+------------------------------------------------------------------+ 38. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 39. { 40. return rates_total; 41. }; 42. //+------------------------------------------------------------------+ 43. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 44. { 45. static bool check = true; 46. 47. switch (id) 48. { 49. case CHARTEVENT_MOUSE_MOVE: 50. ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 51. if (((uchar)sparam & (MOUSE_LEFT | MOUSE_KEY_CTRL)) == (MOUSE_LEFT | MOUSE_KEY_CTRL)) 52. { 53. if (check) Print(StringFormat("CHARTEVENT_MOUSE_MOVE\n%s", StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam))); 54. check = false; 55. }else 56. check = true; 57. break; 58. case CHARTEVENT_OBJECT_CREATE: 59. Print(StringFormat("CHARTEVENT_OBJECT_CREATE\nObject: %s", sparam)); 60. break; 61. case CHARTEVENT_OBJECT_CLICK: 62. Print(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d Y: %03d Object: %s", (ushort)lparam, (ushort)dparam, sparam)); 63. break; 64. case CHARTEVENT_OBJECT_DRAG : 65. Print(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam)); 66. break; 67. } 68. ChartRedraw(); 69. }; 70. //+------------------------------------------------------------------+ 71. void OnDeinit(const int reason) 72. { 73. Comment(""); 74. ObjectsDeleteAll(0, def_Prefix); 75. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 76. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false); 77. ChartRedraw(); 78. }; 79. //+------------------------------------------------------------------+
コード03
このコードを見ると、次のように考えるかもしれません。「ちょっと待ってください。このコードは非常に複雑ではありませんか。チャート上のオブジェクトの複製を防ぐだけのために、本当にここまで複雑な処理が必要なのでしょうか。」さて、コード03では、まだオブジェクトの複製を防止しようとしているわけではありません。そして、このコードは見た目ほど複雑ではありません。そう見えてしまう理由は、おそらく、これまでの説明や過去の記事で解説された内容を十分に理解する前に、偶然この記事にたどり着いたからでしょう。確かに、コード03は一見すると大きな課題のように見えます。
それでも、私の記事を継続的に読んで学習してくださっている皆さんへの敬意を込めて、ここでは新しく追加された部分だけに焦点を当てます。それ以外の部分については、これまで説明してきた内容を学び、実際に練習してきた方であれば、初心者の方であっても理解できるものだと考えています。
ここで注目すべき最初の新しい部分は、33行目です。ここに注意してください。MetaTrader 5では、オブジェクト作成イベントはデフォルトで無効になっています。しかし、アプリケーションによって作成されたオブジェクトを検出させず、作成イベントを発生させたくない場合は、そのオブジェクトが作成された後まで、このイベントを無効のまま維持する必要があります。33行目で有効化する前におこなっている処理が、まさにこれに該当します。OBJ_LABELオブジェクトを作成した後にのみ、このイベントを有効化しているため、そのオブジェクトは作成イベントを発生させません。そして、その作成イベントが発生しなければ、OnChartEvent関数によって取得されることもありません。
さて、このイベントを有効化した後は、チャート上に配置されたあらゆるオブジェクトが作成イベントを発生させます。このデータは58行目によって取得され、その後、ターミナル上にメッセージが表示されます。最後に、76行目が実行されると、この作成イベントを無効化します。これにより、MetaTrader 5がチャート上でこのイベントを継続的に発生させることを防ぎます。
コードの残りの部分については、それほど難しくありません。それでは、次にコード03を実際の環境で適用する部分へ進みましょう。まずは、以下のアニメーションで確認できる内容から始めます。

アニメーション04
このアニメーションでは、チャート上に存在しているオブジェクトを確認できます。また、コードが何もメッセージを出力していないことにも気付くでしょう。これは、先ほど説明した通り、イベントの発生状況が期待通りであることを示しています。
コード03が動作している状態で、オブジェクト一覧を表示するウィンドウを開くことで、チャート上に存在する要素を確認できます。一覧を開くと、以下の図のような表示になります。

図02
さて、ここで一つ重要な点に注目してください。 オブジェクトの複製を防ぐ方法には、大きく分けて2つの考え方があります。1つ目は、図02で確認できるオブジェクト一覧を監視し、分析する方法です。これはコードによって実装できます。しかし、私の考えでは、この方法は不要なリソース消費につながります。なぜなら、58行目が作成イベントを検出するたびに、そのオブジェクト一覧に何が起きたのかを分析しなければならないからです。これはコードを不必要に複雑化させ、CPU使用率も増加させます。
もう1つの方法は、49行目で取得しているマウスイベントを分析する方法です。特に、51行目のフィルター条件がtrueになるイベントに注目します。しかし、この仕組みは100%完全ではありません。なぜなら、同じフィルターが誤検出を発生させる状況が存在するためです。
何が誤検出の原因になるのかを理解するには、私の別の記事である「一からの取引エキスパートアドバイザーの開発(第23部):新規受注システム(VI)」を参照してください。そこでは、51行目を有効化するこの組み合わせを使用しています。そして、この仕組みはオブジェクトを複製するために利用され、保留注文を取引サーバーへ送信できるようにしています。しかし、少しの忍耐と注意、そして慎重な処理によって、この誤検出の問題は回避できます。この点については別の機会に扱うことにします。なぜなら、それには現在説明するには適切ではない、いくつかの特殊な操作が関係しているためです。ここでの目的はまったく別のものです。つまり、複製されたオブジェクトが作成されることをどのように防ぐかを理解することです。
それでは、コード03がチャート上で何が起きているのかを、どのように表示してくれるのかを詳しく見ていきましょう。これは以下のアニメーションで確認できます。

アニメーション05
この場合、発生したイベントはCHARTEVENT_OBJECT_CLICKとCHARTEVENT_OBJECT_DRAGだけでした。これは、すべてが期待通りに動作したことを確認するものです。次に、チャート上のボタンに対して少し異なる操作をしてみます。これは以下のアニメーションで確認できます。

アニメーション06
ここで、一つの重要な点に注目してください。コピーが作成された正確な瞬間、つまり複製が生成された瞬間に、MetaTrader 5は2つのイベントを発生させます。これは、以下の図でより明確に確認できます。

図03
図03では、オブジェクトがコピーされた瞬間、つまり複製が生成された瞬間をはっきり確認できます。ここで注意してください。元のオブジェクトはその場に残っています。移動したのはコピーの方です。これは、図02と、以下に示す図04を比較することで確認できます。

図04
では、なぜこのことを知る必要があるのでしょうか。その理由は、私たちがおこないたい処理によっては、コピーを削除する必要がある場合もあれば、元のオブジェクトを削除する必要がある場合もあるということです。個人的には、コピーを削除する方が簡単だと考えています。なぜなら、作成イベントを取得した時点で、そのコピーの名前がすでに提供されているからです。もちろん、元のオブジェクトを削除し、その後コピーの名前を変更することで、以前と同じ状態に戻すことも可能です。もしかすると、本来の意図はオブジェクトを移動させることであり、誤ってコピーが作成されてしまったのかもしれません。しかし、その方法は少し作業量が多すぎるように思います。そのため、ここでは最も単純な方法を選択します。
コード03に予定している変更では、以前実装し、説明した機能が削除されることになります。そのため、別の方法を取ります。新しいファイルを作成することにします。こうすることで、コード03は記事の添付ファイルとして含めておき、皆さんは手入力することなく、落ち着いて学習できます。必要な変更を加えた新しいコードは、以下のようになります。
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. enum eBtnMouse { 09. MOUSE_LEFT = 0x01, 10. MOUSE_RIGHT = 0x02, 11. MOUSE_KEY_SHIFT = 0x04, 12. MOUSE_KEY_CTRL = 0x08, 13. MOUSE_MIDDLE = 0x10, 14. MOUSE_EXTRA_1 = 0x20, 15. MOUSE_EXTRA_2 = 0x40 16. }; 17. //+------------------------------------------------------------------+ 18. string gl_NameObj; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 23. 24. ObjectCreate(0, gl_NameObj = macro_NameObject, OBJ_LABEL, 0, 0, 0); 25. ObjectSetInteger(0, gl_NameObj, OBJPROP_SELECTABLE, true); 26. ObjectSetInteger(0, gl_NameObj, OBJPROP_XDISTANCE, 50); 27. ObjectSetInteger(0, gl_NameObj, OBJPROP_YDISTANCE, 50); 28. ObjectSetInteger(0, gl_NameObj, OBJPROP_XSIZE, 150); 29. ObjectSetInteger(0, gl_NameObj, OBJPROP_COLOR, clrMediumBlue); 30. ObjectSetInteger(0, gl_NameObj, OBJPROP_FONTSIZE, 20); 31. ObjectSetString(0, gl_NameObj, OBJPROP_FONT, "Lucida Console"); 32. 33. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true); 34. 35. return INIT_SUCCEEDED; 36. }; 37. //+------------------------------------------------------------------+ 38. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 39. { 40. return rates_total; 41. }; 42. //+------------------------------------------------------------------+ 43. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 44. { 45. static bool check = true; 46. static string szObjName = ""; 47. 48. switch (id) 49. { 50. case CHARTEVENT_MOUSE_MOVE: 51. ObjectSetString(0, gl_NameObj, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam)); 52. if (((uchar)sparam & (MOUSE_LEFT | MOUSE_KEY_CTRL)) == (MOUSE_LEFT | MOUSE_KEY_CTRL)) 53. { 54. if (check) 55. if (ObjectDelete(0, szObjName)) 56. Comment("Removing copy object: " + szObjName); 57. check = false; 58. }else 59. check = true; 60. break; 61. case CHARTEVENT_OBJECT_CREATE: 62. szObjName = sparam; 63. break; 64. case CHARTEVENT_OBJECT_CLICK: 65. Comment(StringFormat("CHARTEVENT_OBJECT_CLICK\nX: %03d Y: %03d Object: %s", (ushort)lparam, (ushort)dparam, sparam)); 66. break; 67. case CHARTEVENT_OBJECT_DRAG : 68. Comment(StringFormat("CHARTEVENT_OBJECT_DRAG\nObject: %s", sparam)); 69. break; 70. } 71. ChartRedraw(); 72. }; 73. //+------------------------------------------------------------------+ 74. void OnDeinit(const int reason) 75. { 76. Comment(""); 77. ObjectsDeleteAll(0, def_Prefix); 78. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 79. ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false); 80. ChartRedraw(); 81. }; 82. //+------------------------------------------------------------------+
コード04
このコード04を実行すると、以下のような非常によく似た結果が得られます。

アニメーション07
ここでは、新しいオブジェクトを何の問題もなく追加していることに注目してください。その直後、このオブジェクトに対して、この記事で説明した仕組みを利用してコピーを作成しようとしました。そして、チャートの左上に表示されている内容に注意してください。つまり、私たちはオブジェクトの複製を防ぐ方法を見つけたことになります。もちろん、同じ結果を得るためには、他にも別の方法を取ることができます。
最終的な考察
この記事では、オブジェクトイベントの処理だけに焦点を当てました。しかし、オブジェクトに関連するイベントは、まだ3つ残っています。これらについては、次回の記事で説明する予定です。特に、MetaTrader 5をMQL5によって制御する方法を学び始めたばかりの方々にとって、内容を必要以上に複雑にしたくはありません。
ですので、この記事で扱った内容をしっかり学習し、実際にコードを使って練習してみてください。この知識は、MetaTrader 5がどのように動作しているのかを理解するうえで、非常に役立つものになります。そして、次のように言えるようになるためには、この仕組みを十分に習得しておく必要があります。「はい、私はMetaTrader 5の機能を制御できるだけの十分なプログラミング能力を身につけています。」
それでは、添付ファイルのコードを使って練習してみてください。次回の記事でお会いしましょう。
| MQ5ファイル | 説明 |
|---|---|
| コード01 | オブジェクトイベントのデモンストレーション |
| コード02 | オブジェクトイベントのデモンストレーション |
| コード03 | オブジェクトイベントのデモンストレーション |
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/16035
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
多通貨エキスパートアドバイザーの開発(第28回):決済マネージャの追加
市場シミュレーション:MQL5におけるSQL入門(IV)
市場取引アルゴリズム(EMA)
MQL5におけるタイムギャップ分析(第2回):時間経過に伴う流動性分布のヒートマップ作成
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索