English 中文 Español Deutsch 日本語 Português
preview
Как сделать график более интересным: добавление фона

Как сделать график более интересным: добавление фона

MetaTrader 5Примеры | 15 апреля 2022, 14:23
2 397 7
Daniel Jose
Daniel Jose

Введение

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

 


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


Планирование

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

Как только это определено, мы должны понять еще одну вещь: где должны находиться наши изображения? В MetaTrader 5 есть структура каталогов, которую мы должны использовать для доступа к вещам. Мы не можем использовать дерево каталогов вне этих рамок, и знание того, как использовать эту структуру, имеет первостепенное значение, если мы хотим получать доступ к изображениям позже. Поскольку мы планируем организовать и поддерживать эту организацию с течением времени, мы создадим каталог внутри каталога FILES и назовем его WALLPAPERS. Это необходимо для того, чтобы при обращении к изображениям мы не смогли покинуть дерево, корнем которого является каталог MQL5...

Но почему бы не поместить файлы в папку IMAGES? Это можно было бы сделать, но тогда нам пришлось бы перемещаться по дереву, что было бы ненужной задачей, усложняющей логику программы, но поскольку мы стремимся к максимальной простоте, мы будем использовать то, что предлагает нам MetaTrader 5. Таким образом, наша структура будет выглядеть так, как на изображении ниже:




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



Это очень просто, мы размещаем столько изображений, сколько захотим, и это нисколько не помешает работе программы. Теперь нужно обратить внимание на важную деталь. Изображения находятся в формате BITMAP, и они должны быть в 24 или 32 битном типе, потому что эти форматы легче читать, MetaTrader 5 сам может читать эти форматы по умолчанию, поэтому я оставил всё в таком виде. Но ничто не мешает нам использовать другой тип, пока мы программируем функцию чтения так, чтобы в конце у нас было изображение BITMAP, то есть проще использовать редактор изображений и конвертировать его в 24 или 32 битный стандарт, чем создавать функцию только для этого. Файлы в каталоге LOGOS следуют тем же принципам, но с некоторыми исключениями, которые мы скоро увидим.

Исходя из этого, давайте приступим к кодированию. Код следует принципам объектно-ориентированного программирования (ООП), поэтому вы можете легко перенести его на скрипт или индикатор, если хотите, и помимо этого изолировать его при необходимости.


Шаг за шагом

Код начинается с некоторых определений:

//+------------------------------------------------------------------+
enum eType {IMAGEM, LOGO, COR};
//+------------------------------------------------------------------+
input char     user01 = 30;               //Прозрачность ( 0 a 100 )
input string   user02 = "WallPaper_01";   //Имя файла
input eType    user03 = IMAGEM;           //Вид фона графика
//+------------------------------------------------------------------+


Здесь мы указываем, что мы собираемся делать, перечисление eType указывает, каким будет графический фон, это может быть ИЗОБРАЖЕНИЕ, ЛОГОТИП или ЦВЕТ, дальше можно не продолжать. Запись USER02 указывает имя файла, который будет использоваться в качестве фона, поскольку в записи USER03 выбран тип IMAGE. USER01 указывает уровень прозрачности нашего фонового изображения, так как в некоторых случаях оно может мешать оптимальной визуализации данных на графике, поэтому мы изменяем прозрачность, чтобы минимизировать этот эффект, значение простое для понимания - оно идет от 0% до 100% прозрачности, чем выше значение, тем прозрачнее будет фоновое изображение.




Функции, которые вам действительно необходимо добавить в свою программу, следующие:

Функция Параметры Где объявить функцию  Результат
Init(string szName, char cView) Имя файла и нужный уровень прозрачности В качестве первой функции кода OnInit Загружает указанный файл BITMAP и отображает его с заданной прозрачностью
Init(string szName)  Требуется только файл В качестве первой функции кода OnInit Загружает указанный файл BITMAP без какой-либо степени прозрачности
Resize(void) Параметры не требуются В коде OnChartEvent, в событии CHARTEVENT_CHART_CHANGE Изменение размера изображения на графике соответствующим образом

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

int OnInit()
  {
   if(user03 != COR)
      WallPaper.Init(user03 == ИЗОБРАЖЕНИЕ ? "WallPapers\\" + user02 : "WallPapers\\Logos\\" + _Symbol, (char)(100 - user01));
   return INIT_SUCCEEDED;
  }


Обратите внимание, что если мы используем режим ЦВЕТ, то изображение не появится, но посмотрите повнимательнее на тройной оператор. Когда мы выбираем изображение, оно будет указывать на каталог WALLPAPER в дереве FILES, а когда это LOGO, оно будет указывать на правильное место, но обратите внимание, что имя файла изображения, если это логотип, должно быть именем символа, иначе будет сгенерирована ошибка. В случае использования непрерывных серий на этом всё и закончится. Но может случиться так, что мы используем актив с истекающим сроком действия, в этом случае нам придется добавить небольшую функцию для отделения части имени, которая отличает текущую серию от просроченной. Простой факт переименования файла изображения так, чтобы имя было именем текущей серии, уже решит проблему, а для тех, кто работает с использованием кросс-ордеров, может быть интересно построить эту функцию для изменения имени символа.

Следующая функция, которая все еще заслуживает внимания, это:

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


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

Код в нашем классе имеет следующие особенности:

Функция Параметры   Результат 
MsgError(const eErr err, int fp) Тип ошибки и дескриптор файла Закрывает файл и выводит соответствующее сообщение об ошибке.
MsgError(const eErr err) Вид ошибки Отображает сообщение о соответствующей ошибке
LoadBitmap(const string szFileName, uint &data[], int &width, int &height) Имя файла и указатели данных Загружает нужный файл и возвращает его данные в формате data[] и также его размеры в пикселях.
~C_WallPaper() Параметры не требуются Обеспечивает закрытие класса объектов
Init(const string szName, const char cView) Имя файла и уровень прозрачности Инициализирует весь класс правильно
Init(const string szName) Имя файла Инициализирует весь класс правильно
Destroy(void) Параметры не требуются Завершает класс соответствующим образом
Resize(void)  Параметры не требуются Правильно изменяет размер изображения

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

   bool              MsgError(const eErr err, int fp = 0)
     {
      string sz0;
      switch(err)
        {
         case FILE_NOT_FOUND  :
            sz0 = "Файл не найден";
            break;
         case FAILED_READ     :
            sz0 = "Ошибка чтения";
            break;
         case FAILED_ALLOC    :
            sz0 = "Ошибка памяти";
            break;
         case FAILED_CREATE   :
            sz0 = "Ошибка при создании внутренного ресурса";
            break;
        };
      MessageBox(sz0, "ВНИМАНИЕ", MB_OK);
      if(fp > 0)
         FileClose(fp);
      return false;
     }


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

   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;
     }


Посмотрите на следующую строку в коде:

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


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

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

   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;
     }


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

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

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


Но использование этого кода не гарантирует, что изображение будет представлено так, как ожидается, за исключением того, что оно имеет точные размеры графики, но я советую вам использовать изображения высокого разрешения. Большие изображения отображаются лучше, и это значительно облегчает часть расчета, тем самым экономя время обработки, которое может быть решающим в некоторых сценариях. У нас всё еще остается проблема, что изображение отображается неправильно, поскольку объект не изменит ресурс таким образом, чтобы он соответствовал спецификациям объекта, поэтому мы должны сделать что-то, чтобы ресурс был смоделирован правильно и чтобы его можно было отобразить с помощью объекта. Математика, задействованная в случае с изображением, варьируется от простейших расчетов до очень сложных вещей, но поскольку мы делаем что-то такое, где время обработки имеет решающее значение, а именно использование ценового графика, мы не можем позволить себе делать слишком много расчетов. Мы должны всё реализовать как можно проще и быстрее. Нам придется использовать изображения с размерами больше, чем наш график, потому что тогда единственное, что нам придется рассчитывать - это уменьшение. Давайте посмотрим, как это будет работать.

Математические отношения, которые будут представлять вышеприведенный график, можно получить следующим образом:


Обратите внимание на следующий факт, мы добились того, что f(x) = f(y) и это сохраняет соотношение и пропорции изображения. Это также известно как "соотношение сторон", то есть изображение изменяется полностью, что увеличивает и уменьшает размеры, но что если f(y) не зависела от f(x), что в таком случае произошло бы с нашим изображением? Ну, он будет изменен непропорционально, таким образом, принимая любую форму. Хотя с уменьшением у нас нет проблем, но то же самое не верно для увеличения, т.е. если значение f(x) > 1.0 или f(y) > 1.0, мы получим увеличение изображения, и в этот момент начинаются некоторые проблемы. Первую проблему можно увидеть ниже:


Это происходит потому, что изображение подвергается эффекту, который лучше всего виден на рисунке ниже, обратите внимание, что БЕЛЫЕ пробелы представляют собой пустые места, которые появляются на изображении, когда оно подвергается эффекту увеличения, и этот факт всегда будет регистрироваться, когда f(x) или f(y) больше 1.0, т.е. когда мы следуем за красной стрелкой. В случае рисунка ниже f(x) = f(y) = 2,0, т.е. мы увеличиваем изображение в 2 раза.


Есть несколько способов обойти эту проблему, один из них - интерполяция, которая должна произойти, когда найден пустой блок. В этот момент мы должны использовать коэффициент и вычислить промежуточный цвет между используемыми, что создаст эффект сглаживания в то же время, что и заполнение пустых точек, но у нас есть проблема, которая заключается в вычислительной части. Хотя интерполяция производится быстро, это может быть не самым подходящим способом для использования на графиках, таких как те, что создаются в MetaTrader 5, которые являются графиками реального времени. Даже если изменение размера выполняется несколько раз за всё время нахождения графика на экране, потому что в большинстве случаев размеры графика будут меньше, чем изображение, и в этом случае f(x) и f(y) будут равны или меньше 1. 0, и интерполяция не принесет никакой пользы, думая об использовании изображений 1920 x 1080 (FULL HD изображение) на экране с тем же размером, добавление вычисления интерполяции приведет только к увеличению времени обработки без пользы для конечного результата.

Давайте посмотрим ниже, как будет выполняться расчет интерполяции на изображении, которое удвоит свой размер, очевидно, что это будет очень быстро, но мы должны помнить, что мы должны делать это в 32-битной цветовой схеме, или ARGB, где у нас есть 4 байта по 8 бит для расчета. Графический процессор имеет функции, которые позволяют нам делать эти расчеты быстро, но доступ к этим функциям через OpenCL может не дать нам никакой практической пользы, так как у нас будет задержка на ввод и вывод информации из графического процессора, и это время может не принести нам никакой пользы от скорости вычислений, выполняемых графической системой.


                             


Думая об этом, я предполагаю, что небольшое ухудшение изображения из-за эффекта сглаживания совсем не страшно, поскольку в большинстве случаев f(x) или f(y) не будет выше 2, и это произойдет в случае использования изображения FULL HD на экране 4k, в этом сценарии сглаживание будет минимальным и его трудно заметить. Поэтому вместо интерполяции точек я предпочитаю перетаскивать точку на следующую, быстро заполняя пустые значения, тем самым снижая вычислительные затраты до минимума. То, как это делается, можно увидеть ниже, это довольно просто. Поскольку мы всего лишь копируем данные, мы можем обработать все 32 бита за один шаг, и это будет так же быстро, как и то, что было бы доставлено системой обработки графики.


                   


Таким образом, мы приходим к необходимой функции, чтобы изменение размера изображения происходило как можно быстрее.

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();
}   


В функции есть вложенный цикл, внутренний цикл будет выполнять функцию f(x), а внешний цикл - функцию f(y), когда мы выполняем функцию f(x), мы можем получить пустые блоки, что исправлено в этой строке:

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


Если случайно возникнет разница между значениями X, то эта строка исправит её, скопировав последнее значение изображения. Как следствие у нас произойдёт алиасинг, но вычислительные затраты в этих случаях будут минимальными, так как этот фрагмент будет иметь внутренний цикл, работающий в течение минимального времени, когда он выполняется (что будет не всегда). Если вы хотите интерполировать данные, избегая этого эффекта алиасинга, просто измените эту строку, чтобы создать вычисления, которые были объяснены выше.

После расчета полной линии мы проверяем f(y), чтобы избежать появления пустых блоков, если f(y) больше 1, и это достигается на данной строке:

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];

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


Заключение:

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

Последняя деталь, о которой стоит упомянуть: в случае использования класса размещения фонового изображения в советнике, он должен быть первым объявлен на функции INIT, чтобы избежать наложения фонового изображения на другие графические объекты, созданные советником.

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



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

Прикрепленные файлы |
EA_With_Wallpaper.zip (7778.85 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (7)
[Удален] | 27 апр. 2022 в 20:45
утилита работает идеально, но некоторые индикаторы, использующие OnChartEvent, перестают работать и становятся некликабельными, как только я применяю этот советник для фона
Daniel Jose
Daniel Jose | 2 мая 2022 в 14:32
Arpit Tailang # : Утилита работает отлично, за исключением некоторых синализаторов, которые используют OnChartEvent для работы и становятся неустанавливаемыми, когда я применяю этот советник в фоновом режиме
.

De hecho, aconteceu algo envolvendo as atualizações do MT5, que transformam o Wallpaper em algo diferente do esperado e mostrado no artigo, já que o correto que ele seja exibido no FUNDO GRÁFICO, pois na classe C_WallPaper você encontrará a seguinte linha:

ObjectSetInteger ( Terminal.Get_ID(), szName, OBJPROP_BACK , true ); 

Isso informa que o objeto terá que ficar no fundo, mais estranhamente, ele está vindo para a frente, por conta disto o OBJETO que recebe ou BitMap começa a receber os cliques, uma solução seria aumentar o Status de todos os objetos, ou tentar baixar o status do Bitmap, no caso esta segunda seria mais simples, isto seria conseguido alterando valor da propriedade OBJPROP_ZORDER no objeto que recebe o Wallpaper, Попробовал два варианта решения, но не смог стабилизировать ситуацию, чтобы исправить проблему, portanto, e INFELIZMENTE, o papel de parede deve ser descartado por hora ... Если вы обратите внимание, вы увидите, что растровое изображение будет накладываться на тело свечи, указывая на то, что растровый объект находится на переднем плане, опять же это не ожидаемое поведение из-за строки кода выше, из-за этого он получает все события щелчка ... 🙁

felipe ramos
felipe ramos | 2 мая 2022 в 20:40
A possibilidade de utilizar  na função que apresento  nesse código
//+------------------------------------------------------------------+
//|                                                   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();
     }
  }
//+------------------------------------------------------------------+
, tipo mudar a imagem no gráfico  ao clicar no arquivo. queria saber se e possível, se não der e for complexo eu evito quebra meus neurônios kkkkkk
Guilherme Mendonca
Guilherme Mendonca | 3 мая 2022 в 20:05
Daniel Jose #:

De hecho, что-то случилось с обновлениями MT5, что преобразует обои в нечто отличное от ожидаемого и показанного в статье, так как правильно, что они отображаются в GRAPHIC BACKGROUND, потому что в классе C_WallPaper вы найдете следующую строку:

Это сообщает, что объект должен оставаться на заднем плане, более странно, что он выходит на передний, из-за этого ОБЪЕКТ, который получает или BitMap начинает получать клики, решением было бы увеличить статус всех объектов, или попытаться понизить статус Bitmap, в данном случае второе было бы проще, Этого можно было бы достичь, изменив значение свойства OBJPROP_ZORDER в объекте, который получает обои, я попробовал оба решения, но я не смог стабилизировать все таким образом, чтобы решить проблему, поэтому, и INFELIZMENTE, обои должны быть отброшены на данный момент... Если вы обратите внимание, то увидите, что растровое изображение накладывается на тело свечи, указывая на то, что растровый объект находится на переднем плане, что опять же не является ожидаемым поведением из-за приведенной выше строки кода, из-за чего он получает все события щелчка мыши ... 🙁


Дэниел,

Можно ли поставить таймер и запустить функцию отключения и повторного включения обоев или что-то, что "подтверждает" функцию, переводя обои в фоновый режим (назад)?
Панель с графическим интерфейсом, которую я использую, мне пришлось пойти на это решение. Некоторые графические элементы необходимо удалить и создать заново, чтобы они были на заднем или переднем плане в зависимости от типа объекта.

gordenis
gordenis | 2 авг. 2023 в 15:36
Здравствуйте! Не программист,не могу разобраться как установить это в МТ5. Добавил обои в Logos , а дальше то что делать?.....
DoEasy. Элементы управления (Часть 2): Продолжаем работу над классом CPanel DoEasy. Элементы управления (Часть 2): Продолжаем работу над классом CPanel
В статье избавимся от некоторых ошибок при работе с графическими элементами и продолжим разработку элемента управления CPanel. Это будут методы для установки параметров шрифта, который используется по умолчанию для всех текстовых объектов панели, которые в свою очередь могут быть на ней расположены в дальнейшем.
Несколько индикаторов на графике (Часть 02): Первые эксперименты Несколько индикаторов на графике (Часть 02): Первые эксперименты
В предыдущей статье "Несколько индикаторов на графике" я представил концепции и основы того, как мы можем использовать несколько индикаторов на графике. В данной статье я представлю и детально объясню исходный код.
Несколько индикаторов на графике (Часть 03): Разработка пользовательских определений Несколько индикаторов на графике (Часть 03): Разработка пользовательских определений
Сегодня мы впервые обновляем функциональность системы индикаторов. В предыдущей статье "Несколько индикаторов на одном графике" мы рассмотрели основы кода, позволяющего использовать более одного индикатора в подокне, но то, что было представлено, было лишь начальной основой для гораздо более крупной системы.
DoEasy. Элементы управления (Часть 1): Первые шаги DoEasy. Элементы управления (Часть 1): Первые шаги
С этой статьи начинаем обширную тему по созданию на MQL5 элементов управления в стиле Windows Forms. И начнём тему с создания класса панели. Без наличия элементов управления уже становится сложно обходиться. Поэтому мы создадим все возможные элементы управления в стиле Windows Forms.