Разработка системы репликации (Часть 52): Всё усложняется (IV)
Введение
В предыдущей статье Разработка системы репликации (Часть 51): Всё усложняется (III), мы внесли некоторые изменения в наш старый указатель мыши, чтобы он правильно использовался в нашей системе репликации/моделирования. В той же статье я на практике продемонстрировал разницу между получением ID графика с помощью ChartOpen - функции, используемой программами для указания MetaTrader 5 на открытие графика, и получением того же идентификатора уже открытого графика, но с помощью функции ChartID.
Я думаю, было совершенно ясно, что разница и долгосрочные возможности позволят вам гораздо шире использовать программирование на 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/ru/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 кода можно видеть, что значение смещения, которое раньше было равно четырем, теперь равно пяти. С чем связано данное изменение? Увеличение на одну единицу необходимо для обеспечения более широкого охвата, но для лучшего понимания давайте посмотрим на функцию, отвечающую за установку буфера индикатора в строке 84.
Скорее всего, вы не заметите никаких отличий данной функции от тех, что были в прошлом. Но изменения едва уловимы. Вы должны понимать их, чтобы использовать в своих личных проектах, где вы, возможно, захотите интегрировать этот индикатор в свою конкретную модель.
В частности, начиная со строки 92 и далее, всё начинает выглядеть по-другому, но не настолько. Почему я объявляю две переменные X и две переменные Y? И почему одна называется Adjusted, а другая Graphics? Именно в этом и заключается смысл. Ранее индикатор возвращал только графическую переменную, скорректированную по времени и цене, но это подходит не во всех случаях. Бывают ситуации, когда нам действительно нужно значение графической координаты, а не заданное значение. Чтобы выполнить обе ситуации, индикатор возвращает два этих значения, чтобы мы могли использовать их наилучшим образом.
Обратите внимание, что в нулевой позиции буфера будет находиться значение цены, а в первой - значение времени. Я решил оставить это из соображений обратной совместимости с другими вещами, которые я уже использую лично. Однако не нужно читать буфер в том виде, в котором он был создан. Помните, что на более высоком уровне есть более простой способ - использовать класс Mouse, но если вы всё же хотите читать данные из буфера напрямую, вы должны понять, как он был построен. Поэтому понимание данной функции, присутствующей в строке 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
По сути, в этом классе единственные реальные изменения были внесены в функцию Draw, которая находится в строке 42. Данные изменения направлены на сохранение логики исследований, основанных на времени и цене. Однако объекты, чья система координат имеет графический вид, вызывают эту ошибку. Данный вид объекта уже был объяснен при создании этого индикатора мыши несколько статей назад в этой же последовательности. Тогда я объяснил, почему и как работать с координатами "цена-время" и графическими координатами типа X-Y.
Чтобы сохранить обратную совместимость, мы используем скорректированные графические координаты. Это видно из строк 46-48. Однако если мы используем другие объекты, которым нужны графические координаты, и хотим, чтобы они следовали координатам Price-Time, нам нужно будет просто использовать скорректированные графические координаты, как показано здесь. Если вы замените эту скорректированную систему на графическую, которая не будет корректироваться, исследование может выглядеть несколько иначе.
Возможно, стоит испытать это на себе, чтобы только что сказанное мною, стало более понятным.
Но чтобы завершить часть, касающуюся индикатора мыши, давайте рассмотрим класс, отвечающий за поддержку базовой системы. Это позволит нам лучше понять разницу между скорректированными и нескорректированными графическими координатами. Затем мы перейдем к коду индикатора управления, чтобы понять причину всех этих изменений. Полный код класса мыши можно увидеть чуть ниже. Я надеюсь, что не придется вносить в него новые поправки.
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 includes, можно получить из прошлых статей. Они не изменились, так что нет смысла повторять их здесь.
Если спуститься чуть ниже, то в строках с 22 по 25 мы найдем объявления переменных, которые мы заметили в предыдущих кодах. Следует обратить внимание на то, что они являются частью структуры и что данная структура является публичной. Таким образом, мы сможем использовать эту же структуру в дальнейшем, что сделает код более читабельным и качественным. Эти же переменные снова используются в строках 110 и 111 только для того, чтобы обеспечить правильное расположение некоторых объектов.
Но именно в строке 157 всё становится по-настоящему интересным. Помните, в этой же статье я говорил, что вам не нужно знать, как был смонтирован буфер, что мы можем использовать класс мыши для получения нужных нам значений? Именно благодаря использованию данной функции строки 157 это становится возможным. Эта функция способна адаптироваться и правильно реагировать на запрос чтения буфера, возвращая информацию через структуру, упомянутую выше. И это значительно упрощает программирование.
Обратите внимание, что точно так же, как создавался буфер, здесь мы должны действовать в обратном направлении, чтобы получить информацию, которая присутствует в буфере. Затем, если всё происходит правильно, функция 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/ru/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 у нас есть структура, которая будет располагаться в массиве, вмещающем элементы управления. Обратите на это внимание, так как способность воспринимать данный вид конструкции будет важна для следующих шагов.
Если посмотреть на код класса управления, то можно увидеть, что мы больше не используем все прежние вызовы. На самом деле, код подвергся довольно объемной переработке. В этой статье я не буду вдаваться во всё подробности, поскольку она еще не закончена и позволяет нам полностью контролировать ситуацию. Но, учитывая то, что уже было реализовано... Теперь мы рассмотрим процедуру SetPlay, которая находится в строке 73. Я не уверен, что это будет окончательное название процедуры, но на данный момент оно именно такое.
В данной процедуре в строке 75 мы проверим, был ли создан указатель для кнопки воспроизведения и паузы. Если он не был создан, в строке 76 выполнится конструктор класса рисования. Таким образом, мы указываем, какие изображения надо использовать. Точно так же, как если бы мы использовали функцию ObjectCreate из библиотеки MQL5. Затем, в строке 77, мы воспроизводим изображение в позиции, указанной кнопкой управления, и говорим, какое изображение будем отслеживать. Наконец, в строке 78 мы просим обновить график, чтобы рисовать изображение.
Я не буду пока объяснять другие события, поскольку мы их рассмотрим в другой раз. Но я хочу, чтобы вы обратили внимание на строку 188. В этой строке мы перехватываем события мыши, которые MetaTrader 5 просматривает для нас. Я быстро объясню происходящее, так вы поймете основы. Позже, когда код станет более сложным, мы подробно объясним, что здесь происходит на самом деле.
Когда MetaTrader 5 посылает нам событие мыши, мы спрашиваем у индикатора мыши, произошел ли щелчок левой кнопкой. Это будет подтверждено только в том случае, если индикатор мыши не находится в режиме исследования. Если щелчок произошел, то класс управления проверит, на какой из них был произведен щелчок. Возникла ошибка, которую мы устраним позже, но когда будет подтверждено, что кнопка воспроизведения/паузы была нажата, код между строками 191 и 195 будет выполнен, так что мы получим представление о взаимодействии, которое происходит между пользователем и всей системой.
На видео ниже можно увидеть, как это происходит. Обратите внимание, что у нас еще нет ничего функционального, но идея заключалась в том, чтобы попытаться настроить всё так, чтобы обеспечить именно такое взаимодействие между индикатором мыши и индикатором управления.
Заключение
В этой статье мы рассмотрели, как изменить систему, чтобы сделать ее более приятной и полезной. В долгосрочной перспективе это означает, что мы перестанем так часто составлять программы, а система будет становиться всё более стабильной и надежной. Однако мы так же рассмотрели, что во многих случаях нам приходится углубляться в использование MQL5, чтобы получить более качественный результат.
Я не включил исполняемые файлы в приложение, и это связано с тем, что они не являются рабочими. Вы найдете изображения, которыми нужно заменить старые, если хотите получить ту же систему, которую разрабатываю я, при этом сохраняя тот же код, что показан в статьях. Однако не стесняйтесь изменять и исправлять то, что вам действительно нужно.
В следующей статье мы подробнее рассмотрим данный индикатор управления. Теперь всё начнет расширяться в геометрической прогрессии.
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/11925
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Контрастный Трансформер паттернов (Окончание)
Поиск произвольных паттернов валютных пар на Python с использованием MetaTrader 5
Модифицированный советник Grid-Hedge в MQL5 (Часть IV): Оптимизация простой сеточной стратегии (I)
Разработка системы репликации (Часть 51): Все усложняется (III)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования