Русский 中文 Español Deutsch 日本語 Português
preview
Making charts more interesting: Adding a background

Making charts more interesting: Adding a background

MetaTrader 5Examples | 27 April 2022, 11:08
9 092 9
Daniel Jose
Daniel Jose

Introduction

Many workstations contain some representative image which shows something about the user. These images make the working environment more beautiful and inspiring, as people always try to choose the vest and most beautiful images to use as wallpaper. But when we open the trading platform, we see that it is somehow boring. All we have is the graphical representation of numerical data.

 


You can look at images and photos for quite a long period without getting tired, but observing a price chart even for a few minutes can be very tiring. So, let's make it so that we can watch and analyze the chart, while the image in the background motivates us and reminds us of something good.


Planning

First of all, we need to determine one thing that will define how the entire project will work: whether we want to change the chart background from time to time, or we are going to use one image throughout the entire lifetime of the program, with the same image used for all charts? I like to place different images on different charts. For example, something that represents the type of the asset I am trading or something that hints at what I should be looking into in this asset. Therefore, the resulting compiled file will not have any internal image, and thus we will be able to select any desired image later.

Now there is another thing to define: Where should our images be located? MetaTrader 5 has a directory structure that we should use to access things. The directory tree cannot be used outside of this framework. Knowing how to use this structure is paramount if we want to access images later. Since we plan to organize the storage and maintain it over time, we will create a folder in the FILES directory and name it WALLPAPERS. Thus we will be accessing images without leaving the tree, the root of which is the MQL5 directory.

But why not put the files in the IMAGES folder? In this case, we would have to navigate through the tree, which would be an unnecessary task that complicates the logic of the program. But since we strive for maximum simplicity, we will use what MetaTrader 5 offers us. So, the structure will look as follows:




After that we will add images as shown below, i.e. we will separate logo images from general background images. This organization keeps things in order, as there can be a lot of different images used as logos, if we are working with multiple assets.



This is a simple and efficient solution: add as many images as you need without interfering with the program operation. Now, please pay attention to an important detail. The images are in the BITMAP format. They should be in 24 or 32 bit type because these formats are easy to read: MetaTrader 5 can read these formats by default so I left it that way. Of course, it is also possible to use other types, if you can program the reading routine so that in the end you have a BITMAP image. However, I believe that it is easier to use an image editor and convert an image to the 24-bit or 32-bit standard than to create a separate reading function. Files in the LOGOS folder follow the same principles, but with some exceptions, which we will see shortly.

Now that we have defined the rules, let's proceed to coding. The code follows the principles of Object Oriented Programming (OOP), so you can easily port it to a script or indicator if you want, or even isolate it if needed.


Step by Step

The code starts with some definitions:

//+------------------------------------------------------------------+
enum eType {IMAGEM, LOGO, COR};
//+------------------------------------------------------------------+
input char     user01 = 30;               //Transparency ( 0 a 100 )
input string   user02 = "WallPaper_01";   //File name
input eType    user03 = IMAGEM;           //Chart background type
//+------------------------------------------------------------------+



Here we indicate what we are going to do. The eType enumeration indicates what the background graphics will be: IMAGE, LOGO or Color. The USER02 entry specifies the name of the file that will be used as a background, provided that IMAGE type is selected in USER03. USER01 indicates the level of transparency of the background image, as in some cases it can interfere with data visualization on the chart. So we use transparency to minimize this effect. The transparency value can be from 0% to 100%: the higher the value, the more transparent the image.




The following functions should be added to your program:

Function Parameters Where to declare a function  Result
Init(string szName, char cView) File name and desired transparency level As the first function in OnInit code Loads the specified BITMAP file and renders it with the specified transparency
Init(string szName)  Only the file is required As the first function in OnInit code Loads the specified BITMAP file without any transparency
Resize(void) No parameters required In OnChartEvent code; in the CHARTEVENT_CHART_CHANGE event Resizing the image on the chart appropriately

So, let's see how to use these functions in the main code, starting with the class initialization shown below. Note that the user in this case can specify the level of transparency. For the value to be correct, we should subtract it from 100.

int OnInit()
  {
   if(user03 != COR)
      WallPaper.Init(user03 == IMAGE ? "WallPapers\\" + user02 : "WallPapers\\Logos\\" + _Symbol, (char)(100 - user01));
   return INIT_SUCCEEDED;
  }



Note that if we use the COLOR mode, the image will not appear. But pay attention to the triple operator. When we select an image, the program will point to the WALLPAPER directory in the FILES tree. When it is LOGO, it will also point to the relevant location, but note that the filename for the logo must match the symbol name, otherwise an error will be generated. This is all in case of continuous series. But if you use an asset with an expiration date, it will be necessary to add a small function to separate the part of the name that distinguishes the current series from the expired one. The problem can be solved by simply renaming the image so that it reflects the current name. For those who use cross orders, it may be interesting to set up a separate symbol name adjustment routine.

The next function to pay attention to is the following:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if(id == CHARTEVENT_CHART_CHANGE)
      WallPaper.Resize();
  }



All codes are very short, as I don't like to complicate things as it makes it difficult to improve or change the system. Also, try to make it your rule. The above function will guarantee that any change in chart sizes will call the function that will resize the image to always provide a nice look and to have the image fully rendered.

Our class code has the following features:

Function Parameters   Result 
MsgError(const eErr err, int fp) Error type and file descriptor Closes the file and shows the corresponding error message.
MsgError(const eErr err) Error type Shows the corresponding error message
LoadBitmap(const string szFileName, uint &data[], int &width, int &height) File name and data pointers Loads the required file and returns its data in the data[] format, as well as its dimensions in pixels.
~C_WallPaper() No parameters required Provides closing of the object class
Init(const string szName, const char cView) File name and transparency level Correctly initialize the entire class
Init(const string szName) File name Correctly initialize the entire class
Destroy(void) No parameters required Ends the class appropriately
Resize(void)  No parameters required Correctly resizes the image

To avoid complete chaos in the code, I concentrated error processing in one function which is shown below. The only thing it does is send a message to the user if something goes wrong. This makes things easier since in the case of translating to another language, all you need to do is change the messages in a single routine and not try to find every message used.

   bool              MsgError(const eErr err, int fp = 0)
     {
      string sz0;
      switch(err)
        {
         case FILE_NOT_FOUND  :
            sz0 = "File not found";
            break;
         case FAILED_READ     :
            sz0 = "Reading error";
            break;
         case FAILED_ALLOC    :
            sz0 = "Memory error";
            break;
         case FAILED_CREATE   :
            sz0 = "Error creating an internal resource";
            break;
        };
      MessageBox(sz0, "WARNING", MB_OK);
      if(fp > 0)
         FileClose(fp);
      return false;
     }



The below function reads the file and loads it into memory. The only information we need is the file name, while the function will fill the rest of the data. In the end you will get the dimensions of the image and the image itself, but in the BITMAP format. It is important to note this because although there are several formats, the result always ends with BITMAP; only the way it is compressed is what differs one format from another.

   bool              LoadBitmap(const string szFileName, uint &data[], int &width, int &height)
     {
      struct BitmapHeader
        {
         ushort      type;
         uint        size;
         uint        reserv;
         uint        offbits;
         uint        imgSSize;
         uint        imgWidth;
         uint        imgHeight;
         ushort      imgPlanes;
         ushort      imgBitCount;
         uint        imgCompression;
         uint        imgSizeImage;
         uint        imgXPelsPerMeter;
         uint        imgYPelsPerMeter;
         uint        imgClrUsed;
         uint        imgClrImportant;
        } Header;
      int fp;
      bool noAlpha, noFlip;
      uint imgSize;

      if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE)
         return MsgError(FILE_NOT_FOUND);
      if(FileReadStruct(fp, Header) != sizeof(Header))
         return MsgError(FAILED_READ, fp);
      width = (int)Header.imgWidth;
      height = (int)Header.imgHeight;
      if(noFlip = (height < 0))
         height = -height;
      if(Header.imgBitCount == 32)
        {
         uint tmp[];
         noAlpha = true;
         imgSize = FileReadArray(fp, data);
         if(!noFlip)
            for(int c0 = 0; c0 < height / 2; c0++)
              {
               ArrayCopy(tmp, data, 0, width * c0, width);
               ArrayCopy(data, data, width * c0, width * (height - c0 - 1), width);
               ArrayCopy(data, tmp, width * (height - c0 - 1), 0, width);
              }
         for(uint c0 = 0; (c0 < imgSize && noAlpha); c0++)
            if(uchar(data[c0] >> 24) != 0)
               noAlpha = false;
         if(noAlpha)
            for(uint c0 = 0; c0 < imgSize; c0++)
               data[c0] |= 0xFF000000;
        }
      else
        {
         int byteWidth;
         uchar tmp[];
         byteWidth = width * 3;
         byteWidth = (byteWidth + 3) & ~3;
         if(ArrayResize(data, width * height) != -1)
            for(int c0 = 0; c0 < height; c0++)
              {
               if(FileReadArray(fp, tmp, 0, byteWidth) != byteWidth)
                  return MsgError(FAILED_READ, fp);
               else
                  for(int j = 0, k = 0, p = width * (height - c0 - 1); j < width; j++, k+=3, p++)
                     data[p] = 0xFF000000 | (tmp[k+2] << 16) | (tmp[k + 1] << 8) | tmp[k];
              }
        }
      FileClose(fp);
      return true;
     }



Look at the following code line:

      if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE)



Please note that the file extension is specified at this point, i.e. we don't specify it at the moment when we indicate what the image will be, because if we specify the extension, we will get a "file not found" error. The rest of the function is quite simple: first it reads the header of the file and checks if it is a 32 bit or 24 bit BITMAP; then it reads the image accordingly, since 32 bit images have a slightly different internal structure than 24 bit ones.

The next function initializes all data for our bitmap image which will be displayed on the screen. Please note that during this function we convert this bitmap file into a program resource. It is necessary because later we will link this resource to an object, and it is precisely this object that will be displayed on the screen not as an object but as a resource. It seems hard to understand why we're doing it this way. But this step allows us to create multiple resources of the same type and then to associate them with a single object that will be used to display something. If we added a single certain resource to the program, we would define it as an internal resource and compiled the file. In this case it would be impossible to change the resource without recompiling the source code. However, by creating a dynamic resource, it is possible to specify which resource to use.

   bool              Init(const string szName, const char cView = 100, const int iSub = 0)
     {
      double dValue = ((cView > 100 ? 100 : (cView < 0 ? 0 : cView)) * 2.55) / 255.0;
      m_Id = ChartID();
      if(!LoadBitmap(szName, m_BMP, m_MemWidthBMP, m_MemHeightBMP))
         return false;
      Destroy();
      m_Height = m_MemHeightBMP;
      m_Width = m_MemWidthBMP;
      if(ArrayResize(m_Pixels, (m_MemSizeArr = m_Height * m_Width)) < 0)
         return MsgError(FAILED_ALLOC);
      m_szRcName = "::" + szName + (string)(GetTickCount64() + MathRand());
      if(!ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
         return MsgError(FAILED_CREATE);
      if(!ObjectCreate(m_Id, (m_szObjName = szName), OBJ_BITMAP_LABEL, iSub, 0, 0))
         return MsgError(FAILED_CREATE);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_XDISTANCE, 0);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_YDISTANCE, 0);
      ObjectSetString(m_Id, m_szObjName, OBJPROP_BMPFILE, m_szRcName);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_BACK, true);
      for(uint i = 0; i < m_MemSizeArr; i++)
         m_BMP[i] = (uchar(double(m_BMP[i] >> 24) * dValue) << 24) | m_BMP[i] & 0x00FFFFFF;
      return true;
     }



This is all very nice and apparently practical, but the object itself is not capable of changing the resource. It means that it is not possible to change the way a resource will work or be presented simply by linking it to an object. This sometimes complicates things a bit, since most of the time we have to code the way the resource should be modified inside the object.

Until this point, an image could be rendered using the following code:

      if(ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
         ChartRedraw();



But using this code will not guarantee that the image will be presented as expected, except for the fact that it has the exact dimensions of the chart. I advise you to use high-definition images. Large images are represented better, making calculation easier and this saving time on processing which can be crucial in certain scenarios. But even so we still have the problem of the image not being correctly presented as the object will not change the resource so that it fits the object's specifications. So, we have to do something to enable correct modeling of the resource so that it can be presented using the object. The math related to the image ranges from the simplest calculations, to very complicated things. But since processing time is crucial for us as it is using the price chart, we can't afford doing excessive calculations. Things should as simple and fast as possible. Due to this, we can use images larger than the chart, as the only calculation required would be reducing the size. Let's see how this will work.

The mathematical relationships used on the above chart can be obtained as follows:


Note that here f(x) = f(y) which preserves the image ratio. This is also known as the "aspect ratio", meaning the image changes completely. But what if f(y) did not depend on f(x), what would happen to our image? Well, it would be changing disproportionately, thus taking any shape. Although we have no problem when reducing the size, this is not true for increasing: if the f(x) > 1.0 or f(y) > 1.0, we will get an image zoom, which is where problems start. The first problem appears in the image below:


This happens because of the effect which can be seen in the figure below. Note that the white spaces represent empty areas which appear in the image when it grows through the zoom in effect. This will always happen when f(x) or f(y) is greater than one, i.e. when we follow the red arrow. In the figure below, f(x) = f(y) = 2.0, i.e. we enlarge the image by 2x.


There are several ways to solve this problem, one of them is the interpolation that should happen when an empty block is found. At this moment, we should do factorization and calculate the intermediate color between the used one to produce a smoothing effect and to fill in empty points. But there is a problem connected with computations. Even if the interpolation is done quickly, this may not be a suitable solution for MetaTrader 5 charts featuring real-time data. Even if the resizing is done a few times during the entire time that the chart is on screen (because in most cases the chart size will be smaller than the image, in which case f(x) and f(y) will be equal to or less than 1.0, and interpolation has no effect) but if you think about using an image sized 1920 x 1080 (FULL HD) on the same sized screen, the interpolation con considerably increase processing time with no benefit to the final result.

Let's see below how the interpolation calculation will be done on an image that will double its size. Obviously it will be very fast, but don't forget this should be done in a 32-bit color scheme, or ARGB, where we have 4 bytes of 8 bits for calculation. The GPU has functions that allow do these calculations quickly but accessing these functions through OpenCL may not give any practical benefit, since we will have a delay in data input and output from the GPU. Due to this there can be no advantage from the speed of calculations performed by the GPU.


                             


With that in mind, I think that a slightly worse image quality due to the smoothing effect is not a big deal, since in most cases f(x) or f(y) will not be higher than 2, only when using a FULL HD image on a 4k screen. In this scenario, smoothing will be minimal and can hardly be seen. Therefore, instead of interpolating points, I prefer to drag a point to the next one, quickly filling in empty values, thereby reducing computational costs to a minimum. The way it is done is shown below. Since we simply copy the data, we can process all 32 bits in one step, and it will be as fast as what would be delivered by a graphics processing system.


                   


Thus, here is the function enabling quick resizing of the image.

void Resize(void)
{
        m_Height =(uint) ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS);
        m_Width = (uint) ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS);
        double fx = (m_Width * 1.0) / m_MemWidthBMP;
        double fy = (m_Height * 1.0) / m_MemHeightBMP;
        uint pyi, pyf, pxi, pxf, tmp;

        ArrayResize(m_Pixels, m_Height * m_Width);
        ArrayInitialize(m_Pixels, 0x00FFFFFF);
        for (uint cy = 0, y = 0; cy < m_MemHeightBMP; cy++, y += m_MemWidthBMP)
        {
                pyf = (uint)(fy * cy) * m_Width;
                tmp = pyi = (uint)(fy * (cy - 1)) * m_Width;
                for (uint x = 0; x < m_MemWidthBMP; x++)
                {
                        pxf = (uint)(fx * x);
                        pxi = (uint)(fx * (x - 1));
                        m_Pixels[pxf + pyf] = m_BMP[x + y];
                        for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y];
                }
                for (pyi += m_Width; pyi < pyf; pyi += m_Width) for (uint x = 0; x < m_Width; x++) m_Pixels[x + pyi] = m_Pixels[x + tmp];
        }
        if (ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
                ChartRedraw();
}   



The function has a nested loop, the inner loop will execute the function f(x), the external loop - f(y). When executing f(x), we can have empty areas - this is fixed in the following line:

for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y];



If a difference occurs between X values, the above line will fix it by copying the last value of the image. As a consequence, we will have aliasing, but the computational cost in these cases will be minimal, since this fragment will have an internal loop running for the minimum time in case it is executed (which will not always be the case). If you want to interpolate data without this aliasing effect, just change this line to create the calculations explained above.

Once the whole line has been calculated, check f(y) to avoid empty areas if f(y) is greater than 1. This is done in the following line:

for (pyi += m_Width; pyi < pyf; pyi += m_Width) for (uint x = 0; x < m_Width; x++) m_Pixels[x + pyi] = m_Pixels[x + tmp];

Again, this will lead to aliasing, but this can be fixed in the same way as changing the code in the previous line. We add the width value of the new image because we are copying a line which has already been handled by the loop responsible for handling f(x) of the new image. If this were done using any other value, the image would be distorted in a strange way.


Conclusion

I hope this idea will make your charts more fun and enjoyable to look at for hours and hours, because when the background image becomes tiring, you can simply choose another one, without having to recompile anything. Simply choose the new image you want to be displayed as your chart background.

One last detail to be mentioned here: if you care using the background image placing class in an EA, it must be the first thing to be declared in the INIT routine. This will prevent the background image from overlapping other graphical objects created by the EA.

Enjoy the final result responsibly, as now you will delve even deeper into chart analysis...



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

Attached files |
EA_With_Wallpaper.zip (7778.85 KB)
Last comments | Go to discussion (9)
felipe ramos
felipe ramos | 2 May 2022 at 20:40
The possibility of using it in the function that I present in this code
 //+------------------------------------------------------------------+
//|                                                   SelectFile.mqh |
//+------------------------------------------------------------------+
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Edit.mqh>
#include <Controls\ListView.mqh>
#include <Controls\CheckGroup.mqh>
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
#define  INDENT_LEFT             ( 4 )             // indent from left
#define  INDENT_TOP              ( 4 )             // indent from top
#define  INDENT_RIGHT            ( 4 )             // indent from right
#define  BUTTON_WIDTH            ( 60 )     // size by X coordinate
#define  BUTTON_HEIGHT   ( 20 )       // size by Y coordinate
#define  EDIT_HEIGHT             ( 20 )       // size by Y coordinate
#define  COMMON_WIDTH            ( 90 )       // size by X coordinate
//+------------------------------------------------------------------+
//| Class CSelectFile                                                |
//+------------------------------------------------------------------+
class CSelectFile : public CDialog
  {
   CEdit             m_filename;
   CButton           m_button_ok;
   CButton           m_button_cancel;
   CListView         m_filelist;
   CCheckGroup       m_common;
   string             m_instance_id,
                     m_files[],
                     m_folders[],
                     m_prevfolder,
                     m_cfolder,
                     m_fullname;
   int                m_numberfiles,
                     m_numberfolders,
                     m_totalfiles,
                     m_fileflag,
                     m_pressbutton;
protected :
   CChart            m_chart;
public :
                     CSelectFile( void );
                    ~CSelectFile( void );
   virtual bool       Create( const long chart, const string name, const int x1, const int y1, const int x2, const int y2);
   int                ChartEvent( const int id, const long &lparam, const double &dparam, const string &sparam);
   string             Filename(){ m_pressbutton= 0 ; return (m_fullname);}
   int                FileFlag(){ return (m_fileflag);}
protected :
   void               OnClickButtonOK( void );
   void               OnClickButtonCancel( void );
   virtual void       OnClickButtonClose( void );
   void               OnChangeList( void );
   void               OnCheckCommon( void );
   void               SetFolder( string m_fol= "" );
  };
//+------------------------------------------------------------------+
CSelectFile::CSelectFile( void )
  {
   m_instance_id= IntegerToString ( rand (), 5 , '0' );
   m_fileflag= 0 ;
   m_pressbutton= 0 ;
   m_fullname= "" ;
   m_cfolder= "" ;
   m_prevfolder= "" ;
   m_numberfiles= 0 ;
   m_numberfolders= 0 ;
  }
//+------------------------------------------------------------------+
CSelectFile::~CSelectFile( void )
  {
   ArrayFree (m_folders);
   ArrayFree (m_files);
   m_chart.Detach();
   CDialog::Destroy();
  }
//+------------------------------------------------------------------+
bool CSelectFile::Create( const long chart, const string name, const int x1, const int y1, const int x2, const int y2)
  {
   if (x2-x1< 280 || y2-y1< 200 ) return ( false );
   m_chart_id=chart;
   m_name=name;
   m_subwin= 0 ;
//--- initialize chart object
   m_chart.Attach(chart);
//--- specify object and mouse events
   if (!m_chart.EventObjectCreate() || !m_chart.EventObjectDelete() || !m_chart.EventMouseMove())
     {
       Print ( "CSelectFile: object events specify error" );
      m_chart.Detach();
       return ( false );
     }
//--- call method of the parent class
   if (!CDialog::Create(m_chart.ChartId(),m_instance_id,m_subwin,x1,y1,x2,y2))
     {
       Print ( "CSelectFile: expert dialog create error" );
      m_chart.Detach();
       return ( false );
     }
   Caption(name);
//--- create dependent controls
//--- create list of files
   int _x1=INDENT_LEFT;
   int _y1=INDENT_TOP;
   int _x2=ClientAreaWidth()-INDENT_RIGHT;
   int _y2=_y1+(ClientAreaHeight()-INDENT_TOP* 5 -EDIT_HEIGHT* 2 );
   if (!m_filelist.Create(m_chart_id,m_name+ "FileList" ,m_subwin,_x1,_y1,_x2,_y2)) return ( false );
   if (!Add(m_filelist)) return ( false );
   m_prevfolder= "" ;
   m_cfolder= "" ;
   SetFolder(m_cfolder);
//--- create field of filename
   _x1=INDENT_LEFT;
   _y1=INDENT_TOP+_y2;
   _x2=ClientAreaWidth()-INDENT_RIGHT;
   _y2=_y1+EDIT_HEIGHT+INDENT_TOP;
   if (!m_filename.Create(m_chart_id,m_name+ "Filename" ,m_subwin,_x1,_y1,_x2,_y2)) return ( false );
   if (!Add(m_filename)) return ( false );
//--- create common check
   _x1=INDENT_LEFT;
   _y1=INDENT_TOP+_y2;
   _x2=_x1+COMMON_WIDTH;
   _y2=_y1+EDIT_HEIGHT;
   if (!m_common.Create(m_chart_id,m_name+ "Common" ,m_subwin,_x1,_y1,_x2,_y2)) return ( false );
   if (!Add(m_common)) return ( false );
   if (!m_common.AddItem( "Common" , 1 )) return ( false );
//--- create button Cancel
   _x1=ClientAreaWidth()-INDENT_RIGHT-BUTTON_WIDTH;
   _x2=_x1+BUTTON_WIDTH;
   _y2=_y1+BUTTON_HEIGHT;
   if (!m_button_cancel.Create(m_chart_id,m_name+ "Cancel" ,m_subwin,_x1,_y1,_x2,_y2)) return ( false );
   if (!m_button_cancel.Text( "Cancel" )) return ( false );
   if (!Add(m_button_cancel)) return ( false );
//--- create button OK
   _x1=_x1-INDENT_RIGHT-BUTTON_WIDTH;
   _x2=_x1+BUTTON_WIDTH;
   if (!m_button_ok.Create(m_chart_id,m_name+ "OK" ,m_subwin,_x1,_y1,_x2,_y2)) return ( false );
   if (!m_button_ok.Text( "OK" )) return ( false );
   if (!Add(m_button_ok)) return ( false );
//----
   m_pressbutton= 0 ;
   m_fullname= "" ;
   m_chart.Redraw();
//----
   if (Id(m_subwin*CONTROLS_MAXIMUM_ID)>CONTROLS_MAXIMUM_ID)
     {
       Print ( "CSelectFile: too many objects" );
       return ( false );
     }
   return ( true );
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
int CSelectFile::ChartEvent( const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if (id==(ON_CLICK+ CHARTEVENT_CUSTOM ) && lparam==m_button_ok.Id()) OnClickButtonOK();
   else if (id==(ON_CLICK+ CHARTEVENT_CUSTOM ) && lparam==m_button_cancel.Id()) OnClickButtonCancel();
   else if (id==(ON_CHANGE+ CHARTEVENT_CUSTOM ) && lparam==m_filelist.Id()) OnChangeList();
   else if (id==(ON_CHANGE+ CHARTEVENT_CUSTOM ) && lparam==m_common.Id()) OnCheckCommon();
   else if (!CDialog::OnEvent(id,lparam,dparam,sparam)) return ( 0 );
   m_chart.Redraw();
   return (m_pressbutton);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSelectFile::OnClickButtonOK( void )
  {
   m_fullname=m_filename.Text();
   StringTrimLeft (m_fullname);
   StringTrimRight (m_fullname);
   if ( StringLen (m_fullname)> 0 )
     {
      m_fullname=m_cfolder+m_fullname;
      m_pressbutton= 1 ;
     }
  }
//+------------------------------------------------------------------+
void CSelectFile::OnClickButtonCancel( void )
  {
   m_pressbutton=- 1 ;
   m_fullname= "" ;
  }
//+------------------------------------------------------------------+
void CSelectFile::OnClickButtonClose( void )
  {
   OnClickButtonCancel();
  }
//+------------------------------------------------------------------+
void CSelectFile::OnChangeList( void )
  {
   int i=( int )m_filelist.Value();
   if (i< 0 ) return ;
   else if (i== 0 )
     {
       string s;
       if (m_cfolder==m_prevfolder || s==m_prevfolder)
        {
         m_cfolder= "" ;
         m_prevfolder=m_cfolder;
         SetFolder(m_cfolder);
        }
       else
        {
         s= "\\" +m_prevfolder;
         StringReplace (m_cfolder,s, "\\" );
         m_prevfolder=m_cfolder;
         if (m_cfolder== "\\" ) m_cfolder= "" ;
         SetFolder(m_cfolder+ "\\" );
        }
      m_filename.Text( "" );
     }
   else if (i<m_numberfolders)
     {
      m_prevfolder=m_folders[i];
      m_cfolder+=m_prevfolder;
      SetFolder(m_cfolder+ "\\" );
      m_filename.Text( "" );
     }
   else m_filename.Text(m_filelist.Select());
  }
//+------------------------------------------------------------------+
void CSelectFile::OnCheckCommon( void )
  {
   m_fileflag=m_common.Value()> 0 ? FILE_COMMON : 0 ;
   if (m_fileflag== 0 )
     {
      m_prevfolder= "" ;
      m_cfolder= "" ;
      SetFolder(m_cfolder);
      m_filename.Text( "" );
     }
   else
     {
      m_prevfolder= "" ;
      m_cfolder= "" ;
      SetFolder(m_cfolder);
      m_filename.Text( "" );
     }
  }
//+------------------------------------------------------------------+
void CSelectFile::SetFolder( string fol= "" )
  {
   string fl,ff,fld=fol;
   StringReplace (fld, "\\\\" , "\\" );
   int i;
   m_filelist.Select( 0 );
   m_filelist.ItemsClear();
   ArrayResize (m_folders, 1 );
   ArrayResize (m_files, 1 );
   if (fld== "Files\\" ) fl= "" ; else fl=fld;
   //---folders
   long   hfind= FileFindFirst (fl+ "*" ,ff,m_fileflag);
   if (hfind== INVALID_HANDLE )
     { //empty folder
      m_numberfiles= 0 ;
      m_numberfolders= 1 ;
      m_folders[ 0 ]= "Files\\" +fld;
      m_filelist.ItemAdd(m_folders[ 0 ]);
      m_totalfiles= 0 ;
     }
   else
     {
      m_numberfiles= 0 ;
      m_numberfolders= 0 ;
       do
        {
         if ( StringFind (ff, "\\" )> 1 ) m_numberfolders++;
         m_numberfiles++;
        }
       while ( FileFindNext (hfind,ff));
       FileFindClose (hfind);
       ArrayResize (m_folders,m_numberfolders+ 1 );
      hfind= FileFindFirst (fl+ "*" ,ff,m_fileflag);
       if (hfind== INVALID_HANDLE ) return ;
      m_numberfolders= 1 ;
       do
        {
         if ( StringFind (ff, "\\" )> 1 ){ m_folders[m_numberfolders]=ff; m_numberfolders++;}
        }
       while ( FileFindNext (hfind,ff));
       FileFindClose (hfind);
       if (fld== "" )
        {
         m_folders[ 0 ]= "Files\\" ;
         ff= "" ;
        }
       else
        {
         m_folders[ 0 ]=fld;
         ff= "Files\\" ;
        }
      m_filelist.ItemAdd(ff+m_folders[ 0 ]);
       int nn=m_numberfolders;
       for (i= 1 ; i<nn; i++) m_filelist.ItemAdd(m_folders[i]);
       //---files
      hfind= FileFindFirst (fl+ "*.*" ,ff,m_fileflag);
      m_numberfiles= 0 ;
       do
        {
         if ( StringFind (ff, "\\" )< 0 ) m_numberfiles++;
        }
       while ( FileFindNext (hfind,ff));
       FileFindClose (hfind);
       if (m_numberfiles> 0 )
        {
         ArrayResize (m_files,m_numberfiles);
         m_numberfiles= 0 ;
         hfind= FileFindFirst (fl+ "*.*" ,ff,m_fileflag);
         if (hfind!= INVALID_HANDLE )
           {
             do
              {
               if ( StringFind (ff, "\\" )< 0 ) { m_files[m_numberfiles]=ff; m_numberfiles++;}
              }
             while ( FileFindNext (hfind,ff));
             FileFindClose (hfind);
           }
         for (i= 0 ; i<m_numberfiles; i++) m_filelist.ItemAdd(m_files[i]);
        }
      m_totalfiles=m_numberfiles+m_numberfolders+ 1 ;
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                              Test_SelectFile.mq5 |
//+------------------------------------------------------------------+
#include <class_SelectFile.mqh>
//+------------------------------------------------------------------+
CSelectFile *SelectFile= NULL ;
//+------------------------------------------------------------------+
int OnInit ()
  {
   SelectFile= new CSelectFile();
   if (!SelectFile.Create( 0 , "Select a file" , 20 , 20 , 350 , 300 )) return (- 1 );
   return ( 0 );
  }
//+------------------------------------------------------------------+
void OnDeinit ( const int reason)
  {
   if (SelectFile!= NULL ) delete SelectFile;
  }
//+------------------------------------------------------------------+
void OnChartEvent ( const int id,         // event ID  
                   const long & lparam,   // event parameter of the long type
                   const double & dparam, // event parameter of the double type
                   const string & sparam) // event parameter of the string type
  {
   if (SelectFile!= NULL )
     {
       int key=SelectFile.ChartEvent(id,lparam,dparam,sparam);
       if (key> 0 )
        { //press button OK
         string file=SelectFile.Filename();
         int flag=SelectFile.FileFlag();
         delete SelectFile;
         SelectFile= NULL ;
         Print ( "The selected file - " ,flag== FILE_COMMON ? "[Common]" : "" ,file);
         int handle= FileOpen (file,flag);
         if (handle> 0 )
           {
             Print (file, " - open" );
             FileClose (handle);
           }
         else Print (file, " - failed to open" );
        }
       else if (key< 0 )
        { //press button Cancel
         delete SelectFile;
         SelectFile= NULL ;
         Print ( "No file selected" );
        }
     }
   if (SelectFile== NULL )
     {
       Print ( "The program is completed" );
       ExpertRemove ();
     }
  }
//+------------------------------------------------------------------+
type change the imem in the graph when clicking on the file. I wanted to know if it's possible, if it's not possible and it's complex I avoid breaking my neurons kkkkkk
Guilherme Mendonca
Guilherme Mendonca | 3 May 2022 at 20:05
Daniel Jose #:

De hecho, something happened involving the MT5 updates, that transform the Wallpaper in something different from the expected and shown in the article, since the correct that it be displayed in the GRAPHIC BACKGROUND, because in the C_WallPaper class you will find the following line:

This informs that the object will have to stay in the background, more strangely, it is coming to the front, because of this the OBJECT that receives or BitMap starts to receive the clicks, a solution would be to increase the Status of all objects, or try to lower the Bitmap status, in the case this second one would be simpler, this would be achieved by changing the value of the OBJPROP_ZORDER property in the object that receives the Wallpaper, I tried both solutions, but I couldn't stabilize the whole thing in a way to correct the problem, therefore, and INFELIZMENTE, the wallpaper must be discarded for now ... If you pay attention you will see that the bitmap image is being plotted on the candles body, indicating that the bitmap object is in the foreground, again this is not the expected behaviour because of the above line of code, because of this it happens to receive any and all click events ... 🙁


Daniel,

Is it possible if you put a timer and run a function to disable and enable the wallpaper again or something that "reaffirm" the function by putting the wallpaper in the background (back) ?
A GUI panel I use, I had to go for this solution. Some graphic elements need to be deleted and created again to be in the background or in the front depending on the object type.

John Winsome Munar
John Winsome Munar | 21 May 2022 at 20:04
Thanks for this useful article. Very informative.
Paulo Tassio Da Silva Lordelo
Paulo Tassio Da Silva Lordelo | 6 Jun 2023 at 09:49
Help me fix this "File not found." when i restart mt5
Paulo Tassio Da Silva Lordelo
Paulo Tassio Da Silva Lordelo | 11 Jun 2023 at 06:30
Paulo Tassio Da Silva Lordelo #:
Help me fix this "File not found." when i restart mt5

Got it, this error appears when using the same wallpaper in more than one graphic

What you can do with Moving Averages What you can do with Moving Averages
The article considers several methods of applying the Moving Average indicator. Each method involving a curve analysis is accompanied by indicators visualizing the idea. In most cases, the ideas shown here belong to their respected authors. My sole task was to bring them together to let you see the main approaches and, hopefully, make more reasonable trading decisions. MQL5 proficiency level — basic.
Graphics in DoEasy library (Part 97): Independent handling of form object movement Graphics in DoEasy library (Part 97): Independent handling of form object movement
In this article, I will consider the implementation of the independent dragging of any form objects using a mouse. Besides, I will complement the library by error messages and new deal properties previously implemented into the terminal and MQL5.
Learn how to design a trading system by ADX Learn how to design a trading system by ADX
In this article, we will continue our series about designing a trading system using the most popular indicators and we will talk about the average directional index (ADX) indicator. We will learn this indicator in detail to understand it well and we will learn how we to use it through a simple strategy. By learning something deeply we can get more insights and we can use it better.
DirectX Tutorial (Part I): Drawing the first triangle DirectX Tutorial (Part I): Drawing the first triangle
It is an introductory article on DirectX, which describes specifics of operation with the API. It should help to understand the order in which its components are initialized. The article contains an example of how to write an MQL5 script which renders a triangle using DirectX.