
开发回放系统(第 72 部分):异常通信(一)
概述
在上一篇文章开发回放系统(第 71 部分):取得正确的时间(四)中 ,我展示了如何将另一篇文章中介绍的内容添加到回放/模拟器服务中。
具体来说,在文章开发回放系统(第 70 部分):取得正确的时间(三)中 ,我们使用测试服务来更好地了解如何处理订单簿事件。当焦点位于自定义交易品种上时就会发生这种情况。
最后两篇文章中的整个过程非常有趣。这主要是由于我们必须采取的方法来实现预期的结果。我相信你们中的许多人已经学习并理解了让 MetaTrader 5 使用订单簿的正确方法。我再次强调,我们正在讨论自定义交易品种,请不要忘记这一点。
有趣的是,只需添加订单簿,我们就可以允许鼠标指标使用 OnCalculate 函数,其中数据由 MetaTrader 5 放入数组中。这大大简化了流程,因为我们不再需要使用 iSpread 函数来获取柱形价差。
如果没有这两篇文章中提供的知识,当时间框架大于一分钟时,就不可能通过 OnCalculate 函数的参数获取数据。除此之外,通过 OnCalculate 参数获取数据没有任何问题。然而,一旦使用自定义订单簿事件,我们就能够直接从 OnCalculate 参数读取价差。
但在上篇文章的最后,我解释了一个需要解决的问题。如果不解决这个问题,我们将完全无法正常使用该应用程序。因此,对于刚刚阅读本文的人,让我们快速回顾一下问题是什么。
重述问题
我们需要解决的问题是,每当时间框架发生变化时,就会出现故障。虽然不是灾难性的,但很不方便。具体表现为:当我们回放/模拟器服务时,我们可以选择所需的时间框架。图表打开后,它将按照选定的时间框架运行。
但是,如果您使用 MetaTrader 5 更改时间框架,鼠标指标提供的信息将从“竞价活动”变为“市场关闭”。此外,控制指标将从图表中完全消失,从而阻止任何进一步的交互。
尽管这很不方便,但失败的发生是因为 MetaTrader 5 只是重新加载图表上的指标并立即调用 OnInit 函数。无论如何,MetaTrader 5 的运行完全符合其设计。正是我们试图以一种完全不同的方式使用它。因为我们要求它根据我们的目的以不同的方式运作。
那么,在这些情况下会发生什么?当触发此故障时,我们必须等待服务向控制指标触发自定义事件,以便使用新值重新初始化。问题是,这可能需要相当长的时间。此外,为了使服务真正触发允许控制指标重新初始化的自定义事件,服务必须处于播放模式。
如果服务未处于播放模式,则控制指标将永远不会收到初始化所需的自定义事件,从而使用户完全无法访问它。在这种情况下,用户必须停止服务并重新启动它,这相当麻烦。现在,假设服务处于播放模式,我们仍然必须等待自定义事件被触发。一旦触发该事件,控制指标将再次变得可以访问。此时,用户必须暂停回放/模拟器服务,然后再次按播放。这将刷新鼠标指标显示的信息,特别是柱形的剩余时间,它将再次正确显示。如果用户再次更改时间框架,整个问题将会重演。
在上一篇文章中,我比较详细地解释了这个问题。在这里,我只是简单地回顾一下上下文,特别是对于那些从本文开始本系列文章的人。无论如何,我在上一篇文章中给读者留下了一个小挑战。挑战就是想出一种方法来解决这个问题。尽管许多人认为编程只是编写计算机可以执行的代码的艺术,但编程远不止于此。编写代码是比较容易的部分。真正的挑战是弄清楚如何解决问题。事实上,编程是通过代码解决问题的艺术。
老实说,亲爱的读者,我不介意你是否能够解决这个问题。重要的是,你至少努力尝试过。因为解决问题和设计解决方案确实是一门艺术,而键入代码只是一种形式。那么,让我们看看我们将如何解决这个特殊的问题。
首个解决方案的尝试
如果您真正理解了这个问题,您立即想到的可能是以下解决方案:实现一种方法来检测图表时间框架何时发生变化。一旦发生这种情况,我们就会让指标恢复到从图表中删除之前的状态。这样,当 MetaTrader 5 将它们返回到图表时,控制和鼠标指标都会知道它们的最后状态,并可以从该点恢复。好主意,但这有一个缺点。无论您如何存储指标的最后一个已知状态,这些信息都不能直接与指标本身相关联。一旦调用 OnInit 函数,它就应该可供指标使用。否则,我们就会遇到问题。
有几种方法可以实现这一点,但所有这些方法都需要额外的实现和测试,以验证保存的状态是否可以安全使用。就我个人而言,我发现这个解决方案过于繁琐,因为仅仅验证存储的信息是否可用就需要进行大量的额外测试。
这实际上该如何实现呢?首先,让我们考虑一下我们需要存储哪些信息。对于控制指标,我们需要存储它是处于暂停模式还是播放模式。嗯,看来已经足够了。至于鼠标指标,它只需要返回以指示资产处于竞价模式。好吧,存储这些数据相当简单。您可以将其写入文件或全局终端变量。无论哪种方式,这部分都很容易实现。现在让我们考虑一下这会带来的问题。
对于控制指标,不会有太大麻烦。仅当回放/模拟器服务运行时才会添加到图表中。因此,任何一种解决方案(即文件或全局终端变量)都可以起作用。我们检查文件或变量是否存在。如果是,则使用它的值。但问题是:随着时间的推移,这些数据可能会与服务的当前状态不同步。我们必须确保服务在关闭后立即删除全局变量或文件。
换句话说,只是需要管理和担心更多的编程开销。现在,回到鼠标指标。如果您使用值“竞价模式”对其进行初始化,则在将其添加到真实资产图表时可能会出现问题。这是因为市场实际上可能已经关闭,而鼠标指标会错误地显示竞价模式。而且我还计划进一步增强鼠标指标,因此强制它以竞价模式启动只会使事情变得复杂。它引入了一层不必要的未来问题,我想不惜一切代价避免这些问题。
所以,总结一下:使用全局终端变量是完全不可能的。它将为指标引入两种不同的初始化路径。同样,使用文件也会带来同样的问题。这个解决方案根本不能满足我们的需求。我们需要另一种解决方案,它仍然与指标挂钩,但也允许我们测试图表本身发生了什么。
第二个解决方案的尝试
有人可能会建议让服务检查自定义交易品种的当前时间框架。事实上,这将是一个非常聪明的解决方案,因为它消除了为指标存储任何状态信息的需要。请记住:回放/模拟器服务始终知道执行期间任何给定时间的指标状态。然而,这个解决方案也存在一个小问题:服务无法直接获取当前图表时间框架。
但在我们放弃这个选择之前,让我们想一想。如果我们能以某种方式检测到时间框架何时发生变化,我们所需要做的就是让服务调用头文件 C_Replay.mqh 中的 UpdateIndicatorControl 方法,问题就会得到解决。这几乎完全正确。我们只需要对 UpdateIndicatorControl 过程进行小幅调整,但这比为指标创建完全单独的初始化路径要简单得多。因此,我们需要该服务以某种方式检测图表时间框架是否已更改。之后,它可以使用我们已经实现的机制来正确地重新初始化指标。即使在服务中需要一些微小的更改来使用 UpdateIndicatorControl,这些调整也会比修改指标本身来处理这种情况要小得多,也更易于管理。简而言之,这个替代方案绝对值得考虑。
第三个解决方案的尝试
我们尝试一些更非传统的东西怎么样?让我们将前面的两种方法结合到一个解决方案中,但不使用全局终端变量或文件。相反,我们将使用数据缓冲区来传输我们需要的信息 —— 在这个方案中,是时间框架。通过这种方式,该服务可以监控正在发生的事情。因此,每当时间框架发生变化时,服务就会立即检测到。当发生这种情况时,服务可以通知指标需要自行更新。
我们真正的挑战就是将时间框架信息写入数据缓冲区并从服务内部读取该信息。当用户改变时间框架时,整个应用程序应该从服务接收自定义事件。该事件将确保鼠标指标接收到正确的值以显示给用户,并且控制指标也将相应更新。这将防止它无法用于控制播放和暂停模式。
开始实施测试
与一些人的想法相反,我们程序员在实际应用之前总是先进行测试。我们从不从修改已经处于高级开发阶段的程序或应用程序开始。每当需要实现新功能时,我们首先创建一个测试版本。这个版本要尽可能简单。我们使用它来微调实现,并研究最终过程应该如何实际工作。这使我们不必删除或重写后来被证明不适合最终版本的代码段。
因此,让我们创建一个非常简单的指标来测试时间框架变化的问题。该指标的完整源代码如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. #property indicator_chart_window 05. #property indicator_plots 0 06. //+------------------------------------------------------------------+ 07. int OnInit() 08. { 09. Print(_Period); 10. 11. return INIT_SUCCEEDED; 12. } 13. //+------------------------------------------------------------------+ 14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 15. { 16. return rates_total; 17. } 18. //+------------------------------------------------------------------+
测试指标的源代码
这个小代码是做什么的?简而言之,它所做的一切都发生在第 9 行。也就是说,它将存储在内部变量 _Period 中的值打印到终端。其执行示例如下图所示:
执行结果
注意这里有一些有趣的东西。在上图的 SOURCE 列中,我们看到了指标的名称,但对我们来说真正重要的是括号内的内容。更确切地说,是逗号后的值。如您所见,我多次更改了时间框架,并且每次更改后,消息列中都会打印不同的值。这看起来是显而易见的。但现在,请忽略数值不同的事实,不要试图从中得出任何特定的逻辑。我们真正感兴趣的是:_Period 实际上可以容纳的最高值是多少?嗯,MetaTrader 5 允许我们使用每月的时间框架。在图像中,您可以在倒数第二行看到这个值:最高值是 49153。为什么了解这个值很重要?因为我们需要一种方法让服务检测此值何时更改。但我们不能使用任意位长度来实现这一点,我们需要确保使用的位数尽可能小。你很快就会明白原因的。
让我们对上面的代码做一个小修改,就像下面这样:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. #property indicator_chart_window 05. #property indicator_plots 0 06. //+------------------------------------------------------------------+ 07. int OnInit() 08. { 09. Print(_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))); 10. 11. return INIT_SUCCEEDED; 12. } 13. //+------------------------------------------------------------------+ 14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 15. { 16. return rates_total; 17. } 18. //+------------------------------------------------------------------+
测试指标的源代码
在我们分析第 9 行现在在做什么之前,我们先来看看下图所示的执行结果。
执行结果
现在,请注意以下这一点。以前,我们需要 16 位来存储允许我们检测时间框架何时发生变化的值。但现在,观察最高可能值:96。哇!但我们真的能做到这一点吗?是的,我们可以,而且我们会的。因为显然 MetaTrader 5 已经合并了这些时间框架的值。因此,我们可以使用这个简化的值安全地修改操作。
现在,回到代码,请注意缩减过程非常简单。它可以在一行中仅使用常量执行。这对我们的目的来说已经足够了,因为现在我们只需要 7 位来传输值。所以我们需要考虑一下如何实现这一点。请记住:我们感兴趣的不是时间框架本身,而是它是否发生了变化。
开始将信息传输至服务
为了将时间框架信息传输到服务,以便它能够检测到何时发生变化,我们需要使用指标缓冲区,因为我们没有其他合适的机制。事实上,还有其他选择。但我是有意使用指标缓冲区来完成此任务的。这种方法使所有内容对用户不可见,同时还在传输过程中提供一定程度的信息封装。
但在此之前,让我们将我们测试的最新代码集成到我们的系统中。它将被添加为定义。因此,头文件 Defines.mqh 中的代码如下:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. //+------------------------------------------------------------------+ 18. union uCast_Double 19. { 20. double dValue; 21. long _long; // 1 Information 22. datetime _datetime; // 1 Information 23. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 24. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 25. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 26. }; 27. //+------------------------------------------------------------------+ 28. enum EnumEvents { 29. evHideMouse, //Hide mouse price line 30. evShowMouse, //Show mouse price line 31. evHideBarTime, //Hide bar time 32. evShowBarTime, //Show bar time 33. evHideDailyVar, //Hide daily variation 34. evShowDailyVar, //Show daily variation 35. evHidePriceVar, //Hide instantaneous variation 36. evShowPriceVar, //Show instantaneous variation 37. evCtrlReplayInit //Initialize replay control 38. }; 39. //+------------------------------------------------------------------+
Defines.mqh 文件源代码
好吧,现在看第 16 行,这是上一节描述的测试中使用的代码。非常好,这保证了我们使用的代码已经过测试并且运行正常。现在,更复杂的部分来了,至少如果你从一开始就没有关注过这个系列文章,或者刚刚加入。
指标的数据缓冲区始终为双精度类型,因此占用 8 个字节的内存。由于我在本系列之前的文章中解释过的原因,我们从指标传输到服务的任何信息都必须在这 8 个字节内。我们无法承受这种信息交换需要超过 8 个字节,所有内容都必须塞进这 8 个字节中。让我们仔细考虑一下这个问题。鼠标指标可以在实时市场中使用。换句话说,它可以对从实际交易服务器接收数据的代码进行操作。但我们在此解决的问题仅在使用回放/模拟服务时发生。到目前为止,回放/模拟模式严格要求的唯一指标是控制指标。因此,关注控制指标的数据缓冲区当前的结构是完全有意义的。您可以在下面看到:
QWORD 是一个来自汇编语言的术语,这表明我们正在处理一个 64 位值。最右边的字节是字节 0,最左边的字节是字节 7。就控制指标而言,它目前使用 4 个字节。为了更好地理解这一点,请看一下头文件 C_Controls.mqh 中的代码摘录,如下所示:
168. //+------------------------------------------------------------------+ 169. void SetBuffer(const int rates_total, double &Buff[]) 170. { 171. uCast_Double info; 172. 173. info._16b[eCtrlPosition] = m_Slider.Minimal; 174. info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause)); 175. if (rates_total > 0) 176. Buff[rates_total - 1] = info.dValue; 177. } 178. //+------------------------------------------------------------------+
摘录自文件 C_Controls.mqh
在第 171 行,我们声明了之前在上面显示的 Defines.mqh 文件中定义的联合。请注意,我们使用两个数组值,每个数组值使用 16 位。这样一来,一共就有 32 位。尽管实际上并非所有位都被使用,但为了简单起见,我们假设它们都被使用了。因此,8 个可用字节中的 4 个已被使用。但是,由于我们的建模系统允许我们使用单个字节对时间框架进行编码,因此我们现在将使用 8 个可用字节中的 5 个字节。但最关键的部分是确定我们应该对这些新数据使用哪个索引。这种细节往往会让许多编程初学者感到困惑,因为他们经常忘记,当使用联合时,一些字节已经被有用的信息占用。如果意外使用了错误的索引,则有覆盖有效数据的风险,从而导致值损坏。因此,让我们再次参考 QWORD 图像。从索引 0 开始,已经分配了 4 个字节。这意味着索引 0 到 3 正在使用中,不得重新用于任何其他目的。因此,新数据的第一个可用索引是索引 4。
因此,我们更新 Defines.mqh 头文件如下:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evHideMouse, //Hide mouse price line 31. evShowMouse, //Show mouse price line 32. evHideBarTime, //Hide bar time 33. evShowBarTime, //Show bar time 34. evHideDailyVar, //Hide daily variation 35. evShowDailyVar, //Show daily variation 36. evHidePriceVar, //Hide instantaneous variation 37. evShowPriceVar, //Show instantaneous variation 38. evCtrlReplayInit //Initialize replay control 39. }; 40. //+------------------------------------------------------------------+
Defines.mqh 文件源代码
在第 17 行,我们有一个新的定义,可以让我们安全地引用正确的索引。所以,现在我们有了我们需要的基础。然而,由于这项任务比最初看起来要复杂一些,我们首先来看看 C_Controls.mqh 头文件的实际结构。完整代码如下:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\C_DrawImage.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_PathBMP "Images\\Market Replay\\Control\\" 008. #define def_ButtonPlay def_PathBMP + "Play.bmp" 009. #define def_ButtonPause def_PathBMP + "Pause.bmp" 010. #define def_ButtonCtrl def_PathBMP + "Ctrl.bmp" 011. #define def_ButtonCtrlBlock def_PathBMP + "Ctrl_Block.bmp" 012. #define def_ButtonPin def_PathBMP + "Pin.bmp" 013. #resource "\\" + def_ButtonPlay 014. #resource "\\" + def_ButtonPause 015. #resource "\\" + def_ButtonCtrl 016. #resource "\\" + def_ButtonCtrlBlock 017. #resource "\\" + def_ButtonPin 018. //+------------------------------------------------------------------+ 019. #define def_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A)) 020. #define def_PosXObjects 120 021. //+------------------------------------------------------------------+ 022. #define def_SizeButtons 32 023. #define def_ColorFilter 0xFF00FF 024. //+------------------------------------------------------------------+ 025. #include "..\Auxiliar\C_Mouse.mqh" 026. //+------------------------------------------------------------------+ 027. class C_Controls : private C_Terminal 028. { 029. protected: 030. private : 031. //+------------------------------------------------------------------+ 032. enum eMatrixControl {eCtrlPosition, eCtrlStatus}; 033. enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)}; 034. //+------------------------------------------------------------------+ 035. struct st_00 036. { 037. string szBarSlider, 038. szBarSliderBlock; 039. ushort Minimal; 040. }m_Slider; 041. struct st_01 042. { 043. C_DrawImage *Btn; 044. bool state; 045. short x, y, w, h; 046. }m_Section[eObjectControl::eNull]; 047. C_Mouse *m_MousePtr; 048. //+------------------------------------------------------------------+ 049. inline void CreteBarSlider(short x, short size) 050. { 051. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 052. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 053. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 054. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 055. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 060. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 064. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 065. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 066. } 067. //+------------------------------------------------------------------+ 068. void SetPlay(bool state) 069. { 070. if (m_Section[ePlay].Btn == NULL) 071. m_Section[ePlay].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay); 072. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0, state ? "Press to Pause" : "Press to Start"); 073. if (!state) CreateCtrlSlider(); 074. } 075. //+------------------------------------------------------------------+ 076. void CreateCtrlSlider(void) 077. { 078. if (m_Section[ePin].Btn != NULL) return; 079. CreteBarSlider(77, 436); 080. m_Section[eLeft].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock); 081. m_Section[eRight].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock, true); 082. m_Section[ePin].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin, NULL); 083. PositionPinSlider(m_Slider.Minimal); 084. } 085. //+------------------------------------------------------------------+ 086. inline void RemoveCtrlSlider(void) 087. { 088. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 089. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 090. { 091. delete m_Section[c0].Btn; 092. m_Section[c0].Btn = NULL; 093. } 094. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B")); 095. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 096. } 097. //+------------------------------------------------------------------+ 098. inline void PositionPinSlider(ushort p) 099. { 100. int iL, iR; 101. string szMsg; 102. 103. m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 104. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 105. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 106. m_Section[ePin].x += def_PosXObjects; 107. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 108. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) if (m_Section[c0].Btn != NULL) 109. { 110. switch (c0) 111. { 112. case eLeft : szMsg = "Previous Position"; break; 113. case eRight : szMsg = "Next Position"; break; 114. case ePin : szMsg = "Go To: " + IntegerToString(p); break; 115. default : szMsg = "\n"; 116. } 117. m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)), szMsg); 118. } 119. 120. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 121. } 122. //+------------------------------------------------------------------+ 123. inline eObjectControl CheckPositionMouseClick(short &x, short &y) 124. { 125. C_Mouse::st_Mouse InfoMouse; 126. 127. InfoMouse = (*m_MousePtr).GetInfoMouse(); 128. x = (short) InfoMouse.Position.X_Graphics; 129. y = (short) InfoMouse.Position.Y_Graphics; 130. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 131. { 132. if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y)) 133. return c0; 134. } 135. 136. return eNull; 137. } 138. //+------------------------------------------------------------------+ 139. public : 140. //+------------------------------------------------------------------+ 141. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 142. :C_Terminal(Arg0), 143. m_MousePtr(MousePtr) 144. { 145. if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 146. if (_LastError >= ERR_USER_ERROR_FIRST) return; 147. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 148. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 149. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 150. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 151. { 152. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 153. m_Section[c0].y = 25; 154. m_Section[c0].Btn = NULL; 155. } 156. m_Section[ePlay].x = def_PosXObjects; 157. m_Section[eLeft].x = m_Section[ePlay].x + 47; 158. m_Section[eRight].x = m_Section[ePlay].x + 511; 159. m_Slider.Minimal = eTriState; 160. } 161. //+------------------------------------------------------------------+ 162. ~C_Controls() 163. { 164. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 165. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 166. delete m_MousePtr; 167. } 168. //+------------------------------------------------------------------+ 169. void SetBuffer(const int rates_total, double &Buff[]) 170. { 171. uCast_Double info; 172. 173. info._16b[eCtrlPosition] = m_Slider.Minimal; 174. info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause)); 175. info._8b[def_IndexTimeFrame] = (uchar) def_IndicatorTimeFrame; 176. if (rates_total > 0) 177. Buff[rates_total - 1] = info.dValue; 178. } 179. //+------------------------------------------------------------------+ 180. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 181. { 182. short x, y; 183. static ushort iPinPosX = 0; 184. static short six = -1, sps; 185. uCast_Double info; 186. 187. switch (id) 188. { 189. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 190. info.dValue = dparam; 191. if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break; 192. iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition])); 193. SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay); 194. break; 195. case CHARTEVENT_OBJECT_DELETE: 196. if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName("")) 197. { 198. if (sparam == def_ObjectCtrlName(ePlay)) 199. { 200. delete m_Section[ePlay].Btn; 201. m_Section[ePlay].Btn = NULL; 202. SetPlay(m_Section[ePlay].state); 203. }else 204. { 205. RemoveCtrlSlider(); 206. CreateCtrlSlider(); 207. } 208. } 209. break; 210. case CHARTEVENT_MOUSE_MOVE: 211. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 212. { 213. case ePlay: 214. SetPlay(!m_Section[ePlay].state); 215. if (m_Section[ePlay].state) 216. { 217. RemoveCtrlSlider(); 218. m_Slider.Minimal = iPinPosX; 219. }else CreateCtrlSlider(); 220. break; 221. case eLeft: 222. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 223. break; 224. case eRight: 225. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 226. break; 227. case ePin: 228. if (six == -1) 229. { 230. six = x; 231. sps = (short)iPinPosX; 232. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 233. } 234. iPinPosX = sps + x - six; 235. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 236. break; 237. }else if (six > 0) 238. { 239. six = -1; 240. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 241. } 242. break; 243. } 244. ChartRedraw(GetInfoTerminal().ID); 245. } 246. //+------------------------------------------------------------------+ 247. }; 248. //+------------------------------------------------------------------+ 249. #undef def_PosXObjects 250. #undef def_ButtonPlay 251. #undef def_ButtonPause 252. #undef def_ButtonCtrl 253. #undef def_ButtonCtrlBlock 254. #undef def_ButtonPin 255. #undef def_PathBMP 256. //+------------------------------------------------------------------+
C_Controls.mqh 文件的源代码
由于在前面的文章中进行了几处更改,您有必要理解并对 C_Controls.mqh 头文件进行至少一些修改,以及我现在将展示的其他一些更改。请看第 10 行:这一行引用了图像 Ctrl.bmp。但这是哪张图片?这实际上与之前称为 LEFT.BMP 的图像相同。同样,第 11 行引用了以前称为 LEFT_BLOCK.BMP 的内容。从现在开始,我们将为这些图像使用不同的名称。无论如何,我已经将这些图片包含在附件中,这样如果你有任何疑问,就可以很容易地将它们放在你的项目中。
还有另一个需要强调的变化 —— 它位于 C_Controls 类的构造函数中。看第 146 行,现在,只有当遇到的错误是我们作为开发人员在代码中明确指出的错误时,构造函数才会提前退出。其他类型的错误将被忽略,至少目前是这样。
对我们来说真正重要的部分出现在第 175 行。在这里,我们定义了将写入指标缓冲区的位置和内容,如本文所述。但是,由于使用这些信息比简单地将其写入缓冲区要复杂一些,我暂时不会显示完整的代码。这是因为对于那些仍在学习如何为 MetaTrader 5 编程的人来说,解释事情是如何运作的很重要。所以,亲爱的读者,如果你已经更有经验了,我很抱歉现在没有提供完整的代码。
最后的探讨
由于我仍需要解释将对服务代码和指示器代码进行的一些额外更改,因此我将在本文中不提供完整的代码。我向那些已经拥有丰富的 MQL5 经验的人表示歉意。然而,在我发表这些文章的整个过程中,我注意到许多读者仍然刚刚开始学习MQL5 —— 渴望学习的有抱负的程序员。所以我试着把事情解释清楚。尽管如此,您仍会在本文中找到两张图片。这些是 C_Controls.mqh 头文件引用的图像。
如果你打算继续跟进,不要忘记将此调整应用于你的项目。
在下一篇文章中,我们将介绍以下任务:介绍并解释对控制指标源代码所做的修改。我们已经在头文件中看到了变化,但我们仍然需要修改指标代码本身。此外,我将展示如何调整 C_Replay.mqh 头文件的源代码。如果没有这些更改,服务将无法仅通过 “查看” 图表上的指标来确定时间框架是否已更改。
那么,下次再见。别忘了仔细研究这些内容。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12362
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。


