English Deutsch
preview
MQL5で取引管理者パネルを作成する(第9回):コード編成(III)コミュニケーションモジュール

MQL5で取引管理者パネルを作成する(第9回):コード編成(III)コミュニケーションモジュール

MetaTrader 5 | 27 6月 2025, 07:40
33 0
Clemence Benjamin
Clemence Benjamin

内容


はじめに

今回は、前回の記事で導入したモジュール化という、より広範なコード構成の重要な要素を基に、新しい管理パネルをさらに拡張していきます。前回は、管理者ホームインターフェイスの構築を担うAdminHomeDialogクラスを紹介しました。このホームパネルは、各種機能へのアクセスを集約する中心的なハブとして機能し、以下の3つの主要コンポーネントパネルへとつながるアクセスコントロールボタンで構成されています。

  • 取引管理パネル 
  • コミュニケーションパネル
  • 分析パネル

これらはシステムの最終形ではなく、既存の基盤を改善・拡張し続けることで、新たな機能を追加することも可能です。今回はその中でも、モジュールとしてのコミュニケーションパネルに焦点を当て、従来のモノリシックな管理パネル構成よりも、さらに進化した形で強化していきます。

このディスカッションでの主なポイントは以下のとおりです。

  • MQL5におけるクラスの理解
  • ヘッダーファイルの開発
  • 組み込みクラスからの継承
  • ListViewヘッダーファイルの活用
  • UI設計における色の適用
前回の記事を振り返ると、モジュール化とは、アプリケーションを独立した単位(モジュール)に分割することであり、それぞれが個別のファイルで定義されます。これにより、各モジュールは独立して開発・保守できるようになります。このプロジェクトでは、メインプログラムは.mq5ファイル(New_Admin_Panel.mq5)であり、ダイアログは.mqhヘッダーファイル(AdminHomeDialog.mqhおよびCommunicationsDialog.mqh)内でクラスとして定義されています。構成としては、メインプログラムがAdminHomeDialogのインスタンスを生成し、そこからCommunicationsDialogや将来的に追加されるダイアログのインスタンスを生成することができます。下の図を参照してください。

モジュール(New_Admin_Panel)フロー

新しい管理パネルのモジュールフロー

1. メインプログラム(New_Admin_Panel.mq5)

  • このプログラムはアプリケーションのエントリーポイントです。
  • システムを初期化し、AdminHomeDialogのインスタンスを生成します。これがメインのユーザーインターフェイスとなります。

2. AdminHomeDialog

  • ユーザーとのやり取りの中心となるハブとして機能します。
  • ボタンクリックなどのイベントに応じて、CommunicationsDialogなどの他のダイアログのインスタンスを生成します。
  • 将来的に必要に応じて追加のダイアログ(例:FutureDialog)を生成できるように設計されています。

3. CommunicationsDialog

  • 特定の機能(例:Telegram APIを介したメッセージ送信)を担当する専用のダイアログです。
  • ユーザーの操作(例:[メッセージ送信]ボタンのクリック)によって、AdminHomeDialogから動的に生成されます。

4. イベント駆動型インタラクション

  • このシステムでは、MQL5アプリケーションで一般的なイベント駆動型のアプローチを採用しています。たとえば、AdminHomeDialogでのボタンクリックがCommunicationsDialogの生成と表示をトリガーし、その後ダイアログがタスク(例:Telegramとの通信)を実行します。

5. モジュラー設計

  • 各コンポーネントは明確な役割を持っています。メインプログラムは初期化をおこない、AdminHomeDialogはインターフェースを管理し、CommunicationsDialogは通信タスクを処理します。このようなモジュール化により、機能の拡張や修正が容易になります。

このようにして新しいプログラムの概要を確認したうえで、次はいよいよ先駆的なモジュールであるCommunicationsDialogの開発に踏み込んでいきます。(I)では、これはまだ基本的な通信インターフェイスにすぎませんでしたが、今回は新たな機能を取り入れることで、その役割をさらに拡張しました。この基礎的な知識をもとに、今後の開発の方向性をより明確に描くことができます。次のサブセクションでは、カスタムクラスを可能にするための基本的な構成要素を詳しく見ていきます。


CommunicationsDialogクラスの開発

理解を深めるために、以下にベースクラスからカスタムクラスへのフロー階層を示した図を掲載しています。このアプローチはコードの再利用性を高める強力な手法です。本プロジェクトでは、多くのパネルコンポーネントが存在しており、それぞれが異なる目的を担っています。このような独自性により、各コードコンポーネントは他のプロジェクトへの統合にも適応可能です。

MQL5において、Dialogクラスは通常(Dialog.mqh)インクルードファイル内のCAppDialogまたはCDialogを指します。これらは標準ライブラリにおける基本的なダイアログクラスとして機能します。

CommunicationsDialogCAdminHomeDialogははどちらもCAppDialogを継承しており、複数のダイアログクラスが共通のベースから機能を共有する構造的な階層を形成しています。この構造は、以下の階層フローチャートで示されます。

ダイアログからカスタムクラスを構築する

基本クラスとカスタムクラスの関係

作業を始めるには、デスクトップからMetaEditor 5を開くか、ターミナルでF4キーを押して起動します。

ナビゲータで、Includesフォルダ内にあるDialog.mqhを見つけて、参照用に開きます。 

ガイドとして、以下の画像を参考にしてください。

Dialog.mqhを見つける

MetaEditor 5でDialogクラスを見つける

次に、新しいクラスを開発するためのファイルを作成します。 

通常、私たちのプログラムはヘッダーの設定、レイアウトや色の定義、クラス宣言、メソッド実装といったセクションで構成されています。ここでは、ファイルの基本部分から始めていきます。ファイルの冒頭にあるヘッダーコメントは、このファイルが何であるか(CommunicationsDialog.mqh)、誰が所有しているか(MetaQuotes Ltd.※ただし自分の名前に変更しても構いません)、そしてどこから来たのか(MQL5コミュニティ)を示しています。これらのコメントは、コードのタイトルページのようなもので、他の人がその目的をすぐに理解できるようにします。

次に、#ifndef#defineを使ってCOMMUNICATIONS_DIALOG_MQHという「ガード」を作成します。これは、同じファイルがプロジェクト内で複数回インクルードされるのを防ぐためのものです。もし何度も開こうとするとエラーの原因になるため、「すでに開かれているなら、もう開かないで」とロックをかけておくようなものです。

その後の#include行では、MQL5ライブラリから必要なツールを読み込みます。Dialog.mqhは基本となるダイアログクラス(CAppDialog)を提供し、Button.mqhとEdit.mqhはボタンとテキストボックスのクラスを提供します。Label.mqhとListView.mqhは、ラベルとクイックメッセージ用のリストを追加します。最後に、Telegram.mqhはTelegramメッセージ処理を行うカスタムファイル(存在していると仮定)です。これらはすべて、ダイアログを構築するためにツールボックスから取り出してくる道具のようなものです。以下はこのセクションのコードスニペットです。

セクション1:ファイルヘッダーとインクルード

//+------------------------------------------------------------------+
//|                                         CommunicationsDialog.mqh |
//|                             Copyright 2000-2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#ifndef COMMUNICATIONS_DIALOG_MQH
#define COMMUNICATIONS_DIALOG_MQH

#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Label.mqh>
#include <Controls\ListView.mqh>
#include "Telegram.mqh"

セクション2:レイアウトと色の定義

ダイアログを構築する前に、そのサイズと見た目を計画する必要があります。#define文は、寸法や色を設定する設計図のようなものです。レイアウトに関しては、COMMS_PANEL_WIDTH(300ピクセル)とCOMMS_PANEL_HEIGHT(350ピクセル)が画面上でのダイアログの大きさを決めます。COMMS_MARGIN_LEFT、COMMS_MARGIN_TOP、COMMS_MARGIN_RIGHTは端の余白を設定し、COMMS_GAP_VERTICALは各要素の縦方向の間隔を決めます。各コントロールには個別のサイズが設定されています。たとえば、入力ボックスは高さがあり(COMMS_INPUT_HEIGHT)、ボタンは小さな長方形(COMMS_BUTTON_WIDTHとCOMMS_BUTTON_HEIGHT)になっています。リストビューやラベルにもそれぞれ独自の寸法があります。

色は、ダイアログを見やすく、美しく整えるために使われます。色指定には16進数(たとえば、0x808080はダークグレー)を使います。これは、MQL5でコンピュータが色を認識する標準的な方法です。CLR_PANEL_BGはダイアログ全体の背景色、CLR_CLIENT_BGは各コントロールが配置される領域の背景色を指定します。CLR_CAPTION_BGとCLR_CAPTION_TEXTはタイトルバーの背景と文字色を設定します。境界線にはCLR_BORDER_BGとCLR_BORDERが使われます。さらに、入力ボックスにはCLR_INPUT_BGとCLR_INPUT_TEXT、ボタンにはCLR_SEND_BGやCLR_CLEAR_BGといった個別の色が設定されます。各#defineの横にはコメントが付いており、それぞれの設定が何を意味するかを説明しています。これにより、後から調整したいときにも簡単に変更できます。

// **Layout Defines**
#define COMMS_PANEL_WIDTH       300
#define COMMS_PANEL_HEIGHT      350
#define COMMS_MARGIN_LEFT       10
#define COMMS_MARGIN_TOP        10
#define COMMS_MARGIN_RIGHT      10
#define COMMS_GAP_VERTICAL      10
#define COMMS_INPUT_HEIGHT      30
#define COMMS_BUTTON_WIDTH      80
#define COMMS_BUTTON_HEIGHT     30
#define COMMS_LISTVIEW_WIDTH    280
#define COMMS_LISTVIEW_HEIGHT   80
#define COMMS_LABEL_HEIGHT      20

// **Color Defines (Hexadecimal Values)**
#define CLR_PANEL_BG      0x808080  // Dark Gray (Dialog background)
#define CLR_CLIENT_BG     0xD3D3D3  // Light Gray (Client area background)
#define CLR_CAPTION_BG    0x404040  // Darker Gray (Caption background)
#define CLR_CAPTION_TEXT  0xFFFFFF  // White (Caption text)
#define CLR_BORDER_BG     0xFFFFFF  // White (Border background)
#define CLR_BORDER        0xA9A9A9  // Gray (Border color)
#define CLR_INPUT_BG      0xFFFFFF  // White (Input box background)
#define CLR_INPUT_TEXT    0x000000  // Black (Input box text)
#define CLR_SEND_BG       0x00FF00  // Lime Green (Send button background)
#define CLR_CLEAR_BG      0xF08080  // Light Coral (Clear button background)
#define CLR_BUTTON_TEXT   0x000000  // Black (Button text)
#define CLR_LABEL_TEXT    0xFFFFFF  // White (Label text)
#define CLR_LIST_BG       0xFFFFFF  // White (List view background)
#define CLR_LIST_TEXT     0x000000  // Black (List view text)

セクション3:クラス宣言

ここでは、ダイアログの中心となるCCommunicationDialogクラスを定義します。クラスは、ダイアログオブジェクトを作るためのレシピのようなものです。「public CAppDialog」と書くのは、MQL5の既製ダイアログクラスCAppDialogを基にしてこのクラスを構築しているからです。これにより、タイトルバーや枠線といった基本的なダイアログ機能が使えるようになります。

privateセクションには、このダイアログの中で使う「材料」を列挙します。m_inputBoxはユーザーがメッセージを入力するためのテキストフィールド、m_sendButtonm_clearButtonはそれぞれメッセージの送信と消去を行うボタンです。m_quickMsgLabelはテキストラベルで、m_quickMessageListはあらかじめ設定されたメッセージのリストです。また、Telegram用にm_chatIdm_botTokenという文字列も保持し、m_quickMessagesという配列には、8つのクイックメッセージが格納されます。

publicセクションでは、誰でもこのダイアログとやり取りできる関数を定義します。コンストラクタ(CCommunicationDialog)は、チャットIDとボットトークンを使ってダイアログを初期化し、デストラクタ(~CCommunicationDialog)は使用が終わったときにクリーンアップをおこないます。Createはチャート上にダイアログを構築し、OnEventはクリックや操作イベントを処理し、Toggleはダイアログの表示・非表示を切り替えます。

再びprivateセクションに戻ると、各コントロールを作成する補助関数(CreateInputBoxなど)や、ボタンがクリックされたときに何をするかを定めるイベントハンドラ(OnClickSend、OnClickClear)を定義しています。これらはダイアログ内部の動作に関わるものであるため、privateにして外部からは見えないようにします。

//+------------------------------------------------------------------+
//| Class CCommunicationDialog                                       |
//| Purpose: A dialog for sending Telegram messages with controls    |
//+------------------------------------------------------------------+
class CCommunicationDialog : public CAppDialog
{
private:
   CEdit         m_inputBox;           // Field to edit/send message
   CButton       m_sendButton;         // Send message button
   CButton       m_clearButton;        // Clear edit box button
   CLabel        m_quickMsgLabel;      // Label for "QuickMessages"
   CListView     m_quickMessageList;   // ListView for quick messages
   string        m_chatId;             // Telegram chat ID
   string        m_botToken;           // Telegram bot token
   string        m_quickMessages[8];   // Array of quick messages

public:
   CCommunicationDialog(const string chatId, const string botToken);
   ~CCommunicationDialog();
   
   virtual bool Create(const long chart, const string name, const int subwin,
                       const int x1, const int y1, const int x2, const int y2);
   virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   void Toggle();                      // Toggle dialog visibility

private:
   //--- Create dependent controls
   bool CreateInputBox(void);
   bool CreateClearButton(void);
   bool CreateSendButton(void);
   bool CreateQuickMsgLabel(void);
   bool CreateQuickMessageList(void);

   //--- Handlers of dependent controls events
   void OnClickSend(void);      // Handler for Send button
   void OnClickClear(void);     // Handler for Clear button
};

セクション4:コンストラクタとデストラクタ

コンストラクタは、新しいおもちゃを遊ぶ前に準備するようなものです。誰かがCCommunicationDialogを作成するときは、chatIdとbotToken(Telegram用の文字列)を渡さなければなりません。m_chatId(chatId)やm_botToken(botToken)の部分では、これらの値をプライベート変数にコピーします。これによって、ダイアログがどこにメッセージを送ればいいのかを把握できるようになります。中括弧内では、では、m_quickMessages配列に8つの便利な定型文をあらかじめ入れておきます。これはダイアログが作られたときに実行されるため、すぐに使える状態になります。

デストラクタは後片付け係のようなものです。ダイアログが削除されるとき(たとえばプログラムを閉じたとき)に実行されます。現在は中身が空ですが、これはCAppDialogがほとんどの後処理をしてくれるためです。今は何も片付けるものがありませんが、将来的にメモリ解放などの特別な処理が必要になったときのための予備として書かれています。

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCommunicationDialog::CCommunicationDialog(const string chatId, const string botToken)
   : m_chatId(chatId), m_botToken(botToken)
{
   // Initialize quick messages
   m_quickMessages[0] = "Updates";
   m_quickMessages[1] = "Close all";
   m_quickMessages[2] = "In deep profits";
   m_quickMessages[3] = "Hold position";
   m_quickMessages[4] = "Swing Entry";
   m_quickMessages[5] = "Scalp Entry";
   m_quickMessages[6] = "Book profit";
   m_quickMessages[7] = "Invalid Signal";
}

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CCommunicationDialog::~CCommunicationDialog()
{
}

セクション5:Createメソッド

Createメソッドは、ダイアログを画面上に構築する場所です。部品からおもちゃを組み立てるようなものです。chart(どのチャートに表示するか)、name(一意のID)、subwin(どのサブウィンドウに表示するか)、x1、y1、x2、y2(位置とサイズの座標)などの入力を受け取ります。最初に、CAppDialog::Createを呼び出して基本的なダイアログ構造をセットアップします。もしこれが失敗したら、何か問題が起きたことを意味してfalseを返します。

次に、Caption("Communications Panel")でダイアログの上部に表示されるタイトルを設定します。それから、色を設定します。ObjectSetIntegerは、ダイアログの各部分(たとえばBackは背景、Clientはコントロール部分)に対して色を直接指定するために使われます。ここで定義済みの色(たとえばCLR_PANEL_BGなど)を使って見た目を整えます。if(!m_panel_flag)のチェックでは、m_panel_flagがfalseである場合にのみ枠線を追加します。これはCAppDialogから継承された変数で、ダイアログが特別なタイプかどうかを判断するために使われます。

その後、補助関数を使って各コントロール(入力ボックス、ボタンなど)を作成します。どれか一つでも失敗したら、そこで処理を止めてfalseを返します。最後に、最初のクイックメッセージを入力ボックスにセットし(m_inputBox.Text)、ChartRedraw()を呼び出してすべてをチャートに表示させます。trueを返すことで、正常に完了したことを示します。

//+------------------------------------------------------------------+
//| Create Method                                                    |
//| Initializes the dialog and its controls with full color styling  |
//+------------------------------------------------------------------+
bool CCommunicationDialog::Create(const long chart, const string name, const int subwin,
                                  const int x1, const int y1, const int x2, const int y2)
{
   // Create the base dialog
   if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2))
      return(false);
   
   Caption("Communications Panel"); // Set the title
   
   // Set dialog background color
   ObjectSetInteger(m_chart_id, m_name + "Back", OBJPROP_BGCOLOR, CLR_PANEL_BG);
   
   // Set client area background color
   ObjectSetInteger(m_chart_id, m_name + "Client", OBJPROP_BGCOLOR, CLR_CLIENT_BG);
   
   // Set caption colors
   ObjectSetInteger(m_chart_id, m_name + "Caption", OBJPROP_BGCOLOR, CLR_CAPTION_BG);
   ObjectSetInteger(m_chart_id, m_name + "Caption", OBJPROP_COLOR, CLR_CAPTION_TEXT);
   
   // Set border colors (if border exists, i.e., m_panel_flag is false)
   if(!m_panel_flag)
   {
      ObjectSetInteger(m_chart_id, m_name + "Border", OBJPROP_BGCOLOR, CLR_BORDER_BG);
      ObjectSetInteger(m_chart_id, m_name + "Border", OBJPROP_BORDER_COLOR, CLR_BORDER);
   }
   
   // Create all controls
   if(!CreateInputBox())
      return(false);
   if(!CreateClearButton())
      return(false);
   if(!CreateSendButton())
      return(false);
   if(!CreateQuickMsgLabel())
      return(false);
   if(!CreateQuickMessageList())
      return(false);
   
   // Set initial text in input box
   m_inputBox.Text(m_quickMessages[0]);
   ChartRedraw();
   return(true);
}

セクション6:コントロール作成メソッド

これらのメソッドは、ダイアログの各パーツを組み立てる作業のようなものです。それぞれがコントロール(入力ボックス、ボタン、ラベル、リスト)を作成し、ダイアログ上に配置します。すべてのメソッドは成功下場合はtrueを返し、何か失敗すればfalseを返すので、問題があればその時点で処理を中断できます。

CreateInputBoxでは、レイアウト定義(例:COMMS_MARGIN_LEFT)を使って座標を計算します。入力ボックスは横幅が広く(ClientAreaWidth()を使用)、高さはCOMMS_INPUT_HEIGHTの3倍になります。m_inputBox.Createでは、チャートID、ユニークな名前(m_name + "_InputBox")、座標を指定します。Addでダイアログに配置し、ObjectSetIntegerで色を設定します。

CreateClearButtonとCreateSendButtonは、入力ボックスの下にボタンを作成します。ボタン同士はCOMMS_GAP_VERTICALで縦に間隔を空けて配置し、SendボタンはClearの横に配置するためにclear_button_x2を使って位置を決めます。それぞれに名前とラベル(ClearまたはSend")、そして定義済みの色を設定します。

CreateQuickMsgLabelは、ボタンの下にテキストラベルを追加します。ここでも間隔の計算をおこない、表示するのはテキストだけなので必要なのは文字色だけです。CreateQuickMessageListでは、さらに下にリストビューを作成し、コンストラクタで定義したクイックメッセージを一覧として表示します。すべてのコントロールは共通のパターンで処理されます。作成、追加、色設定、そしてエラーがあった場合にはPrintメッセージでデバッグを助けるようになっています。

//+------------------------------------------------------------------+
//| CreateInputBox                                                   |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateInputBox(void)
{
   int x1 = COMMS_MARGIN_LEFT;
   int y1 = COMMS_MARGIN_TOP;
   int x2 = ClientAreaWidth() - COMMS_MARGIN_RIGHT;
   int y2 = y1 + 3 * COMMS_INPUT_HEIGHT;

   if(!m_inputBox.Create(m_chart_id, m_name + "_InputBox", m_subwin, x1, y1, x2, y2))
   {
      Print("Failed to create InputBox");
      return(false);
   }
   if(!Add(m_inputBox))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_InputBox", OBJPROP_BGCOLOR, CLR_INPUT_BG);
   ObjectSetInteger(m_chart_id, m_name + "_InputBox", OBJPROP_COLOR, CLR_INPUT_TEXT);
   return(true);
}

//+------------------------------------------------------------------+
//| CreateClearButton                                                |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateClearButton(void)
{
   int button_y1 = COMMS_MARGIN_TOP + 3 * COMMS_INPUT_HEIGHT + COMMS_GAP_VERTICAL;
   int x1 = COMMS_MARGIN_LEFT;
   int x2 = x1 + COMMS_BUTTON_WIDTH;
   int y2 = button_y1 + COMMS_BUTTON_HEIGHT;

   if(!m_clearButton.Create(m_chart_id, m_name + "_ClearButton", m_subwin, x1, button_y1, x2, y2))
   {
      Print("Failed to create ClearButton");
      return(false);
   }
   m_clearButton.Text("Clear");
   if(!Add(m_clearButton))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_ClearButton", OBJPROP_BGCOLOR, CLR_CLEAR_BG);
   ObjectSetInteger(m_chart_id, m_name + "_ClearButton", OBJPROP_COLOR, CLR_BUTTON_TEXT);
   return(true);
}

//+------------------------------------------------------------------+
//| CreateSendButton                                                 |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateSendButton(void)
{
   int button_y1 = COMMS_MARGIN_TOP + 3 * COMMS_INPUT_HEIGHT + COMMS_GAP_VERTICAL;
   int clear_button_x2 = COMMS_MARGIN_LEFT + COMMS_BUTTON_WIDTH;
   int x1 = clear_button_x2 + COMMS_GAP_VERTICAL;
   int x2 = x1 + COMMS_BUTTON_WIDTH;
   int y2 = button_y1 + COMMS_BUTTON_HEIGHT;

   if(!m_sendButton.Create(m_chart_id, m_name + "_SendButton", m_subwin, x1, button_y1, x2, y2))
   {
      Print("Failed to create SendButton");
      return(false);
   }
   m_sendButton.Text("Send");
   if(!Add(m_sendButton))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_SendButton", OBJPROP_BGCOLOR, CLR_SEND_BG);
   ObjectSetInteger(m_chart_id, m_name + "_SendButton", OBJPROP_COLOR, CLR_BUTTON_TEXT);
   return(true);
}

//+------------------------------------------------------------------+
//| CreateQuickMsgLabel                                              |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateQuickMsgLabel(void)
{
   int label_y1 = COMMS_MARGIN_TOP + 3 * COMMS_INPUT_HEIGHT + COMMS_GAP_VERTICAL +
                  COMMS_BUTTON_HEIGHT + COMMS_GAP_VERTICAL;
   int x1 = COMMS_MARGIN_LEFT;
   int x2 = x1 + COMMS_LISTVIEW_WIDTH;
   int y2 = label_y1 + COMMS_LABEL_HEIGHT;

   if(!m_quickMsgLabel.Create(m_chart_id, m_name + "_QuickMsgLabel", m_subwin, x1, label_y1, x2, y2))
   {
      Print("Failed to create QuickMessages Label");
      return(false);
   }
   m_quickMsgLabel.Text("QuickMessages");
   if(!Add(m_quickMsgLabel))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_QuickMsgLabel", OBJPROP_COLOR, CLR_LABEL_TEXT);
   return(true);
}

//+------------------------------------------------------------------+
//| CreateQuickMessageList                                           |
//+------------------------------------------------------------------+
bool CCommunicationDialog::CreateQuickMessageList(void)
{
   int list_y1 = COMMS_MARGIN_TOP + 3 * COMMS_INPUT_HEIGHT + COMMS_GAP_VERTICAL +
                 COMMS_BUTTON_HEIGHT + COMMS_GAP_VERTICAL +
                 COMMS_LABEL_HEIGHT + COMMS_GAP_VERTICAL;
   int x1 = COMMS_MARGIN_LEFT;
   int x2 = x1 + COMMS_LISTVIEW_WIDTH;
   int y2 = list_y1 + COMMS_LISTVIEW_HEIGHT;

   if(!m_quickMessageList.Create(m_chart_id, m_name + "_QuickMsgList", m_subwin, x1, list_y1, x2, y2))
   {
      Print("Failed to create ListView");
      return(false);
   }
   if(!Add(m_quickMessageList))
      return(false);
   
   ObjectSetInteger(m_chart_id, m_name + "_QuickMsgList", OBJPROP_BGCOLOR, CLR_LIST_BG);
   ObjectSetInteger(m_chart_id, m_name + "_QuickMsgList", OBJPROP_COLOR, CLR_LIST_TEXT);
   
   for(int i = 0; i < ArraySize(m_quickMessages); i++)
   {
      if(!m_quickMessageList.AddItem("Message: " + m_quickMessages[i]))
         return(false);
   }
   
   return(true);
}

セクション7:トグルとイベント処理

トグルは、ダイアログの表示・非表示を切り替えるシンプルなスイッチです。IsVisible()でダイアログが画面に表示されているかを確認します。表示されていればHide()で非表示にし、表示されていなければShow()で再表示します。ChartRedraw()を呼ぶことでチャートがすぐに更新され、変更がすぐに反映されます。ダイアログのためのライトスイッチのようなものです。

OnEventは、ユーザーの操作(クリックなど)を監視する頭脳のようなものです。id(何が起きたか)、lparam(詳細)、dparam(さらに詳細)、sparam(どのオブジェクトか)を受け取ります。idがCHARTEVENT_OBJECT_CLICKの場合、何かがクリックされたことを意味します。sparam(クリックされたオブジェクトの名前)がm_sendButton.Name()またはm_clearButton.Name()と一致していれば、それぞれOnClickSendまたはOnClickClearを呼び出します。処理をおこなった場合はtrueを返して、「対応済み」であることを示します。

idがON_CHANGEで、sparamがリストの名前と一致している場合は、ユーザーがクイックメッセージを選択したことを示します。どれを選んだかはlparamの数値で分かるので、それに対応するメッセージをm_inputBox.Textで入力欄にセットします。どの条件にも一致しない場合は、CAppDialog::OnEventを呼び出して、イベント処理を上位クラスに引き渡します。

//+------------------------------------------------------------------+
//| Toggle                                                           |
//+------------------------------------------------------------------+
void CCommunicationDialog::Toggle()
{
   if(IsVisible())
      Hide();
   else
      Show();
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| OnEvent                                                          |
//+------------------------------------------------------------------+
bool CCommunicationDialog::OnEvent(const int id, const long &lparam,
                                   const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == m_sendButton.Name())
      {
         OnClickSend();
         return true;
      }
      else if(sparam == m_clearButton.Name())
      {
         OnClickClear();
         return true;
      }
   }
   else if(id == ON_CHANGE && sparam == m_quickMessageList.Name())
   {
      int selectedIndex = (int)lparam;
      if(selectedIndex >= 0 && selectedIndex < ArraySize(m_quickMessages))
      {
         m_inputBox.Text(m_quickMessages[selectedIndex]);
      }
      return true;
   }
   return CAppDialog::OnEvent(id, lparam, dparam, sparam);
}

セクション8:ボタンイベントハンドラ

OnClickSendは、[Send]ボタンがクリックされたときに実行されます。まず、m_inputBox.Text()で入力ボックスのテキストを取得し、それが空文字("")でないかどうかを確認します。もしメッセージがあれば、SendMessageToTelegram(Telegram.mqh内の関数)を使って、m_chatIdとm_botTokenを引数にして送信を試みます。成功した場合は成功メッセージを出力し、失敗した場合は失敗メッセージを出力します。入力ボックスが空だった場合は、送信せずに注意メッセージを表示するだけです。これはダイアログの主な役割、つまりメッセージを送る機能です。

OnClickClearはもっとシンプルです。[Clear]ボタンがクリックされたとき、m_inputBox.Text("")で入力ボックスの内容を空にし、ChartRedraw()で画面を更新して変更を反映させ、確認メッセージを出力します。フォームのリセットボタンのような動作です。

//+------------------------------------------------------------------+
//| OnClickSend                                                      |
//+------------------------------------------------------------------+
void CCommunicationDialog::OnClickSend()
{
   string message = m_inputBox.Text();
   if(message != "")
   {
      if(SendMessageToTelegram(message, m_chatId, m_botToken))
         Print("Message sent to Telegram: ", message);
      else
         Print("Failed to send message to Telegram");
   }
   else
   {
      Print("No message to send - input box is empty");
   }
}

//+------------------------------------------------------------------+
//| OnClickClear                                                     |
//+------------------------------------------------------------------+
void CCommunicationDialog::OnClickClear()
{
   m_inputBox.Text("");  // Clear the input box
   ChartRedraw();
   Print("Input box cleared.");
}

ファイルの末尾には、ヘッダーガードを閉じるために#endifを追加します。

#endif // COMMUNICATIONS_DIALOG_MQH

これはファイルの冒頭にある#ifndefと対になっており、全体をきれいに包み込むように閉じています。


CommunicationsDialogを他のヘッダーファイルおよびメインプログラムと統合する

CommunicationsDialogAdminHomeDialog内で扱われます。このセクションでは、その動作の仕組みについて説明します。AdminHomeDialogの完全なコードについては、前回の記事ですでに詳しく扱っているため、ここでは取り上げません。

手順1

CommunicationsDialogAdminHomeDialogと接続するには、それをファイルに読み込む必要があります。「#include <CommunicationsDialog.mqh>」という行は、両者の間にドアを開けるようなもので、MQL5にCommunicationsDialog.mqhファイルを読み込ませることで、CCommunicationDialogクラスが使用可能になります。この#include行は、AdminHomeDialog.mqhの先頭付近、Dialog.mqhやButton.mqhなど他のインクルードファイルのあとに配置します。これがないと、AdminHomeDialogはコミュニケーションパネルの存在を認識できないため、統合の第一歩として重要です。

#include <CommunicationsDialog.mqh>  // Use the enhanced Communications dialog

手順2

CAdminHomeDialogクラスの内部でコミュニケーションパネルを保持するための仕組みが必要です。これには、privateセクションに「CCommunicationDialog *m_commPanel」を追加します。ここでアスタリスク(*)は、この変数が後で作成されるオブジェクトへのポインタ(参照)であることを意味します。これは、必要になったときに展開するおもちゃの置き場所を予約しておくようなものです。あわせて、m_chatIdとm_botTokenというTelegram情報を保持するためのstring変数も追加します。これらは後でCCommunicationDialogに渡す情報です。これらの変数は、このクラス内だけで管理すればよいためprivateにしておきます。こうすることで、統合のための準備が整います。

class CAdminHomeDialog : public CAppDialog
{
private:
   CCommunicationDialog *m_commPanel; // Pointer to the Communications panel
   string              m_chatId;      // Telegram Chat ID
   string              m_botToken;    // Telegram Bot Token
   
///.................Space for other members e.g. buttons
};

手順3

コンストラクタは、CAdminHomeDialogが作成されたときに初期化をおこないます。これを更新して、chatIdとbotTokenを引数として受け取るようにします。そして「: m_chatId(chatId), m_botToken(botToken)」という初期化リストで、それぞれm_chatIdとm_botTokenに値をコピーします。また、m_commPanel(NULL)でm_commPanelをNULL(まだ何もない状態)に設定します。これは、コミュニケーションパネルをすぐに作らず、ユーザーが必要になったときに作るようにするためです。中身が入った箱をまだ開けずに取っておくようなものです。

デストラクタは、ダイアログの使用が終了したときに後片付けをおこないます。まず、if(m_commPanel)でコミュニケーションパネルが作成されているかどうかを確認します。もし作られていれば、「delete m_commPanel」でそのメモリを解放します(使い終わったおもちゃを処分するようなものです)。そのあと、「m_commPanel = NULL」として、うっかり再び使おうとしないようにします。これにより、プログラムがきれいに整理され、CommunicationsDialogとの連携時のクラッシュなどを防ぐことができます。

CAdminHomeDialog::CAdminHomeDialog(string chatId, string botToken)
   : m_commPanel(NULL), m_chatId(chatId), m_botToken(botToken)
{
}

CAdminHomeDialog::~CAdminHomeDialog(void)
{
   if(m_commPanel)
   {
      delete m_commPanel;
      m_commPanel = NULL;
   }
}

手順4

ここが「魔法が起こる場所」です。OnClickCommunicationsは、[Communications]ボタンがクリックされたときにコミュニケーションパネルを起動します。まず、「if(m_commPanel == NULL)」で、コミュニケーションパネルがまだ作られていないかどうかを確認します。NULLの場合、「new CCommunicationDialog(m_chatId, m_botToken)」で新しいインスタンスを作成し、保存しておいたTelegramの情報を渡します。これは、あの「おもちゃの箱」を開けて、正しい説明書に従って組み立てるようなものです。

newが失敗した場合(たとえばメモリ不足など)、m_commPanelはNULLのままなので、エラーメッセージを出力して処理を中断します。成功した場合は、m_commPanel.Createを呼び出して、チャート上にダイアログを構築します。引数には、現在のチャートID(m_chart_id)、名前として"CommPanel"、サブウィンドウ番号(m_subwin)、そして座標(左上を(20, 435)、幅300、高さ350)を指定します。Createが失敗した場合は、エラーメッセージを出力し、「delete m_commPanel」で削除してから「m_commPanel = NULL」でリセットします。

すでにコミュニケーションパネルが作られている、あるいは今作ったばかりの場合は、m_commPanel.Toggle()を使って表示・非表示を切り替えます。非表示なら表示し、表示されていれば非表示にします。この「遅延生成(lazy creation)」の仕組みにより、ユーザーが実際にクリックするまではコミュニケーションパネルを作らず、リソースの節約が可能になります。

void CAdminHomeDialog::OnClickCommunications()
{
   if(m_commPanel == NULL)
   {
      m_commPanel = new CCommunicationDialog(m_chatId, m_botToken); // Pass chatId and botToken
      if(m_commPanel == NULL)
      {
         Print("Error: Failed to allocate Communications panel");
         return;
      }
      if(!m_commPanel.Create(m_chart_id, "CommPanel", m_subwin, 20, 435, 20 + 300, 435 + 350))
      {
         Print("Error: Failed to create Communications panel");
         delete m_commPanel;
         m_commPanel = NULL;
         return;
      }
   }
   m_commPanel.Toggle(); 
}

手順5

OnEventはクリックなどのユーザー操作を監視し、CommunicationsDialogにイベントを渡して連携します。idがCHARTEVENT_OBJECT_CLICKの場合、sparam(クリックされたオブジェクトの名前)がm_commButton.Name()と一致するかどうかをチェックします。一致すればメッセージを表示し、OnClickCommunicationsを呼んでパネルを開き、trueを返して「処理済み」であることを示します。

重要な統合部分は、else if(m_commPanel != NULL && m_commPanel.IsVisible())のブロックです。コミュニケーションパネルが存在し(!= NULL)、かつ画面に表示されている(IsVisible())場合、イベント(id、lparamなど)をm_commPanel.OnEventに渡します。これにより、CommunicationsDialogは自分自身のクリックイベント(SendやClearなど)を処理できます。そして、m_commPanel.OnEventの戻り値を返すことで、2つのダイアログのイベント処理を連結します。どの条件にも該当しなければ、CAppDialog::OnEventが処理を引き継ぎます。この連携により、両方のダイアログがユーザー操作にスムーズに応答できるようになります。

bool CAdminHomeDialog::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      Print("Clicked object: ", sparam);
      if(sparam == m_commButton.Name())
      {
         Print("Communications button detected");
         OnClickCommunications();
         return true;
      }
      // ... other button checks ...
   }
   // Forward remaining events to CommPanel if visible
   else if(m_commPanel != NULL && m_commPanel.IsVisible())
   {
      return m_commPanel.OnEvent(id, lparam, dparam, sparam);
   }
   return CAppDialog::OnEvent(id, lparam, dparam, sparam);
}


テストと結果 

コミュニケーションモジュールのコンパイルが成功し、メインプログラムと統合できた後、問題なくターミナルのチャート上でプログラムを起動しました。すべてのパネルはクリックに反応し、EAログにも記録が残っていることを確認しました。AdminHomeDialogの[Communications Panel]ボタンをクリックするとCommunicationsDialogの生成が始まりますが、初期のロジックで非表示になっているため、2回目のクリックで表示される仕組みです。CommunicationsDialogがチャートに表示されている状態で再度ボタンを押すと、パネルが隠れ、表示と非表示が切り替わる動作が確認できました。

ただし、リストビューに関しては大きな課題がありました。クイックメッセージの選択におけるクリックやスクロールイベントが期待通りに動作していません。原因の特定が必要であり、これは修正可能な小さな問題だと考えています。現時点では、基本的なコンセプトは機能して表示されているため、次のステップとして改善と機能追加を進めていきます。

新しいコミュニケーションパネルのテスト

コミュニケーションモジュールのテスト


結論

AdminHomeDialog.mqhに統合されたCommunicationsDialogモジュールの開発により、新しい管理パネルであるMQL5取引アプリケーション内でTelegramメッセージング用の機能的かつモジュール式のシステムが実現しました。EAログに「Clicked object: m_SendButton.」のようなクリックイベントが記録されていることからもわかるように、オンデマンドで通信パネルを正常に起動・切り替える応答性の高い管理インターフェイスを達成しています。遅延作成とトグル方式によりリソースの使用が最適化され、イベント駆動型設計によってスケーラビリティが確保されているため、コアコンセプトがチャート上で可視化され、動作していることが証明されました。CAdminHomeDialogをハブに、CCommunicationDialogを専用ツールとして構成したこのモジュール構造は、将来の拡張に向けた堅固な基盤となります。

しかしながら、リストビューのクリックおよびスクロールイベントがクイックメッセージの選択に正しく機能しない点や、通信パネルのボタンイベントのさらなる改良が必要であるなど、小さな課題が残っています。  それでもシステムの強みである効率性、応答性、明確な統合性がこれらの課題を上回っています。イベント伝播に関する重点的な修正と機能拡張の計画により、このプロトタイプをトレーダー向けの洗練された多機能ツールへと進化させる準備は整っています。朗報として、Telegram.mqhTファイルがコードベースで利用可能になりました。

添付ファイルの表

ファイル名 説明
CommunicationsDialog.mqh テキスト入力とクイックメッセージオプションのリストを備えた、Telegramメッセージ送信用ダイアログの定義
AdminHomeDialog.mqh 管理者ホームダイアログの作成用(すべての座標宣言を含む)
New_Admin_Panel.mqh モジュール性の概念が組み込まれた最新の管理パネル
Telegram.mqh Telegram経由のメッセージ・通知の送信用

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/17044

添付されたファイル |
AdminHomeDialog.mqh (16.81 KB)
Telegram.mqh (1.35 KB)
受信者動作特性曲線の紹介 受信者動作特性曲線の紹介
ROC曲線は、分類器の性能を評価するために使用されるグラフ表現です。ROC曲線は比較的単純に見えますが、実際に使用する際には、よくある誤解や陥りやすい落とし穴があります。この記事の目的は、分類器の性能評価を理解しようとする実務者に向けて、ROC曲線を紹介することです。
データサイエンスとML(第34回):時系列分解、株式市場を核心にまで分解 データサイエンスとML(第34回):時系列分解、株式市場を核心にまで分解
ノイズが多く、予測が難しいデータで溢れる世界では、意味のあるパターンを特定するのは困難です。この記事では、データをトレンド、季節パターン、ノイズといった主要な要素に分解する強力な分析手法「季節分解」について解説します。こうしてデータを分解することで、隠れた洞察を見つけ、より明確で解釈しやすい情報を得ることが可能になります。
最適化におけるカスタム基準への新しいアプローチ(第1回):活性化関数の例 最適化におけるカスタム基準への新しいアプローチ(第1回):活性化関数の例
これは、カスタム基準に関する数学的考察をおこなう連載記事の第1回目です。特に、ニューラルネットワークで使用される非線形関数、実装用のMQL5コード、さらにターゲットオフセットや補正オフセットの活用に焦点を当てています。
MQL5での取引戦略の自動化(第11回):マルチレベルグリッド取引システムの開発 MQL5での取引戦略の自動化(第11回):マルチレベルグリッド取引システムの開発
本記事では、MQL5を使用してマルチレベルのグリッド取引システムEAを開発し、グリッド取引戦略の背後にあるアーキテクチャとアルゴリズム設計に焦点を当てます。複数層にわたるグリッドロジックの実装と、市場のさまざまな状況に対応するためのリスク管理手法について探ります。最後に、自動売買システムの構築・テスト・改善をおこなうための詳細な説明と実践的なヒントを提供します。