初級から中級まで:オブジェクトイベント(IV)
はじめに
前回の「初級から中級まで:オブジェクトイベント(III)」では、今回の記事で完成させる、より大きな仕組みの最初の部分を紹介しました。
これらの記事の目的は、読者の皆さんに、MetaTrader 5を制御して、さまざまなアプリケーションを実装する方法をお見せすることです。そのため、ここで説明している内容をぜひ勉強し、実際に練習してみてください。ここで説明しているコードは、決して完成されたアプリケーションとして扱ったり、適切な注意や常識を持たずに使用したりしないでください。
私のやっていることはばかげていると思う人もいるかもしれません。なぜなら、私がここで示しているようなことを目的とするアプリケーションなどあり得ない、と多くの人が考えるからです。しかし、プログラミングの美しさは、明白なことを実行することにあるのではありません。むしろ、ほとんど誰にも見えていない場所に新しい可能性を見つけ出すことにあります。だからこそ、私はこの一連の記事を作成することにしました。その目的は、いくつかの概念を理解すれば、ほとんど何でも実現できるということを示すことです。それでは、前回の記事で紹介したすべての内容を、本当に意味のあるものへと発展させる時が来ました。
インタラクティブ性の向上
前回の記事で紹介した内容は、少なくとも添付ファイルにある現在のコードだけを見ると、論理的に完全なソリューションにはまだ程遠いものです。しかし、これは練習用として意図的に残していました。比較的少ないコードで、このようなインタラクションをどのように作成できるのかを示すためです。
前回の記事に添付したファイルは、まだ完全には成熟していません。非常に興味深い主目的のために、しかも非常にシンプルな方法で、いくつかのことを実装できることはすでに示しましたが、それでもまだ、私たちが望んでいる方法でオブジェクトを操作することはできません。簡単に言えば、このコードが作成するのは、下のアニメーション01で確認できるものだけです。

アニメーション01
これは一見すると、少し馬鹿げていて、それほど役に立たないものに思えるかもしれません。しかし、このアニメーションは、このコードにすでに組み込まれている可能性を示しています。なぜなら、選択されたオブジェクトの周囲にアウトラインを作成できるからです。しかし、私たちの目的は、このようなアウトラインを作成することだけではありません。選択されたオブジェクトのサイズを変更することも目的です。ここから、いくつかの混乱が始まります。特に、前回の記事で説明した内容を練習し、学習していない人にとっては、まさにここでいくつかの困難が現れます。
これまでの記事では、MetaTrader 5とやり取りし、特定の結果を実現できることを示してきました。しかし、その作業の大部分はMetaTrader 5に任せており、コードを実装する際には、私たちはその一部だけを担当していました。ところが、ここでは小さな問題があります。それは次のようにまとめることができます。MetaTrader 5は、選択されたオブジェクトのサイズ変更を処理するためにアプリケーションが必要とする作業の大部分を実行できます。しかし、追加の作業なしにこれをおこなうことはできません。なぜこのように言うのかというと、以下に完全な形で示すソースコードを使用した場合、100%正確なインタラクションは得られないからです。その実行結果はアニメーション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_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. //+------------------------------------------------------------------+
コード01
「では、なぜインタラクションが100%正確ではないと言うのでしょうか。仕組みは一見すると正常に動作しているようですから、私には意味が分かりません。」確かに、読者の皆さん、仕組み自体は動作しています。しかし、次の図に示すような小さな問題があります。

図01
図01だけを見ても、何も分からないかもしれません。しかし、ここではある一点を強調しています。まさに、ある細かな点に注意を向けてもらうためです。そのアウトラインには、インタラクティブな要素がありません。そのため、MetaTrader 5にもユーザーにも、それを移動させるための明確なインタラクションポイントがありません。ここで注意してください。コード01の84行目は、まさにユーザーが、同じコードの04行目で指定されている接頭辞を名前に持つオブジェクトを選択できないようにするために存在しています。では、これは何を意味するのでしょうか。そうです。これが、OBJ_BUTTONオブジェクトを選択できない理由を説明しています。なぜなら、このオブジェクトの名前には、その接頭辞が含まれているからです。これは下の図02で確認できます。

図02
緑色で強調表示されているのが、まさに、このオブジェクトを選択できないようにしている接頭辞であることに注意してください。一見すると、それほど重要なことには思えないかもしれません。しかし、この単純な一点だけで、84行目のチェックがそのオブジェクトを選択できないようにするには十分なのです。なるほど。しかし、アウトラインとして使用している緑色の線を選択する必要はありません。もっと良い方法があります。OBJ_RECTANGLE_LABELオブジェクトそのものを選択し、残りの処理をMetaTrader 5に任せればよいのです。はい、これは部分的には正しいです。そのためには、コード01を少し変更するだけで済みます。その変更は、以下のコード断片で確認できます。
. . . 034. //+----------------+ 035. public : 036. void CreateBoxResize(const string szArg, const uchar adjust = 2) 037. { 038. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 039. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 040. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 041. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 042. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 043. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 044. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 045. for (uchar c = 0; c < 4; c++) 046. { 047. CreateEdge(c); 048. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 049. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 050. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 051. } 052. } 053. //+----------------+ . . .
コード02
「では、コード02のこのコード断片をコード01に組み込むと、何が作られるのでしょうか。その結果を、下のアニメーション02で確認できます。」

アニメーション02
さて、アニメーション02を見ると、何かが確認できます。落ち着いて詳しく見てみましょう。図03を見てください。

図03
図03で強調表示されている部分が、チャート上でオブジェクトをドラッグするために使用できるアンカーポイント(操作点)です。この操作には、ほかのことを気にすることなくMetaTrader 5を使用します。しかし、よく見ると、3つのポイントしか表示されていないことに気付くでしょう。4つ目のポイントはどこにあるのでしょうか。これが、私たちが直面する最初の問題です。4つ目のポイントは、別のポイントのすぐ隣、まさに図03で強調されている場所にあります。もしこれだけが実際の問題であれば、大したことではありません。私たちにできることはほとんどないでしょう。しかし、これよりも少し厄介な問題があります。それを示しているのが、下のアニメーションです。

アニメーション03
「今度は何も分かりません。アニメーション03で見えているものはMetaTrader 5の通常の動作の一部なのですから、なぜそれが問題になるのでしょうか?」そうですね、読者の皆さん。私たちが何をしようとしているのかを、まだ正確に理解していないのではないかと思います。ここで、状況を少し明確にしましょう。私たちが実現しようとしていることは、アニメーション03に示されているものに似ています。ただし、緑色の線同士のつながりが失われないようにする必要があります。言い換えれば、あるアンカーポイントを移動した場合、そのポイントにつながっている線も、それに応じて移動しなければなりません。そうすることで、新しい領域の境界を示し、選択したオブジェクトの新しいサイズがどのようになるのかを表示できます。それが私たちの目的です。アニメーション03で見られるものが目的なのではありません。これで問題が分かったでしょうか?
さて、この時点で、多くの人は諦めて、次のように言い始めるでしょう。「あなたがやろうとしていることは不可能です。」本当に不可能なのでしょうか。少し考えてみてください。MetaTrader 5が、オブジェクトに対して発生したイベントに関する情報を、どのように私たちのアプリケーションへ渡しているのかは分かっています。また、実装時に下す特定の判断が、私たちが実現できる結果の種類にどのような影響を与えるのかについても見てきました。そこで、私は皆さんにこう問いかけます。
私が実現したいことは、本当に不可能なのでしょうか。
答えはいいえです。しかし、知識や経験が限られているため、多くの人は最終的に、それは不可能だと考えてしまいます。アニメーション03で示されているように、物事が期待どおりに動作しないからです。しかし、ここでもう一度、次のことを強調しておく必要があります。
コードに執着しすぎないでください。常に、そこに関係している概念と、それらがどのように使用されているのかを理解するようにしてください。
これは、まさにプログラマーがおこなっていることです。問題を取り上げ、プログラミングの知識を使って、その解決策を見つけ、発展させていきます。その解決策をどのように作り上げるかは、プログラマーによって確実に異なります。しかし、本当に重要なのは最終的な結果です。したがって、アプリケーションによってインタラクティブ性の程度が異なることもありますし、その使い方を学ぶことが簡単な場合もあれば、難しい場合もあります。しかし、どの場合でも、本当に重要であり、私たちが関心を持つべきなのは、最終的な結果です。
さて、ここで少し立ち止まって考えてみましょう。コードで定義されている接頭辞を名前に持つオブジェクトは、どれも選択できないことが分かっています。これはまさにコード01の84行目が原因で起こります。しかし、ここで私たちは、これらのオブジェクトを選択する必要がないことを理解しました。オブジェクトを作成するときに、そのオブジェクトがすでに選択された状態になるよう、MetaTrader 5に伝えることができます。これは、コード02に表示されている49行目と50行目に関係しています。そして、この2行が、コード01に実装した変更そのものです。
これによって、これまで移動できなかったオブジェクトを、MetaTrader 5によってユーザーが移動できるようにすることができました。以前は、そのオブジェクトを選択できなかったため、移動することもできませんでした。読者の皆さん、私がここで説明しようとしていることについて、きちんとついてきていただけているか分かりません。もし分からない場合は、前回の記事ですでに説明した内容をもう一度読み返してみてください。私がここで示そうとしているのは、まさに、私たちが最終的な目標を実現するために使用する概念です。つまり、チャート上に存在するオブジェクトのサイズを完全にインタラクティブな方法で変更することです。
さて、次のようにしましょう。コード02のコード断片をコード01に組み込み、アニメーション03で示されている結果を得る方法が分からない人も多いかもしれません。そこで、コード01を基にして新しいコードを作成し、コード02で示した変更を加えます。こうすることで、添付ファイルからコード02全体にアクセスできるようにします。これなら、落ち着いてコードを研究し、既存のコードに別のコードの一部分を組み込む方法を学ぶことができます。この作業はこれから何度もおこなうことになります。この方法を理解しておけば、そうでなければ実現不可能だったことについても、勉強し、実際に練習できるようになります。
それでは、コード01をベースにして、コード02のコード断片で示した変更を加えた新しいコードを作成しましょう。そのコードを以下に示します。
. . . 013. string szObjects[6]; 014. //+----------------+ 015. void CreateEdge(const char what) 016. { 017. ushort p1 = x, p2 = y, p3 = w, p4 = h; 018. 019. switch (what) 020. { 021. case 0: p4 = 1; break; 022. case 1: p3 = 1; break; 023. case 2: p2 = y + h; p4 = 1; break; 024. case 3: p1 = x + w; p3 = 1; break; 025. case 5: p1 += p3; p2 += p4; 026. case 4: p3 = p4 = 1; break; 027. } . . . 035. } 036. //+----------------+ 037. public : 038. void CreateBoxResize(const string szArg, const uchar adjust = 2) 039. { . . . 047. for (uchar c = 0; c < szObjects.Size(); c++) 048. { 049. CreateEdge(c); 050. if (c > 3) 051. { 052. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 053. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 054. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 055. } 056. } 057. } 058. //+----------------+ . . .
コード03
さて、読者の皆さん、次の内容を見てください。コード03には、1つだけ簡単な変更があります。しかし、それは非常に重要な変更です。13行目で、配列の要素数を変更していることに注目してください。また、19行目のswitch文には、25行目と26行目に見られる、さらに2つの興味深いポイントがあります。ここで少し立ち止まり、もう1つ非常に重要なことを説明しましょう。
記事「初級から中級まで:SWITCHオペレーター」では、赤い線について説明している画像がありました。下にその画像をもう一度示します。

図04
ここで重要なのは、まさに図04に表示されている赤い線です。19行目のこのswitch文では、まさにこの赤い線を使用しています。これは重要なことです。なぜなら、これによって実行を次のcaseへフォールスルーさせることができるからです。「うわあ、これ以上ややこしくなることはないと思っていました。それなのに今度は、実行が別のコードブロックに移ることもあると言っているのですか。しかも、それを意図的におこなうのですか。プログラミングというのは、私が想像していたよりずっと難しいようです。もう全部諦めて、何か別のことをしようかと考えています。」
落ち着いてください、読者の皆さん。諦める理由などありません。これからさらに面白くなろうとしているところで、本当に諦めてしまうのですか。いいえ。一緒に、この私が話しているステップが実際には何を意味しているのかを理解していきましょう。
case文の並びを見てください。ほぼ順番どおりに並んでいることに気付くでしょう。しかし、途中で突然順番が変わっています。3番目から5番目へ進み、その後4番目に戻っています。「なんて奇妙なんでしょう。そんなことには気付きませんでした。言われてみると、確かに変ですね。でも、なぜこんなことをしているのですか。」意図的にそうしているからです。なぜでしょうか。まさに、図04で確認できる赤い線について説明するためです。
ですから、よく注意してください。これは単純なことに見えるかもしれませんが、実際には初心者だけでなく、多くの優秀なプログラマーさえもかなり混乱させることがあります。コード03にある21行目から26行目までを見てください。25行目を除き、すべての行の最後にbreak文があります。では、なぜ25行目にはないのでしょうか。25行目のcaseが一致した場合、その行が実行されるからです。しかし、ここで次のcaseへ移るという問題が出てきます。 私はここで実行を終了させたくありません。次のcaseにある処理を、そのcaseを事前に評価することなく実行したいのです。
「なんということでしょう。全然わかりません。でも、ちょっと待ってください。落ち着いてあなたの言葉について考えさせてください。私の理解が正しければ、25行目がチェックされ、そのcaseの処理が実行されたとき、さらに26行目のcaseにある処理も実行したい、ということですか。」はい、そのとおりです。そして、それは可能です、読者の皆さん。「しかし、1つ分からないことがあります。26行目のcaseをチェックせずに、どうやってそのcaseのコードを実行するのですか。そんなことは意味がありません。」
まさにここで、次のcaseへのフォールスルーが発生します。ここからは、よく注意してください、読者の皆さん。図04に示されているように、case文は必ずbreak文で終了します。したがって、BREAK文がない場合、実行は次のcaseの分岐へと続きます。しかも、 次のcaseをチェックすることなく 実行されます。そして、ここで赤い線が登場するのです。
オブジェクト番号04を作成するとき、そのサイズを1に設定していることに注目してください。オブジェクト番号05を作成するときも、そのサイズを1に設定しています。ただし、オブジェクト番号05の場合は、初期位置を変更し、右下隅に配置しています。一方、オブジェクト番号04は左上隅に配置されています。これらすべては、19行目のswitch文で確認できる、次のcaseへフォールスルーする仕組みを利用することで実現されています。
しかし、次の点に注意してください。50行目では、どのオブジェクトを作成しているのかをチェックしています。選択されたオブジェクトを囲む四角形を描画した後、コード02のコード断片で示したものと同じ方法で、最後の2つのオブジェクトの描画へ進みます。ただし今回は、異なる目的のために、異なる結果になるようにこれをおこなっています。

図05
この場合、2つのポイントがあることに注目してください。そして、私たちに必要なのはそれだけです。しかし、まだ終わりではありません。ポイントはすでに作成されていますが、それらとインタラクションしたときに何が起こるのかを見る価値があります。その様子は、下のアニメーションで確認できます。

アニメーション04
「うーん、興味深いですね。ある意味では、今のところ比較的有望な見通しが立ってきたように思います。では、次のステップは何でしょうか。さて、読者の皆さん、物事はいつもそれほど単純とは限りません。ここで問題なのは、次のステップが何になるかではありません。最も重要なこと、そして私が皆さんに求めていることは、ここで私たちが何をしているのかを正確に理解することです。ここで示しているこの種の操作には、ある程度の注意と、すべてが制御不能にならないことを確認するためのテストの作成が必要です。そうしなければ、1つのミスによって、その日を非常に不快なものにしてしまう危険があります。
ですから、もう一度繰り返します。適用できる概念を理解しようとし、自分にとって明確なソリューションを作成してください。それによってのみ、アプリケーションのライフサイクル中に発生する問題を解決できるようになります。
この注意については、何度繰り返しても飽きることがありません。それでは、この注意喚起を終えたところで、次のポイントへ進みましょう。今回は、イベント処理用のコードを変更する必要があります。さらに、後ほど説明する別の部分についても変更する必要があります。しかし、まずはイベント処理用のコードからです。そのコードを以下に示します。
. . . 079. //+------------------------------------------------------------------+ 080. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 081. { 082. static string szNameObj = NULL; 083. 084. switch (id) 085. { 086. case CHARTEVENT_KEYDOWN : 087. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 088. { 089. gl_RZ.RemoveBoxResize(szNameObj); 090. szNameObj = NULL; 091. } 092. break; 093. case CHARTEVENT_OBJECT_CLICK : 094. Print((ushort)lparam, " x ", (ushort)dparam, " ->> ", sparam); 095. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) break; 096. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 097. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 098. break; 099. case CHARTEVENT_OBJECT_DRAG : 100. Print("CHARTEVENT_OBJECT_DRAG ->> ", sparam); 101. break; 102. } 103. ChartRedraw(); 104. }; 105. //+------------------------------------------------------------------+ . . .
コード04
コード04ではシンプルな変更を加えています。これは、私の考えでは重要なことを示すためです。もっとも、これはすでに以前の記事でも説明しています。それでも、ある概念を本当に理解するまで、繰り返し確認することは常に役に立ちます。
94行目と100行目で、いくつかのメッセージをターミナルに出力するよう指定していることに注目してください。それでは、いわば最初の「通常の」コード実行の結果を見てみましょう。その様子は、下のアニメーションで確認できます。

アニメーション05
次に、私たちが避けたい動作を見てみましょう。これは以下のアニメーション06に示されています。

アニメーション06
アニメーション05と06では、MetaTrader 5の標準的な動作が実際に確認できます。しかし、アニメーション05に示されている動作については問題ありません。一方、アニメーション06に示されている動作は受け入れることができません。アニメーション06では、ユーザーが、本来選択できるはずではないオブジェクトについて、選択状態をオン・オフしています。私たちは、そのオブジェクトを移動できるだけにするべきです。しかし、そのオブジェクトの選択状態を有効にしたり無効にしたりすることはできないようにする必要があります。
それでは、この課題を一つずつ解決していきましょう。最初のステップは、ユーザーが単純にオブジェクトをクリックしたときに、そのオブジェクトの選択状態が変更されないようにすることです。これは、以下のコードを使用することで実現できます。
. . . 007. //+------------------------------------------------------------------+ 008. struct st_BoxResize 009. { . . . 036. //+----------------+ 037. public : . . . 065. //+----------------+ 066. void Block(const string szArg) 067. { 068. for (uchar c = 0; c < szObjects.Size(); c++) 069. if (szObjects[c] == szArg) 070. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 071. } 072. //+----------------+ 073. }gl_RZ; 074. //+------------------------------------------------------------------+ . . . 086. //+------------------------------------------------------------------+ 087. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 088. { 089. static string szNameObj = NULL; 090. 091. switch (id) 092. { 093. case CHARTEVENT_KEYDOWN : 094. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 095. { 096. gl_RZ.RemoveBoxResize(szNameObj); 097. szNameObj = NULL; 098. } 099. break; 100. case CHARTEVENT_OBJECT_CLICK : 101. Print((ushort)lparam, " x ", (ushort)dparam, " ->> ", sparam); 102. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 103. { 104. gl_RZ.Block(sparam); 105. break; 106. } 107. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj); 108. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 109. break; 110. case CHARTEVENT_OBJECT_DRAG : 111. Print("CHARTEVENT_OBJECT_DRAG ->> ", sparam); 112. break; 113. } 114. ChartRedraw(); 115. }; 116. //+------------------------------------------------------------------+ . . .
コード05
最初の問題に対する解決策が、いかに簡単に実装できるかに注目してください。私たちがしなければならないのは、構造体に新しい関数を追加することだけです。これは66行目で確認できます。次に、この新しい関数を呼び出す必要があります。これは104行目でおこなっています。では、その結果はどうなるのでしょうか?下のアニメーションを見れば、ご自身で確認できます。

アニメーション07
それでは、解決する必要がある問題の2つ目の部分に進みましょう。しかし、その前に1つ注意しておきたいことがあります。ここで紹介する解決策は、あくまでも教育目的で示すものです。必ずしも、私たちが実装できる唯一の選択肢というわけではありません。ただし、これらの記事の目的を考えれば、今回は可能な限りシンプルな解決策を紹介することにします。
とはいえ、皆さんがもっと適切な解決策を実装することを妨げるものは何もありません。たとえば、サイズを変更するためにマウスを動かすたびに、その動きが、インタラクティブにサイズ変更しようとしているオブジェクトのサイズに即座に反映されるような解決策も考えられます。もしかすると、後ほどその方法を紹介するかもしれません。原理的には、これは非常に教育的であり、将来皆さんが実装しようと考えているさまざまなケースにおいて、興味深いものになる可能性もあります。
いずれにしても、ここでは最もシンプルな解決策を使うことにしましょう。基本的には、これは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. enum E_Elment { 009. TOP, 010. LEFT, 011. BOTTOM, 012. RIGHT, 013. TL, 014. BR 015. }; 016. //+------------------------------------------------------------------+ 017. struct st_BoxResize 018. { 019. private : 020. ushort x, y, w, h; 021. bool isBack; 022. string szObjects[6]; 023. //+----------------+ 024. void SetPosition(const E_Elment what) 025. { 026. ushort p1 = x, p2 = y, p3 = w, p4 = h; 027. 028. switch (what) 029. { 030. case TOP: p4 = 1; break; 031. case LEFT: p3 = 1; break; 032. case BOTTOM: p2 = y + h; p4 = 1; break; 033. case RIGHT: p1 = x + w; p3 = 1; break; 034. case BR: p1 += p3; p2 += p4; 035. case TL: p3 = p4 = 1; break; 036. } 037. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 038. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 039. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 040. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 041. } 042. //+----------------+ 043. void CreateEdge(const E_Elment what) 044. { 045. szObjects[what] = macro_NameObject; 046. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 047. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 048. SetPosition(what); 049. } 050. //+----------------+ 051. public : . . . 072. //+----------------+ 073. void UpdateBoxSize(const string szArg) 074. { 075. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 076. if (szObjects[c] == szArg) 077. { 078. x = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_XDISTANCE); 079. y = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_YDISTANCE); 080. w = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_XDISTANCE) - x; 081. h = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_YDISTANCE) - y; 082. for (E_Elment i = 0; i < (E_Elment)4; i++) 083. SetPosition(i); 084. } 085. } 086. //+----------------+ . . . 100. //+----------------+ 101. }gl_RZ; 102. //+------------------------------------------------------------------+ . . . 114. //+------------------------------------------------------------------+ 115. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 116. { 117. static string szNameObj = NULL; 118. 119. switch (id) 120. { . . . 137. case CHARTEVENT_OBJECT_DRAG : 138. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 139. gl_RZ.UpdateBoxSize(sparam); 140. break; 141. } 142. ChartRedraw(); 143. }; 144. //+------------------------------------------------------------------+ . . .
コード06
コード06がかなり断片的になっていることは分かっています。しかし、これは意図的にそうしています。実際に実装する必要がある部分だけを強調したいからです。いくつかのことを簡単にするために、08行目で列挙型を作成していることに注目してください。これによって、最後の2つのステップをより簡単に実装できるようになります。また、境界矩形を構成するオブジェクトの作成に関する部分も、2つのブロックに分けています。そのうちの1つが、24行目にあるSetPosition関数です。もう1つが、境界矩形の各辺を直接作成する部分です。この場合、その関数は43行目にあります。
ここで重要なのは、73行目にあるUpdateBoxSize関数の登場です。この関数は基本的に、139行目に示されている関数がCHARTEVENT_OBJECT_DRAGイベントによって呼び出されたときに、新しい位置に合わせて境界矩形を再構築します。
いずれにしても、私たちが最終的に実現したいものは、以下で確認できます。

アニメーション08
アニメーション08に示されているこのコードが重要なポイントです。ここでは、アニメーション08に示されている結果を得る方法自体は変更することができます。しかし、重要なのは、アニメーション08で示されているとおりに動作することです。ただし、この目的とは別に、アニメーション08には、個々のプログラマーが必要、あるいは望ましいと考える内容に応じて変更できる部分があります。アニメーション08に示されている段階で、サイズ変更の処理を完了したものと考えることもできます。あるいは、別の方法で実装することもできます。そして、私たちがこれから進むのはこちらの道です。その理由は単純です。私が理想的だと考える方法に、できるだけ近い形で教育的なアプローチを維持するためです。
したがって、次のステップは以下のコードで確認できます。ただし今回は、コード全体をそのまま掲載します。これは、非常にシンプルで基本的なレベルのプログラミングであっても、これまで示してきたさまざまな概念と知識を正しく適用することで、どれほど素晴らしいものを作り出せるのかを、皆さんに味わっていただくためです。ただ、その結果をじっくり眺め、楽しんでください。ここまで長い間待ち、チャート上のオブジェクトをインタラクティブにサイズ変更するという問題の解決策について、さまざまな想像をしてきた読者の皆さんには、その資格があります。
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. enum E_Elment { 009. TOP, 010. LEFT, 011. BOTTOM, 012. RIGHT, 013. TL, 014. BR 015. }; 016. //+------------------------------------------------------------------+ 017. struct st_BoxResize 018. { 019. private : 020. ushort x, y, w, h; 021. bool isBack; 022. string szObjects[6]; 023. //+----------------+ 024. void SetPosition(const E_Elment what) 025. { 026. ushort p1 = x, p2 = y, p3 = w, p4 = h; 027. 028. switch (what) 029. { 030. case TOP: p4 = 1; break; 031. case LEFT: p3 = 1; break; 032. case BOTTOM: p2 = y + h; p4 = 1; break; 033. case RIGHT: p1 = x + w; p3 = 1; break; 034. case BR: p1 += p3; p2 += p4; 035. case TL: p3 = p4 = 1; break; 036. } 037. ObjectSetInteger(0, szObjects[what], OBJPROP_XDISTANCE, p1); 038. ObjectSetInteger(0, szObjects[what], OBJPROP_YDISTANCE, p2); 039. ObjectSetInteger(0, szObjects[what], OBJPROP_XSIZE, p3); 040. ObjectSetInteger(0, szObjects[what], OBJPROP_YSIZE, p4); 041. } 042. //+----------------+ 043. void CreateEdge(const E_Elment what) 044. { 045. szObjects[what] = macro_NameObject; 046. ObjectCreate(0, szObjects[what], OBJ_RECTANGLE_LABEL, 0, 0, 0); 047. ObjectSetInteger(0, szObjects[what], OBJPROP_BGCOLOR, clrLime); 048. SetPosition(what); 049. } 050. //+----------------+ 051. public : 052. void CreateBoxResize(const string szArg, const uchar adjust = 2) 053. { 054. isBack = (bool)ObjectGetInteger(0, szArg, OBJPROP_BACK); 055. ObjectSetInteger(0, szArg, OBJPROP_BACK, true); 056. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 057. x = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XDISTANCE) - adjust; 058. y = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YDISTANCE) - adjust; 059. w = (ushort)ObjectGetInteger(0, szArg, OBJPROP_XSIZE) + adjust; 060. h = (ushort)ObjectGetInteger(0, szArg, OBJPROP_YSIZE) + adjust; 061. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 062. { 063. CreateEdge(c); 064. if ((c == TL) || (c == BR)) 065. { 066. ObjectSetInteger(0, szObjects[c], OBJPROP_WIDTH, 3); 067. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTABLE, true); 068. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 069. } 070. } 071. } 072. //+----------------+ 073. void UpdateBoxSize(const string szArg) 074. { 075. for (E_Elment c = 0; c < (E_Elment)szObjects.Size(); c++) 076. if (szObjects[c] == szArg) 077. { 078. x = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_XDISTANCE); 079. y = (ushort)ObjectGetInteger(0, szObjects[TL], OBJPROP_YDISTANCE); 080. w = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_XDISTANCE) - x; 081. h = (ushort)ObjectGetInteger(0, szObjects[BR], OBJPROP_YDISTANCE) - y; 082. for (E_Elment i = 0; i < (E_Elment)4; i++) 083. SetPosition(i); 084. } 085. } 086. //+----------------+ 087. void RemoveBoxResize(const string szArg, const bool update) 088. { 089. ObjectSetInteger(0, szArg, OBJPROP_BACK, isBack); 090. ObjectSetInteger(0, szArg, OBJPROP_SELECTED, false); 091. if (update) 092. { 093. ObjectSetInteger(0, szArg, OBJPROP_XDISTANCE, x); 094. ObjectSetInteger(0, szArg, OBJPROP_YDISTANCE, y); 095. ObjectSetInteger(0, szArg, OBJPROP_XSIZE, w); 096. ObjectSetInteger(0, szArg, OBJPROP_YSIZE, h); 097. } 098. ObjectsDeleteAll(0, def_Prefix); 099. } 100. //+----------------+ 101. void Block(const string szArg) 102. { 103. for (uchar c = 0; c < szObjects.Size(); c++) 104. if (szObjects[c] == szArg) 105. ObjectSetInteger(0, szObjects[c], OBJPROP_SELECTED, true); 106. } 107. //+----------------+ 108. }gl_RZ; 109. //+------------------------------------------------------------------+ 110. int OnInit() 111. { 112. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 113. 114. return INIT_SUCCEEDED; 115. }; 116. //+------------------------------------------------------------------+ 117. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 118. { 119. return rates_total; 120. }; 121. //+------------------------------------------------------------------+ 122. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 123. { 124. static string szNameObj = NULL; 125. 126. switch (id) 127. { 128. case CHARTEVENT_KEYDOWN : 129. if ((szNameObj != NULL) && (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE))) 130. { 131. gl_RZ.RemoveBoxResize(szNameObj, false); 132. szNameObj = NULL; 133. } 134. break; 135. case CHARTEVENT_OBJECT_CLICK : 136. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 137. { 138. gl_RZ.Block(sparam); 139. break; 140. } 141. if (szNameObj != NULL) gl_RZ.RemoveBoxResize(szNameObj, true); 142. if (szNameObj != sparam) gl_RZ.CreateBoxResize(szNameObj = sparam); else szNameObj = NULL; 143. break; 144. case CHARTEVENT_OBJECT_DRAG : 145. if (StringFind(sparam, def_Prefix) != INVALID_HANDLE) 146. gl_RZ.UpdateBoxSize(sparam); 147. break; 148. } 149. ChartRedraw(); 150. }; 151. //+------------------------------------------------------------------+ 152. void OnDeinit(const int reason) 153. { 154. ObjectsDeleteAll(0, def_Prefix); 155. ChartRedraw(); 156. }; 157. //+------------------------------------------------------------------+
コード07
この場合、コード07で何が起こっているのかについては説明しません。皆さん自身でこのコードを研究してみてください。そして、一見すると意味のないものに思え、多くの人には、ある天才的な狂人の想像力から生まれたもののように見えるこのコードが、どのようにして下のアニメーションに示されていることを実現しているのかを理解してほしいと思います。まずは、通常のサイズ変更です。

アニメーション09
次に、後からキャンセルすることになる別のサイズ変更と組み合わせたサイズ変更です。

アニメーション10
最終的な考察
プログラミングは、退屈でも、難しいものでも、苦痛なものでもありません。実際には、楽しく、刺激的で、面白いものです。重要なのは、「自分が何をしたいのかを理解しているか」です。そして、基本的な概念を理解し、それらを論理的な命令の集合として適用することができますか。つまり、機械に対して、何をするのか、いつそれをするのか、そしてなぜそれをするのかを伝えることができますか。それとも、機械があなたの望んでいることを理解してくれることを期待しながら、まったく意味のないものを大量に入力しているだけでしょうか。
多くの人は、プログラミングとは、優れた才能を持つ一部の専門家にしかできないものだと考えています。それが本当である可能性を否定するつもりはありません。しかし、読者の皆さんが、ある問題を解決する方法を自分自身で考え、それを紙に書き出し、どのような概念を使用すべきなのかを分析し、それをコードとして実装することを妨げるものは何もありません。
才能は一部の人に与えられるものです。しかし、努力、決意、学習、そして規律は、自分がどこへ行きたいのかを本当に理解し、その目標を達成する方法を探している人々のものです。
考えてみてください。学習と練習のために、皆さんが利用できる教材を活用してください。昨日できたはずのことを、明日まで先延ばしにしないでください。いずれにしても、何か自分に喜びを与えてくれることを始め、それに専念するのに、遅すぎるということは決してありません。
もしかすると次回の記事では、ここで扱ったコードをさらに改善するための別の方法を紹介するかもしれません。そのコードは添付ファイルで利用できるようにします。すべては、そこで紹介する内容が、今回扱った内容に何かを付け加えるものになるかどうかにかかっています。いずれにしても、私が皆さんに改善方法を教えてくれることを期待しないでください。解決策は皆さん自身で見つけなければなりません。もし最初からすべてを実装できないのであれば、ここまでに説明した内容をもう一度勉強してみてください。この内容を完全に身につけるには、時間と練習が必要です。
| MQ5ファイル | 説明 |
|---|---|
| コード01 | オブジェクトイベントのデモンストレーション |
| コード02 | オブジェクトイベントのデモンストレーション |
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/16094
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
市場シミュレーション(第24回):ポジション表示(II)
Extremal Optimization (EO)アルゴリズム
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
市場シミュレーション(第23回):ポジション表示(I)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索