对象显示设置:颜色、样式和边框
可通过各种特性更改对象的外观,本节我们将从颜色、样式、线宽和边框开始探讨这些特性。字体、倾斜度、文本对齐等其他格式特性将在后续章节详细说明。
下表所有特性类型均与整数兼容,因此可通过 ObjectGetInteger和 ObjectSetInteger 函数进行管理。
标识符 |
说明 |
特性类型 |
---|---|---|
OBJPROP_COLOR |
对象线条和主要元素的颜色(例如字体或填充色) |
color |
OBJPROP_STYLE |
线条样式 |
ENUM_LINE_STYLE |
OBJPROP_WIDTH |
线条粗细(像素) |
int |
OBJPROP_FILL |
用颜色填充对象(适用于 OBJ_RECTANGLE、OBJ_TRIANGLE、OBJ_ELLIPSE、OBJ_CHANNEL、OBJ_STDDEVCHANNEL 和 OBJ_REGRESSION) |
bool |
OBJPROP_BACK |
对象置于背景层 |
bool |
OBJPROP_BGCOLOR |
OBJ_EDIT、OBJ_BUTTON、OBJ_RECTANGLE_LABEL 的背景颜色 |
color |
OBJPROP_BORDER_TYPE |
矩形面板 OBJ_RECTANGLE_LABEL 的边框类型 |
ENUM_BORDER_TYPE |
OBJPROP_BORDER_COLOR |
输入字段 OBJ_EDIT 和按钮 OBJ_BUTTON 的边框颜色 |
color |
与其他大多数带线条对象(如独立垂直线/水平线、趋势线、周期线、通道等)不同,这些对象的 OBJPROP_COLOR 特性定义线条颜色,而对于 OBJ_BITMAP_LABEL 和 OBJ_BITMAP 图像对象,该特性定义边框颜色,而 OBJPROP_STYLE 定义边框绘制类型。
我们在指标一章的 绘图设置章节中已经介绍过用于 OBJPROP_STYLE 的 ENUM_LINE_STYLE 枚举类型。
务必需区分由前景色 OBJPROP_COLOR 执行的填充和背景色 OBJPROP_BGCOLOR 执行的填充。两者分别由不同的对象类型组支持,这些类型已在表中列出。
OBJPROP_BACK 特性需单独说明。需要明确的是,所有对象和指标都默认显示在价格图表上层。用户可以通过以下方式更改整个图表的此行为:进入图表的Setting对话框,然后进入Shared书签中的Chart on top选项。该标志在软件中有对应特性,即 CHART_FOREGROUND 特性(请参阅 图表显示模式章节)。但有时可能需要仅将特定对象移至背景层,而非全部对象。此时,可将这些对象的 OBJPROP_BACK 设为true。在此情况下,如果图表启用了网格和周期分隔线,该对象甚至会被这些分隔线覆盖。
当启用了 OBJPROP_FILL 填充模式时,形状内部的柱线颜色取决于 OBJPROP_BACK 特性。默认情况下,当 OBJPROP_BACK 为false时,与对象重叠的柱线会以 OBJPROP_COLOR 的反色绘制(反色通过将颜色值的所有二进制位取反得到,例如 0xFF0080 的反色为 0x00FF7F)。当 OBJPROP_BACK 为 true时,柱线将保持常规绘制,因为此时对象显示在背景层的图表“下层”(参见下方示例图)。
ENUM_BORDER_TYPE 枚举包含以下元素:
标识符 |
外观 |
---|---|
BORDER_FLAT |
平面 |
BORDER_RAISED |
凸起 |
BORDER_SUNKEN |
凹陷 |
当边框为平面 (BORDER_FLAT) 时,将根据 OBJPROP_COLOR、OBJPROP_STYLE 和 OBJPROP_WIDTH 特性,绘制为对应的颜色、样式和宽度。凸起和凹陷样式会通过 OBJPROP_BGCOLOR 的渐变色模拟边缘的立体倒角效果。
若未设置边框颜色 OBJPROP_BORDER_COLOR(默认值为 clrNone),输入字段将以主色 OBJPROP_COLOR 的线条勾勒边框,而按钮周围则会绘制带有 OBJPROP_BGCOLOR 渐变色调倒角的三维边框。
为测试新特性,可考虑使用ObjectStyle.mq5脚本。在该脚本中,我们将创建 5 个 OBJ_RECTANGLE 类型的矩形,这些对象均基于时间和价格。这些矩形将在窗口整个宽度范围均匀分布,用于高亮显示五个时间段中每个时间段内的最高价 High和最低价Low 之间的区间。对于所有对象,我们将调整并定期更改以下特性:线条颜色、线条样式、线条粗细以及图表底层的填充和显示选项。
我们将再次使用从 Object Selector派生而来的辅助类ObjectBuilder。和上一节不同的是,我们在 ObjectBuilder中添加了析构函数,其中将调用 ObjectDelete。
#include <MQL5Book/ObjectMonitor.mqh>
|
通过这种设计,该辅助类不仅可以配置对象,还可以在脚本运行结束时自动移除这些对象。
在 OnStart函数中,我们将确定可见柱线数量和第一根柱线的索引,同时计算单个矩形所占的柱体宽度。
#define OBJECT_NUMBER 5
|
我们为对象预留一个智能指针数组,以确保调用 ObjectBuilder析构函数。
AutoPtr<ObjectBuilder> objects[OBJECT_NUMBER]; |
定义颜色调色板并创建 5 个矩形对象。
color colors[OBJECT_NUMBER] = {clrRed, clrGreen, clrBlue, clrMagenta, clrOrange};
|
此处,为每个对象计算两个锚点的坐标;设置初始颜色、样式和线宽。
接下来,在无限循环中更改对象的特性。当 ScrollLock开启时,动画可以暂停。
const int key = TerminalInfoInteger(TERMINAL_KEYSTATE_SCRLOCK);
|
图表实际效果如下。
具有不同显示设置的 OBJ_RECTANGLE 矩形
最左侧红色矩形启用了填充模式并位于前景层。因此,其内部柱线显示为对比鲜明的亮蓝色(clrAqua,通常也称为 cyan,即clrRed 的反色)。紫色矩形同样有填充,但设置了背景选项,内部柱线以标准方式显示。
请注意,橙色矩形因其较宽的边框线宽且设置为图表顶层显示,会完全覆盖其子范围起点和终点的柱线。
当启用填充时,不会考虑线宽。当边框宽度大于 1 时,部分虚线样式不会被应用。
ObjectShapesDraw
在本节的第二个示例中,回顾我们在第三章学习 OOP 时构思的虚拟绘图程序。当时我们的开发进度停留在:在虚拟绘图方法(名为 draw)中,只能向日志中输出“正在绘制特定形状”的消息。现在通过学习图形对象,我们已具备实际绘图能力。
让我们以 Shapes5stats.mq5 脚本为起点。更新后的版本将命名为 ObjectShapesDraw.mq5。
需要说明的是,除了基类Shape外,我们还描述了多个形状类:RectangleEllipse、Triangle、Square 和Circle。所有这些类都成功创建对应类型的图形对象:OBJ_RECTANGLE、OBJ_ELLIPSE、OBJ_TRIANGLE。但存在一些细微差别。
所有指定对象均与时间和价格坐标绑定,而我们的绘图程序采用统一的 X 轴和 Y 轴进行点定位。为此,我们需要以特殊方式配置图表以进行绘制,并调用 ChartXYToTimePrice 函数将屏幕坐标点重新换算为时间和价格值
此外,OBJ_ELLIPSE 和 OBJ_TRIANGLE 对象允许任意旋转(特别是椭圆的长短轴可旋转),而 OBJ_RECTANGLE 的边始终保持水平和垂直方向。为简化示例,我们限制所有形状仅使用标准位置。
从理论上讲,新实现应视为图形对象的演示,而非完整的绘图程序。对于成熟的绘图功能,更合适的做法是使用 图形资源,这样可以避免图形对象带来的限制(因为图形对象一般用于其他用途,例如图表标记)。因此,我们将在后续关于资源章节中重新审视绘图程序的设计。
在新的 Shape类中,我们将移除包含对象坐标的嵌套结构 Pair:该结构原本用于演示 OOP 的多项原则,但现在更简单的做法是将原始的字段描述 int x, y 直接还原到Shape 类中。我们还将添加一个包含对象名的字段。
class Shape
|
name字段是必需的,可用于设置图形对象的特性,也可用于从图表中移除对象,这一操作在析构函数中执行是合乎逻辑的。
由于不同类型的形状需要不同数量的点或特征尺寸,除 draw虚方法外,我们还会在 Shape 接口中添加 setup 方法:
virtual void setup(const int ¶meters[]) = 0; |
需要说明的是,我们在脚本中已实现了一个嵌套类Shape::Registrator,它原本负责按类型统计形状数量。现在需要赋予它更重要的职责,作为形状工厂来运作。“工厂”类或方法的优势在于能够以统一的方式创建不同类的对象。
为此,我们向 Registrator中添加一个创建形状的方法,其参数包括:第一个点的坐标(必需)、颜色以及一个额外参数数组(每个形状都能够按照自己的规则解析该数组,且未来可从中读取或写入文件)。
virtual Shape *create(const int px, const int py, const color back,
|
该方法是抽象虚方法,因为特定类型的形状只能通过Shape派生类中描述的派生注册类来创建。为简化派生日志记录类的编写工作,我们引入了一个模板类 MyRegistrator,其包含适用于所有场景的 create 方法实现。
template<typename T>
|
在此方法中,我们调用某个先前未知形状 T 的构造函数,通过调用 setup对其进行调整,并将实例返回给调用代码。
以下是在 Rectangle类中的使用方式,该类有两个用于宽度和高度的额外参数。
class Rectangle : public Shape
|
创建形状时,其名称不仅包含类名(typename),还包含通过 r.increment() 调用计算得出的实例序号。
其他形状类的描述方式类似。
现在我们需要研究 Rectangle类的draw 方法。在该方法中,我们使用 ChartXYToTimePrice将一对点 (x,y) 和 (x + dx, y + dy) 转换为时间/价格坐标,并创建一个 OBJ_RECTANGLE 对象。
void draw() override
|
当然,不要忘记将颜色设置为 OBJPROP_COLOR,将填充设置为 OBJPROP_FILL。
对于 Square类,实际上不需要做任何更改:只需将 dx 和 dy 设置为相等即可。
对于 Ellipse类,两个额外选项 dx 和dy 决定了相对于中心 (x,y) 绘制的短半径和长半径。相应地,在 draw方法中,我们将计算 3 个锚点并创建一个 OBJ_ELLIPSE 对象。
class Ellipse : public Shape
|
Circle 是长半径和短半径相等的特殊椭圆。
最后一点,现阶段仅支持等边三角形:其边长包含在额外字段dx中。我们建议你自行查阅源代码中的 draw方法。
新脚本将延续之前的功能,随机生成指定数量的形状。它们由 addRandomShape函数创建。
Shape *addRandomShape()
|
在该函数中,我们可以看到工厂方法 create的用法,该方法通过随机选择的寄存器对象,带有数字n。如果决定后续添加其他形状类,也无需更改生成逻辑中的任何内容。
所有形状都均位于窗口中央,且尺寸不超过窗口的四分之一。
接下来需要直接研究 addRandomShape函数的调用,以及前文提及的特殊时间表设置。
为实现屏幕上点的正方形呈现,可设置 CHART_SCALEFIX_11 模式。此外,我们将沿时间轴选择最密集(压缩)的刻度 CHART_SCALE (0),因为其中一个柱线占用 1 个水平像素(最高精度)。最后,通过将 CHART_SHOW 设置为 false来禁用图表本身的显示。
void OnStart()
|
为存储形状,我们将预留一个智能指针数组,并用随机形状填充该数组。
#define FIGURES 21
|
然后,运行一个无限循环,直到用户停止脚本,在循环中通过 move方法微调所有形状的位置。
while(!IsStopped())
|
最后,我们将恢复图表设置。
// it's not enough to disable CHART_SCALEFIX_11, you need CHART_SCALEFIX
|
以下截图展示了绘制形状后的图表可能呈现的效果:
图表形状对象
对象绘制的特点是在重叠区域产生颜色“叠加”效果。
由于 Y 轴采用自上而下的方向,所有三角形均呈现倒置状态,但这个问题不是很严重,因为我们最终将基于资源重构绘图程序。