开发回放系统(第 67 部分):完善控制指标
此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。
概述
在上一篇文章“开发回放系统(第 66 部分):玩转服务(七) “中,我们实现了一种方法来确定图表上何时会出现新的柱形。尽管这种方法对于流动性良好的模型非常有效,但它绝不适合用于低流动性模型或经常被竞价中止的模型。这类问题将在不久的将来得到解决。
不过,在这里,我想向大家展示一些其他的东西。这同样有趣,尽管对于只对使用应用程序感兴趣的用户来说没有太大区别。对于这些用户来说,我在本文开头开始探讨的内容几乎没有什么意义。但对于那些正在学习并真正致力于成为一名熟练程序员的人来说,我接下来要展示的内容将产生重大影响。它既影响你编写代码的方式,也影响你阅读自己和其他开发人员的代码的方式。事实上,通过观察其他人如何通过代码解决问题,我们可以学到很多东西。根据开发人员的不同,即使是有些相关的问题也可以以完全不同的方式实现。甚至由同一开发人员用给定的语言重新审视和改进他们的方法。
无论如何,我即将介绍的内容可能会在未来的某个时候对你有所帮助。
在上一篇文章中,我对代码进行了一些更改,以防止应用程序在删除时产生错误。这可能是由于用户关闭了它,也可能是因为主图表被关闭了。问题已解决。然而,同样的代码可以得到显著改进。这样做将有助于简化整个系统,特别是在维护源代码方面。
那么,让我们来看看这是怎么回事。为此,我们将从本文的第一个主题开始。
提升控制指标的动态性
我们将要处理的代码是控制指标的代码。虽然它已经运行得相当好,但在代码质量方面仍有改进的空间。首先,让我们回顾一下头文件 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_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_Controls.mqh 文件的源代码
对我们来说,真正重要的是这段代码的可扩展性,即它能够在不需要我们编写额外代码的情况下扩展或支持新功能。虽然上述代码目前不一定需要新功能,但它广泛依赖的代码却不需要。这是指负责在图表上创建和管理对象的代码。我说的是 C_DrawImage 类,它在第 47 行声明。
即使你假设 C_Controls 类负责创建放置在图表上的对象,这也不完全准确,至少对大多数对象来说是不准确的。事实上,C_Controls 实际创建的仅有的两个对象是支持滑块控件的对象。换句话说,这个 C_Controls 类创建并添加到图表中的所有内容都可以在第 53 行的过程中找到。话虽如此,让我们仔细看看头文件 C_DrawImage.mqh 中的代码。这是其完整代码。
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_DrawImage.mqh 文件的源代码
现在事情将变得真正有趣。尽管在 C_DrawImage 类中,我包含了一个允许我们处理位图文件的方法,但实际上很少(如果有的话)使用这个方法。原因是我将位图作为内部资源嵌入到应用程序本身中。因此,其中的大部分代码甚至无法由 MQL5 编译。然而,这不是我想在这里关注的 C_DrawImage 类的方面。
还有一个更重要的问题需要考虑。让我们想一想:控制指标代码并不直接访问 C_DrawImage 类。事实上,控制指标甚至没有意识到 C_DrawImage 的存在。但仔细看看:在声明类的第 8行,它公开继承自 C_Terminal 类。没关系,但是在控制指标访问的 C_Controls 类中,我们在第 31 行看到它是从同一个 C_Terminal 类私有继承的。哇!现在事情变得有点复杂,我们可能面临设计问题。更糟糕的是,看一下 C_DrawImage 类的第 156 行。请注意,我们正在那里执行检查,以可能添加一个新的指标。然而,这种检查实际上从未以任何有意义的方式使用过。这意味着 C_Terminal 类在这里只服务于一个目的:让我们访问图表 ID,以便我们可以修改对象属性。
但是想想看:为什么我们应该为一个并不真正需要 C_Terminal 类的类授予访问权限?即使 C_DrawImage 确实需要访问 C_Terminal,更谨慎的做法也是将指针传递给 C_DrawImage,允许它通过该指针访问 C_ Terminal。
这将产生更加一致的代码,并且将来扩展起来也更安全。这种扩展可以通过继承或其他设计策略来实现。但无论如何,它将比我们目前拥有的更安全、更高效。
这就引出了我想说的话。让我们更改这个 C_DrawImage 类,使其变得依赖,但同时允许我们在必要时扩展它。因此,首先要做的是更改 C_DrawImage.mqh 文件的源代码。但首先,让我们看看 C_DrawImage.mqh 所依赖的头文件。该文件是 Macro.mqh。如下所示:
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. //+------------------------------------------------------------------+
macro.mqh 文件的源代码
非常好,文件 Macro.mqh 将被包含在 C_Terminal.mqh 文件中。如果您对此有任何疑问,请参阅前面的文章以了解如何做到这一点。现在我们可以看看新修改的 C_DrawImage.mqh 文件,其中包括新的使用可能性。更新后的代码如下所示:
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_DrawImage.mqh 文件的源代码
乍一看,你可能会认为没有什么大的区别,但它们确实存在,而且从长远来看,它们使类更加可持续和令人愉快。从理论上讲,我们基本上保留了以前所做的一切。这是因为该类仍然专注于使用嵌入在可执行文件本身中的资源,而不是依赖于外部文件。
这种方法大大简化了我们实际需要做的事情。您会注意到,现在需要管理的代码、例程和过程都少得多。但关键的变化在于类构造函数。请密切关注第 61 行,该行声明了构造函数。请注意,C_Terminal 类现在作为指针传递。因此,在第 63 行中,我们可以从 C_Terminal 调用一个方法。同样,在第 64 行,我们初始化一个私有类变量来提供对图表 ID 的访问。这是为了能够操纵类将处理的对象的属性。这些对象具体来说是 OBJ_BITMAP_LABEL。所以在这一点上没有太多事情要做。
现在观察第 65 行和第 66 行。这种新的构造使初始化过程更加稳健,结构更好。它在第 48 行和 57 行之间完全实现。至少在我看来,这是一个相对简单的实现,它积极使用 MQL5 标准库中的函数。除了第 52 行的循环外,整个代码块由 MQL5 库函数组成。一定要研究文档,以准确了解这里发生了什么。
然而,第 52 行中的循环做了一些有趣的事情,值得解释。请注意,这是一个嵌套循环。让我们看看这里发生了什么。否则,我们将不清楚稍后使用 C_DrawImage 类时会做什么。在循环的第一部分,我们声明两个局部变量。然而,在这个循环中只有 Y 变量递增,所以你不需要担心第二个变量 —— 它只需要在这里声明和初始化。
在第二个循环中,我们声明了两个额外的变量。现在密切关注第 52 行发生的情况。第一个变量 PM 被声明并初始化为分配数组的大小。不得修改此变量,因为它决定循环何时终止。我们只需要比较 PM 和 PI。请注意,它也在同一行声明,并用零值初始化。请注意:我们对这个循环中的变量完全不做任何事情。第 52 行上的循环的唯一目的是控制第 53 行上声明的后续循环。
现在,在第 53 行,我们有一个更复杂的循环。但只要稍加注意,你就能理解发生了什么。首先,我们声明一个新变量 C0。我们用 0 初始化它。此变量可确保循环运行,直到达到图像宽度。这部分很简单,现在来看看更复杂的部分。我们还声明了第二个变量 PF,该变量的初始值取决于 PI 和我们对图像执行的操作。无论如何,PI 总是递增的。然而,PF 可以递增也可以不递增,甚至可以递减。要理解原因,请注意,在过程声明中,我们有一个名为 R180 的布尔参数。当 R180 为 true 时,我们希望生成的图像是原始图像的镜像。为了实现这一点,当 R180 为 true 时,PF 将使用 PI 和图像宽度的总和进行初始化。此外,PF 在每次循环迭代中递减。如果 R180 为 false,我们理论上会将原始图像直接复制到内存中的数组中。
为什么我说“理论上”?因为第 54 行发生的事情。在这里,我们对图像应用过滤器,搜索特定的颜色。当找到此颜色时,相应的像素变为透明。因此,副本和镜像在理论上都与原始图像相同。所有这些与数组相关的工作(包括内存分配和释放)都是必要的,因为 MQL5 处理指针的方式与 C 或 C++ 不同。因此,我们必须以不同的方式实现这些过程。只要有可能,我建议使用 MQL5 标准库提供的函数。这些函数比您可能编写的任何自定义代码都要优化得多,以实现相同的结果。此外,对 MQL5 库所做的任何改进都将自动使您的程序受益。与必须手动更新的自定义实现不同。
也就是说,还有另一个程序严重依赖 MQL5 库。我指的是从第 76 行开始的 PAINT 过程。除了第 78 行的调用外,此方法中的几乎所有调用都指向 MQL5 库函数。此行调用第 21 行和第 46 行之间定义的过程。请密切关注此处。在第 28 行,我们为将显示在对象上的图像分配内存。然后,在第 29 行,我们检查图像是否需要修改。如果没有发生这种情况,则可以将图像直接传输到对象上。为了尽可能加快这一过程,我们使用库函数将图像直接复制到数组中。同样,如果 MQL5 像 C 或 C++ 一样处理指针,则实现方式会有所不同。但是,如果图像确实需要以任何方式进行修改,我们会从第 30 行开始进入一个循环来修改图像,根据需要调整其尺寸和透明度。
至此,我们结束了本节对 C_DrawImage 类代码所做的更改。由于此代码已被修改,我们现在也需要更新 C_Controls 类,因为它目前直接使用 C_DrawImage。让我们看看 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_Controls.mqh 文件的源代码
关于新代码,您首先注意到的是一组定义。问题:左右按钮的定义在哪里?您可能已经注意到,在第 7 行和第 17 行之间,左右按钮不再存在。相反,我们现在看到了一种稍微不同的定义。有趣的是,我们引用的是左侧图像 LEFT.BMP 和 LEFT_BLOCK.BMP,但没有引用它们右侧的图像。为什么呢?C_DrawImage.mqh 文件中解释了原因。由于左右按钮基本相同 —— 更确切地说,一个是另一个的镜像 —— 因此不需要定义这两个按钮。只访问其中一个就足够了,因为另一个可以使用 C_DrawImage 类生成。
让我们看看还有什么变化。在第 68 行,我们看到播放和暂停按钮的创建和标识。看看同一例程中的第 72 行。在声明的末尾,我们为对象分配了一个额外的属性,这可以提供额外的信息。虽然这在这种情况下可能没有太大区别,但在其他情况下可能非常有用。
在这个创建播放/暂停按钮的例程和下面创建按钮和滑块的例程中,都有一些重要的事情需要解释。为了理解它,让我们回到 C_DrawImage 类的构造函数代码。
如果你查看 C_DrawImage 第 61 行构造函数的源代码,你会注意到第一个参数必须是对 C_Terminal 类的引用。请密切关注这一点,因为它非常重要。构造函数期望接收一个指向 C_Terminal 类实例的指针作为其第一个参数。但是,回到 C_Controls.mqh 头文件的第 71、80、81 和 82 行,我们没有以您期望的方式传递该信息。让我们回到 C_Controls 类的声明,它位于第 27 行。你必须理解我要解释的内容;否则,以下代码将毫无意义。
C_Controls 类从 C_Terminal 类私有继承。因此,C_Controls 类中的任何代码都可以引用 C_Terminal,就像它是 C_Controls 的一部分一样。理解这一点极其重要。此引用仅在 C_Controls 类内部可用。这是因为继承是私有发生的。如果继承是公有的,任何引用 C_Controls 的代码也可以访问 C_Terminal 的公有函数和方法。好了,非常好。这与 C_DrawImage 构造函数有何关系?这是我们所做的。如果你已经理解了继承模型,我们现在可以把这些点联系起来。C_DrawImage 的构造函数需要一个指向 C_Terminal 的指针。然而我们正在向 C_Controls 传递一个指针。这是如何运作的?当我们使用库函数 GetPointer 并将其作为参数传递时,我们实际上是在引用当前类 —— 在本例中为 C_Controls。
要理解这一点,您需要知道 this 运算符的含义。它总是指当前的类,无论是什么类。因此,当我们调用 GetPointer(this) 时,我们要求应用程序获取指向当前类的指针。在这种情况下,它是 C_Controls 的实例。但 C_Controls 私有继承自 C_Terminal。由于构造函数逻辑在该继承关系内运行,因此 C_DrawImage 构造函数可以通过 C_Controls 指针访问 C_Terminal。
你可能会认为这违反了继承规则,但事实并非如此。在 C++ 中,甚至有一个允许类似行为的运算符,尽管它更复杂并且可能更危险。不过,你不应该放松警惕。这种结构有其风险。如果你不小心设计了 C_Terminal,允许任何函数或方法更改其私有成员,或者在私有范围之外暴露变量,那么 C_DrawImage 最终可能会修改 C_Terminal 的内部状态,即使没有直接访问,因为它是由 C_Controls 继承的。因此,如果你在没有纪律的情况下编写代码,这种结构可能会成为一个严重的漏洞。但是,如果您保持适当的控制,特别是确保稳健的数据封装,使用这种方法就不会遇到问题。
我在这里的主要目标是引起您对这一结构的注意。虽然它看起来很不寻常,但它允许更大的灵活性,并实现了一些非常有趣的功能 —— 只要你小心使用。
如果没有继承,这种方法是不可能的。即使您尝试从 C_DrawImage 中访问 C_Controls 的组件或函数,也无法访问。尽管我们通过它传递了一个 C_Controls 实例,但 C_DrawImage 实际上只能访问与 C_Terminal 相关的对象部分。
在结束本文之前,让我们来看看一些值得关注的最后细节。为此,我们将检查第 98 行上的程序,该程序负责定位滑销。它还提供了对滑块附近的左右按钮以及锁定栏的最小控制。然而,我们真正感兴趣的是第 110 行和第 117 行之间。我们在这里看到了什么?看一下 C_DrawImage 类中的 Paint 方法。此方法接受字符串作为最后一个参数。当鼠标悬停在对象上时,此字符串用于显示工具提示样式消息。到目前为止,我们依靠自己的直觉来理解互动过程中发生了什么。我们可以以简单直接的方式显示自定义消息,这些消息可以在使用过程中提供有意义的反馈。前两条消息(第 112 行和第 113 行)相对次要,在大多数情况下不会有太大相关性。但如果我们看看第 114 行的消息,我们会看到一些有趣的东西。当您调整滑块销并将鼠标悬停在其上时,您将看到它当前指向的相对位置。想想这在各种情况下有多方便。
假设你正在处理一个数据集,你知道数据中有一个特定的点需要分析。由于回放/模拟器不允许直接跳转到特定位置,这在某种程度上是一件好事,因为它可以防止过程变得太可预测,因此您只能依靠反复试验。我们可以尝试确定哪个位置紧挨着我们想要分析的位置。通过在点击“播放”之前观察引线的值,你可以判断你是否已经通过了兴趣点。如果走得太远,只需关闭并重新打开应用程序,然后提前几步定位引线。由于缺乏相对位置意识,这种方法曾经非常困难,但现在更容易管理。
结论
今天的文章到此结束。我希望这里分享的知识不仅对这个特定的实现有用,而且对您可能遇到的其他编程任务也有用。我明白,一些读者可能会觉得我在这里展示的内容很奇怪,甚至没有必要。尤其是控制指标已经运行良好。但这篇文章和我所有的文章的目标不是提供一个明确的解决方案,而是帮助你,有抱负的 MQL5 程序员,意识到有多少东西需要学习,以及对新想法和不同观点保持开放的重要性。
虽然我不再将源代码文件附加到这些文章中,但所有相关代码仍然完整地显示在文本中。即使是被称为“片段”的代码也是功能整体的一部分。您只需要复制显示的片段并替换代码中的相应部分。这可以确保您的代码与本文的说明保持同步。
我不再提供完整的源文件的原因正是因为我们在本文中讨论的那种构造。经验丰富的开发人员通常可以在修改或常规使用过程中处理错误或意外行为。然而,经验不足的用户在尝试实现这种逻辑时可能会犯严重的错误。
在任何情况下,在应用程序中,您都可以访问使用系统所需的可执行文件。结果应该类似于下面显示的演示视频。最后一点:演示中使用的数据可以从前面的文章中下载,我在三个 ZIP 文件中提供了这些数据,其中包含了这些示例中使用的分时报价和柱形。
演示视频
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12293
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
Connexus客户端(第七部分):添加客户端层
您应当知道的 MQL5 向导技术(第 42 部分):ADX 振荡器
原子轨道搜索(AOS)算法:改进与拓展
从Python到MQL5:量子启发式交易系统的探索之旅