MQL5 ObjectCreate & OnChartEvent Guide: UI Buttons & Event Handling
Learn how to create chart objects in MQL5 using ObjectCreate and ObjectFind, handle click events with OnChartEvent, and understand why scripts cannot process chart events.
What this MQL5 guide covers
ObjectCreate creates visual and interactive elements including OBJ_BUTTON, OBJ_LABEL, and OBJ_RECTANGLE_LABEL on MetaTrader 5 charts.
ObjectCreate queues a chart command; true does not prove the object was created. ObjectFind can verify existence, but is synchronous and can add latency.
The OnChartEvent handler captures user interactions such as CHARTEVENT_OBJECT_CLICK and CHARTEVENT_KEYDOWN in Expert Advisors and indicators.
OnChartEvent does not function in MQL5 scripts because scripts run synchronously inside OnStart and exit without an event message pump.
ObjectCreate Syntax, Object Types and ObjectFind
The ObjectCreate function queues a request to create a graphical object on a specified chart window. Parameters define the chart identifier, unique object name, object type enum, target subwindow, and anchor time/price coordinates.
Calling ObjectFind(chart_id, name) checks whether an object already exists. If the object exists, ObjectFind returns the subwindow number (0 for main chart); if not found, it returns a negative value. Use a unique ownership prefix and decide whether to reuse or update an existing object. Creating with an existing name attempts to change its coordinates; it is not necessarily a duplicate-name error.
| Object Type | Coordinate Mode | Common Trading Use Case |
|---|---|---|
| OBJ_BUTTON | Pixels (X, Y from corner) | One-click trading panels, buy/sell buttons |
| OBJ_LABEL | Pixels (X, Y from corner) | Static text overlays, account info readouts |
| OBJ_RECTANGLE_LABEL | Pixels (X, Y, width, height) | Background container panels for custom GUIs |
| OBJ_HLINE | Price (single price level) | Support, resistance, breakeven thresholds |
| OBJ_TREND | Time & Price (two points) | Trend lines, channel boundaries, breakout alerts |
Handling User Interaction with OnChartEvent
Expert Advisors and custom indicators process chart events through the OnChartEvent callback. When a trader clicks a chart button, MetaTrader 5 triggers this function with event ID CHARTEVENT_OBJECT_CLICK.
The sparam argument contains the string name of the clicked object. Match sparam against your known button names to dispatch the intended application action, then reset button click states with ObjectSetInteger.
| Event Constant | Trigger Description | Key Parameters |
|---|---|---|
| CHARTEVENT_OBJECT_CLICK | Mouse click on a chart graphical object | sparam = object name |
| CHARTEVENT_CLICK | Mouse click on an empty chart area | lparam = X pixel, dparam = Y pixel; use ChartXYToTimePrice for time/price |
| CHARTEVENT_KEYDOWN | Keyboard key pressed while chart has focus | lparam = key code |
| CHARTEVENT_CHART_CHANGE | Chart scale, size, or timeframe altered | No documented value payload for this event |
Why OnChartEvent Does Not Work for Scripts
A common source of confusion in MQL5 is placing an OnChartEvent handler inside a script program and finding that clicks are completely ignored.
Scripts operate on a synchronous execution model. When attached, a script runs its OnStart() function from start to finish on its own thread, then terminates and unloads. OnChartEvent is supported for EAs and indicators, not scripts; keeping OnStart alive does not turn a script into a chart-event handler.
- Scripts execute sequentially and terminate; they cannot listen for clicks.
- If your trading tool requires interactive buttons, write it as an Expert Advisor.
- If visual indicators must process clicks, place OnChartEvent inside an indicator.
Redraws and Deinitialization Cleanup
After creating, moving, or changing properties on chart objects, call ChartRedraw(chart_id) to force MetaTrader 5 to update the visual canvas immediately rather than waiting for the next incoming price tick.
In OnDeinit(), iterate through your program objects using ObjectDelete() or call ObjectsDeleteAll(chart_id, prefix) to leave the user chart clean when removing the EA.
Implementation details & mechanics
Core implementation pattern
Create uniquely named buttons in OnInit, verify that their objects exist, and inspect CHARTEVENT_OBJECT_CLICK plus sparam in OnChartEvent. Reset OBJPROP_STATE after handling the click. This example logs clicks by default; order submission is disabled unless AllowDemoOrders is enabled on a demo account. UI event handling is separate from validating trading requests.
Error handling & safety rules
The close button is restricted to the current symbol and the example magic number. This still requires care on netting accounts, where positions aggregate by symbol. OnDeinit removes objects with this example prefix; use a prefix unique to the instance and avoid sharing it with other chart tools.
MQL5 Code Implementation
//+------------------------------------------------------------------+
//| ChartObjectsEventsGuideEA.mq5|
//| Copyright 2026, Pineify Guides Series |
//| https://pineify.app |
//+------------------------------------------------------------------+
#property copyright "Pineify Guides Series"
#property link "https://pineify.app/mql5/chart-objects-events-guide"
#property version "1.00"
#property strict
#include <Trade/Trade.mqh>
#define PREFIX "PineifyUI_"
CTrade g_trade;
input bool AllowDemoOrders=false; // UI demo logs clicks unless explicitly enabled.
const ulong DemoMagic=998877;
//+------------------------------------------------------------------+
//| Helper: Create a chart button with pixel positioning |
//+------------------------------------------------------------------+
bool CreateButton(const string name, const string text,
const int x, const int y,
const int width, const int height,
const color bgCol, const color textCol)
{
// Check if object already exists to avoid redundant creation
if(ObjectFind(0, name) >= 0)
return true;
if(!ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0))
{
PrintFormat("Failed to create button %s! Error: %d", name, GetLastError());
return false;
}
// Anchor to top-left corner using pixel coordinates
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
// Styling
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetString(0, name, OBJPROP_FONT, "Segoe UI");
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 10);
ObjectSetInteger(0, name, OBJPROP_COLOR, textCol);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgCol);
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrDarkGray);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_STATE, false);
return ObjectFind(0,name)>=0;
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
g_trade.SetExpertMagicNumber(DemoMagic);
g_trade.SetTypeFillingBySymbol(_Symbol);
// Create UI buttons
if(!CreateButton(PREFIX + "BuyBtn", "DEMO BUY", 20, 40, 110, 36, clrDodgerBlue, clrWhite)) return INIT_FAILED;
if(!CreateButton(PREFIX + "CloseBtn", "CLOSE OWN", 140, 40, 110, 36, clrCrimson, clrWhite)) return INIT_FAILED;
// Refresh chart to display objects immediately
ChartRedraw(0);
Print("Chart objects UI created successfully.");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Delete all graphical objects matching program prefix
int deleted = ObjectsDeleteAll(0, PREFIX);
ChartRedraw(0);
PrintFormat("Removed %d UI chart objects. Reason: %d", deleted, reason);
}
//+------------------------------------------------------------------+
//| Chart event handler |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
// Filter for object click events
if(id == CHARTEVENT_OBJECT_CLICK)
{
if(!AllowDemoOrders || AccountInfoInteger(ACCOUNT_TRADE_MODE)!=ACCOUNT_TRADE_MODE_DEMO)
{
Print("UI demonstration click: ",sparam,"; no order sent.");
ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
ChartRedraw(0);
return;
}
// Handle BUY button click
if(sparam == PREFIX + "BuyBtn")
{
Print("User clicked BUY button.");
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(!g_trade.Buy(0.10, _Symbol, ask, 0, 0, "Pineify Button Click") ||
g_trade.ResultRetcode()!=TRADE_RETCODE_DONE)
Print("Buy result: ",g_trade.ResultRetcode()," ",g_trade.ResultRetcodeDescription());
// Reset button pressed state
ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
ChartRedraw(0);
}
// Handle CLOSE ALL button click
else if(sparam == PREFIX + "CloseBtn")
{
Print("User clicked CLOSE ALL button.");
// Close all positions matching this symbol
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket>0 && PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC)==(long)DemoMagic)
if(!g_trade.PositionClose(ticket) || g_trade.ResultRetcode()!=TRADE_RETCODE_DONE)
Print("Close result: ",g_trade.ResultRetcode());
}
// Reset button pressed state
ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
ChartRedraw(0);
}
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Events for this EA are processed sequentially; keep handlers short.
}
Copy this code into MetaEditor, save it in your MQL5 folder (Experts, Scripts, or Include), and compile with F7.
Generate custom MQL5 code with Pineify AI
Describe the trade execution, indicator calculations, risk management, or data processing logic you need. Pineify generates editable MQL5 source code. Review its logic, request a syntax check, and compile and test the final file in MetaEditor.
Frequently Asked Questions
Related MQL5 pages
Technical references
Risk and testing note
These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.