Русский Español Português
preview
Developing a Replay System (Part 67): Refining the Control Indicator

Developing a Replay System (Part 67): Refining the Control Indicator

MetaTrader 5Examples | 8 May 2025, 14:04
706 0
Daniel Jose
Daniel Jose

The content presented here is intended solely for educational purposes. Under no circumstances should the application be viewed for any purpose other than to learn and master the concepts presented.


Introduction

In the previous article "Developing a Replay System (Part 66): Playing the service (VII)", we implemented a method to determine when a new bar would appear on the chart. Although that method works very well for models with good liquidity, it is by no means suitable for use with low-liquidity models or those frequently subject to auction halts. This type of issue will be resolved in the near future.

However, here I would like to show you something else. This is equally interesting, although it doesn't make much of a difference for users who are only interested in using the application. For those users, what I begin exploring at the start of this article is of little relevance. But for those who are studying and truly aiming to become a skilled programmer, what I'll show next makes a significant difference. It impacts both how you write code and how you read your own code as well as that of other developers. Indeed, we can learn a great deal by observing how others approach problem-solving through code. Even problems that are somewhat related can be implemented in completely different ways, depending on the developer. Or even by the same developer revisiting and refining their approach in a given language.

In any case, what I'm about to present may prove helpful to you at some point in the future.

In a previous article, I made a few changes to the code in order to prevent the application from generating errors when being removed. This could happen either due to the user closing it or because the main chart was closed. The issue was resolved. However, that same code can be significantly improved. Doing so would help simplify the overall system, especially in terms of maintaining the source code.

So, let's take a look at what this is all about. To do that, we'll begin with the first topic of this article.


Improving the Control Indicator's Dynamism

The code we'll be working on is that of the control indicator. Although it is already functioning fairly well, there is still room for improvement in terms of code quality. First, let's review the original code found in the header file C_Controls.mqh. It's full code is provided below:

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. //+------------------------------------------------------------------+

Source code of the C_Controls.mqh file

What truly matters to us here is how scalable this code is, i.e. its ability to grow or support new functionalities without requiring us to write additional code. Although the code above may not necessarily need new features at the moment, the same cannot be said for the code it relies on extensively. This refers to the code responsible for creating and managing objects on the chart. I'm talking about the C_DrawImage class, which is declared on line 47.

Even if you assume that the C_Controls class is responsible for creating the objects placed on the chart, that is not entirely accurate, at least not for the majority of them. In fact, the only two objects that C_Controls actually creates are those supporting the slider control. In other words, everything this C_Controls class creates and adds to the chart is found within the procedure located on line 53. With that said, let's take a closer look at the code in the header file C_DrawImage.mqh. Here is its full 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. //+------------------------------------------------------------------+

Source code of the C_DrawImage.mqh file

Now things are about to get truly interesting. Although in the C_DrawImage class I've included a method that allows us to work with bitmap files, this is something that will rarely (if ever) actually be used. The reason is that I embedded bitmaps as internal resources within the application itself. As a result, much of this code will not even be compiled by MQL5. However, that's not the aspect of the C_DrawImage class I want to focus on here.

There's a more important point to consider. Let's think for a moment: the control indicator code does not directly access the C_DrawImage class. In fact, the control indicator isn't even aware of the existence of C_DrawImage. But take a closer look: on line 8, where the class is declared, it publicly inherits from the C_Terminal class. That's fine. But in the C_Controls class, which is accessed by the control indicator, we see in line 31 that it inherits privately from the same C_Terminal class. Wow! Now things get a bit more complicated, and we may be facing a design problem. To make matters worse, take a look at line 156 of the C_DrawImage class. Notice that we're performing a check there to potentially add a new indicator. However, this check is never actually used in any meaningful way. This implies that the C_Terminal class is only serving one purpose here: to give us access to the chart ID so we can modify object properties.

But think about it: why should we be granting access to the C_Terminal class for a class that doesn't really need it? And even if C_DrawImage did need access to C_Terminal, it would be far more prudent to pass a pointer to C_DrawImage, allowing it to access C_Terminal through that pointer.

This would result in more consistent code, code that is also safer to extend in the future. Such extension could be done through inheritance or other design strategies. But in any case, it would be much safer and more efficient than what we currently have.

And that brings us to what I want to say. Let's change this C_DrawImage class so that it becomes dependent, but at the same time allows us to extend it if necessary. So the first thing to do is to make a change to the source code of the C_DrawImage.mqh file. But first, let's look at the header file that C_DrawImage.mqh will depend on. The file is Macro.mqh. It is shown below:

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. //+------------------------------------------------------------------+

Source code of the macro.mqh file

Very well. The file Macro.mqh will be included in the C_Terminal.mqh file. If you have any questions about this, refer to the previous articles to understand how this is being done. Now we can take a look at the newly modified C_DrawImage.mqh file, which includes new usage possibilities. The updated code is shown below:

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. //+------------------------------------------------------------------+

Source code of the C_DrawImage.mqh file

At first glance, you may think there are no major differences, but they do exist, and they make the class significantly more sustainable and pleasant to use in the long term. Essentially, we've retained everything that was previously being done, in theory. This is because the class is still focused on using resources embedded within the executable itself, rather than relying on external files.

This type of approach greatly simplifies what we actually need to do. You'll notice that there's now much less code, fewer routines, and fewer procedures to manage. But the key change lies in the class constructor. Pay close attention to line 61, where the constructor is declared. Note that the C_Terminal class is now passed as a pointer. Thus, in line 63, we can call a method from C_Terminal. Similarly, on line 64, we initialize a private class variable to provide access to the chart ID. This is intended to enable manipulation of the properties of objects that the class will handle. These objects are specifically OBJ_BITMAP_LABEL. So there's not much more to do at this point.

Now observe lines 65 and 66. This new construction makes the initialization procedure more robust and better structured. It is fully implemented between lines 48 and 57. It's a relatively straightforward implementation, at least from my perspective, and it actively uses functions from the MQL5 standard library. With the exception of the loop on line 52, the entire block consists of MQL5 library functions. Be sure to study the documentation to understand exactly what's happening here.

However, the loop in line 52 does something interesting that deserves explanation. Note that it's a nested loop. Let's see what's going on here. Otherwise, it will not be clear what we will do later when using the C_DrawImage class. In the first part of the loop, we declare two local variables. However, only the Y variable is incremented in this loop, so you don’t need to worry about the second variable—it just needs to be declared and initialized here.

In the second loop, we declare two additional variables. Now pay close attention to what happens in line 52. The first variable, PM, is declared and initialized with the size of the allocated array. This variable MYST NOT be modified, as it determines when the loop will terminate. We only need to compare PM with PI. Note that it is also declared on the same line and initialized with a value of zero. Take note: we are doing absolutely nothing with the variables inside this loop. The sole purpose of the loop on line 52 is to control the subsequent loop declared on line 53.

Now, on line 53, we have a more complex loop. But with some attention, you can understand what's happening. First, we declare a new variable C0. We initialize it with zero. This variable ensures that the loop runs until it reaches the image width. This part is simple. Now for the more complex part. We also declare a second variable, PF. This variable's initial value depends on PI and the operation we're performing on the image. In any case, PI is always incremented. PF, however, may or may not be incremented - it can even be decremented. To understand why, note that in the procedure declaration, we have a boolean parameter named R180. When R180 is true, we want the resulting image to be a mirror of the original. To accomplish this, when R180 is true, PF is initialized with the sum of PI and the image width. Furthermore, PF is decremented in each loop iteration. If R180 is false, we theoretically perform a direct copy of the original image into the array in memory.

Why do I say "theoretically"? Because of what happens on line 54. Here, we apply a filter to the image, searching for a specific color. When this color is found, the corresponding pixel becomes transparent. For this reason, both the copy and the mirror are theoretically identical to the original image. All this work with arrays, including memory allocation and deallocation, is necessary because MQL5 does not handle pointers in the same way as C or C++. Therefore, we must implement these processes somewhat differently. Whenever possible, I recommend using the functions provided by the MQL5 standard library. These functions are far more optimized than any custom code you might write to achieve the same result. Additionally, any improvements made to the MQL5 library will automatically benefit your program. Unlike custom implementations, which must be manually updated.

That said, there’s another procedure that heavily relies on the MQL5 library. I mean the PAINT procedure, which starts on line 76. Nearly every call in this method is directed to MQL5 library functions, except for the one on line 78. This line invokes a procedure defined between lines 21 and 46. Pay close attention here. On line 28, we allocate memory for the image that will be displayed on the object. Then, on line 29, we check whether the image will need to be modified. If this does not happen, then the image can be transferred directly to the object. To speed this process up as much as possible, we use a library function to copy the image directly into the array. Again, if MQL5 handled pointers like C or C++, this would be implemented differently. But if the image does need to be modified in any way, we enter a loop starting on line 30 to modify the image, adjusting its dimensions and transparency as necessary.

With that, we conclude this section on the changes made to the C_DrawImage class code. Since this code has been modified, we now need to update the C_Controls class as well, since it currently makes direct use of C_DrawImage. Let's take a look at the updated code in the C_Controls.mqh file.

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. //+------------------------------------------------------------------+

Source code of the C_Controls.mqh file

The first thing that you notce about the new code is the set of definitions. Question: where are the definitions for the left and right buttons? You may have noticed that between lines 7 and 17, the right and left buttons are no longer present. Instead, we now see a slightly different kind of definition. Interestingly, we are referencing the images LEFT.BMP and LEFT_BLOCK.BMP, but not their right-side counterparts. Why? The reason was explained in the C_DrawImage.mqh file. Since the right and left buttons are essentially identical—more precisely, one is a mirror image of the other—there is no need to define both buttons. Accessing just one of them is enough, as the other can be generated using the C_DrawImage class.

Let's see what else has changed. In line 68, we see the creation and identification of the play and pause buttons. Take a look at line 72 within the same routine. At the end of the declaration, we’re assigning an additional property to the object, which can provide extra information. While this may not make a huge difference in this case, it can be quite useful in other contexts.

In both this routine, where the play/pause buttons are created and the one below, which creates the buttons and slider, there's something important that needs to be explained. To understand it, let's return to the constructor code for the C_DrawImage class.

If you look at the source code for the constructor on line 61 of C_DrawImage, you'll notice that the first parameter must be a reference to the C_Terminal class. Pay close attention to this, as it's very important. The constructor expects to receive, as its first parameter, a pointer to an instance of the C_Terminal class. However, back in the C_Controls.mqh header file, in lines 71, 80, 81, and 82, we're not passing that information In the way you might expect. So let's go back to the declaration of the C_Controls class, which is found on line 27. It's crucial that you understand what I'm about to explain; otherwise, the following code will make little sense.

The C_Controls class inherits privately from the C_Terminal class. Because of this, any code within the C_Controls class can refer to C_Terminal as though it were part of C_Controls. It is extremely important that you understand this. This reference is only possible INSIDE the C_Controls class. This is due to the fact that inheritance occurs privately. If the inheritance were public, any code referencing C_Controls would also have access to the public functions and methods of C_Terminal. Alright. Great. How does this relate to the C_DrawImage constructor? Here's what we do. If you've understood the inheritance model, we can now connect the dots. The constructor for C_DrawImage expects a pointer to C_Terminal. Yet we are passing a pointer to C_Controls. How does this work? When we use the library function GetPointer and pass this as the argument, we are in fact referring to the current class - in this case, C_Controls.

To understand this, you need to know what the this operator means. It always refers to the current class, no matter what class that is. So, when we call GetPointer(this), we ask the application to get a pointer to the current class. In this case, it is the instance of C_Controls. But C_Controls privately inherits from C_Terminal. And since the constructor logic operates within that inheritance relationship, the C_DrawImage constructor can access C_Terminal through the C_Controls pointer.

You might think this violates inheritance rules, but it does not. In C++, there's even an operator that allows similar behavior, though it's more complex and potentially more dangerous. Still, you should not lower your guard. This structure has its risks. If you design C_Terminal carelessly, allowing any function or method to alter its private members, or by exposing variables outside of private scope, then C_DrawImage could end up modifying C_Terminal's internal state, even without direct access, due to the way it is inherited by C_Controls. So, if you write code without discipline, this structure could become a serious vulnerability. However, if you maintain proper control and especially ensure robust data encapsulation, you won't run into issues using this approach.

My main goal here is to draw your attention to this construction. Although it may seem unconventional, it allows for greater flexibility and enables some very interesting capabilities—provided you use it with care.

Without the inheritance, this approach wouldn't be possible. And even if you try to access components or functions of C_Controls from within C_DrawImage, you won't be able to. Although we're passing a C_Controls instance via this, only the portion of the object that pertains to C_Terminal is actually accessible to C_DrawImage.

Let's now look at a few final details that deserve attention before we wrap up this article. For that, we'll examine the procedure on line 98, which is responsible for positioning the slider pin. It also provides minimal control over the left and right buttons adjacent to the slider, as well as the lock bar. However, what really interests us here is found between lines 110 and 117. What do we see here? Take a look at the Paint method in the C_DrawImage class. This method accepts a string as the final argument. This string is used to display a tooltip-style message when the mouse hovers over the object. Until now, we were relying on our own intuition to understand what was happening during interaction. We can display custom messages in a simple and direct way - messages that can provide meaningful feedback during usage. The first two messages (lines 112 and 113) are relatively minor and won't be very relevant in most situations. But if we look at the message on line 114, we see something interesting. When you adjust the slider pin and hover the mouse over it, you'll see the relative position it's currently pointing to. Think about how handy that can be in a wide range of scenarios.

Suppose you're working with a dataset and you know there's a particular point in the data that you want to analyze. Since the replay/simulator doesn't allow jumping directly to a specific position, which is, in a way, a good thing as it prevents the process from becoming too predictable, you're left to rely on trial and error. We can try to determine which position is immediately before what we want to analyze. By observing the pin's value before hitting "play", you can judge whether you've already passed the point of interest or not. If you’ve gone too far, simply close and reopen the application, then position the pin a few steps earlier. This approach, once quite difficult due to the lack of relative position awareness, is now much more manageable.


Conclusion

This concludes today's article. I hope that the knowledge shared here proves useful not just for this particular implementation, but for other programming tasks you may encounter. I understand that some readers might find what I've shown here to be strange or even unnecessary. Especially since the control indicator was already functioning well. But the goal of this and all my articles is not to provide a definitive solution, but to help you, the aspiring MQL5 programmer, realize how much there is to learn and how important it is to stay open to new ideas and different perspectives.

Although I no longer attach source code files to these articles, all the relevant code is still shown in full within the text. Even code referred to as "fragments" is part of a functional whole. You just need to copy the shown fragment and replace the corresponding section in your code This ensures your code remains synchronized with the article's instructions.

The reason I don't provide full source files anymore is precisely because of the kind of construction we discussed in this article. An experienced developer can usually handle errors or unexpected behavior during modification or regular use. However, less experienced users may end up making serious mistakes when trying to implement this kind of logic.

In any case, in the application you will get access to the executable files necessary for working with the system. The result should resemble the demo video shown below. One final note: the data used in the demonstrations can be downloaded from earlier articles, where I provided it in three ZIP files containing the ticks and bars used in those examples.

Demo video

Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/12293

Attached files |
Versko_DEMO.zip (247.12 KB)
MQL5 Wizard Techniques you should know (Part 64): Using Patterns of DeMarker and Envelope Channels with the White-Noise Kernel MQL5 Wizard Techniques you should know (Part 64): Using Patterns of DeMarker and Envelope Channels with the White-Noise Kernel
The DeMarker Oscillator and the Envelopes' indicator are momentum and support/ resistance tools that can be paired when developing an Expert Advisor. We continue from our last article that introduced these pair of indicators by adding machine learning to the mix. We are using a recurrent neural network that uses the white-noise kernel to process vectorized signals from these two indicators. This is done in a custom signal class file that works with the MQL5 wizard to assemble an Expert Advisor.
Forecasting exchange rates using classic machine learning methods: Logit and Probit models Forecasting exchange rates using classic machine learning methods: Logit and Probit models
In the article, an attempt is made to build a trading EA for predicting exchange rate quotes. The algorithm is based on classical classification models - logistic and probit regression. The likelihood ratio criterion is used as a filter for trading signals.
MQL5 Trading Tools (Part 2): Enhancing the Interactive Trade Assistant with Dynamic Visual Feedback MQL5 Trading Tools (Part 2): Enhancing the Interactive Trade Assistant with Dynamic Visual Feedback
In this article, we upgrade our Trade Assistant Tool by adding drag-and-drop panel functionality and hover effects to make the interface more intuitive and responsive. We refine the tool to validate real-time order setups, ensuring accurate trade configurations relative to market prices. We also backtest these enhancements to confirm their reliability.
Economic forecasts: Exploring the Python potential Economic forecasts: Exploring the Python potential
How to use World Bank economic data for forecasts? What happens when you combine AI models and economics?