对象优先级(Z 轴顺序)

图表上的对象不仅提供信息展示,还能通过事件与用户及 MQL 程序进行交互,我们将在 下一章对此进行详细探讨。其中一个事件源是鼠标指针。具体来说,图表能够追踪鼠标移动和按键操作。

如果某个对象位于鼠标下方,则可以针对该对象执行特定的事件处理。然而,对象可能会相互重叠(当它们的坐标在考虑 尺寸的情况下重叠时)。在这种情况下,整数特性 OBJPROP_ZORDER 开始发挥作用。该特性用于设置图形对象接收鼠标事件的优先级。当多个对象重叠时,只有优先级最高的对象才能接收到事件。

默认情况下,创建对象时,其 Z 轴顺序为零,但可根据需要增大该值。

需特别注意,Z 轴顺序仅影响鼠标事件的处理顺序,而不影响对象的绘制顺序。对象始终按照其添加到图表的先后顺序进行绘制。这一点可能会导致误解。例如,当一个对象在视觉上位于另一个对象之上时,可能因被重叠对象的 Z 轴优先级更高(见示例)而无法显示其工具提示。

ObjectZorder.mq5脚本中,我们将创建 12 个 OBJ_RECTANGLE_LABEL 类型的对象,并按类似钟面将其排列成圆形。对象的添加顺序对应小时刻度:从 1 到 12。为清晰起见,所有矩形将设置随机颜色(关于 OBJPROP_BGCOLOR 特性,请参阅 下一节)和随机优先级。当用户将鼠标悬停在对象上时,可通过工具提示确定当前所属对象。

为方便设置对象特性,我们定义了从 Object Selector派生的特殊类 ObjectBuilder

#include "ObjectPrefix.mqh"
#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);
   }
   
   // changing the name and chart is prohibited
   virtual void name(const string _idoverride = delete;
   virtual void chart(const long _chartoverride = delete;
};

对象标识符 (id) 和图表标识符 (host) 的字段已存在于ObjectSelector 类中。在派生类中,我们添加了对象类型 (ENUM_OBJECT type) 和窗口编号 (int window)。构造函数会调用 ObjectCreate

特性的设置和读取完全继承自 ObjectSelector中的一组 getset 方法。

与之前的测试脚本一样,我们将确定脚本放置的窗口、窗口尺寸和中心点坐标。

void OnStart()
{
   const int t = ChartWindowOnDropped();
   int h = (int)ChartGetInteger(0CHART_HEIGHT_IN_PIXELSt);
   int w = (int)ChartGetInteger(0CHART_WIDTH_IN_PIXELS);
   int x = w / 2;
   int y = h / 2;
   ...

由于 OBJ_RECTANGLE_LABEL 对象类型支持显式像素尺寸,因此我们将每个矩形的宽度 dx和高度dy 计算为窗口的四分之一。我们使用这些值来设置 OBJPROP_XSIZE 和 OBJPROP_YSIZE 特性(相关说明请参阅 确定对象宽度和高度章节。

   const int dx = w / 4;
   const int dy = h / 4;
   ...

接下来,在循环中创建 12 个对象。变量 pxpy 包含“表盘”上下一个“标记”相对于中心点 (x, y) 的偏移量。z轴优先级采用随机选择。对象名称及其工具提示 (OBJPROP_TOOLTIP) 包含类似“XX - YYY”的字符串,其中 XX 表示“小时”编号(表盘上的位置 1 至 12),YYY 表示优先级值。

   for(int i = 0i < 12; ++i)
   {
      const int px = (int)(MathSin((i + 1) * 30 * M_PI / 180) * dx) - dx / 2;
      const int py = -(int)(MathCos((i + 1) * 30 * M_PI / 180) * dy) - dy / 2;
      
      const int z = rand();
      const string text = StringFormat("%02d - %d"i + 1z);
   
      ObjectBuilder *builder =
         new ObjectBuilder(ObjNamePrefix + textOBJ_RECTANGLE_LABEL);
      builder.set(OBJPROP_XDISTANCEx + px).set(OBJPROP_YDISTANCEy + py)
      .set(OBJPROP_XSIZEdx).set(OBJPROP_YSIZEdy)
      .set(OBJPROP_TOOLTIPtext)
      .set(OBJPROP_ZORDERz)
      .set(OBJPROP_BGCOLOR, (rand() << 8) | rand());
      delete builder;
   }

在调用 ObjectBuilder构造函数后,针对新的 builder 对象,可以通过链式调用重载的 set 方法设置不同特性(set 方法返回对象自身指针)。

由于图形对象创建和配置完成后不再需要 MQL 对象,因此我们会立即删除 builder

脚本执行后,图表上会出现如下对象:

对象叠加和 Z 轴顺序优先级工具提示

对象叠加和 Z 轴顺序优先级工具提示

每次运行时颜色和优先级都会不同,但矩形的视觉叠加顺序始终一致,按照创建顺序从最底层的 1 到最顶层的 12 排列(此处指的是对象的叠加层级,而非 12 一定位于表盘顶部)。

在图中,鼠标光标位于存在两个对象的位置,即 01(荧光酸橙绿)和 12(沙色)。此时,01 号对象的工具提示可见,尽管视觉上 12 号对象显示在 01 号之上。这是因为 01 号被随机生成了比 12 号更高的优先级。

由于一次只能显示一个工具提示,因此可以将鼠标移至没有对象重叠的其他区域,通过工具提示中的信息来查看优先级关系,此时工具提示中的信息属于光标下方的单个对象。

在下一章学习鼠标事件处理时,我们可以改进此示例,测试 Z 轴顺序对鼠标点击对象的影响。

要删除创建的对象,可使用 ObjectCleanup1.mq5脚本。