
Desarrollo de un sistema de repetición (Parte 67): Refinando el indicador de control
Introducción
En el artículo anterior, "Desarrollo de un sistema de repetición (Parte 66): Presionando play en el servicio (VII)", implementamos un método para determinar cuándo aparecería una nueva barra en el gráfico. Aunque este método funciona muy bien con modelos con buena liquidez, no es adecuado en absoluto para modelos con baja liquidez o que entran constantemente en subasta. Este problema se solucionará muy pronto.
Sin embargo, me gustaría mostrar otra cosa. Esto también es interesante, aunque no suponga una gran diferencia para el usuario que solo quiere utilizar la aplicación. Para ese usuario, lo que voy a empezar a explorar al inicio de este artículo no tiene la menor relevancia. Sin embargo, para ti, que estás estudiando para convertirte en un buen programador, lo que mostraré a continuación sí marca una gran diferencia. Tanto en la forma de programar como en la de observar tu propio código y el de otros programadores. Porque sí, podemos aprender mucho observando cómo otros programadores crean soluciones para diversos problemas. Incluso problemas similares pueden tener implementaciones completamente diferentes dependiendo del programador, o incluso cuando este revisa sus métodos de trabajo en un lenguaje determinado.
De todos modos, es posible que lo que voy a mostrarte te sea útil en algún momento futuro.
En un artículo anterior, realicé algunos cambios en el código para evitar que la aplicación produjera errores al quitarla. Esto podría ocurrir tanto porque el usuario cerrara la aplicación como porque se cerrara el gráfico principal. En cualquier caso, la corrección se llevó a cabo. Sin embargo, ese mismo código puede mejorarse significativamente. Esto simplifica todo el sistema, especialmente en lo que respecta al mantenimiento del código fuente.
Así que vamos a ver de qué se trata el tema del que estoy hablando. Para ello, empezaremos con el primer tema de este artículo.
Mejoramos el dinamismo del indicador de control
El código con el que trabajaremos es el del indicador de control. Aunque ya funciona bastante bien, podemos mejorar algunos aspectos de la codificación en términos de calidad. Primero, revisemos el código original del archivo de cabecera C_Controls.mqh. Este puede consultarse íntegramente a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\C_DrawImage.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_PathBMP "Images\\Market Replay\\Control\\" 008. #define def_ButtonPlay def_PathBMP + "Play.bmp" 009. #define def_ButtonPause def_PathBMP + "Pause.bmp" 010. #define def_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_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A)) 024. #define def_PosXObjects 120 025. //+------------------------------------------------------------------+ 026. #define def_SizeButtons 32 027. #define def_ColorFilter 0xFF00FF 028. //+------------------------------------------------------------------+ 029. #include "..\Auxiliar\C_Mouse.mqh" 030. //+------------------------------------------------------------------+ 031. class C_Controls : private C_Terminal 032. { 033. protected: 034. private : 035. //+------------------------------------------------------------------+ 036. enum eMatrixControl {eCtrlPosition, eCtrlStatus}; 037. enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)}; 038. //+------------------------------------------------------------------+ 039. struct st_00 040. { 041. string szBarSlider, 042. szBarSliderBlock; 043. ushort Minimal; 044. }m_Slider; 045. struct st_01 046. { 047. C_DrawImage *Btn; 048. bool state; 049. short x, y, w, h; 050. }m_Section[eObjectControl::eNull]; 051. C_Mouse *m_MousePtr; 052. //+------------------------------------------------------------------+ 053. inline void CreteBarSlider(short x, short size) 054. { 055. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 064. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 065. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 070. } 071. //+------------------------------------------------------------------+ 072. void SetPlay(bool state) 073. { 074. if (m_Section[ePlay].Btn == NULL) 075. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay); 076. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0); 077. if (!state) CreateCtrlSlider(); 078. } 079. //+------------------------------------------------------------------+ 080. void CreateCtrlSlider(void) 081. { 082. if (m_Section[ePin].Btn != NULL) return; 083. CreteBarSlider(77, 436); 084. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 085. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 086. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(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_ObjectCtrlName("B")); 099. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 100. } 101. //+------------------------------------------------------------------+ 102. inline void PositionPinSlider(ushort p) 103. { 104. int iL, iR; 105. 106. m_Section[ePin].x = (short)(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 - (def_SizeButtons / 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. 114. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 115. } 116. //+------------------------------------------------------------------+ 117. inline eObjectControl CheckPositionMouseClick(short &x, short &y) 118. { 119. C_Mouse::st_Mouse InfoMouse; 120. 121. InfoMouse = (*m_MousePtr).GetInfoMouse(); 122. x = (short) InfoMouse.Position.X_Graphics; 123. y = (short) InfoMouse.Position.Y_Graphics; 124. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 125. { 126. if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y)) 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)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 140. if (_LastError != ERR_SUCCESS) return; 141. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 142. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 143. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 144. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 145. { 146. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 147. m_Section[c0].y = 25; 148. m_Section[c0].Btn = NULL; 149. } 150. m_Section[ePlay].x = def_PosXObjects; 151. m_Section[eLeft].x = m_Section[ePlay].x + 47; 152. m_Section[eRight].x = m_Section[ePlay].x + 511; 153. m_Slider.Minimal = eTriState; 154. } 155. //+------------------------------------------------------------------+ 156. ~C_Controls() 157. { 158. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 159. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 160. delete m_MousePtr; 161. } 162. //+------------------------------------------------------------------+ 163. void SetBuffer(const int rates_total, double &Buff[]) 164. { 165. uCast_Double info; 166. 167. info._16b[eCtrlPosition] = m_Slider.Minimal; 168. info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause)); 169. if (rates_total > 0) 170. Buff[rates_total - 1] = info.dValue; 171. } 172. //+------------------------------------------------------------------+ 173. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 174. { 175. short x, y; 176. static ushort iPinPosX = 0; 177. static short six = -1, sps; 178. uCast_Double info; 179. 180. switch (id) 181. { 182. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 183. info.dValue = dparam; 184. if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break; 185. iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition])); 186. SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay); 187. break; 188. case CHARTEVENT_OBJECT_DELETE: 189. if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName("")) 190. { 191. if (sparam == def_ObjectCtrlName(ePlay)) 192. { 193. delete m_Section[ePlay].Btn; 194. m_Section[ePlay].Btn = NULL; 195. SetPlay(m_Section[ePlay].state); 196. }else 197. { 198. RemoveCtrlSlider(); 199. CreateCtrlSlider(); 200. } 201. } 202. break; 203. case CHARTEVENT_MOUSE_MOVE: 204. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 205. { 206. case ePlay: 207. SetPlay(!m_Section[ePlay].state); 208. if (m_Section[ePlay].state) 209. { 210. RemoveCtrlSlider(); 211. m_Slider.Minimal = iPinPosX; 212. }else CreateCtrlSlider(); 213. break; 214. case eLeft: 215. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 216. break; 217. case eRight: 218. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 219. break; 220. case ePin: 221. if (six == -1) 222. { 223. six = x; 224. sps = (short)iPinPosX; 225. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 226. } 227. iPinPosX = sps + x - six; 228. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 229. break; 230. }else if (six > 0) 231. { 232. six = -1; 233. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 234. } 235. break; 236. } 237. ChartRedraw(GetInfoTerminal().ID); 238. } 239. //+------------------------------------------------------------------+ 240. }; 241. //+------------------------------------------------------------------+ 242. #undef def_PosXObjects 243. #undef def_ButtonPlay 244. #undef def_ButtonPause 245. #undef def_ButtonLeft 246. #undef def_ButtonRight 247. #undef def_ButtonPin 248. #undef def_PathBMP 249. //+------------------------------------------------------------------+
Código fuente del archivo C_Controls.mqh
Básicamente, lo que nos importa aquí es cuánto puede crecer este código o adquirir nuevas funcionalidades sin necesidad de codificar nada adicional. Sin embargo, aunque el código mostrado arriba no requiere incorporar nuevas funcionalidades, no se puede decir lo mismo del código que utiliza de forma extensiva. Esta es la función encargada de crear y mantener los objetos en el gráfico. Me refiero a la clase C_DrawImage, declarada en la línea 47.
Aunque puedas imaginar que esta clase C_Controls es la que crea los objetos que se colocan en el gráfico, en realidad no es así, al menos no en su gran mayoría. Los dos únicos objetos que realmente crea son los que soportan el control deslizante. Es decir, todo lo que genera y posiciona en el gráfico esta clase C_Controls está presente en el procedimiento que se encuentra en la línea 53. Dicho esto, echemos un buen vistazo al código del archivo de cabecera C_DrawImage.mqh. Puede consultarse íntegramente a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_MaxImages 2 007. //+------------------------------------------------------------------+ 008. class C_DrawImage : public C_Terminal 009. { 010. //+------------------------------------------------------------------+ 011. private : 012. struct st_00 013. { 014. int widthMap, 015. heightMap; 016. uint Map[]; 017. }m_InfoImage[def_MaxImages]; 018. uint m_Pixels[]; 019. string m_szObjName, 020. m_szRecName; 021. //+------------------------------------------------------------------+ 022. bool LoadBitmap(int index, string szFileName) 023. { 024. struct BitmapHeader 025. { 026. ushort type; 027. uint size, 028. reserv, 029. offbits, 030. imgSSize, 031. imgWidth, 032. imgHeight; 033. ushort imgPlanes, 034. imgBitCount; 035. uint imgCompression, 036. imgSizeImage, 037. imgXPelsPerMeter, 038. imgYPelsPerMeter, 039. imgClrUsed, 040. imgClrImportant; 041. } Header; 042. int fp, w; 043. bool noAlpha, noFlip; 044. uint imgSize; 045. 046. if ((fp = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE) return false; 047. if (FileReadStruct(fp, Header) != sizeof(Header)) 048. { 049. FileClose(fp); 050. return false; 051. }; 052. m_InfoImage[index].widthMap = (int)Header.imgWidth; 053. m_InfoImage[index].heightMap = (int)Header.imgHeight; 054. if (noFlip = (m_InfoImage[index].heightMap < 0)) m_InfoImage[index].heightMap = -m_InfoImage[index].heightMap; 055. if (Header.imgBitCount == 32) 056. { 057. uint tmp[]; 058. 059. noAlpha = true; 060. imgSize = FileReadArray(fp, m_InfoImage[index].Map); 061. if (!noFlip) for (int c0 = 0; c0 < m_InfoImage[index].heightMap / 2; c0++) 062. { 063. ArrayCopy(tmp, m_InfoImage[index].Map, 0, m_InfoImage[index].widthMap * c0, m_InfoImage[index].widthMap); 064. ArrayCopy(m_InfoImage[index].Map, m_InfoImage[index].Map, m_InfoImage[index].widthMap * c0, m_InfoImage[index].widthMap * (m_InfoImage[index].heightMap - c0 - 1), m_InfoImage[index].widthMap); 065. ArrayCopy(m_InfoImage[index].Map, tmp, m_InfoImage[index].widthMap * (m_InfoImage[index].heightMap - c0 - 1), 0, m_InfoImage[index].widthMap); 066. } 067. for (uint c0 = 0; (c0 < imgSize && noAlpha); c0++) if (uchar(m_InfoImage[index].Map[c0] >> 24) != 0) noAlpha = false; 068. if (noAlpha) for(uint c0 = 0; c0 < imgSize; c0++) m_InfoImage[index].Map[c0] |= 0xFF000000; 069. } else 070. { 071. uchar tmp[]; 072. 073. w = ((m_InfoImage[index].widthMap * 3) + 3) & ~3; 074. if (ArrayResize(m_InfoImage[index].Map, m_InfoImage[index].widthMap * m_InfoImage[index].heightMap) != -1) for(int c0 = 0; c0 < m_InfoImage[index].heightMap; c0++) 075. { 076. if (FileReadArray(fp, tmp, 0, w) != w) 077. { 078. FileClose(fp); 079. return false; 080. }; 081. for (int j = 0, k = 0, p = m_InfoImage[index].widthMap * (m_InfoImage[index].heightMap - c0 - 1); j < m_InfoImage[index].widthMap; j++, k+=3, p++) 082. m_InfoImage[index].Map[p] = 0xFF000000 | (tmp[k+2] << 16) | (tmp[k + 1] << 8) | tmp[k]; 083. } 084. } 085. FileClose(fp); 086. 087. return true; 088. } 089. //+------------------------------------------------------------------+ 090. void ReSizeImage(const int w, const int h, const uchar v, const int what) 091. { 092. #define _Transparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0) 093. double fx = (w * 1.0) / m_InfoImage[what].widthMap; 094. double fy = (h * 1.0) / m_InfoImage[what].heightMap; 095. uint pyi, pyf, pxi, pxf, tmp; 096. uint uc; 097. 098. ArrayResize(m_Pixels, w * h); 099. for (int cy = 0, y = 0; cy < m_InfoImage[what].heightMap; cy++, y += m_InfoImage[what].widthMap) 100. { 101. pyf = (uint)(fy * cy) * w; 102. tmp = pyi = (uint)(fy * (cy - 1)) * w; 103. for (int x = 0; x < m_InfoImage[what].widthMap; x++) 104. { 105. pxf = (uint)(fx * x); 106. pxi = (uint)(fx * (x - 1)); 107. uc = (uchar(double((uc = m_InfoImage[what].Map[x + y]) >> 24) * _Transparency(v)) << 24) | uc & 0x00FFFFFF; 108. m_Pixels[pxf + pyf] = uc; 109. for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = uc; 110. } 111. for (pyi += w; pyi < pyf; pyi += w) 112. for (int x = 0; x < w; x++) 113. m_Pixels[x + pyi] = m_Pixels[x + tmp]; 114. } 115. #undef _Transparency 116. } 117. //+------------------------------------------------------------------+ 118. void Initilize(int sub, string szObjName, const color cFilter, const string szFile1, const string szFile2 = NULL) 119. { 120. string sz0; 121. 122. m_szObjName = m_szRecName = NULL; 123. CreateObjectGraphics(m_szObjName = szObjName, OBJ_BITMAP_LABEL); 124. m_szRecName = "::" + m_szObjName; 125. for (int c0 = 0; (c0 < def_MaxImages) && (_LastError == ERR_SUCCESS); c0++) 126. { 127. switch (c0) 128. { 129. case 1: 130. if ((sz0 = szFile2) != NULL) break; 131. case 0: 132. sz0 = szFile1; 133. break; 134. } 135. if (StringFind(sz0, "::") >= 0) 136. ResourceReadImage(sz0, m_InfoImage[c0].Map, m_InfoImage[c0].widthMap, m_InfoImage[c0].heightMap); 137. else if (!LoadBitmap(c0, sz0)) 138. { 139. SetUserError(C_Terminal::ERR_FileAcess); 140. return; 141. } 142. ArrayResize(m_Pixels, m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap); 143. ArrayInitialize(m_Pixels, 0); 144. for (int c1 = (m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap) - 1; c1 >= 0; c1--) 145. if ((m_InfoImage[c0].Map[c1] & 0x00FFFFFF) != cFilter) m_Pixels[c1] = m_InfoImage[c0].Map[c1]; 146. ArraySwap(m_InfoImage[c0].Map, m_Pixels); 147. } 148. ArrayResize(m_Pixels, 1); 149. } 150. //+------------------------------------------------------------------+ 151. public : 152. //+------------------------------------------------------------------+ 153. C_DrawImage(string szShortName, long id, int sub, string szObjName, string szFile) 154. :C_Terminal(id) 155. { 156. if (!IndicatorCheckPass(szShortName)) return; 157. Initilize(sub, szObjName, clrNONE, szFile); 158. } 159. //+------------------------------------------------------------------+ 160. C_DrawImage(long id, int sub, string szObjName, const color cFilter, const string szFile1, const string szFile2 = NULL) 161. :C_Terminal(id) 162. { 163. Initilize(sub, szObjName, cFilter, szFile1, szFile2); 164. } 165. //+------------------------------------------------------------------+ 166. ~C_DrawImage() 167. { 168. for (int c0 = 0; c0 < def_MaxImages; c0++) 169. ArrayFree(m_InfoImage[c0].Map); 170. ArrayFree(m_Pixels); 171. ObjectDelete(GetInfoTerminal().ID, m_szObjName); 172. ResourceFree(m_szRecName); 173. } 174. //+------------------------------------------------------------------+ 175. void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what) 176. { 177. 178. if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return; 179. ReSizeImage(w, h, cView, what); 180. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x); 181. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y); 182. if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) 183. { 184. ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName); 185. ChartRedraw(GetInfoTerminal().ID); 186. } 187. } 188. //+------------------------------------------------------------------+ 189. }; 190. //+------------------------------------------------------------------+ 191. #undef def_MaxImages 192. //+------------------------------------------------------------------+
Código fuente del archivo C_DrawImage.mqh
Aquí es donde realmente se ponen interesantes las cosas. Aunque en la clase C_DrawImage implementé una forma de trabajar con un BitMap desde un archivo, esto difícilmente sucederá en la práctica. El motivo es que prefiero colocar los bitmaps como recursos internos de la propia aplicación. Por lo tanto, gran parte de este código no será compilado por MQL5. Sin embargo, lo que quiero destacar del código de la clase C_DrawImage no es esto, sino otro aspecto.
Pensemos un poco: el código del indicador de control no accederá realmente a la clase C_DrawImage. De hecho, el indicador de control ni siquiera sabe que dicha clase existe. Pero presta atención a lo siguiente: en la línea ocho, estamos declarando la clase, que hereda de forma pública la clase C_Terminal. Está bien. Sin embargo, en el código de la clase C_Controls, que es precisamente la que utiliza el indicador de control, en la línea 31 estamos declarando la clase y esta hereda de forma privada la misma clase C_Terminal. Ay caramba. Aquí surgen complicaciones y nos encontramos con un problema. Pero, para empeorar aún más las cosas, observa la línea 156 de la clase C_DrawImage. Nota que aquí también estamos evaluando para añadir un nuevo indicador, pero no utilizamos esto de ninguna manera. Por lo tanto, la clase C_Terminal solo sirve para proporcionarnos acceso al ID del gráfico con el fin de modificar las propiedades de los objetos.
Pero piénsalo: ¿por qué necesitamos conceder acceso a la clase C_Terminal desde una clase que realmente no lo necesita? Incluso si la clase C_DrawImage necesitara acceder a C_Terminal, sería mucho más prudente pasar un puntero a dicha clase para que pudiera acceder a C_Terminal a través de dicho puntero.
De este modo, el código sería más consistente y, al mismo tiempo, más seguro a la hora de ampliarlo con el tiempo. Esta expansión podría realizarse mediante herencia u otros métodos. En cualquier caso, sería mucho más seguro y eficiente que la manera en que se está implementando actualmente.
Así llegamos al punto que quiero mostrar. Vamos a modificar esta clase, C_DrawImage, de manera que se vuelva dependiente, pero que al mismo tiempo nos permita ampliarla si es necesario. Por lo tanto, lo primero que debemos hacer es modificar el código original del archivo C_DrawImage.mqh. Pero antes de hacerlo, veamos el archivo de cabecera del que C_DrawImage.mqh pasará a depender. Este archivo es Macro.mqh y puede consultarse a continuación:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define macroRemoveSec(A) (A - (A % 60)) 05. #define macroGetDate(A) (A - (A % 86400)) 06. #define macroGetSec(A) (A - (A - (A % 60))) 07. //+------------------------------------------------------------------+ 08. #define macroColorRGBA(A, B) ((uint)((B << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16))) 09. #define macroTransparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0) 10. //+------------------------------------------------------------------+
Código fuente del archivo macro.mqh
Muy bien, este archivo Macro.mqh se incluirá en el archivo C_Terminal.mqh. Si tienes alguna duda al respecto, revisa los artículos anteriores para entender cómo se está realizando esta integración. Ahora podemos ver el nuevo archivo C_DrawImage.mqh, ya modificado y con nuevas posibilidades de uso. A continuación, examinaremos el nuevo código:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_Terminal.mqh" 05. //+------------------------------------------------------------------+ 06. class C_DrawImage 07. { 08. //+------------------------------------------------------------------+ 09. private : 10. struct st_00 11. { 12. int widthMap, 13. heightMap; 14. uint Map[]; 15. }m_InfoImage[2]; 16. uint m_Pixels[]; 17. string m_szObjName, 18. m_szRecName; 19. long m_ID; 20. //+------------------------------------------------------------------+ 21. void ReSizeImage(const int w, const int h, const uchar v, const int what) 22. { 23. double fx = (w * 1.0) / m_InfoImage[what].widthMap; 24. double fy = (h * 1.0) / m_InfoImage[what].heightMap; 25. uint pyi, pyf, pxi, pxf, tmp; 26. uint uc; 27. 28. ArrayResize(m_Pixels, w * h); 29. if ((m_InfoImage[what].widthMap == w) && (m_InfoImage[what].heightMap == h) && (v == 100)) ArrayCopy(m_Pixels, m_InfoImage[what].Map); 30. else for (int cy = 0, y = 0; cy < m_InfoImage[what].heightMap; cy++, y += m_InfoImage[what].widthMap) 31. { 32. pyf = (uint)(fy * cy) * w; 33. tmp = pyi = (uint)(fy * (cy - 1)) * w; 34. for (int x = 0; x < m_InfoImage[what].widthMap; x++) 35. { 36. pxf = (uint)(fx * x); 37. pxi = (uint)(fx * (x - 1)); 38. uc = (uchar(double((uc = m_InfoImage[what].Map[x + y]) >> 24) * macroTransparency(v)) << 24) | uc & 0x00FFFFFF; 39. m_Pixels[pxf + pyf] = uc; 40. for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = uc; 41. } 42. for (pyi += w; pyi < pyf; pyi += w) 43. for (int x = 0; x < w; x++) 44. m_Pixels[x + pyi] = m_Pixels[x + tmp]; 45. } 46. } 47. //+------------------------------------------------------------------+ 48. void Initilize(const int index, const color cFilter, const string szFile, const bool r180) 49. { 50. if (StringFind(szFile, "::") < 0) return; 51. ResourceReadImage(szFile, m_InfoImage[index].Map, m_InfoImage[index].widthMap, m_InfoImage[index].heightMap); 52. for (int pm = ArrayResize(m_Pixels, m_InfoImage[index].widthMap * m_InfoImage[index].heightMap), pi = 0; pi < pm;) 53. for (int c0 = 0, pf = pi + (r180 ? m_InfoImage[index].widthMap - 1 : 0); c0 < m_InfoImage[index].widthMap; pi++, (r180 ? pf-- : pf++), c0++) 54. m_Pixels[pf] = ((m_InfoImage[index].Map[pi] & 0x00FFFFFF) != cFilter ? m_InfoImage[index].Map[pi] : 0); 55. ArraySwap(m_InfoImage[index].Map, m_Pixels); 56. ArrayFree(m_Pixels); 57. } 58. //+------------------------------------------------------------------+ 59. public : 60. //+------------------------------------------------------------------+ 61. C_DrawImage(C_Terminal *ptr, string szObjName, const color cFilter, const string szFile1, const string szFile2, const bool r180 = false) 62. { 63. (*ptr).CreateObjectGraphics(m_szObjName = szObjName, OBJ_BITMAP_LABEL); 64. ObjectSetString(m_ID = (*ptr).GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName = "::" + m_szObjName); 65. Initilize(0, cFilter, szFile1, r180); 66. if (szFile2 != NULL) Initilize(1, cFilter, szFile2, r180); 67. } 68. //+------------------------------------------------------------------+ 69. ~C_DrawImage() 70. { 71. ArrayFree(m_Pixels); 72. ResourceFree(m_szRecName); 73. ObjectDelete(m_ID, m_szObjName); 74. } 75. //+------------------------------------------------------------------+ 76. void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what, const string tipics = "\n") 77. { 78. ReSizeImage(w, h, cView, what); 79. ObjectSetInteger(m_ID, m_szObjName, OBJPROP_XDISTANCE, x); 80. ObjectSetInteger(m_ID, m_szObjName, OBJPROP_YDISTANCE, y); 81. ObjectSetString(m_ID, m_szObjName, OBJPROP_TOOLTIP, tipics); 82. ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE); 83. ObjectSetString(m_ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName); 84. ChartRedraw(m_ID); 85. } 86. //+------------------------------------------------------------------+ 87. }; 88. //+------------------------------------------------------------------+
Código fuente del archivo C_DrawImage.mqh
Tal vez pienses que no hay grandes diferencias. Pero sí las hay, y hacen que la clase sea mucho más sostenible y agradable a largo plazo. Básicamente, hemos mantenido todo igual, excepto en la teoría. Esto se debe a que el enfoque principal de la clase es aprovechar los recursos incorporados en los ejecutables y no en archivos externos.
Este enfoque facilita bastante lo que realmente necesitamos hacer. Como puedes ver, ahora tenemos mucho menos código, rutinas y procedimientos de los que ocuparnos. Pero lo más importante está precisamente en el constructor de la clase. Presta mucha atención a la línea 61, donde declaramos el constructor. Fíjate en que ahora la clase C_Terminal se pasa como un puntero. De este modo, en la línea 63 podemos hacer uso de un procedimiento de dicha clase. Asimismo, en la línea 64 inicializamos una variable privada de la clase para acceder al ID del gráfico. Esto tiene como finalidad permitirnos trabajar con las propiedades de los objetos a los que tendrá acceso la clase. Dichos objetos son básicamente OBJ_BITMAP_LABEL, por lo que no habrá mucho más que hacer en este momento.
Sin embargo, observa las líneas 65 y 66. Este tipo de construcción permitió que el procedimiento de inicialización fuera mucho más sólido. Está completamente implementado entre las líneas 48 y 57. Nota que es algo bastante simple de comprender, al menos desde mi punto de vista, y que hace un uso intensivo de las funciones de la biblioteca de MQL5. Con la excepción del bucle presente en la línea 52, todo lo demás son funciones de MQL5. Estudia la documentación para entender qué está sucediendo.
Sin embargo, el bucle de la línea 52 realiza algo curioso, que merece una explicación. Se trata de un bucle doble. Veamos qué está ocurriendo aquí. De lo contrario, no entenderás lo que se hará más adelante, cuando utilicemos la clase C_DrawImage. Comencemos con la primera parte del bucle. En ella declaramos dos variables locales. Sin embargo, solo se incrementará la variable Y en este bucle, así que no te preocupes por la segunda variable, ya que solo es necesario declararla e inicializarla aquí.
En el segundo bucle declaramos otras dos variables. Sin embargo, presta mucha atención a lo que ocurre en la línea 52. La primera, PM, se declara e inicializa con el tamaño del array asignado. Esta variable NO DEBERÁ SER MODIFICADA, ya que indica cuándo debe finalizar el bucle. Observa que para lograr esto, solo necesitaremos comparar esta variable PM con la variable PI. No obstante, fíjate en que la variable PI también se declara en esta misma línea 52, inicializándose con el valor cero. Ahora, ten en cuenta que no estamos haciendo nada con las variables aquí. Por lo tanto, este bucle en la línea 52 sirve únicamente para controlar completamente el siguiente bucle, que se encuentra declarado en la línea 53.
En la línea 53 nos encontramos con un bucle mucho más complejo. Sin embargo, si prestas atención, podrás comprender lo que sucede aquí. La primera acción es declarar una nueva variable, C0. La inicializamos a cero y será la encargada de garantizar que este bucle se ejecute hasta alcanzar el ancho de la imagen. Este es el primer paso y también el más sencillo. Ahora viene la parte complicada. Observa que también declaramos una segunda variable, PF. Muy bien, esta variable PF tendrá su valor inicial dependiente de la variable PI y de lo que estemos haciendo con la imagen. En cualquier caso, la variable PI siempre se incrementará. Sin embargo, la variable PF no necesariamente se incrementará; en algunos casos, incluso podrá decrecer. Para entender el porqué de esto, fíjate en que en la declaración del procedimiento tenemos un valor booleano, R180. Cuando este valor sea verdadero, queremos que la imagen obtenida sea un reflejo de la imagen original. Para lograrlo, cuando R180 sea verdadero, PF se inicializará con el valor de la variable PI sumado al ancho de la imagen. Además, PF se decrementará en cada iteración del bucle. Si R180 es falso, teóricamente obtendremos una simple copia de la imagen original en el array de memoria.
Y ¿por qué dije que, teóricamente, será una copia? El motivo es lo que sucede en la línea 54. Observa que en esta línea filtraremos la imagen en busca de un color específico. Cuando se encuentre ese color, ese punto se volverá transparente en la imagen. Por ello, la copia, al igual que el reflejo, es teóricamente idéntica a la imagen original. Sin embargo, todo este trabajo con los arrays, así como la asignación y liberación de memoria, se debe a que MQL5 no utiliza punteros como C o C++. Por lo tanto, necesitamos implementar las cosas de una manera un poco diferente. Siempre que sea posible, te aconsejo que uses las funciones de la biblioteca estándar de MQL5. Esto se debe a que las funciones de la biblioteca están mucho más optimizadas que cualquier código que quieras desarrollar para realizar la misma tarea. Además, cualquier mejora en el funcionamiento de la biblioteca de MQL5 beneficiará automáticamente a tu programa. A diferencia de lo que ocurriría si implementaras el procedimiento por tu cuenta.
De todos modos, contamos con otro procedimiento que hace un uso intensivo de la biblioteca de MQL5. Me refiero al procedimiento PAINT, que comienza en la línea 76. Prácticamente todas las llamadas están dirigidas a la biblioteca de MQL5, excepto una que se encuentra en la línea 78. Esta ejecutará el procedimiento que puedes visualizar entre las líneas 21 y 46. Ahora presta atención a un detalle: en la línea 28, asignamos memoria para colocar la imagen que se mostrará en el objeto. Sin embargo, en la línea 29 verificamos si se modificará de alguna manera. Si esto no ocurre, significa que la imagen se puede transferir directamente al objeto. Para acelerar este proceso al máximo, utilizamos una función de la biblioteca para copiar la imagen directamente en el array. Si MQL5 utilizara punteros como lo hacen C o C++, las cosas se implementarían de manera diferente. Sin embargo, si la imagen necesita ser modificada de alguna forma, a partir de la línea 30 ingresamos en un bucle con el fin de modificar la imagen para ajustarla a lo esperado, al menos en términos de dimensiones y nivel de transparencia.
De esta forma, concluimos esta parte relacionada con los cambios en el código de la clase C_DrawImage. Sin embargo, dado que este código se ha modificado, será necesario ajustar el código de la clase C_Controls, que en este momento utiliza de manera inmediata la clase C_DrawImage. A continuación, examinaremos el nuevo código del archivo C_Controls.mqh.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\C_DrawImage.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_PathBMP "Images\\Market Replay\\Control\\" 008. #define def_ButtonPlay def_PathBMP + "Play.bmp" 009. #define def_ButtonPause def_PathBMP + "Pause.bmp" 010. #define def_ButtonCtrl def_PathBMP + "Left.bmp" 011. #define def_ButtonCtrlBlock def_PathBMP + "Left_Block.bmp" 012. #define def_ButtonPin def_PathBMP + "Pin.bmp" 013. #resource "\\" + def_ButtonPlay 014. #resource "\\" + def_ButtonPause 015. #resource "\\" + def_ButtonCtrl 016. #resource "\\" + def_ButtonCtrlBlock 017. #resource "\\" + def_ButtonPin 018. //+------------------------------------------------------------------+ 019. #define def_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A)) 020. #define def_PosXObjects 120 021. //+------------------------------------------------------------------+ 022. #define def_SizeButtons 32 023. #define def_ColorFilter 0xFF00FF 024. //+------------------------------------------------------------------+ 025. #include "..\Auxiliar\C_Mouse.mqh" 026. //+------------------------------------------------------------------+ 027. class C_Controls : private C_Terminal 028. { 029. protected: 030. private : 031. //+------------------------------------------------------------------+ 032. enum eMatrixControl {eCtrlPosition, eCtrlStatus}; 033. enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)}; 034. //+------------------------------------------------------------------+ 035. struct st_00 036. { 037. string szBarSlider, 038. szBarSliderBlock; 039. ushort Minimal; 040. }m_Slider; 041. struct st_01 042. { 043. C_DrawImage *Btn; 044. bool state; 045. short x, y, w, h; 046. }m_Section[eObjectControl::eNull]; 047. C_Mouse *m_MousePtr; 048. //+------------------------------------------------------------------+ 049. inline void CreteBarSlider(short x, short size) 050. { 051. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 052. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 053. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 054. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 055. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 060. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 064. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 065. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 066. } 067. //+------------------------------------------------------------------+ 068. void SetPlay(bool state) 069. { 070. if (m_Section[ePlay].Btn == NULL) 071. m_Section[ePlay].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay); 072. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0, state ? "Press to Pause" : "Press to Start"); 073. if (!state) CreateCtrlSlider(); 074. } 075. //+------------------------------------------------------------------+ 076. void CreateCtrlSlider(void) 077. { 078. if (m_Section[ePin].Btn != NULL) return; 079. CreteBarSlider(77, 436); 080. m_Section[eLeft].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock); 081. m_Section[eRight].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock, true); 082. m_Section[ePin].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin, NULL); 083. PositionPinSlider(m_Slider.Minimal); 084. } 085. //+------------------------------------------------------------------+ 086. inline void RemoveCtrlSlider(void) 087. { 088. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 089. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 090. { 091. delete m_Section[c0].Btn; 092. m_Section[c0].Btn = NULL; 093. } 094. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B")); 095. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 096. } 097. //+------------------------------------------------------------------+ 098. inline void PositionPinSlider(ushort p) 099. { 100. int iL, iR; 101. string szMsg; 102. 103. m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 104. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 105. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 106. m_Section[ePin].x += def_PosXObjects; 107. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 108. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) if (m_Section[c0].Btn != NULL) 109. { 110. switch (c0) 111. { 112. case eLeft : szMsg = "Previous Position"; break; 113. case eRight : szMsg = "Next Position"; break; 114. case ePin : szMsg = "Go To: " + IntegerToString(p); break; 115. default : szMsg = "\n"; 116. } 117. m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)), szMsg); 118. } 119. 120. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 121. } 122. //+------------------------------------------------------------------+ 123. inline eObjectControl CheckPositionMouseClick(short &x, short &y) 124. { 125. C_Mouse::st_Mouse InfoMouse; 126. 127. InfoMouse = (*m_MousePtr).GetInfoMouse(); 128. x = (short) InfoMouse.Position.X_Graphics; 129. y = (short) InfoMouse.Position.Y_Graphics; 130. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 131. { 132. if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y)) 133. return c0; 134. } 135. 136. return eNull; 137. } 138. //+------------------------------------------------------------------+ 139. public : 140. //+------------------------------------------------------------------+ 141. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 142. :C_Terminal(Arg0), 143. m_MousePtr(MousePtr) 144. { 145. if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 146. if (_LastError != ERR_SUCCESS) return; 147. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 148. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 149. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 150. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 151. { 152. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 153. m_Section[c0].y = 25; 154. m_Section[c0].Btn = NULL; 155. } 156. m_Section[ePlay].x = def_PosXObjects; 157. m_Section[eLeft].x = m_Section[ePlay].x + 47; 158. m_Section[eRight].x = m_Section[ePlay].x + 511; 159. m_Slider.Minimal = eTriState; 160. } 161. //+------------------------------------------------------------------+ 162. ~C_Controls() 163. { 164. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 165. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 166. delete m_MousePtr; 167. } 168. //+------------------------------------------------------------------+ 169. void SetBuffer(const int rates_total, double &Buff[]) 170. { 171. uCast_Double info; 172. 173. info._16b[eCtrlPosition] = m_Slider.Minimal; 174. info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause)); 175. if (rates_total > 0) 176. Buff[rates_total - 1] = info.dValue; 177. } 178. //+------------------------------------------------------------------+ 179. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 180. { 181. short x, y; 182. static ushort iPinPosX = 0; 183. static short six = -1, sps; 184. uCast_Double info; 185. 186. switch (id) 187. { 188. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 189. info.dValue = dparam; 190. if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break; 191. iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition])); 192. SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay); 193. break; 194. case CHARTEVENT_OBJECT_DELETE: 195. if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName("")) 196. { 197. if (sparam == def_ObjectCtrlName(ePlay)) 198. { 199. delete m_Section[ePlay].Btn; 200. m_Section[ePlay].Btn = NULL; 201. SetPlay(m_Section[ePlay].state); 202. }else 203. { 204. RemoveCtrlSlider(); 205. CreateCtrlSlider(); 206. } 207. } 208. break; 209. case CHARTEVENT_MOUSE_MOVE: 210. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 211. { 212. case ePlay: 213. SetPlay(!m_Section[ePlay].state); 214. if (m_Section[ePlay].state) 215. { 216. RemoveCtrlSlider(); 217. m_Slider.Minimal = iPinPosX; 218. }else CreateCtrlSlider(); 219. break; 220. case eLeft: 221. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 222. break; 223. case eRight: 224. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 225. break; 226. case ePin: 227. if (six == -1) 228. { 229. six = x; 230. sps = (short)iPinPosX; 231. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 232. } 233. iPinPosX = sps + x - six; 234. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 235. break; 236. }else if (six > 0) 237. { 238. six = -1; 239. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 240. } 241. break; 242. } 243. ChartRedraw(GetInfoTerminal().ID); 244. } 245. //+------------------------------------------------------------------+ 246. }; 247. //+------------------------------------------------------------------+ 248. #undef def_PosXObjects 249. #undef def_ButtonPlay 250. #undef def_ButtonPause 251. #undef def_ButtonCtrl 252. #undef def_ButtonCtrlBlock 253. #undef def_ButtonPin 254. #undef def_PathBMP 255. //+------------------------------------------------------------------+
Código fuente del archivo C_Controls.mqh
Lo primero que llama la atención en este nuevo código son las definiciones. La pregunta es: ¿dónde están las definiciones de los botones hacia la derecha y hacia la izquierda? Verás que entre las líneas siete y diecisiete ya no hay botones de derecha e izquierda. En su lugar, encontramos una definición algo diferente. Curiosamente, nos referimos a las imágenes LEFT.BMP y LEFT_BLOCK.BMP, pero no a las del lado derecho. ¿Por qué? La explicación para esto se encuentra en el archivo C_DrawImage.mqh. Como tanto el botón hacia la derecha como el hacia la izquierda son básicamente idénticos, o mejor dicho, uno es el reflejo del otro, no es necesario declarar ambos botones. Basta con tener acceso a uno de ellos para poder usar el otro a través de la clase C_DrawImage.
Además, observemos qué más ha cambiado. En la línea 68 se crean e identifican los botones de play y pause. Bien, ahora observa la línea 72 en esta misma rutina. Al final de la declaración que ocurre en esta línea, damos al objeto la posibilidad de proporcionarnos información adicional. Este tipo de funcionalidad puede ser bastante útil, aunque en este caso no marque una gran diferencia.
Sin embargo, tanto en este procedimiento, que crea y define el botón de play o pause, como en el procedimiento que está debajo y que crea los botones y el control deslizante, hay algo que realmente necesita explicación. Para entender de qué se trata, volvamos al código del constructor de la clase C_DrawImage.
Observa que en el código fuente del constructor de la clase C_DrawImage, en la línea 61, se hace referencia a que el primer parámetro debe ser una referencia a la clase C_Terminal. Presta mucha atención a esto, ya que es muy importante. El constructor de la clase C_DrawImage espera recibir como primer parámetro una referencia a un puntero que apunte a la clase C_Terminal. Sin embargo, si volvemos al código del archivo de cabecera C_Controls.mqh, en las líneas 71, 80, 81 y 82, no estamos pasando este tipo de información. No de la manera en que probablemente esperabas. Volvamos entonces a la declaración de la clase C_Controls, que se encuentra en la línea 27. Presta mucha atención a lo que voy a explicar ahora, porque si no lo entiendes, no podrás comprender nada más a partir de este punto.
La clase C_Controls hereda de forma privada a la clase C_Terminal. Esto permite que cualquier código dentro de la clase C_Controls pueda referirse a la clase C_Terminal como si esta última formara parte de la primera. Es crítico que entiendas esto. La referencia a la clase C_Terminal solo ocurrirá DENTRO de la clase C_Controls, y enfatizo esta palabra. Esto sucede porque la herencia se está realizando de forma privada. Si la herencia se realizara de manera pública, cualquier código que hiciera referencia a la clase C_Controls también tendría acceso a las funciones y procedimientos públicos de la clase C_Terminal. Muy bien. Perfecto. Pero ¿qué tiene esto que ver con el constructor de la clase C_DrawImage? Está bien. Si has entendido la cuestión de la herencia, ahora podemos ver cómo se relaciona con el hecho de que el constructor de la clase C_DrawImage espere un puntero para referenciar la clase C_Terminal, mientras que nosotros estamos pasando un puntero que referencia la clase C_Controls. ¿No lo entiendes todavía? Cuando usamos la función de biblioteca GetPointer y en su argumento especificamos el operador this, en realidad estamos haciendo referencia a la clase C_Controls y no a otra cosa.
Para comprender esto, primero necesitas entender qué significa el operador this, si es que no lo sabes. Este operador siempre hace referencia a la clase en la que nos encontramos, sea cual sea. Por lo tanto, cuando usamos la función GetPointer con el argumento this, estamos solicitando que la aplicación obtenga el puntero que referencia la clase actual. En este caso, esa clase es C_Controls. Sin embargo, C_Controls hereda de C_Terminal de forma privada. Sin embargo, como el código está referenciando la clase C_Controls como si formara parte de ella, esto permite que el constructor de C_DrawImage pueda acceder a la clase C_Terminal.
Podrías pensar que esto rompe el sistema de herencia, pero no es así. En C++, existe un operador que puede lograr algo similar, aunque mucho más peligroso y complicado que lo que se hace aquí con esta construcción que estoy mostrando. Sin embargo, no es momento de bajar la guardia. Esta construcción tiene sus riesgos. Si creas la clase C_Terminal sin cuidado, permitiendo que cualquier procedimiento o función modifique los valores privados de la clase o si dejas las variables fuera de la cláusula privada, esta construcción permitirá que la clase C_DrawImage modifique los valores dentro de C_Terminal, incluso sin tener acceso directo a esta y con la clase C_Terminal heredada de manera privada por la clase C_Controls. Por lo tanto, si codificas sin un control adecuado, esta construcción puede dañar potencialmente tu código. Sin embargo, si programas de manera controlada y, sobre todo, tomando las precauciones necesarias para mantener un buen encapsulamiento de los datos, no tendrás problemas al usar esta construcción.
De todos modos, quería llamar tu atención sobre esta construcción porque, aunque es bastante exótica, nos permite ampliar aún más las funcionalidades y nos ofrece la posibilidad de realizar cosas interesantes, siempre y cuando tomes las debidas precauciones.
Sin la existencia de la herencia, no sería posible usar esta construcción. Incluso si intentas acceder a algún componente, función o procedimiento de la clase C_Controls desde la clase C_DrawImage, no podrás hacerlo. Aunque estamos refiriéndonos a la clase C_Controls a través del operador this, solo la parte correspondiente a la clase C_Terminal podrá accederse a ella en la clase C_DrawImage.
A continuación, veremos los últimos detalles que merecen ser destacados antes de terminar este artículo. Para ello, revisemos el procedimiento de la línea 98, que es el responsable de posicionar el pin del control deslizante. Además, controla mínimamente los botones situados a la izquierda y derecha del control deslizante y de la barra de bloqueo. Sin embargo, lo que nos interesa aquí está entre las líneas 110 y 117. ¿Qué encontramos aquí? Observa en el código de la clase C_DrawImage que el procedimiento Paint puede recibir una string como último argumento. Esta cadena se utilizará para mostrar información específica del objeto cuando el mouse esté posicionado sobre él. Hasta ahora, nuestra única referencia eran las intuiciones sobre lo que estaba ocurriendo. Pero ahora contamos con algo más interesante. Podemos colocar de manera sencilla y directa un mensaje personalizado que nos informe de algo relevante. Las dos primeras, presentes en las líneas 112 y 113, son de baja importancia en la mayoría de los casos. Sin embargo, fíjate en el mensaje de la línea 114, es interesante. Cuando ajustes el PIN del control deslizante y luego posiciones el mouse sobre él, podrás ver la posición relativa a la que apunta. Piensa en cómo esto puede resultar útil en diversos momentos.
Supongamos que tienes en tu poder un conjunto de datos y sabes que te interesa analizar algo de ese conjunto. Sin embargo, como el sistema de repetición/simulador no permite saltar directamente a una posición específica dentro del conjunto de datos, esto resulta, en cierto modo, apropiado, ya que hace que el proceso sea menos predecible. Puedes probar a ver cuál es la posición inmediatamente anterior a lo que deseas analizar mediante ensayo y error. Al observar el valor del pin antes de habilitar la reproducción, puedes determinar si has pasado el punto de interés o si aún no. Si ya lo has pasado, lo único que tendrás que hacer es cerrar la aplicación y abrirla nuevamente, dejando el pin algunas posiciones antes. Anteriormente, esto era muy difícil de realizar porque no tenías una referencia sobre la posición que el sistema realmente indicaba.
Conclusión
Con esto llegamos al final de otro artículo. Espero que el conocimiento contenido en él sea útil en otros aspectos además de lo que estoy mostrando aquí. Soy consciente de que muchos pueden encontrar extraño lo que he presentado en este artículo. Otros pueden considerarlo innecesario, dado que el indicador de control ya funcionaba perfectamente. Sin embargo, el objetivo de este y de todos mis artículos no es proponer una solución definitiva, sino mostrar que los aspirantes a programadores MQL5 tienen mucho que aprender. Y que siempre debes estar dispuesto a aprender y explorar nuevas ideas y conceptos diferentes.
Aunque ya no se incluya el código fuente como anexo, los artículos incluyen el código de forma completa. Incluso las partes referenciadas como fragmentos forman parte, de algún modo, del código. Todo lo que necesitas hacer es capturar el fragmento mostrado en el artículo y sustituir el código por ese fragmento. De esta forma, siempre tendrás el código tal y como se presenta en los artículos.
La razón por la que no incluyo el código fuente en el anexo es precisamente debido a situaciones como las descritas en este artículo. Una persona con más experiencia podrá manejar posibles fallos y errores que puedan surgir, ya sea durante las modificaciones o en el uso normal. Sin embargo, los usuarios con menos experiencia podrían cometer errores graves al tratar de utilizar este tipo de código.
De todas formas, en el anexo tienes acceso a los ejecutables necesarios para utilizar el sistema. El resultado será similar al video mostrado a continuación. Un detalle: los datos utilizados se pueden descargar de artículos anteriores, donde proporcioné los ticks y barras utilizados en las demostraciones que puedes ver en los videos en forma de tres archivos ZIP.
Video de demostración
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12293





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso