在图表窗口中显示消息

我们在前面章节中已经知道,MQL5 支持将消息输出到日志或警报窗口。第一种方法主要用于技术信息,不能保证用户将会注意到消息(因为日志窗口可能被隐藏)。同时,如果使用第二种方法显示经常变化的程序状态,可能看起来干扰性过大。一种折衷的选择是 Comment 函数。

void Comment(argument, ...)

该函数在图表左上角显示一条由传递的所有自变量组成的消息。该消息会一直保留,直至该程序或其它程序将其消除或以另一条消息替换。

该窗口可能仅包含一条注释:每次调用 Comment 时,旧内容(如有)被新内容替换。

要清理注释,只需以空字符串调用该函数:Comment("")

参数的数量不得超过 64。仅支持内置类型的自变量。根据传递值形成结果字符串的概念类似于函数 Print

显示的消息的总长度限制为 2045 个字符。如果超过该限值,则行末将被截断。

注释的当前内容是图表的字符串特性之一,可以通过调用 ChartGetString(NULL, CHART_COMMENT) 函数找到。我们将在单独 章节中探讨图表的这一特性及其它特性(不仅是字符串特性)。

与在 PrintPrintFormat 以及 Alert 函数中一样,字符串自变量可能包含换行符字符('\n' 或 '\r\n'),这会将消息被拆分为适当数量的字符串。对于 Comment,这是显示多行消息的唯一方法。如果使用 print 和 signal 函数,可以通过多次调用来实现相同效果,但使用 Comment 则无法实现,因为每次调用将以新字符串替换旧字符串。

关于 Comment 函数的工作示例,可参见第一章的 数据输出一节中包含欢迎脚本的窗口截图。

此外,我们将开发一个类和一些简化函数以基于给定大小的环形缓冲区显示多行注释。本书中包含了测试脚本 (OutputComment.mq5) 和具有类代码的头文件 (Comments.mqh)。

class Comments
{
 const int capacity// maximum number of strings
 const bool reverse// display order (new ones on top if true)
 string lines[];     // text buffer
 int cursor;         // where to put the next string
 int size;           // actual number of strings saved
   
public:
   Comments(const int limit = N_LINESconst bool r = false):
      capacity(limit), reverse(r), cursor(0), size(0)
   {
      ArrayResize(linescapacity);
   }
   
   void add(const string line);
   void clear();
};

主要工作由 add 方法完成。

void Comments::add(const string line)
{
   ...
   // if the passed text contains multiple strings,
   // split it into elements by newline character
   string inputs[];
   const int n = StringSplit(line, '\n', inputs);
   
   // add all new elements to the ring buffer
   // overwriting the oldest entries at the cursor
   // cursor increases by capacity module (reset to 0 on overflow)
   for(int i = 0i < n; ++i)
   {
      lines[cursor] = inputs[reverse ? n - i - 1 : i];
      cursor = (cursor + 1) % capacity;
      if(size < capacitysize++;
   }
   // concatenate all text entries in forward or reverse order
   // gluing with newline characters
   string result = "";
   for(int i = 0k = size == capacity ? cursor % capacity : 0;
      i < size; ++ik = ++k % capacity)
   {
      if(reverse)
      {
         result = lines[k] + "\n" + result;
      }
      else
      {
         result += lines[k] + "\n";
      }
   }
   
   // output the result
   Comment(result);
}

如果必要,注释以及文本缓冲区可通过 clear 方法或通过调用 add(NULL) 进行清理。

void Comments::clear()
{
   Comment("");
   cursor = 0;
   size = 0;
}

有了这一个类,你可以以所需的缓冲区容量和输出方向定义对象,然后使用其方法。

Comments c(30/*capacity*/true/*order*/);
   
void function()
{
   ...
   c.add("123");
}

但为简化常规函数风格中的注释生成(类似 Comment 函数),我们实现了若干个辅助函数。

void MultiComment(const string line = NULL)
{
   static Comments com(N_LINEStrue);
   com.add(line);
}
 
void ChronoComment(const string line = NULL)
{
   static Comments com(N_LINESfalse);
   com.add(line);
}

区别仅在于缓冲区输出的方向。MultiComment 以逆向时间顺序显示行,即最新内容显示在顶部,就象在公告牌上一样。建议将该函数用于长期持续显示信息并保留历史记录的场景。ChronoComment 以正向顺序显示行,即新行被添加到底部。建议将该函数用于多行消息的批量输出。

缓冲区行数默认为 N_LINES (10)。如果你在包括头文件之前以一个不同的值定义该宏,则将调整大小。

测试脚本包含一个循环,其中定期生成消息。

void OnStart()
{
   for(int i = 0i < 50 && !IsStopped(); ++i)
   {
      if((i + 1) % 10 == 0MultiComment();
      MultiComment("Line " + (string)i + ((i % 3 == 0) ? "\n  (details)" : ""));
      Sleep(1000);
   }
   MultiComment();
}

每十次迭代清理一次注释。每三次迭代生成一条两行消息(其余消息为一行)。有 1 秒的延迟,可让你观察动态效果。

下面是该脚本正在运行时的窗口示例(“新消息在上”模式)。

图表上的多行注释

图表上的多行注释

在一个注释中显示多行信息的能力十分有限。如果你需要按列组织数据输出,以颜色或不同字体来突出显示,实现鼠标单击反应或在图表上的任意位置,你应使用图形 对象