图形对象事件

对于图表上的 图形对象 ,终端会生成多种专门的事件。其中大部分事件适用于任何类型的对象。输入字段中的文本编辑结束事件 CHARTEVENT_OBJECT_ENDEDIT 仅针对 OBJ_EDIT 类型的对象生成。

对象点击事件 (CHARTEVENT_OBJECT_CLICK)、鼠标拖动事件 (CHARTEVENT_OBJECT_DRAG) 和对象特性更改事件 (CHARTEVENT_OBJECT_CHANGE) 始终处于激活状态,而对象创建事件 (CHARTEVENT_OBJECT_CREATE) 和对象删除事件 (CHARTEVENT_OBJECT_DELETE) 则需要通过设置图表的相关特性来显式启用:CHART_EVENT_OBJECT_CREATE 和 CH ART_EVENT_OBJECT_DELETE。

当手动重命名对象时(通过特性对话框),终端会依次生成事件序列:CHARTEVENT_OBJECT_DELETE、CHARTEVENT_OBJECT_CREATE、CHARTEVENT_OBJECT_CHANGE。通过编程方式重命名对象时,不会生成这些事件。

所有对象事件都会在 OnChartEvent函数的 sparam 参数中携带关联对象的名称。

此外,CHARTEVENT_OBJECT_CLICK 事件会传递点击坐标:X 存储在 lparam参数中,Y 存储在dparam 参数中。坐标在整个图表中通用(包括子窗口)。

点击不同类型的对象时,其工作机制会有所不同。对于部分对象(如椭圆),光标必须位于任意锚点上方。对于其他对象(如三角形、矩形、线条),光标可以位于对象周边区域(不仅限于点)。在所有这些情况下,当鼠标光标悬停在对象的交互式区域时,将显示包含对象名称的工具提示。

与屏幕坐标绑定的对象(可用于构建程序图形界面,例如按钮、输入字段和矩形面板),鼠标点击对象内部任意位置时将生成事件。

如果光标所在处有多个对象,则仅对 Z 轴优先级最高的对象生成事件。如果多个对象优先级相同,则事件将被分配给创建时间较晚的对象(这与其视觉显示行为一致,即后创建的对象会覆盖更早创建的对象)。

指标新版本EventAllObjects.mq5可用于检测对象事件。我们将使用已熟悉的多对象 Object Selector类创建并配置该指标,然后在 OnChartEvent 处理程序中捕捉其特征事件。

#include <MQL5Book/ObjectMonitor.mqh>
   
class ObjectBuilderpublic ObjectSelector
{
protected:
   const ENUM_OBJECT type;
   const int window;
public:
   ObjectBuilder(const string _idconst ENUM_OBJECT _type,
      const long _chart = 0const int _win = 0):
      ObjectSelector(_id_chart), type(_type), window(_win)
   {
      ObjectCreate(hostidtypewindow00);
   }
};

首先,在OnInit中创建一个按钮对象和一条垂直线。针对该线条,我们将追踪其移动(拖拽)事件,当用户按下按钮时,我们将创建一个输入字段,并对输入的文本进行校验。

const string ObjNamePrefix = "EventShow-";
const string ButtonName = ObjNamePrefix + "Button";
const string EditBoxName = ObjNamePrefix + "EditBox";
const string VLineName = ObjNamePrefix + "VLine";
   
bool objectCreateobjectDelete;
   
void OnInit()
{
   // remember the original settings to restore in OnDeinit
   objectCreate = ChartGetInteger(0CHART_EVENT_OBJECT_CREATE);
   objectDelete = ChartGetInteger(0CHART_EVENT_OBJECT_DELETE);
   
   // set new properties
   ChartSetInteger(0CHART_EVENT_OBJECT_CREATEtrue);
   ChartSetInteger(0CHART_EVENT_OBJECT_DELETEtrue);
   
   ObjectBuilder button(ButtonNameOBJ_BUTTON);
   button.set(OBJPROP_XDISTANCE100).set(OBJPROP_YDISTANCE100)
   .set(OBJPROP_XSIZE200).set(OBJPROP_TEXT"Click Me");
   
   ObjectBuilder line(VLineNameOBJ_VLINE);
   line.set(OBJPROP_TIMEiTime(NULL00))
   .set(OBJPROP_SELECTABLEtrue).set(OBJPROP_SELECTEDtrue)
   .set(OBJPROP_TEXT"Drag Me").set(OBJPROP_TOOLTIP"Drag Me");
   
   ChartRedraw();
}

在此过程中,不要忘记将图表特性 CHART_EVENT_OBJECT_CREATE 和 CHART_EVENT_OBJECT_DELETE 设置为 true ,以便在对象集合发生更改时获得通知。

OnChartEvent函数中,我们将对必要事件提供额外响应:拖拽操作完成后,我们将在日志中显示线条的新位置,并在输入字段的文本编辑完成后,显示其内容。

void OnChartEvent(const int id,
   const long &lparamconst double &dparamconst string &sparam)
{
   ENUM_CHART_EVENT evt = (ENUM_CHART_EVENT)id;
   PrintFormat("%s %lld %f '%s'"EnumToString(evt), lparamdparamsparam);
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == ButtonName)
   {
      if(ObjectGetInteger(0ButtonNameOBJPROP_STATE))
      {
         ObjectBuilder edit(EditBoxNameOBJ_EDIT);
         edit.set(OBJPROP_XDISTANCE100).set(OBJPROP_YDISTANCE150)
         .set(OBJPROP_BGCOLORclrWhite)
         .set(OBJPROP_XSIZE200).set(OBJPROP_TEXT"Edit Me");
      }
      else
      {
         ObjectDelete(0EditBoxName);
      }
      
      ChartRedraw();
   }
   else if(id == CHARTEVENT_OBJECT_ENDEDIT && sparam == EditBoxName)
   {
      Print(ObjectGetString(0EditBoxNameOBJPROP_TEXT));
   }
   else if(id == CHARTEVENT_OBJECT_DRAG && sparam == VLineName)
   {
      Print(TimeToString((datetime)ObjectGetInteger(0VLineNameOBJPROP_TIME)));
   }
}

请注意,当首次按下按钮时,其状态将从释放转变为按下,我们将针对这一状态变化创建一个输入字段。如果再次点击按钮,其状态会恢复原状,输入字段将从图表中移除。

以下是指标运行时的图表图像。

由 OnChartEvent 事件处理程序控制的对象
由 OnChartEvent 事件处理程序控制的对象

指标启动后,日志中会立即显示以下内容:

CHARTEVENT_OBJECT_CREATE 0 0.000000 'EventShow-Button'
CHARTEVENT_OBJECT_CREATE 0 0.000000 'EventShow-VLine'
CHARTEVENT_CHART_CHANGE 0 0.000000 ''

如果我们用鼠标拖拽线条,将看到类似如下的信息:

CHARTEVENT_OBJECT_DRAG 0 0.000000 'EventShow-VLine'
2022.01.05 10:00

接下来,你可以点击按钮,并在新创建的输入字段中编辑文本(编辑完成后按Enter键或点击输入字段外部)。这将在日志中生成以下条目(坐标和消息文本可能不同,此处输入的文本为 "new message"):

CHARTEVENT_OBJECT_CLICK 181 113.000000 'EventShow-Button'
CHARTEVENT_CLICK 181 113.000000 ''
CHARTEVENT_OBJECT_CREATE 0 0.000000 'EventShow-EditBox'
CHARTEVENT_OBJECT_CLICK 152 160.000000 'EventShow-EditBox'
CHARTEVENT_CLICK 152 160.000000 ''
CHARTEVENT_OBJECT_ENDEDIT 0 0.000000 'EventShow-EditBox'
new message

如果随后松开按钮,输入字段将被删除。

CHARTEVENT_OBJECT_CLICK 162 109.000000 'EventShow-Button'
CHARTEVENT_CLICK 162 109.000000 ''
CHARTEVENT_OBJECT_DELETE 0 0.000000 'EventShow-EditBox'

值得注意的是,该按钮默认为双位开关,即每次鼠标点击会使其在按下和释放状态之间交替切换。对于普通按钮,这种行为是多余的:如果仅需追踪按钮的按下操作,应在事件处理过程中通过调用 ObjectSetInteger(0, ButtonName, OBJPROP_STATE, false)将其恢复为释放状态。