MQL5取引ツール(第8回):ドラッグ&最小化可能な拡張情報ダッシュボード
はじめに
前回の記事(第7回)では、MetaQuotes Language 5 (MQL5)で、複数銘柄のポジションや口座指標(「残高」「証拠金」「余剰証拠金」など)を監視できる情報ダッシュボードを開発しました。このダッシュボードには、列のソート機能やComma Separated Values (CSV)形式でのエクスポート機能も実装しました。今回の第8回では、このダッシュボードをさらに拡張し、ドラッグ&最小化機能を追加するほか、ポジションのクローズや表示切り替え、エクスポートのためのインタラクティブボタンや、ホバーエフェクトを導入し、より動的なユーザー体験を実現します。この拡張により、リアルタイムのポジション追跡やヘッダーのグロー効果は維持されます。本記事では以下のトピックを扱います。
これらを通じて、効率的な取引監視に適した、汎用性が高く使いやすいMQL5ダッシュボードを作成できるようになります。それでは、さっそく始めましょう。
拡張ダッシュボードアーキテクチャの理解
第7回の情報ダッシュボードをベースに、ドラッグ&最小化機能、インタラクティブボタン、ホバーエフェクトを追加することで、複数ポジションの管理をより柔軟かつユーザーフレンドリーにします。これらの拡張は、ダッシュボードをチャート上の任意の位置に移動できるようにして分析時の画面の混雑を減らすことや、最小化機能で画面スペースを節約すること、さらにインタラクティブ要素で即時の視覚的フィードバックを提供することに有効です。これにより、スピードが求められる取引環境でも快適に操作できます。
具体的には、ドラッグ操作やボタンクリックのためのマウスイベント処理を組み込み、コアとなるポジション追跡機能を失うことなく、ダッシュボードをレスポンシブで適応性のあるものにします。また、エクスポート機能はヘッダーにアイコンを追加して視認性を高めつつ、従来のキーボードショートカットも維持します。これらの拡張を加えながら、ソート可能なグリッドやリアルタイム更新を維持することで、日常の取引で直感的かつ効率的に使えるツールを作り上げます。以下では、目指す機能の概要を示したうえで、実装の説明に進みます。

MQL5での実装
MQL5でプログラムを拡張するために、新しいダッシュボードコンポーネントを定義する必要があります。通常、これらは4つのコンポーネントになります。
//--- existing components #define HEADER_PANEL_TEXT "HEADER_PANEL_TEXT"//--- Header title label #define CLOSE_BUTTON "BUTTON_CLOSE" //--- Close button identifier #define EXPORT_BUTTON "BUTTON_EXPORT" //--- Export button identifier #define TOGGLE_BUTTON "BUTTON_TOGGLE" //--- Toggle (minimize/maximize) button //--- the rest of the components
既存のコンポーネントに加えて、情報ダッシュボードの拡張機能をサポートするための新しい定義を追加します。これにより、インタラクティブなユーザーインターフェイス(UI)要素の識別子を導入できます。HEADER_PANEL_TEXTはダッシュボードのタイトルラベル用にHEADER_PANEL_TEXTと定義しており、明確な視覚的ヘッダーを提供します。CLOSE_BUTTONはBUTTON_CLOSEと定義し、ダッシュボードを閉じるためのボタン識別子を作成します。これにより、チャート上からダッシュボードを削除できます。EXPORT_BUTTONはBUTTON_EXPORTと定義し、CSVエクスポートを実行するボタンをセットアップします。これにより、データのアクセス性が向上します。TOGGLE_BUTTONはBUTTON_TOGGLEと定義し、ダッシュボードを最小化・最大化するボタンを有効にします。これにより、画面スペースの管理が容易になります。
これらの定義により、新しいインタラクティブコンポーネントの命名が整理され、ドラッグ&最小化機能拡張をサポートできます。次におこなう変更としては、ヘッダーのシャドウ色を明確に定義されたMQL5定数に置き換えます。
// Dashboard settings struct DashboardSettings { //--- Structure for dashboard settings int panel_x; //--- X-coordinate of panel int panel_y; //--- Y-coordinate of panel int row_height; //--- Height of each row int font_size; //--- Font size for labels string font; //--- Font type for labels color bg_color; //--- Background color of main panel color border_color; //--- Border color of panels color header_color; //--- Default color for header text color text_color; //--- Default color for text color section_bg_color; //--- Background color for header/footer panels int zorder_panel; //--- Z-order for main panel int zorder_subpanel; //--- Z-order for sub-panels int zorder_labels; //--- Z-order for labels int label_y_offset; //--- Y-offset for label positioning int label_x_offset; //--- X-offset for label positioning int header_x_distances[9]; //--- X-distances for header labels (9 columns) color header_shades[12]; //--- Array of header color shades for glow effect } settings = { //--- Initialize settings with default values 20, //--- panel_x 20, //--- panel_y 24, //--- row_height 11, //--- font_size "Calibri Bold", //--- font C'240,240,240', //--- bg_color (light gray) clrBlack, //--- border_color C'0,50,70', //--- header_color (dark teal) clrBlack, //--- text_color C'200,220,230', //--- section_bg_color (light blue-gray) 100, //--- zorder_panel 101, //--- zorder_subpanel 102, //--- zorder_labels 3, //--- label_y_offset 25, //--- label_x_offset {10, 120, 170, 220, 280, 330, 400, 470, 530}, //--- header_x_distances {clrBlack, clrRed, clrBlue, clrGreen, clrMagenta, clrDarkOrchid, clrDeepPink, clrSkyBlue, clrDodgerBlue, clrDarkViolet, clrOrange, clrCrimson} //--- header_shades }; //--- the previous one was as below /* //--- {C'0,0,0', C'255,0,0', C'0,255,0', C'0,0,255', C'255,255,0', C'0,255,255', C'255,0,255', C'255,255,255', C'255,0,255', C'0,255,255', C'255,255,0', C'0,0,255'} */
ここでは、DashboardSettings構造体を拡張し、header_shades配列を更新してヘッダーのグロー効果を改善し、視覚的に魅力的な表示を実現します。従来、header_shadesには基本的なRGB色(たとえば、純粋な黒、赤、緑、青、黄、シアン、マゼンタ、白)が混在しており、グローサイクルに使用されていました。今回、header_shadesは厳選された12色(clrBlack、clrRed、clrBlue、clrGreen、clrMagenta、clrDarkOrchid、clrDeepPink、clrSkyBlue、clrDodgerBlue、clrDarkViolet、clrOrange、clrCrimson)で定義します。
このアップグレードにより、より豊かで多彩なパレットがグローサイクルに反映され、ヘッダーの強調表示機能を維持しながら、ダッシュボードの美観が向上します。最後に、ホバーやドラッグ状態を管理するための追加のグローバル変数も定義し、ダッシュボードをより動的に操作できるようにします。
//--- added global variables int prev_num_symbols = 0; //--- Previous number of active symbols bool panel_is_visible = true; //--- Flag to control panel visibility bool panel_minimized = false; //--- Flag to control minimized state bool panel_dragging = false; //--- Flag to track if panel is being dragged int panel_drag_x = 0; //--- Mouse x-coordinate when drag starts int panel_drag_y = 0; //--- Mouse y-coordinate when drag starts int panel_start_x = 0; //--- Panel x-coordinate when drag starts int panel_start_y = 0; //--- Panel y-coordinate when drag starts int prev_mouse_state = 0; //--- Previous mouse state bool header_hovered = false; //--- Header hover state bool toggle_hovered = false; //--- Toggle button hover state bool close_hovered = false; //--- Close button hover state bool export_hovered = false; //--- Export button hover state int last_mouse_x = 0; //--- Last mouse x position int last_mouse_y = 0; //--- Last mouse y position bool prev_header_hovered = false; //--- Previous header hover state bool prev_toggle_hovered = false; //--- Previous toggle hover state bool prev_close_hovered = false; //--- Previous close button hover state bool prev_export_hovered = false; //--- Previous export button hover state
最後に、拡張されたインタラクティビティとドラッグ機能をサポートするために、追加のグローバル変数を導入します。まず、prev_num_symbolsを0に設定し、アクティブな銘柄数の変化を追跡してダッシュボードの動的リサイズに対応します。panel_is_visibleをtrueに設定してダッシュボードの表示/非表示を制御し、panel_minimizedをfalseに設定して最小化状態を管理します。ドラッグ機能を有効にするために、panel_draggingをfalseに設定してドラッグ状態を追跡し、panel_drag_xとpanel_drag_yを0に設定してドラッグ開始時のマウス座標を保持します。さらに、panel_start_xとpanel_start_yを0に設定してドラッグ開始時のパネル座標を保持します。
マウスクリック状態を監視するためにprev_mouse_stateを0に設定します。ホバーエフェクト用には、header_hovered、toggle_hovered、close_hovered、export_hoveredをfalseに設定し、ヘッダーや各ボタンのホバー状態を追跡します。最後のマウス位置はlast_mouse_xとlast_mouse_yに0を設定して保存し、ホバー状態の変化を検出するためにprev_header_hovered、prev_toggle_hovered、prev_close_hovered、prev_export_hoveredをfalseに設定します。
これらの変数により、ドラッグや最小化、ホバー時のフィードバックなど、動的なUI操作が可能になります。変数の更新が完了したので、次に関数も更新してオブジェクト作成を標準化します。これにより、モジュール化された高度な設計を実現できます。まずはラベルを作成し、ツールチップ機能を追加する関数から始めます。
//+------------------------------------------------------------------+ //| Creating label object | //+------------------------------------------------------------------+ bool createLABEL(string objName, string txt, int xD, int yD, color clrTxt, int fontSize, string font, int anchor, string tooltip = "", bool selectable = false) { if(!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Creating label object Print(__FUNCTION__, ": Failed to create label '", objName, "'. Error code = ", GetLastError()); //--- Logging creation failure return(false); //--- Returning failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Setting x-coordinate ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Setting y-coordinate ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Setting corner alignment ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Setting text content ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Setting font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Setting font type ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Setting text color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Setting to foreground ObjectSetInteger(0, objName, OBJPROP_STATE, selectable); //--- Setting selectable state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, selectable); //--- Setting selectability ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Setting not selected ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor); //--- Setting anchor point ObjectSetInteger(0, objName, OBJPROP_ZORDER, settings.zorder_labels); //--- Setting z-order //--- added this tooltip feature ObjectSetString(0, objName, OBJPROP_TOOLTIP, tooltip == "" ? (selectable ? "Click to sort" : "Position data") : tooltip); //--- Setting tooltip text //--- //--- the existing was a hardcoded to this // ObjectSetString(0, objName, OBJPROP_TOOLTIP, selectable ? "Click to sort" : "Position data"); //--- Set tooltip //--- ChartRedraw(0); //--- Redrawing chart return(true); //--- Returning success }
createLABEL関数では、ツールチップのロジックを改善し、さまざまなUI要素で柔軟かつ再利用可能にしました。従来は、ツールチップがObjectSetStringでOBJPROP_TOOLTIPを設定する際にハードコードされており、選択可能なラベルでは「Click to sort」、選択不可のラベルでは「Position data」と固定されていたため、カスタマイズの幅が限られていました。今回、tooltipパラメータを追加し、デフォルトは空文字に設定しました。ObjectSetStringでOBJPROP_TOOLTIPを設定する際に三項演算子を使用し、tooltipが空の場合は選択可能ラベルでは「Click to sort」、その他では「Position data」をデフォルトとして使用し、tooltipが指定されていればその値を使用するように変更しました。この変更により、「Minimize dashboard」や「Close dashboard」などのボタン固有のツールチップを設定できる一方で、ヘッダーやデータラベルではデフォルトを維持でき、ユーザーへの案内と操作性の明確化が向上します。
次に、パネル作成を標準化するために、OnInitイベントハンドラ内でのインラインのオブジェクト作成呼び出しを関数に置き換え、保守性を向上させます。以下はその際に採用するロジックです。
//+------------------------------------------------------------------+ //| Creating rectangle object | //+------------------------------------------------------------------+ bool createRectangle(string object_name, int x_distance, int y_distance, int x_size, int y_size, color background_color, color border_color = clrBlack) { if(!ObjectCreate(0, object_name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Creating rectangle object Print(__FUNCTION__, ": Failed to create Rectangle: '", object_name, "'. Error code = ", GetLastError()); //--- Logging creation failure return(false); //--- Returning failure } ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance); //--- Setting x-coordinate ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance); //--- Setting y-coordinate ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size); //--- Setting width ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size); //--- Setting height ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Setting corner alignment ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color); //--- Setting background color ObjectSetInteger(0, object_name, OBJPROP_BORDER_COLOR, border_color); //--- Setting border color ObjectSetInteger(0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Setting border type ObjectSetInteger(0, object_name, OBJPROP_BACK, false); //--- Setting to foreground ObjectSetInteger(0, object_name, OBJPROP_ZORDER, settings.zorder_subpanel); //--- Setting z-order return(true); //--- Returning success }
ここでは、単純にboolean型のcreateRectangle関数を作成し、ラベル作成時と同様の構造で定義をおこないます。特に新しい内容ではないため、時間を節約して次の更新に進みます。次の更新はカウント関数の軽微な修正で、ループの方向を変更するものです。
//+------------------------------------------------------------------+ //| Counting total positions for a symbol | //+------------------------------------------------------------------+ string countPositionsTotal(string symbol) { int totalPositions = 0; //--- Initializing position counter int count_Total_Pos = PositionsTotal(); //--- Getting total positions for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterating through positions ulong ticket = PositionGetTicket(i); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) totalPositions++; //--- Checking symbol and magic } } return IntegerToString(totalPositions); //--- Returning total as string } //+------------------------------------------------------------------+ //| Counting buy or sell positions for a symbol | //+------------------------------------------------------------------+ string countPositions(string symbol, ENUM_POSITION_TYPE pos_type) { int totalPositions = 0; //--- Initializing position counter int count_Total_Pos = PositionsTotal(); //--- Getting total positions for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterating through positions ulong ticket = PositionGetTicket(i); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_TYPE) == pos_type && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol, type, magic totalPositions++; //--- Incrementing counter } } } return IntegerToString(totalPositions); //--- Returning total as string } //+------------------------------------------------------------------+ //| Counting pending orders for a symbol | //+------------------------------------------------------------------+ string countOrders(string symbol) { int total = 0; //--- Initializing counter int tot = OrdersTotal(); //--- Getting total orders for(int i = tot - 1; i >= 0; i--) { //--- Iterating through orders ulong ticket = OrderGetTicket(i); //--- Getting order ticket if(ticket > 0 && OrderSelect(ticket)) { //--- Checking if order selected if(OrderGetString(ORDER_SYMBOL) == symbol && (MagicNumber < 0 || OrderGetInteger(ORDER_MAGIC) == MagicNumber)) total++; //--- Checking symbol and magic } } return IntegerToString(total); //--- Returning total as string } //+------------------------------------------------------------------+ //| Summing double property for positions of a symbol | //+------------------------------------------------------------------+ string sumPositionDouble(string symbol, ENUM_POSITION_PROPERTY_DOUBLE prop) { double total = 0.0; //--- Initializing total int count_Total_Pos = PositionsTotal(); //--- Getting total positions for(int i = count_Total_Pos - 1; i >= 0; i--) { //--- Iterating through positions ulong ticket = PositionGetTicket(i); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol and magic total += PositionGetDouble(prop); //--- Adding property value } } } return DoubleToString(total, 2); //--- Returning total as string } //+------------------------------------------------------------------+ //| Summing commission for positions of a symbol from history | //+------------------------------------------------------------------+ double sumPositionCommission(string symbol) { double total_comm = 0.0; //--- Initializing total commission int pos_total = PositionsTotal(); //--- Getting total positions for(int p = 0; p < pos_total; p++) { //--- Iterating through positions ulong ticket = PositionGetTicket(p); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if selected if(PositionGetString(POSITION_SYMBOL) == symbol && (MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber)) { //--- Checking symbol and magic long pos_id = PositionGetInteger(POSITION_IDENTIFIER); //--- Getting position ID if(HistorySelectByPosition(pos_id)) { //--- Selecting history by position int deals_total = HistoryDealsTotal(); //--- Getting total deals for(int d = 0; d < deals_total; d++) { //--- Iterating through deals ulong deal_ticket = HistoryDealGetTicket(d); //--- Getting deal ticket if(deal_ticket > 0) { //--- Checking valid total_comm += HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); //--- Adding commission } } } } } } return total_comm; //--- Returning total commission } //+------------------------------------------------------------------+ //| Collecting active symbols with positions or orders | //+------------------------------------------------------------------+ void CollectActiveSymbols() { string symbols_temp[]; //--- Temporary array for symbols int added = 0; //--- Counter for added symbols // Collecting from positions int pos_total = PositionsTotal(); //--- Getting total positions for(int i = pos_total - 1; i >= 0; i--) { //--- Iterating through positions ulong ticket = PositionGetTicket(i); //--- Getting position ticket if(ticket > 0 && PositionSelectByTicket(ticket)) { //--- Checking if position selected if(MagicNumber < 0 || PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Checking magic number string sym = PositionGetString(POSITION_SYMBOL); //--- Getting symbol bool found = false; //--- Flag for symbol found for(int k = 0; k < added; k++) { //--- Checking existing symbols if(symbols_temp[k] == sym) { //--- Symbol already added found = true; //--- Setting found flag break; //--- Exiting loop } } if(!found) { //--- If not found ArrayResize(symbols_temp, added + 1); //--- Resizing array symbols_temp[added] = sym; //--- Adding symbol added++; //--- Incrementing counter } } } } // Collecting from orders int ord_total = OrdersTotal(); //--- Getting total orders for(int i = ord_total - 1; i >= 0; i--) { //--- Iterating through orders ulong ticket = OrderGetTicket(i); //--- Getting order ticket if(ticket > 0 && OrderSelect(ticket)) { //--- Checking if order selected if(MagicNumber < 0 || OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Checking magic number string sym = OrderGetString(ORDER_SYMBOL); //--- Getting symbol bool found = false; //--- Flag for symbol found for(int k = 0; k < added; k++) { //--- Checking existing symbols if(symbols_temp[k] == sym) { //--- Symbol already added found = true; //--- Setting found flag break; //--- Exiting loop } } if(!found) { //--- If not found ArrayResize(symbols_temp, added + 1); //--- Resizing array symbols_temp[added] = sym; //--- Adding symbol added++; //--- Incrementing counter } } } } // Setting symbol_data ArrayResize(symbol_data, added); //--- Resizing symbol data array for(int i = 0; i < added; i++) { //--- Iterating through added symbols symbol_data[i].name = symbols_temp[i]; //--- Setting symbol name symbol_data[i].buys = 0; //--- Initializing buys symbol_data[i].sells = 0; //--- Initializing sells symbol_data[i].trades = 0; //--- Initializing trades symbol_data[i].lots = 0.0; //--- Initializing lots symbol_data[i].profit = 0.0; //--- Initializing profit symbol_data[i].pending = 0; //--- Initializing pending symbol_data[i].swaps = 0.0; //--- Initializing swaps symbol_data[i].comm = 0.0; //--- Initializing commission symbol_data[i].buys_str = "0"; //--- Initializing buys string symbol_data[i].sells_str = "0"; //--- Initializing sells string symbol_data[i].trades_str = "0"; //--- Initializing trades string symbol_data[i].lots_str = "0.00"; //--- Initializing lots string symbol_data[i].profit_str = "0.00"; //--- Initializing profit string symbol_data[i].pending_str = "0"; //--- Initializing pending string symbol_data[i].swaps_str = "0.00"; //--- Initializing swaps string symbol_data[i].comm_str = "0.00"; //--- Initializing commission string } }
カウント関数のロジックを強化するために、ループの方向を増分から減分に変更しました。MQL5では通常、減分ループの方が安全ですが、元の増分ループを維持することも可能です。さらに、注文やポジションを選択する前にチケットのチェックを追加し、無効なチケットやリストの変更による潜在的なエラーを防止しています。完全に動的なダッシュボードを実現するため、初期のダッシュボード作成処理を関数化し、作成方法を2つに分けます。1つは最大化された状態の作成用、もう1つは最小化された状態の作成用です。
//+------------------------------------------------------------------+ //| Creating full dashboard UI | //+------------------------------------------------------------------+ void createFullDashboard() { CollectActiveSymbols(); //--- Collecting active symbols int num_rows = ArraySize(symbol_data); //--- Getting number of rows int num_columns = ArraySize(headers); //--- Getting number of columns int column_width_sum = 0; //--- Initializing column width sum for(int i = 0; i < num_columns; i++) //--- Iterating through columns column_width_sum += column_widths[i]; //--- Adding column width int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width // Creating main panel string panel_name = PREFIX + PANEL; //--- Defining main panel name createRectangle(panel_name, settings.panel_x, settings.panel_y, panel_width, (num_rows + 3) * settings.row_height, settings.bg_color, settings.border_color); //--- Creating main panel // Creating header panel string header_panel = PREFIX + HEADER_PANEL; //--- Defining header panel name createRectangle(header_panel, settings.panel_x, settings.panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating header panel // Creating header title createLABEL(PREFIX + HEADER_PANEL_TEXT, "Trading Dashboard", settings.panel_x + 10, settings.panel_y + 8 + settings.label_y_offset, clrBlack, 14, settings.font, ANCHOR_LEFT, "Dashboard Title"); //--- Creating header title // Creating export button createLABEL(PREFIX + EXPORT_BUTTON, CharToString(60), settings.panel_x + panel_width - 90, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Click to export or press 'E' key to export", true); //--- Creating export button // Creating toggle button createLABEL(PREFIX + TOGGLE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 60, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Minimize dashboard", true); //--- Creating toggle button // Creating close button createLABEL(PREFIX + CLOSE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 30, settings.panel_y + 12, clrBlack, 18, "Webdings", ANCHOR_CENTER, "Close dashboard", true); //--- Creating close button // Creating headers int header_y = settings.panel_y + settings.row_height + 8 + settings.label_y_offset; //--- Calculating header y-coordinate for(int i = 0; i < num_columns; i++) { //--- Iterating through headers string header_name = PREFIX + HEADER + IntegerToString(i); //--- Defining header label name int header_x = settings.panel_x + settings.header_x_distances[i] + settings.label_x_offset; //--- Calculating header x-coordinate createLABEL(header_name, headers[i], header_x, header_y, settings.header_color, 12, settings.font, ANCHOR_LEFT, "Click to sort", true); //--- Creating header label } // Creating symbol and data labels int first_row_y = header_y + settings.row_height; //--- Calculating first row y-coordinate int symbol_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting symbol x-coordinate for(int i = 0; i < num_rows; i++) { //--- Iterating through rows string symbol_name = PREFIX + SYMB + IntegerToString(i); //--- Defining symbol label name createLABEL(symbol_name, symbol_data[i].name, symbol_x, first_row_y + i * settings.row_height + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Symbol name"); //--- Creating symbol label int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting data x-offset for(int j = 0; j < num_columns - 1; j++) { //--- Iterating through data columns string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name color initial_color = data_default_colors[j]; //--- Setting initial color string initial_txt = (j <= 2 || j == 5) ? "0" : "0.00"; //--- Setting initial text createLABEL(data_name, initial_txt, x_offset, first_row_y + i * settings.row_height + settings.label_y_offset, initial_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Data value"); //--- Creating data label x_offset += column_widths[j + 1]; //--- Updating x-offset } } // Creating footer panel int footer_y = settings.panel_y + (num_rows + 3) * settings.row_height - settings.row_height; //--- Calculating footer y-coordinate string footer_panel = PREFIX + FOOTER_PANEL; //--- Defining footer panel name createRectangle(footer_panel, settings.panel_x, footer_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating footer panel // Creating footer text and data int footer_text_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting footer text x-coordinate createLABEL(PREFIX + FOOTER_TEXT, "Total:", footer_text_x, footer_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Totals"); //--- Creating footer text label int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting footer data x-offset for(int j = 0; j < num_columns - 1; j++) { //--- Iterating through footer data string footer_data_name = PREFIX + FOOTER_DATA + IntegerToString(j); //--- Defining footer data label name color footer_color = data_default_colors[j]; //--- Setting footer data color string initial_txt = (j <= 2 || j == 5) ? "0" : "0.00"; //--- Setting initial text createLABEL(footer_data_name, initial_txt, x_offset, footer_y + 8 + settings.label_y_offset, footer_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Total value"); //--- Creating footer data label x_offset += column_widths[j + 1]; //--- Updating x-offset } // Creating account panel int account_panel_y = footer_y + settings.row_height + 5; //--- Calculating account panel y-coordinate string account_panel_name = PREFIX + ACCOUNT_PANEL; //--- Defining account panel name createRectangle(account_panel_name, settings.panel_x, account_panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating account panel // Creating account text and data labels int acc_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting account label x-coordinate int acc_data_offset = 160; //--- Setting data offset int acc_spacing = (panel_width - 45) / ArraySize(account_items); //--- Calculating spacing for(int k = 0; k < ArraySize(account_items); k++) { //--- Iterating through account items string acc_text_name = PREFIX + ACC_TEXT + IntegerToString(k); //--- Defining account text label name int text_x = acc_x + k * acc_spacing; //--- Calculating text x-coordinate createLABEL(acc_text_name, account_items[k] + ":", text_x, account_panel_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_LEFT, "Account info"); //--- Creating account text label string acc_data_name = PREFIX + ACC_DATA + IntegerToString(k); //--- Defining account data label name int data_x = text_x + acc_data_offset; //--- Calculating data x-coordinate createLABEL(acc_data_name, "0.00", data_x, account_panel_y + 8 + settings.label_y_offset, settings.text_color, settings.font_size, settings.font, ANCHOR_RIGHT, "Account value"); //--- Creating account data label } ChartRedraw(0); //--- Redrawing chart } //+------------------------------------------------------------------+ //| Creating minimized dashboard UI | //+------------------------------------------------------------------+ void createMinimizedDashboard() { int num_columns = ArraySize(headers); //--- Getting number of columns int column_width_sum = 0; //--- Initializing column width sum for(int i = 0; i < num_columns; i++) //--- Iterating through columns column_width_sum += column_widths[i]; //--- Adding column width int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width // Creating header panel createRectangle(PREFIX + HEADER_PANEL, settings.panel_x, settings.panel_y, panel_width, settings.row_height, settings.section_bg_color, settings.border_color); //--- Creating header panel // Creating header title createLABEL(PREFIX + HEADER_PANEL_TEXT, "Trading Dashboard", settings.panel_x + 10, settings.panel_y + 8 + settings.label_y_offset, clrBlack, 14, settings.font, ANCHOR_LEFT, "Dashboard Title"); //--- Creating header title // Creating export button createLABEL(PREFIX + EXPORT_BUTTON, CharToString(60), settings.panel_x + panel_width - 90, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Click to export or press 'E' key to export", true); //--- Creating export button // Creating toggle button (maximize) createLABEL(PREFIX + TOGGLE_BUTTON, CharToString('o'), settings.panel_x + panel_width - 60, settings.panel_y + 12, clrBlack, 18, "Wingdings", ANCHOR_CENTER, "Maximize dashboard", true); //--- Creating toggle button // Creating close button createLABEL(PREFIX + CLOSE_BUTTON, CharToString('r'), settings.panel_x + panel_width - 30, settings.panel_y + 12, clrBlack, 18, "Webdings", ANCHOR_CENTER, "Close dashboard", true); //--- Creating close button ChartRedraw(0); //--- Redrawing chart } //+------------------------------------------------------------------+ //| Deleting all dashboard objects | //+------------------------------------------------------------------+ void deleteAllObjects() { ObjectsDeleteAll(0, PREFIX, -1, -1); //--- Deleting all objects with prefix }
UIを管理し、フル表示と最小化表示の両方に対応したインタラクティブ要素を提供するために、createFullDashboard、createMinimizedDashboard、deleteAllObjects関数を実装します。createFullDashboardでは、まずCollectActiveSymbolsを呼び出してsymbol_dataを取得し、ArraySizeでnum_rowsとnum_columnsを計算します。また、column_widthsとsettings.header_x_distancesを使用してpanel_widthを算出します。メインパネルはcreateRectangleを使用して「PREFIX + PANEL」で作成し、ヘッダーパネルは「PREFIX + HEADER_PANEL」、ヘッダーのタイトルはcreateLABELで「PREFIX + HEADER_PANEL_TEXT」として「Trading Dashboard」として作成します。
さらに、ボタンはcreateLABELで追加します。「PREFIX + EXPORT_BUTTON」(Wingdings 60)、「PREFIX + TOGGLE_BUTTON」(最小化用にWingdings「r」)、「PREFIX + CLOSE_BUTTON」(Webdings「r」)を作成し、それぞれに特定のツールチップを設定し、選択可能にします。アイコンスタイルの選択は任意ですが、フォントに使用できるものを簡潔に示します。フォントやシンボルは正確なものを使用してください。

次に、ヘッダーラベルはheadersを計算済みの位置に作成し、銘柄ラベルはsymbol_data[i].name用に作成します。データラベルは初期値を設定し、フッターパネルは「PREFIX + FOOTER_PANEL」で作成、フッターテキストは「Total:」、フッターデータラベル、口座パネルは「PREFIX + ACCOUNT_PANEL」、口座ラベルはaccount_items用に作成します。すべてcreateRectangleとcreateLABELを使用し、適切な座標と色を指定した後、ChartRedrawを実行します。
createMinimizedDashboardでは、ヘッダーパネルのみのコンパクトなUIを作成します。createRectangleで「PREFIX + HEADER_PANEL」を作成し、createLABELで「PREFIX + HEADER_PANEL_TEXT」としてヘッダータイトルを作成します。また、ボタンはエクスポート(Wingdings 60)、トグル(最大化用にWingdings「o」)、クローズ(Webdings「r」)を作成し、画面の使用を最小限に抑えてChartRedrawを実行します。
deleteAllObjects関数では、ObjectsDeleteAllを使用してPREFIXに関連するすべてのチャートとタイプのダッシュボードオブジェクトを削除し、UI更新や閉鎖時にクリーンな状態を保証します。これらの関数により、フル表示と最小化表示に対応した柔軟なダッシュボードを実現でき、ドラッグやトグルなどのユーザー操作をサポートします。次に、これらの動的関数を用いてダッシュボード関数を更新していきます。
//+------------------------------------------------------------------+ //| Updating dashboard data and visuals | //+------------------------------------------------------------------+ void UpdateDashboard() { bool needs_redraw = false; //--- Initializing redraw flag CollectActiveSymbols(); //--- Collecting active symbols int current_num = ArraySize(symbol_data); //--- Getting current number of symbols if(current_num != prev_num_symbols) { //--- Checking if symbol count changed deleteAllObjects(); //--- Deleting all objects if(panel_minimized) { //--- Checking if minimized createMinimizedDashboard(); //--- Creating minimized dashboard } else { createFullDashboard(); //--- Creating full dashboard } prev_num_symbols = current_num; //--- Updating previous symbol count needs_redraw = true; //--- Setting redraw flag } if(!panel_is_visible || panel_minimized) return; //--- Exiting if not visible or minimized // Resetting totals totalBuys = 0; //--- Resetting total buys totalSells = 0; //--- Resetting total sells totalTrades = 0; //--- Resetting total trades totalLots = 0.0; //--- Resetting total lots totalProfit = 0.0; //--- Resetting total profit totalPending = 0; //--- Resetting total pending totalSwap = 0.0; //--- Resetting total swap totalComm = 0.0; //--- Resetting total commission // Calculating symbol data and totals for(int i = 0; i < current_num; i++) { //--- Iterating through symbols string symbol = symbol_data[i].name; //--- Getting symbol name for(int j = 0; j < 8; j++) { //--- Iterating through data columns string value = ""; //--- Initializing value color data_color = data_default_colors[j]; //--- Setting default color double dval = 0.0; //--- Initializing double value int ival = 0; //--- Initializing integer value switch(j) { //--- Handling data type case 0: // Buy positions value = countPositions(symbol, POSITION_TYPE_BUY); //--- Getting buy positions ival = (int)StringToInteger(value); //--- Converting to integer if(value != symbol_data[i].buys_str) { //--- Checking if changed symbol_data[i].buys_str = value; //--- Updating buys string symbol_data[i].buys = ival; //--- Updating buys count } totalBuys += ival; //--- Adding to total buys break; case 1: // Sell positions value = countPositions(symbol, POSITION_TYPE_SELL); //--- Getting sell positions ival = (int)StringToInteger(value); //--- Converting to integer if(value != symbol_data[i].sells_str) { //--- Checking if changed symbol_data[i].sells_str = value; //--- Updating sells string symbol_data[i].sells = ival; //--- Updating sells count } totalSells += ival; //--- Adding to total sells break; case 2: // Total trades value = countPositionsTotal(symbol); //--- Getting total trades ival = (int)StringToInteger(value); //--- Converting to integer if(value != symbol_data[i].trades_str) { //--- Checking if changed symbol_data[i].trades_str = value; //--- Updating trades string symbol_data[i].trades = ival; //--- Updating trades count } totalTrades += ival; //--- Adding to total trades break; case 3: // Lots value = sumPositionDouble(symbol, POSITION_VOLUME); //--- Getting total lots dval = StringToDouble(value); //--- Converting to double if(value != symbol_data[i].lots_str) { //--- Checking if changed symbol_data[i].lots_str = value; //--- Updating lots string symbol_data[i].lots = dval; //--- Updating lots value } totalLots += dval; //--- Adding to total lots break; case 4: // Profit value = sumPositionDouble(symbol, POSITION_PROFIT); //--- Getting total profit dval = StringToDouble(value); //--- Converting to double data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrGray; //--- Setting color based on value if(value != symbol_data[i].profit_str) { //--- Checking if changed symbol_data[i].profit_str = value; //--- Updating profit string symbol_data[i].profit = dval; //--- Updating profit value } totalProfit += dval; //--- Adding to total profit break; case 5: // Pending value = countOrders(symbol); //--- Getting pending orders ival = (int)StringToInteger(value); //--- Converting to integer if(value != symbol_data[i].pending_str) { //--- Checking if changed symbol_data[i].pending_str = value; //--- Updating pending string symbol_data[i].pending = ival; //--- Updating pending count } totalPending += ival; //--- Adding to total pending break; case 6: // Swap value = sumPositionDouble(symbol, POSITION_SWAP); //--- Getting total swap dval = StringToDouble(value); //--- Converting to double data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrPurple; //--- Setting color based on value if(value != symbol_data[i].swaps_str) { //--- Checking if changed symbol_data[i].swaps_str = value; //--- Updating swap string symbol_data[i].swaps = dval; //--- Updating swap value } totalSwap += dval; //--- Adding to total swap break; case 7: // Comm dval = sumPositionCommission(symbol); //--- Getting total commission value = DoubleToString(dval, 2); //--- Formatting commission data_color = (dval > 0) ? clrGreen : (dval < 0) ? clrRed : clrBrown; //--- Setting color based on value if(value != symbol_data[i].comm_str) { //--- Checking if changed symbol_data[i].comm_str = value; //--- Updating commission string symbol_data[i].comm = dval; //--- Updating commission value } totalComm += dval; //--- Adding to total commission break; } } } // Sort after calculating values SortDashboard(); //--- Sorting dashboard data // Update header breathing effect glow_counter += MathMax(UpdateIntervalMs, 10); //--- Incrementing glow counter if(glow_counter >= GLOW_INTERVAL_MS) { //--- Checking if glow interval reached if(glow_direction) { //--- Checking if glowing forward glow_index++; //--- Incrementing glow index if(glow_index >= ArraySize(settings.header_shades) - 1) //--- Checking if at end glow_direction = false; //--- Reversing glow direction } else { //--- Glow backward glow_index--; //--- Decrementing glow index if(glow_index <= 0) //--- Checking if at start glow_direction = true; //--- Reversing glow direction } glow_counter = 0; //--- Resetting glow counter } color header_shade = settings.header_shades[glow_index]; //--- Getting current header shade for(int i = 0; i < ArraySize(headers); i++) { //--- Iterating through headers string header_name = PREFIX + HEADER + IntegerToString(i); //--- Defining header name ObjectSetInteger(0, header_name, OBJPROP_COLOR, header_shade); //--- Updating header color needs_redraw = true; //--- Setting redraw flag } // Update symbol and data labels bool labels_updated = false; //--- Initializing label update flag for(int i = 0; i < current_num; i++) { //--- Iterating through symbols string symbol = symbol_data[i].name; //--- Getting symbol name string symb_name = PREFIX + SYMB + IntegerToString(i); //--- Defining symbol label name string current_symb_txt = ObjectGetString(0, symb_name, OBJPROP_TEXT); //--- Getting current symbol text if(current_symb_txt != symbol) { //--- Checking if symbol changed ObjectSetString(0, symb_name, OBJPROP_TEXT, symbol); //--- Updating symbol text labels_updated = true; //--- Setting label updated flag } for(int j = 0; j < 8; j++) { //--- Iterating through data columns string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name string value; //--- Initializing value color data_color = data_default_colors[j]; //--- Setting default color switch(j) { //--- Handling data type case 0: //--- Buy positions value = symbol_data[i].buys_str; //--- Getting buys string data_color = clrRed; //--- Setting color to red break; case 1: //--- Sell positions value = symbol_data[i].sells_str; //--- Getting sells string data_color = clrGreen; //--- Setting color to green break; case 2: // Total trades value = symbol_data[i].trades_str; data_color = clrDarkGray; break; case 3: //--- Lots value = symbol_data[i].lots_str; //--- Getting lots string data_color = clrOrange; //--- Setting color to orange break; case 4: //--- Profit value = symbol_data[i].profit_str; //--- Getting profit string data_color = (symbol_data[i].profit > 0) ? clrGreen : (symbol_data[i].profit < 0) ? clrRed : clrGray; //--- Setting color based on profit break; case 5: //--- Pending value = symbol_data[i].pending_str; //--- Getting pending string data_color = clrBlue; //--- Setting color to blue break; case 6: //--- Swap value = symbol_data[i].swaps_str; //--- Getting swap string data_color = (symbol_data[i].swaps > 0) ? clrGreen : (symbol_data[i].swaps < 0) ? clrRed : clrPurple; //--- Setting color based on swap break; case 7: //--- Comm value = symbol_data[i].comm_str; //--- Getting commission string data_color = (symbol_data[i].comm > 0) ? clrGreen : (symbol_data[i].comm < 0) ? clrRed : clrBrown; //--- Setting color based on commission break; } if(updateLABEL(data_name, value, data_color)) labels_updated = true; //--- Updating label if changed } } if(labels_updated) needs_redraw = true; //--- Setting redraw flag if labels updated // Updating totals string new_total_buys = IntegerToString(totalBuys); //--- Formatting total buys if(new_total_buys != total_buys_str) { //--- Checking if changed total_buys_str = new_total_buys; //--- Updating buys string if(updateLABEL(PREFIX + FOOTER_DATA + "0", new_total_buys, clrRed)) needs_redraw = true; //--- Updating label } string new_total_sells = IntegerToString(totalSells); //--- Formatting total sells if(new_total_sells != total_sells_str) { //--- Checking if changed total_sells_str = new_total_sells; //--- Updating sells string if(updateLABEL(PREFIX + FOOTER_DATA + "1", new_total_sells, clrGreen)) needs_redraw = true; //--- Updating label } string new_total_trades = IntegerToString(totalTrades); //--- Formatting total trades if(new_total_trades != total_trades_str) { //--- Checking if changed total_trades_str = new_total_trades; //--- Updating trades string if(updateLABEL(PREFIX + FOOTER_DATA + "2", new_total_trades, clrDarkGray)) needs_redraw = true; //--- Updating label } string new_total_lots = DoubleToString(totalLots, 2); //--- Formatting total lots if(new_total_lots != total_lots_str) { //--- Checking if changed total_lots_str = new_total_lots; //--- Updating lots string if(updateLABEL(PREFIX + FOOTER_DATA + "3", new_total_lots, clrOrange)) needs_redraw = true; //--- Updating label } string new_total_profit = DoubleToString(totalProfit, 2); //--- Formatting total profit color total_profit_color = (totalProfit > 0) ? clrGreen : (totalProfit < 0) ? clrRed : clrGray; //--- Setting color based on profit if(new_total_profit != total_profit_str) { //--- Checking if changed total_profit_str = new_total_profit; //--- Updating profit string if(updateLABEL(PREFIX + FOOTER_DATA + "4", new_total_profit, total_profit_color)) needs_redraw = true; //--- Updating label } string new_total_pending = IntegerToString(totalPending); //--- Formatting total pending if(new_total_pending != total_pending_str) { //--- Checking if changed total_pending_str = new_total_pending; //--- Updating pending string if(updateLABEL(PREFIX + FOOTER_DATA + "5", new_total_pending, clrBlue)) needs_redraw = true; //--- Updating label } string new_total_swap = DoubleToString(totalSwap, 2); //--- Formatting total swap color total_swap_color = (totalSwap > 0) ? clrGreen : (totalSwap < 0) ? clrRed : clrPurple; //--- Setting color based on swap if(new_total_swap != total_swap_str) { //--- Checking if changed total_swap_str = new_total_swap; //--- Updating swap string if(updateLABEL(PREFIX + FOOTER_DATA + "6", new_total_swap, total_swap_color)) needs_redraw = true; //--- Updating label } string new_total_comm = DoubleToString(totalComm, 2); //--- Formatting total commission color total_comm_color = (totalComm > 0) ? clrGreen : (totalComm < 0) ? clrRed : clrBrown; //--- Setting color based on commission if(new_total_comm != total_comm_str) { //--- Checking if changed total_comm_str = new_total_comm; //--- Updating commission string if(updateLABEL(PREFIX + FOOTER_DATA + "7", new_total_comm, total_comm_color)) needs_redraw = true; //--- Updating label } // Updating account info double balance = AccountInfoDouble(ACCOUNT_BALANCE); //--- Getting account balance double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Getting account equity double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Getting free margin string new_bal = DoubleToString(balance, 2); //--- Formatting balance if(new_bal != acc_bal_str) { //--- Checking if changed acc_bal_str = new_bal; //--- Updating balance string if(updateLABEL(PREFIX + ACC_DATA + "0", new_bal, clrBlack)) needs_redraw = true; //--- Updating label } string new_eq = DoubleToString(equity, 2); //--- Formatting equity color eq_color = (equity > balance) ? clrGreen : (equity < balance) ? clrRed : clrBlack; //--- Setting color based on equity if(new_eq != acc_eq_str) { //--- Checking if changed acc_eq_str = new_eq; //--- Updating equity string if(updateLABEL(PREFIX + ACC_DATA + "1", new_eq, eq_color)) needs_redraw = true; //--- Updating label } string new_free = DoubleToString(free_margin, 2); //--- Formatting free margin if(new_free != acc_free_str) { //--- Checking if changed acc_free_str = new_free; //--- Updating free margin string if(updateLABEL(PREFIX + ACC_DATA + "2", new_free, clrBlack)) needs_redraw = true; //--- Updating label } if(needs_redraw) { //--- Checking if redraw needed ChartRedraw(0); //--- Redrawing chart } }
UpdateDashboard関数には、処理の先頭でCollectActiveSymbolsを呼び出す処理を追加しました。これにより、常に合計値や残高フィールドが更新されるようになります。次に、銘柄数が変化した場合はdeleteAllObjects関数を呼び出してダッシュボードを破棄し、createFullDashboardまたはcreateMinimizedDashboard関数を通じて再作成します。データ計算ループ内では、利益、スワップ、手数料の色付けロジックを追加しました。以前は部分的にしか反映されていませんでした。変更箇所は識別しやすいようにハイライトしています。最後に、初期化時にこのロジックを呼び出すことで、今回のマイルストーンとなるダッシュボードの完成を確認できます。
//+------------------------------------------------------------------+ //| Initializing expert | //+------------------------------------------------------------------+ int OnInit() { createFullDashboard(); //--- Creating full dashboard prev_num_symbols = ArraySize(symbol_data); //--- Setting initial symbol count ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Enabling mouse move events EventSetMillisecondTimer(MathMax(UpdateIntervalMs, 10)); //--- Setting timer with minimum 10ms UpdateDashboard(); //--- Updating dashboard return(INIT_SUCCEEDED); //--- Returning success } //+------------------------------------------------------------------+ //| Deinitializing expert | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { deleteAllObjects(); //--- Deleting all objects ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); //--- Disabling mouse move events EventKillTimer(); //--- Stopping timer } //+------------------------------------------------------------------+ //| Handling timer for millisecond-based updates | //+------------------------------------------------------------------+ void OnTimer() { if(panel_is_visible && !panel_minimized) { //--- Checking if visible and not minimized UpdateDashboard(); //--- Updating dashboard } }
ここでは、OnInit、OnDeinit、OnTimerイベントハンドラを実装し、ダッシュボードのライフサイクルと更新を管理することで、インタラクティブかつ動的な機能を実現します。OnInit関数では、まずcreateFullDashboardを呼び出して完全なUIを構築し、prev_num_symbolsをArraySizeを用いてsymbol_dataのサイズに設定し、初期銘柄を追跡します。さらに、ChartSetIntegerでCHART_EVENT_MOUSE_MOVEをtrueに設定してマウス移動イベントを有効化し、ドラッグやホバーエフェクトを可能にします。EventSetMillisecondTimerでUpdateIntervalMsと10msの最大値をタイマーに設定し、定期的な更新をおこないます。そして、UpdateDashboardを呼び出して初期データを反映させ、成功時にはINIT_SUCCEEDEDを返します。
OnDeinit関数では、deleteAllObjectsを呼び出してPREFIXに関連するすべてのダッシュボードオブジェクトを削除し、ChartSetIntegerでCHART_EVENT_MOUSE_MOVEをfalseに設定してマウス移動イベントを無効化し、EventKillTimerでタイマーを停止してリソースを解放します。
OnTimer関数では、panel_is_visibleがtrueでpanel_minimizedがfalseの場合にのみUpdateDashboardを呼び出し、ダッシュボードが完全に表示されているときだけデータを更新します。これにより、最小化または非表示状態では処理をおこなわず、効率的な更新を実現します。コンパイル後、初期化時には以下のような成果が確認できます。

画像からも、新機能が正常に表示されていることが確認できます。次に、ドラッグ時にオブジェクトを再作成せずにパネル位置を更新できる関数の作成に進みます。
//+------------------------------------------------------------------+ //| Updating panel object positions | //+------------------------------------------------------------------+ void updatePanelPositions() { int num_rows = ArraySize(symbol_data); //--- Getting number of rows int num_columns = ArraySize(headers); //--- Getting number of columns int column_width_sum = 0; //--- Initializing column width sum for(int i = 0; i < num_columns; i++) //--- Iterating through columns column_width_sum += column_widths[i]; //--- Adding column width int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width // Updating header panel and buttons ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating header panel x-coordinate ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE, settings.panel_y); //--- Updating header panel y-coordinate ObjectSetInteger(0, PREFIX + HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, settings.panel_x + 10); //--- Updating header text x-coordinate ObjectSetInteger(0, PREFIX + HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, settings.panel_y + 8 + settings.label_y_offset); //--- Updating header text y-coordinate ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 90); //--- Updating export button x-coordinate ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating export button y-coordinate ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 60); //--- Updating toggle button x-coordinate ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating toggle button y-coordinate ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE, settings.panel_x + panel_width - 30); //--- Updating close button x-coordinate ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE, settings.panel_y + 12); //--- Updating close button y-coordinate if(!panel_minimized) { //--- Checking if not minimized // Updating main panel ObjectSetInteger(0, PREFIX + PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating main panel x-coordinate ObjectSetInteger(0, PREFIX + PANEL, OBJPROP_YDISTANCE, settings.panel_y); //--- Updating main panel y-coordinate // Updating headers int header_y = settings.panel_y + settings.row_height + 8 + settings.label_y_offset; //--- Calculating header y-coordinate for(int i = 0; i < num_columns; i++) { //--- Iterating through headers string header_name = PREFIX + HEADER + IntegerToString(i); //--- Defining header name int header_x = settings.panel_x + settings.header_x_distances[i] + settings.label_x_offset; //--- Calculating header x-coordinate ObjectSetInteger(0, header_name, OBJPROP_XDISTANCE, header_x); //--- Updating header x-coordinate ObjectSetInteger(0, header_name, OBJPROP_YDISTANCE, header_y); //--- Updating header y-coordinate } // Updating symbol and data labels int first_row_y = header_y + settings.row_height; //--- Calculating first row y-coordinate int symbol_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting symbol x-coordinate for(int i = 0; i < num_rows; i++) { //--- Iterating through rows string symbol_name = PREFIX + SYMB + IntegerToString(i); //--- Defining symbol label name ObjectSetInteger(0, symbol_name, OBJPROP_XDISTANCE, symbol_x); //--- Updating symbol x-coordinate ObjectSetInteger(0, symbol_name, OBJPROP_YDISTANCE, first_row_y + i * settings.row_height + settings.label_y_offset); //--- Updating symbol y-coordinate int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting data x-offset for(int j = 0; j < num_columns - 1; j++) { //--- Iterating through data columns string data_name = PREFIX + DATA + IntegerToString(i) + "_" + IntegerToString(j); //--- Defining data label name ObjectSetInteger(0, data_name, OBJPROP_XDISTANCE, x_offset); //--- Updating data x-coordinate ObjectSetInteger(0, data_name, OBJPROP_YDISTANCE, first_row_y + i * settings.row_height + settings.label_y_offset); //--- Updating data y-coordinate x_offset += column_widths[j + 1]; //--- Updating x-offset } } // Updating footer panel and labels int footer_y = settings.panel_y + (num_rows + 3) * settings.row_height - settings.row_height; //--- Calculating footer y-coordinate ObjectSetInteger(0, PREFIX + FOOTER_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating footer panel x-coordinate ObjectSetInteger(0, PREFIX + FOOTER_PANEL, OBJPROP_YDISTANCE, footer_y); //--- Updating footer panel y-coordinate ObjectSetInteger(0, PREFIX + FOOTER_TEXT, OBJPROP_XDISTANCE, settings.panel_x + 10 + settings.label_x_offset); //--- Updating footer text x-coordinate ObjectSetInteger(0, PREFIX + FOOTER_TEXT, OBJPROP_YDISTANCE, footer_y + 8 + settings.label_y_offset); //--- Updating footer text y-coordinate int x_offset = settings.panel_x + 10 + column_widths[0] + settings.label_x_offset; //--- Setting footer data x-offset for(int j = 0; j < num_columns - 1; j++) { //--- Iterating through footer data string footer_data_name = PREFIX + FOOTER_DATA + IntegerToString(j); //--- Defining footer data name ObjectSetInteger(0, footer_data_name, OBJPROP_XDISTANCE, x_offset); //--- Updating footer data x-coordinate ObjectSetInteger(0, footer_data_name, OBJPROP_YDISTANCE, footer_y + 8 + settings.label_y_offset); //--- Updating footer data y-coordinate x_offset += column_widths[j + 1]; //--- Updating x-offset } // Updating account panel and labels int account_panel_y = footer_y + settings.row_height + 5; //--- Calculating account panel y-coordinate ObjectSetInteger(0, PREFIX + ACCOUNT_PANEL, OBJPROP_XDISTANCE, settings.panel_x); //--- Updating account panel x-coordinate ObjectSetInteger(0, PREFIX + ACCOUNT_PANEL, OBJPROP_YDISTANCE, account_panel_y); //--- Updating account panel y-coordinate int acc_x = settings.panel_x + 10 + settings.label_x_offset; //--- Setting account label x-coordinate int acc_data_offset = 160; //--- Setting data offset int acc_spacing = (panel_width - 45) / ArraySize(account_items); //--- Calculating spacing for(int k = 0; k < ArraySize(account_items); k++) { //--- Iterating through account items string acc_text_name = PREFIX + ACC_TEXT + IntegerToString(k); //--- Defining account text name int text_x = acc_x + k * acc_spacing; //--- Calculating text x-coordinate ObjectSetInteger(0, acc_text_name, OBJPROP_XDISTANCE, text_x); //--- Updating account text x-coordinate ObjectSetInteger(0, acc_text_name, OBJPROP_YDISTANCE, account_panel_y + 8 + settings.label_y_offset); //--- Updating account text y-coordinate string acc_data_name = PREFIX + ACC_DATA + IntegerToString(k); //--- Defining account data name int data_x = text_x + acc_data_offset; //--- Calculating data x-coordinate ObjectSetInteger(0, acc_data_name, OBJPROP_XDISTANCE, data_x); //--- Updating account data x-coordinate ObjectSetInteger(0, acc_data_name, OBJPROP_YDISTANCE, account_panel_y + 8 + settings.label_y_offset); //--- Updating account data y-coordinate } } ChartRedraw(0); //--- Redrawing chart }
ここでは、updatePanelPositions関数を実装し、拡張されたダッシュボードのドラッグ機能を有効にします。これにより、ダッシュボードをドラッグした際にすべてのUI要素が一体となって移動するようになります。まず、symbol_dataとheadersに対してArraySizeを使用してnum_rowsとnum_columnsを計算し、column_widthsを合計し、settings.header_x_distancesとパディングを加えてMathMaxでpanel_widthを算出します。ヘッダーパネルやボタンの位置は、ObjectSetIntegerでOBJPROP_XDISTANCEとOBJPROP_YDISTANCEを設定することで更新します。対象は「PREFIX + HEADER_PANEL」、「PREFIX + HEADER_PANEL_TEXT」、「PREFIX + EXPORT_BUTTON」、「PREFIX + TOGGLE_BUTTON」、「PREFIX + CLOSE_BUTTON」で、設定座標にはsettings.panel_xとsettings.panel_yを使用します。
panel_minimizedがfalseの場合、メインパネルの位置は「PREFIX + PANEL」で更新し、ヘッダーはheader_yを「settings.panel_y + settings.row_height + 8 + settings.label_y_offset」から計算して配置します。銘柄およびデータラベルはfirst_row_yにsymbol_xとx_offsetをcolumn_widthsで調整して配置します。フッターパネルとラベルは「num_rows + 3」から計算したfooter_yに配置し、アカウントパネルとラベルはaccount_panel_yにacc_xとacc_spacingを使用して整列させます。すべてObjectSetIntegerを使用して更新し、最後にChartRedrawを呼び出して表示をリフレッシュします。これにより、ドラッグ中でもダッシュボード全体がシームレスに移動し、レイアウトの整合性が維持されます。次に、ヘッダーやボタン上でのカーソル位置を追跡するロジックを定義する必要があります。以下がその実装に使用したロジックです。
//+------------------------------------------------------------------+ //| Checking if cursor is inside header or buttons | //+------------------------------------------------------------------+ bool isCursorInHeaderOrButtons(int mouse_x, int mouse_y) { int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE); //--- Getting header x-coordinate int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE); //--- Getting header y-coordinate int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE); //--- Getting header width int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE); //--- Getting header height bool in_header = (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height); //--- Checking if in header int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Getting close button x-coordinate int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Getting close button y-coordinate int close_width = 20; //--- Setting close button width int close_height = 20; //--- Setting close button height bool in_close = (mouse_x >= close_x && mouse_x <= close_x + close_width && mouse_y >= close_y && mouse_y <= close_y + close_height); //--- Checking if in close button int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE); //--- Getting export button x-coordinate int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE); //--- Getting export button y-coordinate int export_width = 20; //--- Setting export button width int export_height = 20; //--- Setting export button height bool in_export = (mouse_x >= export_x && mouse_x <= export_x + export_width && mouse_y >= export_y && mouse_y <= export_y + export_height); //--- Checking if in export button int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Getting toggle button x-coordinate int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Getting toggle button y-coordinate int toggle_width = 20; //--- Setting toggle button width int toggle_height = 20; //--- Setting toggle button height bool in_toggle = (mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width && mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height); //--- Checking if in toggle button return in_header || in_close || in_export || in_toggle; //--- Returning combined check }
ここでは、isCursorInHeaderOrButtons関数を実装し、マウスカーソルがインタラクティブ要素上にあるかを検出できるようにします。これにより、ドラッグ操作やボタン操作が可能になります。まず、ObjectGetIntegerを使用して「PREFIX + HEADER_PANEL」の座標とサイズを取得し、OBJPROP_XDISTANCE、OBJPROP_YDISTANCE、OBJPROP_XSIZE、OBJPROP_YSIZEをheader_x、header_y、header_width、header_heightに格納します。その後、カーソル位置(mouse_x, mouse_y)がヘッダーの範囲内にあるかをin_headerで確認します。
同様に、「PREFIX + CLOSE_BUTTON」、「PREFIX + EXPORT_BUTTON」、「PREFIX + TOGGLE_BUTTON」の座標をOBJPROP_XDISTANCEとOBJPROP_YDISTANCEで取得し、close_width、close_height、export_width、export_height、toggle_width、toggle_heightを20に設定します。カーソルが各ボタンの範囲内にあるかをin_close、in_export、in_toggleで確認します。カーソルがヘッダーまたはいずれかのボタン上にある場合は、OR演算子で条件を組み合わせてtrueを返します。ホバー検出後は、視覚的フィードバックのために検出されたヘッダーやボタンの状態を更新する必要があります。これを実現するために、以下のロジックを実装しています。
//+------------------------------------------------------------------+ //| Updating button hover states | //+------------------------------------------------------------------+ void updateButtonHoverStates(int mouse_x, int mouse_y) { int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Getting close button x-coordinate int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Getting close button y-coordinate int close_width = 20; //--- Setting close button width int close_height = 20; //--- Setting close button height bool is_close_hovered = (mouse_x >= close_x && mouse_x <= close_x + close_width && mouse_y >= close_y && mouse_y <= close_y + close_height); //--- Checking if close button hovered if(is_close_hovered != prev_close_hovered) { //--- Checking hover change ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? clrRed : clrBlack); //--- Updating close button color ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? clrDodgerBlue : clrNONE); //--- Updating close button background prev_close_hovered = is_close_hovered; //--- Updating previous hover state ChartRedraw(0); //--- Redrawing chart } int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE); //--- Getting export button x-coordinate int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE); //--- Getting export button y-coordinate int export_width = 20; //--- Setting export button width int export_height = 20; //--- Setting export button height bool is_export_hovered = (mouse_x >= export_x && mouse_x <= export_x + export_width && mouse_y >= export_y && mouse_y <= export_y + export_height); //--- Checking if export button hovered if(is_export_hovered != prev_export_hovered) { //--- Checking hover change ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_COLOR, is_export_hovered ? clrOrange : clrBlack); //--- Updating export button color ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_BGCOLOR, is_export_hovered ? clrDodgerBlue : clrNONE); //--- Updating export button background prev_export_hovered = is_export_hovered; //--- Updating previous hover state ChartRedraw(0); //--- Redrawing chart } int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Getting toggle button x-coordinate int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Getting toggle button y-coordinate int toggle_width = 20; //--- Setting toggle button width int toggle_height = 20; //--- Setting toggle button height bool is_toggle_hovered = (mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width && mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height); //--- Checking if toggle button hovered if(is_toggle_hovered != prev_toggle_hovered) { //--- Checking hover change ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? clrBlue : clrBlack); //--- Updating toggle button color ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? clrDodgerBlue : clrNONE); //--- Updating toggle button background prev_toggle_hovered = is_toggle_hovered; //--- Updating previous hover state ChartRedraw(0); //--- Redrawing chart } } //+------------------------------------------------------------------+ //| Updating header hover state | //+------------------------------------------------------------------+ void updateHeaderHoverState(int mouse_x, int mouse_y) { int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE); //--- Getting header x-coordinate int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE); //--- Getting header y-coordinate int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE); //--- Getting header width int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE); //--- Getting header height int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Getting close button x-coordinate int close_y = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_YDISTANCE); //--- Getting close button y-coordinate int close_width = 20; //--- Setting close button width int close_height = 20; //--- Setting close button height int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE); //--- Getting export button x-coordinate int export_y = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_YDISTANCE); //--- Getting export button y-coordinate int export_width = 20; //--- Setting export button width int export_height = 20; //--- Setting export button height int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE); //--- Getting toggle button x-coordinate int toggle_y = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_YDISTANCE); //--- Getting toggle button y-coordinate int toggle_width = 20; //--- Setting toggle button width int toggle_height = 20; //--- Setting toggle button height bool is_header_hovered = (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height && !(mouse_x >= close_x && mouse_x <= close_x + close_width && mouse_y >= close_y && mouse_y <= close_y + close_height) && !(mouse_x >= export_x && mouse_x <= export_x + export_width && mouse_y >= export_y && mouse_y <= export_y + export_height) && !(mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width && mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height)); //--- Checking if header hovered if(is_header_hovered != prev_header_hovered && !panel_dragging) { //--- Checking hover change ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : settings.section_bg_color); //--- Updating header background prev_header_hovered = is_header_hovered; //--- Updating previous hover state ChartRedraw(0); //--- Redrawing chart } updateButtonHoverStates(mouse_x, mouse_y); //--- Updating button hover states }
最後に、updateButtonHoverStatesおよびupdateHeaderHoverState関数を実装し、ユーザー操作に対する視覚的フィードバックを追加します。これにより、ボタンやヘッダーの反応性が向上します。updateButtonHoverStatesでは、「PREFIX + CLOSE_BUTTON」、「PREFIX + EXPORT_BUTTON」、「PREFIX + TOGGLE_BUTTON」の座標をObjectGetIntegerでOBJPROP_XDISTANCEおよびOBJPROP_YDISTANCEを取得し、close_width、close_height、export_width、export_height、toggle_width、toggle_heightを20に設定して、ボタンのホバー状態をチェックします。
閉じるボタンでは、mouse_xとmouse_yが範囲内にある場合にis_close_hoveredを設定し、prev_close_hoveredと異なる場合はObjectSetIntegerでOBJPROP_COLORをclrRedまたはclrBlack、OBJPROP_BGCOLORをclrDodgerBlueまたはclrNONEに更新し、prev_close_hoveredを更新してChartRedrawを呼び出します。エクスポートボタンでは、clrOrangeまたはclrBlack、clrDodgerBlueまたはclrNONEを設定してprev_export_hoveredを更新し、再描画します。トグルボタンではclrBlueまたはclrBlackを設定し、prev_toggle_hoveredを更新して再描画します。
updateHeaderHoverStateでは、「PREFIX + HEADER_PANEL」のheader_x、header_y、header_width、header_heightおよびボタン座標を取得し、カーソルがヘッダー内かつボタン範囲外にある場合にis_header_hoveredを設定します。is_header_hoveredがprev_header_hoveredと異なり、panel_draggingがfalseの場合は、ObjectSetIntegerで「PREFIX + HEADER_PANEL」のOBJPROP_BGCOLORをclrRedまたはsettings.section_bg_colorに更新し、prev_header_hoveredを更新してChartRedrawを呼び出し、さらにupdateButtonHoverStatesを実行します。これらの関数により、直感的なユーザー操作のための動的なホバーエフェクトが提供されます。これらの機能を活用するために、OnChartEvent関数を拡張して視覚的フィードバックのロジックを組み込みます。
//+------------------------------------------------------------------+ //| Handling chart events for sorting, export, and UI interactions | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK) { //--- Handling object click if(sparam == PREFIX + CLOSE_BUTTON) { //--- Checking close button click Print("Closing the dashboard"); //--- Logging closing PlaySound("alert.wav"); //--- Playing alert sound panel_is_visible = false; //--- Setting panel invisible deleteAllObjects(); //--- Deleting all objects ChartRedraw(0); //--- Redrawing chart } else if(sparam == PREFIX + EXPORT_BUTTON) { //--- Checking export button click Print("Exporting dashboard to CSV"); //--- Logging exporting ExportToCSV(); //--- Exporting to CSV ChartRedraw(0); //--- Redrawing chart } else if(sparam == PREFIX + TOGGLE_BUTTON) { //--- Checking toggle button click deleteAllObjects(); //--- Deleting all objects panel_minimized = !panel_minimized; //--- Toggling minimized state if(panel_minimized) { //--- Checking if minimized Print("Minimizing the dashboard"); //--- Logging minimizing createMinimizedDashboard(); //--- Creating minimized dashboard } else { Print("Maximizing the dashboard"); //--- Logging maximizing createFullDashboard(); //--- Creating full dashboard // Resetting string variables to force update total_buys_str = ""; //--- Resetting buys string total_sells_str = ""; //--- Resetting sells string total_trades_str = ""; //--- Resetting trades string total_lots_str = ""; //--- Resetting lots string total_profit_str = ""; //--- Resetting profit string total_pending_str = ""; //--- Resetting pending string total_swap_str = ""; //--- Resetting swap string total_comm_str = ""; //--- Resetting commission string acc_bal_str = ""; //--- Resetting balance string acc_eq_str = ""; //--- Resetting equity string acc_free_str = ""; //--- Resetting free margin string UpdateDashboard(); //--- Updating dashboard } prev_header_hovered = false; //--- Resetting header hover prev_close_hovered = false; //--- Resetting close hover prev_export_hovered = false; //--- Resetting export hover prev_toggle_hovered = false; //--- Resetting toggle hover ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, settings.section_bg_color); //--- Resetting header background ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting close button color ObjectSetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting close button background ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting export button color ObjectSetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting export button background ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_COLOR, clrBlack); //--- Resetting toggle button color ObjectSetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE); //--- Resetting toggle button background ChartRedraw(0); //--- Redrawing chart } else { for(int i = 0; i < ArraySize(headers); i++) { //--- Iterating through headers if(sparam == PREFIX + HEADER + IntegerToString(i)) { //--- Checking header click if(sort_column == i) //--- Checking if same column sort_ascending = !sort_ascending; //--- Toggling sort direction else { sort_column = i; //--- Setting new sort column sort_ascending = true; //--- Setting to ascending } UpdateDashboard(); //--- Updating dashboard break; //--- Exiting loop } } } } else if(id == CHARTEVENT_KEYDOWN && lparam == 'E') { //--- Handling 'E' key press ExportToCSV(); //--- Exporting to CSV } else if(id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handling mouse move int mouse_x = (int)lparam; //--- Getting mouse x-coordinate int mouse_y = (int)dparam; //--- Getting mouse y-coordinate int mouse_state = (int)sparam; //--- Getting mouse state if(mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) { //--- Checking if mouse moved return; //--- Exiting if no movement } last_mouse_x = mouse_x; //--- Updating last mouse x last_mouse_y = mouse_y; //--- Updating last mouse y updateHeaderHoverState(mouse_x, mouse_y); //--- Updating header hover state int header_x = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XDISTANCE); //--- Getting header x-coordinate int header_y = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YDISTANCE); //--- Getting header y-coordinate int header_width = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_XSIZE); //--- Getting header width int header_height = (int)ObjectGetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_YSIZE);//--- Getting header height int close_x = (int)ObjectGetInteger(0, PREFIX + CLOSE_BUTTON, OBJPROP_XDISTANCE); //--- Getting close button x-coordinate int close_width = 20; //--- Setting close button width int export_x = (int)ObjectGetInteger(0, PREFIX + EXPORT_BUTTON, OBJPROP_XDISTANCE);//--- Getting export button x-coordinate int export_width = 20; //--- Setting export button width int toggle_x = (int)ObjectGetInteger(0, PREFIX + TOGGLE_BUTTON, OBJPROP_XDISTANCE);//--- Getting toggle button x-coordinate int toggle_width = 20; //--- Setting toggle button width if(prev_mouse_state == 0 && mouse_state == 1) { //--- Checking mouse click start if(mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height && !(mouse_x >= close_x && mouse_x <= close_x + close_width) && !(mouse_x >= export_x && mouse_x <= export_x + export_width) && !(mouse_x >= toggle_x && mouse_x <= toggle_x + toggle_width)) { //--- Checking if in draggable area panel_dragging = true; //--- Starting dragging panel_drag_x = mouse_x; //--- Setting drag start x panel_drag_y = mouse_y; //--- Setting drag start y panel_start_x = header_x; //--- Setting panel start x panel_start_y = header_y; //--- Setting panel start y ObjectSetInteger(0, PREFIX + HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Setting dragging color ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disabling chart scroll } } if(panel_dragging && mouse_state == 1) { //--- Handling dragging int dx = mouse_x - panel_drag_x; //--- Calculating x change int dy = mouse_y - panel_drag_y; //--- Calculating y change settings.panel_x = panel_start_x + dx; //--- Updating panel x settings.panel_y = panel_start_y + dy; //--- Updating panel y int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Getting chart width int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Getting chart height int num_columns = ArraySize(headers); //--- Getting number of columns int column_width_sum = 0; //--- Initializing column width sum for(int i = 0; i < num_columns; i++) column_width_sum += column_widths[i]; //--- Adding column width int panel_width = MathMax(settings.header_x_distances[num_columns - 1] + column_widths[num_columns - 1], column_width_sum) + 20 + settings.label_x_offset; //--- Calculating panel width int panel_height = panel_minimized ? settings.row_height : (ArraySize(symbol_data) + 3) * settings.row_height; //--- Calculating panel height settings.panel_x = MathMax(0, MathMin(chart_width - panel_width, settings.panel_x)); //--- Constraining x settings.panel_y = MathMax(0, MathMin(chart_height - panel_height, settings.panel_y)); //--- Constraining y updatePanelPositions(); //--- Updating object positions ChartRedraw(0); //--- Redrawing chart } if(mouse_state == 0 && prev_mouse_state == 1) { //--- Handling mouse release if(panel_dragging) { //--- Checking if was dragging panel_dragging = false; //--- Stopping dragging updateHeaderHoverState(mouse_x, mouse_y); //--- Updating hover state ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enabling chart scroll ChartRedraw(0); //--- Redrawing chart } } prev_mouse_state = mouse_state; //--- Updating previous mouse state } }
ここでは、OnChartEvent関数を拡張してインタラクティブなイベントを処理できるようにします。これにより、クローズ、エクスポート、トグル、ソートのクリックや、ドラッグのためのマウス移動を管理します。CHARTEVENT_OBJECT_CLICKの場合、sparamが「PREFIX + CLOSE_BUTTON」であれば、Printでログを出力し、PlaySoundでalert.wavを再生し、panel_is_visibleをfalseに設定してdeleteAllObjectsを呼び出し、ChartRedrawで再描画します。sparamが「PREFIX + EXPORT_BUTTON」の場合は、ログ出力後にExportToCSVを呼び出します。
「PREFIX + TOGGLE_BUTTON」の場合は、オブジェクトを削除し、panel_minimizedを切り替え、Printで「Minimizing」または「Maximizing」を出力し、createMinimizedDashboardまたはcreateFullDashboardを呼び出します。さらに、total_buys_strやacc_bal_strなどの文字列変数をリセットし、UpdateDashboardを呼び出します。ホバー状態(prev_header_hovered、prev_close_hoveredなど)をリセットし、「PREFIX + HEADER_PANEL」、「PREFIX + CLOSE_BUTTON」、「PREFIX + EXPORT_BUTTON」、「PREFIX + TOGGLE_BUTTON」の色もObjectSetIntegerでリセットします。これにより、次のような動作を実現できます。

ヘッダーのクリックについては、headersをループし、sort_columnが一致する場合はsort_ascendingを切り替え、そうでなければ新しいsort_columnを設定してsort_ascendingをtrueにし、UpdateDashboardを呼び出します。CHARTEVENT_KEYDOWNで「E」が押された場合はExportToCSVを呼び出します。CHARTEVENT_MOUSE_MOVEでは、panel_is_visibleがtrueの場合、mouse_x、mouse_y、mouse_stateを取得し、状態が変わっておらずドラッグもおこなわれていなければ処理を終了します。last_mouse_xとlast_mouse_yを更新し、updateHeaderHoverStateを呼び出します。
prev_mouse_stateが0でmouse_stateが1の場合は、ボタンを除くドラッグ可能領域のクリックをチェックし、panel_draggingをtrueに設定して座標を保存し、ヘッダーの色をclrMediumBlueに変更、ChartSetIntegerでスクロールを無効化します。ドラッグ中かつmouse_stateが1の場合はdxとdyを計算し、settings.panel_xとsettings.panel_yをチャート範囲内で更新し、updatePanelPositionsを呼び出して再描画します。マウスを離した際にはドラッグを終了し、ホバー状態を更新、スクロールを再有効化して再描画します。これにより、ユーザーフレンドリーなダッシュボードの動的なUI操作が可能になります。コンパイル後には、ボタンのホバー状態が確認できます。

最大化状態およびドラッグ状態では、次のような結果となります。

画像からも、ホバー、ドラッグ、最小化のロジックに対応したダッシュボードコンポーネントが追加され、目的どおりに動作していることが確認できます。残されているのはプロジェクトの実用性をテストすることであり、それは次のセクションで扱います。
バックテスト
テストを実施しました。以下はコンパイル後の可視化を単一のGraphics Interchange Format (GIF)ビットマップ画像形式で示したものです。

結論
結論として、第8回ではMQL5の情報ダッシュボードを拡張し、ドラッグ&最小化機能、CLOSE_BUTTONやTOGGLE_BUTTONなどのインタラクティブボタン、さらにホバーエフェクトを追加しました。これにより、複数銘柄のポジションや口座の監視機能を維持しつつ、ユーザー体験が向上しています。アーキテクチャと実装手順も詳細に説明し、createFullDashboardやupdatePanelPositions、OnChartEventなどの関数を用いて、柔軟で視覚的に反応するツールを実現しました。リアルタイム更新やExcel CSVへのエクスポート機能も備えており、取引ワークフローの最適化やポジション分析の直感的かつ効率的な運用が可能です。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19059
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
取引システムの構築(第2回):ポジションサイズ管理の科学
共和分株式による統計的裁定取引(第2回):エキスパートアドバイザー、バックテスト、最適化
ダイナミックマルチペアEAの形成(第4回):ボラティリティとリスク調整
ログレコードをマスターする(第10回):抑制機能を実装してログの再表示を防ぐ
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索