
开发回放系统(第 52 部分):事情变得复杂(四)
概述
在上一篇文章开发回放系统(第 51 部分):事情变得复杂 (三)中 ,我们对旧鼠标指标做了一些改动,以便它能在我们的回放/模拟器系统中正常工作。在同一篇文章中,我实际演示了使用 ChartOpen(程序用来告诉 MetaTrader 5 打开图表的函数)获取的图表 ID 与使用 ChartID 函数获取已打开图表的相同 ID 之间的差异。
我认为很明显,这种差异和长期可能性将使您能够更广泛地使用 MQL5 编程。
尽管对鼠标指针和控制指标做了很多改动,但在集成方面,或者更准确地说,在使鼠标指针和控制指标正确交互方面,还是遇到了一些困难。这是由于我的错误,因为在开发鼠标指针的过程中,我错过了一些细节,导致交互无法正常工作。
在本文中,我们将解决这个问题:别担心,这是一件非常简单但非常必要的事情。修改后,您就可以在个人项目中使用相同的鼠标指针。这样,您将拥有一个个性化的学习系统和一种与图形交互的安全方式。当鼠标指针处于研究模式时,它不允许您与图表上的其他对象进行交互。但要做到这一点,我们需要将其集成到本地代码中。
为了说明如何进行集成,因为我们将对回放/模拟器系统的其他部分进行同样的集成,我们将使用一个控制指标。然而,这个控制指标已经过修改,可以使用更合适的系统,就像以前我们使用基本的 MQL5 系统一样。我们将更深入地了解 MQL5,我们将拥有一些以前无法获得的功能。
我们在本文中要实现的交互是最基本的,但足以让您了解如何将鼠标指针集成到您的程序中,从而获得一个定制的学习系统,同时不会与图表上的对象发生不稳定的交互。因此,让我们从本文的第一个主题开始,看看需要对鼠标指针进行哪些修改。
改进鼠标指针
所有代码(暂时保持原样)将在本篇文章中全文发布。你可能会认为它被分成了太多部分。不过,这样更容易解释所有细节。因此,如果代码中引用的内容不在这里,您就必须在以前的文章中查找引用。
通过这种方式,我可以完全相信您确实在遵循解释并理解我是如何开发代码的。记住这个简短的解释,让我们来看看鼠标指针代码。如下图所示:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property description "This is an indicator for graphical studies using the mouse." 004. #property description "This is an integral part of the Replay / Simulator system." 005. #property description "However it can be used in the real market." 006. #property version "1.52" 007. #property icon "/Images/Market Replay/Icons/Indicators.ico" 008. #property link "https://www.mql5.com/en/articles/11925" 009. #property indicator_chart_window 010. #property indicator_plots 0 011. #property indicator_buffers 1 012. //+------------------------------------------------------------------+ 013. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 014. //+------------------------------------------------------------------+ 015. C_Study *Study = NULL; 016. //+------------------------------------------------------------------+ 017. input long user00 = 0; //ID 018. input C_Study::eStatusMarket user01 = C_Study::eAuction; //Market Status 019. input color user02 = clrBlack; //Price Line 020. input color user03 = clrPaleGreen; //Positive Study 021. input color user04 = clrLightCoral; //Negative Study 022. //+------------------------------------------------------------------+ 023. C_Study::eStatusMarket m_Status; 024. int m_posBuff = 0; 025. double m_Buff[]; 026. //+------------------------------------------------------------------+ 027. int OnInit() 028. { 029. ResetLastError(); 030. Study = new C_Study(user00, "Indicator Mouse Study", user02, user03, user04); 031. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 032. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 033. { 034. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 035. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 036. m_Status = C_Study::eCloseMarket; 037. }else 038. m_Status = user01; 039. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 040. ArrayInitialize(m_Buff, EMPTY_VALUE); 041. 042. return INIT_SUCCEEDED; 043. } 044. //+------------------------------------------------------------------+ 045. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 046. { 047. m_posBuff = rates_total - 5; 048. (*Study).Update(m_Status); 049. 050. return rates_total; 051. } 052. //+------------------------------------------------------------------+ 053. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 054. { 055. (*Study).DispatchMessage(id, lparam, dparam, sparam); 056. SetBuffer(); 057. 058. ChartRedraw((*Study).GetInfoTerminal().ID); 059. } 060. //+------------------------------------------------------------------+ 061. void OnBookEvent(const string &symbol) 062. { 063. MqlBookInfo book[]; 064. C_Study::eStatusMarket loc = m_Status; 065. 066. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 067. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 068. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 069. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 070. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 071. if (loc != m_Status) (*Study).Update(m_Status); 072. } 073. //+------------------------------------------------------------------+ 074. void OnDeinit(const int reason) 075. { 076. if (reason != REASON_INITFAILED) 077. { 078. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 079. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 080. } 081. delete Study; 082. } 083. //+------------------------------------------------------------------+ 084. inline void SetBuffer(void) 085. { 086. uCast_Double Info; 087. 088. m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff); 089. m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price; 090. Info._datetime = (*Study).GetInfoMouse().Position.dt; 091. m_Buff[m_posBuff + 1] = Info.dValue; 092. Info._int[0] = (*Study).GetInfoMouse().Position.X_Adjusted; 093. Info._int[1] = (*Study).GetInfoMouse().Position.Y_Adjusted; 094. m_Buff[m_posBuff + 2] = Info.dValue; 095. Info._int[0] = (*Study).GetInfoMouse().Position.X_Graphics; 096. Info._int[1] = (*Study).GetInfoMouse().Position.Y_Graphics; 097. m_Buff[m_posBuff + 3] = Info.dValue; 098. Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0); 099. m_Buff[m_posBuff + 4] = Info.dValue; 100. } 101. //+------------------------------------------------------------------+
鼠标指针源代码
您很可能不会注意到该代码与之前的代码有任何区别。正如我所说,修改不大,但确实存在。
其中第一个,或许也是唯一的一个,是将指标写入的缓冲区的索引。在第 47 行代码中,可以看到偏移值从原来的 4 变成了 5。这一修改的原因是什么呢?为了扩大覆盖范围,增加一个单位是必要的,但为了更好地理解,让我们看看第 84 行负责设置指标缓冲区的函数。
您可能不会注意到这项功能与以往的功能有什么不同,这些变化是微妙的。您应该了解它们,以便在您的个人项目中使用它们,因为您可能希望将该指标整合到您的特定模型中。
特别是,从第 92 行开始,情况开始有所不同,但没那么大。为什么要声明两个 X 变量和两个 Y 变量?为什么一个叫 "Adjusted"(调整),另一个叫 "Graphics"(图形)?这就是重点。以前,该指标只返回一个按时间和价格调整的图形变量,但这并不适用于所有情况。在某些情况下,我们实际上需要图形坐标的值,而不是给定的值。为了涵盖这两种情况,指标返回这两个值,以便我们能够以最佳方式使用它们。
请注意,缓冲区的零位将包含价格值,第一位将包含时间值。我决定保留它,以便与我个人已经使用的其他内容向后兼容。不过,没有必要在创建缓冲区时读取它。请记住,在更高的层次上,有一种更简单的方法是使用鼠标类,但如果你仍然想直接从缓冲区读取数据,你必须了解它是如何构建的。因此,理解第 84 行所示的函数至关重要。
变化还不止于此,重要的是要明白,修改代码可能会影响代码的大部分其他部分。然而,在负责创建和开展研究的类中,变化并没有那么大,但仍然值得解释一下。研究代码如下所示。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. #define def_ExpansionBtn1 def_ExpansionPrefix + "B1" 008. #define def_ExpansionBtn2 def_ExpansionPrefix + "B2" 009. #define def_ExpansionBtn3 def_ExpansionPrefix + "B3" 010. //+------------------------------------------------------------------+ 011. class C_Study : public C_Mouse 012. { 013. private : 014. //+------------------------------------------------------------------+ 015. struct st00 016. { 017. eStatusMarket Status; 018. MqlRates Rate; 019. string szInfo; 020. color corP, 021. corN; 022. int HeightText; 023. }m_Info; 024. //+------------------------------------------------------------------+ 025. const datetime GetBarTime(void) 026. { 027. datetime dt; 028. u_Interprocess info; 029. int i0 = PeriodSeconds(); 030. 031. if (m_Info.Status == eInReplay) 032. { 033. if (!GlobalVariableGet(def_GlobalVariableServerTime, info.df_Value)) return ULONG_MAX; 034. if ((dt = info.ServerTime) == ULONG_MAX) return ULONG_MAX; 035. }else dt = TimeCurrent(); 036. if (m_Info.Rate.time <= dt) 037. m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0; 038. 039. return m_Info.Rate.time - dt; 040. } 041. //+------------------------------------------------------------------+ 042. void Draw(void) 043. { 044. double v1; 045. 046. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 047. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 048. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 049. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo); 050. v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / GetInfoMouse().Position.Price) * 100.0), 2); 051. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 052. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 053. v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0)) * 100.0), 2); 054. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 055. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 056. } 057. //+------------------------------------------------------------------+ 058. public : 059. //+------------------------------------------------------------------+ 060. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 061. :C_Mouse(IdParam, szShortName, corH, corP, corN) 062. { 063. if (_LastError != ERR_SUCCESS) return; 064. ZeroMemory(m_Info); 065. m_Info.Status = eCloseMarket; 066. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 067. m_Info.corP = corP; 068. m_Info.corN = corN; 069. CreateObjectInfo(2, 110, def_ExpansionBtn1, clrPaleTurquoise); 070. CreateObjectInfo(2, 53, def_ExpansionBtn2); 071. CreateObjectInfo(58, 53, def_ExpansionBtn3); 072. } 073. //+------------------------------------------------------------------+ 074. void Update(const eStatusMarket arg) 075. { 076. datetime dt; 077. 078. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 079. { 080. case eCloseMarket : m_Info.szInfo = "Closed Market"; 081. break; 082. case eInReplay : 083. case eInTrading : 084. if ((dt = GetBarTime()) < ULONG_MAX) 085. { 086. m_Info.szInfo = TimeToString(dt, TIME_SECONDS); 087. break; 088. } 089. case eAuction : m_Info.szInfo = "Auction"; 090. break; 091. default : m_Info.szInfo = "ERROR"; 092. } 093. Draw(); 094. } 095. //+------------------------------------------------------------------+ 096. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 097. { 098. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 099. if (id == CHARTEVENT_MOUSE_MOVE) Draw(); 100. } 101. //+------------------------------------------------------------------+ 102. }; 103. //+------------------------------------------------------------------+ 104. #undef def_ExpansionBtn3 105. #undef def_ExpansionBtn2 106. #undef def_ExpansionBtn1 107. #undef def_ExpansionPrefix 108. #undef def_MousePrefixName 109. //+------------------------------------------------------------------+
C_Study 类的源代码
该类中唯一真正的修改是第 42 行的绘制函数。这些变化旨在保持基于时间和价格的研究逻辑。但是,坐标系为图形系统的对象会导致此错误。我们在几篇文章前创建鼠标指标时已经看到了对这种对象的解释。我解释了使用价格-时间坐标和 XY 型图形坐标的原因和方法。
为了保持向后兼容性,我们使用调整后的图形坐标。从第 46 到 48 行可以看出这一点。但是,如果我们使用其他需要图形坐标的对象,并希望它们遵循价格-时间坐标,我们只需使用调整后的图形坐标即可,如图所示。如果你用一个没有调整的图形系统替换这个调整后的系统,这项研究可能会有所不同。
也许你应该亲自试一试。
要完成鼠标指标部分,让我们来看看负责支持基础系统的类。这将使我们更好地理解调整后的图形坐标和未调整的图形坐标之间的区别。然后,我们将转到控制指标代码,以了解所有这些修改的原因。鼠标类的完整代码如下。我希望我们将不需要再作任何修改。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. #include "Interprocess.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_MousePrefixName "MouseBase_" 008. #define def_NameObjectLineH def_MousePrefixName + "H" 009. #define def_NameObjectLineV def_MousePrefixName + "TV" 010. #define def_NameObjectLineT def_MousePrefixName + "TT" 011. #define def_NameObjectStudy def_MousePrefixName + "TB" 012. //+------------------------------------------------------------------+ 013. class C_Mouse : public C_Terminal 014. { 015. public : 016. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 017. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 018. struct st_Mouse 019. { 020. struct st00 021. { 022. int X_Adjusted, 023. Y_Adjusted, 024. X_Graphics, 025. Y_Graphics; 026. double Price; 027. datetime dt; 028. }Position; 029. uint ButtonStatus; 030. bool ExecStudy; 031. }; 032. //+------------------------------------------------------------------+ 033. protected: 034. enum eEventsMouse {ev_HideMouse, ev_ShowMouse}; 035. //+------------------------------------------------------------------+ 036. void CreateObjectInfo(int x, int w, string szName, color backColor = clrNONE) const 037. { 038. if (m_Mem.szShortName != NULL) return; 039. CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 042. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 044. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 046. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 047. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 048. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 049. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 050. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 051. } 052. //+------------------------------------------------------------------+ 053. private : 054. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 055. struct st01 056. { 057. st_Mouse Data; 058. color corLineH, 059. corTrendP, 060. corTrendN; 061. eStudy Study; 062. }m_Info; 063. struct st_Mem 064. { 065. bool CrossHair, 066. IsFull; 067. datetime dt; 068. string szShortName; 069. }m_Mem; 070. bool m_OK; 071. //+------------------------------------------------------------------+ 072. void GetDimensionText(const string szArg, int &w, int &h) 073. { 074. TextSetFont("Lucida Console", -100, FW_NORMAL); 075. TextGetSize(szArg, w, h); 076. h += 5; 077. w += 5; 078. } 079. //+------------------------------------------------------------------+ 080. void CreateStudy(void) 081. { 082. if (m_Mem.IsFull) 083. { 084. CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH); 085. CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH); 086. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2); 087. CreateObjectInfo(0, 0, def_NameObjectStudy); 088. } 089. m_Info.Study = eStudyCreate; 090. } 091. //+------------------------------------------------------------------+ 092. void ExecuteStudy(const double memPrice) 093. { 094. double v1 = GetInfoMouse().Position.Price - memPrice; 095. int w, h; 096. 097. if (!CheckClick(eClickLeft)) 098. { 099. m_Info.Study = eStudyNull; 100. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 101. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 102. }else if (m_Mem.IsFull) 103. { 104. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 105. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0))); 106. GetDimensionText(sz1, w, h); 107. ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1); 108. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 109. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w); 110. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h); 111. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 112. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 113. ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 114. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 115. } 116. m_Info.Data.ButtonStatus = eKeyNull; 117. } 118. //+------------------------------------------------------------------+ 119. public : 120. //+------------------------------------------------------------------+ 121. C_Mouse(const long id, const string szShortName) 122. :C_Terminal(id), 123. m_OK(false) 124. { 125. m_Mem.szShortName = szShortName; 126. } 127. //+------------------------------------------------------------------+ 128. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 129. :C_Terminal(id) 130. { 131. if (!(m_OK = IndicatorCheckPass(szShortName))) SetUserError(C_Terminal::ERR_Unknown); 132. if (_LastError != ERR_SUCCESS) return; 133. m_Mem.szShortName = NULL; 134. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 135. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 136. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 137. ZeroMemory(m_Info); 138. m_Info.corLineH = corH; 139. m_Info.corTrendP = corP; 140. m_Info.corTrendN = corN; 141. m_Info.Study = eStudyNull; 142. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 143. CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 144. } 145. //+------------------------------------------------------------------+ 146. ~C_Mouse() 147. { 148. if (!m_OK) return; 149. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, 0, false); 150. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false); 151. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 152. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 153. } 154. //+------------------------------------------------------------------+ 155. inline bool CheckClick(const eBtnMouse value) 156. { 157. return (GetInfoMouse().ButtonStatus & value) == value; 158. } 159. //+------------------------------------------------------------------+ 160. inline const st_Mouse GetInfoMouse(void) 161. { 162. if (m_Mem.szShortName != NULL) 163. { 164. double Buff[]; 165. uCast_Double loc; 166. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 167. 168. ZeroMemory(m_Info.Data); 169. if (CopyBuffer(handle, 0, 0, 5, Buff) == 5) 170. { 171. m_Info.Data.Position.Price = Buff[0]; 172. loc.dValue = Buff[1]; 173. m_Info.Data.Position.dt = loc._datetime; 174. loc.dValue = Buff[2]; 175. m_Info.Data.Position.X_Adjusted = loc._int[0]; 176. m_Info.Data.Position.Y_Adjusted = loc._int[1]; 177. loc.dValue = Buff[3]; 178. m_Info.Data.Position.X_Graphics = loc._int[0]; 179. m_Info.Data.Position.Y_Graphics = loc._int[1]; 180. loc.dValue = Buff[4]; 181. m_Info.Data.ButtonStatus = loc._char[0]; 182. IndicatorRelease(handle); 183. } 184. } 185. 186. return m_Info.Data; 187. } 188. //+------------------------------------------------------------------+ 189. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 190. { 191. int w = 0; 192. static double memPrice = 0; 193. 194. if (m_Mem.szShortName == NULL) switch (id) 195. { 196. case (CHARTEVENT_CUSTOM + ev_HideMouse): 197. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE); 198. break; 199. case (CHARTEVENT_CUSTOM + ev_ShowMouse): 200. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH); 201. break; 202. case CHARTEVENT_MOUSE_MOVE: 203. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (int)lparam, m_Info.Data.Position.Y_Graphics = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 204. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); 205. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 206. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X_Adjusted, m_Info.Data.Position.Y_Adjusted); 207. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0); 208. m_Info.Data.ButtonStatus = (uint) sparam; 209. if (CheckClick(eClickMiddle)) 210. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 211. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 212. { 213. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 214. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 215. m_Info.Study = eStudyExecute; 216. } 217. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 218. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 219. break; 220. case CHARTEVENT_OBJECT_DELETE: 221. if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 222. break; 223. } 224. } 225. //+------------------------------------------------------------------+ 226. }; 227. //+------------------------------------------------------------------+ 228. #undef def_NameObjectLineV 229. #undef def_NameObjectLineH 230. #undef def_NameObjectLineT 231. #undef def_NameObjectStudy 232. //+------------------------------------------------------------------+
C_Mouse 类的源代码
您可能会注意到这里的情况似乎很紧张,但不要被第一印象所迷惑。让我们来看看最初的细节。第 4 行和第 5 行中的包含文件可以从以前的文章中获得。它们并没有改变,所以没有必要在此重复。
如果我们向下滚动一点,在第 22 行至第 25 行,我们会发现前面代码中的变量声明。它们是公有结构的一部分。因此,我们今后可以使用相同的结构。这样,代码的可读性更强,质量更高。第 110 和 111 行再次使用了这些变量,以确保某些对象放置在正确的位置上。
但第 157 行才是真正有趣的地方。正如我们之前看到的,你不需要知道缓冲区是如何匹配的,就可以使用鼠标类来获取我们需要的值。正是通过使用第 157 行中的这一函数,才有可能做到这一点。该函数能够适应并正确响应缓冲区读取请求,通过上述结构返回信息。这使得编程变得更加容易。
但要注意的是,就像创建缓冲区一样,在这里我们必须反向操作,才能获取缓冲区中的信息。然后,如果一切顺利,GetInfoMouse 函数将返回与鼠标位置和点击相关的数据。这些数据可用于您开发的任何程序或鼠标指标本身。界面是相同的,这使得代码的维护和理解更加容易。
在这个鼠标的 GetInfoMouse 函数之后,我们还有一个可能是最重要的函数,因为它将对与鼠标的交互做出响应。这个名为 DispatchMessage 的函数位于第 186 行,但它与之前的函数基本相同,只是做了一些改进,以满足我们的需要。
有几件事需要了解:MetaTrader 5 使用的消息系统与 Windows 非常相似,因此消息传递给我们程序的方式与在 Windows 上运行的程序基本相同。在这些情况下,了解 Windows 编程是一个很大的帮助,但即使根据需要对信息进行审查,它也往往以错误的形式或不熟悉编程的人所不熟悉的格式出现。
因此,当 MetaTrader 5 向我们的程序发送鼠标事件时,它会将鼠标数据组织到消息本身中。从第 200 行可以看出,MetaTrader 5 完全按照 Windows 的提示进行检查。因此,MetaTrader 5 不会根据价格和时间报告鼠标坐标,而是以图形的形式报告。在这一阶段,我们获得了必要的图形坐标,这些坐标未经调整,代表的是鼠标在窗口中的位置。不是在图表上,而是在窗口里。不过,这里有一点需要澄清。这里的 "窗口" 指的是图形窗口。要理解这一点,您需要了解 Windows 编程,但这不是本文的目的。
一旦有了这个位置,我们就可以使用 MQL5 将其转换为价格和时间位置。因此,使用这些坐标定位的对象可能会稍稍偏离该点,与图表上的柱形没有直接联系。为了解决这个问题,我们使用了调整价格的第 201 行和调整时间的第 202 行。这就在位置和图表柱形之间建立了对应关系。
但由于我们希望在某些点上根据价格和时间调整 X 值和 Y 值,因此我们使用第 203 行。这样我们就能得到调整后的数值。以前,图形调整值和未调整的值之间没有区别。不过,在尝试操作对象并将鼠标指标与控制指标整合在一起时,就有必要进行这种区分。讨论完鼠标指标,我们可以将注意力转移到控制指标上。
新的控制指标
正如我们在文章开头提到的,我们将更深入地使用 MQL5,以便对控制指标进行一些改进。这些改进将使我们的生活更加舒适。在应用程序中,我们将可以访问新的形状,从而实现透明度。为了明确如何实现这一点,我们将使用新的光栅图像。
您可能想知道:我需要为此创建一个新的类吗?别担心,我保证在本文结束时会向您展示主要的交互。
该类的源代码如下。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_Terminal.mqh" 05. //+------------------------------------------------------------------+ 06. #define def_MaxImages 2 07. //+------------------------------------------------------------------+ 08. class C_DrawImage : protected C_Terminal 09. { 10. //+------------------------------------------------------------------+ 11. private : 12. struct st_00 13. { 14. int widthMap, 15. heightMap; 16. uint Map[]; 17. }m_InfoImage[def_MaxImages]; 18. uint m_Pixels[]; 19. string m_szObjName, 20. m_szRecName; 21. //+------------------------------------------------------------------+ 22. void ReSizeImage(const int w, const int h, const uchar v, const int what) 23. { 24. #define _Transparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0) 25. double fx = (w * 1.0) / m_InfoImage[what].widthMap; 26. double fy = (h * 1.0) / m_InfoImage[what].heightMap; 27. uint pyi, pyf, pxi, pxf, tmp; 28. uint uc; 29. 30. ArrayResize(m_Pixels, w * h); 31. for (int cy = 0, y = 0; cy < m_InfoImage[what].heightMap; cy++, y += m_InfoImage[what].widthMap) 32. { 33. pyf = (uint)(fy * cy) * w; 34. tmp = pyi = (uint)(fy * (cy - 1)) * w; 35. for (int x = 0; x < m_InfoImage[what].widthMap; x++) 36. { 37. pxf = (uint)(fx * x); 38. pxi = (uint)(fx * (x - 1)); 39. uc = (uchar(double((uc = m_InfoImage[what].Map[x + y]) >> 24) * _Transparency(v)) << 24) | uc & 0x00FFFFFF; 40. m_Pixels[pxf + pyf] = uc; 41. for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = uc; 42. } 43. for (pyi += w; pyi < pyf; pyi += w) 44. for (int x = 0; x < w; x++) 45. m_Pixels[x + pyi] = m_Pixels[x + tmp]; 46. } 47. #undef _Transparency 48. } 49. //+------------------------------------------------------------------+ 50. public : 51. //+------------------------------------------------------------------+ 52. C_DrawImage(long id, int sub, string szObjName, const color cFilter, const string szFile1, const string szFile2 = NULL) 53. :C_Terminal(id), 54. m_szObjName(NULL), 55. m_szRecName(NULL) 56. { 57. if (!ObjectCreate(GetInfoTerminal().ID, m_szObjName = szObjName, OBJ_BITMAP_LABEL, sub, 0, 0)) SetUserError(C_Terminal::ERR_Unknown); 58. m_szRecName = "::" + m_szObjName; 59. for (int c0 = 0; (c0 < def_MaxImages) && (_LastError == ERR_SUCCESS); c0++) 60. { 61. ResourceReadImage((c0 == 0 ? szFile1 : (szFile2 == NULL ? szFile1 : szFile2)), m_InfoImage[c0].Map, m_InfoImage[c0].widthMap, m_InfoImage[c0].heightMap); 62. ArrayResize(m_Pixels, m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap); 63. ArrayInitialize(m_Pixels, 0); 64. for (int c1 = (m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap) - 1; c1 >= 0; c1--) 65. if ((m_InfoImage[c0].Map[c1] & 0x00FFFFFF) != cFilter) m_Pixels[c1] = m_InfoImage[c0].Map[c1]; 66. ArraySwap(m_InfoImage[c0].Map, m_Pixels); 67. } 68. ArrayResize(m_Pixels, 1); 69. } 70. //+------------------------------------------------------------------+ 71. ~C_DrawImage() 72. { 73. for (int c0 = 0; c0 < def_MaxImages; c0++) 74. ArrayFree(m_InfoImage[c0].Map); 75. ArrayFree(m_Pixels); 76. ObjectDelete(GetInfoTerminal().ID, m_szObjName); 77. ResourceFree(m_szRecName); 78. } 79. //+------------------------------------------------------------------+ 80. void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what) 81. { 82. 83. if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return; 84. ReSizeImage(w, h, cView, what); 85. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x); 86. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y); 87. if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) 88. { 89. ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName); 90. ChartRedraw(GetInfoTerminal().ID); 91. } 92. } 93. //+------------------------------------------------------------------+ 94. }; 95. //+------------------------------------------------------------------+ 96. #undef def_MaxImages 97. //+------------------------------------------------------------------+
C_DrawImage 类的源代码
请注意,代码非常紧凑。这是因为我们不会使用外部资源。我们拥有 MQL5 和它能为我们提供的一切帮助。本质上,该类是使用 OBJ_BITMAP_LABEL 或 OBJ_BITMAP 对象的另一种方式。这两种类型都是 MQL5 标准库的一部分,但它们不能在图像上使用透明度或不可见的点。因此,上述类可以扩展以包含我们需要的东西。
这个类非常简单,我们只使用了四个简单的过程。让我简单介绍一下这里的工作原理。
在第 6 行中,我们指定了要使用的图像的最大数量。这个数字可以增加,但需要修改一些代码。很快我就会告诉你哪里需要修改。然后,我们将进入类代码的第 8 行。请注意,它继承了终端类。这在以前的文章中有过详细解释。
紧接着,在第 11 行中,我们使用代码的私有部分来表示,声明的数据对类的内部代码来说是私有的。实际上,我们不需要修改声明此类数据的这一部分。不过,在第 22 行,我们进入了预渲染程序,在这里我们设置了图像大小和透明度级别。该级别从 0% 到 100% 不等,以整数步长变化,即每次变化一个步长。第 24 行的定义确保了图像透明度变换的正确性。
在这里,我们可以提高或降低图像分辨率。由于这些图像较小,我认为没有必要添加抗锯齿处理。因此,如果放大太多,图像可能会显得非常像素化。如果出于某种原因需要放大图像,则应考虑添加抗锯齿功能,但目前的系统对于我们的目的来说已经足够了。
第 52 行包含类的构造函数。在此,如果您想处理更多图像,就必须增加参数数量,但我们的目的还是为了扩展 MQL5 的功能,因此,两张图片就足够了。
在构造函数中的第 61 行,我们将访问作为资源引用的图像。请记住,我们的系统不需要外部资源,因此只有可执行文件。第 63 行确保图像完全透明。此外,在第 64 行中,我们引入了一个循环,该循环将遍历加载的图像,在第 65 行的每次交互中,我们将检查指定的颜色是否为透明色。如果为 "true",则不添加颜色;如果为 "false",则添加颜色。
在第 66 行,我们使用 MQL5 函数将原始图像转换为修改后的图像,因此我们可以尽快完成所有操作。从第 68 行开始,我们将清理系统。
析构函数开始于第 71 行,它用于将所有资源(在本例中是内存)归还给系统,释放出来供其他程序使用。这里无需特别说明或解释。
第 80 行包含绘图过程。此过程将根据前面的四个参数,在指定的位置和尺寸上将图像复制到图表上。第五个调用参数必须是介于 0 和 100 之间的值,其中 0 表示无透明度,100 表示完全透明。第六个参数指定实际渲染的图像。该值应始终以 0 开头 - 这是第一个图像的索引,最大值为最高数字减 1。也就是说,我们使用与 OBJ_BITMAP_LABEL 和 OBJ_BITMAP 对象相同的系统,其中我们指定了图像索引,只是在这里,如果我们更改我指定的点数,索引可能会多于 2。如果我们想扩展功能,无需对该绘图函数进行任何解释或额外更改。
唯一需要修改的地方是构造函数和第 6 行的定义。现在,如果我们想直接从文件加载图像,我们也需要实现这一点。对于我们要做的事情来说,这个类已经非常完美了。
现在,我们可以继续编写指标和控制类的代码。不过,在查看控件类代码之前,让我们先快速查看一下指标代码。下面是完整代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property description "Control indicator for the Replay-Simulator service." 05. #property description "This one doesn't work without the service loaded." 06. #property version "1.52" 07. #property link "https://www.mql5.com/en/articles/11925" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Controls.mqh> 12. //+------------------------------------------------------------------+ 13. C_Controls *control = NULL; 14. //+------------------------------------------------------------------+ 15. input long user00 = 0; //ID 16. //+------------------------------------------------------------------+ 17. int OnInit() 18. { 19. u_Interprocess Info; 20. 21. ResetLastError(); 22. if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID) 23. SetUserError(C_Terminal::ERR_PointerInvalid); 24. if (_LastError != ERR_SUCCESS) 25. { 26. Print("Control indicator failed on initialization."); 27. return INIT_FAILED; 28. } 29. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 30. EventChartCustom(user00, C_Controls::evInit, Info.s_Infos.iPosShift, Info.df_Value, ""); 31. GlobalVariableTemp(def_GlobalVariableReplay); 32. 33. return INIT_SUCCEEDED; 34. } 35. //+------------------------------------------------------------------+ 36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 37. { 38. return rates_total; 39. } 40. //+------------------------------------------------------------------+ 41. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 42. { 43. (*control).DispatchMessage(id, lparam, dparam, sparam); 44. } 45. //+------------------------------------------------------------------+ 46. void OnDeinit(const int reason) 47. { 48. switch (reason) 49. { 50. case REASON_TEMPLATE: 51. Print("Modified template. Replay/simulation system shutting down."); 52. case REASON_PARAMETERS: 53. case REASON_REMOVE: 54. case REASON_CHARTCLOSE: 55. if (ChartSymbol(user00) != def_SymbolReplay) break; 56. GlobalVariableDel(def_GlobalVariableReplay); 57. ChartClose(user00); 58. break; 59. } 60. delete control; 61. } 62. //+------------------------------------------------------------------+
控制指标的源代码
您可以看到,这段代码已经失去了一些重量(体积)。但这只是暂时的,因为我们需要更简洁的代码来正确测试。您可以看到,我们对控制类的调用和引用少了很多。为什么呢?原因在于组织过程的方式发生了变化。我们应该尽可能减少访问点。请注意,OnInit 代码截然不同。接下来,第 30 行有一些非常不寻常的地方。在这一步中,我们将在构造函数之后初始化控制类数据。不过,至于第 22 行提到的构造函数,我们决定它只处理所用指针的初始化。事件处理函数将处理所有其他任务。因此,我们有了第 30 行。为了更清楚地说明问题,让我们来看看控制类的基本代码。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\Interprocess.mqh" 005. #include "..\Auxiliar\C_DrawImage.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_ButtonLeft def_PathBMP + "Left.bmp" 011. #define def_ButtonLeftBlock def_PathBMP + "Left_Block.bmp" 012. #define def_ButtonRight def_PathBMP + "Right.bmp" 013. #define def_ButtonRightBlock def_PathBMP + "Right_Block.bmp" 014. #define def_ButtonPin def_PathBMP + "Pin.bmp" 015. #resource "\\" + def_ButtonPlay 016. #resource "\\" + def_ButtonPause 017. #resource "\\" + def_ButtonLeft 018. #resource "\\" + def_ButtonLeftBlock 019. #resource "\\" + def_ButtonRight 020. #resource "\\" + def_ButtonRightBlock 021. #resource "\\" + def_ButtonPin 022. //+------------------------------------------------------------------+ 023. #define def_PrefixCtrlName "MarketReplayCTRL_" 024. #define def_PosXObjects 120 025. //+------------------------------------------------------------------+ 026. #define def_SizeButtons 32 027. #define def_ColorFilter 0xFF00FF 028. //+------------------------------------------------------------------+ 029. #include "..\Auxiliar\C_Terminal.mqh" 030. #include "..\Auxiliar\C_Mouse.mqh" 031. //+------------------------------------------------------------------+ 032. class C_Controls : private C_Terminal 033. { 034. protected: 035. enum EventCustom {evInit}; 036. private : 037. //+------------------------------------------------------------------+ 038. enum eObjectControl {ePlay, eLeft, eRight, ePin, eNull}; 039. //+------------------------------------------------------------------+ 040. struct st_00 041. { 042. string szBarSlider, 043. szBarSliderBlock; 044. int Minimal; 045. }m_Slider; 046. struct st_01 047. { 048. C_DrawImage *Btn; 049. bool state; 050. int x, y, w, h; 051. }m_Section[eObjectControl::eNull]; 052. C_Mouse *m_MousePtr; 053. //+------------------------------------------------------------------+ 054. inline void CreteBarSlider(int x, int size) 055. { 056. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "B1", OBJ_RECTANGLE_LABEL, 0, 0, 0); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 064. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 065. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_PrefixCtrlName + "B2", OBJ_RECTANGLE_LABEL, 0, 0, 0); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 070. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 071. } 072. //+------------------------------------------------------------------+ 073. void SetPlay(bool state) 074. { 075. if (m_Section[ePlay].Btn == NULL) 076. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause); 077. 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) ? 0 : 1)); 078. ChartRedraw(GetInfoTerminal().ID); 079. } 080. //+------------------------------------------------------------------+ 081. void CreateCtrlSlider(void) 082. { 083. CreteBarSlider(77, 436); 084. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 085. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 086. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePin), def_ColorFilter, "::" + def_ButtonPin); 087. PositionPinSlider(m_Slider.Minimal); 088. } 089. //+------------------------------------------------------------------+ 090. inline void RemoveCtrlSlider(void) 091. { 092. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 093. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 094. { 095. delete m_Section[c0].Btn; 096. m_Section[c0].Btn = NULL; 097. } 098. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName + "B"); 099. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 100. } 101. //+------------------------------------------------------------------+ 102. inline void PositionPinSlider(int p) 103. { 104. int iL, iR; 105. 106. m_Section[ePin].x = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 107. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 108. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 109. m_Section[ePin].x += def_PosXObjects; 110. m_Section[ePin].x += 95 - (m_Section[ePin].w / 2); 111. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 112. 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))); 113. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 114. } 115. //+------------------------------------------------------------------+ 116. inline eObjectControl CheckPositionMouseClick(void) 117. { 118. C_Mouse::st_Mouse InfoMouse; 119. int x, y; 120. 121. InfoMouse = (*m_MousePtr).GetInfoMouse(); 122. x = InfoMouse.Position.X_Graphics; 123. y = InfoMouse.Position.Y_Graphics; 124. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 125. { 126. if ((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)) 127. return c0; 128. } 129. 130. return eNull; 131. } 132. //+------------------------------------------------------------------+ 133. public : 134. //+------------------------------------------------------------------+ 135. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 136. :C_Terminal(Arg0), 137. m_MousePtr(MousePtr) 138. { 139. if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); 140. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 141. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 142. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 143. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 144. { 145. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 146. m_Section[c0].y = 25; 147. m_Section[c0].Btn = NULL; 148. } 149. m_Section[ePlay].x = def_PosXObjects; 150. m_Section[eLeft].x = m_Section[ePlay].x + 47; 151. m_Section[eRight].x = m_Section[ePlay].x + 511; 152. } 153. //+------------------------------------------------------------------+ 154. ~C_Controls() 155. { 156. delete m_MousePtr; 157. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 158. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 159. } 160. //+------------------------------------------------------------------+ 161. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 162. { 163. u_Interprocess Info; 164. 165. switch (id) 166. { 167. case CHARTEVENT_CUSTOM + C_Controls::evInit: 168. Info.df_Value = dparam; 169. m_Slider.Minimal = Info.s_Infos.iPosShift; 170. SetPlay(Info.s_Infos.isPlay); 171. if (!Info.s_Infos.isPlay) CreateCtrlSlider(); 172. break; 173. case CHARTEVENT_OBJECT_DELETE: 174. if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName) 175. { 176. if (sparam == (def_PrefixCtrlName + EnumToString(ePlay))) 177. { 178. delete m_Section[ePlay].Btn; 179. m_Section[ePlay].Btn = NULL; 180. SetPlay(false); 181. }else 182. { 183. RemoveCtrlSlider(); 184. CreateCtrlSlider(); 185. } 186. } 187. break; 188. case CHARTEVENT_MOUSE_MOVE: 189. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick()) 190. { 191. case ePlay: 192. SetPlay(!m_Section[ePlay].state); 193. if (m_Section[ePlay].state) RemoveCtrlSlider(); 194. else CreateCtrlSlider(); 195. break; 196. case eLeft: 197. break; 198. case eRight: 199. break; 200. case ePin: 201. break; 202. } 203. break; 204. } 205. ChartRedraw(GetInfoTerminal().ID); 206. } 207. //+------------------------------------------------------------------+ 208. }; 209. //+------------------------------------------------------------------+ 210. #undef def_PosXObjects 211. #undef def_ButtonPlay 212. #undef def_ButtonPause 213. #undef def_ButtonLeft 214. #undef def_ButtonRight 215. #undef def_ButtonPin 216. #undef def_PrefixCtrlName 217. #undef def_PathBMP 218. //+------------------------------------------------------------------+
C_Controls 类的源代码
我们之所以说 "基础代码",是因为它实际上并没有做任何有用的事情。它只是让我们演示上述绘图类的使用,以及与鼠标指针的一些非常简单的交互。看看我们有什么。在第 26 行,我们定义了交互按钮的大小。在第 27 行中,我们指定了用于表示图像某一区域完全透明的颜色。您可以在所附的图片中看到这种颜色,这些图片将作为资源嵌入到可执行文件中。
在第 38 行,我们指定了主要控件的类型,在第 46 至 51 行之间,我们有一个结构,它将位于存放控件的数组中。请注意这一点,因为理解这种构造的能力对于接下来的步骤非常重要。
如果查看控制类代码,就会发现我们不再使用以前的所有调用。事实上,代码已经发生了很大变化。在本文中,我不会详述所有细节,因为它还没有完成并且可以让我们完全控制局面。现在,让我们看看位于第 73 行的 SetPlay 过程。我不确定程序的最终名称是什么,但现在就是这样。
在本程序的第 75 行,我们将检查播放和暂停按钮的指针是否已创建。如果尚未创建,绘图类构造函数将在第 76 行执行。这样,我们就可以指定使用哪些图像,就像我们使用 MQL5 库中的 ObjectCreate 函数一样。然后,在第 77 行,我们在控制按钮指定的位置播放图像,并告诉它要跟踪哪个图像。最后,在第 78 行,我们要求更新图表以绘制图像。
至于其他事件,我暂且不做解释,我们下次再看。但我希望你们注意第 188 行。在这一行中,我们拦截 MetaTrader 5 跟踪的鼠标事件。我将简要介绍一下情况,以便您了解基本知识。稍后,当代码变得更加复杂时,我们将详细解释这里到底发生了什么。
当 MetaTrader 5 向我们发送鼠标事件时,我们会询问鼠标指标是否发生了左键点击。只有当鼠标指标不在研究模式下时,才能确认这一点。如果发生点击,控制类将检查点击的是哪一个。我们稍后会修正一个错误,但当确认按下播放/暂停按钮后,第 191到195 行的代码将被执行,这样我们就能了解用户与整个系统之间的交互情况。
您可以在下面的视频中看到这种情况是如何发生的。请注意,我们还没有任何功能性的东西,但我们的想法是尝试在鼠标指标和控制指标之间建立这样一种交互。
结论
在这篇文章中,我们探讨了如何修改系统,使其更愉快、更有用。从长远来看,这意味着我们将不再需要频繁地编写程序,系统将变得越来越稳定和可靠。不过,我们也看到,在很多情况下,我们必须深入研究 MQL5,才能获得更好的结果。
我没有在应用程序中加入可执行文件,因为它们现在还无法运行。如果您想获得我正在开发的相同系统,同时保留文章中显示的相同代码,您可以在附件中找到需要替换旧系统的文件。如有需要,请随时修改。
在下一篇文章中,我们将详细介绍控制指标。现在一切都将开始呈指数级增长。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11925
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。


