English Русский 中文 Español Deutsch 日本語
preview
Deixando o gráfico mais interessante — Adicionando uma tela de fundo

Deixando o gráfico mais interessante — Adicionando uma tela de fundo

MetaTrader 5Exemplos | 28 janeiro 2022, 11:11
2 360 9
Daniel Jose
Daniel Jose

Introdução

Muitas estações de trabalho contém alguma imagem representativa e que mostra algo sobre o usuário, estas imagens deixam o ambiente de trabalho mais bonito e animador, as pessoas sempre procuram escolher as melhores e mais bonitas imagens para colocar como papel de parede, mas quando abrimos a plataforma de negociação, a coisa se torna chata e sem graça, onde tudo que temos são representações gráficas de dados numéricos.

 


Você pode ficar olhando uma foto por longos períodos sem se cansar, mas ficar alguns minutos olhando um gráfico numérico é muito cansativo, então vamos deixar as coisas mais interessantes, de forma que possamos olhar e analisar o gráfico enquanto a imagem no fundo nos motiva e nos remete a algo de bom ....


Planejamento

Para começar, devemos definir uma coisa que irá influenciar como todo o projeto irá trabalhar, a coisa em questão é: Queremos mudar o fundo do gráfico de tempos em tempos, ou vamos usar apenas uma única imagem por toda a existência do programa, tendo apenas uma única imagem para todos os gráficos ? Bem, eu gosto de colocar imagens diferentes em cada gráfico, algo que possa representar o tipo de ativo que estou negociando, ou algo que indique o que devo procurar naquele ativo em um dado momento, por conta disto o arquivo compilado não terá nenhuma imagem interna, assim podemos selecionar qualquer imagem a posterior.

Definido isto, temos que entender uma outra coisa: Onde deverá ficar nossas imagens ? Bem, o MetaTrader 5 conta com uma estrutura de diretórios que deveremos usar, para poder acessar as coisas, não podemos usar a arvore de diretórios fora destes limites, e saber como usar esta estrutura é primordial para você conseguir acessar as imagens depois. Já que a intenção é organizar e manter está organização com o tempo, vamos então criar um diretório dentro do diretório FILES, e vamos chama-lo de WALLPAPERS. O motivo de fazer assim é que quando formos acessar as imagens não seremos capazes de sair da arvore cuja raiz é o diretório MQL5 ...

Mas por que não colocar os arquivos na pasta IMAGES ?!?! Até daria para fazer isto, mas ai iriamos ter que navegar na arvore o que seria uma tarefa desnecessária, complicando a lógica do programa, mas como a minha intenção é sempre procurar deixar as coisas o mais simples possível, vamos usar o que o MetaTrader 5 nos oferece. Então nossa estrutura ficará como mostrado abaixo:




Feito isto vamos adicionar as imagens conforme mostrado abaixo, ou seja, separamos imagens de logotipo das imagens de fundo genérico. Isto é importante para manter as coisas o mais organizado possível, dado que as imagens a serem usadas como logotipo podem ter um numero bem grande, se observamos vários ativos.



Notem que é bem simples, coloque quantas imagens desejar, e isto não iria atrapalhar em nada o programa. Agora um detalhe, observem que as imagens são do tipo BITMAP, e elas tem que ser do tipo 24 bits ou 32 bits, e isto se deve ao fato de estes formatos serem mais simples de ler, o próprio MetaTrader 5 consegue ler estes formatos por padrão, então mantive as coisas assim, mas nada impede de você usar um outro tipo, desde que você programe a rotina de leitura de forma a no final ter uma imagem BITMAP, ou seja é mais simples usar um editor de imagem e converter a coisa para o padrão bitmap de 24 ou 32 bits, do que criar uma rotina só para fazer isto. Os arquivos no diretório LOGOS seguem os mesmos princípios, com algumas ressalvas que veremos em breve.

Tendo isto em mente, vamos a começar a parte da codificação. O código segue os preceitos de uma programação orientada a objetos ( OOP ), desta forma você pode facilmente transporta-lo para um script ou indicador, se assim desejar, além de poder isolar o mesmo quando necessário.


O passo a passo

O código começa com algumas definições:

//+------------------------------------------------------------------+
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
//+------------------------------------------------------------------+

Aqui indicamos o que iremos fazer, a enumeração eType indica qual seria o fundo do gráfico, podendo ser uma IMAGEM, um LOGO, ou uma COR, não há necessidade de ir além disto. Já a entrada USER02 indica o nome do arquivo que será usado como fundo, desde é claro o tipo IMAGEM esteja selecionado na entrada USER03. Já USER01 indica o nível de transparência da nossa imagem de fundo, já que em alguns casos ela pode atrapalhar a perfeita visualização do dados no gráfico, então mudamos a transparência para minimizar este efeito, o valor é simples e ser entendido, vai de 0% a 100% de transparência, quanto maior o valor mais transparente ficará a imagem de fundo.




As funções das quais você de fato deve adicionar ao seu programa são as seguintes:

Função Parâmetros Onde declarar a função  Resultado
Init(string szName, char cView) Nome do arquivo e nível de transparência desejado Como primeira função no código OnInit Carrega o arquivo BITMAP especificado e apresenta ele com a transparência indicada
Init(string szName)  Apenas o arquivo é requerido Como primeira função no código OnInit Carrega o arquivo BITMAP especificado e sem nenhum grau de transparência
Resize(void) Nenhum parâmetro necessário No código OnChartEvent, no evento CHARTEVENT_CHART_CHANGE Redimensiona a imagem de forma adequada no gráfico

Vamos então ver como usar estas funções no código principal, começando na inicialização da classe, que se dará conforme mostrado abaixo, notem que neste caso o usuário poderá indicar um nível de transparência, e para que ele fique correto devemos corrigir o valor subtraindo ele de 100.

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

Notem que se estivermos usando o modo COR nenhuma imagem irá aparecer, mas observem com atenção o operador triplo. Quando selecionamos uma imagem, ele irá apontar para o diretório WALLPAPER dentro da arvore FILES e quando for um LOGO ele irá apontar para o local correto, mas observem que o nome do arquivo de imagem no caso de ser logotipo deverá ser o nome do símbolo, caso contrário irá ser gerado um erro. Em caso de usar series continuas a coisa termina ai, mas pode ser que você esteja usando um ativo com data de vencimento, neste caso será necessário adicionar uma pequena rotina para separar a parte do nome que diferencia uma serie corrente de uma já vencida, mas o simples fato de renomear o arquivo de imagem para que o nome seja o da serie atual, já resolverá o problema, para quem opera usando ordens cruzadas, talvez seja interessante montar esta rotina de ajuste do nome do símbolo.

Bem a próxima rotina que ainda merece destaque é:

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

Todos os códigos são sempre bem curtos, não gosto de complicar muito, já que isto dificulta melhorias e mudanças no sistema, tentem também fazer disto uma regra quando forem criar as coisas, a rotina acima irá garantir que qualquer mudança nas dimensões do gráfico, chame a rotina responsável por mudar a escala da imagem, mantendo as coisas sempre agradáveis de serem vistas e a imagem seja renderizada por completo.

O código da nossa classe conta com os seguintes recursos:

Função Parâmetros   Resultado 
MsgError(const eErr err, int fp) Tipo de erro e o descritor do arquivo Fecha o arquivo e mostra a mensagem de erro correspondente
MsgError(const eErr err) Tipo de erro Mostra a mensagem sobre qual foi o erro correspondente
LoadBitmap(const string szFileName, uint &data[], int &width, int &height) Nome do arquivo e ponteiros dos dados Carrega o arquivo desejado e retorna os dados do mesmo em data[] mais suas dimensões em pixels
~C_WallPaper() Nenhum parâmetro necessário Garante o encerramento da classe objeto
Init(const string szName, const char cView) Nome do arquivo e nível de transparência Inicializa toda a classe de forma adequada
Init(const string szName) Nome do arquivo Inicializa toda a classe de forma adequada
Destroy(void) Nenhum parâmetro necessário Finaliza a classe de forma adequada
Resize(void)  Nenhum parâmetro necessário Executa o redimensionamento da imagem de forma adequada

Para não tornar todo o código uma bagunça, eu concentrei o tratamento de erros em uma única rotina que pode ser vista abaixo, tudo que ela faz é enviar uma mensagem para o usuário indicando que algo esta errado, isto facilita as coisas, já que em caso de tradução para uma outra língua tudo que temos que fazer é mudar as mensagem em uma única rotina, e não ficar procurando onde esta cada uma das mensagens apresentadas.

   bool              MsgError(const eErr err, int fp = 0)
     {
      string sz0;
      switch(err)
        {
         case FILE_NOT_FOUND  :
            sz0 = "Arquivo não encontrado.";
            break;
         case FAILED_READ     :
            sz0 = "Erro na leitura.";
            break;
         case FAILED_ALLOC    :
            sz0 = "Falha de memória.";
            break;
         case FAILED_CREATE   :
            sz0 = "Falha na criação de recurso interno.";
            break;
        };
      MessageBox(sz0, "AVISO", MB_OK);
      if(fp > 0)
         FileClose(fp);
      return false;
     }

A função abaixo irá ler um arquivo e carregar ele para a memória, a única informação que precisamos é o nome do arquivo, já os demais dados serão preenchidos pela função, no final teremos as dimensões da imagem e a própria imagem em si, mas no formato de BITMAP, é importante notar isto, pois apesar de existirem vários formatos, no final o resultado será sempre um BITMAP, apenas a forma como ele esta compactado é que difere um formato de outro.

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

Observem a seguinte linha no código:

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

vejam que a extensão do arquivo é indicado neste ponto, ou seja não devemos indicar a extensão no momento em que formos dizer qual será a imagem, pois se fizer isto, será gerado um erro de arquivo não encontrado. Todo o restante da função é bem básica, primeiro ela irá ler o cabeçario do arquivo, e verificar se é um BITMAP de 32 ou 24 bits, depois irá ler a imagem da forma correta, já que uma imagem de 32 bits tem uma estrutura interna um pouco diferente da de 24 bits.

A próxima função irá inicializar todo os dados para nosso bitmap ser apresentado na tela. Uma coisa que se deve observar, é que durante esta função iremos transformar o arquivo bitmap em um recurso do programa, isto se faz necessário, já que depois iremos ligar este recurso a um objeto, e é justamente este objeto que irá ser apresentado na tela, mas não como um objeto e sim como um recurso. Parece complicado de entender o porque fazemos assim, mas isto é algo que nos permite criar vários recursos do mesmo tipo e depois liga-los a um único objeto que será usado para apresentar algo. Se no caso fossemos colocar um único e bem definido recurso ao programa, simplesmente o definiríamos ele como um recurso interno e compilaríamos o arquivo, mas isto nos impediria de trocar o recurso sem ter que recompilar o código fonte, mas criando um recurso de forma dinâmica, podemos indicar qual recurso desejamos usar.

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

Tudo isto é muito bonito e aparentemente pratico, mas o objeto em si não é capaz de mudar o recurso, ou seja, não é possível mudar a forma como um recurso irá funcionar ou ser apresentado simplesmente ligando ele a um objeto. Isto as vezes complica um pouco as coisas, já que em na grande maioria das vezes temos que codificar a forma como o recurso deverá ser modificado dentro do objeto.

Até neste ponto você poderia apresentar a imagem usando o seguinte código:

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

mas usar este código não irá garantir que a imagem irá ser apresentada conforme o esperado, salvo o fato de ela ter as exatas dimensões do gráfico, mas eu aconselho você a usar imagens em alta definição, imagens grandes são mais bem representadas e facilita e muito a parte dos cálculos, poupando assim tempo de processamento que pode ser crucial em vários cenários, mas mesmo assim ainda temos o problema da imagem não ser corretamente apresentada já que o objeto não irá mudar o recurso de forma que ele se ajuste as especificações do objeto, então temos que fazer algo para que o recurso seja modelado da forma correta e assim possa ser apresentado usando o objeto. A matemática envolvida no caso de uma imagem é algo que vai do mais simples cálculo, até coisas bem complicadas, mas já que estamos fazendo algo que o tempo de processamento é crucial, que é usar um gráfico de preços, não podemos nos dá ao luxo de efetuar cálculos em exagero, devemos fazer a coisa o mais simples e rápido quanto for possível, e isto nos leva ao fato de que teríamos que usar imagens com dimensões maiores que o nosso gráfico, pois ai tudo que teríamos que calcular seria a redução, vamos ver como isto se daria.

As relações matemáticas que irão representar o gráfico acima podem ser conseguidas da seguinte maneira:


Atenção ao seguinte fato, fizemos f(x) = f(y) e isto mantem a razão, ou proporção da imagem isto também é conhecido como aspect ratio, ou seja a imagem está sendo modificada de forma completa, isto tanto irá ampliar quanto reduzir as dimensões, mas e se f(y) fosse independente de f(x) o que aconteceria com a nossa imagem ? Bem, ela seria modificada de forma não proporcional, assumindo assim qualquer forma. Porém apesar de não termos problemas quanto a questão da redução o mesmo não se aplica na ampliação, ou seja se o valor de f(x) > 1.0 ou f(y) > 1.0 teremos uma ampliação da imagem e neste ponto começa a surgir alguns problemas. O primeiro problema pode ser visto abaixo:


o motivo disto acontecer é que a imagem esta sofrendo o efeito que pode ser mais bem notado na figura abaixo, notem que os espaços em BRANCO representam espaços vazios que aparecem na imagem quando ela sofre o efeito de ampliação, e este feito sempre será registrado quando f(x) ou f(y) for maior que 1.0, ou seja quando seguimos a seta vermelha. No caso da figura abaixo f(x) = f(y) = 2.0 ou seja estamos ampliando a imagem em 2x.


Existem diversas formas de se contornar este problema, uma delas é a interpolação, ela deverá ocorrer quando um bloco vazio fosse encontrado, neste momento iriamos fazer uma fatoração e calcular uma cor intermediaria entre as usadas, isto produz um efeito de suavização ao mesmo tempo que temos o preenchimento dos pontos vazios, só que temos um problema que é a parte da computação, mesmo que a interpolação ocorra rapidamente, ela talvez não seja o caminho mais adequado para ser usada em gráficos como os produzidos no MetaTrader 5, que são gráficos de tempo real, mesmo que o redimensionamento seja feito poucas vezes durante todo o tempo em que o gráfico fique na tela, já que em grande maioria das vezes as dimensões do gráfico será inferior as dimensões da imagem, e neste caso o f(x) e f(y) seriam igual ou menor que 1.0 e a interpolação não traria nenhum benefício, isto pensando em usar imagens de 1920 x 1080 ( imagem FULL HD ) em uma tela com a mesma dimensão, adicionar o cálculo de interpolação apenas iria causar um aumento no tempo de processamento sem beneficiar o resultado final.

Veja abaixo como seria feito o calculo de interpolação em uma imagem que irá dobrar de tamanho, aparentemente a coisa seria bem rápida, mas você tem que se lembrar que você deverá fazer isto em um padrão de cor em 32 bits, ou ARGB, onde temos 4 bytes de 8 bits para ser calculado, o processador de gráfico tem funções que permitem fazer estes cálculos rapidamente, mas acessar estas funções via OpenCL pode não nos dar nenhum beneficio pratico, já que teríamos um retardo tanto para colocar, quanto para retirar as informações de dentro do processador gráfico, e este tempo pode não nos trazer beneficio da velocidade do cálculo executado pelo sistema gráfico.


                             


Pensando nisto, assumo que um pouco de degradação da imagem por conta do efeito serrilhado não seria de todo ruim, já na grande maioria das vezes o f(x) ou o f(y) não estaria acima de 2, e isto aconteceria em um caso de se usar uma imagem FULL HD em uma tela de 4k, neste cenário o serrilhado seria mínimo e difícil de ser notado. Então no lugar de interpolar os pontos, eu prefiro arrastar o ponto para o próximo, preenchendo rapidamente os valores vazios, reduzindo assim ao máximo o custo computacional. A forma como isto é feito pode ser visto abaixo, vejam que é bem simples, e já que estamos simplesmente copiando dados, podemos tratar os 32 bits em um único passo, e isto seria tão rápido quanto o que seria entregue pelo sistema de processamento gráfico.


                   


Desta forma chegamos a rotina necessária para fazer o redimensionamento da imagem o mais rápido possível.

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

A rotina conta com um laço aninhados, o laço interno irá executar a função f(x) e o laço externo a função f(y), quando executamos a função f(x) pode ser que iremos produzir blocos vazios, isto é corrigido nesta linha

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

se por um acaso ocorrer uma diferença entre os valores de X, esta linha irá corrigir isto, copiando o ultimo valor da imagem, como consequência teremos a produção de um serrilhado, mas o custo computacional nestes casos seria mínimo, já que este fragmento teria um laço interno executando por um mínimo de tempo, isto quando ele executa, o que não será sempre, se desejar interpolar os dados evitando este serrilhado, bastará modificar esta linha de forma a criar os cálculos que foram explicados acima.

Assim que uma linha completa já tiver sido calculada, verificamos o f(y) de forma a evitar que apareça blocos vazios caso o f(y) seja maior que 1, e isto é conseguido nesta linha

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

novamente isto irá produzir um serrilhado, mas se pode corrigir isto da mesma forma que se faria a mudança do código da linha anterior. O fato de somarmos o valor de largura da nova imagem é por estarmos copiando uma linha já tratada pelo laço responsável por tratar o f(x) da nova imagem, se a soma fosse feita usando qualquer outro valor, a imagem seria torcida de uma forma estranha.


Conclusão

Espero que esta ideia deixe seus gráficos mais divertidos e agradáveis de serem observados por horas e horas a fio, já que quando a imagem de fundo ficar cansativa e enjoativa, você apenas precisará escolher outra, sem precisar recompilar nada, apenas escolhendo a nova imagem que será usada como fundo do gráfico.

Um último detalhe que merece ser mencionado, é que no caso de usar a classe de colocação de uma imagem de fundo em um EA, ela deverá ser a primeira coisa a ser declarada na rotina INIT, isto para evitar que a imagem de fundo venha a sobrepor outros objetos gráficos criados pelo EA.

Aprecie com moderação o resultado final, pois agora você ficará ainda mais envolvido com os gráficos ....



Arquivos anexados |
EA_With_Wallpaper.zip (7778.85 KB)
Últimos Comentários | Ir para discussão (9)
felipe ramos
felipe ramos | 2 mai 2022 em 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 utilizando essa funcao. 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 mai 2022 em 20:05
Daniel Jose #:

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, 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, verá que a imagem do bitmap estará sendo plotada no corpo das velas, indicando que o objeto bitmap está em primeiro plano, mais uma vez este não é o comportamento esperado por causa da linha de código acima, por causa disto acontece de receber todo e qualquer evento de clique ... 🙁


Daniel, 

Será que se colocar um temporizador e rodar uma função para desabilitar e habilitar o wallpaper novamente ou alguma coisa que "reafirme" a função colocando o wallpaper no fundo (back) ?
Um painel GUI que eu utilizo, tive que partir para esse solução. Alguns elementos gráficos precisam ser deletados e criados novamente para que fiquem no fundo ou na frente dependendo do tipo de objeto.

Eudes Filho
Eudes Filho | 25 jul 2022 em 23:51

Daniel, parabéns pelo trabalho, uso MT4 há anos e com a limitação de ativos pelos provedores de liquidez e corretoras fui forçado a migrar para o MT5 e há tempos procurava algo para customizar com imagens meu template assim como uso no MT4 e tenho o Chart Wall para o MT4, segue link para download no MQL5: https://www.mql5.com/pt/market/product/2767?source=External%3Ahttps%3A%2F%2Fwww.google.com%2F

E vídeo no Youtube para auxiliar quem assim precisar: https://www.youtube.com/watch?v=AMNhI45EZ2o

Mais uma vez, parabéns pelo trabalho de fazer e deixar código aberto.

Sucesso.

leandronetto011
leandronetto011 | 26 out 2022 em 01:56

Olá! Daniel. Beleza? 

Desculpa por ressucitar o tópico... estou usando seu código no meu E.A e estou deparado com o seguinte erro:"arquivo não encontrato", já li o código várias vezes e não consigo encontrar o erro,  fiz todos os passos que vc deixou... E também usei os arquivos de imagens que deixou para download e deu o mesmo erro. Teria como me ajudar? Desde já agradeço! 

Daniel Jose
Daniel Jose | 26 out 2022 em 12:26
leandronetto011 #:

Olá! Daniel. Beleza? 

Desculpa por ressucitar o tópico... estou usando seu código no meu E.A e estou deparado com o seguinte erro:"arquivo não encontrato", já li o código várias vezes e não consigo encontrar o erro,  fiz todos os passos que vc deixou... E também usei os arquivos de imagens que deixou para download e deu o mesmo erro. Teria como me ajudar? Desde já agradeço! 

Não entendi 🤔🤔 ... você deve pegar e descompactar o arquivo na pasta MQL5. Para fazer isto de forma correta: Abra o MetaEditor. No canto direito você irá ver o navegador das pastas. Clique na RAIZ ( MQL5 ) com o botão direito, Selecione ABRIR PASTA. O Explorer será aberto na pasta MQL5. Nesta pasta você deverá descompactar o arquivo ZIP. Assim as imagens serão encontradas quando você for lançar o sistema no gráfico.

Gráficos na biblioteca DoEasy (Parte 89): programando objetos gráficos padrão Funcionalidade básica Gráficos na biblioteca DoEasy (Parte 89): programando objetos gráficos padrão Funcionalidade básica
Agora nossa biblioteca pode rastrear a remoção, modificação de parâmetros e o surgimento de objetos gráficos padrão no gráfico do terminal do cliente. Mas para ter um "jogo" completo, certamente nos falta a capacidade de criar objetos gráficos padrão a partir de nossos programas.
Construindo uma rede neural profunda do zero em linguagem MQL Construindo uma rede neural profunda do zero em linguagem MQL
Neste artigo, vou apresentar a vocês uma rede neural profunda implementada em linguagem MQL com suas diferentes funções de ativação, entre elas estão a função tangente hiperbólica para as camadas ocultas e a função Softmax para a camada de saída. Avançaremos do primeiro passo até o final para formar completamente a rede neural profunda.
Indicadores múltiplos em um gráfico (Parte 01): Entendendo os conceitos Indicadores múltiplos em um gráfico (Parte 01): Entendendo os conceitos
Entenda como é possível adicionar vários indicadores ao mesmo tempo sem ocupar muda área do seu gráfico. Muita gente gosta e se sente mais segura operando quando observa diversos indicadores ao mesmo tempo, por exemplo RSI, ESTOCÁSTICO, MACD, ADX entre outros, e em alguns casos até mesmo ativos distintos que compõem um determinado índice.
Trabalhando com o tempo (Parte 2): funções Trabalhando com o tempo (Parte 2): funções
Vamos aprender a reconhecer automaticamente as diferenças de tempo junto à corretora, bem como o Tempo Médio de Greenwich. Em vez de preguntar à corretora, que provavelmente dará uma resposta imprecisa (e quem quer explicar onde está o horário de negociação?), seremos nós mesmos a ver a que horas ela recebe as cotações nas semanas em que os fusos horários são trocados. Mas é claro que não vamos fazer isso manualmente, deixaremos o software fazer o trabalho por nós.