MQL5 GUI and Data Visualization Guide
MQL5 GUI and Data Visualization Guide
The graphics architecture in MQL5 is a fundamental departure from the model used in its
predecessor, MQL4. MQL4's design was procedural, direct, and synchronous; a function call
to modify a graphical object would often pause the program's execution until the operation
was complete.1 This approach was a direct reflection of the platform's overall single-threaded
architecture, which could lead to the entire program freezing during time-consuming
calculations or graphical updates.1
In contrast, the MQL5 graphics architecture is a direct consequence of the platform's overall
shift to a non-blocking, event-driven model designed to overcome MQL4's single-threaded
limitations.1 MQL5's core philosophy is centered on asynchronicity to ensure a responsive user
interface and to leverage multi-core processors. If graphical operations remained
synchronous, they would undermine this core objective by blocking the main execution
thread. Consequently, the graphics system was engineered as an object-oriented,
asynchronous system where commands are queued and processed separately from the main
program logic, ensuring that even complex GUI updates do not introduce lag or freeze the
application.1
At the procedural level, MQL5 provides a set of core functions for direct manipulation of
graphical objects on a chart. The primary functions are:
● ObjectCreate(): Creates a new graphical object of a specified type.
● ObjectDelete(): Removes a specified graphical object from the chart.
● ObjectSetInteger(): Sets an integer-type property of an object (e.g., color, width,
Z-order).
● ObjectSetDouble(): Sets a double-type property of an object (e.g., price level).
● ObjectSetString(): Sets a string-type property of an object (e.g., text, font name). 2
A critical concept for developers to understand is that these functions do not modify the
chart visually in real-time.2 Instead, when called, they send a command to an asynchronous
chart event queue.2 The client terminal processes this queue independently, applying the
visual updates in response to other events, such as the arrival of a new tick or the resizing of
the chart window.2 This decoupling prevents the Expert Advisor from being blocked while
waiting for the chart to be rendered.
To ensure that visual changes are applied immediately when required, the ChartRedraw()
function is essential. Calling ChartRedraw() forces the client terminal to process its event
queue and redraw the specified chart, making all pending graphical changes visible without
delay. It is standard practice to call this function after a block of object modifications to
ensure the user interface reflects the program's current state.2
While the procedural functions provide direct control, the modern and recommended
best-practice approach is to use the object-oriented classes provided in the MQL5 Standard
Library.1 Located in the Include\ChartObjects directory, this library contains a comprehensive
set of classes that encapsulate the properties and methods for various graphical objects.8
Examples include:
● CChartObjectButton: For creating and managing button controls.9
● CChartObjectRectangle: For shape objects.11
● CChartObjectLabel: For text labels anchored by pixel coordinates.12
● CChartObjectTrend: For trend lines.12
These classes abstract the underlying procedural calls into a more intuitive, object-oriented
interface. They handle the details of setting properties and managing object state, leading to
cleaner, more maintainable, and less error-prone code. This aligns with the overall design
philosophy of MQL5, which encourages the use of Object-Oriented Programming (OOP) to
build complex, scalable trading systems.1
This is accomplished by using the ChartSetInteger() function, typically within the OnInit()
function, to set the CHART_EVENT_MOUSE_MOVE property to true.
Code snippet
//+------------------------------------------------------------------+
//| EnableMouseEvents_EA.mq5 |
//| Copyright 2023, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Enable mouse move events for the current chart
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
Print("Mouse move events have been enabled.");
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- Disable mouse move events on exit
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
Print("Mouse move events have been disabled.");
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
//--- If a mouse move event is received, print the coordinates
if(id == CHARTEVENT_MOUSE_MOVE)
{
PrintFormat("Mouse Move Event: X=%d, Y=%d", lparam, dparam);
}
}
//+------------------------------------------------------------------+
The OnChartEvent() function receives four parameters: id, lparam, dparam, and sparam. The
id parameter contains the event type, while the other three parameters contain event-specific
data. Understanding how to interpret these parameters is crucial for handling GUI interactions
correctly.
CHARTEVENT_ Not used (0) Not used (0.0) Name of the Notifying that
OBJECT_DRAG dragged a standard
object object drag
operation has
been
completed.
17
This tutorial demonstrates the end-to-end process of creating a functional, interactive GUI
panel, applying the architectural and event-handling concepts previously discussed.
3.1. Part 1: Layout and Creation in OnInit()
All graphical objects for the panel are created once in the OnInit() function. This is the most
efficient approach, as it avoids redundant object creation calls on every tick or chart event.23
The panel will consist of a main background rectangle, two buttons for buy and sell actions,
and two labels to display live prices.
Code snippet
//+------------------------------------------------------------------+
//| TradingPanel_P1.mq5 |
//| Copyright 2023, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
//--- Panel object names
#define PANEL_NAME "TradingPanel"
#define BTN_BUY_NAME "BtnBuy"
#define BTN_SELL_NAME "BtnSell"
#define LBL_BID_NAME "LblBid"
#define LBL_ASK_NAME "LblAsk"
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Create the main panel background (OBJ_RECTANGLE_LABEL)
ObjectCreate(0, PANEL_NAME, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, PANEL_NAME, OBJPROP_XDISTANCE, 20);
ObjectSetInteger(0, PANEL_NAME, OBJPROP_YDISTANCE, 50);
ObjectSetInteger(0, PANEL_NAME, OBJPROP_XSIZE, 150);
ObjectSetInteger(0, PANEL_NAME, OBJPROP_YSIZE, 120);
ObjectSetInteger(0, PANEL_NAME, OBJPROP_BGCOLOR, clrDarkSlateGray);
ObjectSetInteger(0, PANEL_NAME, OBJPROP_BORDER_COLOR, clrGray);
ObjectSetInteger(0, PANEL_NAME, OBJPROP_SELECTABLE, true);
ObjectSetInteger(0, PANEL_NAME, OBJPROP_SELECTED, false);
//--- Create the Buy button (OBJ_BUTTON)
ObjectCreate(0, BTN_BUY_NAME, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, BTN_BUY_NAME, OBJPROP_XDISTANCE, 30);
ObjectSetInteger(0, BTN_BUY_NAME, OBJPROP_YDISTANCE, 70);
ObjectSetInteger(0, BTN_BUY_NAME, OBJPROP_XSIZE, 60);
ObjectSetInteger(0, BTN_BUY_NAME, OBJPROP_YSIZE, 25);
ObjectSetString(0, BTN_BUY_NAME, OBJPROP_TEXT, "BUY");
ObjectSetInteger(0, BTN_BUY_NAME, OBJPROP_BGCOLOR, clrGreen);
ObjectSetInteger(0, BTN_BUY_NAME, OBJPROP_COLOR, clrWhite);
//--- Create the Sell button (OBJ_BUTTON)
ObjectCreate(0, BTN_SELL_NAME, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, BTN_SELL_NAME, OBJPROP_XDISTANCE, 100);
ObjectSetInteger(0, BTN_SELL_NAME, OBJPROP_YDISTANCE, 70);
ObjectSetInteger(0, BTN_SELL_NAME, OBJPROP_XSIZE, 60);
ObjectSetInteger(0, BTN_SELL_NAME, OBJPROP_YSIZE, 25);
ObjectSetString(0, BTN_SELL_NAME, OBJPROP_TEXT, "SELL");
ObjectSetInteger(0, BTN_SELL_NAME, OBJPROP_BGCOLOR, clrRed);
ObjectSetInteger(0, BTN_SELL_NAME, OBJPROP_COLOR, clrWhite);
//--- Create the Bid price label (OBJ_LABEL)
ObjectCreate(0, LBL_BID_NAME, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, LBL_BID_NAME, OBJPROP_XDISTANCE, 30);
ObjectSetInteger(0, LBL_BID_NAME, OBJPROP_YDISTANCE, 110);
ObjectSetString(0, LBL_BID_NAME, OBJPROP_TEXT, "Bid: -");
ObjectSetInteger(0, LBL_BID_NAME, OBJPROP_COLOR, clrWhite);
//--- Create the Ask price label (OBJ_LABEL)
ObjectCreate(0, LBL_ASK_NAME, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, LBL_ASK_NAME, OBJPROP_XDISTANCE, 30);
ObjectSetInteger(0, LBL_ASK_NAME, OBJPROP_YDISTANCE, 130);
ObjectSetString(0, LBL_ASK_NAME, OBJPROP_TEXT, "Ask: -");
ObjectSetInteger(0, LBL_ASK_NAME, OBJPROP_COLOR, clrWhite);
//--- Enable mouse move events for interactivity
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
ChartRedraw();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- Clean up all created objects
ObjectsDeleteAll(0, PANEL_NAME);
ObjectsDeleteAll(0, BTN_BUY_NAME);
ObjectsDeleteAll(0, BTN_SELL_NAME);
ObjectsDeleteAll(0, LBL_BID_NAME);
ObjectsDeleteAll(0, LBL_ASK_NAME);
ChartRedraw();
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
//--- Event handling logic will be added here
}
//+------------------------------------------------------------------+
To make the panel movable, state variables are used to track the dragging process. The logic
is handled within OnChartEvent() and follows these steps:
1. Detect Click: When a CHARTEVENT_OBJECT_CLICK event occurs on the main panel
background (PANEL_NAME), the dragging state is initiated. The initial mouse coordinates
and the panel's current position are stored.
2. Track Mouse Move: While in the dragging state, every CHARTEVENT_MOUSE_MOVE
event is processed. The change (delta) in mouse position from the initial click point is
calculated.
3. Update Positions: The calculated delta is applied to the initial positions of all panel
elements (OBJPROP_XDISTANCE, OBJPROP_YDISTANCE), moving them together as a
single unit.
4. Detect Release: The dragging state is terminated when a CHARTEVENT_MOUSE_MOVE
event is received where the left mouse button is no longer pressed. This is checked by
inspecting the sparam bitmask.
Code snippet
A hover effect provides important visual feedback to the user. This is achieved by tracking the
mouse cursor's position during CHARTEVENT_MOUSE_MOVE events and changing the
button's background color when the cursor enters or leaves its area. State variables
(isHoverBuy, isHoverSell) are crucial to prevent flooding the event queue with redundant color
change commands on every mouse movement.25
Code snippet
// --- Add these global variables for hover state management ---
bool isHoverBuy = false;
bool isHoverSell = false;
// --- Add this code inside the OnChartEvent() function, after the dragging logic ---
// --- Handle hover effects for buttons
if(id == CHARTEVENT_MOUSE_MOVE &&!isPanelDragging)
{
int x = (int)lparam;
int y = (int)dparam;
//--- Check Buy Button
int buyX = (int)ObjectGetInteger(0, BTN_BUY_NAME, OBJPROP_XDISTANCE);
int buyY = (int)ObjectGetInteger(0, BTN_BUY_NAME, OBJPROP_YDISTANCE);
int buyXSize = (int)ObjectGetInteger(0, BTN_BUY_NAME, OBJPROP_XSIZE);
int buyYSize = (int)ObjectGetInteger(0, BTN_BUY_NAME, OBJPROP_YSIZE);
if(x >= buyX && x <= buyX + buyXSize && y >= buyY && y <= buyY + buyYSize)
{
if(!isHoverBuy)
{
ObjectSetInteger(0, BTN_BUY_NAME, OBJPROP_BGCOLOR, clrDarkGreen);
ChartRedraw();
isHoverBuy = true;
}
}
else
{
if(isHoverBuy)
{
ObjectSetInteger(0, BTN_BUY_NAME, OBJPROP_BGCOLOR, clrGreen);
ChartRedraw();
isHoverBuy = false;
}
}
//--- Check Sell Button
int sellX = (int)ObjectGetInteger(0, BTN_SELL_NAME, OBJPROP_XDISTANCE);
int sellY = (int)ObjectGetInteger(0, BTN_SELL_NAME, OBJPROP_YDISTANCE);
int sellXSize = (int)ObjectGetInteger(0, BTN_SELL_NAME, OBJPROP_XSIZE);
int sellYSize = (int)ObjectGetInteger(0, BTN_SELL_NAME, OBJPROP_YSIZE);
if(x >= sellX && x <= sellX + sellXSize && y >= sellY && y <= sellY + sellYSize)
{
if(!isHoverSell)
{
ObjectSetInteger(0, BTN_SELL_NAME, OBJPROP_BGCOLOR, clrDarkRed);
ChartRedraw();
isHoverSell = true;
}
}
else
{
if(isHoverSell)
{
ObjectSetInteger(0, BTN_SELL_NAME, OBJPROP_BGCOLOR, clrRed);
ChartRedraw();
isHoverSell = false;
}
}
}
The OnTick() function is the ideal place to update information that changes with every new
price quote. Here, it is used to fetch the current bid and ask prices using SymbolInfoDouble()
and update the text of the corresponding labels on the panel.
Code snippet
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Get current bid and ask prices
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
//--- Update the text of the labels
string bidText = "Bid: " + DoubleToString(bid, _Digits);
string askText = "Ask: " + DoubleToString(ask, _Digits);
ObjectSetString(0, LBL_BID_NAME, OBJPROP_TEXT, bidText);
ObjectSetString(0, LBL_ASK_NAME, OBJPROP_TEXT, askText);
// No ChartRedraw() is needed here, as the terminal redraws on new ticks.
}
Beyond standard GUI controls, MQL5 offers powerful features for visualizing complex data
sets directly on the chart.
The OBJ_CHART graphical object allows a developer to embed a fully functional chart of a
different symbol and timeframe directly within the main chart window. This is useful for
creating multi-timeframe analysis tools or dashboards that display correlated instruments.26
Code snippet
//+------------------------------------------------------------------+
//| ObjChart_Demo.mq5 |
//| Copyright 2023, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property script_show_inputs
input string InpSymbol = "EURUSD"; // Symbol for the nested chart
input ENUM_TIMEFRAMES InpTf = PERIOD_H1; // Timeframe for the nested chart
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
string obj_name = "NestedChart";
//--- Create the OBJ_CHART object
ObjectCreate(0, obj_name, OBJ_CHART, 0, 0, 0);
//--- Set its position and size (in pixels)
ObjectSetInteger(0, obj_name, OBJPROP_XDISTANCE, 50);
ObjectSetInteger(0, obj_name, OBJPROP_YDISTANCE, 50);
ObjectSetInteger(0, obj_name, OBJPROP_XSIZE, 400);
ObjectSetInteger(0, obj_name, OBJPROP_YSIZE, 250);
//--- Set the symbol and timeframe for the nested chart
ObjectSetString(0, obj_name, OBJPROP_SYMBOL, InpSymbol);
ObjectSetInteger(0, obj_name, OBJPROP_PERIOD, InpTf);
//--- Set other visual properties
ObjectSetInteger(0, obj_name, OBJPROP_CHART_SCALE, 3);
ObjectSetInteger(0, obj_name, OBJPROP_DATE_SCALE, true);
ObjectSetInteger(0, obj_name, OBJPROP_PRICE_SCALE, true);
ChartRedraw();
}
//+------------------------------------------------------------------+
A far more performant and powerful technique is to delegate the rendering to the terminal's
native graphics engine. This is achieved by encapsulating the visualization logic within a
custom indicator that uses one of MQL5's advanced built-in drawing styles, such as
DRAW_CANDLES, DRAW_BARS, or DRAW_FILLING.30 The Expert Advisor can then simply call
this indicator using the iCustom() function. This single call instructs the terminal to handle all
the complex drawing internally using its highly optimized code, which is orders of magnitude
faster than managing hundreds of MQL objects from an EA.31
When graphical objects overlap, MQL5 provides a mechanism to control which object receives
mouse click events. This is managed by the OBJPROP_ZORDER property. OBJPROP_ZORDER
is an integer value that determines the priority for receiving mouse events like
CHARTEVENT_OBJECT_CLICK. When multiple objects are stacked in the same location, the
click event is delivered only to the object with the highest OBJPROP_ZORDER value.35
It is critical to note that OBJPROP_ZORDER controls only the event priority and does not
affect the visual drawing order of the objects.35 Objects are always drawn on the chart in
the order they were created. This can lead to situations where an object is visually obscured
by another, yet it is the one that receives the mouse click because it has a higher Z-order.
The MetaTrader 5 platform is under continuous development, with new features and compiler
behaviors introduced in periodic builds. Staying current with these changes is essential for
long-term code maintenance.
Recent platform builds have introduced GUI-related enhancements that developers can
leverage. For example, build 5200 (released August 2025) added the ability for the terminal to
automatically switch between its light and dark themes based on the host operating system's
settings. A modern, professionally designed GUI should detect and adapt to this setting to
provide a consistent user experience.40
5.1. Clarification: The 'Deprecated Behavior' Compiler Warning
A common warning that appears during compilation is: deprecated behavior, hidden method
calling will be disabled in a future MQL compiler version. This warning is frequently
misinterpreted and is not related to the deprecation of core graphical functions like
ObjectCreate or ObjectSetInteger.41
Code snippet
class CBase
{
public:
void PrintMessage(string msg) { Print("Base: ", msg); }
void PrintMessage(int val) { Print("Base: ", val); }
};
class CDerived : public CBase
{
public:
// This method hides BOTH PrintMessage methods in CBase
void PrintMessage(string msg) { Print("Derived: ", msg); }
};
void OnStart()
{
CDerived derived;
derived.PrintMessage("Hello"); // Calls CDerived::PrintMessage
derived.PrintMessage(123); // COMPILER WARNING HERE
}
Corrected Version:
To resolve the warning, the developer can either rename the method in the derived class or
explicitly bring the hidden base class methods into the derived class's scope with the using
keyword.
Code snippet
class CBase
{
public:
void PrintMessage(string msg) { Print("Base: ", msg); }
void PrintMessage(int val) { Print("Base: ", val); }
};
class CDerived : public CBase
{
public:
using CBase::PrintMessage; // Makes base methods visible
// This now correctly OVERLOADS, not hides
void PrintMessage(string msg) { Print("Derived: ", msg); }
};
void OnStart()
{
CDerived derived;
derived.PrintMessage("Hello"); // Calls CDerived::PrintMessage
derived.PrintMessage(123); // No warning, correctly calls CBase::PrintMessage
}
6. Developer Ecosystem & Best Practices
The MQL5 Code Base and Market offer numerous third-party libraries designed to simplify
and accelerate GUI development. While these can be powerful tools, their use involves a
trade-off analysis.
● Advantages:
○ Faster Development Time: Libraries often provide complex, pre-built controls (e.g.,
tabs, data grids, advanced charts) that would take significant time to develop from
scratch.46
○ Access to Complex Controls: They can offer sophisticated UI elements that are not
available through standard MQL5 graphical objects.
● Disadvantages:
○ External Dependency: The project becomes dependent on an external codebase
that the developer does not control.
○ Potential for Bugs and Performance Overhead: A poorly written library can
introduce bugs, memory leaks, or performance bottlenecks into the application,
causing terminal lag.46
○ Lack of Customization: The library may not offer the specific look, feel, or
functionality required, limiting creative control.
○ Abandonment Risk: The library's author may cease development, leaving the
project dependent on outdated or unsupported code.47
Works cited