Español Português
preview
Разработка системы репликации (Часть 67): Совершенствуем индикатор управления

Разработка системы репликации (Часть 67): Совершенствуем индикатор управления

MetaTrader 5Примеры | 17 февраля 2025, 08:51
150 0
Daniel Jose
Daniel Jose

Представленные здесь материалы предназначены только для обучения. Ни в коем случае нельзя рассматривать это приложение как окончательное, цели которого будут иные, кроме изучения представленных концепций.


Введение

В предыдущей статье "Разработка системы репликации (Часть 66): Нажатие кнопки воспроизведения в сервисе (VII)", мы реализовали метод, который позволяет определить, когда на графике появится новый бар. Хотя данный метод очень хорошо работает с моделями с хорошей ликвидностью, он совершенно не подходит для моделей с низкой ликвидностью или постоянно участвующих в аукционах. Эта проблема будет решена очень скоро.

Однако я хотел бы показать кое-что еще. Это тоже интересно, хотя и не имеет большого значения для пользователей, которые просто хотят использовать приложение. Для них то, о чем я расскажу в начале статьи, не имеет никакого значения. Однако для тех, кто учиться, чтобы стать хорошими программистами, всё то, что мы рассмотрим далее, действительно имеет большое значение: и на то, как вы программируете, и на то, как вы смотрите на свой собственный код и код других программистов. Потому что мы действительно можем многому научиться, наблюдая за тем, как другие программисты создают решения различных проблем. Даже схожие задачи могут иметь совершенно разные реализации в зависимости от каждого отдельного программиста, или даже когда программист пересматривает свои методы работы на этом языке.

Однако не исключено, что сегодняшний материал пригодится вам в будущем.

В предыдущей статье были внесены некоторые изменения в код, чтобы приложение не выдавало ошибок при удалении. Это может произойти либо из-за того, что пользователь закрывает приложение, либо из-за того, что закрывается основной график. В любом случае, исправление было доведено до конца. Но тот же самый код можно значительно улучшить. И это упрощает всю систему, особенно в том, что касается сопровождения исходного кода.

Давайте посмотрим, о чем идет речь. Для этого мы начнем с первой темы данной статьи.


Повышаем динамичность индикатора исправления

Сегодня мы будем работать с кодом индикатора управления. Хотя он уже работает достаточно хорошо, мы можем улучшить некоторые аспекты написания кода с точки зрения качества. Сначала рассмотрим исходный код в заголовочном файле C_Controls.mqh. Ниже можно ознакомиться с ним полностью:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonLeft        def_PathBMP + "Left.bmp"
011. #define def_ButtonLeftBlock   def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonRight       def_PathBMP + "Right.bmp"
013. #define def_ButtonRightBlock  def_PathBMP + "Right_Block.bmp"
014. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
015. #resource "\\" + def_ButtonPlay
016. #resource "\\" + def_ButtonPause
017. #resource "\\" + def_ButtonLeft
018. #resource "\\" + def_ButtonLeftBlock
019. #resource "\\" + def_ButtonRight
020. #resource "\\" + def_ButtonRightBlock
021. #resource "\\" + def_ButtonPin
022. //+------------------------------------------------------------------+
023. #define def_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
024. #define def_PosXObjects         120
025. //+------------------------------------------------------------------+
026. #define def_SizeButtons         32
027. #define def_ColorFilter         0xFF00FF
028. //+------------------------------------------------------------------+
029. #include "..\Auxiliar\C_Mouse.mqh"
030. //+------------------------------------------------------------------+
031. class C_Controls : private C_Terminal
032. {
033.    protected:
034.    private   :
035. //+------------------------------------------------------------------+
036.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
037.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
038. //+------------------------------------------------------------------+
039.       struct st_00
040.       {
041.          string   szBarSlider,
042.                   szBarSliderBlock;
043.          ushort   Minimal;
044.       }m_Slider;
045.       struct st_01
046.       {
047.          C_DrawImage *Btn;
048.          bool         state;
049.          short        x, y, w, h;
050.       }m_Section[eObjectControl::eNull];
051.       C_Mouse   *m_MousePtr;
052. //+------------------------------------------------------------------+
053. inline void CreteBarSlider(short x, short size)
054.          {
055.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
056.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
060.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
064.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
065.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
066.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
067.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
068.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
069.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
070.          }
071. //+------------------------------------------------------------------+
072.       void SetPlay(bool state)
073.          {
074.             if (m_Section[ePlay].Btn == NULL)
075.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
076.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0);
077.             if (!state) CreateCtrlSlider();
078.          }
079. //+------------------------------------------------------------------+
080.       void CreateCtrlSlider(void)
081.          {
082.             if (m_Section[ePin].Btn != NULL) return;
083.             CreteBarSlider(77, 436);
084.             m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
085.             m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
086.             m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin);
087.             PositionPinSlider(m_Slider.Minimal);
088.          }
089. //+------------------------------------------------------------------+
090. inline void RemoveCtrlSlider(void)
091.          {         
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
093.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
094.             {
095.                delete m_Section[c0].Btn;
096.                m_Section[c0].Btn = NULL;
097.             }
098.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
099.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
100.          }
101. //+------------------------------------------------------------------+
102. inline void PositionPinSlider(ushort p)
103.          {
104.             int iL, iR;
105.             
106.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
107.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
108.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
109.             m_Section[ePin].x += def_PosXObjects;
110.              m_Section[ePin].x += 95 - (def_SizeButtons / 2);
111.              for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
112.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)));
113. 
114.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
115.          }
116. //+------------------------------------------------------------------+
117. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
118.          {
119.             C_Mouse::st_Mouse InfoMouse;
120.             
121.             InfoMouse = (*m_MousePtr).GetInfoMouse();
122.             x = (short) InfoMouse.Position.X_Graphics;
123.             y = (short) InfoMouse.Position.Y_Graphics;
124.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
125.             {   
126.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
127.                   return c0;
128.             }
129.             
130.             return eNull;
131.          }
132. //+------------------------------------------------------------------+
133.    public   :
134. //+------------------------------------------------------------------+
135.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
136.          :C_Terminal(Arg0),
137.           m_MousePtr(MousePtr)
138.          {
139.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
140.             if (_LastError != ERR_SUCCESS) return;
141.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
142.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
143.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
144.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
145.             {
146.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
147.                m_Section[c0].y = 25;
148.                m_Section[c0].Btn = NULL;
149.             }
150.             m_Section[ePlay].x = def_PosXObjects;
151.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
152.             m_Section[eRight].x = m_Section[ePlay].x + 511;
153.             m_Slider.Minimal = eTriState;
154.          }
155. //+------------------------------------------------------------------+
156.       ~C_Controls()
157.          {
158.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
159.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
160.             delete m_MousePtr;
161.          }
162. //+------------------------------------------------------------------+
163.       void SetBuffer(const int rates_total, double &Buff[])
164.          {
165.             uCast_Double info;
166.             
167.             info._16b[eCtrlPosition] = m_Slider.Minimal;
168.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));
169.             if (rates_total > 0)
170.                Buff[rates_total - 1] = info.dValue;
171.          }
172. //+------------------------------------------------------------------+
173.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
174.          {
175.             short x, y;
176.             static ushort iPinPosX = 0;
177.             static short six = -1, sps;
178.             uCast_Double info;
179.             
180.             switch (id)
181.             {
182.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
183.                   info.dValue = dparam;
184.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
185.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
186.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
187.                   break;
188.                case CHARTEVENT_OBJECT_DELETE:
189.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
190.                   {
191.                      if (sparam == def_ObjectCtrlName(ePlay))
192.                      {
193.                         delete m_Section[ePlay].Btn;
194.                         m_Section[ePlay].Btn = NULL;
195.                         SetPlay(m_Section[ePlay].state);
196.                      }else
197.                      {
198.                         RemoveCtrlSlider();
199.                         CreateCtrlSlider();
200.                      }
201.                   }
202.                   break;
203.                case CHARTEVENT_MOUSE_MOVE:
204.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
205.                   {
206.                      case ePlay:
207.                         SetPlay(!m_Section[ePlay].state);
208.                         if (m_Section[ePlay].state)
209.                         {
210.                            RemoveCtrlSlider();
211.                            m_Slider.Minimal = iPinPosX;
212.                         }else CreateCtrlSlider();
213.                         break;
214.                      case eLeft:
215.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
216.                         break;
217.                      case eRight:
218.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
219.                         break;
220.                      case ePin:
221.                         if (six == -1)
222.                         {
223.                            six = x;
224.                            sps = (short)iPinPosX;
225.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
226.                         }
227.                         iPinPosX = sps + x - six;
228.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
229.                         break;
230.                   }else if (six > 0)
231.                   {
232.                      six = -1;
233.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
234.                   }
235.                   break;
236.             }
237.             ChartRedraw(GetInfoTerminal().ID);
238.          }
239. //+------------------------------------------------------------------+
240. };
241. //+------------------------------------------------------------------+
242. #undef def_PosXObjects
243. #undef def_ButtonPlay
244. #undef def_ButtonPause
245. #undef def_ButtonLeft
246. #undef def_ButtonRight
247. #undef def_ButtonPin
248. #undef def_PathBMP
249. //+------------------------------------------------------------------+

Исходный код файла C_Controls.mqh

По сути, здесь важно то, насколько данный код может разрастаться или приобретать новые функции без дополнительного написания кода. Однако, если код, показанный выше, не нуждается в новой функциональности, то так нельзя сказать о коде, который в нем активно используется. Это функция, которая отвечает за создание и поддержку объектов на графике. Я имею в виду класс C_DrawImage, объявленный в строке 47.

Хотя можно представить, что именно этот класс C_Controls создает объекты, которые размещаются на графике, на самом деле это не так, по крайней мере, в большинстве случаев. Единственные два объекта, которые он действительно создает, - это те, которые поддерживают ползунок. То есть всё, что генерируется и размещается на графике этим классом C_Controls, присутствует в процедуре, расположенной в строке 53. Давайте внимательно посмотрим на код в заголовочном файле C_DrawImage.mqh. Полностью с ним можно ознакомиться ниже.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. //+------------------------------------------------------------------+
006. #define def_MaxImages 2
007. //+------------------------------------------------------------------+
008. class C_DrawImage : public C_Terminal
009. {
010. //+------------------------------------------------------------------+
011.    private   :
012.       struct st_00
013.       {
014.          int   widthMap,
015.                heightMap;
016.          uint  Map[];
017.       }m_InfoImage[def_MaxImages];
018.       uint     m_Pixels[];
019.       string   m_szObjName,
020.                m_szRecName;
021. //+------------------------------------------------------------------+
022.       bool LoadBitmap(int index, string szFileName)
023.          {
024.             struct BitmapHeader
025.             {
026.                ushort   type;
027.                uint     size,
028.                         reserv,
029.                         offbits,
030.                         imgSSize,
031.                         imgWidth,
032.                         imgHeight;
033.                ushort   imgPlanes,
034.                         imgBitCount;
035.                uint     imgCompression,
036.                         imgSizeImage,
037.                         imgXPelsPerMeter,
038.                         imgYPelsPerMeter,
039.                         imgClrUsed,
040.                         imgClrImportant;
041.             } Header;
042.             int fp, w;
043.             bool noAlpha, noFlip;
044.             uint imgSize;
045. 
046.             if ((fp = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE) return false;
047.             if (FileReadStruct(fp, Header) != sizeof(Header))
048.             {
049.                FileClose(fp);
050.                return false;
051.             };
052.             m_InfoImage[index].widthMap  = (int)Header.imgWidth;
053.             m_InfoImage[index].heightMap = (int)Header.imgHeight;
054.             if (noFlip = (m_InfoImage[index].heightMap < 0)) m_InfoImage[index].heightMap = -m_InfoImage[index].heightMap;
055.             if (Header.imgBitCount == 32)
056.             {               
057.                uint tmp[];
058.                
059.                noAlpha = true;
060.                imgSize = FileReadArray(fp, m_InfoImage[index].Map);
061.                if (!noFlip) for (int c0 = 0; c0 < m_InfoImage[index].heightMap / 2; c0++)
062.                {
063.                   ArrayCopy(tmp, m_InfoImage[index].Map, 0, m_InfoImage[index].widthMap * c0, m_InfoImage[index].widthMap);
064.                   ArrayCopy(m_InfoImage[index].Map, m_InfoImage[index].Map, m_InfoImage[index].widthMap * c0, m_InfoImage[index].widthMap * (m_InfoImage[index].heightMap - c0 - 1), m_InfoImage[index].widthMap);
065.                   ArrayCopy(m_InfoImage[index].Map, tmp, m_InfoImage[index].widthMap * (m_InfoImage[index].heightMap - c0 - 1), 0, m_InfoImage[index].widthMap);
066.                }
067.                for (uint c0 = 0; (c0 < imgSize && noAlpha); c0++) if (uchar(m_InfoImage[index].Map[c0] >> 24) != 0) noAlpha = false;
068.                if (noAlpha) for(uint c0 = 0; c0 < imgSize; c0++) m_InfoImage[index].Map[c0] |= 0xFF000000;
069.             } else
070.             {
071.                uchar tmp[];
072. 
073.                w = ((m_InfoImage[index].widthMap * 3) + 3) & ~3;
074.                if (ArrayResize(m_InfoImage[index].Map, m_InfoImage[index].widthMap * m_InfoImage[index].heightMap) != -1) for(int c0 = 0; c0 < m_InfoImage[index].heightMap; c0++)
075.                {
076.                   if (FileReadArray(fp, tmp, 0, w) != w)
077.                   {
078.                      FileClose(fp);
079.                      return false;
080.                   };
081.                   for (int j = 0, k = 0, p = m_InfoImage[index].widthMap * (m_InfoImage[index].heightMap - c0 - 1); j < m_InfoImage[index].widthMap; j++, k+=3, p++)
082.                      m_InfoImage[index].Map[p] = 0xFF000000 | (tmp[k+2] << 16) | (tmp[k + 1] << 8) | tmp[k];
083.                }
084.             }
085.             FileClose(fp);
086.             
087.             return true;
088.          }
089. //+------------------------------------------------------------------+
090.       void ReSizeImage(const int w, const int h, const uchar v, const int what)
091.          {
092. #define _Transparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0)
093.             double fx = (w * 1.0) / m_InfoImage[what].widthMap;
094.             double fy = (h * 1.0) / m_InfoImage[what].heightMap;
095.             uint pyi, pyf, pxi, pxf, tmp;
096.             uint uc;
097. 
098.             ArrayResize(m_Pixels, w * h);
099.             for (int cy = 0, y = 0; cy < m_InfoImage[what].heightMap; cy++, y += m_InfoImage[what].widthMap)
100.             {
101.                pyf = (uint)(fy * cy) * w;
102.                tmp = pyi = (uint)(fy * (cy - 1)) * w;
103.                for (int x = 0; x < m_InfoImage[what].widthMap; x++)
104.                {
105.                   pxf = (uint)(fx * x);
106.                   pxi = (uint)(fx * (x - 1));
107.                   uc = (uchar(double((uc = m_InfoImage[what].Map[x + y]) >> 24) * _Transparency(v)) << 24) | uc & 0x00FFFFFF;
108.                   m_Pixels[pxf + pyf] = uc;
109.                   for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = uc;
110.                }
111.                for (pyi += w; pyi < pyf; pyi += w) 
112.                   for (int x = 0; x < w; x++)
113.                      m_Pixels[x + pyi] = m_Pixels[x + tmp];
114.             }
115. #undef _Transparency
116.          }
117. //+------------------------------------------------------------------+
118.       void Initilize(int sub, string szObjName, const color cFilter, const string szFile1, const string szFile2 = NULL)
119.          {
120.             string sz0;
121.             
122.             m_szObjName = m_szRecName = NULL;
123.             CreateObjectGraphics(m_szObjName = szObjName, OBJ_BITMAP_LABEL);
124.             m_szRecName = "::" + m_szObjName;            
125.             for (int c0 = 0; (c0 < def_MaxImages) && (_LastError == ERR_SUCCESS); c0++)
126.             {
127.                switch (c0)
128.                {
129.                   case 1:
130.                      if ((sz0 = szFile2) != NULL) break;
131.                   case 0:
132.                      sz0 = szFile1;
133.                      break;
134.                }
135.                if (StringFind(sz0, "::") >= 0)
136.                   ResourceReadImage(sz0, m_InfoImage[c0].Map, m_InfoImage[c0].widthMap, m_InfoImage[c0].heightMap);
137.                else if (!LoadBitmap(c0, sz0))
138.                {
139.                   SetUserError(C_Terminal::ERR_FileAcess);
140.                   return;
141.                }
142.                ArrayResize(m_Pixels, m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap);
143.                ArrayInitialize(m_Pixels, 0);
144.                for (int c1 = (m_InfoImage[c0].heightMap * m_InfoImage[c0].widthMap) - 1; c1 >= 0; c1--)
145.                   if ((m_InfoImage[c0].Map[c1] & 0x00FFFFFF) != cFilter) m_Pixels[c1] = m_InfoImage[c0].Map[c1];
146.                ArraySwap(m_InfoImage[c0].Map, m_Pixels);
147.             }
148.             ArrayResize(m_Pixels, 1);
149.          }
150. //+------------------------------------------------------------------+
151.    public   :
152. //+------------------------------------------------------------------+
153.       C_DrawImage(string szShortName, long id, int sub, string szObjName, string szFile)
154.          :C_Terminal(id)
155.          {
156.             if (!IndicatorCheckPass(szShortName)) return;
157.             Initilize(sub, szObjName, clrNONE, szFile);
158.          }
159. //+------------------------------------------------------------------+
160.       C_DrawImage(long id, int sub, string szObjName, const color cFilter, const string szFile1, const string szFile2 = NULL)
161.          :C_Terminal(id)
162.          {
163.             Initilize(sub, szObjName, cFilter, szFile1, szFile2);
164.          }
165. //+------------------------------------------------------------------+
166.       ~C_DrawImage()
167.          {
168.             for (int c0 = 0; c0 < def_MaxImages; c0++)
169.                ArrayFree(m_InfoImage[c0].Map);
170.             ArrayFree(m_Pixels);
171.             ObjectDelete(GetInfoTerminal().ID, m_szObjName);
172.             ResourceFree(m_szRecName);
173.          }
174. //+------------------------------------------------------------------+
175.       void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what)
176.          {
177.             
178.             if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return;
179.             ReSizeImage(w, h, cView, what);
180.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x);
181.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y);
182.             if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
183.             {
184.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName);
185.                ChartRedraw(GetInfoTerminal().ID);
186.             }
187.          }
188. //+------------------------------------------------------------------+
189. };
190. //+------------------------------------------------------------------+
191. #undef def_MaxImages
192. //+------------------------------------------------------------------+

Исходный код для файла C_DrawImage.mqh

Именно здесь всё становится по-настоящему интересным. Хотя в классе C_DrawImage мы реализовали способ работы с BitMap из файла, на практике это вряд ли произойдет, потому что я предпочитаю размещать растровые изображения как внутренние ресурсы самого приложения. Поэтому большая часть данного кода не будет компилироваться в MQL5. Однако в коде класса C_DrawImage я хочу выделить не это, а другой момент.

Давайте подумаем: код индикатора управления на самом деле не будет обращаться к классу C_DrawImage, на самом деле индикатор управления даже не знает о существовании такого класса. Но обратите внимание на следующее: в строке 08 мы объявляем класс, который публичным образом наследует класс C_Terminal. Всё в порядке, но в коде класса C_Controls, который как раз и использует индикатор управления, в строке 31 мы объявляем класс, и он приватно наследует тот же класс C_Terminal. Ух ты! Здесь мы сталкиваемся с проблемой. Но чтобы не усугубить ситуацию, посмотрите на строку 156 класса C_DrawImage. Обратите внимание, что здесь также проверяется добавление нового индикатора, но мы никак его не используем. Поэтому класс C_Terminal служит только для предоставления доступа к идентификатору графика, чтобы изменять свойства объекта.

Но подумайте: а зачем нам предоставлять доступ к классу C_Terminal тому классу, которому он на самом деле не нужен? Даже если бы классу C_DrawImage требовался доступ к C_Terminal, было бы гораздо разумнее передать данному классу указатель, чтобы он мог обращаться к C_Terminal через этот указатель.

Это сделает код более последовательным и безопасным для расширения в будущем. Данное расширение может осуществляться путем наследования или другими способами. Тем не менее, это было бы гораздо безопаснее и эффективнее, чем то, как это реализовано сейчас.

И это подводит нас к тому, что я хочу сказать. Давайте изменим этот класс C_DrawImage так, чтобы он стал зависимым, но в то же время позволял нам расширять его при необходимости. Поэтому первое, что нужно сделать, - это внести изменение в исходный код файла C_DrawImage.mqh. Но сначала давайте посмотрим на заголовочный файл, от которого будет зависеть C_DrawImage.mqh. Файл называется Macro.mqh и находится ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define macroRemoveSec(A) (A - (A % 60))
05. #define macroGetDate(A)   (A - (A % 86400))
06. #define macroGetSec(A)    (A - (A - (A % 60)))
07. //+------------------------------------------------------------------+
08. #define macroColorRGBA(A, B) ((uint)((B << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16)))
09. #define macroTransparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0)
10. //+------------------------------------------------------------------+

Исходный код файла macro.mqh

Хорошо, данный файл Macro.mqh будет включен в файл C_Terminal.mqh. Если у вас есть сомнения на этот счет, можно ознакомиться с предыдущими статьями, чтобы понять, как происходит интеграция. Теперь виден новый файл C_DrawImage.mqh, уже измененный и с новыми возможностями использования. Давайте рассмотрим новый код:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_Terminal.mqh"
05. //+------------------------------------------------------------------+
06. class C_DrawImage
07. {
08. //+------------------------------------------------------------------+
09.    private   :
10.       struct st_00
11.       {
12.          int   widthMap,
13.                heightMap;
14.          uint  Map[];
15.       }m_InfoImage[2];
16.       uint     m_Pixels[];
17.       string   m_szObjName,
18.                m_szRecName;
19.       long     m_ID;
20. //+------------------------------------------------------------------+
21.       void ReSizeImage(const int w, const int h, const uchar v, const int what)
22.          {
23.             double fx = (w * 1.0) / m_InfoImage[what].widthMap;
24.             double fy = (h * 1.0) / m_InfoImage[what].heightMap;
25.             uint pyi, pyf, pxi, pxf, tmp;
26.             uint uc;
27. 
28.             ArrayResize(m_Pixels, w * h);
29.             if ((m_InfoImage[what].widthMap == w) && (m_InfoImage[what].heightMap == h) && (v == 100)) ArrayCopy(m_Pixels, m_InfoImage[what].Map);
30.             else for (int cy = 0, y = 0; cy < m_InfoImage[what].heightMap; cy++, y += m_InfoImage[what].widthMap)
31.             {
32.                pyf = (uint)(fy * cy) * w;
33.                tmp = pyi = (uint)(fy * (cy - 1)) * w;
34.                for (int x = 0; x < m_InfoImage[what].widthMap; x++)
35.                {
36.                   pxf = (uint)(fx * x);
37.                   pxi = (uint)(fx * (x - 1));
38.                   uc = (uchar(double((uc = m_InfoImage[what].Map[x + y]) >> 24) * macroTransparency(v)) << 24) | uc & 0x00FFFFFF;
39.                   m_Pixels[pxf + pyf] = uc;
40.                   for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = uc;
41.                }
42.                for (pyi += w; pyi < pyf; pyi += w) 
43.                   for (int x = 0; x < w; x++)
44.                      m_Pixels[x + pyi] = m_Pixels[x + tmp];
45.             }
46.          }
47. //+------------------------------------------------------------------+
48.       void Initilize(const int index, const color cFilter, const string szFile, const bool r180)
49.          {            
50.             if (StringFind(szFile, "::") < 0) return;
51.             ResourceReadImage(szFile, m_InfoImage[index].Map, m_InfoImage[index].widthMap, m_InfoImage[index].heightMap);
52.             for (int pm = ArrayResize(m_Pixels, m_InfoImage[index].widthMap * m_InfoImage[index].heightMap), pi = 0; pi < pm;)
53.                for (int c0 = 0, pf = pi + (r180 ? m_InfoImage[index].widthMap - 1 : 0); c0 < m_InfoImage[index].widthMap; pi++, (r180 ? pf-- : pf++), c0++)
54.                   m_Pixels[pf] = ((m_InfoImage[index].Map[pi] & 0x00FFFFFF) != cFilter ? m_InfoImage[index].Map[pi] : 0);
55.             ArraySwap(m_InfoImage[index].Map, m_Pixels);
56.             ArrayFree(m_Pixels);
57.          }
58. //+------------------------------------------------------------------+
59.    public   :
60. //+------------------------------------------------------------------+
61.       C_DrawImage(C_Terminal *ptr, string szObjName, const color cFilter, const string szFile1, const string szFile2, const bool r180 = false)
62.          {
63.             (*ptr).CreateObjectGraphics(m_szObjName = szObjName, OBJ_BITMAP_LABEL);
64.             ObjectSetString(m_ID = (*ptr).GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName = "::" + m_szObjName);
65.             Initilize(0, cFilter, szFile1, r180);
66.             if (szFile2 != NULL) Initilize(1, cFilter, szFile2, r180);
67.          }
68. //+------------------------------------------------------------------+
69.       ~C_DrawImage()
70.          {
71.             ArrayFree(m_Pixels);
72.             ResourceFree(m_szRecName);
73.             ObjectDelete(m_ID, m_szObjName);
74.          }
75. //+------------------------------------------------------------------+
76.       void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what, const string tipics = "\n")
77.          {
78.             ReSizeImage(w, h, cView, what);
79.             ObjectSetInteger(m_ID, m_szObjName, OBJPROP_XDISTANCE, x);
80.             ObjectSetInteger(m_ID, m_szObjName, OBJPROP_YDISTANCE, y);
81.             ObjectSetString(m_ID, m_szObjName, OBJPROP_TOOLTIP, tipics);
82.             ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE);
83.             ObjectSetString(m_ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName);
84.             ChartRedraw(m_ID);
85.          }
86. //+------------------------------------------------------------------+
87. };
88. //+------------------------------------------------------------------+

Исходный код для файла C_DrawImage.mqh

Вам может показаться, что разница маленькая, но она есть, и в долгосрочной перспективе она делает класс гораздо более устойчивым и приятным. В принципе, мы сохранили всё прежнее, кроме теории. Это связано с тем, что основной упор в классе делается на использование ресурсов, встроенных в исполняемые файлы, а не во внешние.

Такой подход значительно облегчает выполнение действительно нужных нам элементов. Как видите, теперь у нас гораздо меньше кода, подпрограмм и процедур, с которыми приходится иметь дело. Но самое важное находится именно в конструкторе класса. Обратите внимание на строку 61, где мы объявляем конструктор. Наверное, вы заметили, что класс C_Terminal теперь передается в виде указателя. Таким образом, в строке 63 мы можем использовать процедуру данного класса. Кроме того, в строке 64 мы инициализируем приватную переменную класса для доступа к идентификатору графика. Мы так сделали для того, чтобы работать со свойствами объектов, к которым класс будет иметь доступ. Данные объекты, по сути, являются OBJ_BITMAP_LABEL, так что на этом этапе больше ничего делать не нужно.

Но посмотрите на строки 65 и 66. Этот тип конструкции позволил сделать процедуру инициализации гораздо более надежным. Она полностью реализована между строками 48 и 57. Обратите внимание, что она довольно проста для понимания, по крайней мере, с моей точки зрения, и что в ней активно используются функции библиотеки MQL5. За исключением цикла, который присутствует в строке 52, все остальные являются функциями MQL5. Чтобы понять лучше происходящее, я вам рекомендую изучать документацию.

Однако цикл в строке 52 делает нечто интересное, что заслуживает объяснения. Это двойной цикл. Давайте посмотрим, что здесь происходит. Иначе не будет понятно то, что сделаем позже при использовании класса C_DrawImage. Начнем с первой части цикла, где объявляем две локальные переменные. Но в этом цикле будет увеличиваться только переменная Y, поэтому не стоит беспокоиться о второй переменной, поскольку ее нужно объявить и инициализировать только здесь.

Во втором цикле мы объявляем еще две переменные. Однако прошу обратить внимание на то, что происходит в строке 52. Первая, PM, объявляется и инициализируется с размером равным выделенному массиву. Данную переменную НЕЛЬЗЯ изменять, так как она указывает, когда цикл должен завершиться. Обратите внимание, что для этого нам нужно сравнить только переменную PM с переменной PI. Прошу заметить, в этой же строке 52 объявлена переменная PI, которая инициализируется нулевым значением. Но обратите внимание, что мы ничего не делаем с переменными. Поэтому данный цикл в строке 52 служит только для полного управления следующим циклом, который объявлен в строке 53.

В строке 53 мы сталкиваемся со значительно более сложным циклом. Однако, если вы обратите внимание, то сможете понять происходящее здесь. Первое действие - объявление новой переменной C0. Мы инициализируем его со значением ноль, и он будет отвечать за то, чтобы этот цикл выполнялся до тех пор, пока не будет достигнута ширина изображения. Это первый и самый простой шаг, теперь наступает сложная часть. Обратите внимание, что мы также объявляем вторую переменную PF. У этой переменной PF будет свое начальное значение в зависимости от переменной PI и от того, что мы делаем с изображением. В любом случае переменная PI всегда будет увеличиваться. Однако переменная FP не обязательно увеличится, - в некоторых случаях она может даже уменьшиться. Чтобы понять причины, прошу заметить, что в объявлении процедуры у нас есть булевое значение R180. Когда это значение равно true, мы пытаемся, чтобы полученное изображение было отражением исходного. Для этого, когда R180 равен true, PF инициализируется значением переменной PI плюс ширина изображения. Кроме того, PF будет уменьшаться с каждой итерацией цикла. Если R180 равен false, то теоретически мы получим в массиве памяти простую копию исходного изображения.

И почему мы сказали, что теоретически это будет копия? Причина в том, что происходит в строке 54. Обратите внимание, что в данной строке мы будем фильтровать изображение на предмет определенного цвета. Когда этот цвет найдется, точка на изображении станет прозрачной. Поэтому копия, как и отражение, теоретически идентична оригинальному изображению. Однако вся упомянутая работа с массивами, а также выделение и освобождение памяти, происходит в связи с тем, что MQL5 не использует указатели, как C или C++. Поэтому нам нужно реализовать всё немного по-другому. По возможности я советую использовать функции стандартной библиотеки MQL5, потому что функции библиотеки гораздо более оптимизированы, чем любой код, который мы могли бы разработать для выполнения той же задачи. Кроме того, любое улучшение производительности библиотеки MQL5 автоматически пойдет на пользу нашей программе. В отличие от того, если бы мы выполняли процедуру самостоятельно.

Однако есть и другая процедура, которая интенсивно использует библиотеку MQL5. Я имею в виду процедуру PAINT, которая начинается в строке 76. Практически все вызовы направлены на библиотеку MQL5, за исключением одного вызова в строке 78. Это приведет к выполнению процедуры, которую видим между строками 21 и 46. Теперь обратите внимание на один момент: в строке 28 мы выделяем память для размещения изображения, которое будет показываться на объекте. Однако в строке 29 мы проверяем, будет ли он изменен каким-либо образом. Если это не происходит, значит, изображение можно перенести непосредственно на объект. Чтобы максимально ускорить данный процесс, мы используем библиотечную функцию для копирования изображения непосредственно в массив. Если бы в MQL5 использовались указатели, как в C или C++, всё было бы реализовано по-другому. Но если изображение необходимо каким-либо образом изменить, начиная со строки 30 и далее мы входим в цикл, чтобы изменить изображение в соответствии с ожидаемыми параметрами, по крайней мере, с точки зрения размеров и уровня прозрачности.

На этом мы завершаем изменения в коде класса C_DrawImage. Но, поскольку мы изменили данный код, необходимо будет исправить код класса C_Controls, который в настоящее время использует непосредственно класс C_DrawImage. Далее мы рассмотрим новый код в файле C_Controls.mqh.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonCtrl        def_PathBMP + "Left.bmp"
011. #define def_ButtonCtrlBlock   def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
013. #resource "\\" + def_ButtonPlay
014. #resource "\\" + def_ButtonPause
015. #resource "\\" + def_ButtonCtrl
016. #resource "\\" + def_ButtonCtrlBlock
017. #resource "\\" + def_ButtonPin
018. //+------------------------------------------------------------------+
019. #define def_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
020. #define def_PosXObjects         120
021. //+------------------------------------------------------------------+
022. #define def_SizeButtons         32
023. #define def_ColorFilter         0xFF00FF
024. //+------------------------------------------------------------------+
025. #include "..\Auxiliar\C_Mouse.mqh"
026. //+------------------------------------------------------------------+
027. class C_Controls : private C_Terminal
028. {
029.    protected:
030.    private   :
031. //+------------------------------------------------------------------+
032.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
033.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
034. //+------------------------------------------------------------------+
035.       struct st_00
036.       {
037.          string   szBarSlider,
038.                   szBarSliderBlock;
039.          ushort   Minimal;
040.       }m_Slider;
041.       struct st_01
042.       {
043.          C_DrawImage *Btn;
044.          bool         state;
045.          short        x, y, w, h;
046.       }m_Section[eObjectControl::eNull];
047.       C_Mouse   *m_MousePtr;
048. //+------------------------------------------------------------------+
049. inline void CreteBarSlider(short x, short size)
050.          {
051.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
052.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
053.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
054.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
055.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
056.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
060.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
064.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
065.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
066.          }
067. //+------------------------------------------------------------------+
068.       void SetPlay(bool state)
069.          {
070.             if (m_Section[ePlay].Btn == NULL)
071.                m_Section[ePlay].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
072.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0, state ? "Press to Pause" : "Press to Start");
073.             if (!state) CreateCtrlSlider();
074.          }
075. //+------------------------------------------------------------------+
076.       void CreateCtrlSlider(void)
077.          {
078.             if (m_Section[ePin].Btn != NULL) return;
079.             CreteBarSlider(77, 436);
080.             m_Section[eLeft].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock);
081.             m_Section[eRight].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock, true);
082.             m_Section[ePin].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin, NULL);
083.             PositionPinSlider(m_Slider.Minimal);
084.          }
085. //+------------------------------------------------------------------+
086. inline void RemoveCtrlSlider(void)
087.          {         
088.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
089.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
090.             {
091.                delete m_Section[c0].Btn;
092.                m_Section[c0].Btn = NULL;
093.             }
094.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
095.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
096.          }
097. //+------------------------------------------------------------------+
098. inline void PositionPinSlider(ushort p)
099.          {
100.             int iL, iR;
101.             string szMsg;
102.             
103.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
104.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
105.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
106.             m_Section[ePin].x += def_PosXObjects;
107.              m_Section[ePin].x += 95 - (def_SizeButtons / 2);
108.              for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)   if (m_Section[c0].Btn != NULL)
109.              {
110.                 switch (c0)
111.                 {
112.                    case eLeft  : szMsg = "Previous Position";            break;
113.                    case eRight : szMsg = "Next Position";                break;
114.                    case ePin   : szMsg = "Go To: " + IntegerToString(p); break;
115.                    default     : szMsg = "\n";
116.                 }
117.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)), szMsg);
118.             }
119. 
120.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
121.          }
122. //+------------------------------------------------------------------+
123. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
124.          {
125.             C_Mouse::st_Mouse InfoMouse;
126.             
127.             InfoMouse = (*m_MousePtr).GetInfoMouse();
128.             x = (short) InfoMouse.Position.X_Graphics;
129.             y = (short) InfoMouse.Position.Y_Graphics;
130.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
131.             {   
132.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
133.                   return c0;
134.             }
135.             
136.             return eNull;
137.          }
138. //+------------------------------------------------------------------+
139.    public   :
140. //+------------------------------------------------------------------+
141.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
142.          :C_Terminal(Arg0),
143.           m_MousePtr(MousePtr)
144.          {
145.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
146.             if (_LastError != ERR_SUCCESS) return;
147.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
148.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
149.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
150.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
151.             {
152.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
153.                m_Section[c0].y = 25;
154.                m_Section[c0].Btn = NULL;
155.             }
156.             m_Section[ePlay].x = def_PosXObjects;
157.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
158.             m_Section[eRight].x = m_Section[ePlay].x + 511;
159.             m_Slider.Minimal = eTriState;
160.          }
161. //+------------------------------------------------------------------+
162.       ~C_Controls()
163.          {
164.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
165.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
166.             delete m_MousePtr;
167.          }
168. //+------------------------------------------------------------------+
169.       void SetBuffer(const int rates_total, double &Buff[])
170.          {
171.             uCast_Double info;
172.             
173.             info._16b[eCtrlPosition] = m_Slider.Minimal;
174.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));
175.             if (rates_total > 0)
176.                Buff[rates_total - 1] = info.dValue;
177.          }
178. //+------------------------------------------------------------------+
179.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
180.          {
181.             short x, y;
182.             static ushort iPinPosX = 0;
183.             static short six = -1, sps;
184.             uCast_Double info;
185.             
186.             switch (id)
187.             {
188.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
189.                   info.dValue = dparam;
190.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
191.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
192.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
193.                   break;
194.                case CHARTEVENT_OBJECT_DELETE:
195.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
196.                   {
197.                      if (sparam == def_ObjectCtrlName(ePlay))
198.                      {
199.                         delete m_Section[ePlay].Btn;
200.                         m_Section[ePlay].Btn = NULL;
201.                         SetPlay(m_Section[ePlay].state);
202.                      }else
203.                      {
204.                         RemoveCtrlSlider();
205.                         CreateCtrlSlider();
206.                      }
207.                   }
208.                   break;
209.                case CHARTEVENT_MOUSE_MOVE:
210.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
211.                   {
212.                      case ePlay:
213.                         SetPlay(!m_Section[ePlay].state);
214.                         if (m_Section[ePlay].state)
215.                         {
216.                            RemoveCtrlSlider();
217.                            m_Slider.Minimal = iPinPosX;
218.                         }else CreateCtrlSlider();
219.                         break;
220.                      case eLeft:
221.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
222.                         break;
223.                      case eRight:
224.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
225.                         break;
226.                      case ePin:
227.                         if (six == -1)
228.                         {
229.                            six = x;
230.                            sps = (short)iPinPosX;
231.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
232.                         }
233.                         iPinPosX = sps + x - six;
234.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
235.                         break;
236.                   }else if (six > 0)
237.                   {
238.                      six = -1;
239.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
240.                   }
241.                   break;
242.             }
243.             ChartRedraw(GetInfoTerminal().ID);
244.          }
245. //+------------------------------------------------------------------+
246. };
247. //+------------------------------------------------------------------+
248. #undef def_PosXObjects
249. #undef def_ButtonPlay
250. #undef def_ButtonPause
251. #undef def_ButtonCtrl
252. #undef def_ButtonCtrlBlock
253. #undef def_ButtonPin
254. #undef def_PathBMP
255. //+------------------------------------------------------------------+

Исходный код файла C_Controls.mqh

Первое, что бросается в глаза в новом коде, - это определения. Вопрос в том, где находятся определения кнопок: справа и слева? Видно, что между строками 07 и 17 больше нет кнопок "влево" и "вправо". Вместо этого мы видим несколько иное определение. Интересно, что мы упоминаем изображения LEFT.BMP и LEFT_BLOCK.BMP, но не на изображении с правой стороны. Почему? Объяснение в файле C_DrawImage.mqh. Поскольку и правая, и левая кнопки в принципе идентичны, а точнее, одна является отражением другой, у нас нет необходимости объявлять обе кнопки. Достаточно иметь доступ к одной из них, чтобы иметь возможность использовать другой через класс C_DrawImage.

Давайте посмотрим, что еще изменилось. В строке 68 создаются и идентифицируются кнопки воспроизведения и паузы. Теперь посмотрите на строку 72 в этой же программе. В конце объявления, встречающегося в этой строке, мы даем объекту возможность предоставить нам дополнительную информацию. Подобная функциональность может быть очень полезной, хотя в данном случае она не имеет большого значения.

Однако как в данной процедуре, которая создает и определяет кнопку воспроизведения или паузы, так и в процедуре ниже, которая создает кнопки и ползунок, есть кое-что, что действительно нуждается в объяснении. Чтобы понять, о чем идет речь, давайте вернемся к коду конструктора класса C_DrawImage.

Прошу заметить, что в исходном коде конструктора класса C_DrawImage в строке 61 указываем, что первым параметром должна быть ссылка на класс C_Terminal. Обратите на это внимание, так как это очень важно. Конструктор класса C_DrawImage ожидает получить в качестве первого параметра ссылку на указатель, который указывает на класс C_Terminal. Однако, если вернуться к коду в заголовочном файле C_Controls.mqh, то в строках 71, 80, 81 и 82 мы не передаем подобную информацию. Однако, не так, как вы, вероятно, ожидали. Давайте вернемся к объявлению класса C_Controls, которое находится в строке 27. Внимательно следите за моими объяснениями, потому что если вы не разберетесь сейчас, то потом не сможете больше ничего понять.

Класс C_Controls наследует от класса C_Terminal. Это позволяет любому коду в классе C_Controls ссылаться на класс C_Terminal, как будто последний был частью первого. Крайне важно, чтобы вы это поняли. Ссылка на класс C_Terminal произойдет только ВНУТРИ класса C_Controls, хочу подчеркнуть это слово. И это связано с тем, что наследование происходит частным образом. Если бы наследование было публичным, то любой код, ссылающийся на класс C_Controls, также имел бы доступ к публичным функциям и процедурам класса C_Terminal. Хорошо. Отлично. Но какое отношение это имеет к конструктору класса C_DrawImage? Всё в порядке. Если вы поняли проблему наследования, то теперь мы видим, как она связана с тем, что конструктор класса C_DrawImage ожидает указатель для ссылки на класс C_Terminal, в то время как мы передаем указатель, который ссылается на класс C_Controls. Всё еще не поняли? Когда мы используем функцию библиотеки GetPointer и указываем в ее аргументе оператор this, мы на самом деле ссылаемся на класс C_Controls, а не на что-то другое.

Чтобы понять это, сначала нужно разобраться, что означает оператор this, если вы еще не знаете. Данный оператор всегда ссылается на класс, в котором мы находимся, каким бы он ни был. Поэтому, когда мы используем функцию GetPointer с аргументом this, мы просим приложение получить указатель, который ссылается на текущий класс. В данном случае этим классом является C_Controls. Но C_Controls наследует от C_Terminal частным образом. Однако, поскольку код ссылается на класс C_Controls, как будто он является его частью, это позволяет конструктору C_DrawImage получить доступ к классу C_Terminal.

Можно подумать, что это нарушает систему наследования, но это не так. В C++ есть оператор, который позволяет добиться чего-то подобного, хотя и гораздо более сложного, чем то, что реализовано в данной показанной конструкции. Однако сейчас не время терять бдительность. Такая конструкция имеет свои риски. Если мы создадим класс C_Terminal небрежно, разрешив любой процедуре или функции изменять приватные значения класса, или оставим переменные вне условия private, такая конструкция позволит классу C_DrawImage изменять значения внутри C_Terminal, даже не имея к нему прямого доступа, а класс C_Terminal наследуется приватным образом классом C_Controls. Поэтому, если мы пишем код без надлежащего контроля, эта конструкция может потенциально повредить наш код. Однако, если мы программируем разумно и, прежде всего, принимаем необходимые меры предосторожности для обеспечения должной инкапсуляции данных, у нас не возникнет проблем с использованием этой конструкции.

В любом случае, я хотел обратить ваше внимание на эту конструкцию, потому что, несмотря на то, что она довольно экзотична, она позволяет нам расширить функциональность и предлагает нам возможность делать интересные вещи, при условии, что мы примем правильные меры предосторожности.

Без наследования использование данной конструкции было бы невозможным. Даже если мы попытаемся получить доступ к любому компоненту, функции или процедуре класса C_Controls из класса C_DrawImage, мы не сможем этого сделать. Хотя мы обращаемся к классу C_Controls через оператор this, в классе C_DrawImage можно получить доступ только к части, соответствующей классу C_Terminal.

Теперь мы рассмотрим последние детали, на которых стоит остановиться, прежде чем завершить нынешнюю статью. Для этого рассмотрим процедуру в строке 98, которая отвечает за позиционирование «пина» ползунка. Кроме того, она минимально управляет кнопками слева и справа от ползунка и панелью блокировки. Но то, что нас интересует, находится между строками 110 и 117. Что мы здесь видим? Обратите внимание: в коде класса C_DrawImage процедура Paint может принимать строку в качестве последнего аргумента. Данная строка используется для отображения информации о конкретном объекте при наведении на него мыши. До этого момента, единственным ориентиром для нас была интуиция, но теперь у нас есть кое-что более интересное. Мы можем легко и напрямую разместить пользовательское сообщение, которое информирует нас о чем-то актуальном. Первые два, которые находятся в строках 112 и 113, в большинстве случаев не имеют большого значения. Но если смотреть на сообщение в строке 114, увидим что-то интересное. Если установить «пин» ползунка, а затем навести на него курсор мыши, можно увидеть относительное положение, на которое он указывает. Подумайте, насколько это может быть полезно в разное время.

Предположим, у нас есть набор данных, и мы знаем, что нам интересно проанализировать что-то в этом наборе. Однако, поскольку система репликации/моделирования не позволяет переходить непосредственно к определенной позиции в наборе данных, это в некоторой степени целесообразно, поскольку делает процесс менее предсказуемым. Можно попробовать методом проб и ошибок определить, какая позиция находится непосредственно перед тем, что мы хотим проанализировать. Наблюдая за значением «пина» перед включением воспроизведения, можно определить, прошли ли мы интересующую нас точку. Если мы уже прошли её, достаточно закрыть приложение и открыть его снова, оставив «пин» на несколько позиций раньше. Раньше это было очень сложно сделать, потому что у нас не было ссылки на позицию, которую на самом деле указывала система.


Заключение

На этом мы заканчиваем сегодняшнюю статью. Я надеюсь, что материал будет полезен не только в том виде, в котором он здесь продемонстрирован. Я понимаю, что многим может показаться странным то, что мы рассмотрели в этой статье. Другие могут посчитать это излишним, учитывая, что индикатор управления и так прекрасно работает. Однако цель данной и всех моих статей - не предложить окончательное решение, а показать, что начинающим MQL5-программистам есть чему поучиться, и что вы всегда должны быть готовы учиться и изучать новые идеи и концепции.

Хотя исходный код больше не включен в приложение, статьи содержат его в полном объеме. Даже те части, на которые ссылаются как на фрагменты, так или иначе являются частью кода. Всё, что вам нужно сделать, - это взять фрагмент, показанный в статье, и заменить код на этот фрагмент. Таким образом, у вас всегда будет код в том виде, в котором он представлен в статьях.

Я не включаю исходный код в приложение именно из-за ситуаций, подобных той, что описана в этой статье. Более опытный специалист сможет справиться с возможными ошибками, которые возникают при модификации или при обычном использовании. Однако менее опытные пользователи могут допустить серьезные ошибки при попытке использовать данный тип кода.

В любом случае, в приложении вы получите доступ к исполняемым файлам, необходимым для работы с системой. Результат будет похож на видео, показанное ниже. Еще один момент: используемые данные можно скачать из предыдущих статей, где я предоставил тики и бары, используемые в демонстрациях, которые вы можете увидеть в видео в форме трех ZIP-файлов.

Демонстрационное видео

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/12293

Прикрепленные файлы |
Versko_DEMO.zip (247.12 KB)
Нейросети в трейдинге: Двухмерные модели пространства связей (Chimera) Нейросети в трейдинге: Двухмерные модели пространства связей (Chimera)
Откройте для себя инновационный фреймворк Chimera — двухмерную модель пространства состояний, использующую нейросети для анализа многомерных временных рядов. Этот метод предлагает высокую точность с низкими вычислительными затратами, превосходя традиционные подходы и архитектуры Transformer.
Упрощаем торговлю на новостях (Часть 3): Совершаем сделки Упрощаем торговлю на новостях (Часть 3): Совершаем сделки
В этой статье наш советник новостной торговли начнет открывать сделки на основе экономического календаря, хранящегося в нашей базе данных. Кроме того, мы улучшим графику советника, чтобы отображать более актуальную информацию о предстоящих событиях экономического календаря.
Переосмысливаем классические стратегии (Часть IV): SP500 и казначейские облигации США Переосмысливаем классические стратегии (Часть IV): SP500 и казначейские облигации США
В этой серии статей мы анализируем классические торговые стратегии с использованием современных алгоритмов, чтобы определить, можно ли улучшить стратегию с помощью искусственного интеллекта (ИИ). В сегодняшней статье мы рассмотрим классический подход к торговле индексом SP500, используя его взаимосвязь с казначейскими облигациями США (US Treasury Notes).
Разрабатываем мультивалютный советник (Часть 22): Начало перехода на горячую замену настроек Разрабатываем мультивалютный советник (Часть 22): Начало перехода на горячую замену настроек
Если мы взялись за автоматизацию проведения периодической оптимизации, то надо позаботиться и об автоматическом обновлении настроек советников, которые уже работают на торговом счёте. Также это должно позволять запускать советник в тестере стратегий и менять его настройки в рамках одного прохода.