
开发回放系统(第 46 部分):Chart Trade 项目(五)
概述
在上一篇文章 "开发回放系统(第 45 部分):Chart Trade 项目 (四)"中,我介绍了如何启动 Chart Trade 指标的功能。您可能已经注意到,在那篇文章的附件中,有很多文件需要传输。犯一些错误的风险,比如忘记添加文件或意外删除重要内容,使得这种方法不太有吸引力。
我知道很多人都使用这种分发和存储形式,但还有一种更合适的方式。至少在分发和存储可执行文件时是这样。
这里将介绍的方法非常有用,因为您可以将 MetaTrader 5 本身用作优秀的助手,也可以使用 MQL5。此外,它并不难理解。我将在本文中解释的内容可以帮助您避免正确保存可执行文件及其正确操作所需的一切问题。
理解思路
在开始之前,我们需要明白,在实现我们的计划时会遇到一些问题和限制。造成这些限制的原因是,MQL5 语言和 MetaTrader 5 平台都不适合以某些方式使用。
该语言和平台都是设计用作市场可视化工具的。我们希望或试图让它们以不同的方式工作,并不一定意味着它们不能做什么。
这是存在的第一个限制,您应该了解。接下来要注意的是,您几乎可以在可执行文件中添加任何内容。请注意,我说的是"几乎"。理论上讲,只要知道如何在可执行文件中添加或包含内容,几乎就可以添加任何东西。当然,在可执行文件中包含信息的方法是将其作为可执行文件本身的内部资源。
我注意到很多人都添加了图像、声音和类似内容,但我们能否添加一个模板文件,使其成为可执行文件的一部分?阅读文档后,你会发现这是不可能的。事实上,如果您尝试这样做,编译器在尝试编译程序时会返回错误信息。
#define def_IDE_RAD "Files\\Chart Trade\\IDE_RAD.tpl" #resource "\\" + def_IDE_RAD;
如果您尝试编译包含上述代码的可执行文件,您会发现编译器会产生错误,因为该定义引用了一个模板。对于将在 MetaTrader 5 平台上使用的可执行文件来说,这通常是禁止的。
我必须承认,这类限制令人相当不快,但它们很容易规避,尽管并非没有代价。有一些限制和问题需要解决。事实上,你可以在使用 MetaEditor 编译可执行文件中添加一个模板,我会告诉你如何操作。但主要问题不是如何在可执行文件中包含模板,而是如何使用这个模板。
我为什么要解释如何做到这一点,常规使用不是更简单吗?用我们习惯的方式来使用所有东西确实要容易得多。不过,正如我在本文开头提到的,将所有内容都包含在可执行文件中要更有实际意义。试想一下,如果要在 MetaTrader 5 中运行特定功能,就必须打包所有必要文件,然后将它们传输到用于交易的机器上。如果您忘记了什么,就必须重新寻找丢失的文件(可能还有多个)。您必须将它们放在目录树中的正确位置,以便应用程序可以访问它们。
在我看来,这很累人。许多人使用这样的方式开发和使用 MetaTrader 5。我想告诉这些人,这至少可以说是一个很大的错误。不要,切记不要将同一台机器或 MetaTrader5 安装用于交易和开发。这样做的话,您最终可能会在应用程序中造成某种故障或漏洞,如果您使用它,可能会影响自动 EA 的正确操作。
在开发阶段,我见过很多奇怪的事情。有时,一个程序或应用程序会轻易地开始与图表上运行的另一个应用程序交互,导致事情的行为非常奇怪。这可能会导致错误或一些无法解释的事情。有些人可能会说,这是因为我一直在测试东西。这倒是真的,有时,这可能确实是测试造成的,但其他时候就说不通了。
这就是为什么在使用 MetaTrader 5 时需要安装两套系统:一套用于开发,另一套专门用于市场工作。当然,这里指的是开发人员。在任何其他情况下,都没有必要这样做。
我想了很多,是否应该教你如何做某些事情。大多数人无法理解 MQL5 的基础知识或 MetaTrader 5 的工作原理,所以想象一下,如果他们试图理解我要向你展示的内容,会发生什么。有些人会认为我疯了。但事实上,您可以比较本文和上一篇文章中的附件,包括功能和可移植性。功能是 相同的。就可移植性而言,我认为这个方案要好得多。您只需发送一个文件,无需担心任何目录结构。
重要提示:虽然我说不用担心目录结构,但这只适用于将可执行文件存储在它所在的特定目录中。将其从一个目录移动到另一个目录,甚至重新命名,都会导致系统工作错误。
那么,让我们看看这是如何实现的,以及如何在自己的代码中使用它。
资源、资源和资源
要使用这个模型,我们需要对代码进行一些修改。尽管如此,该指标的源代码保持不变,因此不会出现在本文中。C_ChartFloatingRAD 类中的代码发生了一些细微的变化,但由于这些变化并没有影响到整个类,因此我将只关注实际发生变化的部分。
下面是这一部分的内容和解释。
068. inline void AdjustTemplate(const bool bFirst = false) 069. { 070. #define macro_AddAdjust(A) { \ 071. (*Template).Add(A, "size_x", NULL); \ 072. (*Template).Add(A, "size_y", NULL); \ 073. (*Template).Add(A, "pos_x", NULL); \ 074. (*Template).Add(A, "pos_y", NULL); \ 075. } 076. #define macro_GetAdjust(A) { \ 077. m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x")); \ 078. m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y")); \ 079. m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \ 080. m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \ 081. } 082. #define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade 083. 084. C_AdjustTemplate *Template; 085. 086. if (bFirst) 087. { 088. Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl", true); 089. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0)); 090. AdjustEditabled(Template, true); 091. }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate); 092. m_Info.Leverage = (m_Info.Leverage <= 0 ? 1 : m_Info.Leverage); 093. m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.Leverage)); 094. m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.Leverage)); 095. (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol); 096. (*Template).Add("MSG_LEVERAGE_VALUE", "descr", IntegerToString(m_Info.Leverage)); 097. (*Template).Add("MSG_TAKE_VALUE", "descr", DoubleToString(m_Info.FinanceTake, 2)); 098. (*Template).Add("MSG_STOP_VALUE", "descr", DoubleToString(m_Info.FinanceStop, 2)); 099. (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0")); 100. (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0")); 101. (*Template).Execute(); 102. if (bFirst) 103. { 104. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0); 105. m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x; 106. AdjustEditabled(Template, false); 107. }; 108. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6)); 109. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx)); 110. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny)); 111. 112. delete Template; 113. 114. ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate); 115. ChartRedraw(m_Info.WinHandle); 116. 117. #undef macro_PointsToFinance 118. #undef macro_GetAdjust 119. #undef macro_AddAdjust 120. }
C_ChartFloatingRAD 类代码片段
你可能没有注意到这里的任何差异,但它们确实存在。在第 88 行可以看到这种差异,尽管它并不那么明显。因此,如果将上一篇文章中的 C_ChartFloatingRAD 类代码,完全按照本部分所示修改第 88 行,就能得到我们要使用的新数据模型。
您可以注意到,与原始代码不同,这里我们只定义了一个字符串。为什么呢?原因是我们将不再像以前那样使用模板。我们将使用一种资源。换句话说,模板将嵌入到指标的可执行文件中。因此,我们不再需要传输大量数据。
然而,这一变化很快就会引发另一个问题。如果我们不能将模板作为可执行文件的一部分进行编译,即将其作为资源包含在内,我们该如何将模板用作资源?事实上,我们可以在可执行文件中包含任何内容。问题是我们到底应该怎么做。
为了理解这一点,让我们看看 C_AdjustTemplate 类的代码,以了解为什么对 C_ChartFloatingRAD 类的第 88 行进行了更改。C_AdjustTemplate 类的完整代码如下所示。由于对它进行了更多的更改,虽然没有那么重要,但了解实际发生的事情会很有趣。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "../Auxiliar/C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_PATH_BTN "Images\\Market Replay\\Chart Trade" 007. #define def_BTN_BUY def_PATH_BTN + "\\BUY.bmp" 008. #define def_BTN_SELL def_PATH_BTN + "\\SELL.bmp" 009. #define def_BTN_DT def_PATH_BTN + "\\DT.bmp" 010. #define def_BTN_SW def_PATH_BTN + "\\SW.bmp" 011. #define def_BTN_MAX def_PATH_BTN + "\\MAX.bmp" 012. #define def_BTN_MIN def_PATH_BTN + "\\MIN.bmp" 013. #define def_IDE_RAD "Files\\Chart Trade\\IDE_RAD.tpl" 014. //+------------------------------------------------------------------+ 015. #resource "\\" + def_BTN_BUY 016. #resource "\\" + def_BTN_SELL 017. #resource "\\" + def_BTN_DT 018. #resource "\\" + def_BTN_SW 019. #resource "\\" + def_BTN_MAX 020. #resource "\\" + def_BTN_MIN 021. #resource "\\" + def_IDE_RAD as string IdeRad; 022. //+------------------------------------------------------------------+ 023. class C_AdjustTemplate 024. { 025. private : 026. string m_szName[], 027. m_szFind[], 028. m_szReplace[], 029. m_szFileName; 030. int m_maxIndex, 031. m_FileIn, 032. m_FileOut; 033. bool m_bFirst; 034. //+------------------------------------------------------------------+ 035. public : 036. //+------------------------------------------------------------------+ 037. C_AdjustTemplate(const string szFile, const bool bFirst = false) 038. :m_maxIndex(0), 039. m_szFileName(szFile), 040. m_bFirst(bFirst), 041. m_FileIn(INVALID_HANDLE), 042. m_FileOut(INVALID_HANDLE) 043. { 044. ResetLastError(); 045. if (m_bFirst) 046. { 047. int handle = FileOpen(m_szFileName, FILE_TXT | FILE_WRITE); 048. FileWriteString(handle, IdeRad); 049. FileClose(handle); 050. } 051. if ((m_FileIn = FileOpen(m_szFileName, FILE_TXT | FILE_READ)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess); 052. if ((m_FileOut = FileOpen(m_szFileName + "_T", FILE_TXT | FILE_WRITE)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess); 053. } 054. //+------------------------------------------------------------------+ 055. ~C_AdjustTemplate() 056. { 057. FileClose(m_FileIn); 058. FileClose(m_FileOut); 059. FileMove(m_szFileName + "_T", 0, m_szFileName, FILE_REWRITE); 060. ArrayResize(m_szName, 0); 061. ArrayResize(m_szFind, 0); 062. ArrayResize(m_szReplace, 0); 063. } 064. //+------------------------------------------------------------------+ 065. void Add(const string szName, const string szFind, const string szReplace) 066. { 067. m_maxIndex++; 068. ArrayResize(m_szName, m_maxIndex); 069. ArrayResize(m_szFind, m_maxIndex); 070. ArrayResize(m_szReplace, m_maxIndex); 071. m_szName[m_maxIndex - 1] = szName; 072. m_szFind[m_maxIndex - 1] = szFind; 073. m_szReplace[m_maxIndex - 1] = szReplace; 074. } 075. //+------------------------------------------------------------------+ 076. string Get(const string szName, const string szFind) 077. { 078. for (int c0 = 0; c0 < m_maxIndex; c0++) if ((m_szName[c0] == szName) && (m_szFind[c0] == szFind)) return m_szReplace[c0]; 079. 080. return NULL; 081. } 082. //+------------------------------------------------------------------+ 083. void Execute(void) 084. { 085. string sz0, tmp, res[]; 086. int count0 = 0, i0; 087. 088. if ((m_FileIn != INVALID_HANDLE) && (m_FileOut != INVALID_HANDLE)) while ((!FileIsEnding(m_FileIn)) && (_LastError == ERR_SUCCESS)) 089. { 090. sz0 = FileReadString(m_FileIn); 091. if (sz0 == "<object>") count0 = 1; 092. if (sz0 == "</object>") count0 = 0; 093. if (count0 > 0) if (StringSplit(sz0, '=', res) > 1) 094. { 095. if ((m_bFirst) && ((res[0] == "bmpfile_on") || (res[0] == "bmpfile_off"))) 096. sz0 = res[0] + "=\\Indicators\\Replay\\Chart Trade.ex5::" + def_PATH_BTN + res[1]; 097. i0 = (count0 == 1 ? 0 : i0); 098. for (int c0 = 0; (c0 < m_maxIndex) && (count0 == 1); i0 = c0, c0++) count0 = (res[1] == (tmp = m_szName[c0]) ? 2 : count0); 099. for (int c0 = i0; (c0 < m_maxIndex) && (count0 == 2); c0++) if ((res[0] == m_szFind[c0]) && (tmp == m_szName[c0])) 100. { 101. if (StringLen(m_szReplace[c0])) sz0 = m_szFind[c0] + "=" + m_szReplace[c0]; 102. else m_szReplace[c0] = res[1]; 103. } 104. } 105. FileWriteString(m_FileOut, sz0 + "\r\n"); 106. }; 107. } 108. //+------------------------------------------------------------------+ 109. }; 110. //+------------------------------------------------------------------+
C_AdjustTemplate 类源代码
对于初学者来说,这些代码可能看起来很奇怪。虽然代码非常繁琐,但它让我们不必像前一篇文章那样到处移植文件。在这里,我们将所有内容都包含在可执行文件中。但这当然不是免费的,任何事情都有其代价。主要的困难是,我们无法在编译后更改可执行文件的目录或名称。我们可以这样做,但这需要做其他事情,我现在不会解释整个过程。
请仔细阅读第 6 行至第 13 行中的定义。这里的一切对我们来说基本上已经很熟悉了。让我们仔细看第 13 行。这是一个模板,与我们之前使用的模板相同。现在,它不再是一个单独的文件,而是可执行文件的一部分。下面介绍如何做到这一点。
在第 15 行至第 20 行,我们像往常一样进行定义。当我们想要添加图像或声音时,就需要这样做。但在第 21 行,我们有了一些不同的东西,,这在代码中真的很罕见。这是因为在 MQL5 编程中,当我们要在可执行文件中包含资源时,通常不会使用别名。您可以通过查看文档中的资源来了解这里的思路。但除此之外,我们还需要理清其他一些细节。只有这样,你才能理解一切。
别名在某些语言中非常常见。例如,在 Excel 中经常使用的 Visual Basic 或 VBA(Visual Basic for Applications)中,这些别名用于以略有不同的方式访问事物。通常,我们访问资源时会使用"::",这是一个作用域解析器。当我们使用它来访问资源时,我们对资源定义名称进行操作。这可能看起来很复杂,但实际上要简单得多。要理解这一点,请看下面的代码:
01. #define def_BTN_BUY "Images\\Market Replay\\Chart Trade\\BUY.bmp" 02. #define def_BTN_SELL "Images\\Market Replay\\Chart Trade\\SELL.bmp" 03. #resource "\\" + def_BTN_BUY 04. #resource "\\" + def_BTN_SELL 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. long id; 09. string sz; 10. 11. ObjectCreate(id = ChartID(), sz = IntegerToString(ObjectsTotal(id)), OBJ_BITMAP_LABEL, 0, 0, 0); 12. ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, "::" + def_BTN_BUY); 13. ResourceSave("::" + def_BTN_SELL, "BTN_SELL.bmp"); 14. ObjectSetString(id, sz, OBJPROP_BMPFILE, 1, "\\Files\\BTN_SELL.bmp"); 15. 16. return INIT_SUCCEEDED; 17. } 18. //+------------------------------------------------------------------+
片段 01 - 使用示例
在这个片段 01 中,我们可以看到并更好地理解在资源使用的第一个层面上,一切是如何运作的。现在就忘掉 C_AdjustTemplate 类吧。让我们首先学习如何正确使用资源,以便更容易移植我们已经编译的代码。
在第 1 和第 2 行,我们定义了两个字符串。由于第 3 行和第 4 行,这些字符串指向的内容将由编译器编译并内置到可执行文件中。到目前为止,我们没有什么复杂的事情,我们按照通常的方式工作。
在第 11 行,我们表示要创建一个对象。在本例中,它是一个位图。同样,也没什么特别的。但现在到了第一阶段,我们将使用可执行文件中嵌入的资源。请看第 12 行,我们使用 "::" 来表示我们将使用一个资源。在这种情况下,它就是可执行文件中的资源。我们可以转向另一个程序,但为了简单起见,让我们先处理这个更简单的概念。通过阅读第 12 行,编译器将理解以下内容:
12. ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, "::Images\\Market Replay\\Chart Trade\\BUY.bmp");
但是,如果您查看对象中文本字符串的内容,您将不会看到上面显示的内容,而是看到其他内容。现在,让我们回到基础知识上来,因为了解事物的本质是必要的。
所以,第 12 行很容易理解,但以下几行呢?差异就是从这里开始的。你通常不会在代码中看到这些东西,但了解它们对于真正理解 Chart Trade 指标很重要。
第 13 行将获取指定的资源,并将其以指定的名称保存在指定的位置。编译器会对第 13 行进行如下处理:
13. ResourceSave("::Images\\Market Replay\\Chart Trade\\SELL.bmp", "\\Files\\BTN_SELL.bmp");
同样,我在这里使用的是最基本的概念。这次的情况有点复杂。请注意 ResourceSave 调用中的以下内容:可执行文件中的可用资源将作为普通文件保存。这基本上等同于使用 FileMove 函数。因此,第 13 行可以理解为:
FileMove("::Images\\Market Replay\\Chart Trade\\SELL.bmp", 0, "\\Files\\BTN_SELL.bmp", FILE_REWRITE);
实际运行中就是这样。
在第 14 行中,我们不再访问可执行文件的内部资源,而是间接使用该资源。这是因为我们在第 13 行将它保存到了一个文件中,并且现在在第 14 行中指向它。请注意以下内容,与第 12 行不同,在第 14 行中,我们实际使用的是磁盘上的一个物理文件。它将一直存在,直到被移除或更改。
第 13 和 14 行所示的方法在 MQL5 程序中并不常见。我们通常在可执行文件中运行资源,并在其中直接使用。我们通常是这样做的。不过,正如我前面提到的,这里的情况要复杂一些。但同时也更有趣。实际上,您并不需要在每个可执行文件中都放入资源,这将使标准化更加困难。我们可以使用编程中一种非常常见的技术:使用资源文件。通常情况下,在 Windows 系统中,它位于 DLL 中。这样就可以实现某种标准化。
类似的事情也可以在 MQL5 中完成。但请记住,某些类型的代码需要标准化。否则,您将生成大量无用的数据。
那么,如何在 MQL5 中做到这一点呢?其实很简单。只需在使用资源时指定文件名。还记得我说过事情要复杂得多吗?事情就是这样,可执行文件的名称被忽略。因此,假设您有一个名为 ICONS.LIB 的可执行文件,其中包含与片段 01 相同的图像,并且该可执行文件 ICONS.LIB 位于 "Indicators" 文件夹的根目录下。要使用 ICONS.LIB 实现同样的功能,我们需要以下代码:
01. //+------------------------------------------------------------------+ 02. #define def_BTN_BUY "Images\\Market Replay\\Chart Trade\\BUY.bmp" 03. #define def_BTN_SELL "Images\\Market Replay\\Chart Trade\\SELL.bmp" 04. #define def_LIB "\\Indicators\\Icons.Lib" 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. long id; 09. string sz; 10. 11. ObjectCreate(id = ChartID(), sz = IntegerToString(ObjectsTotal(id)), OBJ_BITMAP_LABEL, 0, 0, 0); 12. ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, def_LIB + "::" + def_BTN_BUY); 13. ResourceSave(def_LIB + "::" + def_BTN_SELL, "BTN_SELL.bmp"); 14. ObjectSetString(id, sz, OBJPROP_BMPFILE, 1, "\\Files\\BTN_SELL.bmp"); 15. 16. return INIT_SUCCEEDED; 17. } 18. //+------------------------------------------------------------------+
片段 02
请注意,我们现在定义了可执行文件。因此,上述所有解释都适用于该片段 02。当然,我们还需要了解编译器如何看待代码。那么,让我们看看下面的代码。
01. //+------------------------------------------------------------------+ 02. int OnInit() 03. { 04. long id; 05. string sz; 06. 07. ObjectCreate(id = ChartID(), sz = IntegerToString(ObjectsTotal(id)), OBJ_BITMAP_LABEL, 0, 0, 0); 08. ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, "\\Indicators\\Icons.Lib::Images\\Market Replay\\Chart Trade\\BUY.bmp"); 09. ResourceSave("\\Indicators\\Icons.Lib::Images\\Market Replay\\Chart Trade\\SELL.bmp", "\\Files\\BTN_SELL.bmp"); 10. ObjectSetString(id, sz, OBJPROP_BMPFILE, 1, "\\Files\\BTN_SELL.bmp"); 11. 12. return INIT_SUCCEEDED; 13. } 14. //+------------------------------------------------------------------+
片段 02(扩展的代码)
你不太可能看到有人编写这样的扩展代码。这是因为维护此类代码需要耗费大量人力。但对于编译器来说,一切都是一样的。
既然我已经解释了比较简单的部分,现在让我们回到别名上来。在 MQL5 中使用别名时,会 "强制" 编译器以不同的方式运行。它将使用数据压缩系统来减小文件的大小。同时,它将忽略对资源的任何直接访问。也就是说,您将无法直接使用该定义。了解这一点非常重要。您可以在可执行文件中添加任何内容,但不能直接使用已添加的内容。
事实上,要使用通过别名添加到可执行文件中的内容,您需要使用该别名,而不是资源。看起来很困惑?让我们试着弄清楚。
01. #resource "\\Images\\euro.bmp" as bitmap euro[][] 02. #resource "\\Images\\dollar.bmp" 03. //+------------------------------------------------------------------+ 04. void Image(string name,string rc,int x,int y) 05. { 06. ObjectCreate(0, name, OBJ_BITMAP_LABEL, 0, 0, 0); 07. ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); 08. ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); 09. ObjectSetString(0, name, OBJPROP_BMPFILE, rc); 10. } 11. //+------------------------------------------------------------------+ 12. void OnStart() 13. { 14. for(int x = 0; x < ArrayRange(euro, 1); x++) 15. euro[ArrayRange(euro, 1) / 2][x] = 0xFFFF0000; 16. ResourceCreate("euro_icon", euro, ArrayRange(euro, 1), ArrayRange(euro, 0), 0, 0, ArrayRange(euro, 1), COLOR_FORMAT_ARGB_NORMALIZE); 17. Image("Euro" , "::euro_icon", 10, 40); 18. Image("USD", "::Images\\dollar.bmp", 15 + ArrayRange(euro,1), 40); 19. Image("E2", "::Images\\euro.bmp", 20 + ArrayRange(euro, 1) * 2, 40); 20. } 21. //+------------------------------------------------------------------+
文档中提供的源代码
上面的源代码正好演示了使用别名时使用资源的一种方法。第 1 行定义了别名。从那一刻起,我们不应该再使用资源名称。我们应该使用它的别名。因此,当我们执行第 19 行时,会出现错误,因为我们试图使用资源名称,而实际上应该使用它的别名。
通过上面显示的代码,别名告诉我们使用哪个结构来对数据进行建模。可能是任何一种可能。我们还可以使用工具将一种数据类型转换为另一种。通过这种方式,你可以做比许多人通常想象的更多的事情。从文档中借用的这段代码与我使用的代码之间的区别正是信息的性质。
在文档中的这段代码中,我们使用该系统演示了使用别名的示例。然而,在这种情况下,不需要将现有数据保存为可执行文件的资源。原因是这是我们可以直接使用的数据。但如果资源是一个模板呢?根据规则,MetaTrader 5 不允许使用模板作为资源。因此,有必要从可执行文件中提取模板,使其不再是资源,而是成为常规文件。
这可以通过与片段 01 中的代码所示相同的方式实现。在第 13 行,我们将作为资源包含在可执行文件中的图像转换成文件,以便在第 14 行的对象中使用。
这在理论上可行,但在实践中有点复杂。如果你注意到,在代码 01 的第 13 行,我们使用资源名称而不是别名。我们使用别名的方式与使用资源的方式略有不同,但这不是我们的主要问题。我们的主要问题是,我们不能将模板作为资源包含在可执行文件中。因此,让我们把重点放在 C_AdjustTemplate 类的代码上。这将帮助您了解我是如何克服这两个问题的:无法在可执行文件中包含模板,以及能够使用存储在可执行程序中的模板。
因此,在 C_AdjustTemplate 类的第 13 行,我定义了要使用的模板。这与之前的 Chart Trade 文章中出现的模板相同。但请注意,在第 21 行,我将定义转换为资源时,并没有转换它并使用资源。我使用的是别名。此别名基于字符串数据类型,因此模板文件中存在的所有代码都被添加到可执行文件中,就像它是一个长字符串一样。因而,这行代码就像一个大常数。充分理解这一点非常重要。换句话说,为了更清楚地说明问题:系统会将模板文件视为常量,尽管实际上并非如此。
假定模板文件是一个文本字符串,在可执行文件中包含 IdeRad 别名。我们就可以开始考虑如何利用它。
首先要明白的是,我们不能在 OBJ_CHART 对象中直接使用带有 IdeRad 别名的模板。这是不可能的。我们需要将这些数据转换回文件。但是,ResourceSave 函数无法处理这种情况,因为模板不是资源。但我们可以做些别的。因此,我们编写了第 45 行至第 50 行的代码。
请注意:我使用这种方法并不是因为它是必需的,而是因为我不想更改已经可以运行的类代码。我们可以使用 IdeRad 别名直接读取模板内容,并进行必要的修改。然而,这将使已经创建好和测试过的逻辑复杂化。
因此,让我们弄清楚第 45 行至第 50 行发生了什么。在调用构造函数来操作模板时,我们会指定调用级别以及要访问的文件名。如果这是第一次调用,第 45 行将创建模板文件。该文件将在第 47 行创建,并在第 49 行关闭。如果只有这两行,模板将是空的。而第 48 行才是神奇之处。
在这里,我们将该行的全部内容放入文件中。这一行是什么?就是 IdeRad 变量中的那个。哎呀,等一下。您的意思是说我们将模板作为资源存储在可执行文件中吗?为了避免问题,我们给它分配了一个别名。然后,当我们想要还原其内容时,我们获取该别名的内容并将其放入文件中。真的吗?是的,没错。现在你可能想知道:为什么以前没有人想过做这件事,或者展示如何做?我也不知道。也许是因为没有人真正尝试过,或者,也许没有人能想象如何做到这一点。
在第 49 行关闭文件后,其余过程与前面解释的相同。这是因为 MetaTrader 5 现在要处理的不是资源,而是磁盘上的文件。
最后,有一个小细节我不能忽视。许多人可能会想象一些事情或试图操纵数据,以了解与 Chart Trade 指标交互时发生了什么。问题是关于按钮的。如果在图表上运行该指标,您将无法理解如何访问这些按钮。只有当您对 MQL5 缺乏经验时,才会出现这种情况。您在任何地方都找不到被标记为按钮的图像。如果查看模板内容,您会看到类似内容:
bmpfile_on=\Indicators\Replay\Chart Trade.ex5::Images\Market Replay\Chart Trade\BUY.bmp
bmpfile_off=\Indicators\Replay\Chart Trade.ex5::Images\Market Replay\Chart Trade\BUY.bmp
这说不通。如果您刚刚开始学习 MQL5,则会觉得没有道理。但是,如果您在图表上再现相同的模式,您会注意到它显示正确。这怎么可能?引用的图像位于何处?这就是问题所在。这些图像位于可执行文件内。也就是说,模板直接指定了应使用的图像。这与前面介绍的代码和片段 02 的扩展代码中看到的情况相同。有关代码正是第 08 行,为了让大家更容易理解,我将在下面再次展示:
08. ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, "\\Indicators\\Icons.Lib::Images\\Market Replay\\Chart Trade\\BUY.bmp");
请注意,代码和模板中的代码行是多么相似。这表明 MetaTrader 5 和 MQL5 语言尚未得到充分开发。相信我,MetaTrader 5 和 MQL5 的功能远远超出您的想象。即使有很多人所说的种种限制,我们能做的也比我们想象的要多得多。无需借助其他语言。
结论
在本文中,我展示了如何做到许多人认为不可能做到的事情:在可执行文件中使用模板。虽然这些知识在这里很容易理解,但它实际上可以让你做得更多。好吧,使用 DLL,我们可以更广泛地完成这里显示的相同工作。
这一切都取决于程序员的创造力、能力和个性。有人说有些事情做不到,其他人说没有办法。但也有人通过努力尝试获得了成功。不要成为那些在面对挑战时放弃的人,做那个解决问题的人。我的座右铭是:
真正的专业程序员:在别人只看到问题的时候解决问题。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11737


