Appearance
用户交互
程序与“外部世界”的连接始终是双向的,用于组织这种连接的手段可以大致分为数据输入和输出两类。在经典模式下,用户为程序提供一些设置,然后从程序获取结果。如果程序与某些外部应用程序或服务集成,输入和输出通常通过特殊的交换协议(如通过文件、网络、共享内存等)来完成,绕过用户界面。
MQL 程序执行环境允许以多种方式组织与 MetaTrader 5 用户的交互。
在本章中,我们将介绍其中最简单的几种方式,包括在日志或图表中显示消息、显示简单的对话框以及发出声音警报。
需要提醒的是,向 MQL 程序输入数据的标准方式是使用输入变量。不过,这些变量只能在程序初始化时设置。通过设置对话框更改程序属性意味着使用新的值“重新启动”程序(稍后我们会讨论与某种 MQL 程序类型相关的一些特殊情况,因此这里的“重新启动”加了引号)。
更灵活的交互式关系意味着能够在不停止程序的情况下控制其行为。在一些基础场景中,下面会讨论的 MessageBox
对话框可能适用于此,但对于大多数实际应用来说,这是不够的。
因此,在本书的后续部分,我们将大幅扩展实现用户界面的工具列表,并学习如何基于界面对象创建交互式程序、在指标或资源中显示图形信息、向用户的移动设备发送推送通知等等。
记录日志消息
记录日志是向用户告知程序运行当前信息的最常见方式。这些信息可能包括常规完成的状态、长时间计算过程中的进度指示,或者用于查找和重现错误的调试数据。
遗憾的是,没有程序员能完全避免代码中出现错误。因此,开发人员通常会尝试留下所谓的“线索”:记录程序执行的主要阶段(至少记录函数调用的顺序)。
我们已经熟悉了两个用于记录日志的函数——Print
和 PrintFormat
。在前面章节的示例中我们使用过它们。由于几乎离不开它们,所以我们不得不提前以简化模式使用它们。
通常,一次函数调用会生成一条记录。然而,如果在输出字符串中遇到换行符(\n
),它会将信息分割成两部分。
请注意,所有 Print
和 PrintFormat
调用都会转换为“工具箱”窗口中“专家”选项卡上的日志条目。尽管该选项卡名为“专家”,但它会收集所有打印指令的结果,而不论 MQL 程序的类型是什么。
日志存储在按照“一天 = 一个文件”原则组织的文件中:它们的名称为 YYYYMMDD.log
(Y 代表年份,M 代表月份,D 代表日期)。这些文件位于 <数据目录>/MQL5/Logs
中(不要将它们与 <数据目录>/Logs
文件夹中的终端系统日志混淆)。
请注意,在批量记录日志时(如果 Print
函数调用在短时间内生成大量信息),终端在窗口中仅显示部分条目。这样做是为了优化性能。此外,用户无论如何也无法在运行时查看所有消息。要查看完整的日志版本,需要运行上下文菜单中的“查看”命令。结果会打开一个包含日志的窗口。
还应该记住,日志中的信息在写入磁盘时会被缓存,也就是说,它以延迟模式以大块的形式写入文件,这就是为什么在任何给定时间,日志文件通常不包含最新的条目(尽管它们在窗口中是可见的)。要将缓存刷新到磁盘,可以在日志上下文菜单中运行“查看”或“打开”命令。
每条日志条目前面都有精确到毫秒的时间,以及生成或导致此消息的程序(及其图表)的名称。
c
void Print(argument,...)
该函数将一个或多个值打印到专家日志中,在一行中显示(如果输出数据不包含字符 \n
)。
参数可以是任何内置类型。它们用逗号分隔。参数的数量不能超过 64 个。其可变数量在函数原型中用省略号表示,但 MQL5 不允许你描述具有类似特征的自定义函数:只有一些内置的 API 函数具有可变数量的参数(特别是 StringFormat
、Print
、PrintFormat
和 Comment
)。
对于结构体和类,你应该实现内置的打印方法,或者分别显示它们的字段。
此外,该函数无法处理数组。你可以逐个显示数组元素,或者使用 ArrayPrint
函数。
double
类型的值由该函数以高达 16 位有效数字(尾数和小数部分总共)的精度输出。数字可以以传统格式或科学格式(带指数)显示,哪种格式更紧凑就采用哪种。float
类型的值以 7 位小数的精度显示。要以不同的精度显示实数,或者显式指定格式,必须使用 PrintFormat
函数。
bool
类型的值输出为字符串“true
”或“false
”。
日期以“YYYY.MM.DD hh:mm:ss
”的格式显示,日期和时间以最高精度(精确到秒)指定。要以不同的格式显示日期,请使用 TimeToString
函数(请参阅“日期和时间”部分)。
枚举值以整数形式显示。要显示元素名称,请使用 EnumToString
函数(请参阅“枚举”部分)。
单字节和双字节字符也以整数形式输出。要将符号显示为字符或字母,请使用 CharToString
或 ShortToString
函数(请参阅“处理符号和代码页”部分)。
颜色类型的值要么显示为一个包含三个数字的字符串,表示每个颜色分量的强度(“R, G, B
”),要么显示为颜色名称(如果该颜色存在于颜色集中)。
有关将不同类型的值转换为字符串的更多信息,请参阅“内置类型的数据转换”一章(特别是“数字与字符串的相互转换”、“日期和时间”、“颜色”部分)。
在策略测试器的单次运行模式(测试专家顾问或指标)下工作时,Print
函数的结果将输出到测试代理日志中。
在策略测试器的优化模式下工作时,出于性能原因会抑制日志记录,因此 Print
函数没有可见效果。然而,作为参数给出的所有表达式都会被求值。
所有参数在转换为字符串表示后,会连接成一个公共字符串,中间没有任何分隔字符。如果需要,必须在参数列表中显式写入这些字符。例如:
c
int x;
bool y;
datetime z;
...
Print(x, ", ", y, ", ", z);
在这里,记录了 3 个变量,用逗号分隔。如果没有中间的文本常量“,
”,变量的值在日志条目中会连在一起。
从本书的最初部分开始(例如“第一个程序”、“赋值与初始化”、“表达式与数组”以及其他部分),可以找到许多应用 Print
函数的示例。
作为使用 Print
的一种新方式,我们将实现一个简单的类,该类允许显示一系列任意值,而无需在每个相邻值之间指定分隔字符。我们使用“<<
”运算符重载方法,类似于 C++ 输入输出流(std::cout
)中使用的方法。
类定义将放在一个单独的头文件 OutputStream.mqh
中。下面以简化形式展示一个类。
c
class OutputStream
{
protected:
ushort delimiter;
string line;
// 添加下一个参数,用分隔符分隔(如果有分隔符)
void appendWithDelimiter(const string v)
{
line += v;
if(delimiter != 0)
{
line += ShortToString(delimiter);
}
}
public:
OutputStream(ushort d = 0): delimiter(d) { }
template<typename T>
OutputStream *operator<<(const T v)
{
appendWithDelimiter((string)v);
return &this;
}
OutputStream *operator<<(OutputStream &self)
{
if(&this == &self)
{
print(line);// 输出组合后的字符串
line = NULL;
}
return &this;
}
};
其要点是在字符串变量 line
中累积使用“<<
”运算符传递的任何参数的字符串表示。如果在类构造函数中指定了分隔字符,它会自动插入到参数之间。由于重载的运算符返回一个指向对象的指针,我们可以链式传递一系列参数:
c
OutputStream out(',');
out << x << y << z << out;
作为数据收集结束的一个属性,并且为了将内容 line
实际输出到日志中,使用了对对象本身的相同运算符的重载。
实际的类要稍微复杂一些。特别是,它不仅允许设置分隔字符,还允许设置显示实数的精度,以及选择日期和时间值字段的标志。此外,该类支持将 ushort
类型的字符以字符形式(而不是整数值代码)打印,支持数组的简化输出(输出到一个单独的字符串中),支持将十六进制格式的颜色作为单个值输出(而不是用逗号分隔的三个数字,因为逗号经常用作分隔字符,那样的话日志中的颜色分量看起来就像 3 个不同的变量)。
在脚本 OutputStream.mq5
中给出了使用该类的演示。
c
void OnStart()
{
OutputStream os(5, ',');
bool b = true;
datetime dt = TimeCurrent();
color clr = C'127, 128, 129';
int array[] = {100, 0, -100};
os << M_PI << "text" << clrBlue << b << array << dt << clr << '@' << os;
/*
输出示例
3.14159,text,clrBlue,true
[100,0,-100]
2021.09.07 17:38,clr7F8081,@
*/
}
c
void PrintFormat(const string format,...) ≡ void printf(const string format,...)
该函数根据指定的格式字符串记录一组参数。格式参数不仅提供一个按原样显示的自由文本输出字符串模板,还可以包含转义序列,用于描述特定参数的格式化方式。
包括格式字符串在内的参数总数不能超过 64 个。对参数类型的限制与 Print
函数类似。
PrintFormat
的工作和格式化原则与 StringFormat
函数描述的相同(请参阅“将通用格式化数据输出到字符串”部分)。唯一的区别是 StringFormat
将形成的字符串返回给调用代码,而 PrintFormat
将其发送到日志中。可以说 PrintFormat
有以下条件等效形式:
Print(StringFormat(<参数列表,包括格式字符串本身>))
除了完整名称 PrintFormat
之外,你还可以使用更短的别名 printf
。
与 Print
函数一样,PrintFormat
在测试器的优化模式下工作时也有一些特定的特点:为了提高性能,它输出到日志的操作会被抑制。
我们在许多部分的脚本中都已经见过使用 PrintFormat
的情况,例如“返回转换”、“颜色”、“动态数组”、“文件描述符管理”、“获取全局变量列表”。
警报
在本节中,“信号”指的是 Alert
函数,它用于向终端用户发出警告。
“警报” 一词在 MetaTrader 5 中有多种含义。它在两种情境下被使用:
- 在 “工具箱” 面板的 “警报” 选项卡中由用户手动配置的警报。使用这些警报,你可以追踪价格、交易量或时间超过设定值等简单条件的触发情况,并以各种方式发出通知。
- 由 MQL 代码通过
Alert
函数生成的程序 “警报”。它们与上述第一种警报毫无关系。
c
void Alert(argument,...)
该函数会在一个非模态对话框中显示一条消息,并伴有标准的声音信号(根据终端中 “选项” 对话框里 “事件” 选项卡中的设置来选择声音)。如果该窗口处于隐藏状态,它会显示在终端主窗口的上方(之后可以关闭、最小化或移开该窗口,同时继续使用主窗口进行操作)。该消息也会添加到 “专家” 日志中,并标记为 “Alert”。
如果之前关闭了警报窗口,MetaTrader 5 界面中没有手动打开该窗口的命令。要再次查看警告列表(以其原始形式,无需过滤日志),则需要以某种方式生成一个新的信号。
传递参数、显示信息以及该函数的一般原则与 Print
函数所描述的完全相同。
在第一章 “数据输出” 部分的介绍性问候示例中,展示了带有屏幕截图的 Alert
函数演示。
在需要吸引用户对显示信息的注意力时,请使用 Alert
函数而非 Print
函数。然而,不应过度使用它,因为该窗口频繁出现可能会妨碍用户的工作,迫使用户忽略消息或停止 MQL 程序。请在你的程序中提供一种算法,以限制可能生成消息的频率。
在图表窗口中显示消息
正如我们在前面的章节中所看到的,MQL5 允许将消息输出到日志或警报窗口中。第一种方法主要用于输出技术信息,并且不能保证用户会注意到该消息(因为日志窗口可能处于隐藏状态)。同时,如果使用第二种方法来显示频繁变化的程序状态,可能会显得过于干扰用户。一种折中的选择是使用 Comment
函数。
c
void Comment(argument,...)
该函数会在图表的左上角显示一条由所有传递的参数组成的消息。该消息会一直保留在那里,直到这个或其他程序将其移除或用另一条消息替换它。
图表窗口中只能包含一条注释:每次调用 Comment
函数时,旧的内容(如果有的话)都会被新的内容所替代。
要清除注释,只需使用空字符串调用该函数:Comment("")
。
参数的数量不能超过 64 个。仅支持内置类型的参数。从传递的值形成结果字符串的概念与 Print
函数所描述的类似。
显示消息的总长度限制为 2045 个字符。如果超过限制,行的末尾将被截断。
注释的当前内容是图表的字符串属性之一,可以通过调用 ChartGetString(NULL, CHART_COMMENT)
函数来获取。我们将在单独的章节中讨论图表的这个属性以及其他属性(不仅仅是字符串属性)。
与 Print
、PrintFormat
和 Alert
函数一样,字符串参数中可能包含换行符(\n
或 \r\n
),这会导致消息被分割成相应数量的字符串。对于 Comment
函数来说,这是显示多行消息的唯一方法。如果使用 Print
和 Alert
函数可以通过多次调用来达到相同的效果,但是对于 Comment
函数却不能这样做,因为每次调用都会用新字符串替换旧字符串。
Comment
函数的工作示例在第一章 “数据输出” 部分的欢迎脚本窗口图像中有所展示。
此外,我们将开发一个类和简化函数,用于基于给定大小的环形缓冲区显示多行注释。测试脚本(OutputComment.mq5
)和包含类代码的头文件(Comments.mqh
)已包含在本书中。
c
class Comments
{
const int capacity; // 字符串的最大数量
const bool reverse; // 显示顺序(如果为 true,则新内容在顶部)
string lines[]; // 文本缓冲区
int cursor; // 下一个字符串的放置位置
int size; // 实际保存的字符串数量
public:
Comments(const int limit = N_LINES, const bool r = false):
capacity(limit), reverse(r), cursor(0), size(0)
{
ArrayResize(lines, capacity);
}
void add(const string line);
void clear();
};
主要的工作由 add
方法完成。
c
void Comments::add(const string line)
{
...
// 如果传递的文本包含多个字符串,
// 按换行符将其拆分为元素
string inputs[];
const int n = StringSplit(line, '\n', inputs);
// 将所有新元素添加到环形缓冲区
// 覆盖光标处最旧的条目
// 光标按容量取模增加(溢出时重置为 0)
for(int i = 0; i < n; ++i)
{
lines[cursor] = inputs[reverse ? n - i - 1 : i];
cursor = (cursor + 1) % capacity;
if(size < capacity) size++;
}
// 按正序或逆序连接所有文本条目
// 用换行符连接
string result = "";
for(int i = 0, k = size == capacity ? cursor % capacity : 0;
i < size; ++i, k = ++k % capacity)
{
if(reverse)
{
result = lines[k] + "\n" + result;
}
else
{
result += lines[k] + "\n";
}
}
// 输出结果
Comment(result);
}
如果需要,可以通过 clear
方法或调用 add(NULL)
来清除注释和文本缓冲区。
c
void Comments::clear()
{
Comment("");
cursor = 0;
size = 0;
}
有了这样一个类,你可以定义一个具有所需缓冲区容量和输出方向的对象,然后使用它的方法。
c
Comments c(30/*容量*/, true/*顺序*/);
void function()
{
...
c.add("123");
}
但是,为了以常见的函数式风格简化注释的生成,类似于 Comment
函数,实现了几个辅助函数。
c
void MultiComment(const string line = NULL)
{
static Comments com(N_LINES, true);
com.add(line);
}
void ChronoComment(const string line = NULL)
{
static Comments com(N_LINES, false);
com.add(line);
}
它们仅在缓冲区输出方向上有所不同。MultiComment
以逆时间顺序显示行,即最新的内容在顶部,就像在公告板上一样。对于无限期地以片段形式显示信息并保留历史记录的情况,建议使用此函数。ChronoComment
以正序显示行,即新内容添加在底部。对于批量输出多行消息的情况,建议使用此函数。
缓冲区的行数默认是 N_LINES
(10)。如果在包含头文件之前将此宏定义为不同的值,它将调整大小。
测试脚本包含一个循环,在其中定期生成消息。
c
void OnStart()
{
for(int i = 0; i < 50 && !IsStopped(); ++i)
{
if((i + 1) % 10 == 0) MultiComment();
MultiComment("Line " + (string)i + ((i % 3 == 0) ? "\n (细节)" : ""));
Sleep(1000);
}
MultiComment();
}
在每第十次迭代时,注释被清除。在每第三次迭代时,创建一个由两行组成的消息(对于其余情况 - 由一行组成)。1 秒的延迟可以让你看到动态效果。
这是脚本运行时窗口的一个示例(在“新消息在顶部”模式下)。
图表上的多行注释
图表上的多行注释
在注释中显示多行信息的功能相当有限。如果你需要按列组织数据输出、用颜色或不同字体突出显示、对鼠标点击做出反应,或者在图表上任意位置显示内容,则应该使用图形对象。
消息对话框
MQL5 API 提供了 MessageBox
函数,用于以交互方式提示用户确认操作,或者在特定情况下选择处理选项。
c
int MessageBox(const string message, const string caption = NULL, int flags = 0)
该函数会打开一个无模式对话框,其中包含给定的消息(message
)、标题(caption
)和设置(flags
)。在用户通过点击其中一个可用按钮关闭该窗口之前(具体按钮情况见下文),该窗口会一直显示在终端主窗口的上方。
该消息也会以“Message”标记显示在专家日志中。
如果 caption
参数为 NULL
,则会使用 MQL 程序的名称作为对话框的标题。
flags
参数必须包含通过按位或(|
)操作组合的位标志。支持的标志一般分为 3 组,用于定义:
- 对话框中的按钮集合
- 对话框中的图标图像
- 默认选中的活动按钮
以下表格列出了用于定义对话框按钮的常量和标志值:
常量 | 值 | 描述 |
---|---|---|
MB_OK | 0x0000 | 1 个“确定”按钮(默认) |
MB_OKCANCEL | 0x0001 | 2 个按钮:“确定”和“取消” |
MB_ABORTRETRYIGNORE | 0x0002 | 3 个按钮:“终止”、“重试”、“忽略” |
MB_YESNOCANCEL | 0x0003 | 3 个按钮:“是”、“否”、“取消” |
MB_YESNO | 0x0004 | 2 个按钮:“是”和“否” |
MB_RETRYCANCEL | 0x0005 | 2 个按钮:“重试”和“取消” |
MB_CANCELTRYCONTINUE | 0x0006 | 3 个按钮:“取消”、“再试一次”、“继续” |
以下表格列出了可用的图标(显示在消息左侧):
常量 | 值 | 描述 |
---|---|---|
MB_ICONSTOP MB_ICONERROR MB_ICONHAND | 0x0010 | 停止标志 错误图标 |
MB_ICONQUESTION | 0x0020 | 问号 问题图标 |
MB_ICONEXCLAMATION MB_ICONWARNING | 0x0030 | 感叹号 警告图标 |
MB_ICONINFORMATION MB_ICONASTERISK | 0x0040 | 信息标志 信息图标 |
所有图标都取决于操作系统版本。你计算机上显示的示例可能会有所不同。 以下值用于选择活动按钮:
常量 | 值 | 描述 |
---|---|---|
MB_DEFBUTTON1 | 0x0000 | 如果未选择其他常量,则默认第一个按钮 |
MB_DEFBUTTON2 | 0x0100 | 第二个按钮 |
MB_DEFBUTTON3 | 0x0200 | 第三个按钮 |
MB_DEFBUTTON4 | 0x0300 | 第四个按钮 |
可能会有人疑惑,如果上述常量最多只能设置三个按钮,那么这第四个按钮是什么。事实上,在这些标志中还有 MB_HELP
(0x00004000
)。它指示在对话框中显示“帮助”按钮。如果有三个主要按钮,那么“帮助”按钮就会成为第四个按钮。不过,与其他按钮不同,点击“帮助”按钮不会关闭对话框。根据 Windows 标准,程序可以关联一个帮助文件,当按下“帮助”按钮时,应该打开相应的帮助内容。然而,目前 MQL 程序不支持这项技术。
该函数会根据对话框的关闭方式(即按下了哪个按钮)返回一个预定义的值。
常量 | 值 | 描述 |
---|---|---|
IDOK | 1 | “确定”按钮 |
IDCANCEL | 2 | “取消”按钮 |
IDABORT | 3 | “终止”按钮 |
IDRETRY | 4 | “重试”按钮 |
IDIGNORE | 5 | “忽略”按钮 |
IDYES | 6 | “是”按钮 |
IDNO | 7 | “否”按钮 |
IDTRYAGAIN | 10 | “再试一次”按钮 |
IDCONTINUE | 11 | “继续”按钮 |
如果消息框中有“取消”按钮,那么当按下 ESC
键时(除了点击“取消”按钮),函数会返回 IDCANCEL
。如果消息框中没有“取消”按钮,按下 ESC
键将不起作用。
调用 MessageBox
会暂停当前 MQL 程序的执行,直到用户关闭对话框。因此,在指标中禁止使用 MessageBox
,因为指标是在终端的界面线程中执行的,等待用户响应会减慢图表的更新速度。
此外,该函数不能在服务中使用,因为服务与用户界面没有关联,而其他类型的 MQL 程序是在图表的上下文中执行的。
在策略测试器中运行时,MessageBox
函数没有效果并返回 0。
在从函数调用获取结果后,你可以按照所需的方式进行处理,例如:
c
int result = MessageBox("Continue?", NULL, MB_YESNOCANCEL);
// 根据需要使用'switch' 或 'if'
switch(result)
{
case IDYES:
// ...
break;
case IDNO:
// ...
break;
case IDCANCEL:
// ...
break;
}
可以使用 OutputMessage.mq5
脚本来测试 MessageBox
函数,在该脚本中,用户可以使用输入变量选择对话框的参数,并查看其实际效果。
按钮、图标和默认选中按钮的设置组,以及返回代码,都在特殊的枚举中进行了描述:ENUM_MB_BUTTONS
、ENUM_MB_ICONS
、ENUM_MB_DEFAULT
、ENUM_MB_RESULT
。这通过下拉列表提供了可视化的输入方式,并使用 EnumToString
简化了将它们转换为字符串的过程。
例如,以下是前两个枚举的定义方式:
c
enum ENUM_MB_BUTTONS
{
_OK = MB_OK, // 确定
_OK_CANCEL = MB_OKCANCEL, // 确定 | 取消
_ABORT_RETRY_IGNORE = MB_ABORTRETRYIGNORE, // 终止 | 重试 | 忽略
_YES_NO_CANCEL = MB_YESNOCANCEL, // 是 | 否 | 取消
_YES_NO = MB_YESNO, // 是 | 否
_RETRY_CANCEL = MB_RETRYCANCEL, // 重试 | 取消
_CANCEL_TRYAGAIN_CONTINUE = MB_CANCELTRYCONTINUE, // 取消 | 再试一次 | 继续
};
enum ENUM_MB_ICONS
{
_ICON_NONE = 0, // 无
_ICON_QUESTION = MB_ICONQUESTION, // 问号
_ICON_INFORMATION_ASTERISK = MB_ICONINFORMATION, // 信息(星号)
_ICON_WARNING_EXCLAMATION = MB_ICONWARNING, // 警告(感叹号)
_ICON_ERROR_STOP_HAND = MB_ICONERROR, // 错误(停止,手形)
};
其余的枚举可以在源代码中找到。
然后,它们被用作输入变量的类型(元素注释在用户界面中提供了更友好的显示方式):
c
input string Message = "Message";
input string Caption = "";
input ENUM_MB_BUTTONS Buttons = _OK;
input ENUM_MB_ICONS Icon = _ICON_NONE;
input ENUM_MB_DEFAULT Default = _DEF_BUTTON1;
void OnStart()
{
const string text = Message + "\n"
+ EnumToString(Buttons) + ", "
+ EnumToString(Icon) + ","
+ EnumToString(Default);
ENUM_MB_RESULT result = (ENUM_MB_RESULT)
MessageBox(text, StringLen(Caption) ? Caption : NULL, Buttons | Icon | Default);
Print(EnumToString(result));
}
该脚本会在窗口中显示指定的消息,以及指定的对话框设置。对话的结果会显示在日志中。
以下图片展示了选项选择和生成的对话框的屏幕截图:
窗口属性对话框
窗口属性对话框
收到的消息对话框
收到的消息对话框
声音警报
为了处理声音相关的操作,MQL5 API 提供了一个函数:PlaySound
。
c
bool PlaySound(const string soundfile)
该函数播放指定的 .wav
格式的声音文件。
如果指定的文件名没有路径(例如,"Ring.wav"
),那么该文件必须位于终端安装目录内的 Sounds
文件夹中。如果需要,你可以在 Sounds
文件夹内创建子文件夹。在这种情况下,soundfile
参数中的文件名前面应该加上相对路径。例如,"Example/Ring.wav"
指的是终端安装目录内的 Sounds/Example/Ring.wav
文件夹和文件。
此外,你可以使用位于终端数据目录中任何其他 MQL5 子文件夹中的声音文件。这样的路径前面必须加上前导斜杠(正斜杠 '/'
或双反斜杠 '\\'
),这是在文件系统中相邻文件夹层级之间使用的分隔字符。例如,如果声音文件 Demo.wav
位于 terminal_data_directory/MQL5/Files
中,那么在调用 PlaySound
时,我们将写入路径 "/Files/Demo.wav"
。
使用 NULL
参数调用该函数会停止正在播放的声音。当旧的声音文件还在播放时调用该函数并传入新的文件,会导致旧的声音中断并开始播放新的声音。
除了位于文件系统中的文件外,还可以将指向资源(嵌入在 MQL 程序中的数据块)的路径传递给该函数。特别是,开发人员可以在沙盒内的编译时从本地可用的文件创建一个声音资源。所有资源都位于 .ex5
文件内,这确保了用户拥有这些资源,并简化了将程序作为单个模块进行分发的过程。
关于使用资源的所有方式的详细文章,不仅包括声音,还包括图像、任意二进制和文本数据以及依赖程序(指标),在本书第七部分的相应章节中有介绍。
如果找到文件,PlaySound
函数将返回 true
,否则返回 false
。请注意,即使该文件不是音频文件且无法播放,该函数也会返回 true
。
声音播放是异步进行的,与后续程序指令的执行并行。换句话说,该函数在调用后会立即将控制权返回给调用代码,而无需等待音频效果播放完成。
在策略测试器中,PlaySound
函数不会执行。
OutputSound.mq5
脚本可用于测试该函数的操作。
c
void OnStart()
{
PRTF(PlaySound("new.txt"));
PRTF(PlaySound("abracadabra.wav"));
const uint start = GetTickCount();
PRTF(PlaySound("request.wav"));
PRTF(GetTickCount() - start);
}
该程序尝试播放多个文件。文件 "new.txt"
存在(专门为测试而创建),文件 "abracadabra.wav"
不存在,而 "request.wav"
文件包含在 MetaTrader 5 的标准发行版中。使用一对 GetTickCount
函数调用来测量最后一个函数调用的时间。
运行该脚本的结果是,我们得到以下日志条目:
PlaySound(new.txt)=true / ok
PlaySound(abracadabra.wav)=false / FILE_NOT_EXIST(5019)
PlaySound(request.wav)=true / ok
GetTickCount()-start=0 / ok
文件 "new.txt"
被找到了,因此函数返回了 true
,尽管它没有产生声音。对第二个不存在的文件的调用返回了 false
,并且 _LastError
中的错误代码为 5019(FILE_NOT_EXIST
)。最后,播放最后一个文件(假设它存在)在各方面都应该成功:函数将返回 true
,并且终端将播放音频。调用处理时间几乎为零(声音的持续时间无关紧要)。