Как сделать график более интересным: добавление фона
Введение
Многие рабочие терминалы содержат некоторое репрезентативное изображение, которое показывает что-то о пользователе. Эти изображения делают рабочую среду более красивой и веселой, поскольку люди всегда стараются выбрать лучшие и самые красивые изображения, чтобы устанавливать их в качестве фона. Но стоит только открыть торговую платформу, как она становится скучной и посредственной, и всё, что у нас есть - это графические представления числовых данных.
Мы можем долго смотреть на изображение и не уставать, но смотреть на числовой график в течение нескольких минут очень утомительно, поэтому давайте сделаем так, чтобы мы могли смотреть и анализировать график, а фоновое изображение мотивировало нас и напоминало о чем-то хорошем...
Планирование
Для начала надаопределить одну вещь, которая повлияет на функционирование всего проекта, а именно: хотим ли мы менять фон графика время от времени или будем использовать одно изображение на протяжении всего существования программы, при этом имея одно и то же изображение для всех графиков? Мне нравится размещать различные изображения на каждый график, например: что-то представляющее тип актива, которым я торгую, или указывающее на то, что я должен искать в этом активе в данный момент, поэтому скомпилированный файл не будет иметь никакого внутреннего изображения, так что мы можем выбрать любое изображение впоследствии.
Как только это определено, мы должны понять еще одну вещь: где должны находиться наши изображения? В 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
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
.
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:
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 ... Если вы обратите внимание, вы увидите, что растровое изображение будет накладываться на тело свечи, указывая на то, что растровый объект находится на переднем плане, опять же это не ожидаемое поведение из-за строки кода выше, из-за этого он получает все события щелчка ... 🙁
, 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
De hecho, что-то случилось с обновлениями MT5, что преобразует обои в нечто отличное от ожидаемого и показанного в статье, так как правильно, что они отображаются в GRAPHIC BACKGROUND, потому что в классе C_WallPaper вы найдете следующую строку:
Это сообщает, что объект должен оставаться на заднем плане, более странно, что он выходит на передний, из-за этого ОБЪЕКТ, который получает или BitMap начинает получать клики, решением было бы увеличить статус всех объектов, или попытаться понизить статус Bitmap, в данном случае второе было бы проще, Этого можно было бы достичь, изменив значение свойства OBJPROP_ZORDER в объекте, который получает обои, я попробовал оба решения, но я не смог стабилизировать все таким образом, чтобы решить проблему, поэтому, и INFELIZMENTE, обои должны быть отброшены на данный момент... Если вы обратите внимание, то увидите, что растровое изображение накладывается на тело свечи, указывая на то, что растровый объект находится на переднем плане, что опять же не является ожидаемым поведением из-за приведенной выше строки кода, из-за чего он получает все события щелчка мыши ... 🙁
Дэниел,
Можно ли поставить таймер и запустить функцию отключения и повторного включения обоев или что-то, что "подтверждает" функцию, переводя обои в фоновый режим (назад)?
Панель с графическим интерфейсом, которую я использую, мне пришлось пойти на это решение. Некоторые графические элементы необходимо удалить и создать заново, чтобы они были на заднем или переднем плане в зависимости от типа объекта.