Cómo hacer el gráfico más interesante: Adicionando un fondo de pantalla
Introducción
Muchos terminales de trabajo contienen alguna imagen representativa y que muestra algo del usuario, estas imágenes hacen que el entorno de trabajo sea más bonito y alegre, la gente siempre intenta elegir las mejores y más bonitas imágenes para ponerlas como fondo de pantalla, pero cuando abrimos la plataforma de comercio, la cosa se vuelve aburrida y sosa, donde lo único que tenemos son representaciones gráficas de datos numéricos.
Podemos mirar una imagen durante mucho tiempo sin cansarnos, pero mirar un gráfico numérico durante unos minutos es muy agotador, así que hagamos las cosas más interesantes, para que podamos mirar y analizar el gráfico mientras la imagen de fondo nos motiva y nos recuerda algo bueno...
Planificación
Para empezar, debemos definir una cosa que influirá en el funcionamiento de todo el proyecto, la cosa en cuestión es: ¿Queremos cambiar el fondo del gráfico de vez en cuando, o vamos a utilizar una sola imagen para toda la existencia del programa, teniendo una sola imagen para todos los gráficos? Bueno, a mí me gusta poner diferentes imágenes en cada gráfico, algo que pueda representar el tipo de activo con el que estoy operando, o algo que indique lo que debo buscar en ese activo en un momento dado, por eso el archivo compilado no tendrá ninguna imagen interna, así que podemos seleccionar cualquier imagen después.
Una vez definido esto, tenemos que entender otra cosa: ¿Dónde deben estar nuestras imágenes? Pues bien, MetaTrader 5 tiene una estructura de directorios que debemos utilizar para acceder a las cosas, no podemos utilizar el árbol de directorios fuera de estos límites, y saber utilizar esta estructura es primordial para poder acceder a las imágenes posteriormente. Como la intención es organizar y mantener esta organización en el tiempo, vamos a crear un directorio dentro del directorio FILES, y lo llamaremos WALLPAPERS. La razón para hacerlo es que cuando accedamos a las imágenes no podremos salir del árbol cuya raíz es el directorio MQL5...
Pero, ¿por qué no poner los archivos en la carpeta IMAGES? Sería posible hacerlo, pero entonces tendríamos que navegar por el árbol, lo que sería una tarea innecesaria, complicando la lógica del programa, pero como mi intención es siempre mantener las cosas lo más simples posible, vamos a utilizar lo que nos ofrece MetaTrader 5. Así que nuestra estructura será la que se muestra a continuación:
Una vez hecho esto, añadiremos las imágenes como se muestra a continuación, es decir, separamos las imágenes del logotipo de las imágenes genéricas de fondo. Esto es importante para mantener las cosas lo más organizadas posible, ya que las imágenes que se van a utilizar como logotipos pueden tener un número muy grande, si nos fijamos en varios activos.
Observen que es muy sencillo, pongan todas las imágenes que deseen, y esto no estorbará para nada al programa. Ahora un detalle, noten que las imágenes están en tipo BITMAP, y tienen que estar en tipo 24 bits o 32 bits, y esto es porque esos formatos son más fáciles de leer, el propio MetaTrader 5 puede leer esos formatos por defecto, así que mantuve las cosas así, pero nada nos impide usar otro tipo, siempre y cuando programemos la rutina de lectura de forma que tengamos una imagen BITMAP al final, es decir, es más sencillo usar un editor de imágenes y convertir la cosa al estándar de mapa de bits de 24 o 32 bits, que crear una rutina sólo para hacer esto. Los archivos del directorio LOGOS siguen los mismos principios, con algunas excepciones que veremos pronto.
Teniendo esto en cuenta, comencemos la parte de la codificación. El código sigue los preceptos de la programación orientada a objetos (POO), por lo que puede transportarlo fácilmente a un script o indicador, si lo prefieren, además de poder aislarlo cuando sea necesario.
El paso a paso
El código comienza con algunas definiciones:´
//+------------------------------------------------------------------+ enum eType {IMAGEM, LOGO, COR}; //+------------------------------------------------------------------+ input char user01 = 30; //Transparencia ( 0 a 100 ) input string user02 = "WallPaper_01"; //Nome do arquivo input eType user03 = IMAGEM; //Tipo de fundo do grafico //+------------------------------------------------------------------+
Aquí indicamos lo que vamos a hacer, la enumeración eType indica cuál sería el fondo gráfico, puede ser una IMAGEN, un LOGO, o un COLOR, no es necesario ir más allá. La entrada USER02 indica el nombre del archivo que se utilizará como fondo, ya que el tipo IMAGEN está seleccionado en la entrada USER03. Ya USER01 indica el nivel de transparencia de nuestra imagen de fondo, ya que en algunos casos puede entorpecer la perfecta visualización de los datos en el gráfico, entonces cambiamos la transparencia para minimizar este efecto, el valor es sencillo de entender, va de 0% a 100% de transparencia, cuanto mayor sea el valor más transparente será la imagen de fondo.
Las funciones que realmente debes añadir a tu programa son las siguientes:
Función | Parámetros | Dónde declarar la función | Resultado |
---|---|---|---|
Init(string szName, char cView) | Nombre del archivo y nivel de transparencia deseado | Como primera función del código OnInit | Carga el archivo BITMAP especificado y lo muestra con la transparencia indicada |
Init(string szName) | Sólo se requiere el archivo | Como primera función del código OnInit | Carga el archivo BITMAP especificado sin ningún grado de transparencia |
Resize(void) | No se requieren parámetros | En el código OnChartEvent, en el evento CHARTEVENT_CHART_CHANGE | Redimensiona la imagen adecuadamente en el gráfico |
Así que vamos a ver cómo utilizar estas funciones en el código principal, comenzando por la inicialización de la clase, que tendrá lugar como se muestra a continuación, nótese que en este caso el usuario puede indicar un nivel de transparencia, y para que sea correcto debemos corregir el valor restándolo de 100.
int OnInit() { if(user03 != COR) WallPaper.Init(user03 == IMAGEM ? "WallPapers\\" + user02 : "WallPapers\\Logos\\" + _Symbol, (char)(100 - user01)); return INIT_SUCCEEDED; }
Fíjense que si usamos el modo COLOR no aparecerá ninguna imagen, pero fíjense bien en el operador triple. Cuando seleccionemos una imagen, apuntará al directorio WALLPAPER dentro del árbol FILES y cuando sea un LOGO apuntará al lugar correcto, pero vean con atención que el nombre del archivo de la imagen en caso de ser un logo debe ser el nombre del símbolo, de lo contrario se generará un error. En el caso de utilizar series continuas la cosa acaba ahí, pero puede ser que estemos utilizando un activo con fecha de caducidad, en este caso habrá que añadir una pequeña rutina para separar la parte del nombre que diferencia una serie actual de una caducada, pero el simple hecho de renombrar el archivo de la imagen para que el nombre sea el de la serie actual, ya solucionará el problema, para los que operan utilizando órdenes cruzadas, quizás sea interesante construir esta rutina para ajustar el nombre del símbolo.
Bueno, la siguiente rutina que aún merece ser destacada es:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_CHART_CHANGE) WallPaper.Resize(); }
Todos los códigos son siempre muy cortos, no me gusta complicar demasiado, ya que esto dificulta las mejoras y los cambios en el sistema, también trate de hacer de esto una regla cuando se crean las cosas, la rutina de arriba garantizará que cualquier cambio en las dimensiones de la carta, llame a la rutina responsable de cambiar la escala de la imagen, manteniendo las cosas siempre agradable a la vista y la imagen se renderiza completamente.
El código de nuestra clase tiene las siguientes características:
Función | Parámetros | Resultado |
---|---|---|
MsgError(const eErr err, int fp) | Tipo de error y descriptor del archivo | Cierra el archivo y muestra el mensaje de error correspondiente |
MsgError(const eErr err) | Tipo de error | Muestra el mensaje sobre el error correspondiente |
LoadBitmap(const string szFileName, uint &data[], int &width, int &height) | Nombre del archivo y punteros de datos | Carga el archivo deseado y devuelve sus datos en data[] más sus dimensiones en píxeles |
~C_WallPaper() | No se requieren parámetros | Garantiza el cierre de la clase objeto |
Init(const string szName, const char cView) | Nombre del archivo y nivel de transparencia | Inicializa toda la clase correctamente |
Init(const string szName) | Nombre del archivo | Inicializa toda la clase correctamente |
Destroy(void) | No se requieren parámetros | Termina la clase de manera apropiada |
Resize(void) | No se requieren parámetros | Realiza el redimensionamiento de la imagen correctamente |
Para no hacer todo el código un lío, concentré el manejo de errores en una sola rutina que se puede ver a continuación, lo único que hace es enviar un mensaje al usuario indicando que algo está mal, esto facilita las cosas, ya que en caso de traducción a otro idioma lo único que tenemos que hacer es cambiar los mensajes en una sola rutina, y no estar buscando donde se presenta cada uno de los mensajes.
bool MsgError(const eErr err, int fp = 0) { string sz0; switch(err) { case FILE_NOT_FOUND : sz0 = "Archivo no encontrado"; break; case FAILED_READ : sz0 = "Error de lectura"; break; case FAILED_ALLOC : sz0 = "Error de memoria"; break; case FAILED_CREATE : sz0 = "Error en la creación del recurso interno"; break; }; MessageBox(sz0, "ADVERTENCIA", MB_OK); if(fp > 0) FileClose(fp); return false; }
La función que se muestra a continuación leerá un archivo y lo cargará en memoria, la única información que necesitamos es el nombre del archivo, los demás datos serán llenados por la función, al final tendremos las dimensiones de la imagen y la imagen en sí, pero en el formato BITMAP, es importante notar esto, porque aunque hay varios formatos, al final el resultado siempre será un BITMAP, sólo la forma en que se compacta es lo que diferencia un formato de otro.
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; }
Vean la siguiente línea en el código:
if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE)
Observemos que en este punto se indica la extensión del archivo, es decir, no debemos indicar la extensión en el momento en que vamos a decir cuál será la imagen, porque si lo hacemos se generará un error de archivo no encontrado. Todo el resto de la función es bastante básico, primero leerá la cabecera del archivo, y comprobará si es un BITMAP de 32 o 24 bits, luego leerá la imagen de la forma correcta, ya que una imagen de 32 bits tiene una estructura interna un poco diferente a la de 24 bits.
La siguiente función inicializará todos los datos para que nuestro mapa de bits se muestre en pantalla. Una cosa que hay que notar es que, durante esta función, transformaremos el archivo de mapa de bits en un recurso del programa, esto es necesario, ya que más tarde vincularemos este recurso a un objeto, y es exactamente este objeto el que se mostrará en la pantalla, pero no como un objeto, sino como un recurso. Parece complicado entender por qué lo hacemos así, pero es algo que nos permite crear varios recursos del mismo tipo y luego vincularlos a un único objeto que se utilizará para mostrar algo. Si pusiéramos un único recurso bien definido en el programa, simplemente lo definiríamos como un recurso interno y compilaríamos el archivo, pero esto nos impediría cambiar el recurso sin tener que recompilar el código fuente, pero al crear un recurso dinámicamente, podemos indicar qué recurso queremos utilizar.
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; }
Todo esto es muy bonito y aparentemente práctico, pero el objeto en sí no es capaz de cambiar el recurso, es decir, no es posible cambiar la forma en que un recurso funcionará o se presentará simplemente vinculándolo a un objeto. Esto a veces complica un poco las cosas, ya que la mayoría de las veces tenemos que codificar cómo se debe cambiar el recurso dentro del objeto.
Hasta este punto se podía presentar la imagen utilizando el siguiente código:
if(ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) ChartRedraw();
Pero el uso de este código no garantizará que la imagen se presente como se espera, salvo el hecho de que tenga las dimensiones exactas del gráfico, pero les aconsejo que usen imágenes de alta definición, las imágenes grandes se representan mejor y hace que la parte de los cálculos sea mucho más fácil, ahorrando así tiempo de procesamiento que puede ser crucial en varios escenarios, pero aun así seguimos teniendo el problema de que la imagen no se presente correctamente ya que el objeto no cambiará el recurso de manera que se ajuste a las especificaciones del objeto, por lo que tenemos que hacer algo para que el recurso se modele de manera correcta y así se pueda presentar usando el objeto. Las matemáticas involucradas en el caso de una imagen es algo que va desde el cálculo más simple, hasta cosas muy complicadas, pero como estamos haciendo algo que el tiempo de procesamiento es crucial, que es usar un gráfico de precios, no podemos permitirnos hacer demasiados cálculos, debemos hacer la cosa lo más simple y rápida posible, y esto nos lleva a que tendríamos que usar imágenes con dimensiones más grandes que nuestro gráfico, porque entonces lo único que tendríamos que calcular es la reducción, veamos cómo funcionaría esto.
Las relaciones matemáticas que representarán el gráfico anterior se pueden conseguir de la siguiente manera:
Atención al siguiente hecho, hemos conseguido que f(x) = f(y) y esto mantiene la relación o proporción de la imagen, esto también se conoce como "aspect ratio", es decir, la imagen se está modificando de forma completa, esto ampliará y reducirá las dimensiones, pero, ¿y si f(y) fuera independiente de f(x) qué pasaría con nuestra imagen? Bueno, se modificaría de forma no proporcional, asumiendo así cualquier forma. Pero aunque no tenemos problemas con la reducción, no ocurre lo mismo con la ampliación, es decir, si el valor de f(x) > 1,0 o f(y) > 1,0, tendremos una ampliación de la imagen y en este punto empiezan a surgir algunos problemas. El primer problema puede verse a continuación:
La razón de que esto ocurra es que la imagen está sufriendo el efecto que se puede notar mejor en la figura de abajo, fíjense bien que los espacios BLANCOS representan los espacios vacíos que aparecen en la imagen cuando ésta sufre el efecto de ampliación, y este hecho se registrará siempre cuando f(x) o f(y) sea mayor que 1,0, es decir, cuando sigamos la flecha roja. En el caso de la figura siguiente f(x) = f(y) = 2,0, es decir, estamos ampliando la imagen en 2x.
Hay varias formas de sortear este problema, una de ellas es la interpolación, esta debe producirse cuando se encuentra un bloque vacío, en este momento factorizaríamos y calcularíamos un color intermedio entre los utilizados, esto produce un efecto de suavizado a la vez que tenemos el relleno de los puntos vacíos, pero tenemos un problema que es la parte de cálculo, aunque la interpolación se produzca rápidamente, puede no ser la forma más adecuada para ser utilizada en gráficos como los producidos en MetaTrader 5, que son gráficos en tiempo real, aunque el redimensionamiento se haga pocas veces durante todo el tiempo que el gráfico esté en pantalla, ya que en la mayoría de las ocasiones las dimensiones del gráfico serán inferiores a las de la imagen, y en este caso, la f(x) y la f(y) serían iguales o inferiores a 1. 0, y la interpolación no aportaría ningún beneficio, pensando en utilizar imágenes de 1920 x 1080 (imagen FULL HD) en una pantalla con la misma dimensión, añadir el cálculo de la interpolación sólo provocaría un aumento en el tiempo de procesamiento sin beneficiar el resultado final.
Veamos a continuación como se haría el cálculo de la interpolación en una imagen que duplicará su tamaño, aparentemente la cosa sería muy rápida, pero hay que recordar que hay que hacerlo en un patrón de color de 32 bits, o ARGB, donde tenemos 4 bytes de 8 bits a calcular, el procesador gráfico tiene funciones que nos permiten hacer estos cálculos rápidamente, pero acceder a estas funciones a través de OpenCL puede no darnos ningún beneficio práctico, ya que tendríamos un retardo para poner y tomar la información del procesador gráfico, y este tiempo puede no aportarnos ningún beneficio de la velocidad del cálculo realizado por el sistema gráfico.
Pensando en esto, asumo que una pequeña degradación de la imagen por el efecto del suavizado no sería para nada malo, ya que en la mayoría de las veces la f(x) o la f(y) no estarían por encima de 2, y esto ocurriría en un caso de usar una imagen FULL HD en una pantalla 4k, en este escenario el suavizado sería mínimo y difícil de notar. Así que en lugar de interpolar los puntos, prefiero arrastrar el punto al siguiente, rellenando rápidamente los valores vacíos, reduciendo así el coste computacional al máximo. La forma en que se hace esto se puede ver a continuación, ver que es bastante simple, y ya que estamos simplemente copiando los datos, podemos tratar los 32 bits en un solo paso, y esto sería tan rápido como lo que sería entregado por el sistema de procesamiento de gráficos.
De esta forma llegamos a la rutina necesaria para que el redimensionamiento de la imagen sea lo más rápido posible.
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(); }
La rutina tiene un bucle anidado, el bucle interior ejecutará la función f(x) y el bucle exterior la función f(y), cuando ejecutamos la función f(x) podemos producir bloques vacíos, esto se corrige en esta línea:
for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y];
Si por casualidad se produce una diferencia entre los valores de X, esta línea lo corregirá copiando el último valor de la imagen, como consecuencia tendremos la producción de un "aliasing", pero el coste computacional en estos casos sería mínimo, ya que este fragmento tendría un bucle interno ejecutándose durante un tiempo mínimo, esto cuando se ejecute, que no será siempre, si se quiere interpolar los datos evitando este efecto "aliasing", basta con modificar esta línea para crear los cálculos que se explicaron anteriormente.
Una vez calculada una línea completa, comprobamos f(y) para evitar que aparezcan bloques vacíos si f(y) es mayor que 1, y esto se consigue en esta línea:
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];
De nuevo esto producirá un "aliasing", pero se puede arreglar de la misma manera que se cambiaría el código de la línea anterior. El hecho de añadir el valor de la anchura de la nueva imagen se debe a que estamos copiando una línea ya tratada por el bucle encargado de tratar la f(x) de la nueva imagen, si la suma se hiciera con cualquier otro valor, la imagen se deformaría de forma extraña.
Conclusión
Espero que esta idea permita que sus gráficos sean más divertidos y agradables y que puedan ser observados durante horas y horas, ya que cuando la imagen de fondo resulte cansina y empalagosa, sólo hay que elegir otra, sin tener que recompilar nada, sólo eligiendo la nueva imagen que se utilizará como fondo del gráfico.
Un último detalle que merece ser mencionado es que en el caso de utilizar la clase de colocación de la imagen de fondo en un EA, debe ser lo primero que se declare en la rutina INIT, para evitar que la imagen de fondo se superponga con otros objetos gráficos creados por el EA.
Disfrute del resultado final con moderación, porque ahora estará aún más inmerso en los gráficos...
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/10215
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
De fato, aconteceu algo envolvendo as atualizações do MT5, que transformaram 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, Tentei as duas soluções, mas não consegui estabilizar a coisa toda de forma a corrigir o problema, portanto, e INFELIZMENTE, o papel de parede deve ser descartado por hora ... Se você prestar atenção, irá que a imagem do bitmap estará sendo plotada no corpo dos candles, indicando que o objeto bitmap esta em primeiro plano, novamente isto não é o comportamento esperado por conta da linha de código acima, por conta disto é que ele passa a receber todo e qualquer evento de clique ... 🙁
tipo mudar a imem en el gráfico al hacer clic en el archivo. queria saber si es posible, si no lo es y por complejo eu evito quebra mis neurônios kkkkkk
De hecho, algo pasó con las actualizaciones de MT5, que transforman el Wallpaper en algo diferente a lo esperado y mostrado en el artículo, ya que lo correcto es que se muestre en el GRAPHIC BACKGROUND, porque en la clase C_WallPaper se encuentra la siguiente línea:
Esto informa que el objeto deberá permanecer en segundo plano, más extrañamente, está pasando al frente, debido a esto el OBJETO que recibe o BitMap comienza a recibir los clics, una solución sería aumentar el Estado de todos los objetos, o tratar de bajar el estado del Bitmap, en el caso este segundo sería más sencillo, esto se lograría cambiando el valor de la propiedad OBJPROP_ZORDER en el objeto que recibe el Wallpaper, probé ambas soluciones, pero no pude estabilizar el conjunto de manera de corregir el problema, por lo tanto, e INFELIZMENTE, el wallpaper debe ser descartado por ahora ... Si prestas atención verás que la imagen del mapa de bits está siendo trazada en el cuerpo de las velas, indicando que el objeto mapa de bits está en primer plano, de nuevo este no es el comportamiento esperado debido a la línea de código anterior, debido a esto sucede que recibe todos y cada uno de los eventos de clic... 🙁
Daniel,
¿Es posible si se pone un temporizador y se ejecuta una función para desactivar y volver a activar el fondo de pantalla o algo que "reafirme" la función poniendo el fondo de pantalla en segundo plano (de vuelta)?
Un panel GUI que utilizo, tuve que optar por esta solución. Algunos elementos gráficos deben ser eliminados y creados de nuevo para estar en el fondo o en el frente, dependiendo del tipo de objeto.