English Русский Español Deutsch Português
preview
初級から中級まで:マウスイベントの処理

初級から中級まで:マウスイベントの処理

MetaTrader 5 |
13 0
CODE X
CODE X

はじめに

前回の「初級から中級まで:関数ポインタ」では、スクリプト内でイベントを実装する方法について説明しました。その際、すでにご存じの「ポインタ」という特殊なデータ型をどのように利用できるかについても解説しました。前回の記事には、特にグラフィカルプログラミングをさらに深く学びたい方にとって重要な基礎知識が含まれていますので、ぜひ丁寧に復習していただくことをお勧めします。本記事では、それとは異なる、よりシンプルなテーマを扱います。

今回の主題は、マウス操作とマウス関連のイベントです。内容を整理しながら説明するために、まずは本記事の最初のテーマから見ていきましょう。


マウスイベント

まず最初に理解していただきたいことがあります。ママウスというものは、本当にばかばかしく、厄介で、ひどい代物で、ときには地獄の底から這い出してきたかのようです。コードの動き方も、プログラマー側の制約もお構いなしに、まるで気まぐれに動作しているように見えることがあります。MetaTrader 5は非常に便利で柔軟性の高いプラットフォームですが、デフォルトの状態では、チャートウィンドウ内で動作するアプリケーションに対してマウスイベントを生成しません。では、なぜでしょうか。その理由は、MetaTrader 5が高いパフォーマンスと効率を重視して設計されているということです。一方で、マウスイベントの処理はパフォーマンスや効率に悪影響を与える可能性があります。さらに、チャート上で動作するアプリケーションは、マウスイベントに対する最適化が十分でないことが少なくありません。

アプリケーションのマウスイベント処理が適切に最適化されていない場合、どれほど深刻な問題が発生するのか、まだ実感が湧かないかもしれません。この点については、今後の記事で詳しく取り上げます。ただし、その前に理解しておくべきことがいくつかあります。

おそらく、すでに次のように思われた方もいるでしょう。「それは違うのではありませんか。MetaTrader 5ではチャートを右クリックすればさまざまな機能を利用できますし、ワンクリック機能を使えば注文も出せます。ですから、マウスが問題だというのは認識不足ではないでしょうか。」

そのご指摘はもっともです。確かに、MetaTrader 5自体にとってマウスが問題だとは私は言っていません。私が言いたいのは、チャート上で動作するアプリケーションにとって、マウスイベントは問題になり得るということです。特に、イベントハンドラが適切に最適化されていなかったり、コードの構成が整理されていなかったりする場合には、その影響が顕著になります。そして、コードの規模が大きくなればなるほど、その混乱も大きくなります。そのため、私は常にできるだけ短く、シンプルなコードを書くことを心掛けています。目的は、自分の不注意によって最適化されていないコードを生み出さないようにするためです。

次に重要な点です。MetaTrader 5は、デフォルトではチャートに対してマウスイベントを生成しないため、MetaTrader 5に対して「マウスイベントを受け取りたい」ということを伝えるコードを書かなければなりません。そして、そのチャートでマウスイベントを受け取るよう設定した後は、アプリケーションを終了するときに、マウスイベントを無効化することも、プログラマーである私たち自身がコードとして実装しなければなりません。

残念ながら、ほとんどの場合、そのようにはなっていません。その結果、アプリケーションが有効化したマウスイベントを無効化しなかったために、MetaTrader 5はそのチャートに対してマウスイベントを生成し続けることになります。ですから、注意深く、そして整理された形で作業してください。何かを有効にしたのであれば、必ず無効にしてください。プラットフォーム自身が問題を解決する方法を見つけてくれるだろうと期待してはいけません。

次の点です。必要以上のリソースを有効化したり、使用したりしないでください。MetaTrader 5がマウスイベントを生成するときには、生成されるイベントの種類ごとに細かな違いがあります。アプリケーションが特定のイベント関連を必要としないのであれば、それらを有効化すべきではありません。すべてはシンプルで分かりやすくあるべきです。本当に必要なものだけを有効にしてください。

この短い導入部は、少々厳しい印象を与えるかもしれません。しかし実際には、これからプログラミングを学び始める開発者が、学習や実装の初期段階を正しく進められるようにするためには、明確な説明が必要なのです。私は、必要以上のことを実装しておきながら、その後で「プログラムが期待どおりに動かない」と不満を言う人を数多く見てきました。

立ち止まって実際に何が起こっているのかを見てみると、問題は不要なリソースを有効化しすぎていることにあります。余分な要素を取り除くと、アプリケーションは本来期待されるレベルのパフォーマンスを取り戻します。そして、これはMetaTrader 5のプログラムだけに当てはまる話ではありません。この考え方は、ソフトウェア開発全般に共通するものです。

それでは、本題に入りましょう。マウスイベントを受け取ることができるのは、OnChartEventハンドラだけです。したがって、マウスイベントを受信して処理できるアプリケーションは2種類しかありません。その一つがエキスパートアドバイザー(EA)です。これについては後ほど説明します。もう一つはインジケータです。しかし、ここで説明する内容はEAにもそのまま当てはまります。というのも、すべてのデータはOnChartEventハンドラに送られるからです。この点を理解していただきたいと思います。そうすれば、「インジケータでマウスを扱う方法は説明されていたのに、EAではどのようにおこなうのかは説明されていない」といった疑問を持つことはないでしょう。

それでは、以下に示すコードから説明を始めます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09. 
10.     return INIT_SUCCEEDED;
11. };
12. //+------------------------------------------------------------------+
13. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
14. {
15.     return rates_total;
16. };
17. //+------------------------------------------------------------------+
18. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
19. {
20.     switch (id)
21.     {
22.         case CHARTEVENT_MOUSE_MOVE:
23.             break;
24.         case CHARTEVENT_MOUSE_WHEEL:
25.             break;
26.     }
27. };
28. //+------------------------------------------------------------------+
29. void OnDeinit(const int reason)
30. {
31. };
32. //+------------------------------------------------------------------+

コード01

ここでは、マウスを利用するインジケータの基本的なフレームワークと呼べるものを示しています。ここで、次の点に注目してください。OnChartEventハンドラでは、2種類のマウスイベントを受け取ることができます。1つ目の例は22行目、2つ目の例は24行目です。どちらのイベントを使用しても、コードのコンパイルや実行には影響しません。しかし、現在の状態では、MetaTrader 5はこれら2種類のイベントのいずれも生成しません。これは、両方のイベントタイプがデフォルトで無効になっているためです。OnChartEventハンドラでこれらのイベントのいずれかを受け取るには、受け取りたいイベントの種類だけを明示的に有効化する必要があります。

これらのイベントの有効化と無効化は、実装自体は非常に簡単な処理です。ただし、このような状況への対処方法は、少し異なります。なぜかCHARTEVENT_MOUSE_WHEELイベントは、CHARTEVENT_MOUSE_MOVEイベントとはわずかに異なる動作をします。しかし、今はそのことを気にする必要はありません。すぐにその違いを理解できるようになり、さまざまなアプリケーションで、さまざまな方法でマウスを活用できるようになるでしょう。重要なのは、この仕組みは正しくプログラムすれば、特にグラフィカルなプラットフォーム上では、非常に有用で汎用性の高いツールになるということです。

マウスの動作を実際に観察し、それによって視覚化することで理解しやすくなるさまざまな事柄を確認するために、ここではマウスイベントの一つを有効化すると同時に、いくつかの情報をチャート上へ直接表示するようにします。このようにすることで、実際にどのように動作するのかを実践を通して学ぶことができます。そのために、コード01を修正し、以下に示すコード02に変更します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10. 
11.     return INIT_SUCCEEDED;
12. };
13. //+------------------------------------------------------------------+
14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
15. {
16.     return rates_total;
17. };
18. //+------------------------------------------------------------------+
19. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
20. {
21.     string sz = "";
22. 
23.     switch (id)
24.     {
25.         case CHARTEVENT_MOUSE_MOVE:
26.             Comment(sz);
27.             sz += "Mouse position X: " + (string)(short)lparam;
28.             sz += "\nMouse position Y: " + (string)(short)dparam;
29.             Comment(sz);
30.             break;
31.         case CHARTEVENT_MOUSE_WHEEL:
32.             break;
33.     }
34. };
35. //+------------------------------------------------------------------+
36. void OnDeinit(const int reason)
37. {
38.     Comment("");
39.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
40. };
41. //+------------------------------------------------------------------+

コード02

チャート上でコード02を実行すると、次のアニメーションに示すような結果が表示されます。

アニメーション01

マウスを動かすと、左上に表示されているマウス位置の表示が変化することに注目してください。しかし、位置表示が変化しない領域もあります。なぜでしょうか。位置表示が更新されない領域がクライアント領域の外側だからです。

そうです。チャートウィンドウは複数の領域に分かれています。このような概念は、Windowsアプリケーションの開発など、グラフィカルな環境向けのプログラミングを学ぶ際には一般的に扱われます。この分野には多くの細かな要素がありますが、ここではできる限り説明を簡潔にし、MQL5のプログラミングで本当に重要な部分に集中できるようにしたいと思います。

たとえばWindowsオペレーティングシステムのようなグラフィカルインターフェースには、「デスクトップ」と呼ばれる広大な領域があります。専門家でない方にとっては、「画面全体」と考えていただいて構いません。この領域の各ピクセルは、デスクトップ座標上の1つの位置に対応しています。このデスクトップ領域は、まず大きく2つの領域に分けることができます。1つはタスクバーが配置される領域で、もう1つはアプリケーションウィンドウが配置される領域です。現在の設定でタスクバーが自動的に隠れるようになっているかどうかは関係ありません。タスクバーによって占有されていない領域は、すべてアプリケーション領域として扱われます。

より分かりやすくするために、次の例を考えてみましょう。解像度が1920 × 1080ピクセル、つまりFull HDのディスプレイを使用しているとします。タスクバーの高さが48ピクセルである場合、実際に利用できる画面領域は1920 × 1032ピクセルになります。そのため、タスクバーを表示した状態でアプリケーションを最大化すると、そのアプリケーションが利用できるサイズは、先ほど述べたようにFull HDの画面全体よりも少し小さくなります。この違いはほとんど気付かない程度ですが、補足として、1920 × 1080ピクセルの画像を、それよりわずかに小さい表示領域で表示すると、不足している48ピクセル分を補う必要があるため、表示結果はわずかに異なります。

さて、このクライアント領域の中には、通常は第4象限として構成される座標系(この点については後ほど詳しく説明します)があり、その中にプログラムのウィンドウを配置できます。このウィンドウ自体も、さらに複数の領域に分かれています。実際には、考慮すべき主な領域は3つあります。1つ目はタイトルバー、2つ目はウィンドウ枠(フレーム)、そして3つ目がクライアント領域です。

デスクトップの場合と同様に、クライアント領域以外の部分はすべてアプリケーションが利用できる領域を減少させます。そのため、実際に利用可能な領域は小さくなります。マウス座標が報告されるのは、このクライアント領域内だけです。標準的なチャート表示モデルでは(これも第4象限に配置されています)、左上隅が(0, 0)、右下隅がその領域の最大座標となります。

したがって、マウスカーソルがMetaTrader 5のチャートウィンドウのクライアント領域から外れると、そのウィンドウに対するマウスイベントは生成されなくなります。実は、この動作を回避し、イベントハンドラを持つチャートウィンドウのクライアント領域の外へカーソルが移動した後でも、引き続きマウスイベントを受け取り続ける方法があります。しかし、それは別の話題になります。将来、その方法について取り上げる機会があるかもしれません。

ここまでの内容は、比較的理解しやすい部分です。ここで一つ付け加えておきたい点があります。クライアント領域が画面上のどこに配置されているかは問題になりません。MetaTrader 5は画面上の座標を継続的に再マッピングし、アプリケーションには、その座標があたかもアプリケーション自身の座標系の中に存在しているかのように渡します。言い換えれば、アプリケーションからは、あたかもメイン画面全体、つまりデスクトップ全体を使用しているかのように見えるのです。

MetaTrader 5がこの座標変換を自動的に行ってくれることで、プログラマーの作業量は大幅に軽減されます。MQL5以外のプログラミング環境であれば、このような処理を自分で実装しなければならない場合もあります。また、この仕組みによって、本来はイベントに応答すべきでないにもかかわらず、アプリケーションが誤ってイベントを受信したと判断してしまうような状況も防ぐことができます。

ここまでで、この点は理解できたと思います。では、マウスボタンについてはどうでしょうか。特定のボタンが押されたことは、どのように判定すればよいのでしょうか。マウスボタンの状態は、ビットマスクを用いて管理されます。これを扱う最も簡単な方法は、コード内でマクロを使用することです。この方法を使えば、さまざまなイベントの判定やフィルタリングを簡潔に記述できます。たとえば、ある要素をクリックしながら同時にドラッグするといった操作も、容易に処理できるようになります。

この話題に進む前に、もう一点確認しておきましょう。コード02の9行目では、マウスイベントを有効化しました。そして39行目では、それを無効化しています。また、イベントから渡される情報は異なるデータ形式で提供されるため、期待どおりの値として表示するには、適切な型へ変換する必要があります。そのため、27行目と28行目で見られるように、明示的な型キャストを使用しています。それほど難しい話ではありません。


マウスボタン

マウスボタンの扱いは、かなり議論の分かれるテーマです。そして、その理由もまた議論の対象になります。マウス関連の機能を実装するのは非常に簡単だと考える方もいるかもしれません。しかし実際には、物事は見た目ほど単純ではありません。一見すると、その理由は少し不思議に思えるかもしれません。ほとんどのアプリケーションでは、マウスボタンは何らかの機能を実行するために使用されています。あるいは、オペレーティングシステム自身がマウスボタンを利用している場合もあります。MetaTrader 5では、中央ボタンは分析に使用する標準のクロスヘアツールを呼び出すために割り当てられています。一方、右ボタンはチャートに対するさまざまな操作や設定をおこなうためのメニューを表示します。

しかし、私たちのアプリケーションでも、これらと同じボタンを別の、やはり特定の目的のために使用したい場合があります。そうなると、ユーザーが期待している操作と、MetaTrader 5のデフォルトの動作との間で競合が発生します。場合によっては、この問題にオペレーティングシステムまで関わってくることもあります。とはいえ、MetaTrader 5に関して言えば、私たちプログラマーが一定期間、特定の目的のためにこれらのボタンを一時的に占有することについては特に問題にしません。しかし、アプリケーションがチャートから削除された後に、それらのボタンを元の動作に戻さないのは、極めて非倫理的です。もちろん、アプリケーションが異常終了してしまった場合は別です。その場合は、元に戻すこと自体が不可能だからです。

つまり、私たちのアプリケーションは、借りたマウスボタンをそのまま「持ち去ってしまう」ことになります。その結果、そのチャート上でMetaTrader 5を使用している間、ユーザーはそのボタンを利用できなくなります。アプリケーションが異常終了したためにボタンが返却されなかった場合、そのボタンを再び利用できるようにするには、ユーザーはそのシンボルチャートを一度閉じて、もう一度開き直すしかありません。そうして初めて、MetaTrader 5はそれらのボタンに対する制御を取り戻すことができます。したがって、このような点は十分注意して設計しなければなりません。そうしなければ、私たちのアプリケーションを利用するユーザーにとって、非常に不快な体験を生み出してしまうことになります。

「なるほど。問題があることは分かりました。でも、実際にはどうすればよいのでしょうか。もし何らかの理由で右ボタンや中央ボタンを制御する必要がある場合は、どうすればよいのでしょう。また、それらのボタンの制御をMetaTrader 5に返すには、どのような方法を取るのがよいのでしょうか。」とても良い質問です。そして、この話題に関連して、もう一つ興味深いことを説明したいと思います。これまでキーボードイベントについて解説した記事では、実はキーボードをさらに高度に制御する方法については触れていませんでした。しかし今回は、マウスボタンをはじめとするさまざまなユーザー入力要素を制御する話になりますので、この機会に、ユーザー操作要素をどこまで制御できるかをお見せしたいと思います。これは以下のコードでおこなわれます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10. //+----------------+    
11.     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
12.     ChartSetInteger(0, CHART_CONTEXT_MENU, false);
13.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
14. //+----------------+
15.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false);
16.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, false);
17. //+----------------+
18. 
19.     return INIT_SUCCEEDED;
20. };
21. //+------------------------------------------------------------------+
22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
23. {
24.     return rates_total;
25. };
26. //+------------------------------------------------------------------+
27. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
28. {
29.     string sz = "";
30. 
31.     switch (id)
32.     {
33.         case CHARTEVENT_MOUSE_MOVE:
34.             Comment(sz);
35.             sz += "Mouse position X: " + (string)(short)lparam;
36.             sz += "\nMouse position Y: " + (string)(short)dparam;
37.             Comment(sz);
38.             break;
39.         case CHARTEVENT_MOUSE_WHEEL:
40.             break;
41.     }
42. };
43. //+------------------------------------------------------------------+
44. void OnDeinit(const int reason)
45. {
46.     Comment("");
47.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
48. //+----------------+    
49.     ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
50.     ChartSetInteger(0, CHART_CONTEXT_MENU, true);
51.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
52. //+----------------+
53.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true);
54.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, true);
55. //+----------------+
56. };
57. //+------------------------------------------------------------------+

コード03

コード03を実行すると、MetaTrader 5の標準的な要素や操作が使用できなくなります。たとえば、標準のクロスヘアツールや、ENTERキーを押して現在のチャートのシンボルを変更する機能などが無効になります。それでは、どのようにこれを実現しているのか見ていきましょう。まず、11行目では、ユーザーがチャートをドラッグして現在表示されていないバーを表示する機能を無効にしています。このMetaTrader 5の標準動作を再び有効にするには、49行目を使用します。

12行目では、コードが実行されているチャートのコンテキストメニューを無効にしています。このメニューは、マウスの右ボタンを押すことで表示されます。このMetaTrader 5の標準機能を再び有効にするには、50行目を使用します。13行目では、マウスの中央ボタンを押すことで呼び出される標準のクロスヘアツールを無効にしています。この機能を再び有効にするには、51行目を使用します。

続いて、15行目では、キーボードによるスクロールおよびズームに関する操作を無効にしています。これらを再び有効にするには、53行目を使用します。そして最後に、16行目では、ENTERキーを押してシンボル名や時間足を入力する機能を無効にしています。この機能を再び有効にするには、54行目を使用します。

この種のコードは、実際に使用してみることで理解しやすくなります。コード03を実行する前と実行中でチャートの動作を比較しながら試してみてください。添付ファイルにはここで示したコードが含まれていますが、このようなイベントの扱い方を学ぶには、これ以上簡単な方法はないと思います。実際に試し、その結果を観察して初めて、細かな問題をどのように解決すればよいのかを理解できるようになります。こう言うのは、これらの機能はいつでも有効化したり無効化したりできるからです。状況によっては、あるタイミングでは有効にし、別のタイミングでは無効にする、といった使い方も有効でしょう。いずれにしても、ここでは教育的な目的から、これらの機能をできるだけ分かりやすく示しているだけです。

それでは、より広いテーマであるマウスボタンに話を進めましょう。ただし今回は、それに加えてキーボードも扱います。そうすることで、システム全体をより包括的にテストできるようになります。これは以下のコードでおこなわれます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10. //+----------------+    
11.     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
12.     ChartSetInteger(0, CHART_CONTEXT_MENU, false);
13.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
14. //+----------------+
15.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false);
16.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, false);
17. //+----------------+
18. 
19.     return INIT_SUCCEEDED;
20. };
21. //+------------------------------------------------------------------+
22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
23. {
24.     return rates_total;
25. };
26. //+------------------------------------------------------------------+
27. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
28. {
29.     string sz = "";
30. 
31.     switch (id)
32.     {
33.         case CHARTEVENT_KEYDOWN:
34.             Comment(sz);
35.             sz += "Key press: " + (string)lparam;
36.             Comment(sz);
37.             break;
38.         case CHARTEVENT_MOUSE_MOVE:
39.             Comment(sz);
40.             sz += "Mouse position X: " + (string)(short)lparam;
41.             sz += "\nMouse position Y: " + (string)(short)dparam;
42.             sz += StringFormat("\nHexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam);
43.             Comment(sz);
44.             break;
45.         case CHARTEVENT_MOUSE_WHEEL:
46.             break;
47.     }
48. };
49. //+------------------------------------------------------------------+
50. void OnDeinit(const int reason)
51. {
52.     Comment("");
53.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
54. //+----------------+    
55.     ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
56.     ChartSetInteger(0, CHART_CONTEXT_MENU, true);
57.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
58. //+----------------+
59.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true);
60.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, true);
61. //+----------------+
62. };
63. //+------------------------------------------------------------------+

コード04

コード04は、コード03と同様、多くの初心者にとって少々手強い内容かもしれません。そのため、まずはこのコードを停止する方法、つまりチャートから削除する方法を見ていきましょう。おそらく、ほかのインジケータで普段おこなっている方法では削除できません。これらのMetaTrader 5の標準機能を完全に無効化するこのコードを停止するには、次の画像に示されている項目にアクセスする必要があります。

図01

MetaTrader 5で、標準操作を全面的に無効化するこのインジケータを使用している状態で、図01に示されている項目を選択すると、この一覧にアクセスできます。対象がEAである場合は、EAの一覧を開く必要があります。その後、チャートに表示されている、あるいはプラットフォーム上で実行中のインジケータまたはEAの一覧にアクセスし、そこから対象をチャートから削除します。この方法でなければ削除することはできません。このコードがMetaTrader 5の標準機能へのユーザーアクセスを完全に遮断しているためです。

それでは、この点を理解したところで、コード04が実際に何をおこなうのか見ていきましょう。

アニメーション02

ここで、ぜひ注目していただきたい点があります。アニメーション02の目的は、MQL5がマウスやキーボードの操作をどのように解釈するかを示すことだけです。コード04のインジケータは、必ず実際に動かして試してみてください。マウスボタンとキーボードを組み合わせることで何ができるのかを理解するためには、それが非常に重要です。中には、一見すると単純そうでも、言葉だけでは説明しにくいことが少なくありません。実際に試し、それらの要素をどのように組み合わせられるのかを理解するのが最も効果的です。

マウスボタンとキーボードの組み合わせを観察すると、コード04の42行目では、表示される結果が状況によって変化することに気付くでしょう。同じコードであるにもかかわらず、あるときは一つの結果が表示され、別のときは異なる結果が表示されます。一見すると不思議に思えるかもしれません。しかし、これはSHIFTキーやCTRLキーが押されているときに発生します。この2つのキーには興味深い特徴があります。これらのキーを押したままマウスを動かさなければ、35行目の結果が表示されます。

ところが、その状態でマウスを動かすと、MetaTrader 5は42行目を実行するイベントを発生させます。なぜでしょうか。SHIFTキーとCTRLキーはマウスと関連付けられており、ビットマスクの一部として扱われているからです。このことを理解すれば、そうでなければはるかに作るのが難しいものを作り出すことができるようになるでしょう。ですから、これは非常に重要な点です。MQL5におけるマウスとキーボードの動作を本当に理解したいのであれば、コード04が示す結果を実際に確認しながら学習する必要があります。

さて、まだ説明していないイベントがもう一つあります。それは、コード04の45行目で扱っているイベントです。これはマウスホイールのスクロールイベントですが、率直に言って、このイベントは少々変わっています。少なくともMQL5では、そのように感じられます。このイベントは発生すると、一般に想像されるものとは少し異なるデータ構造を返します。そのためか、MQL5で作成されたアプリケーションでは、あまり利用されていません。

この違いを理解するために、イベントハンドラーへ必要なコードを追加し、CHARTEVENT_MOUSE_MOVEとCHARTEVENT_MOUSE_WHEELの違いを視覚的に確認できるようにします。実際に試してみると、本当に興味深い動作を確認できます。そのために使用するコードを、以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10.     ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);
11. //+----------------+    
12.     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
13.     ChartSetInteger(0, CHART_CONTEXT_MENU, false);
14.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
15. //+----------------+
16.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false);
17.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, false);
18. //+----------------+
19. 
20.     return INIT_SUCCEEDED;
21. };
22. //+------------------------------------------------------------------+
23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
24. {
25.     return rates_total;
26. };
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     string sz = "";
31. 
32.     switch (id)
33.     {
34.         case CHARTEVENT_KEYDOWN:
35.             Comment(sz);
36.             sz += "Key press: " + (string)lparam;
37.             Comment(sz);
38.             break;
39.         case CHARTEVENT_MOUSE_MOVE:
40.             Comment(sz);
41.             sz += " **** Chart Event Mouse Move ****";
42.             sz += "\nMouse position X: " + (string)(short)lparam;
43.             sz += "\nMouse position Y: " + (string)(short)dparam;
44.             sz += StringFormat("\nHexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam);
45.             Comment(sz);
46.             break;
47.         case CHARTEVENT_MOUSE_WHEEL:
48.             Comment(sz);
49.             sz += " **** Chart Event Mouse Wheel ****";
50.             sz += "\nMouse position X: " + (string)(short)lparam;
51.             sz += "\nMouse position Y: " + (string)(short)(lparam >> 16);
52.             sz += "\nMouse Delta: " + (string) dparam;
53.             sz += StringFormat("\nHexadecimal mask of mouse buttons is: 0x%02X", (uchar)(lparam >> 32));
54.             Comment(sz);
55.             break;
56.     }
57. };
58. //+------------------------------------------------------------------+
59. void OnDeinit(const int reason)
60. {
61.     Comment("");
62.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
63.     ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, false);
64. //+----------------+    
65.     ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
66.     ChartSetInteger(0, CHART_CONTEXT_MENU, true);
67.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
68. //+----------------+
69.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true);
70.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, true);
71. //+----------------+
72. };
73. //+------------------------------------------------------------------+

コード05

コード05によって、この資料でお見せしたかった内容はひととおり完了です。ここで注目していただきたいのは、このコードではマウスやキーボードを操作するための特別なテクニックは一切使用していないということです。何が起きているのかを容易に理解できるよう、できるだけ明快でシンプルな方法を採用しています。コード05を実行すると、次のアニメーションのような動作を確認できます。

アニメーション03

コード04とコード05の違いは、ほとんどないことが分かるでしょう。しかし、このわずかな違いによって、両者の目的はまったく異なるものになっています。コード05の10行目では、ユーザーによるマウスホイールの回転を受け取るためのイベントを有効にしています。同様に、63行目では、そのイベントの受信を無効にしています。しかし、ここで特に注目していただきたいのは、イベントハンドラーで処理している内容です。ここに本質的な違いがあります。

CHARTEVENT_MOUSE_WHEELのハンドラは、CHARTEVENT_MOUSE_MOVEのハンドラと非常によく似ています。それぞれの値がどこから渡されてくるのかに注目してください。マウス位置を表す値や、押されているボタンを表す値が格納される場所は、この2つのイベントでは異なります。しかし、本当の違いはそこではありません。CHARTEVENT_MOUSE_WHEELイベントがハンドラに渡されるのはマウスホイールのスクロール中に何らかのい便っとが発生した場合に限られます。それ以外のすべてのマウスイベントは、CHARTEVENT_MOUSE_MOVEイベントとして処理されます。このため、CHARTEVENT_MOUSE_WHEELを利用したアプリケーションやコードを目にする機会は非常に少ないのです。つまり、マウスホイールに関するイベントが発生しない限り、マウスの移動やボタンの押下といった、その他のマウス状態に関する更新はCHARTEVENT_MOUSE_WHEELでは受け取ることができません。

これまで述べてきたように、マウスは非常に頼もしい味方です。しかし、実装したい内容によっては、まさに地獄の底から現れたような厄介な存在にもなり得ます。


最後に

ここまで読まれた方はお気付きかもしれませんが、本記事は、コードを眺めているだけでは内容を十分に理解できない種類の記事の一つです。実際に実行可能なアプリケーションを作成し、任意のチャート上で動かしてみることが不可欠です。そうすることで、キーボードとマウスを組み合わせて特定の要素を作成する方法など、コードを読むだけでは非常に理解しにくい細かな点まで把握できるようになります。

このような組み合わせがどのように成立するのかを理解すれば、SHIFTキーやCTRLキーが押されているかどうかを確認するために、必ずしもCHARTEVENT_KEYDOWNハンドラーを有効にする必要がないコードが存在する理由も理解できるでしょう。さらに、マウスボタン同士の組み合わせを利用して、より複雑で高度にカスタマイズされたツールを作成することも可能になります。

添付ファイルには、本記事で取り上げた各コードが収録されています。ぜひ、それぞれのコードを実際にチャート上で動かし、その結果を確認してみてください。また、マウスの移動やボタン操作、キーボード入力をさまざまに組み合わせて試してみることをお勧めします。理論だけでは退屈に思える内容でも、実際に試してみることで、その仕組みが非常に興味深く、そして意外なほど分かりやすいものであることに気付くはずです。

MQL5ファイル 説明
コード01 マウスデモ


MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/16000

添付されたファイル |
Code_01.mq5 (2.88 KB)
初級から中級まで:オブジェクト(III) 初級から中級まで:オブジェクト(III)
本日の記事では、特にMQL5でプログラミングを学び始めたばかりの方に向けて、非常に魅力的で興味深いインタラクションシステムの実装方法を紹介します。ここで取り上げる内容自体に、根本的に新しいものがあるわけではありません。しかし、本記事では私なりのアプローチで解説を進めるため、内容をより理解しやすく感じていただけると思います。実践的で興味を持って取り組める題材を通して、構造化アプローチを用いたプログラム開発の流れを実際に確認していきます。
ニューラルネットワークの実践:習うより慣れよ ニューラルネットワークの実践:習うより慣れよ
ニューロンをわずかに特化させる単純なコード変更によって、学習段階を大幅に高速化できる方法について見ていきます。結局のところ、後ほど詳しく説明するように、ニューロンやニューラルネットワークが一度学習を完了すると、その後に実行する処理ははるかに高速になります。また、実際には存在しているにもかかわらず、ほとんど語られることのない問題についても取り上げます。
初級から中級まで:オブジェクト(IV) 初級から中級まで:オブジェクト(IV)
これは、これまでの記事の中でも、おそらくいちばん楽しい内容でしょう。なぜなら、ここではMetaTrader 5にすでに存在しているオブジェクトを変更し、本来プラットフォーム上には存在しない別のオブジェクトを作成するからです。もちろん、これから見ていく内容は少し無謀に思えるかもしれません。しかし、実際に動作し、非常に興味深い用途に役立ちます。
取引におけるニューラルネットワーク:特定データへの依存なしに時系列を汎化する(Mamba4Cast) 取引におけるニューラルネットワーク:特定データへの依存なしに時系列を汎化する(Mamba4Cast)
Mamba4Castフレームワークを紹介するとともに、その主要コンポーネントの1つであるタイムスタンプに基づく位置エンコーディングについて詳しく解説します。データのカレンダー構造を考慮して時間埋め込みがどのように生成されるのかを示します。