English
preview
MQL5取引ツール(第9回):EA向けスクロール可能ガイド付き初回実行ユーザー設定ウィザードの開発

MQL5取引ツール(第9回):EA向けスクロール可能ガイド付き初回実行ユーザー設定ウィザードの開発

MetaTrader 5トレーディング |
18 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第8回)では、MetaQuotes Language 5 (MQL5)を用いて、マルチシンボルのポジションと口座情報を監視する情報ダッシュボードを開発しました。第9回では、プログラムを初めて実行するユーザー向けに動的な初回実行ウィザードを作成します。初回実行設定ウィザードは、MetaTrader 5のエキスパートアドバイザー(EA)などの複雑なシステムの設定を簡素化するための重要なツールであり、新しいユーザーを初期設定に導き、最適なパフォーマンスを確保するためのオリエンテーションモデルを提供します。本記事では、スクロール可能なダッシュボード、動的テキスト、インタラクティブなボタン、および設定を簡略化するチェックボックスを備えたMQL5初回実行ユーザー設定ウィザードを開発します。このウィザードは、プログラムの初回起動時にのみ実行されます。本記事では以下のトピックを扱います。

  1. 取引プログラムにおける初回実行設定ガイドの役割と価値の理解
  2. MQL5での実装
  3. 設定ウィザードのテスト
  4. 結論

この記事を読み終える頃には、EAの初期化を強化するインタラクティブなMQL5ウィザードを作成でき、取引ニーズに合わせてカスタマイズできるようになります。それでは始めましょう。


取引プログラムにおける初回実行設定ガイドの役割と価値の理解

初回実行設定ガイドは、MetaTrader 5のEAなどの取引プログラムにおいて非常に重要な機能です。ロットサイズ、リスクレベル、取引フィルターなどの重要な設定をステップごとに指示することで、過大なドローダウンを招く大きすぎるロットサイズの設定など、損失につながる可能性のある誤設定を避ける手助けをします。これは、新しいユーザーにプログラムの構造や機能を紹介するオリエンテーションとして機能します。その価値は、経験の有無にかかわらずトレーダーのオンボーディングプロセスを簡素化し、プログラムの適切な初期設定を保証する点にあります。また、一度ガイドを表示したかどうかを記憶する仕組みを活用することで、将来の初回実行時に不要なプロンプトが表示されず、特に複数回プログラムをチャートにアタッチするトレーダーにとってユーザー体験を最適化できます。

本記事のアプローチは、直感的でスクロール可能なダッシュボードを設計し、視覚的に区別されたテキスト(強調された見出しやサポート用のクリック可能なリンクなど)で明確な設定ガイドを表示し、ユーザー操作用のインタラクティブなボタンや、今後の実行でガイドをスキップするか選択できるチェックボックスを備えることです。MQL5のグローバル変数機能を活用して、ユーザーの選択を保存し、プログラムが初めて実行された際の取引ターミナルのビルド番号(ソフトウェアバージョン)やオペレーティングシステム(OS)を記録し、参照できるようにします。また、ヘッダ、本文、フッタを備えた中央配置のインターフェースを作成し、可読性を高める動的テキストフォーマット、包括的な指示を表示するスクロール可能なコンテンツ、異なる画面解像度に対応する適応サイズを組み込みます。これにより、リスクパラメータの設定やAutoTradingの有効化などのステップをトレーダーが容易に実行でき、設定プロセスをシームレスかつ効率的におこなえるようになります。設定ガイドは、組み込みの「ワンクリック取引」セットアップガイドの構造に従って構築します。

アプローチフレームワークの比較

添付画像から、本記事で採用するアプローチの概要が確認できます。必要に応じて、他のプログラムと区別するためにスケール調整されたプログラム画像を追加します。以下のようなイメージを目指しています。

ウィザード初期実行フレームワークGIF


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用するグローバル変数をいくつか宣言する必要があります。

//+------------------------------------------------------------------+
//|                                      EA Initialization Setup.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict
#property icon "1. Forex Algo-Trader.ico"

//+------------------------------------------------------------------+
//| Global variables for setup                                       |
//+------------------------------------------------------------------+
string GV_SETUP = "";                             //--- Store global variable name with build and OS
string g_scaled_image_resource = "";              //--- Store name of scaled image resource
int g_mainX = 0;                                  //--- Store calculated x-coordinate of main container
int g_mainY = 50;                                 //--- Set starting y-coordinate 50px below chart top
int g_mainWidth = 500;                            //--- Set main container width
int g_headerHeight = 50;                          //--- Set header height
int g_footerHeight = 40;                          //--- Set footer height
int g_padding = 10;                               //--- Set general padding
int g_textPadding = 10;                           //--- Set text padding
int g_spacing = 0;                                //--- Set spacing between header/body/footer
int g_lineSpacing = 3;                            //--- Set spacing between text lines
int g_minBodyHeight = 200;                        //--- Set minimum body height
int g_maxBodyHeight = 400;                        //--- Set maximum body height
int g_bottomMargin = 50;                          //--- Set bottom margin
int g_displayHeight = 0;                          //--- Store calculated display height
int g_mainHeight = 0;                             //--- Store calculated main container height
int g_adjustedLineHeight = 0;                     //--- Store adjusted line height for scrolling
int g_max_scroll = 0;                             //--- Store maximum scroll to prevent overflow
bool scroll_visible = false;                      //--- Track scrollbar visibility
bool mouse_in_body = false;                       //--- Track if mouse is in body
int scroll_pos = 0;                               //--- Store current scroll position
int prev_scroll_pos = -1;                         //--- Store previous scroll position
int slider_height = 20;                           //--- Set default slider height
bool movingStateSlider = false;                   //--- Track slider drag state
int mlbDownX_Slider = 0;                          //--- Store mouse x on slider click
int mlbDownY_Slider = 0;                          //--- Store mouse y on slider click
int mlbDown_YD_Slider = 0;                        //--- Store slider y on click
int g_total_height = 0;                           //--- Store total text height
int g_visible_height = 0;                         //--- Store visible text height
bool checkbox_checked = false;                    //--- Track checkbox state
bool ok_button_hovered = false;                   //--- Track OK button hover
bool cancel_button_hovered = false;               //--- Track Cancel button hover
bool checkbox_hovered = false;                    //--- Track checkbox hover
bool header_cancel_hovered = false;               //--- Track header cancel hover
bool scroll_up_hovered = false;                   //--- Track scroll up button hover
bool scroll_down_hovered = false;                 //--- Track scroll down button hover
bool scroll_slider_hovered = false;               //--- Track scroll slider hover
string ea_name = "Expert Advisor Setup Wizard";   //--- Set EA name
const int MAX_LINES = 100;                        //--- Set maximum text lines

//+------------------------------------------------------------------+
//| Enum for scrollbar mode                                          |
//+------------------------------------------------------------------+
enum ENUM_SCROLLBAR_MODE {                         // Define scrollbar visibility modes
   SCROLL_ALWAYS,                                  // Show scrollbar if needed
   SCROLL_ON_HOVER,                                // Show scrollbar on hover if needed
   SCROLL_NEVER                                    // Never show scrollbar, wheel only
};
ENUM_SCROLLBAR_MODE ScrollbarMode = SCROLL_ALWAYS; // Scrollbar shows when needed and remains

//+------------------------------------------------------------------+
//| Scrollbar object names                                           |
//+------------------------------------------------------------------+
#define SCROLL_LEADER "Setup_Scroll_Leader"        //--- Define scroll leader name
#define SCROLL_UP_REC "Setup_Scroll_Up_Rec"        //--- Define scroll up rectangle name
#define SCROLL_UP_LABEL "Setup_Scroll_Up_Label"    //--- Define scroll up label name
#define SCROLL_DOWN_REC "Setup_Scroll_Down_Rec"    //--- Define scroll down rectangle name
#define SCROLL_DOWN_LABEL "Setup_Scroll_Down_Label" //--- Define scroll down label name
#define SCROLL_SLIDER "Setup_Scroll_Slider"        //--- Define scroll slider name

//+------------------------------------------------------------------+
//| Dashboard object names                                           |
//+------------------------------------------------------------------+
#define SETUP_MAIN "Setup_MainContainer"           //--- Define main container name
#define SETUP_HEADER_BG "Setup_HeaderBg"           //--- Define header background name
#define SETUP_HEADER_IMAGE "Setup_HeaderImage"     //--- Define header image name
#define SETUP_HEADER_TITLE "Setup_HeaderTitle"     //--- Define header title name
#define SETUP_HEADER_SUBTITLE "Setup_HeaderSubtitle" //--- Define header subtitle name
#define SETUP_HEADER_CANCEL "Setup_HeaderCancel"   //--- Define header cancel button name
#define SETUP_BODY_BG "Setup_BodyBg"               //--- Define body background name
#define SETUP_FOOTER_BG "Setup_FooterBg"           //--- Define footer background name
#define SETUP_CHECKBOX_BG "Setup_CheckboxBg"       //--- Define checkbox background name
#define SETUP_CHECKBOX_LABEL "Setup_CheckboxLabel" //--- Define checkbox label name
#define SETUP_CHECKBOX_TEXT "Setup_CheckboxText"   //--- Define checkbox text name
#define SETUP_OK_BUTTON "Setup_OkButton"           //--- Define OK button name
#define SETUP_CANCEL_BUTTON "Setup_CancelButton"   //--- Define Cancel button name

//+------------------------------------------------------------------+
//| Enhanced setup text                                              |
//+------------------------------------------------------------------+
string setup_text =                                //--- Define setup guide text
"\nExpert Advisor Initialization Guide\n\n"
"Welcome to the Expert Advisor Setup Wizard – Your Gateway to Automated Trading in MetaTrader 5!\n\n"
"Unlock the power of algorithmic trading with this comprehensive setup guide. Designed for seamless integration, this wizard ensures your EA is configured optimally for performance, risk management, and reliability across diverse market conditions.\n\n"
"Key Features:\n"
"- Versatile Configuration: Tailor parameters for lot sizing, magic numbers, stop losses, and take profits to suit your trading style and broker requirements.\n"
"- Risk Controls: Implement drawdown limits, position sizing rules, and equity protection mechanisms to safeguard your capital.\n"
"- Filter Integration: Apply time-based, spread, and news filters to avoid unfavorable trading environments and enhance entry precision.\n"
"- Monitoring Tools: Access real-time panels for trade tracking, performance metrics, and alert notifications.\n"
"- Backtesting Support: Optimize settings with historical data, ensuring robust strategies before live deployment.\n"
"- Broker Adaptability: Supports netting and hedging modes, with customizable slippage and execution tolerances.\n\n"
"Initial Setup Instructions:\n"
"1. Attach the EA to a new chart of your selected symbol (e.g., EURUSD) on an appropriate timeframe (e.g., M15 for intraday strategies).\n"
"2. Adjust core inputs: Define risk parameters, enable/disable filters, and set notification preferences to align with your objectives.\n"
"3. Activate AutoTrading: Ensure MT5's AutoTrading is enabled, and verify EA permissions for secure operation.\n"
"4. Customize Interfaces: Toggle visibility of info panels, trade managers, and alerts for an intuitive user experience.\n"
"5. Validate Setup: Run a forward test on demo to confirm functionality and fine-tune based on observed behavior.\n\n"
"Important Notes:\n"
"- Risk Disclaimer: Automated trading carries inherent risks. Always use appropriate leverage and start with conservative settings on a demo account.\n"
"- Compatibility Check: Confirm broker supports required features like hedging; monitor spreads during volatile periods.\n"
"- Optimization Tips: Regularly review performance logs and adjust filters to adapt to evolving market dynamics.\n"
"- Security Measures: Use unique magic numbers and enable two-factor authentication for account protection.\n"
"- Legal Notice: No guarantees of profitability. Trade responsibly and consult professionals as needed.\n\n"
"Contact Methods:\n"
"NB:\n"
"********************************************\n"
" >*** FOR SUPPORT, QUERIES, OR CUSTOMIZATIONS, REACH OUT IMMEDIATELY: ***<\n"
" __________________________________________\n\n"
" 1. Email: mutiiriallan.forex@gmail.com (Primary Support Channel)\n"
" 2. Telegram Channel: @ForexAlgo-Trader (Updates & Community)\n"
" 3. Telegram Group: https://t.me/Forex_Algo_Trader (Direct Assistance & Discussions)\n\n"
"********************************************\n\n"
"Thank you for choosing our Expert Advisor solutions. Configure wisely, trade confidently, and elevate your trading journey! 🚀\n";

ウィザードの基盤を設定するには、グローバル変数を使用してダッシュボードのレイアウトを管理します。座標としてg_mainXを0、g_mainYを50に設定し、寸法としてg_mainWidthを500、g_headerHeightを50、g_footerHeightを40に設定します。また、パディングとしてg_padding、g_textPaddingを10に設定します。間隔はg_spacingを0、g_lineSpacingを3に設定し、本文の高さ制限としてg_minBodyHeightを200、g_maxBodyHeightを400に、マージンとしてg_bottomMarginを50に設定します。スクロール用にはscroll_visible、scroll_posを0、slider_heightを20に設定します。マウス操作の状態としてmovingStateSliderとmlbDownX_Sliderを使用します。ボタンやチェックボックスのホバー状態用のフラグも追加します。ea_nameは「Expert Advisor Setup Wizard」とし、MAX_LINESは100に設定します。

ENUM_SCROLLBAR_MODE列挙型はスクロールバーの動作を定義し、SCROLL_ALWAYS、SCROLL_ON_HOVER、SCROLL_NEVERの3つを持ち、デフォルトはSCROLL_ALWAYSです。ダッシュボードやスクロールバー要素の一貫した命名のために、SETUP_MAIN、SETUP_HEADER_BG、SCROLL_LEADERなどのオブジェクト名用の定数も定義します。最後にsetup_text文字列を作成します。これは、機能、設定手順、注意事項、連絡方法などのセクションを見出しや番号付き手順でフォーマットした包括的なガイドであり、ウィザードのインターフェースとコンテンツをユーザーが操作できるように整理するためのシステムです。位置や内容は自由に変更可能で、ここでは任意の値を使用しました。次に設定する必要があるのは、ヘッダアイコンとして使用する画像です。画像を使用したくない場合は、このステップはスキップできます。画像ファイルはビットマップ(BMP)形式に変換する必要があります。変換後の画像は、次に示すプロパティを満たす必要があります。

ビットマップファイルイメージ

画像を見ると、使用する画像はビットマップファイルであることがわかります。サイズや寸法については心配する必要はありません。必要に応じて後で拡大縮小できます。ファイルはプログラムファイルと同じフォルダに配置してください。次におこなうのは、ファイルをプログラムのリソースとして追加することです。

#resource "1. Forex Algo-Trader SQ.bmp"
#define resourceImg "::1. Forex Algo-Trader SQ.bmp"

#resourceディレクティブを使用して、1. Forex Algo-Trader SQ.bmpという名前の画像ファイルを含め、resourceImgという定数を::1. Forex Algo-Trader SQ.bmpとして定義します。これにより、ウィザードのダッシュボードにプロフェッショナルで一貫性のあるブランド体験を提供できます。次に、インターフェースの作成を開始します。そのためにいくつかのヘルパー関数が必要です。必要な矩形ラベル、テキスト、画像、ボタンを作成する関数を定義していきます。

//+------------------------------------------------------------------+
//| Create rectangle label                                           |
//+------------------------------------------------------------------+
bool createRecLabel(string objName, int xD, int yD, int xS, int yS,
                    color clrBg, int widthBorder, color clrBorder = clrNONE,
                    ENUM_BORDER_TYPE borderType = BORDER_FLAT,
                    ENUM_LINE_STYLE borderStyle = STYLE_SOLID,
                    ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label
      Print(__FUNCTION__, ": failed to create rec label! Error code = ", GetLastError()); //--- Log failure
      return false;                                               //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS);               //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS);               //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg);          //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type
   ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle);      //--- Set border style
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder);      //--- Set border width
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder);        //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selection
   return true;                                                   //--- Return success
}

//+------------------------------------------------------------------+
//| Create button                                                    |
//+------------------------------------------------------------------+
bool createButton(string objName, int xD, int yD, int xS, int yS,
                  string txt = "", color clrTxt = clrBlack, int fontSize = 12,
                  color clrBg = clrNONE, color clrBorder = clrNONE,
                  string font = "Arial",
                  ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, bool isBack = false) {
   ResetLastError();                                              //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) {          //--- Create button
      Print(__FUNCTION__, ": failed to create the button! Error code = ", GetLastError()); //--- Log failure
      return false;                                               //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS);               //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS);               //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, txt);                //--- Set button text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt);           //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);      //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);               //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg);          //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_BACK, isBack);            //--- Set background/foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selection
   return true;                                                   //--- Return success
}

//+------------------------------------------------------------------+
//| Create text label                                                |
//+------------------------------------------------------------------+
bool createLabel(string objName, int xD, int yD,
                 string txt, color clrTxt = clrBlack, int fontSize = 12,
                 string font = "Arial",
                 ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) {           //--- Create label
      Print(__FUNCTION__, ": failed to create the label! Error code = ", GetLastError()); //--- Log failure
      return false;                                               //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, txt);                //--- Set label text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt);           //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);      //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font);               //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor);          //--- Set anchor
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selection
   return true;                                                   //--- Return success
}

//+------------------------------------------------------------------+
//| Create bitmap label                                              |
//+------------------------------------------------------------------+
bool createBitmapLabel(string objName, int xD, int yD, int xS, int yS,
                       string bitmapPath, color clr, ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) {
   ResetLastError();                                              //--- Reset error code
   if (!ObjectCreate(0, objName, OBJ_BITMAP_LABEL, 0, 0, 0)) {    //--- Create bitmap label
      Print(__FUNCTION__, ": failed to create bitmap label! Error code = ", GetLastError()); //--- Log failure
      return false;                                               //--- Return failure
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD);           //--- Set x distance
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD);           //--- Set y distance
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS);               //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS);               //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_CORNER, corner);          //--- Set corner
   ObjectSetString(0, objName, OBJPROP_BMPFILE, bitmapPath);      //--- Set bitmap path
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);              //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set to foreground
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);            //--- Disable state
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);       //--- Disable selectability
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);         //--- Disable selection
   return true;                                                   //--- Return success
}

ここでは、インタラクティブダッシュボードのコアグラフィカルコンポーネントを実装します。まず、createRecLabel関数を作成します。この関数は、指定した座標、サイズ、背景色、枠線幅、タイプ(BORDER_FLAT)、スタイル(STYLE_SOLID)、コーナー(CORNER_LEFT_UPPER)で矩形ラベル(OBJ_RECTANGLE_LABEL)を作成し、ObjectCreateObjectSetIntegerを使用してプロパティを設定します。作成に失敗した場合はPrintでエラーをログに記録し、選択不可の前面表示に設定します。次に、createButton関数を実装します。この関数は、テキスト、色、フォントサイズ、フォント(デフォルトArial)、背景、枠線、コーナーを指定してボタン(OBJ_BUTTON)を作成し、同様の形式でプロパティを設定します。

続いて、createLabel関数を作成します。この関数は、テキスト、色、フォントサイズ、フォント、コーナー、アンカー(ANCHOR_LEFT_UPPER)を指定してテキストラベル(OBJ_LABEL)を生成し、必要に応じてエラーをログに記録します。最後に、createBitmapLabel関数を作成します。この関数は、座標、サイズ、ビットマップパス、色、コーナーを指定して画像用のビットマップラベル(OBJ_BITMAP_LABEL)を作成し、ObjectCreateで生成後、選択不可の前面表示になるようにプロパティを設定し、失敗した場合はログに記録します。これにより、ウィザードのコンテナ、ボタン、テキスト、画像などのビジュアル要素を描画するシステムを構築できます。

次に、ダッシュボードの高さを計算するためのユーティリティ関数を作成します。これは、ダッシュボードを動的に中央配置し、画面解像度に応じてテキストフォントを動的に取得し、異なるデバイスでテキストが小さすぎたり大きすぎたりしないように調整し、長すぎるテキストはオーバーフローを避けるために切り詰める目的です。

//+------------------------------------------------------------------+
//| Calculate font size based on screen DPI                          |
//+------------------------------------------------------------------+
int getFontSizeByDPI(int baseFontSize, int baseDPI = 96) {
   int currentDPI = (int)TerminalInfoInteger(TERMINAL_SCREEN_DPI); //--- Retrieve current screen DPI
   int scaledFontSize = (int)(baseFontSize * (double)baseDPI / currentDPI); //--- Calculate scaled font size
   return scaledFontSize;                                          //--- Return scaled font size
}

//+------------------------------------------------------------------+
//| Calculate dashboard dimensions                                   |
//+------------------------------------------------------------------+
void CalculateDashboardDimensions() {
   long chart_width = ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);   //--- Get chart width
   long chart_height = ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height
   g_mainX = (int)((chart_width - g_mainWidth) / 2); //--- Center main container horizontally
   int available_height = (int)(chart_height - g_mainY - g_bottomMargin - g_headerHeight - g_footerHeight - 2 * g_spacing); //--- Calculate available height
   g_displayHeight = MathMin(g_maxBodyHeight, MathMax(g_minBodyHeight, available_height)); //--- Set display height
   g_mainHeight = g_headerHeight + g_displayHeight + g_footerHeight + 2 * g_spacing; //--- Calculate main container height
}

//+------------------------------------------------------------------+
//| Truncate string                                                  |
//+------------------------------------------------------------------+
string truncateString(string valueStr, int startPos, int lengthStr = -1, int threshHold = 0, bool isEllipsis = false) {
   string result = valueStr;                                        //--- Initialize result
   if (StringLen(valueStr) > threshHold && threshHold > 0) {        //--- Check if truncation needed
      result = StringSubstr(valueStr, startPos, lengthStr);         //--- Extract substring
      if (isEllipsis) result += "...";                              //--- Add ellipsis if needed
   }
   return result;                                                   //--- Return truncated string
}

ここでは、適応サイズとテキストフォーマットを確実にするためのユーティリティ関数を実装します。まず、getFontSizeByDPI関数を作成します。この関数は、TerminalInfoIntegerを使用してTERMINAL_SCREEN_DPIから画面のDPIを取得し、基準DPI(96)に対する比率でベースフォントサイズを調整してスケーリングしたフォントサイズを計算し、デバイス間で一貫したテキスト表示を可能にする値を返します。

次に、CalculateDashboardDimensions関数を作成します。この関数は、ChartGetIntegerを使用してCHART_WIDTH_IN_PIXELSとCHART_HEIGHT_IN_PIXELSからチャートの幅と高さを取得し、g_mainXをチャート幅とg_mainWidthの差の半分に設定してメインコンテナを水平中央に配置します。利用可能な高さは、チャートの高さからg_mainY、g_bottomMargin、g_headerHeight、g_footerHeight、およびg_spacingの2倍を引くことで計算します。g_displayHeightはMathMinMathMaxを使用してg_minBodyHeightとg_maxBodyHeightの範囲内に設定し、g_mainHeightはヘッダ、本文、フッタの高さと間隔の合計として計算します。

最後にtruncateString関数を実装します。この関数は、文字列の長さがしきい値未満または0の場合は入力文字列をそのまま返し、それ以外の場合はStringSubstrを使用してstartPosからlengthStr文字分の部分文字列を抽出し、必要に応じて省略記号を追加してテキストのオーバーフローを管理します。これらの関数を用いることで、メインダッシュボードの作成を開始できます。そのロジックはモジュール化のために関数内にまとめて実装します。

//+------------------------------------------------------------------+
//| Show the setup dashboard                                         |
//+------------------------------------------------------------------+
void ShowDashboard() {
   checkbox_checked = false;                         //--- Reset checkbox state
   createRecLabel(SETUP_MAIN, g_mainX, g_mainY, g_mainWidth, g_mainHeight, C'20,20,20', 1, C'40,40,40', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create main container
   createRecLabel(SETUP_HEADER_BG, g_mainX, g_mainY, g_mainWidth, g_headerHeight, C'45,45,45', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create header background

}

ShowDashboard関数を実装します。まず、チェックボックスの状態をfalseにリセットし、ユーザー操作のためにクリーンな初期状態を確保します。次に、createRecLabelを呼び出して、メインコンテナSETUP_MAINを座標g_mainX、g_mainYに、寸法g_mainWidth、g_mainHeightで描画します。背景色はダークグレー(C'20,20,20')、枠線は1ピクセル(C'40,40,40')、枠線タイプはフラット、スタイルはソリッド、コーナーは左上に設定します。

続いて、createRecLabelを使用してヘッダ背景SETUP_HEADER_BGを作成します。x座標は同じg_mainX、y座標はg_mainYに設定し、幅g_mainWidth、高さg_headerHeightで描画します。背景色はやや明るいグレー(C'45,45,45')、枠線はC60,60,60とし、スタイルを一貫させることでウィザードのダッシュボードの基本的な視覚構造を形成します。この関数はOnInitイベントハンドラで呼び出す必要があります。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   int build = (int)TerminalInfoInteger(TERMINAL_BUILD); //--- Get terminal build number
   string os = TerminalInfoString(TERMINAL_OS_VERSION);  //--- Get operating system version
   StringReplace(os, " ", "_");                          //--- Replace spaces with underscores
   StringReplace(os, ".", "_");                          //--- Replace dots with underscores
   StringReplace(os, ",", "_");                          //--- Replace commas with underscores
   GV_SETUP = "EA_Setup_" + IntegerToString(build) + "_" + os; //--- Set global variable name
   CalculateDashboardDimensions();                       //--- Calculate dashboard dimensions
   if (!GlobalVariableCheck(GV_SETUP)) {                 //--- Check if global variable exists
      Print("Global variable '" + GV_SETUP + "' not found. Creating new one with value FALSE (0.0)."); //--- Log variable creation
      GlobalVariableSet(GV_SETUP, 0.0);                  //--- Set variable to false
      ShowDashboard();                                   //--- Display dashboard
   } else {                                              //--- Variable exists
      double val = GlobalVariableGet(GV_SETUP);          //--- Get variable value
      if (val == 1.0) {                                  //--- Check if set to never show
         // No dashboard
      } else {                                           //--- Show dashboard
         ShowDashboard();                                //--- Display dashboard
      }
   }
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);     //--- Enable mouse move events
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);    //--- Enable mouse wheel events
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);         //--- Enable chart scrolling
   return(INIT_SUCCEEDED);                               //--- Return initialization success
}

OnInit関数内で表示動作を管理する初期化ロジックを実装します。まず、TerminalInfoIntegerを使用してTERMINAL_BUILDからMetaTrader 5ターミナルのビルド番号を取得し、TerminalInfoStringTERMINAL_OS_VERSIONからOSバージョンを取得します。OS文字列内のスペース、ドット、カンマをStringReplaceでアンダースコアに置換し、クリーンなグローバル変数名GV_SETUPを作成します。フォーマットはEA_Setup_<build>_<OS>です。任意にマジックナンバーやプログラム名とバージョン番号を使用することも可能ですが、この組み合わせは独創的でユニークです。次にCalculateDashboardDimensionsを呼び出し、チャートの寸法に基づいてダッシュボードのレイアウトを設定します。

次に、GlobalVariableCheckでグローバル変数が存在するか確認します。存在しない場合はPrintで作成をログに記録し、GlobalVariableSetで0.0 (false)に設定し、ShowDashboardでダッシュボードを表示します。変数が存在する場合はGlobalVariableGetで値を取得し、値が1.0でない場合にのみダッシュボードを表示します(1.0はユーザーが再表示を希望しなかったことを示します)。最後に、ChartSetIntegerを使用してCHART_EVENT_MOUSE_MOVE、CHART_EVENT_MOUSE_WHEEL、CHART_MOUSE_SCROLLを有効にし、OnChartEventイベントハンドラでのインタラクティブ機能をサポートします。初期化が成功した場合はINIT_SUCCEEDEDを返します。コンパイルすると、次の結果が得られます。

ヘッダ付きベースダッシュボード

ヘッダセクションができたので、ここに画像ファイルを追加します。画像を拡大縮小する必要があるため、まず画像のスケーリングをおこなう関数を定義し、その後で画像ファイルのスケーリングに呼び出します。

//+------------------------------------------------------------------+
//| Scale image using bicubic interpolation                          |
//+------------------------------------------------------------------+
void ScaleImage(uint &pixels[], int original_width, int original_height, int new_width, int new_height) {
   uint scaled_pixels[];                                               //--- Declare scaled pixel array
   ArrayResize(scaled_pixels, new_width * new_height);                 //--- Resize scaled pixel array
   for (int y = 0; y < new_height; y++) {                              //--- Iterate through new height
      for (int x = 0; x < new_width; x++) {                            //--- Iterate through new width
         double original_x = (double)x * original_width / new_width;   //--- Calculate original x
         double original_y = (double)y * original_height / new_height; //--- Calculate original y
         uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate pixel
         scaled_pixels[y * new_width + x] = pixel;                     //--- Store scaled pixel
      }
   }
   ArrayResize(pixels, new_width * new_height);                        //--- Resize original pixel array
   ArrayCopy(pixels, scaled_pixels);                                   //--- Copy scaled pixels
}

//+------------------------------------------------------------------+
//| Perform bicubic interpolation for a single pixel                 |
//+------------------------------------------------------------------+
uint BicubicInterpolate(uint &pixels[], int width, int height, double x, double y) {
   int x0 = (int)x;                                                    //--- Get integer x coordinate
   int y0 = (int)y;                                                    //--- Get integer y coordinate
   double fractional_x = x - x0;                                       //--- Calculate fractional x
   double fractional_y = y - y0;                                       //--- Calculate fractional y
   int x_indices[4], y_indices[4];                                     //--- Declare index arrays
   for (int i = -1; i <= 2; i++) {                                     //--- Calculate indices
      x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1);       //--- Clamp x indices
      y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1);      //--- Clamp y indices
   }
   uint neighborhood_pixels[16];                                       //--- Declare neighborhood pixels
   for (int j = 0; j < 4; j++) {                                       //--- Iterate y indices
      for (int i = 0; i < 4; i++) {                                    //--- Iterate x indices
         neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Store pixel
      }
   }
   uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare color components
   for (int i = 0; i < 16; i++) {                                      //--- Extract components
      GetArgb(neighborhood_pixels[i], alpha_components[i], red_components[i], green_components[i], blue_components[i]); //--- Get ARGB components
   }
   uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate alpha
   uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate red
   uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate green
   uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate blue
   return (alpha_out << 24) | (red_out << 16) | (green_out << 8) | blue_out; //--- Combine components
}

//+------------------------------------------------------------------+
//| Perform bicubic interpolation for a single color component       |
//+------------------------------------------------------------------+
double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) {
   double weights_x[4];                                                 //--- Declare x weights
   double t = fractional_x;                                             //--- Set x fraction
   weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t);                 //--- Calculate x weight 0
   weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1);                  //--- Calculate x weight 1
   weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t);             //--- Calculate x weight 2
   weights_x[3] = (0.5 * t * t * t - 0.5 * t * t);                      //--- Calculate x weight 3
   double y_values[4];                                                  //--- Declare y values
   for (int j = 0; j < 4; j++) {                                        //--- Iterate y indices
      y_values[j] = weights_x[0] * components[j * 4 + 0] + weights_x[1] * components[j * 4 + 1] +
                    weights_x[2] * components[j * 4 + 2] + weights_x[3] * components[j * 4 + 3]; //--- Calculate y value
   }
   double weights_y[4];                                                 //--- Declare y weights
   t = fractional_y;                                                    //--- Set y fraction
   weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t);                 //--- Calculate y weight 0
   weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1);                  //--- Calculate y weight 1
   weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t);             //--- Calculate y weight 2
   weights_y[3] = (0.5 * t * t * t - 0.5 * t * t);                      //--- Calculate y weight 3
   double result = weights_y[0] * y_values[0] + weights_y[1] * y_values[1] +
                   weights_y[2] * y_values[2] + weights_y[3] * y_values[3]; //--- Calculate final value
   return MathMax(0, MathMin(255, result));        //--- Clamp result to valid range
}

//+------------------------------------------------------------------+
//| Extract ARGB components from a pixel                             |
//+------------------------------------------------------------------+
void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) {
   alpha = (uchar)((pixel >> 24) & 0xFF);                               //--- Extract alpha component
   red = (uchar)((pixel >> 16) & 0xFF);                                 //--- Extract red component
   green = (uchar)((pixel >> 8) & 0xFF);                                //--- Extract green component
   blue = (uchar)(pixel & 0xFF);                                        //--- Extract blue component
}

ここでは、画像処理関数を実装して視覚品質を向上させます。まず、ScaleImage関数を作成します。この関数は、ArrayResizeを使用して新しいピクセル配列をターゲット寸法(new_width×new_height)で作成し、各ピクセルを反復処理して座標を元の画像に比例スケーリングでマッピングし、BicubicInterpolateを呼び出して補間ピクセル値を計算し、スケールされた配列に格納してからArrayCopyで元の配列にコピーします。

次に、BicubicInterpolate関数を作成します。この関数は、非整数座標(x、y)でのピクセルの色を計算します。4×4のピクセル近傍を選択し、MathMinMathMaxでインデックスを画像境界内にクランプし、GetArgbでARGB成分を抽出し、BicubicInterpolateComponentで各成分を補間し、ビット演算で最終的なピクセル値に結合します。さらに、BicubicInterpolateComponent関数を実装します。この関数は単一の色成分に対して三次補間を適用し、xとyの小数座標に対する三次の重みを計算し、4×4グリッドから中間y値を算出してyの重みで結合し、結果を0から255の範囲にクランプします。最後にGetArgb関数では、ビットシフトとマスクを使用してピクセルからアルファ、赤、緑、青の各成分を抽出します。これにより、ダッシュボードや指定領域に合わせて画像を滑らかにスケーリングする手法が実現できます。これで、リソース画像をスケーリングして表示するためにこの関数を呼び出せます。

uint img_pixels[];                                       //--- Declare pixel array for image
uint orig_width = 0, orig_height = 0;                    //--- Initialize image dimensions
bool image_loaded = ResourceReadImage(resourceImg, img_pixels, orig_width, orig_height); //--- Load image resource
if (image_loaded && orig_width > 0 && orig_height > 0) { //--- Check image load success
   ScaleImage(img_pixels, (int)orig_width, (int)orig_height, 40, 40); //--- Scale image to 40x40
   g_scaled_image_resource = "::SetupHeaderImageScaled"; //--- Set scaled image resource name
   if (ResourceCreate(g_scaled_image_resource, img_pixels, 40, 40, 0, 0, 40, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled resource
      createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, g_scaled_image_resource, clrWhite, CORNER_LEFT_UPPER); //--- Create scaled image label
   } else {                                              //--- Handle resource creation failure
      Print("Failed to create scaled image resource");   //--- Log failure
      createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, resourceImg, clrWhite, CORNER_LEFT_UPPER); //--- Use original image
   }
} else {                                                 //--- Handle image load failure
   Print("Failed to load original image resource");      //--- Log failure
   createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, resourceImg, clrWhite, CORNER_LEFT_UPPER); //--- Use original image
}

画像の読み込みとスケーリングロジックを実装するために、まずピクセル配列img_pixelsを宣言し、寸法orig_widthとorig_heightを0に初期化します。その後、ResourceReadImageを使用してresourceImgからリソース画像を読み込み、成功したか(image_loadedおよび寸法が0より大きいか)を確認します。成功した場合はScaleImageを呼び出して40×40ピクセルにリサイズします(任意に変更可能)、g_scaled_image_resourceを::SetupHeaderImageScaledに設定し、ARGB形式でResourceCreateを使用して新しいリソースを作成します。その後、createBitmapLabelを使用してヘッダの位置に白色でスケーリング画像を表示します。リソース作成に失敗した場合はインスタンスをログに記録し、元の画像にフォールバックします。読み込み自体が失敗した場合もログを記録し、createBitmapLabel関数で元のresourceImgを直接使用します。コンパイルすると、次の結果が得られます。

拡大縮小された画像ファイルを含むヘッダ

画像ファイルの準備ができたので、次のように他のコア要素の実装に進みます。

string truncated_name = truncateString(ea_name, 0, -1, 20, true); //--- Truncate EA name
int titleFontSize = getFontSizeByDPI(14);           //--- Calculate title font size
createLabel(SETUP_HEADER_TITLE, g_mainX + 5 + 40 + 5, g_mainY + 5, truncated_name, clrWhite, titleFontSize, "Arial Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create title label
int subtitleFontSize = getFontSizeByDPI(10);        //--- Calculate subtitle font size
createLabel(SETUP_HEADER_SUBTITLE, g_mainX + 5 + 40 + 5, g_mainY + 25, "Streamlined configuration for optimal performance", C'200,200,200', subtitleFontSize, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create subtitle label
int headerCancelFontSize = getFontSizeByDPI(16);    //--- Calculate cancel button font size
createLabel(SETUP_HEADER_CANCEL, g_mainX + g_mainWidth - 25, g_mainY + 10, ShortToString(0x274C), C'150,150,150', headerCancelFontSize, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create header cancel button
int bodyY = g_mainY + g_headerHeight + g_spacing;   //--- Calculate body y position
createRecLabel(SETUP_BODY_BG, g_mainX, bodyY, g_mainWidth, g_displayHeight, C'25,25,25', 1, C'40,40,40', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create body background
int footerY = bodyY + g_displayHeight + g_spacing;  //--- Calculate footer y position
createRecLabel(SETUP_FOOTER_BG, g_mainX, footerY, g_mainWidth, g_footerHeight, C'35,35,35', 1, C'50,50,50', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create footer background
createRecLabel(SETUP_CHECKBOX_BG, g_mainX + 10, footerY + (g_footerHeight - 20)/2, 20, 20, C'60,60,60', 1, C'80,80,80', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create checkbox background
int checkboxLabelFontSize = getFontSizeByDPI(17);   //--- Calculate checkbox label font size
createLabel(SETUP_CHECKBOX_LABEL, g_mainX + 10 + 2, footerY + (g_footerHeight - 20)/2, " ", clrWhite, checkboxLabelFontSize, "Wingdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create checkbox label
int checkboxTextFontSize = getFontSizeByDPI(10);    //--- Calculate checkbox text font size
createLabel(SETUP_CHECKBOX_TEXT, g_mainX + 40, footerY + (g_footerHeight - 20)/2 + 2, "Do not show this guide again", clrWhite, checkboxTextFontSize, "Calibri Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create checkbox text
int buttonFontSize = getFontSizeByDPI(12);          //--- Calculate button font size
color buttonBg = C'60,60,60';                       //--- Set button background color
color buttonBorder = C'80,80,80';                   //--- Set button border color
createButton(SETUP_OK_BUTTON, g_mainX + g_mainWidth - 170 - 10, footerY + 5, 80, 30, "OK", clrWhite, buttonFontSize, buttonBg, buttonBorder, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, false); //--- Create OK button
createButton(SETUP_CANCEL_BUTTON, g_mainX + g_mainWidth - 80 - 10, footerY + 5, 80, 30, "Cancel", clrWhite, buttonFontSize, buttonBg, buttonBorder, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, false); //--- Create Cancel button
int textFontSize = getFontSizeByDPI(10);            //--- Calculate text font size
for (int i = 0; i < MAX_LINES; i++) {               //--- Create text line labels
   string lineName = "Setup_ResponseLine_" + IntegerToString(i); //--- Generate line name
   createLabel(lineName, 0, -100, " ", clrWhite, textFontSize, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create line label
}

ここでは、ウィザードのユーザーインターフェースを完成させるために残りのダッシュボードコンポーネントを実装します。まず、プログラム名をtruncateStringで20文字に切り詰めて可読性を確保し、計算された座標にタイトルラベルSETUP_HEADER_TITLEをcreateLabelで作成します。フォントサイズはgetFontSizeByDPIでDPIに応じて調整し(ベース14)、フォントはArial Boldを使用します。次に、固定テキストのサブタイトルラベルSETUP_HEADER_SUBTITLEをDPI調整済みの小さめフォントサイズ(ベース10)で追加し、ヘッダキャンセルボタンSETUP_HEADER_CANCELをUnicodeの十字(0x274C)でArial Rounded MT Boldを使用して作成します。以下にその詳細を示します。

クロスマークの詳細

次に、本文のy座標bodyYを計算し、createRecLabelで背景SETUP_BODY_BGを作成します。背景色はダークグレー(C25,25,25)、枠線を設定し、幅はg_mainWidth、高さはg_displayHeightとします。続いて、フッタのy座標footerYを計算し、薄めのグレーで背景SETUP_FOOTER_BGを作成します。その後、チェックボックス背景SETUP_CHECKBOX_BGを20×20の正方形として作成し、チェックボックスラベルSETUP_CHECKBOX_LABELに空のWingdings文字を設定、テキストSETUP_CHECKBOX_TEXTに「Do not show this guide again」をCalibri Boldで表示します。さらに、OKボタンSETUP_OK_BUTTONとCancelボタンSETUP_CANCEL_BUTTONをcreateButtonで作成し、フォントサイズはDPI調整済み(ベース12)とし、グレー系の色で統一します。最後に、MAX_LINESまでのテキストラベルSetup_ResponseLine_をcreateLabelでループ作成し、初期状態では画面外に非表示とし、動的なテキスト表示に対応します。これにより、ウィザードのインタラクティブで視覚的に統一されたダッシュボードを描画するシステムが完成します。プログラム実行時には次のような結果が得られます。

ウィザード強化要素

基本要素を備えたダッシュボードができたので、ウィザード用に指定されたラベルを表示するようにディスプレイを更新する必要があります。MQL5ではリッチな複数行テキストラベルを直接作成する方法がないため、テキストを折り返すアプローチを使用する必要があります。

//+------------------------------------------------------------------+
//| Get line color based on content                                  |
//+------------------------------------------------------------------+
color GetLineColor(string lineText) {
   if (StringLen(lineText) == 0 || lineText == " ") return C'25,25,25'; //--- Set invisible for empty lines
   if (StringFind(lineText, "mutiiriallan.forex@gmail.com") >= 0) return C'255,100,100'; //--- Set light red for email
   if (StringFind(lineText, "https://t.me/Forex_Algo_Trader") >= 0) return C'150,100,200'; //--- Set light purple for group link
   if (StringFind(lineText, "@ForexAlgo-Trader") >= 0) return C'100,150,255'; //--- Set light blue for channel link
   if (StringFind(lineText, "http") >= 0 || StringFind(lineText, "t.me") >= 0) return C'100,150,255'; //--- Set light blue for general links
   string start3 = StringSubstr(lineText, 0, 3);    //--- Get first three characters
   if ((start3 == "1. " || start3 == "2. " || start3 == "3. " || start3 == "4. " || start3 == "5. ") &&
       StringFind(lineText, "Initial Setup Instructions") < 0) { //--- Check instruction lines
      return C'255,200,100';                        //--- Set light yellow for instructions
   }
   return clrWhite;                                 //--- Default to white
}

//+------------------------------------------------------------------+
//| Wrap text with colors                                            |
//+------------------------------------------------------------------+
void WrapText(const string inputText, const string font, const int fontSize, const int maxWidth, string &wrappedLines[], color &wrappedColors[], int offset = 0) {
   const int maxChars = 60;                         //--- Set maximum characters per line
   ArrayResize(wrappedLines, 0);                    //--- Clear wrapped lines array
   ArrayResize(wrappedColors, 0);                   //--- Clear wrapped colors array
   TextSetFont(font, fontSize);                     //--- Set font
   string paragraphs[];                             //--- Declare paragraphs array
   int numParagraphs = StringSplit(inputText, '\n', paragraphs); //--- Split text into paragraphs
   for (int p = 0; p < numParagraphs; p++) {        //--- Iterate through paragraphs
      string para = paragraphs[p];                  //--- Get current paragraph
      color paraColor = GetLineColor(para);         //--- Get paragraph color
      if (StringLen(para) == 0) {                   //--- Check empty paragraph
         int size = ArraySize(wrappedLines);        //--- Get current size
         ArrayResize(wrappedLines, size + 1);       //--- Resize lines array
         wrappedLines[size] = " ";                  //--- Add empty line
         ArrayResize(wrappedColors, size + 1);      //--- Resize colors array
         wrappedColors[size] = C'25,25,25';         //--- Set invisible color
         continue;                                  //--- Skip to next
      }
      string words[];                               //--- Declare words array
      int numWords = StringSplit(para, ' ', words); //--- Split paragraph into words
      string currentLine = "";                      //--- Initialize current line
      for (int w = 0; w < numWords; w++) {          //--- Iterate through words
         string testLine = currentLine + (StringLen(currentLine) > 0 ? " " : "") + words[w]; //--- Build test line
         uint wid, hei;                             //--- Declare width and height
         TextGetSize(testLine, wid, hei);           //--- Get test line size
         int textWidth = (int)wid;                  //--- Get text width
         if (textWidth + offset <= maxWidth && StringLen(testLine) <= maxChars) { //--- Check line fits
            currentLine = testLine;                 //--- Update current line
         } else {                                   //--- Line exceeds limits
            if (StringLen(currentLine) > 0) {       //--- Check non-empty line
               int size = ArraySize(wrappedLines);  //--- Get current size
               ArrayResize(wrappedLines, size + 1); //--- Resize lines array
               wrappedLines[size] = currentLine;    //--- Add line
               ArrayResize(wrappedColors, size + 1); //--- Resize colors array
               wrappedColors[size] = paraColor;     //--- Add color
            }
            currentLine = words[w];                 //--- Start new line
            TextGetSize(currentLine, wid, hei);     //--- Get new line size
            textWidth = (int)wid;                   //--- Update text width
            if (textWidth + offset > maxWidth || StringLen(currentLine) > maxChars) { //--- Check word too long
               string wrappedWord = "";             //--- Initialize wrapped word
               for (int c = 0; c < StringLen(words[w]); c++) { //--- Iterate through characters
                  string testWord = wrappedWord + StringSubstr(words[w], c, 1); //--- Build test word
                  TextGetSize(testWord, wid, hei);  //--- Get test word size
                  int wordWidth = (int)wid;         //--- Get word width
                  if (wordWidth + offset > maxWidth || StringLen(testWord) > maxChars) { //--- Check word fits
                     if (StringLen(wrappedWord) > 0) { //--- Check non-empty word
                        int size = ArraySize(wrappedLines); //--- Get current size
                        ArrayResize(wrappedLines, size + 1); //--- Resize lines array
                        wrappedLines[size] = wrappedWord; //--- Add wrapped word
                        ArrayResize(wrappedColors, size + 1); //--- Resize colors array
                        wrappedColors[size] = paraColor; //--- Add color
                     }
                     wrappedWord = StringSubstr(words[w], c, 1); //--- Start new word
                  } else {                          //--- Word fits
                     wrappedWord = testWord;        //--- Update wrapped word
                  }
               }
               currentLine = wrappedWord;          //--- Set current line to wrapped word
               if (StringLen(currentLine) > 0) {   //--- Check non-empty line
                  int size = ArraySize(wrappedLines); //--- Get current size
                  ArrayResize(wrappedLines, size + 1); //--- Resize lines array
                  wrappedLines[size] = currentLine; //--- Add line
                  ArrayResize(wrappedColors, size + 1); //--- Resize colors array
                  wrappedColors[size] = paraColor; //--- Add color
               }
               currentLine = "";                   //--- Reset current line
            }
         }
      }
      if (StringLen(currentLine) > 0) {            //--- Check remaining line
         int size = ArraySize(wrappedLines);       //--- Get current size
         ArrayResize(wrappedLines, size + 1);      //--- Resize lines array
         wrappedLines[size] = currentLine;         //--- Add line
         ArrayResize(wrappedColors, size + 1);     //--- Resize colors array
         wrappedColors[size] = paraColor;          //--- Add color
      }
   }
}

ここでは、ガイドの可読性を高めるためにテキストフォーマットと色付けのロジックを実装します。GetLineColor関数では、内容に応じて色を割り当てます。空行は不可視のダークグレー(C25,25,25)、メールアドレスはライトレッド(C255,100,100)、グループリンクはライトパープル(C150,100,200)、著者リンクやその他のURLはライトブルー(C100,150,255)、見出しを除く1.から5.で始まる指示行はライトイエロー(C255,200,100)、それ以外は白に設定します。これはあくまでリッチテキストエンコーディングの例としての一例であり、任意に定義可能です。

WrapText関数では、まず入力テキストを改行でStringSplitして段落に分割し、TextSetFontでフォントを設定します。各段落に対してGetLineColorで色を取得し、空段落は不可視色でスペースとして追加します。段落をStringSplitで単語に分割し、各単語を行に追加する際にはTextGetSizeで幅を測定し、maxWidthおよび文字数上限60(最大63)内に収まる場合は追加し、制限を超える場合は新しい行を開始します。非常に長い単語については文字ごとに分割し、TextGetSizeで幅を確認しながら行の制限を超える場合は新しい行に追加します。こうして作成された各行はwrappedLinesに格納し、対応する色はArrayResizeを用いてwrappedColorsに保持します。この関数を使用すると、表示を更新できます。ロジックが関数であることを確認します。

//+------------------------------------------------------------------+
//| Get text height                                                  |
//+------------------------------------------------------------------+
int TextGetHeight(string text, string font, int fontSize) {
   uint wid, hei;                                   //--- Declare width and height
   TextSetFont(font, fontSize);                     //--- Set font
   TextGetSize(text, wid, hei);                     //--- Get text size
   return (int)hei;                                 //--- Return height
}

//+------------------------------------------------------------------+
//| Check if line is a heading                                       |
//+------------------------------------------------------------------+
bool IsHeading(string lineText) {
   if (StringLen(lineText) == 0) return false;      //--- Return false for empty lines
   if (StringGetCharacter(lineText, StringLen(lineText) - 1) == ':') return true; //--- Check for colon
   if (StringFind(lineText, "Expert Advisor Initialization Guide") >= 0) return true; //--- Check main heading
   if (StringFind(lineText, "Key Features") >= 0) return true; //--- Check features heading
   if (StringFind(lineText, "Initial Setup Instructions") >= 0) return true; //--- Check instructions heading
   if (StringFind(lineText, "Important Notes") >= 0) return true; //--- Check notes heading
   if (StringFind(lineText, "Contact Methods") >= 0) return true; //--- Check contact heading
   if (StringFind(lineText, "NB:") >= 0) return true; //--- Check NB heading
   return false;                                    //--- Default to false
}

//+------------------------------------------------------------------+
//| Update body display with scrollable text                         |
//+------------------------------------------------------------------+
void UpdateBodyDisplay() {
   int textX = g_mainX + g_padding + g_textPadding; //--- Set text x position
   int textY = g_mainY + g_headerHeight + g_spacing; //--- Set text y position
   int fullMaxWidth = g_mainWidth - 2 * g_padding - 2 * g_textPadding; //--- Calculate max text width
   string font = "Arial";                           //--- Set font
   int fontSize = getFontSizeByDPI(10);             //--- Calculate font size
   int lineHeight = TextGetHeight("A", font, fontSize); //--- Get line height
   int adjustedLineHeight = lineHeight + g_lineSpacing; //--- Calculate adjusted line height
   g_adjustedLineHeight = adjustedLineHeight;       //--- Store adjusted line height
   int visibleHeight = g_displayHeight;             //--- Set visible height
   g_visible_height = visibleHeight;                //--- Store visible height
   static string wrappedLines[];                    //--- Store wrapped text lines
   static color wrappedColors[];                    //--- Store line colors
   static bool wrapped = false;                     //--- Track if text wrapped
   if (!wrapped) {                                  //--- Check if text needs wrapping
      WrapText(setup_text, font, fontSize, fullMaxWidth, wrappedLines, wrappedColors); //--- Wrap text
      wrapped = true;                               //--- Set wrapped flag
   }
   int numLines = ArraySize(wrappedLines);          //--- Get number of lines
   g_total_height = numLines * adjustedLineHeight;  //--- Calculate total text height
   bool need_scroll = g_total_height > visibleHeight; //--- Check if scrollbar needed
   bool should_show_scrollbar = false;              //--- Initialize scrollbar visibility
   int reserved_width = 0;                          //--- Initialize reserved width
   if (need_scroll && ScrollbarMode != SCROLL_NEVER) { //--- Check scrollbar mode
      should_show_scrollbar = true;                 //--- Enable scrollbar
      reserved_width = 16;                          //--- Reserve scrollbar width
   }
   if (reserved_width > 0 && fullMaxWidth - reserved_width != fullMaxWidth) { //--- Check width change
      WrapText(setup_text, font, fontSize, fullMaxWidth - reserved_width, wrappedLines, wrappedColors); //--- Rewrap text
      numLines = ArraySize(wrappedLines);           //--- Update line count
      g_total_height = numLines * adjustedLineHeight; //--- Update total height
   }

   int startLine = scroll_pos / adjustedLineHeight; //--- Calculate start line
   int currentY = textY;                            //--- Set current y position
   int labelIndex = 0;                              //--- Initialize label index
   for (int line = startLine; line < numLines; line++) { //--- Iterate visible lines
      string lineText = wrappedLines[line];         //--- Get line text
      if (StringLen(lineText) == 0) lineText = " "; //--- Set empty lines to space
      color lineColor = wrappedColors[line];        //--- Get line color
      if (IsHeading(lineText)) lineColor = clrBlue; //--- Set blue for headings
      if (currentY + adjustedLineHeight > textY + visibleHeight) break; //--- Prevent overflow
      string lineName = "Setup_ResponseLine_" + IntegerToString(labelIndex); //--- Generate line name
      if (ObjectFind(0, lineName) >= 0) {          //--- Check if label exists
         ObjectSetString(0, lineName, OBJPROP_TEXT, lineText); //--- Set line text
         ObjectSetInteger(0, lineName, OBJPROP_XDISTANCE, textX); //--- Set x position
         ObjectSetInteger(0, lineName, OBJPROP_YDISTANCE, currentY); //--- Set y position
         ObjectSetInteger(0, lineName, OBJPROP_COLOR, lineColor); //--- Set line color
         string lineFont = IsHeading(lineText) ? "Arial Bold" : "Arial"; //--- Set font
         ObjectSetString(0, lineName, OBJPROP_FONT, lineFont); //--- Set font type
         ObjectSetInteger(0, lineName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
         ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, false); //--- Show label
      }
      currentY += adjustedLineHeight;               //--- Increment y position
      labelIndex++;                                 //--- Increment label index
   }
   for (int i = labelIndex; i < MAX_LINES; i++) {   //--- Hide unused labels
      string lineName = "Setup_ResponseLine_" + IntegerToString(i); //--- Generate line name
      if (ObjectFind(0, lineName) >= 0) {           //--- Check if label exists
         ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, true); //--- Hide label
      }
   }
   ChartRedraw();                                   //--- Redraw chart
}

ガイドを動的に表示するためのテキスト描画とスクロールロジックを実装します。TextGetHeight関数では、TextSetFontでフォントとサイズを設定し、TextGetSizeを使用してサンプル文字の高さを計算し、一貫した行間隔を得るために返します。IsHeading関数では、空行、末尾のコロン、または特定のガイドセクションタイトル(例:Key Features)をチェックして見出しを判別し、該当する場合はtrueを返します。UpdateBodyDisplay関数では、パディングとコンテナの寸法を使用してテキスト領域の位置(textX、textY)と幅(fullMaxWidth)を計算し、フォントをArialに設定、getFontSizeByDPIでDPIに応じたサイズを取得します。行の高さはTextGetHeightにg_lineSpacingを加えて計算し、g_adjustedLineHeightに格納します。

ガイドテキストはWrapTextで折り返しをおこない、総テキスト高さ(g_total_height)を計算します。スクロールバーの表示はScrollbarModeとテキストのオーバーフローに基づいて判断し、必要に応じてスクロールバー用に16ピクセルを確保し、テキストを再折り返しします。scroll_posから開始行を計算し、ObjectSetStringObjectSetIntegerを使用して表示するテキストラベルの位置、色(見出しはIsHeadingで青に設定)、フォントを更新します。使用されないラベルは非表示にし、チャートを再描画します。この関数をダッシュボード表示用の関数から呼び出すと、次のような結果が得られます。

初期化されたディスプレイ

テキストが表示領域にぴったり収まるように表示の準備が整っていることがわかります。必要なのは、必要に応じてスクロールバーを表示するためのロジックを取得することです。

//+------------------------------------------------------------------+
//| Create scrollbar                                                 |
//+------------------------------------------------------------------+
void CreateScrollbar() {
   int bodyY = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position
   int textAreaY = bodyY;                            //--- Set text area y
   int textAreaHeight = g_displayHeight;             //--- Set text area height
   int scrollbar_x = g_mainX + g_mainWidth - 16 - 1; //--- Calculate scrollbar x
   int scrollbar_width = 16;                         //--- Set scrollbar width
   int button_size = 16;                             //--- Set button size
   int scrollbar_y = textAreaY + 2;                  //--- Calculate scrollbar y
   int scrollbar_height = textAreaHeight - 2 - 2;    //--- Calculate scrollbar height
   createRecLabel(SCROLL_LEADER, scrollbar_x, scrollbar_y, scrollbar_width, scrollbar_height, C'45,45,45', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll leader
   createRecLabel(SCROLL_UP_REC, scrollbar_x, scrollbar_y, scrollbar_width, button_size, C'60,60,60', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll up rectangle
   int scrollUpLabelFontSize = getFontSizeByDPI(10); //--- Calculate scroll up font size
   createLabel(SCROLL_UP_LABEL, scrollbar_x + 2, scrollbar_y - 2, CharToString(0x35), C'150,150,150', scrollUpLabelFontSize, "Webdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create scroll up label
   int down_rec_y = scrollbar_y + scrollbar_height - button_size; //--- Calculate scroll down y
   createRecLabel(SCROLL_DOWN_REC, scrollbar_x, down_rec_y, scrollbar_width, button_size, C'60,60,60', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll down rectangle
   createLabel(SCROLL_DOWN_LABEL, scrollbar_x + 2, down_rec_y - 2, CharToString(0x36), C'150,150,150', scrollUpLabelFontSize, "Webdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create scroll down label
   slider_height = CalculateSliderHeight();          //--- Calculate slider height
   createRecLabel(SCROLL_SLIDER, scrollbar_x, scrollbar_y + button_size, scrollbar_width, slider_height, C'80,80,80', 1, C'100,100,100', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll slider
}

//+------------------------------------------------------------------+
//| Delete scrollbar                                                 |
//+------------------------------------------------------------------+
void DeleteScrollbar() {
   string scroll_objects[] = {SCROLL_LEADER, SCROLL_UP_REC, SCROLL_UP_LABEL, SCROLL_DOWN_REC, SCROLL_DOWN_LABEL, SCROLL_SLIDER}; //--- Define scroll objects
   for (int i = 0; i < ArraySize(scroll_objects); i++) { //--- Iterate through objects
      ObjectDelete(0, scroll_objects[i]);            //--- Delete object
   }
   ChartRedraw();                                    //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Calculate slider height                                          |
//+------------------------------------------------------------------+
int CalculateSliderHeight() {
   int textAreaHeight = g_displayHeight;            //--- Get text area height
   int scroll_area_height = textAreaHeight - 32;    //--- Calculate scroll area height
   int slider_min_height = 20;                      //--- Set minimum slider height
   if (g_total_height <= g_visible_height) return scroll_area_height; //--- Return full height if no scroll
   double visible_ratio = (double)g_visible_height / g_total_height; //--- Calculate visible ratio
   int height = (int)MathFloor(scroll_area_height * visible_ratio); //--- Calculate slider height
   return MathMax(slider_min_height, height);       //--- Return minimum or calculated height
}

//+------------------------------------------------------------------+
//| Update slider position                                           |
//+------------------------------------------------------------------+
void UpdateSliderPosition() {
   int bodyY = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position
   int textAreaY = bodyY;                           //--- Set text area y
   int textAreaHeight = g_displayHeight;            //--- Set text area height
   int scroll_area_height = textAreaHeight - 32;    //--- Calculate scroll area height
   int slider_min_y = textAreaY + 16;               //--- Set minimum slider y
   if (g_max_scroll <= 0) return;                   //--- Exit if no scroll
   double scroll_ratio = (double)scroll_pos / g_max_scroll; //--- Calculate scroll ratio
   int slider_max_y = slider_min_y + scroll_area_height - slider_height; //--- Calculate max slider y
   int new_y = slider_min_y + (int)MathRound(scroll_ratio * (slider_max_y - slider_min_y)); //--- Calculate new y
   ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); //--- Set slider y position
}

//+------------------------------------------------------------------+
//| Update scrollbar button colors                                   |
//+------------------------------------------------------------------+
void UpdateButtonColors() {
   int max_scroll = g_max_scroll;                   //--- Get max scroll
   color up_color = (scroll_pos == 0) ? C'80,80,80' : (scroll_up_hovered ? C'100,100,100' : C'150,150,150'); //--- Set up button color
   color down_color = (scroll_pos >= max_scroll) ? C'80,80,80' : (scroll_down_hovered ? C'100,100,100' : C'150,150,150'); //--- Set down button color
   ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, up_color); //--- Update up label color
   ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, down_color); //--- Update down label color
   ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_BGCOLOR, scroll_up_hovered ? C'70,70,70' : C'60,60,60'); //--- Update up rectangle color
   ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_BGCOLOR, scroll_down_hovered ? C'70,70,70' : C'60,60,60'); //--- Update down rectangle color
}

//+------------------------------------------------------------------+
//| Scroll up                                                        |
//+------------------------------------------------------------------+
void ScrollUp() {
   if (g_adjustedLineHeight > 0 && scroll_pos > 0) { //--- Check scroll possible
      scroll_pos = MathMax(0, scroll_pos - g_adjustedLineHeight); //--- Decrease scroll position
      UpdateBodyDisplay();                           //--- Update body display
      if (scroll_visible) {                          //--- Check scrollbar visible
         UpdateSliderPosition();                     //--- Update slider position
         UpdateButtonColors();                       //--- Update button colors
      }
   }
}

//+------------------------------------------------------------------+
//| Scroll down                                                      |
//+------------------------------------------------------------------+
void ScrollDown() {
   int max_scroll = g_max_scroll;                    //--- Get max scroll
   if (g_adjustedLineHeight > 0 && scroll_pos < max_scroll) { //--- Check scroll possible
      scroll_pos = MathMin(max_scroll, scroll_pos + g_adjustedLineHeight); //--- Increase scroll position
      UpdateBodyDisplay();                           //--- Update body display
      if (scroll_visible) {                          //--- Check scrollbar visible
         UpdateSliderPosition();                     //--- Update slider position
         UpdateButtonColors();                       //--- Update button colors
      }
   }
}

セットアップガイドのスムーズなナビゲーションを可能にするスクロールバー機能を実装します。まず、CreateScrollbar関数を作成します。この関数内で、本文のy座標(bodyY)を基にスクロールバーの位置と寸法を計算し、scrollbar_xをメインコンテナの右端に設定、幅16ピクセルでスクロールバーを作成します。スクロールバーのトラックにはcreateRecLabelでリーダー矩形SCROLL_LEADERを作成し、上下ボタンSCROLL_UP_REC、SCROLL_DOWN_RECとそのラベルSCROLL_UP_LABEL、SCROLL_DOWN_LABELにはWebdings矢印(0x35、0x36)を使用します。次にCalculateSliderHeightを呼び出して表示テキスト比率に基づきスライダーの高さを決定し、createRecLabelでスライダーSCROLL_SLIDERを作成します。DeleteScrollbar関数では、SCROLL_LEADERなどすべてのスクロールバーオブジェクトをObjectDeleteで削除し、チャートを再描画します。

CalculateSliderHeightでは、スライダーの高さを表示領域の高さと総テキスト高さの比率として計算し、最低20ピクセルを確保します。UpdateSliderPosition関数では、scroll_posとg_max_scrollから算出したスクロール比率を用いてスライダーのy座標を調整し、ObjectSetIntegerで設定します。UpdateButtonColors関数では、スクロール位置とホバー状態に基づき上下ボタンの色を更新し、動的な視覚フィードバックを提供します。ScrollUpおよびScrollDown関数では、scroll_posをg_adjustedLineHeight単位で増減させ、範囲内にクランプし、スクロールバーが表示されている場合はUpdateBodyDisplay、UpdateSliderPosition、UpdateButtonColorsを呼び出してシームレスなスクロールを実現します。これらの関数は表示更新関数内から呼び出してスクロールバーを追加します。以上が、この機能を実現する手法です。

int num_visible_lines = g_visible_height / g_adjustedLineHeight; //--- Calculate visible lines
g_max_scroll = MathMax(0, (numLines - num_visible_lines) * g_adjustedLineHeight); //--- Calculate max scroll
bool prev_scroll_visible = scroll_visible;       //--- Store previous scrollbar state
scroll_visible = should_show_scrollbar;          //--- Update scrollbar visibility
if (scroll_visible != prev_scroll_visible) {     //--- Check scrollbar state change
   if (scroll_visible) {                         //--- Show scrollbar
      CreateScrollbar();                         //--- Create scrollbar
   } else {                                      //--- Hide scrollbar
      DeleteScrollbar();                         //--- Delete scrollbar
   }
}
scroll_pos = MathMax(0, MathMin(scroll_pos, g_max_scroll)); //--- Clamp scroll position
if (scroll_visible) {                            //--- Update scrollbar
   slider_height = CalculateSliderHeight();      //--- Calculate slider height
   ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height); //--- Set slider height
   UpdateSliderPosition();                       //--- Update slider position
   UpdateButtonColors();                         //--- Update button colors
}

UpdateBodyDisplay関数内では、g_visible_heightをg_adjustedLineHeightで割って表示可能行数を計算し、最大スクロール距離(g_max_scroll)を表示可能行を超えたテキストの高さとしてMathMaxで負の値にならないように求めます。前回のスクロールバー表示状態をprev_scroll_visibleに保存し、スクロールバーが必要かどうかに応じてscroll_visibleを更新します。状態が変化した場合は、スクロールバーを描画するためにCreateScrollbarを呼び出すか、削除するためにDeleteScrollbarを呼び出します。scroll_posはMathMaxとMathMinで0からg_max_scrollの範囲にクランプしてオーバーフローを防ぎます。スクロールバーが表示されている場合は、CalculateSliderHeightでslider_heightを更新し、ObjectSetIntegerでSCROLL_SLIDERの高さを設定し、UpdateSliderPositionおよびUpdateButtonColorsを呼び出してスクロールバーの表示と位置を更新します。コンパイルすると、次の結果が得られます。

スクロールバーが有効

画像から、ダッシュボードの要素がすべて作成されていることが確認できます。次に、ログアウトやプログラムの非初期化時にダッシュボードを破棄する処理を確実におこなう必要があります。

//+------------------------------------------------------------------+
//| Delete the dashboard                                             |
//+------------------------------------------------------------------+
void DeleteDashboard() {
   string objects[] = {                             //--- Define dashboard objects
      SETUP_MAIN, SETUP_HEADER_BG, SETUP_HEADER_IMAGE, SETUP_HEADER_TITLE, SETUP_HEADER_SUBTITLE, SETUP_HEADER_CANCEL,
      SETUP_BODY_BG, SETUP_FOOTER_BG, SETUP_CHECKBOX_BG, SETUP_CHECKBOX_LABEL, SETUP_CHECKBOX_TEXT,
      SETUP_OK_BUTTON, SETUP_CANCEL_BUTTON, SCROLL_LEADER, SCROLL_UP_REC, SCROLL_UP_LABEL,
      SCROLL_DOWN_REC, SCROLL_DOWN_LABEL, SCROLL_SLIDER
   };
   for (int i = 0; i < ArraySize(objects); i++) {   //--- Iterate through objects
      ObjectDelete(0, objects[i]);                  //--- Delete object
   }
   int total = ObjectsTotal(0);                     //--- Get total objects
   for (int j = total - 1; j >= 0; j--) {           //--- Iterate through remaining objects
      string name = ObjectName(0, j);               //--- Get object name
      if (StringFind(name, "Setup_ResponseLine_") == 0) { //--- Check for text lines
         ObjectDelete(0, name);                     //--- Delete text line
      }
   }
   ChartRedraw();                                   //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   DeleteDashboard();                                //--- Remove dashboard objects
   if (StringLen(g_scaled_image_resource) > 0) {     //--- Check if scaled image exists
      ResourceFree(g_scaled_image_resource);         //--- Free scaled image resource
   }
}

ここでは、ウィザードのリソースを適切に管理するためのクリーンアップ機能を実装します。DeleteDashboard関数では、メインコンテナ、ヘッダ、本文、フッタ、ボタン、チェックボックス、スクロールバーの各コンポーネントを含むダッシュボードオブジェクト名の配列を定義し、ObjectDeleteで順に削除してチャートから取り除きます。次にObjectsTotalObjectNameでチャート上の残りのオブジェクトをループ処理し、Setup_ResponseLine_で始まるテキスト行オブジェクトをObjectDeleteで削除し、ChartRedrawでチャートを再描画して表示をきれいにします。OnDeinit関数では、DeleteDashboardを呼び出してすべてのダッシュボード要素を削除し、スケーリング済み画像リソースが存在する場合(StringLen(g_scaled_image_resource) > 0)はResourceFreeでメモリを解放します。これでダッシュボードに命を吹き込む準備が整いました。ボタンをクリックしたときにそれぞれの処理が実行され、ホバー時にはカーソルの状態が反映されるようにしたいです。そのためには、簡潔に処理できる専用の関数を作成する必要があります。

//+------------------------------------------------------------------+
//| Update hover effects                                             |
//+------------------------------------------------------------------+
void UpdateHoverEffects(int mouseX, int mouseY) {
   int ok_x = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_XDISTANCE);    //--- Get OK button x
   int ok_y = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_YDISTANCE);    //--- Get OK button y
   int ok_width = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_XSIZE);    //--- Get OK button width
   int ok_height = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_YSIZE);   //--- Get OK button height
   bool is_ok_hovered = (mouseX >= ok_x && mouseX <= ok_x + ok_width && mouseY >= ok_y && mouseY <= ok_y + ok_height); //--- Check OK button hover
   if (is_ok_hovered != ok_button_hovered) {                                   //--- Check hover state change
      ok_button_hovered = is_ok_hovered;                                       //--- Update hover state
      color hoverBg = is_ok_hovered ? C'40,80,40' : C'60,60,60';               //--- Set hover background
      color hoverBorder = is_ok_hovered ? C'60,100,60' : C'80,80,80';          //--- Set hover border
      ObjectSetInteger(0, SETUP_OK_BUTTON, OBJPROP_BGCOLOR, hoverBg);          //--- Update background
      ObjectSetInteger(0, SETUP_OK_BUTTON, OBJPROP_BORDER_COLOR, hoverBorder); //--- Update border
      ChartRedraw();                                                           //--- Redraw chart
   }
   int cancel_x = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_XDISTANCE);  //--- Get Cancel button x
   int cancel_y = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_YDISTANCE);  //--- Get Cancel button y
   int cancel_width = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_XSIZE);  //--- Get Cancel button width
   int cancel_height = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_YSIZE); //--- Get Cancel button height
   bool is_cancel_hovered = (mouseX >= cancel_x && mouseX <= cancel_x + cancel_width && mouseY >= cancel_y && mouseY <= cancel_y + cancel_height); //--- Check Cancel button hover
   if (is_cancel_hovered != cancel_button_hovered) {                                 //--- Check hover state change
      cancel_button_hovered = is_cancel_hovered;                                     //--- Update hover state
      color hoverBg = is_cancel_hovered ? C'80,40,40' : C'60,60,60';                 //--- Set hover background
      color hoverBorder = is_cancel_hovered ? C'100,60,60' : C'80,80,80';            //--- Set hover border
      ObjectSetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_BGCOLOR, hoverBg);            //--- Update background
      ObjectSetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_BORDER_COLOR, hoverBorder);   //--- Update border
      ChartRedraw();                               //--- Redraw chart
   }
   int checkbox_x = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_XDISTANCE);  //--- Get checkbox x
   int checkbox_y = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_YDISTANCE);  //--- Get checkbox y
   int checkbox_width = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_XSIZE);  //--- Get checkbox width
   int checkbox_height = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_YSIZE); //--- Get checkbox height
   bool is_checkbox_hovered = (mouseX >= checkbox_x && mouseX <= checkbox_x + checkbox_width && mouseY >= checkbox_y && mouseY <= checkbox_y + checkbox_height); //--- Check checkbox hover
   if (is_checkbox_hovered != checkbox_hovered) {   //--- Check hover state change
      checkbox_hovered = is_checkbox_hovered;       //--- Update hover state
      if (checkbox_checked) {                       //--- Check checkbox state
         ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, is_checkbox_hovered ? C'0,150,0' : C'0,128,0'); //--- Update background
      } else {                                      //--- Unchecked state
         ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, is_checkbox_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background
      }
      ChartRedraw();                                //--- Redraw chart
   }
   int header_cancel_x = (int)ObjectGetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_XDISTANCE); //--- Get header cancel x
   int header_cancel_y = (int)ObjectGetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_YDISTANCE); //--- Get header cancel y
   int header_cancel_width = 20;                    //--- Set header cancel width
   int header_cancel_height = 20;                   //--- Set header cancel height
   bool is_header_cancel_hovered = (mouseX >= header_cancel_x && mouseX <= header_cancel_x + header_cancel_width &&
                                    mouseY >= header_cancel_y && mouseY <= header_cancel_y + header_cancel_height); //--- Check header cancel hover
   if (is_header_cancel_hovered != header_cancel_hovered) { //--- Check hover state change
      header_cancel_hovered = is_header_cancel_hovered; //--- Update hover state
      ObjectSetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_COLOR, is_header_cancel_hovered ? C'255,100,100' : C'150,150,150'); //--- Update color
      ChartRedraw();                                //--- Redraw chart
   }
   if (scroll_visible) {                            //--- Check scrollbar visible
      int scroll_up_x = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_XDISTANCE);  //--- Get scroll up x
      int scroll_up_y = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_YDISTANCE);  //--- Get scroll up y
      int scroll_up_width = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_XSIZE);  //--- Get scroll up width
      int scroll_up_height = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_YSIZE); //--- Get scroll up height
      bool is_scroll_up_hovered = (mouseX >= scroll_up_x && mouseX <= scroll_up_x + scroll_up_width &&
                                   mouseY >= scroll_up_y && mouseY <= scroll_up_y + scroll_up_height); //--- Check scroll up hover
      if (is_scroll_up_hovered != scroll_up_hovered) {                               //--- Check hover state change
         scroll_up_hovered = is_scroll_up_hovered;                                   //--- Update hover state
         ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_BGCOLOR, is_scroll_up_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background
         ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, (scroll_pos == 0) ? C'80,80,80' : (is_scroll_up_hovered ? C'100,100,100' : C'150,150,150')); //--- Update label color
         ChartRedraw();                                                              //--- Redraw chart
      }
      int scroll_down_x = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_XDISTANCE);  //--- Get scroll down x
      int scroll_down_y = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_YDISTANCE);  //--- Get scroll down y
      int scroll_down_width = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_XSIZE);  //--- Get scroll down width
      int scroll_down_height = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_YSIZE); //--- Get scroll down height
      bool is_scroll_down_hovered = (mouseX >= scroll_down_x && mouseX <= scroll_down_x + scroll_down_width &&
                                     mouseY >= scroll_down_y && mouseY <= scroll_down_y + scroll_down_height); //--- Check scroll down hover
      if (is_scroll_down_hovered != scroll_down_hovered) {                               //--- Check hover state change
         scroll_down_hovered = is_scroll_down_hovered;                                   //--- Update hover state
         ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_BGCOLOR, is_scroll_down_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background
         ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, (scroll_pos >= g_max_scroll) ? C'80,80,80' : (is_scroll_down_hovered ? C'100,100,100' : C'150,150,150')); //--- Update label color
         ChartRedraw();                                                                  //--- Redraw chart
      }
      int scroll_slider_x = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE);  //--- Get scroll slider x
      int scroll_slider_y = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE);  //--- Get scroll slider y
      int scroll_slider_width = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE);  //--- Get scroll slider width
      int scroll_slider_height = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE); //--- Get scroll slider height
      bool is_scroll_slider_hovered = (mouseX >= scroll_slider_x && mouseX <= scroll_slider_x + scroll_slider_width &&
                                       mouseY >= scroll_slider_y && mouseY <= scroll_slider_y + scroll_slider_height); //--- Check scroll slider hover
      if (is_scroll_slider_hovered != scroll_slider_hovered) {                           //--- Check hover state change
         scroll_slider_hovered = is_scroll_slider_hovered;                               //--- Update hover state
         ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, is_scroll_slider_hovered ? C'100,100,100' : C'80,80,80'); //--- Update background
         ChartRedraw();                                                                  //--- Redraw chart
      }
   }
}

ここでは、作成したインタラクティブ要素にホバー効果を実装してユーザーフィードバックを強化します。UpdateHoverEffects関数を作成し、その中でマウス座標(mouseX、mouseY)を、OKボタン(SETUP_OK_BUTTON)、キャンセルボタン(SETUP_CANCEL_BUTTON)、チェックボックス(SETUP_CHECKBOX_BG)、ヘッダキャンセルボタン(SETUP_HEADER_CANCEL)、およびスクロールバーコンポーネント(SCROLL_UP_REC、SCROLL_DOWN_REC、SCROLL_SLIDER)の位置とサイズと照合します。各要素の寸法はObjectGetIntegerで取得します。

各要素について、マウスが範囲内にあるかを確認してホバー状態を判定し、状態が変化した場合はok_button_hovered、cancel_button_hoveredなどの対応するフラグを更新します。さらにObjectSetIntegerで色を調整します。OKボタンは緑系(C40,80,40)、キャンセルボタンは赤系(C80,40,40)、チェックボックスは選択時に緑(C0,150,0)、未選択時に灰色(C70,70,70)、ヘッダキャンセルは明るい赤(C255,100,100)、スクロールバーのボタンやスライダーはホバーやscroll_posに応じて灰色(C70,70,70またはC100,100,100)を使用します。これらの色は任意に変更可能で、今回は視覚フィードバックの参考として適当な値を設定しています。更新後はChartRedrawでチャートを再描画します。最後に、この処理をOnChartEventイベントハンドラに実装します。このハンドラはすべてのチャートイベントを処理しますが、今回注目するのはマウス移動およびクリックイベントです。

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   int mouseX = (int)lparam;                        //--- Get mouse x coordinate
   int mouseY = (int)dparam;                        //--- Get mouse y coordinate
   int body_inner_y = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position
   int textAreaEventY = body_inner_y;               //--- Set text area y position
   int textAreaEventH = g_displayHeight;            //--- Set text area height
   int bodyX = g_mainX + g_padding + g_textPadding; //--- Calculate body x position
   int bodyW = g_mainWidth - 2 * g_padding - 2 * g_textPadding - (scroll_visible ? 16 : 0); //--- Calculate body width
   if (id == CHARTEVENT_OBJECT_CLICK) {             //--- Handle object click events
      if (sparam == SETUP_HEADER_CANCEL || sparam == SETUP_CANCEL_BUTTON) { //--- Check cancel button click
         GlobalVariableSet(GV_SETUP, 0.0);          //--- Set global variable to false
         DeleteDashboard();                         //--- Remove dashboard from chart
      } else if (sparam == SETUP_OK_BUTTON) {       //--- Check OK button click
         double new_val = checkbox_checked ? 1.0 : 0.0; //--- Set global variable based on checkbox
         GlobalVariableSet(GV_SETUP, new_val);      //--- Update global variable
         DeleteDashboard();                         //--- Remove dashboard from chart
      } else if (sparam == SETUP_CHECKBOX_BG || sparam == SETUP_CHECKBOX_TEXT || sparam == SETUP_CHECKBOX_LABEL) { //--- Check checkbox click
         checkbox_checked = !checkbox_checked;      //--- Toggle checkbox state
         string check_text = checkbox_checked ? CharToString(252) : " ";         //--- Set checkbox symbol
         ObjectSetString(0, SETUP_CHECKBOX_LABEL, OBJPROP_TEXT, check_text);     //--- Update checkbox label text
         color text_color = checkbox_checked ? C'173,216,230' : clrWhite;        //--- Set text color based on state
         ObjectSetInteger(0, SETUP_CHECKBOX_TEXT, OBJPROP_COLOR, text_color);    //--- Update checkbox text color
         if (checkbox_checked) {                    //--- Check if checkbox is selected
            ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, C'0,128,0'); //--- Set checked background color
            ObjectSetInteger(0, SETUP_CHECKBOX_LABEL, OBJPROP_COLOR, clrWhite);  //--- Set checked label color
         } else {                                   //--- Handle unchecked state
            ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, C'60,60,60'); //--- Set unchecked background color
            ObjectSetInteger(0, SETUP_CHECKBOX_LABEL, OBJPROP_COLOR, clrWhite);  //--- Set unchecked label color
         }
         ChartRedraw();                            //--- Redraw chart to reflect changes
      } else if (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL) {         //--- Check scroll up click
         ScrollUp();                               //--- Execute scroll up action
      } else if (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL) {     //--- Check scroll down click
         ScrollDown();                             //--- Execute scroll down action
      }
   } else if (id == CHARTEVENT_MOUSE_MOVE) {       //--- Handle mouse move events
      int MouseState = (int)sparam;                //--- Get mouse state
      bool is_in = (mouseX >= bodyX && mouseX <= bodyX + bodyW && mouseY >= textAreaEventY && mouseY <= textAreaEventY + textAreaEventH); //--- Check if mouse is in body
      mouse_in_body = is_in;                       //--- Update mouse in body status
      UpdateHoverEffects(mouseX, mouseY);          //--- Update hover effects for elements
      static int prevMouseState = 0;               //--- Store previous mouse state
      if (prevMouseState == 0 && MouseState == 1 && scroll_visible) {                //--- Check for slider drag start
         int xd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE); //--- Get slider x position
         int yd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get slider y position
         int xs_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE);     //--- Get slider width
         int ys_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE);     //--- Get slider height
         if (mouseX >= xd_slider && mouseX <= xd_slider + xs_slider &&
             mouseY >= yd_slider && mouseY <= yd_slider + ys_slider) { //--- Check if mouse is over slider
            movingStateSlider = true;              //--- Set slider drag state
            mlbDownX_Slider = mouseX;              //--- Store mouse x position
            mlbDownY_Slider = mouseY;              //--- Store mouse y position
            mlbDown_YD_Slider = yd_slider;         //--- Store slider y position
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, C'100,100,100'); //--- Set drag color
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable chart scrolling
         }
      }
      if (movingStateSlider) {                     //--- Handle slider dragging
         int delta_y = mouseY - mlbDownY_Slider;   //--- Calculate y displacement
         int new_y = mlbDown_YD_Slider + delta_y;  //--- Calculate new slider y position
         int textAreaY_local = body_inner_y;       //--- Set text area y position
         int textAreaHeight_local = g_displayHeight;   //--- Set text area height
         int scroll_area_y_min = textAreaY_local + 16; //--- Set minimum slider y
         int scroll_area_y_max = textAreaY_local + textAreaHeight_local - 16 - slider_height; //--- Set maximum slider y
         new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max));               //--- Clamp new y position
         ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y);                        //--- Update slider y position
         int max_scroll = g_max_scroll;            //--- Get maximum scroll
         double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min); //--- Calculate scroll ratio
         int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll);                      //--- Calculate new scroll position
         if (new_scroll_pos != scroll_pos) {       //--- Check if scroll position changed
            scroll_pos = new_scroll_pos;           //--- Update scroll position
            UpdateBodyDisplay();                   //--- Update body text display
         }
         ChartRedraw();                            //--- Redraw chart
      }
      if (MouseState == 0) {                       //--- Handle mouse release
         if (movingStateSlider) {                  //--- Check if slider was being dragged
            movingStateSlider = false;             //--- Reset drag state
            int max_scroll = g_max_scroll;         //--- Get maximum scroll
            int textAreaY_local = body_inner_y;    //--- Set text area y position
            int textAreaHeight_local = g_displayHeight; //--- Set text area height
            int scroll_area_y_min_local = textAreaY_local + 16; //--- Set minimum slider y
            int scroll_area_y_max_local = textAreaY_local + textAreaHeight_local - 16 - slider_height; //--- Set maximum slider y
            int current_slider_y = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get current slider y
            double scroll_ratio = (double)(current_slider_y - scroll_area_y_min_local) / (scroll_area_y_max_local - scroll_area_y_min_local); //--- Calculate scroll ratio
            int temp_scroll = (int)MathRound(scroll_ratio * max_scroll); //--- Calculate temporary scroll
            if (g_adjustedLineHeight > 0) {        //--- Check if line height valid
               int snapped_line = (int)MathRound((double)temp_scroll / g_adjustedLineHeight); //--- Calculate snapped line
               scroll_pos = MathMax(0, MathMin(snapped_line * g_adjustedLineHeight, max_scroll)); //--- Snap to nearest line
            } else {                               //--- No valid line height
               scroll_pos = temp_scroll;           //--- Use temporary scroll
            }
            UpdateBodyDisplay();                   //--- Update body text display
            UpdateSliderPosition();                //--- Update slider position
            ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, scroll_slider_hovered ? C'100,100,100' : C'80,80,80'); //--- Reset slider color
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Re-enable chart scrolling
         }
      }
      prevMouseState = MouseState;                 //--- Update previous mouse state
   } else if (id == CHARTEVENT_MOUSE_WHEEL) {      //--- Handle mouse wheel events
      int wheel_delta = (int)sparam;               //--- Get wheel delta
      bool in_body = (mouseX >= bodyX && mouseX <= bodyX + bodyW && mouseY >= textAreaEventY && mouseY <= textAreaEventY + textAreaEventH); //--- Check if mouse in body
      if (in_body && g_total_height > g_visible_height && g_adjustedLineHeight > 0) { //--- Check scroll conditions
         int direction = (wheel_delta > 0) ? -1 : 1; //--- Determine scroll direction
         int notches = MathAbs(wheel_delta) / 120; //--- Calculate scroll notches
         int scroll_amount = g_adjustedLineHeight * direction * notches; //--- Calculate scroll amount
         scroll_pos += scroll_amount;              //--- Update scroll position
         int max_scroll = g_max_scroll;            //--- Get maximum scroll
         scroll_pos = MathMax(0, MathMin(scroll_pos, max_scroll)); //--- Clamp scroll position
         UpdateBodyDisplay();                      //--- Update body text display
         if (scroll_visible) {                     //--- Check if scrollbar visible
            UpdateSliderPosition();                //--- Update slider position
            UpdateButtonColors();                  //--- Update button colors
         }
         ChartRedraw();                            //--- Redraw chart
      }
   }
}

最後に、OnChartEvent関数内ではクリックイベント(CHARTEVENT_OBJECT_CLICK)を処理します。クリックされたオブジェクト(sparam)を確認し、ヘッダキャンセル(SETUP_HEADER_CANCEL)またはキャンセルボタン(SETUP_CANCEL_BUTTON)の場合は、グローバル変数GV_SETUPをGlobalVariableSetで0.0に設定し、DeleteDashboardでダッシュボードを削除します。OKボタン(SETUP_OK_BUTTON)の場合は、チェックボックスが選択されている場合(checkbox_checked)はGV_SETUPを1.0、そうでない場合は0.0に設定し、ダッシュボードを削除します。チェックボックスのコンポーネント(SETUP_CHECKBOX_BG、SETUP_CHECKBOX_TEXT、SETUP_CHECKBOX_LABEL)がクリックされた場合は、checkbox_checkedを切り替え、チェックマーク(Unicode 252)またはスペースをObjectSetStringでチェックボックスラベルに設定します。さらに、テキストの色をObjectSetIntegerでライトブルー(C173,216,230)または白に設定し、チェックボックスの背景を緑(C0,128,0)または灰色(C60,60,60)に調整します。MQL5にはWingdingsフォントの詳細な文字リストがあり、それを使用しています。必要に応じて任意の文字や別の方法を使用できます。以下は使用可能なMQL5 Wingdings文字の例です。

MQL5 WINGDINGS

スクロールアップ(SCROLL_UP_REC、SCROLL_UP_LABEL)またはスクロールダウン(SCROLL_DOWN_REC、SCROLL_DOWN_LABEL)のクリックでは、それぞれScrollUpまたはScrollDownを呼び出します。マウス移動イベント(CHARTEVENT_MOUSE_MOVE)では、本文エリアを計算し、mouse_in_bodyを更新して、mouseXとmouseYを渡してUpdateHoverEffectsを呼び出します。スライダーのドラッグ開始(MouseState 1)は、マウスがSCROLL_SLIDER上にあるかを確認してmovingStateSliderを設定し、マウス位置を保持することで検出します。ドラッグ中は、ObjectSetIntegerでスライダーのy座標を調整し、スクロール比率に基づいてscroll_posを更新し、UpdateBodyDisplayで表示を更新します。マウスリリース(MouseState 0)時には、scroll_posを最寄りの行にスナップし、スライダー状態をリセットし、ChartSetIntegerでチャートスクロールを再度有効化します。マウスホイールイベント(CHARTEVENT_MOUSE_WHEEL)では、ホイールの方向とノッチに基づきscroll_posを調整し、g_max_scroll内にクランプして表示とスクロールバーを更新します。コンパイルすると、次の結果が得られます。

フル機能を備えた最終ウィザード

画像から、ダッシュボードが完全に機能しており、インタラクティブであることが確認できます。OKボタンをクリックすると、グローバル変数が設定されるはずです。グローバル変数にアクセスするには、[Tools]から[Global Variables]を選択するか、キーボードのF3キーを押します。以下はその完全な可視化です。

グローバル変数設定

画像から、ウィザードが正しく設定され、すべての目的が達成されていることが確認できます。残されているのはプロジェクトの実用性をテストすることであり、それは次のセクションで扱います。


設定ウィザードのテスト

テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

ウィザードバックテストGIF


結論

MQL5でEA向けの初回実行ユーザーセットアップウィザードを開発し、スクロール可能なガイド、動的テキストフォーマット、ボタンやチェックボックスなどのユーザー操作要素を備えたインタラクティブなダッシュボードを作成しました。これにより、MetaTrader 5でのプログラム設定が効率化されます。このツールは、明確な手順を提供し、将来の表示をスキップする仕組みを備えることで、トレーダーの初期導入を支援し、画面設定に応じた適応性を確保します。カスタムの一度きりのユーザーインサイトを提供することで、プログラムの初期化を簡素化でき、さらに取引ツールキットでのカスタマイズにも対応可能です。取引をお楽しみください。

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

添付されたファイル |
MQL5での取引戦略の自動化(第36回):リテストとインパルスモデルによる需給取引 MQL5での取引戦略の自動化(第36回):リテストとインパルスモデルによる需給取引
本記事では、MQL5を用いて、需給(S&D: Supply and Demand)取引システムを構築します。本システムは、レンジ相場による需給ゾーンの特定、インパルスムーブによるゾーンの検証、そしてトレンド確認を伴うリテストでのエントリーをおこないます。さらに、カスタマイズ可能なリスク管理パラメータやトレーリングストップをサポートし、動的なラベルやカラー表示によるゾーンの可視化も実装しています。
知っておくべきMQL5ウィザードのテクニック(第81回): β-VAE推論学習で一目均衡表とADX-Wilderのパターンを利用する 知っておくべきMQL5ウィザードのテクニック(第81回): β-VAE推論学習で一目均衡表とADX-Wilderのパターンを利用する
本記事は第80回の続編です。前回は、強化学習フレームワーク下で一目均衡表とADXの組み合わせを検証しました。今回は焦点を推論学習に移します。一目均衡表とADXは前回も述べた通り補完的な指標ですが、今回は前回の記事で触れたパイプライン使用に関する結論を再検討します。推論学習には、変分オートエンコーダのβアルゴリズムを用います。また、MQL5ウィザードとの統合を目的として設計されたカスタムシグナルクラスの実装も継続します。
古典的な戦略を再構築する(第16回):ダブルボリンジャーバンドブレイクアウト 古典的な戦略を再構築する(第16回):ダブルボリンジャーバンドブレイクアウト
本記事では、古典的なボリンジャーバンドのブレイクアウト戦略を再考し、その弱点を補う手法を紹介します。古典的戦略は、偽のブレイクアウトに弱いというよく知られた課題があります。本記事では、その弱点に対する一つの解決策として「ダブルボリンジャーバンド戦略」を提示します。この比較的知られていない手法は、従来戦略の弱点を補い、市場をより動的に捉える視点を提供します。これにより、従来のルールに縛られた制約を超え、トレーダーにとってより適応力のあるフレームワークを提供できるのです。
MQL5でのAI搭載取引システムの構築(第3回):スクロール対応の単一スレッド型チャットUIへのアップグレード MQL5でのAI搭載取引システムの構築(第3回):スクロール対応の単一スレッド型チャットUIへのアップグレード
本記事では、MQL5で構築したChatGPT統合プログラムを、タイムスタンプ付きの会話履歴管理と動的スクロール機構を備えた、単一スレッド型チャット指向のUIへとアップグレードします。本システムはJSON解析を用いてマルチターンのメッセージを管理し、スクロールバー表示モードの切り替えやホバーエフェクトをサポートすることで、実装面と操作性の両面からユーザー体験を向上させます。