MQL標準ライブラリエクスプローラー(第2回):ライブラリコンポーネントの接続
内容
はじめに
熱心な初心者から、他言語から移行してきた経験豊富なプログラマーまで、MQL5のエコシステムに入る多くの開発者が共通して直面する、フラストレーションの原因となる障害があります。それは、「コードは書けるが、堅牢で実務的なエキスパートアドバイザー(EA)を構築するのが難しい」という問題です。MQL5標準ライブラリは、この課題を解決するために設計された、事前に構築、テスト、最適化されたクラスの宝庫です。しかし、その豊富さゆえに圧倒されることもあります。問題の本質は、コンポーネントが不足していることではなく、これらの高度な構成要素を統合して一貫して動作する取引システムに組み込むための明確なロードマップがないことです。
多くの開発者は、精密な道具が詰まったツールボックスを持っているにもかかわらず、それを完成したソリューションに組み立てる設計図を持っていません。この複雑さを簡略化するために、現実の例えを用います。人体が脳の指揮のもとで専門の臓器が連携して機能するように、EAも、専門のモジュールがメインプログラムにより調整されて動作します。
この例えでいうと、EAは脳として中央の意思決定者の役割を果たします。サポートするライブラリクラスは臓器として、それぞれ専門の機能を担います。
- CTradeクラス:取引を実行する(手が行動する役割に相当)
- CIndicatorsクラス:市場データを分析する(目が環境を認識する役割に相当)
- CAccountInfoクラス:口座の状態を監視する(神経系が情報を収集し、伝達する役割に相当)

図1:概念的なソフトウェアシステム

図2:概念的な生物学的システム
ここでのアナロジーは、オブジェクト指向プログラミング(OOP)の抽象的な構造をより直感的に理解するための概念的側面にすぎません。ライブラリの各部分がどのように相互作用し、調整された効率的な取引システムを形成するかを読者が視覚的に把握する助けとなります。
これらはすべてOOPの利点(モジュール化、再利用性、抽象化)ですが、本連載ではあえて理論的な深掘りは省き、まずは実践的な応用に焦点を当てます。多くの学習者は「手を動かして学ぶ」ことで概念をより理解しやすくなります。実際にクラスを構築し、統合していく中で、OOPの基本原理が自然と理解できるようになります。
OOP理論を先に学んでから進めたい読者のためには、MQL5コミュニティや他のリソースに、カプセル化、継承、ポリモーフィズムを詳細に解説した優れた参考文献が数多く存在します。しかし、ここでのアプローチは実践重視です。実例、図解、アナロジーを通して、MQL5標準ライブラリとの学習体験を楽しくかつ生産的にします。
この議論の最後には、単なる個別例を超え、統合設計図を提示します。これは、MQL5標準ライブラリの強力な要素をプロフェッショナルなEAの堅牢なフレームワークに組み込むための決定版ガイドであり、同時にライブラリ自体を拡張する方法も示します。

図3:EAは脳、クラスは臓器
EAは脳、すなわちメインプログラムとして扱われますが、モジュール自体も他のモジュールを取り込むことで構築できることを理解する必要があります。これを「依存関係」と呼びます。EAプログラム内にライブラリ全体を統合することを学ぶのと同様に、モジュール同士を組み合わせたり、あるモジュールを別のモジュール内で実装したりするスキルも必要です。
このアナロジーからわかるのは、モジュール間の相互関係は、メインプログラムによる全体の調整を介して存在する場合もあれば、直接的な依存関係によって成立する場合もあるということです。たとえば、CPanelクラスは単独で存在せず、WndObj.mqhとChartObjectsTxtControls.mqhを直接インクルードし、それらに依存しています。

図4:クラス間の通信
良いニュースは、この関係ネットワークは理解すれば複雑ではないということです。EAでモジュールを使用する際、単純なクラスであれ複雑な依存モジュールであれ、毎回成功する統合のための一貫性があり、再現可能な手順があります。以下の部分的なコードスニペットは、Panel.mqhで使用される主要な依存関係と、それらがどこで含まれているかを示しています。
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "WndObj.mqh" #include <ChartObjects\ChartObjectsTxtControls.mqh>
次のセクションでは、私がライブラリクラスをプロジェクトに統合する際に通常おこなっている手順を紹介します。これにより、実装段階に進む前にしっかりと理解を固めることができ、実際の例をより詳しく検討する準備が整います。
EAへのクラス統合
ほとんどの場合、どのクラスを使用するかの選択は、EAの全体戦略や目標を定義した後におこないます。プロジェクトに不要なコンポーネントを過剰に組み込む必要はありません。しかし、このセクションでは戦略設計ではなく、統合プロセスそのものの理解に焦点を当てます。
ここでの目的は、ライブラリの任意のモジュールを将来のプロジェクトに自信を持って統合できるように、必要な知識と実践的手順を共有することです。以下では、その手順をフロー図とともに示します。
プロセスの流れ
- 分析:クラスの目的と継承チェーンを確認する
- インクルード:正しいパスで#includeディレクティブを追加する
- 宣言:EA内でオブジェクトインスタンスを作成する
- 初期化:OnInit()内でCreate()を呼び出し、必要なパラメータを渡す
- 実行時:OnTick()内で更新やイベント処理をおこなう
- クリーンアップ:OnDeinit()内でリソースを解放する
エラー処理
- コンパイルエラー → 依存関係を確認
- オブジェクト生成失敗 → パラメータを検証
- 実行時問題 → オブジェクトの状態を監視

図5:MQL5ライブラリ統合ロードマップ
モジュール間統合フロー
依存関係分析
- 親子関係:例)CPanel → WndObj → ChartObjectsTxtControls
- インターフェース契約:どのメソッドを実装する必要があるか
- データフロー:モジュール間で情報がどのように渡されるか

図6:モジュール継承階層
実装手順
- インクルードチェーン:各モジュールは直接依存するモジュールを#includeで取り込む
- 継承の設定:extendsによるクラス階層を構築
- メソッド実装:必要に応じて仮想メソッドをオーバーライド
- 単体テスト:EAに統合する前に、モジュールを個別にテスト
一般的なパターン
- 階層型アーキテクチャ:上位モジュールが下位モジュールに依存
- インターフェース分離:モジュールはシンプルで焦点の定まったAPIを公開
- 依存性注入:必要なオブジェクトはハードコーディングせず渡す

図7:モジュール間統合フロー
アーキテクチャの基盤が確立し、統合ロードマップが明確になったところで、次は実装フェーズに移ります。ここで理論を実践に落とし込み、概念的理解を機能するEAへと変換します。
実装計画としては、InfoPanel EAを構築します。これは情報ダッシュボードとして、CPanelクラスとその依存関係を取引環境内で動作させる方法を示すものです。この例を通して、統合手順の各ステップを実際に適用し、クリーンでプロフェッショナル、かつ拡張可能なシステムを構築する方法を学ぶことができます。
実装
MQL5でライブラリクラスに取り組む際、統合はまず、そのクラスが本質的に何であるかを理解することから始まります。すなわち、その目的、継承関係、そしてチャートコントロールのエコシステム内での位置づけです。CPanel は単なる四角形の領域ではなく、CWndObjから継承された自己完結型のインターフェースレイヤーであり、チャート上で自身のライフサイクルを管理します。複雑なUIコンポーネントの設計骨格として機能するのです。EAにコードを実装する前に、この理解を持つことで、統合がスムーズに進むための思考フレームが整います。
CPanelクラスの分析:Panel.mqh
クラスに初めて触れる際は、常に調査的なアプローチを取ることが重要です。Panel.mqhのヘッダを読むことで、主要な関係性や拡張ポイントを把握できます。CPanelはCWndObjを継承しているため、イベントの処理やチャート上のウィンドウ管理能力も受け継ぎます。内部では、CChartObjectRectLabelをカプセル化しており、これがパネルの可視フレームを提供します。
特に公開エントリーポイントであるCreate()とBorderType()を特定することで、EAが操作するコントロールハンドルの位置が把握できます。このような事前の検査により、試行錯誤に頼るのではなく、情報に基づいた設計が可能になります。
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "WndObj.mqh" #include <ChartObjects\ChartObjectsTxtControls.mqh> //+------------------------------------------------------------------+ //| Class CPanel | //| Usage: control that is displayed by | //| the CChartObjectRectLabel object | //+------------------------------------------------------------------+ class CPanel : public CWndObj { private: CChartObjectRectLabel m_rectangle; // chart object //--- parameters of the chart object ENUM_BORDER_TYPE m_border; // border type public: CPanel(void); ~CPanel(void); //--- create virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); //--- parameters of the chart object ENUM_BORDER_TYPE BorderType(void) const { return(m_border); } bool BorderType(const ENUM_BORDER_TYPE type); protected: //--- handlers of object settings virtual bool OnSetText(void) { return(m_rectangle.Description(m_text)); } virtual bool OnSetColorBackground(void) { return(m_rectangle.BackColor(m_color_background)); } virtual bool OnSetColorBorder(void) { return(m_rectangle.Color(m_color_border)); } //--- internal event handlers virtual bool OnCreate(void); virtual bool OnShow(void); virtual bool OnHide(void); virtual bool OnMove(void); virtual bool OnResize(void); virtual bool OnChange(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPanel::CPanel(void) : m_border(BORDER_FLAT) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPanel::~CPanel(void) { } //+------------------------------------------------------------------+ //| Create a control | //+------------------------------------------------------------------+ bool CPanel::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { //--- call method of the parent class if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- create the chart object if(!m_rectangle.Create(chart,name,subwin,x1,y1,Width(),Height())) return(false); //--- call the settings handler return(OnChange()); } //+------------------------------------------------------------------+ //| Set border type | //+------------------------------------------------------------------+ bool CPanel::BorderType(const ENUM_BORDER_TYPE type) { //--- save new value of parameter m_border=type; //--- set up the chart object return(m_rectangle.BorderType(type)); } //+------------------------------------------------------------------+ //| Create object on chart | //+------------------------------------------------------------------+ bool CPanel::OnCreate(void) { //--- create the chart object by previously set parameters return(m_rectangle.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top,m_rect.Width(),m_rect.Height())); } //+------------------------------------------------------------------+ //| Display object on chart | //+------------------------------------------------------------------+ bool CPanel::OnShow(void) { return(m_rectangle.Timeframes(OBJ_ALL_PERIODS)); } //+------------------------------------------------------------------+ //| Hide object from chart | //+------------------------------------------------------------------+ bool CPanel::OnHide(void) { return(m_rectangle.Timeframes(OBJ_NO_PERIODS)); } //+------------------------------------------------------------------+ //| Absolute movement of the chart object | //+------------------------------------------------------------------+ bool CPanel::OnMove(void) { //--- position the chart object return(m_rectangle.X_Distance(m_rect.left) && m_rectangle.Y_Distance(m_rect.top)); } //+------------------------------------------------------------------+ //| Resize the chart object | //+------------------------------------------------------------------+ bool CPanel::OnResize(void) { //--- resize the chart object return(m_rectangle.X_Size(m_rect.Width()) && m_rectangle.Y_Size(m_rect.Height())); } //+------------------------------------------------------------------+ //| Set up the chart object | //+------------------------------------------------------------------+ bool CPanel::OnChange(void) { //--- set up the chart object return(CWndObj::OnChange() && m_rectangle.BorderType(m_border)); } //+------------------------------------------------------------------+
各ステップは最終的に、EA内での初期化処理に直接対応します。
統合手順
ステップ1:ヘッダと依存関係のインクルード
パネルの目的と位置づけが明確になったら、次はインクルードです。EAがCPanelとその基底クラスを認識できるように、正しい#includeディレクティブでファイルを取り込みます。MQL5ではインクルードパスに厳格なので、Panel.mqhやサポートヘッダ(WndObj.mqh、ChartObjectsTxtControls.mqhなど)がプロジェクト内、またはグローバルなIncludeディレクトリに存在することを確認してください。
#include <Controls\Panel.mqh> // gives access to CPanel and its internals
この段階でEAはパネルクラスが存在すると認識し、インスタンス化の準備が整います。
ステップ2:オブジェクトの宣言
宣言は、パネルがEAのメモリ上でどこに存在するかを定義します。多くの開発者は、CPanelをグローバルレベルのポインタとして宣言し、イベント間で保持し、クリーンアップ時に安全に削除できるようにします。
CPanel *mainPanel = NULL; これにより、OnInit、OnTick、OnDeinitなど、すべてのイベントハンドラからオブジェクトにアクセス可能になり、ライフサイクルが一貫します。
ステップ3:初期化 - パネルを活性化する
生成はOnInit()内でおこないます。ヘッダで定義された設計がここで実行に移ります。Create()メソッドは、チャートID、ユニーク名、対象サブウィンドウ、パネルの矩形を定義する4座標の6つの引数を必要とします。返り値がtrueであることを必ず確認してください。falseの場合はパラメータや依存関係に問題があることが多いです。
int OnInit() { mainPanel = new CPanel(); if(!mainPanel.Create(ChartID(), "InfoPanel", 0, 10, 10, 400, 120)) { Print("Panel creation failed."); delete mainPanel; mainPanel = NULL; return(INIT_FAILED); } mainPanel.BorderType(BORDER_RAISED); mainPanel.Text("Information Panel"); return(INIT_SUCCEEDED); }
このコードで最小限ながら完全な初期化がおこなわれ、指定した矩形の位置にパネルがチャート上に表示されます。
ステップ4:実行時の管理
パネルが静的であれば、常時更新は必要ありません。しかし、統合ロジックを拡張する場合は、ラベルやボタンなどの子コントロールを埋め込んだり、内容を定期的に更新したりできます。
動的更新には OnTimer()イベントを使うと、OnTick()ループに負荷をかけずに安定した更新が可能です。
void OnTimer() { // Placeholder for runtime UI updates }
各更新でオブジェクトを再描画することなく、パネル内の表示内容をリフレッシュできます。
ステップ5:クリーンアップとエラー処理
動的に生成したオブジェクトは、必ず削除してメモリリークを防ぎます。OnDeinit()で delete を呼び、ポインタをNULLに設定します。これにより適切な解放がおこなわれ、チャート上に残留するオブジェクトも防げます。
void OnDeinit(const int reason) { if(mainPanel) { mainPanel.OnHide(); delete mainPanel; mainPanel = NULL; } }
コンパイルエラーの場合は、インクルード漏れや未定義参照がないかを確認します。生成エラーが発生した場合は、座標や名前の重複、パラメータに問題がないかをチェックします。実行時のちらつきやイベントの問題は、多くの場合、チャートオブジェクトの重なりに起因します。
ChartObjectsTxtControls.mqh内のCChartObjectTextの分析
このセクションでは、ChartObjectsTxtControls.mqhヘッダのインターフェース層に焦点を当てます。明確さと簡潔さのために、クラス全体ではなく公開されているトップレベル部分のみを抽出しました。この部分は、EAからどのように操作できるかを定義しており、クラスの「正面玄関」にあたります。完全なソースコードははるかに長いですが、分析には公開インターフェースだけで十分です。
このように一部のみを見るのは意図的です。オブジェクト指向プログラミング(OOP)の本質は抽象化にあります。内部がどのように動作するかを完全に理解する必要はなく、インターフェースと正しい使い方を理解していれば十分です。インターフェースには、利用可能なメソッド、パラメータ、期待される振る舞いが定義されており、統合を効果的におこなうためにはこれだけで足ります。
ここでの目的は、ヘッダーを分析してその役割を理解し、統合時の注目点を特定することです。統合時の注目点とは、EAのメインプログラムから呼び出してチャート上のテキストを定義し、操作する際に使用する主要なメソッドのことです。今回、このヘッダを含めたのは、パネル構築のため(CPanelの依存関係として使用したとき)のではなく、EA内でテキスト記述をおこなうためです。
以前、依存関係として使用した場合は内部ロジックに注目する必要はほとんどありませんでした。親クラスであるCPanelがそれを管理していたためです。しかし教育的な目的では、各クラスがどのように構築され、独立して使用できるかを理解するために、意図的に内部構造も学習します。
以下の抜粋はCChartObjectTextのインターフェースを示しており、チャート上のテキストオブジェクトを直接操作するために必要な主要メソッドを提供しています。その中でもCreate()、FontSize()、Anchor()は、パネルやEAにテキスト要素を統合する際の重要な操作メソッドです。これらのメソッドを用いることで、ピクセル位置の指定、フォントスタイルの設定、整列の制御、表示と非表示の管理など、InfoPanelのテキスト記述層を構築するために必要な操作をすべて実現できます。
//+------------------------------------------------------------------+ //| ChartObjectsTxtControls.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //| All text objects. | //+------------------------------------------------------------------+ #include "ChartObject.mqh" //+------------------------------------------------------------------+ //| Class CChartObjectText. | //| Purpose: Class of the "Text" object of chart. | //| Derives from class CChartObject. | //+------------------------------------------------------------------+ class CChartObjectText : public CChartObject { public: CChartObjectText(void); ~CChartObjectText(void); //--- method of creating the object bool Create(long chart_id,const string name,const int window,const datetime time,const double price); //--- method of identifying the object virtual int Type(void) const override { return(OBJ_TEXT); } //--- methods of access to properties of the object double Angle(void) const; bool Angle(const double angle) const; string Font(void) const; bool Font(const string font) const; int FontSize(void) const; bool FontSize(const int size) const; ENUM_ANCHOR_POINT Anchor(void) const; bool Anchor(const ENUM_ANCHOR_POINT anchor) const; //--- methods for working with files virtual bool Save(const int file_handle) override; virtual bool Load(const int file_handle) override; };
CChartObjectTextをEAに統合する手順
ステップ1:インクルード
EAの先頭付近でChartObjectsTxtControls.mqhをインクルードし、クラス定義を可視化します。ライブラリと同じパスを使用します。Panel.mqhをすでにインクルードしている場合、このヘッダーは間接的に含まれますが、EA内で明示的にインクルードすることで意図と依存関係が明確になります。
#include <ChartObjects\ChartObjectsTxtControls.mqh> // provides CChartObjectLabel et al.
ステップ2:宣言する
ラベルオブジェクトはEAのライフサイクル関数からアクセスできるよう、グローバルスコープで宣言します。チャートテキストクラスは内部で添付を管理するためスタックオブジェクトを使うのが一般的ですが、必要に応じてnew/delete形式のポインタでも構いません。ラベル名はチャートごとにユニークに設定し、複数ラベルがある場合は「InfoPanel_123_txt1」のようなパターンを使用します。
// global declarations CChartObjectLabel txtDesc; // pixel-based label (good for panel body)
ステップ3:初期化(OnInit)
OnInit()内でヘッダに示されたシグネチャを使ってラベルを生成します:Create(long chart_id, const string name, const int window, const int X, const int Y)。パネルの矩形に対して相対位置を指定するとレイアウトが整います。
生成に失敗した場合は、チャートID、名前、座標などの情報をログに出力し、初期化を中止します。
int OnInit() { string txt_name = panel_name + "_txtDesc"; int left = PANEL_X1 + PADDING; int top = PANEL_Y1 + PADDING + 18; if(!txtDesc.Create(ChartID(), txt_name, 0, left, top)) { Print("txtDesc.Create failed"); return(INIT_FAILED); } txtDesc.Description("Hello trader! I am InfoPanel EA"); txtDesc.FontSize(11); // txtDesc.Anchor(ANCHOR_LEFT_UPPER); // if API available return(INIT_SUCCEEDED); }
ステップ4:実行時管理(OnTick/OnTimer/OnChartEvent)
静的テキストの場合、実行時の処理は最小限にします。動的フィールドが必要な場合は、OnTimer()内でDescription()を更新します(OnInit()でEventSetTimer()を設定)。インタラクティブ要素(ボタンや編集フィールドなど)がある場合は、OnChartEvent()を転送して、sparamをチェックしてオブジェクトクリックを判定します。
複数行テキストは、縦に積み重ねた複数のCChartObjectLabelインスタンスを使うか、\nをDescription()に渡して改行できるかを試します。できない場合はラベルを分けて配置します。
void OnTimer() { // e.g., update price line inside the panel txtDesc.Description("Bid: " + DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); }
ステップ5:クリーンアップ(OnDeinit)
EA終了時にチャートオブジェクトを削除します。クラスのDelete()メソッドを使用するか、必要に応じてObjectDelete()を代替として使います。他クラスの内部protectedメソッドは呼び出さず、自分のラベルを削除し、パネルポインタがある場合は解放します。
void OnDeinit(const int reason) { txtDesc.Delete(); // removes the chart object // delete panel pointer here if allocated EventKillTimer(); // if used }
EAへの各要素の統合
CPanelクラスをInfoPanel EAに最終的に統合することで、標準ライブラリ学習の基礎段階が完了します。このパネルはインタラクティブなダッシュボードや取引コントローラーではなく、静的な情報表示です。シンプルながらもプロフェッショナルなチャート上のマークとして、使用中の製品を明示的に示します。多くの開発者は、EAの名前や詳細をチャート上に直接表示するために、同様のデザインを選ぶことがあります。これにより、デフォルトのチャート名の制限やプロパティを探す手間を避けることができます。パネルは控えめながら実用的な署名として機能し、チャート環境に明快さをもたらします。
一見控えめに見えるこのコンセプトの価値は、その規模ではなく手法そのものにあります。ここでは、標準ライブラリヘッダのインクルードから始まり、CPanelインスタンスの生成とカスタマイズ、さらにチャートテキスト要素との組み合わせによる有意義な表示まで、クラス統合の一連の流れを実践しました。このプロセスは、プロフェッショナルなMQL5コンポーネントがどのように構築され管理されるかの核心を示しています。インクルード、宣言、生成、制御というアーキテクチャのリズムを理解することが、より高度なシステム構築に再利用される基盤となります。
この最初のプロジェクトはあえてシンプルにしています。CPanelに慣れた後は、より豊富なテキスト出力のためにCChartObjectLabelを追加したり、ブランド表示用にCPictureを導入したりすることも自然におこなえます。こうして構築された静的要素は、小さくても完結した情報可視化のエコシステムを形成し、学習が進むにつれて複雑化させることができます。
さらに広い視点では、標準ライブラリを単なる固定フレームワークではなく、無限に拡張可能な柔軟な基盤として捉える思考法を養うことが目的です。CPanel、CChartObjectLabel、CPictureなど各クラスは、積み重ねることで大きなシステムを形作るためのビルディングブロックです。今日のシンプルな表示パネルは、将来的にデータ可視化、カスタムコントロール、適応レイアウトを備えた包括的な作業空間へと進化する可能性を秘めています。
したがって、CPanelとの取り組みは単なる静的情報パネルの作成にとどまらず、標準ライブラリを基盤としたシステム構築の論理と技巧を段階的に習得する旅の始まりでもあります。
//+------------------------------------------------------------------+ //| InfoPanel_EA.mq5 | //| Minimal example: creating a CPanel and a label text showing | //| a short EA description with panel background & border. We will | //| also add a logo to explore CPicture. | //+------------------------------------------------------------------+ #property copyright "Clemence Benjamin" #property version "1.0" #include <Controls/Panel.mqh> // Panel header (correct path) #include <ChartObjects\ChartObjectsTxtControls.mqh> // for CChartObjectLabel //---- Globals -------------------------------------------------------- CPanel *infoPanel = NULL; // pointer to panel (allocate/free) CChartObjectLabel txtDesc; // label text inside the panel // layout (pixels) int PANEL_X1 = 10; int PANEL_Y1 = 30; int PANEL_X2 = 300; // widened to give text room int PANEL_Y2 = 100; int PADDING = 10; // short description that fits comfortably on one line string EA_DESCRIPTION = "Hello trader! I am InfoPanel EA"; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { // allocate the panel object infoPanel = new CPanel(); if(infoPanel == NULL) { Print("InfoPanel: allocation failed"); return(INIT_FAILED); } // unique panel name string panel_name = "InfoPanel_" + IntegerToString((int)ChartID()); // create the panel on the current chart (subwindow 0 = main) if(!infoPanel.Create(ChartID(), panel_name, 0, PANEL_X1, PANEL_Y1, PANEL_X2, PANEL_Y2)) { Print("InfoPanel: Create() failed"); delete infoPanel; infoPanel = NULL; return(INIT_FAILED); } // appearance: border type, header text, background and border colors infoPanel.BorderType(BORDER_RAISED); infoPanel.Text("InfoPanel"); // Set a visible background and border color. // Use standard color constants; change them if you prefer another palette. // If your library expects ARGB or different color encoding, adapt accordingly. infoPanel.ColorBackground(clrSilver); // panel background infoPanel.ColorBorder(clrBlack); // panel border // compute child label position inside panel (some padding below header) int left = PANEL_X1 + PADDING; int top = PANEL_Y1 + PADDING + 18; // leave header area // create a CChartObjectLabel as panel body (unique name) string txt_name = panel_name + "_txtDesc"; if(!txtDesc.Create(ChartID(), txt_name, 0, left, top)) { Print("InfoPanel: txtDesc.Create() failed"); delete infoPanel; infoPanel = NULL; return(INIT_FAILED); } // set the short description and font size using the correct API txtDesc.Description(EA_DESCRIPTION); txtDesc.FontSize(11); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // remove the label object txtDesc.Delete(); // free the panel (do NOT call protected methods) if(infoPanel != NULL) { delete infoPanel; infoPanel = NULL; } // safe kill of timer if later added EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert tick function (kept intentionally light) | //+------------------------------------------------------------------+ void OnTick() { // static info panel — no per-tick UI updates } //+------------------------------------------------------------------+ //| Chart event handler (optional extension point) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // placeholder for future interactive controls }

図8:初期テスト
次のステップとして、パネルにBMP形式のロゴを追加し、明確な識別を与えます。私は常にオープンソースの画像作成ツールを好んで使用しており、GIMPは特に優れています。BMP形式での書き出しが簡単で迅速におこなえるためです。もちろん、お好みの他のソフトウェアを使用しても問題ありません。作成したロゴは、ターミナルのImagesフォルダに保存しておくとアクセスが容易です。準備が整ったら、CPictureクラスを使ってパネルに統合する方法を検討します。
以下の2つの図を参照してください。図9はロゴがパネル内で配置される位置を示しており、図10は私がGIMP.で作成したロゴです。BMP形式で書き出す際は、必ず24ビットカラー、単一レイヤー、アルファチャンネルなしの設定にしてください。この設定でテスト時に正しく表示されました。その他の書き出し設定では、パネル上で正しく表示されない場合があります。ここで示したロゴ画像は正しいBMP形式ではないため、動作しません。PNG版を使用しており、これは公開用としてサポートされています。記事の最後に添付ロゴを確認できます。

図9:EAロゴ配置スペース

図10:56x59ピクセルのロゴ
CPictureクラスの理解と統合の準備
CPictureクラスは、すでに構築したCPanelとCChartObjectLabelの基盤をさらに拡張します。パネルがコンテナを提供し、ラベルがテキスト表示を可能にしたのと同様に、CPictureはチャート上にビットマップ画像を表示する手段を提供します。これにより、情報パネルにロゴのサムネイルを追加する次の改善が容易になります。
CPictureは内部的にCChartObjectBmpLabelをラップしており、CWndObjを継承しています。そのため、以前扱ったパネルやラベルと同じライフサイクルメソッド(Create、OnShow、OnHideなど)に従います。この設計により、EAへの統合は以前のクラスと同じステップ(確保、生成、設定、クリーンアップ)で進められます。
注目すべきポイントは次の通りです。主な生成エントリであるCreate(chart, name, subwin, x1, y1, x2, y2),は、パネルと同様にピクセル座標でオブジェクトの領域を定義します。生成後、BmpName(name)メソッドでターミナルのImagesフォルダにある.bmpファイルを指定できます。Border(value)は表示領域の境界線や幅を調整しますが、ロゴ表示の場合は通常ゼロに設定します。これらのメソッドは内部でOnChange()ハンドラを通じてチャートオブジェクトを更新します。
統合時の注目点
実際の統合において注目すべき実用的なポイントは以下の通りです。
- Create():ユニーク名とピクセル座標を指定してチャート上にオブジェクトを初期化
- BmpName():表示するビットマップファイルを割り当て(例:EAロゴ)
- Border():必要に応じて境界線や幅を設定(ロゴでは省略可能)
- デストラクタ/delete:EA終了時に安全にメモリを解放
//+------------------------------------------------------------------+ //| Picture.mqh | //| Copyright 2000-2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "WndObj.mqh" #include <ChartObjects\ChartObjectsBmpControls.mqh> //+------------------------------------------------------------------+ //| Class CPicture | //| Note: image displayed by | //| the CChartObjectBmpLabel object | //+------------------------------------------------------------------+ class CPicture : public CWndObj { private: CChartObjectBmpLabel m_picture; // chart object //--- parameters of the chart object int m_border; // border width string m_bmp_name; // filename public: CPicture(void); ~CPicture(void); //--- create virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); //--- parameters of the chart object int Border(void) const { return(m_border); } bool Border(const int value); string BmpName(void) const { return(m_bmp_name); } bool BmpName(const string name); protected: //--- internal event handlers virtual bool OnCreate(void); virtual bool OnShow(void); virtual bool OnHide(void); virtual bool OnMove(void); virtual bool OnChange(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPicture::CPicture(void) : m_border(0), m_bmp_name(NULL) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPicture::~CPicture(void) { } //+------------------------------------------------------------------+ //| Create a control | //+------------------------------------------------------------------+ bool CPicture::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { //--- call method of the parent class if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); //--- create the chart object if(!m_picture.Create(chart,name,subwin,x1,y1)) return(false); //--- call the settings handler return(OnChange()); } //+------------------------------------------------------------------+ //| Set border width | //+------------------------------------------------------------------+ bool CPicture::Border(const int value) { //--- save new value of parameter m_border=value; //--- set up the chart object return(m_picture.Width(value)); } //+------------------------------------------------------------------+ //| Set image | //+------------------------------------------------------------------+ bool CPicture::BmpName(const string name) { //--- save new value of parameter m_bmp_name=name; //--- set up the chart object return(m_picture.BmpFileOn(name)); } //+------------------------------------------------------------------+ //| Create object on chart | //+------------------------------------------------------------------+ bool CPicture::OnCreate(void) { //--- create the chart object by previously set parameters return(m_picture.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top)); } //+------------------------------------------------------------------+ //| Display object on chart | //+------------------------------------------------------------------+ bool CPicture::OnShow(void) { return(m_picture.Timeframes(OBJ_ALL_PERIODS)); } //+------------------------------------------------------------------+ //| Hide object from chart | //+------------------------------------------------------------------+ bool CPicture::OnHide(void) { return(m_picture.Timeframes(OBJ_NO_PERIODS)); } //+------------------------------------------------------------------+ //| Absolute movement of the chart object | //+------------------------------------------------------------------+ bool CPicture::OnMove(void) { //--- position the chart object return(m_picture.X_Distance(m_rect.left) && m_picture.Y_Distance(m_rect.top)); } //+------------------------------------------------------------------+ //| Set up the chart object | //+------------------------------------------------------------------+ bool CPicture::OnChange(void) { //--- set up the chart object return(m_picture.Width(m_border) && m_picture.BmpFileOn(m_bmp_name)); } //+------------------------------------------------------------------+
この理解をもとに、次のステップではCPictureをパネルに統合し、情報パネルにロゴを表示できるようにします。
統合計画
CPictureの仕組みが理解できたので、次はInfoPanel_EAに統合する計画を立てます。まず画像ファイル(ロゴ)はBMP形式で準備します。前述の通り、GIMPなどのツールを使えばBMP形式での書き出しは簡単です。ファイルを用意したら、ターミナルのImagesフォルダ(MQL5/Images/)に配置し、EAから名前で簡単にアクセスできるようにします。
コード上では以下の手順で進めます。
- Picture.mqhヘッダをインクルードします。
- パネルと同様に、CPictureインスタンス用のグローバルポインタを宣言します。
- OnInit()内でオブジェクトを生成し、パネル内またはパネルの上方に適切に配置します。
- BmpName("logo.bmp")を使って画像ファイルを割り当てます。
- OnDeinit()でリソースを解放し、クリーンな終了を保証します。
こうすることで、パネルは単なるテキスト表示からブランド付き情報表示へと進化します。これは控えめながらも、EAの見た目をよりプロフェッショナルに、識別可能にする意味のある一歩です。
このステップの意義
一見小さな追加のように思えますが、標準ライブラリの任意の視覚クラスを実際に組み合わせて統合する手順を深く理解することにつながります。各統合は生成、設定、ライフサイクルの安全な管理というパターンを強化します。ロゴ統合を完了すると、CPanel、CChartObjectLabel、CPictureの3つの異なる標準クラスを1つの統合インターフェースに接続したことになります。
次のセクションでは、この統合を実際に動作させ、EAのパネルにロゴを表示します。これにより静的情報パネルが完成し、ここまで学んだ内容の視覚的な復習にもなります。このセクションでは、複数のクラスがどのように共存し、EAを情報豊かで視覚的に特徴あるものにできるかを実践的に示します。
//+------------------------------------------------------------------+ //| InfoPanel_EA.mq5 | //| Info panel with logo placed inside the panel and on foreground | //+------------------------------------------------------------------+ #property copyright "Clemence Benjamin" #property version "1.0" #include <Controls\Panel.mqh> #include <ChartObjects\ChartObjectsTxtControls.mqh> #include <Controls\Picture.mqh> //---- Globals -------------------------------------------------------- CPanel *infoPanel = NULL; CChartObjectLabel txtDesc; CPicture *logo = NULL; // layout (pixels) int PANEL_X1 = 10; int PANEL_Y1 = 30; int PANEL_X2 = 300; int PANEL_Y2 = 100; int PADDING = 10; // logo size and filename int LOGO_SIZE = 56; string LOGO_FILE = "Logo.bmp"; // short description string EA_DESCRIPTION = "Hello trader! I am InfoPanel EA"; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { // 1. Create panel infoPanel = new CPanel(); if(infoPanel == NULL) { Print("InfoPanel: allocation failed"); return(INIT_FAILED); } string panel_name = "InfoPanel_" + IntegerToString((int)ChartID()); if(!infoPanel.Create(ChartID(), panel_name, 0, PANEL_X1, PANEL_Y1, PANEL_X2, PANEL_Y2)) { Print("InfoPanel: Create() failed"); delete infoPanel; infoPanel = NULL; return(INIT_FAILED); } infoPanel.BorderType(BORDER_RAISED); infoPanel.Text("InfoPanel"); infoPanel.ColorBackground(clrSilver); infoPanel.ColorBorder(clrBlack); // 2. Create label inside panel int label_left = PANEL_X1 + PADDING; int label_top = PANEL_Y1 + PADDING + 18; string txt_name = panel_name + "_txtDesc"; if(!txtDesc.Create(ChartID(), txt_name, 0, label_left, label_top)) { Print("InfoPanel: txtDesc.Create() failed"); delete infoPanel; infoPanel = NULL; return(INIT_FAILED); } txtDesc.Description(EA_DESCRIPTION); txtDesc.FontSize(11); // 3. Set panel as background if(!ObjectSetInteger(ChartID(), panel_name, OBJPROP_BACK, true)) PrintFormat("InfoPanel: unable to set OBJPROP_BACK for '%s' (non-fatal).", panel_name); // 4. Compute logo coordinates int logo_right = PANEL_X2 - PADDING; int logo_left = logo_right - LOGO_SIZE; int logo_top = PANEL_Y1 + PADDING; int logo_bottom = logo_top + LOGO_SIZE; string logo_name = panel_name + "_logo"; // 5. Create logo logo = new CPicture(); if(logo == NULL) { Print("InfoPanel: logo allocation failed (continuing without logo)"); } else { if(!logo.Create(ChartID(), logo_name, 0, logo_left, logo_top, logo_right, logo_bottom)) { Print("InfoPanel: logo.Create() failed (continuing without logo)"); delete logo; logo = NULL; } else { // Ensure logo is in foreground if(!ObjectSetInteger(ChartID(), logo_name, OBJPROP_BACK, false)) Print("InfoPanel: Failed to set OBJPROP_BACK=false for logo (non-fatal)."); // Log coordinates PrintFormat("InfoPanel: Logo coords: left=%d, top=%d, right=%d, bottom=%d", logo_left, logo_top, logo_right, logo_bottom); // Load built-in logo.bmp with correct relative path (no fullpath needed).You can use custom logo names as long you update the code. string bmp_path = "\\Images\\Logo.bmp"; bool ok = logo.BmpName(bmp_path); PrintFormat("InfoPanel: logo.BmpName('%s') returned %s", bmp_path, ok ? "true" : "false"); if(!ok) { Print("InfoPanel: LOGO load failed. Verify MT5 installation has MQL5\\Images\\dollar.bmp."); } else { Print("InfoPanel: Logo loaded successfully!"); } logo.Border(LOGO_SIZE); ChartRedraw(ChartID()); // Force redraw } } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(logo != NULL) { delete logo; logo = NULL; } txtDesc.Delete(); if(infoPanel != NULL) { delete infoPanel; infoPanel = NULL; } EventKillTimer(); } //+------------------------------------------------------------------+ //| OnTick | //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ //| Chart events | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { }
CPictureのロゴをCPanelの矩形内に配置し、前面に表示させることで、小さいながらも強力なデモを完成させました。これにより、3つの標準ライブラリクラス(CPanel、CChartObjectLabel、CPicture)を1つの統合されたUI要素に組み合わせることができました。手順としては、まずパネルを作成し、次に説明用ラベルを追加し、最後に正確なピクセル座標を計算してロゴを挿入しました。ロゴはパネルの右側にきれいに収まるように配置しています。パネルには「ObjectSetInteger(..., OBJPROP_BACK, true)」を呼び出し、ロゴには「OBJPROP_BACK = false」を設定して描画順を制御しました。これにより、ロゴが背景の上に、しかし他のインタラクティブ要素の背後に描画されます。さらにChartRedraw()を呼び出すことで、ビットマップ読み込み後にターミナルが即座に再描画されるようにしました。これらすべての手順は、これまでと同じ、インクルード → 宣言 → 生成 → 設定 → 戻り値チェック → クリーンアップの統合のリズムに従っています。
統合から得られた具体的な教訓としては、まず座標計算と命名の規律が重要であることが挙げられます。パネルの矩形に対して相対座標で位置を計算し、オブジェクト名はユニークかつ予測可能に設定することで、衝突を避けデバッグも容易になります。次に、外部リソースの扱いには注意が必要です。ビットマップはMQL5\Imagesに保存し、相対パス「\Images\Logo.bmp」をBmpName()に渡すことで、ファイルが存在しない場合でもEAが安定して動作するように成功や失敗をログに記録します。また、各クラスのライフサイクルを尊重することも重要です。オブジェクトはnewで確保し、Create()やBmpName()の呼び出し時に検証し、OnDeinit()で必ず削除してチャート上に残留オブジェクトを作らず、メモリリークも防ぎます。さらに、レイヤー管理や再描画の制御も実用的なスキルです。OBJPROP_BACKとChartRedraw()を用いることで、表示順序や描画タイミングを確実に制御できます。
最後に、この作業は、標準ライブラリの任意のクラスに応用できる再現可能なパターンを強化します。具体的には、公開されている主要な操作メソッド(Create、Border()/BmpName()/Text()などの設定用メソッド)を特定し、EAのライフサイクル(OnInit → 実行時 → OnDeinit)内で統合し、各ステップを明確なログで検証し、既存要素に対して相対的に座標を計算してレイアウトの保守性を確保する、という流れです。一見小さな機能、すなわちパネル内のロゴ表示であっても、これは小さく、挙動の安定したモジュールを組み合わせて、より大きくプロフェッショナルなインターフェースを構築する訓練場として機能します。ここから、パネルを拡張して応答性のあるレイアウトや追加の静的情報行、非インタラクティブアイコンなどを加える場合でも、標準ライブラリの任意のクラスを同じ規律ある手順で統合できることが自信を持って確認できます。
テスト
MetaEditor 5でコードをコンパイルした後、チャート上にデプロイして、説明文とロゴを備えた小さな情報パネルを確認しました(下の画像参照)。このパネルとその構成要素は、EAの視覚的なアイデンティティとして捉えることができます。同じ考え方を応用すれば、今後開発する製品に対しても、チャート上で独自かつ認識しやすい存在感を持たせることが可能です。ひとまず、今回のデモを通じてコンセプトを実証し、統合プロセスへの理解を深められたことを誇りに思うことができます。

図11:チャートでのInfoPanel_EAテスト
結論
今回の演習を通じて、標準ライブラリのクラスをEAに統合する再現可能な手順を確立することに成功しました。CPanelの生成から始まり、CChartObjectLabelによる説明ラベル、CPictureによるロゴの追加まで、異なるUIコンポーネントを組み合わせて制御することで、洗練され機能的な表示を作り上げる方法を学びました。この小さなデモであっても、より大規模で複雑なシステム構築に必要なアーキテクチャ的な規律を反映しています。
ここでの目標は、単なる静的情報パネルの作成に留まらず、MQL5のクラスエコシステム全体で応用可能な作業パターン(インクルード、宣言、初期化、設定、クリーンアップ)を体得することでした。今後の連載において、この基礎演習がより高度なコントロールや複合モジュールを扱う際に役立ちます。
さらに改善や創造的なバリエーションは常に可能ですが、今回の演習で得た本質的な理解は、モジュール化されたクラスがどのように相互作用し、小さな再利用可能パーツが完全なアプリケーションへと発展するかを把握することです。反復練習を強く推奨します。今回の例を再現し、カスタムカラーや代替レイアウト、CButton、CDialog、CCheckBoxなどの新しいクラスを試すことで、MQL5のオブジェクトモデルに対する直感がさらに深まります。
モジュール化こそが成長の鍵です。標準ライブラリにはすでに強力なビルディングブロックが存在しており、開発者としての私たちの役割は、それらを組み合わせて知的なシステムを構築することにあります。この技術を極めたいのであれば、オブジェクト指向プログラミング(OOP)の学習に時間を割くことが不可欠です。OOPの理解は、スケーラブルなフレームワークを設計、拡張、保守する洞察力を与えてくれます。
表面的にはシンプルに見える演習ですが、プロフェッショナルなMQL5開発への旅路における重要な一筆となります。多くのことに触れましたが、まだまだ学ぶべきことは多くあります。添付ファイルには完全な実装が含まれており、自由にテスト、改良、意見交換が可能です。以下でコメントや創造的な拡張を共有し、さらに有意義な学びを深めましょう。
| ファイル名 | 詳細 |
|---|---|
| InfoPanel_EA.mq5 | 複数の標準ライブラリクラスをEA内で統合する方法を示す例。チャート上に情報パネルを作成するCPanel、説明テキストを表示するCChartObjectLabel、ロゴ画像を埋め込むCPictureを使用。MQL5におけるモジュール化UI構築とクラス間の相互作用を理解する基礎として活用できます。 |
| Logo.zip | サポートされているBMP形式のロゴを含むファイル。ターミナルのImagesディレクトリに展開してください。オープンソースツール(GIMPなど)を用いて自分好みのカスタムロゴを作成することも推奨されます。 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19834
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
プライスアクション分析ツールキットの開発(第46回):MQL5におけるスマートな可視化を備えたインタラクティブフィボナッチリトレースメントEAの設計
MQL5で自己最適化エキスパートアドバイザーを構築する(第15回):線形系同定
知っておくべきMQL5ウィザードのテクニック(第85回):ストキャスティクスとFrAMAのパターンを用いたβ-VAEによる推論
知っておくべきMQL5ウィザードのテクニック(第84回):ストキャスティクスとFrAMAのパターンの使用 - 結論
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索