
Entwicklung eines Replay-Systems (Teil 67): Verfeinerung des Kontrollindikators
Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Einführung
Im vorherigen Artikel „Entwicklung eines Replay-Systems (Teil 66): Abspielen des Dienstes (VII)“ haben wir eine Methode implementiert, um zu bestimmen, wann ein neuer Balken im Chart erscheint. Obwohl diese Methode für Modelle mit guter Liquidität sehr gut funktioniert, ist sie für Modelle mit geringer Liquidität oder solche, bei denen es häufig zu Auktionsabbrüchen kommt, keineswegs geeignet. Diese Art von Problemen wird in naher Zukunft gelöst werden.
Hier möchte ich Ihnen aber noch etwas anderes zeigen. Dies ist ebenfalls interessant, auch wenn es für Nutzer, die nur an der Nutzung der Anwendung interessiert sind, keinen großen Unterschied macht. Für diese Nutzer ist das, was ich zu Beginn dieses Artikels erkläre, von geringer Bedeutung. Aber für diejenigen, die studieren und wirklich darauf abzielen, ein erfahrener Programmierer zu werden, macht das, was ich als Nächstes zeige, einen bedeutenden Unterschied. Sie beeinflusst sowohl die Art und Weise, wie Sie Code schreiben, als auch wie Sie Ihren eigenen Code und den anderer Entwickler lesen. In der Tat können wir viel lernen, wenn wir beobachten, wie andere an die Problemlösung durch Code herangehen. Selbst Probleme, die in gewisser Weise verwandt sind, können je nach Entwickler auf völlig unterschiedliche Weise umgesetzt werden. Oder sogar von ein und demselben Entwickler, der seinen Ansatz in einer bestimmten Sprache überarbeitet und verfeinert.
In jedem Fall könnte sich das, was ich Ihnen jetzt vorstelle, in Zukunft als hilfreich erweisen.
In einem früheren Artikel habe ich einige Änderungen am Code vorgenommen, um zu verhindern, dass die Anwendung Fehler erzeugt, wenn sie entfernt wird. Dies kann entweder darauf zurückzuführen sein, dass der Nutzer das Chart schließt oder dass das Hauptchart geschlossen wurde. Das Problem wurde behoben. Derselbe Code kann jedoch erheblich verbessert werden. Dies würde zur Vereinfachung des Gesamtsystems beitragen, insbesondere im Hinblick auf die Pflege des Quellcodes.
Schauen wir uns also einmal an, worum es hier geht. Dazu beginnen wir mit dem ersten Thema dieses Artikels.
Verbesserung der Dynamik des Kontrollindikators
Der Code, an dem wir arbeiten werden, ist der des Kontrollindikators. Obwohl sie bereits recht gut funktioniert, gibt es noch Raum für Verbesserungen in Bezug auf die Codequalität. Schauen wir uns zunächst den ursprünglichen Code in der Header-Datei C_Controls.mqh an. Der vollständige Code ist unten angegeben:
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. //+------------------------------------------------------------------+
Quellcode der Datei C_Controls.mqh
Was uns hier wirklich interessiert, ist die Skalierbarkeit dieses Codes, d. h. seine Fähigkeit, zu wachsen oder neue Funktionen zu unterstützen, ohne dass wir zusätzlichen Code schreiben müssen. Auch wenn der obige Code im Moment nicht unbedingt neue Funktionen benötigt, gilt dies nicht für den Code, auf den er sich weitgehend stützt. Dies bezieht sich auf den Code, der für die Erstellung und Verwaltung von Objekten im Chart verantwortlich ist. Ich spreche von der Klasse C_DrawImage, die in Zeile 47 deklariert wird.
Selbst wenn man davon ausgeht, dass die Klasse C_Controls für die Erstellung der im Chart platzierten Objekte verantwortlich ist, ist das nicht ganz richtig, zumindest nicht für die meisten von ihnen. Die einzigen beiden Objekte, die C_Controls tatsächlich erstellt, sind diejenigen, die den Schieberegler unterstützen. Mit anderen Worten: Alles, was diese C_Controls-Klasse erstellt und dem Chart hinzufügt, befindet sich in der Prozedur in Zeile 53. Schauen wir uns nun den Code in der Header-Datei C_DrawImage.mqh genauer an. Hier ist der vollständige Code.
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. //+------------------------------------------------------------------+
Quellcode der Datei C_DrawImage.mqh
Jetzt wird es erst richtig interessant. Obwohl ich in die Klasse C_DrawImage eine Methode aufgenommen habe, die es uns ermöglicht, mit Bitmap-Dateien zu arbeiten, ist dies etwas, das nur selten (wenn überhaupt) tatsächlich verwendet wird. Der Grund dafür ist, dass ich Bitmaps als interne Ressourcen in die Anwendung selbst eingebettet habe. Infolgedessen wird ein Großteil dieses Codes nicht einmal von MQL5 kompiliert werden. Das ist jedoch nicht der Aspekt der Klasse C_DrawImage, auf den ich mich hier konzentrieren möchte.
Es gibt noch einen wichtigeren Punkt zu bedenken. Denken wir einen Moment nach: Der Code des Kontrollindikators greift nicht direkt auf die Klasse C_DrawImage zu. Tatsächlich weiß der Kontrollindikator nicht einmal von der Existenz von C_DrawImage. Aber sehen Sie genauer hin: In Zeile 8, wo die Klasse deklariert wird, erbt sie öffentlich von der Klasse C_Terminal. Das ist in Ordnung. Aber in der Klasse C_Controls, auf die der Kontrollindikator zugreift, sehen wir in Zeile 31, dass sie privat von der gleichen Klasse C_Terminal erbt. Wow! Jetzt werden die Dinge etwas komplizierter, und wir stehen möglicherweise vor einem Designproblem. Um die Sache noch schlimmer zu machen, werfen Sie einen Blick auf Zeile 156 der Klasse C_DrawImage. Beachten Sie, dass wir hier eine Prüfung durchführen, um möglicherweise einen neuen Indikator hinzuzufügen. Diese Kontrolle wird jedoch nie wirklich sinnvoll genutzt. Dies bedeutet, dass die Klasse C_Terminal hier nur einem Zweck dient: Sie ermöglicht uns den Zugriff auf die Chart-ID, damit wir Objekteigenschaften ändern können.
Aber denken Sie darüber nach: Warum sollten wir einer Klasse, die es nicht wirklich braucht, Zugriff auf die Klasse C_Terminal gewähren? Und selbst wenn C_DrawImage Zugriff auf C_Terminal bräuchte, wäre es weitaus sinnvoller, einen Zeiger an C_DrawImage zu übergeben, damit es über diesen Zeiger auf C_Terminal zugreifen kann.
Dies würde zu einem konsistenteren Code führen, der auch in Zukunft sicherer zu erweitern ist. Eine solche Erweiterung könnte durch Vererbung oder andere Entwurfsstrategien erfolgen. Aber auf jeden Fall wäre es viel sicherer und effizienter als das, was wir derzeit haben.
Und das bringt uns zu dem, was ich sagen möchte. Ändern wir diese Klasse C_DrawImage so, dass sie abhängig wird, aber gleichzeitig erlaubt, dass wir sie bei Bedarf erweitern können. Als Erstes muss also der Quellcode der Datei C_DrawImage.mqh geändert werden. Aber schauen wir uns zunächst die Header-Datei an, von der C_DrawImage.mqh abhängen wird. Die Datei heißt Macro.mqh. Es ist unten aufgeführt:
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. //+------------------------------------------------------------------+
Quellcode der Datei macro.mqh
Sehr gut. Die Datei Macro.mqh wird in die Datei C_Terminal.mqh aufgenommen. Wenn Sie Fragen dazu haben, lesen Sie die vorangegangenen Artikel, um zu verstehen, wie dies geschieht. Jetzt können wir einen Blick auf die neu geänderte Datei C_DrawImage.mqh werfen, die neue Verwendungsmöglichkeiten enthält. Der aktualisierte Code ist unten abgebildet:
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. //+------------------------------------------------------------------+
Quellcode der Datei C_DrawImage.mqh
Auf den ersten Blick mag man meinen, dass es keine großen Unterschiede gibt, aber es gibt sie, und sie machen die Klasse auf lange Sicht deutlich nachhaltiger und angenehmer im Gebrauch. Im Wesentlichen haben wir alles beibehalten, was vorher theoretisch gemacht wurde. Dies liegt daran, dass die Klasse immer noch auf die Verwendung von Ressourcen ausgerichtet ist, die in der ausführbaren Datei selbst eingebettet sind, anstatt sich auf externe Dateien zu verlassen.
Diese Art von Ansatz vereinfacht das, was wir tatsächlich tun müssen, erheblich. Sie werden feststellen, dass es jetzt viel weniger Code, weniger Routinen und weniger Verfahren zu verwalten gibt. Die wichtigste Änderung liegt jedoch im Klassenkonstruktor. Achten Sie genau auf Zeile 61, wo der Konstruktor deklariert wird. Beachten Sie, dass die Klasse C_Terminal jetzt als Zeiger übergeben wird. So können wir in Zeile 63 eine Methode von C_Terminal aufrufen. In ähnlicher Weise wird in Zeile 64 eine private Klassenvariable initialisiert, die den Zugriff auf die Chart-ID ermöglicht. Dies soll die Manipulation der Eigenschaften von Objekten ermöglichen, die von der Klasse behandelt werden. Diese Objekte sind speziell OBJ_BITMAP_LABEL. Zu diesem Zeitpunkt gibt es also nicht mehr viel zu tun.
Beachten Sie nun die Zeilen 65 und 66. Diese neue Konstruktion macht das Initialisierungsverfahren robuster und besser strukturiert. Sie ist zwischen den Zeilen 48 und 57 vollständig implementiert. Es ist eine relativ einfache Implementierung, zumindest aus meiner Sicht, und es verwendet aktiv Funktionen aus der MQL5-Standardbibliothek. Mit Ausnahme der Schleife in Zeile 52 besteht der gesamte Block aus MQL5-Bibliotheksfunktionen. Lesen Sie unbedingt die Dokumentation, um genau zu verstehen, was hier passiert.
Die Schleife in Zeile 52 tut jedoch etwas Interessantes, das eine Erklärung verdient. Beachten Sie, dass es sich um eine verschachtelte Schleife handelt. Schauen wir mal, was hier los ist. Andernfalls wird nicht klar, was wir später bei der Verwendung der Klasse C_DrawImage tun werden. Im ersten Teil der Schleife deklarieren wir zwei lokale Variablen. In dieser Schleife wird jedoch nur die Variable Y inkrementiert, sodass Sie sich nicht um die zweite Variable kümmern müssen - sie muss hier nur deklariert und initialisiert werden.
In der zweiten Schleife deklarieren wir zwei zusätzliche Variablen. Achten Sie nun genau darauf, was in Zeile 52 passiert. Die erste Variable, PM, wird deklariert und mit der Größe des zugewiesenen Arrays initialisiert. Diese Variable DARF NICHT geändert werden, da sie bestimmt, wann die Schleife beendet wird. Wir müssen nur PM mit PI vergleichen. Beachten Sie, dass er in derselben Zeile deklariert und mit einem Wert von Null initialisiert wird. Beachten Sie, dass wir in dieser Schleife absolut nichts mit den Variablen machen. Der einzige Zweck der Schleife in Zeile 52 ist die Steuerung der nachfolgenden Schleife, die in Zeile 53 deklariert wird.
In Zeile 53 haben wir nun eine komplexere Schleife. Aber mit etwas Aufmerksamkeit können Sie verstehen, was passiert. Zunächst deklarieren wir eine neue Variable C0. Wir initialisieren sie mit Null. Diese Variable sorgt dafür, dass die Schleife so lange läuft, bis sie die Bildbreite erreicht. Dieser Teil ist einfach. Jetzt kommt der komplexere Teil. Wir deklarieren auch eine zweite Variable, PF. Der Anfangswert dieser Variablen hängt von PI und der Operation ab, die wir mit dem Bild durchführen. In jedem Fall wird PI immer inkrementiert. PF kann jedoch inkrementiert oder nicht inkrementiert werden - es kann sogar dekrementiert werden. Um zu verstehen, warum, beachten Sie, dass in der Prozedurdeklaration ein boolescher Parameter mit dem Namen R180 vorhanden ist. Wenn R180 wahr ist, soll das resultierende Bild ein Spiegelbild des Originals sein. Um dies zu erreichen, wird PF mit der Summe aus PI und der Bildbreite initialisiert, wenn R180 wahr ist. Außerdem wird PF in jeder Schleifeniteration dekrementiert. Wenn R180 falsch ist, führen wir theoretisch eine direkte Kopie des Originalbildes in das Feld im Speicher durch.
Warum sage ich „theoretisch“? Aufgrund dessen, was in Zeile 54 passiert. Hier wenden wir einen Filter auf das Bild an und suchen nach einer bestimmten Farbe. Wenn diese Farbe gefunden wird, wird das entsprechende Pixel transparent. Aus diesem Grund sind sowohl die Kopie als auch die Spiegelung theoretisch mit dem Originalbild identisch. All diese Arbeit mit Arrays, einschließlich Speicherzuweisung und -freigabe, ist notwendig, weil MQL5 Zeiger nicht auf die gleiche Weise wie C oder C++ behandelt. Deshalb müssen wir diese Prozesse etwas anders umsetzen. Wann immer möglich, empfehle ich die Verwendung der Funktionen der MQL5-Standardbibliothek. Diese Funktionen sind weitaus optimierter als jeder nutzerdefinierte Code, den Sie schreiben könnten, um das gleiche Ergebnis zu erzielen. Darüber hinaus kommen alle Verbesserungen, die an der MQL5-Bibliothek vorgenommen werden, automatisch Ihrem Programm zugute. Im Gegensatz zu nutzerdefinierten Implementierungen, die manuell aktualisiert werden müssen.
Abgesehen davon gibt es ein weiteres Verfahren, das sich stark auf die MQL5-Bibliothek stützt. Ich meine die PAINT-Prozedur, die in Zeile 76 beginnt. Nahezu jeder Aufruf in dieser Methode ist an Funktionen der MQL5-Bibliothek gerichtet, mit Ausnahme des Aufrufs in Zeile 78. Diese Zeile ruft eine zwischen den Zeilen 21 und 46 definierte Prozedur auf. Passen Sie hier gut auf. In Zeile 28 wird Speicher für das Bild zugewiesen, das auf dem Objekt angezeigt werden soll. In Zeile 29 wird dann geprüft, ob das Bild geändert werden muss. Wenn dies nicht der Fall ist, kann das Bild direkt auf das Objekt übertragen werden. Um diesen Prozess so weit wie möglich zu beschleunigen, verwenden wir eine Bibliotheksfunktion, um das Bild direkt in das Array zu kopieren. Auch hier gilt: Wenn MQL5 Zeiger wie C oder C++ behandeln würde, würde dies anders umgesetzt werden. Wenn das Bild jedoch in irgendeiner Weise geändert werden muss, wird ab Zeile 30 eine Schleife eingefügt, um das Bild zu ändern, wobei die Abmessungen und die Transparenz je nach Bedarf angepasst werden.
Damit schließen wir diesen Abschnitt über die Änderungen, die am Code der Klasse C_DrawImage vorgenommen wurden. Da dieser Code geändert wurde, müssen wir nun auch die Klasse C_Controls aktualisieren, da sie derzeit direkt auf C_DrawImage zurückgreift. Werfen wir einen Blick auf den aktualisierten Code in der Datei 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. //+------------------------------------------------------------------+
Quellcode der Datei C_Controls.mqh
Das erste, was Sie an dem neuen Kodex bemerken, ist der Satz von Definitionen. Frage: Wo sind die Definitionen für die linke und rechte Taste? Vielleicht haben Sie bemerkt, dass zwischen den Zeilen 7 und 17 die Schaltflächen rechts und links nicht mehr vorhanden sind. Stattdessen sehen wir jetzt eine etwas andere Art der Definition. Interessanterweise verweisen wir auf die Bilder LEFT.BMP und LEFT_BLOCK.BMP, aber nicht auf ihre Gegenstücke auf der rechten Seite. Warum? Der Grund wurde in der Datei C_DrawImage.mqh erläutert. Da die rechte und die linke Schaltfläche im Wesentlichen identisch sind - genauer gesagt ist die eine ein Spiegelbild der anderen - ist es nicht notwendig, beide Schaltflächen zu definieren. Der Zugriff auf einen der beiden reicht aus, da der andere mit der Klasse C_DrawImage erzeugt werden kann.
Schauen wir mal, was sich sonst noch geändert hat. In Zeile 68 sehen wir die Erstellung und Kennzeichnung der Tasten für Play und Pause. Werfen Sie einen Blick auf Zeile 72 in derselben Routine. Am Ende der Deklaration ordnen wir dem Objekt eine zusätzliche Eigenschaft zu, die zusätzliche Informationen liefern kann. Das mag in diesem Fall keinen großen Unterschied machen, kann aber in anderen Zusammenhängen sehr nützlich sein.
Sowohl in dieser Routine, in der die Tasten Play/Pause erstellt werden, als auch in der folgenden Routine, in der die Schaltflächen und der Schieberegler erstellt werden, gibt es etwas Wichtiges, das erklärt werden muss. Um dies zu verstehen, kehren wir zum Code des Konstruktors der Klasse C_DrawImage zurück.
Wenn Sie sich den Quellcode für den Konstruktor in Zeile 61 von C_DrawImage ansehen, werden Sie feststellen, dass der erste Parameter ein Verweis auf die Klasse C_Terminal sein muss. Achten Sie genau darauf, denn das ist sehr wichtig. Der Konstruktor erwartet als seinen ersten Parameter einen Zeiger auf eine Instanz der Klasse C_Terminal. In der Header-Datei C_Controls.mqh in den Zeilen 71, 80, 81 und 82 werden diese Informationen jedoch nicht so weitergegeben, wie Sie es vielleicht erwarten. Gehen wir also zurück zur Deklaration der Klasse C_Controls, die sich in Zeile 27 befindet. Es ist wichtig, dass Sie verstehen, was ich jetzt erkläre, sonst macht der folgende Code wenig Sinn.
Die Klasse C_Controls erbt privat von der Klasse C_Terminal. Aus diesem Grund kann jeder Code innerhalb der Klasse C_Controls auf C_Terminal verweisen, als ob es Teil von C_Controls wäre. Es ist äußerst wichtig, dass Sie dies verstehen. Diese Referenz ist nur INNERHALB der Klasse C_Controls möglich. Dies ist darauf zurückzuführen, dass die Vererbung privat erfolgt. Wenn die Vererbung öffentlich wäre, hätte jeder Code, der auf C_Controls verweist, auch Zugriff auf die öffentlichen Funktionen und Methoden von C_Terminal. Na gut. Großartig. Wie hängt dies mit dem Konstruktor C_DrawImage zusammen? Wir machen Folgendes. Wenn Sie das Vererbungsmodell verstanden haben, können wir jetzt die Punkte verbinden. Der Konstruktor für C_DrawImage erwartet einen Zeiger auf C_Terminal. Wir übergeben jedoch einen Zeiger auf C_Controls. Wie funktioniert das? Wenn wir die Bibliotheksfunktion GetPointer verwenden und diese als Argument übergeben, beziehen wir uns tatsächlich auf die aktuelle Klasse - in diesem Fall C_Controls.
Um dies zu verstehen, müssen Sie wissen, was der Operator this bedeutet. Sie bezieht sich immer auf die aktuelle Klasse, unabhängig davon, welche Klasse das ist. Wenn wir also GetPointer(this) aufrufen, bitten wir die Anwendung, einen Zeiger auf die aktuelle Klasse zu erhalten. In diesem Fall ist es die Instanz von C_Controls. Aber C_Controls erbt privat von C_Terminal. Und da die Konstruktorlogik innerhalb dieser Vererbungsbeziehung arbeitet, kann der C_DrawImage-Konstruktor über den C_Controls-Zeiger auf C_Terminal zugreifen.
Man könnte meinen, dass dies gegen die Vererbungsregeln verstößt, aber das ist nicht der Fall. In C++ gibt es sogar einen Operator, der ein ähnliches Verhalten ermöglicht, obwohl er komplexer und potenziell gefährlicher ist. Dennoch sollten Sie nicht unvorsichtig sein. Diese Struktur hat ihre Risiken. Wenn Sie C_Terminal unvorsichtig entwerfen und jeder Funktion oder Methode erlauben, ihre privaten Mitglieder zu ändern oder Variablen außerhalb des privaten Bereichs freizugeben, könnte C_DrawImage den internen Zustand von C_Terminal ändern, auch ohne direkten Zugriff, da es von C_Controls geerbt wird. Wenn Sie also Code ohne Disziplin schreiben, kann diese Struktur zu einer ernsthaften Schwachstelle werden. Wenn Sie jedoch die richtige Kontrolle behalten und vor allem eine robuste Datenkapselung sicherstellen, werden Sie bei diesem Ansatz keine Probleme bekommen.
Mein Hauptziel ist es, Ihre Aufmerksamkeit auf diese Konstruktion zu lenken. Obwohl es unkonventionell erscheinen mag, ermöglicht es eine größere Flexibilität und einige sehr interessante Möglichkeiten - vorausgesetzt, Sie verwenden sie mit Bedacht.
Ohne die Vererbung wäre dieser Ansatz nicht möglich. Und selbst wenn Sie versuchen, von C_DrawImage aus auf Komponenten oder Funktionen von C_Controls zuzugreifen, wird Ihnen das nicht gelingen. Obwohl wir hier eine Instanz von C_Controls übergeben, ist nur der Teil des Objekts, der sich auf C_Terminal bezieht, tatsächlich für C_DrawImage zugänglich.
Bevor wir diesen Artikel abschließen, wollen wir uns noch ein paar Details ansehen, die Beachtung verdienen. Dazu untersuchen wir die Prozedur in Zeile 98, die für die Positionierung des Schiebereglers zuständig ist. Sie bietet auch eine minimale Kontrolle über die linken und rechten Tasten neben dem Schieberegler sowie über die Sperrleiste. Was uns jedoch wirklich interessiert, befindet sich zwischen den Zeilen 110 und 117. Was sehen wir hier? Schauen Sie sich die Methode Paint in der Klasse C_DrawImage an. Diese Methode akzeptiert eine Zeichenkette als letztes Argument. Diese Zeichenfolge wird verwendet, um eine Meldung im Stil eines Tooltips anzuzeigen, wenn die Maus über dem Objekt verweilt. Bisher haben wir uns auf unsere eigene Intuition verlassen, um zu verstehen, was während der Interaktion passiert. Wir können nutzerdefinierte Meldungen auf einfache und direkte Weise anzeigen - Meldungen, die während der Nutzung sinnvolles Feedback geben können. Die ersten beiden Meldungen (Zeilen 112 und 113) sind relativ unbedeutend und werden in den meisten Situationen nicht sehr relevant sein. Aber wenn wir uns die Nachricht in Zeile 114 ansehen, sehen wir etwas Interessantes. Wenn wir den Schieberegler einstellen und mit der Maus darüber fahren, sehen wir die relative Position, auf die er gerade zeigt. Stellen Sie sich vor, wie praktisch das in einer Vielzahl von Situationen sein kann.
Angenommen, Sie arbeiten mit einem Datensatz und wissen, dass es einen bestimmten Punkt in den Daten gibt, den Sie analysieren möchten. Da es bei der Wiedergabe/Simulation nicht möglich ist, direkt zu einer bestimmten Position zu springen, was in gewisser Weise eine gute Sache ist, da es verhindert, dass der Prozess zu vorhersehbar wird, ist man auf Versuch und Irrtum angewiesen. Wir können versuchen, die Position zu bestimmen, die unmittelbar vor der zu analysierenden Position liegt. Indem wir den Wert des Schiebereglers kontrollieren, bevor wir auf „Play“ drücken, können wir beurteilen, ob wir den interessanten Punkt bereits passiert haben oder nicht. Wenn wir zu weit gegangen sind, schließen wir die Anwendung, öffnen wir sie erneut und positionieren den Schieberegler ein paar Schritte früher. Dieser Ansatz, der früher aufgrund des fehlenden Bewusstseins für die relative Position recht schwierig war, ist jetzt viel einfacher zu handhaben.
Schlussfolgerung
Damit ist der heutige Artikel beendet. Ich hoffe, dass sich das hier vermittelte Wissen nicht nur für diese spezielle Implementierung als nützlich erweist, sondern auch für andere Programmieraufgaben, denen Sie möglicherweise begegnen. Ich verstehe, dass einige Leser das, was ich hier gezeigt habe, seltsam oder sogar unnötig finden könnten. Zumal die Kontrollindikator bereits gut funktionierte. Aber das Ziel dieses und aller meiner Artikel ist es nicht, eine endgültige Lösung zu bieten, sondern Ihnen, dem angehenden MQL5-Programmierer, dabei zu helfen, zu erkennen, wie viel es zu lernen gibt und wie wichtig es ist, offen für neue Ideen und andere Perspektiven zu bleiben.
Obwohl ich diesen Artikeln keine Quellcodedateien mehr beifüge, ist der gesamte relevante Code immer noch vollständig im Text enthalten. Selbst der als „Fragmente“ bezeichnete Code ist Teil eines funktionalen Ganzen. Sie brauchen nur das gezeigte Fragment zu kopieren und den entsprechenden Abschnitt in Ihrem Code zu ersetzen. Dadurch wird sichergestellt, dass Ihr Code mit den Anweisungen des Artikels synchronisiert bleibt.
Der Grund, warum ich keine vollständigen Quelldateien mehr zur Verfügung stelle, liegt genau in der Art der Konstruktion, die wir in diesem Artikel besprochen haben. Ein erfahrener Entwickler kann in der Regel mit Fehlern oder unerwartetem Verhalten während der Änderung oder der regulären Nutzung umgehen. Weniger erfahrene Nutzer können jedoch beim Versuch, diese Art von Logik zu implementieren, schwere Fehler machen.
In jedem Fall erhalten Sie in der Anwendung Zugang zu den ausführbaren Dateien, die für die Arbeit mit dem System erforderlich sind. Das Ergebnis sollte dem unten gezeigten Demo-Video ähneln. Ein letzter Hinweis: Die in den Demonstrationen verwendeten Daten können aus früheren Artikeln heruntergeladen werden, wo ich sie in drei ZIP-Dateien zur Verfügung gestellt habe, die die in diesen Beispielen verwendeten Ticks und Balken enthalten.
Demo-Video
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12293





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.