English Русский Español Deutsch Português
preview
リプレイシステムの開発(第67回):コントロールインジケーターの改良

リプレイシステムの開発(第67回):コントロールインジケーターの改良

MetaTrader 5 | 21 5月 2025, 08:20
23 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ファイルのソースコード

ここで私たちにとって本当に重要なのは、このコードがどれだけスケーラブルであるか、つまり追加のコードを書くことなく、新たな機能の拡張やサポートが可能かどうかという点です。上記のコード自体は、現時点で新機能を必要としていないかもしれませんが、それが大きく依存しているコードについてはそうとは言い切れません。ここで指しているのは、チャート上のオブジェクトの作成と管理を担うコードです。具体的には、47行目で宣言されているC_DrawImageクラスのことです。

C_Controlsクラスがチャートに配置されるオブジェクトの作成を担当していると思われるかもしれませんが、それは完全には正しくありません。少なくとも、ほとんどのオブジェクトについては該当しません。実際、C_Controlsクラスが生成しているのは、スライダーコントロールをサポートする2つのオブジェクトのみです。言い換えれば、C_Controlsクラスが作成してチャートに追加するオブジェクトは、すべて53行目にあるプロシージャ内に記述されています。それでは、C_DrawImage.mqhヘッダーファイルのコードを詳しく見ていきましょう。以下に、その全コードを掲載します。

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

C_DrawImage.mqhファイルのソースコード

ここからが本当に面白くなってきます。C_DrawImageクラスにはビットマップファイルを扱うメソッドが含まれていますが、これは実際には使用されることはほとんどありません。その理由は、ビットマップをアプリケーション内の内部リソースとして埋め込んでいるということです。その結果、これに関連するコードの多くはMQL5によってコンパイルすらされません。とはいえ、今回焦点を当てたいのは、C_DrawImageクラスのそうした側面ではありません。

ここで注目すべき、より重要なポイントがあります。少し考えてみましょう。コントロールインジケーターのコードはC_DrawImageクラスに直接アクセスしていません。というより、そもそもその存在すら認識していません。ですが、よく見てみましょう。C_DrawImageクラスが宣言されている8行目では、C_Terminalクラスをpublic継承しています。これ自体は問題ありません。ところが、コントロールインジケーターがアクセスしているC_Controlsクラスの方を見ると、31行目で同じC_Terminalクラスをprivate継承していることがわかります。これは少々複雑な構造であり、設計上の問題を抱えている可能性があります。さらに悪いことに、C_DrawImageクラスの156行目を見てみてください。ここでは、新しいインジケーターを追加するかどうかを確認する処理がおこなわれていますが、実際にはこのチェックが意味のある形で使われることはありません。つまり、C_TerminalクラスはここでチャートIDにアクセスし、オブジェクトのプロパティを操作するという、ただ1つの目的のためだけに使用されているということです。

でも、よく考えてみてください。実際に必要ないクラスにまでC_Terminalへのアクセス権を与える理由があるでしょうか。たとえC_DrawImageがC_Terminalにアクセスする必要があるとしても、ポインタを渡し、そのポインタ経由でアクセスできるようにする方がはるかに賢明です。

こうした構成にすることで、より一貫性のある、安全かつ将来的に拡張しやすいコード設計が可能になります。拡張は継承や他の設計パターンを通じておこなうことができますが、いずれにしても、今の状態よりも遥かに安全で効率的な実装になります。

そして、ここが本題です。C_DrawImageクラスをより明確に依存型に変更しつつ、必要に応じて拡張可能な設計へとリファクタリングしましょう。そのための最初のステップとして、C_DrawImage.mqhのソースコードを修正します。ただしその前に、このファイルが依存するヘッダーファイル、Macro.mqhを確認しておきましょう。以下にその内容を示します。

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

macro.mqhファイルのソースコード

さて、ファイルMacro.mqhはC_Terminal.mqhファイルにインクルードされています。この点について疑問がある場合は、前回までの記事を参照し、どのように処理されているかをご確認ください。それでは、新たな使用方法にも対応した、変更後のC_DrawImage.mqhファイルを見ていきましょう。以下に、更新されたコードを掲載します。

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

C_DrawImage.mqhファイルのソースコード

一見すると大きな違いはないように見えるかもしれませんが、確かに変更点は存在しており、これによりクラスは長期的に見て、より持続可能で扱いやすい設計になっています。本質的には、これまでおこなっていた処理の方針を維持しつつ、実装を改善しています。というのも、このクラスは依然として外部ファイルではなく、実行ファイルに埋め込まれたリソースを使用することに焦点を当てているためです。

このようなアプローチにより、実際に必要な処理は大幅に簡素化されます。ご覧のとおり、コード量も少なく、ルーチンや手続きの管理も容易になっています。しかし、最も重要な変更点はクラスのコンストラクタにあります。61行目に注目してください。ここでコンストラクタが宣言されています。ここでは、C_Terminalクラスがポインタとして渡されるようになっています。したがって、63行目ではC_Terminalのメソッドを呼び出すことができ、64行目ではチャートIDにアクセスするためのprivate変数が初期化されます。これは、このクラスが扱うオブジェクト(具体的にはOBJ_BITMAP_LABEL)のプロパティを操作できるようにするためです。したがって、この時点で他に多くをする必要はありません。

65〜66行目では、新しい構造によって初期化処理がより堅牢かつ明確に構成されています。この初期化処理は48〜57行目で完全に実装されています。少なくとも私の観点からすると、これは比較的単純な実装であり、MQL5標準ライブラリの関数を積極的に使用しています。52行目のループを除けば、ブロック全体がMQL5ライブラリ関数で構成されています。ここで何が起こっているのかを正確に理解するには、必ずMQL5のドキュメントを確認してください。

ただし、52行目のループは興味深い処理をおこなっており、説明が必要です。これはネストされたループです。ここで何が起きているのかを把握しておかないと、後でC_DrawImageクラスを使う際に、何をしているのかが分かりづらくなります。このループの前半では、2つのローカル変数が宣言されます。しかし、このループ内でインクリメントされるのはY変数だけです。もう1つの変数については、ここで宣言と初期化をしておけば、それ以上気にする必要はありません。

次に、2つ目のループではさらに2つの変数が宣言されます。ここで、52行目の中で何が起こっているかに注目してください。最初の変数「PM」は、割り当てられた配列のサイズで初期化されます。この変数は、ループを終了させる条件として使われるため、絶対に変更してはいけません。ここでは、単純にPMをPIと比較することだけが必要です。PMも同じ行で宣言され、0で初期化されています。重要な点として、このループ内では実際に何も処理をおこなっていません。このループの唯一の目的は、続く53行目のループの制御に使うためのものです。

そして53行目では、より複雑なループが始まります。しかし、少し注意すれば、何が起きているのか理解できます。まず、新しい変数C0が宣言され、0で初期化されます。これは画像の幅に達するまでループを実行するためのカウンタです。この部分は単純です。次に、より複雑な部分に入ります。ここでは、もう1つの変数「PF」を宣言します。この変数の初期値は、PIの値と、画像に対してどのような操作をおこなうかによって決まります。いずれにしても、PIは常に増加していきます。しかしPFの方は、増加することもあれば、しないこともあり、時には減少する場合さえあります。その理由を理解するには、プロシージャの宣言にあるブール型パラメータ「R180」に注目してください。R180がtrueの場合、私たちは生成される画像が元の画像の鏡像になるようにしたいわけです。それを実現するために、R180がtrueのときは、PFをPIと画像の幅の合計で初期化します。そして、ループの各反復ごとにPFをデクリメントしていきます。一方で、R180がfalseの場合、理論上は元の画像をそのままメモリ上の配列に直接コピーすることになります。

「理論上は」と言う理由は、それは54行目で何がおこなわれているかに関係しています。ここでは画像にフィルターをかけ、特定の色を探しています。そしてその色が見つかった場合、そのピクセルを透明に変換します。このため、コピー処理もミラー処理も、理論上は元の画像と同じになります。このように配列を使った処理をおこない、メモリの割り当てや解放を自分で制御する必要があるのは、MQL5がCやC++のようにポインタを扱えないためです。したがって、これらのプロセスはやや異なる形で実装しなければなりません。私は、可能な限りMQL5標準ライブラリの関数を使うことを強く勧めます。これらの関数は、自分で書いたコードよりもはるかに最適化されており、同じ結果を得るにしても効率がまったく違います。それに加えて、MQL5ライブラリに将来的な改良が加えられれば、その恩恵は自動的にプログラムに反映されます。これは、手動で更新しなければならないカスタム実装とは対照的です。

とはいえ、MQL5ライブラリに大きく依存している別のプロシージャがあります。76行目から始まるPAINTです。このメソッドの中では、ほとんどすべての呼び出しがMQL5標準ライブラリの関数です。唯一の例外が78行目で、ここでは21行目から46行目の間に定義されたプロシージャを呼び出しています。ここは注意深く見てほしいところです。28行目では、オブジェクトに表示する画像のメモリを割り当てています。そして29行目では、その画像が変更される必要があるかどうかをチェックしています。もし変更の必要がなければ、その画像はそのままオブジェクトに転送できます。処理速度を最大限に高めるために、ライブラリの関数を使って画像を直接配列にコピーします。繰り返しますが、もしMQL5がCやC++のようにポインタを扱えるのであれば、この実装は全く異なる形になっていたはずです。しかし、画像に何らかの変更が必要であれば、30行目から始まるループに入ります。このループでは画像のサイズを調整したり、透明度を設定したりといった必要な処理をおこないます。

これで、C_DrawImageクラスのコードに加えられた変更に関する説明は完了です。このコードは変更されたため、現在C_DrawImageを直接使用しているC_Controlsクラスも更新する必要があります。次に、更新されたC_Controls.mqhファイルのコードを見てみましょう。

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

C_Controls.mqhファイルのソースコード

新しいコードで最初に気づくのは、一連の定義です。さて、質問です。左ボタンと右ボタンの定義はどこにあるでしょう。7行目から17行目の間を見てみると、これまであった右ボタンと左ボタンの定義がなくなっています。その代わりに、少し異なる形の定義が登場しています。興味深いことに、ここではLEFT.BMPとLEFT_BLOCK.BMPの画像が参照されていますが、右側の画像は参照されていません。なぜこのようなことが起こるのでしょうか。その理由はC_DrawImage.mqhファイルで説明されています。右ボタンと左ボタンは本質的に同一、より正確には一方が他方の鏡像であるため、両方を定義する必要はありません。片方さえ定義しておけば、もう一方はC_DrawImageクラスを使って生成できるのです。

他に何が変わったか見てみましょう。68行目では、再生ボタンと一時停止ボタンの作成と識別がおこなわれています。同じルーチンの72行目では、宣言の末尾でオブジェクトに追加のプロパティを割り当てているのがわかります。これにより、オブジェクトに対して追加の情報を持たせることができます。このケースではあまり大きな違いにはなりませんが、別の文脈では非常に有用になることがあります。

再生/一時停止ボタンを作成しているこのルーチンと、その下でボタンとスライダーを作成しているルーチンには、共通して説明しておきたい重要なポイントがあります。それを理解するには、一度C_DrawImageクラスのコンストラクタのコードに戻ってみましょう。

C_DrawImageの61行目にあるコンストラクタのソースコードを見ると、第一引数にC_Terminalクラスへの参照が必要であることがわかります。これは非常に重要なので、細心の注意を払ってください。コンストラクタは最初の引数として、C_Terminalクラスのインスタンスへのポインタを受け取ることを前提にしています。ところがC_Controls.mqhヘッダーファイルの71、80、81、82行目を見ると、期待されるような形でこの情報が渡されていません。そこで、27行目にあるC_Controlsクラスの宣言に立ち戻ってみましょう。これから説明する内容を理解できなければ、この先のコードはほとんど意味をなさなくなります。

C_Controlsクラスは、C_Terminalクラスをprivateに継承しています。このため、C_Controlsクラス内部のコードでは、C_TerminalをまるでC_Controlsの一部であるかのように参照することができます。これは非常に重要な点です。ただし、この参照が可能なのはC_Controlsクラスの内部に限られます。なぜなら、継承がprivateでおこなわれているからです。もし継承がpublicであれば、C_Controlsを使う他のコードからもC_Terminalのpublicメソッドや関数にアクセスできるようになります。さて、では、これがC_DrawImageのコンストラクタとどう関係するのでしょうか。ここまでの継承モデルを理解していれば、話がつながってくると思います。C_DrawImageのコンストラクタは、C_Terminalへのポインタを必要としています。しかし、実際にはC_Controlsのポインタを渡しているわけです。これがどのように機能しているのでしょうか。ライブラリ関数「GetPointer」にthisを引数として渡すことで、現在のクラス(この場合はC_Controls)のポインタを取得しています。

ここでthis演算子の意味を理解する必要があります。thisは、どのクラスで使われてもその時点でのクラス自身を指します。したがって、GetPointer(this)を呼び出すと、その時点のクラス、つまりC_Controlsのインスタンスへのポインタを取得することになります。そしてC_ControlsはC_Terminalをprivateに継承しているため、継承関係の内部においてはC_Terminalとしても扱えるのです。このため、C_DrawImageのコンストラクタがC_Terminalを受け取るように設計されていても、C_Controlsのポインタで代用できるわけです。

一見すると、これは継承のルールに反しているように思えるかもしれませんが、そうではありません。実際、C++には同様の挙動を実現する演算子が存在しますが、それはより複雑で、場合によっては危険性も伴います。ただし、ここで安心してはいけません。この構造には一定のリスクがあります。もしC_Terminalを無計画に設計し、どんな関数やメソッドからでもprivateメンバーにアクセスできるようにしてしまったり、変数をpublicにしてしまった場合、C_DrawImageは間接的にC_Terminalの内部状態を変更できてしまうことになります。これは継承がC_Controlsを経由しているからです。つまり、適切な設計がされていなければ、この構造は大きなセキュリティホールになり得ます。ですが、しっかりとしたカプセル化がなされていれば、このような柔軟な構成でも問題は起こりません。

ここで私が伝えたいのは、この構造の持つ重要性です。たしかに型破りなように思えるかもしれませんが、適切に運用すれば非常に柔軟で、面白い使い方が可能になります。

継承がなければ、このアプローチは成り立ちません。また、C_DrawImage内からC_Controlsのコンポーネントや関数にアクセスしようとしてもできません。C_Controlsのインスタンスをthis経由で渡していますが、C_DrawImageが実際にアクセスできるのは、C_Terminalに関連するオブジェクトの部分のみです。

この記事を締めくくる前に、最後に注目すべきいくつかのポイントを見てみましょう。98行目にあるスライダーピンの位置を決めるプロシージャを確認します。この部分は、スライダーの左右にあるボタンやロックバーに対しても最低限の操作を提供しています。しかし、特に注目したいのは110行目から117行目の間です。ここで何がおこなわれているのか、C_DrawImageクラスのPaintメソッドを見てみましょう。このメソッドは最後の引数に文字列を受け取り、マウスがオブジェクト上にあるときにツールチップ形式のメッセージを表示します。これまでは、操作中に何が起こっているのか直感に頼っていましたが、カスタムメッセージをシンプルかつ直接的に表示できるようになり、使用時の意味のあるフィードバックを提供できます。最初の2つのメッセージ(112行目と113行目)は比較的小さなもので、ほとんどのケースであまり重要ではありません。しかし、114行目のメッセージには興味深い内容があります。スライダーピンを調整してマウスを乗せると、現在指している相対位置が表示されます。これがさまざまな場面でどれほど役立つかを考えてみてください。

例えば、あるデータセットを扱っていて、分析したい特定のポイントがある場合を想定しましょう。リプレイやシミュレーターは特定の位置に直接ジャンプすることを許さず、これはプロセスが単調になりすぎるのを防ぐという意味でむしろ良いことですが、そのため試行錯誤に頼らざるを得ません。分析対象の直前の位置を推測する必要があります。[再生]ボタンを押す前にピンの値を確認することで、興味あるポイントをすでに通過しているか判断できます。もし行き過ぎてしまったら、アプリケーションを一旦閉じて再起動し、ピンを数ステップ前に戻せばよいのです。相対位置が分からなかったためにかつては非常に難しかったこの作業も、今でははるかに簡単になっています。


結論

この記事はこれで終わりです。ここで共有した内容が、今回の実装だけでなく、今後のプログラミング作業にも役立つことを願っています。もしかすると、一部の読者には今回の内容が少し変わっていたり、不必要に思えるかもしれません。特に、コントロールインジケーター自体はすでに問題なく動作していたのでなおさらです。ただ、この記事や私の他の記事の目的は、完全な解決策を示すことではなく、MQL5プログラマーを目指す皆さんに「学ぶべきことはまだたくさんある」ということを理解してもらい、新しいアイデアや異なる視点に対して柔軟であることの重要性を伝えることにあります。

最近はソースコードのファイル自体は配布していませんが、記事中には必要なコードをすべて掲載しています。たとえ「コードの断片」と呼んでいるものも、実際には動作するコードの一部です。表示されている断片をコピーして、ご自身のコードの該当部分に置き換えれば、記事の内容とコードが同期した状態で動かせます。

完全なソースファイルを配布しなくなった理由は、まさにこの記事で説明したような構造に起因しています。経験豊富な開発者であれば、修正時や通常の使用時に発生するエラーや予期しない挙動を対処できますが、経験の浅い方が同じロジックを実装しようとすると、重大なミスをしやすいからです。

とはいえ、配布しているアプリケーションにはシステム操作に必要な実行ファイルが含まれており、下のデモ動画のように動作します。なお、デモで使っているデータは過去の記事で提供しているティックとバーの3つのZIPファイルから入手可能です。

デモビデオ

MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12293

添付されたファイル |
Versko_DEMO.zip (247.12 KB)
初級から中級へ:配列と文字列(II) 初級から中級へ:配列と文字列(II)
この記事では、プログラミングがまだ非常に初歩的な段階にあるにもかかわらず、すでにいくつかの興味深いアプリケーションを実装できることを示します。今回は、比較的シンプルなパスワードジェネレーターを作成します。このようにして、これまでに説明してきたいくつかの概念を実際に適用することができます。加えて、特定の問題に対する解決策をどのように構築できるかについても考察していきます。
取引におけるニューラルネットワーク:Superpoint Transformer (SPFormer) 取引におけるニューラルネットワーク:Superpoint Transformer (SPFormer)
本記事では、中間データの集約を不要とするSuperpoint Transformer (SPFormer)に基づく3Dオブジェクトのセグメンテーション手法を紹介します。これによりセグメンテーション処理の高速化とモデル性能の向上が実現されます。
雲モデル最適化(ACMO):実践編 雲モデル最適化(ACMO):実践編
この記事では、ACMO(Atmospheric Cloud Model Optimization:雲モデル最適化)アルゴリズムの実装について、さらに詳しく掘り下げていきます。特に、低気圧領域への雲の移動および水滴の初期化と雲間での分布を含む降雨シミュレーションという2つの重要な側面に焦点を当てます。また、雲の状態を管理し、環境との相互作用を適切に保つために重要な役割を果たす他の手法についても紹介します。
初級から中級へ:配列と文字列(I) 初級から中級へ:配列と文字列(I)
本日の記事では、いくつかの特殊なデータ型について見ていきます。まず、文字列とは何かを定義し、いくつかの基本的な操作方法を説明します。これにより、興味深いデータ型を扱えるようになりますが、初心者にとっては少し混乱することもあるかもしれません。ここで提示されるコンテンツは、教育目的のみに使用されることを意図しています。いかなる状況においても、提示された概念を学習し習得する以外の目的でアプリケーションを閲覧することは避けてください。