English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Desenhando e implementando novos widgets GUI com base no objeto CChartObject

Desenhando e implementando novos widgets GUI com base no objeto CChartObject

MetaTrader 5Indicadores | 7 fevereiro 2014, 09:15
1 807 0
investeo
investeo

Introdução

Depois que escrevi um artigo anterior sobre um Expert Advisor semiautomático com interface GUI, descobri que seria desejável aprimorar a interface com algumas novas funcionalidades para Expert Advisors e indicadores mais complexos. Após familiarizar-me com as classes da Biblioteca Padrão do MQL5, implementei novos widgets.

Neste artigo, descreverei um processo de utilização de classes da Biblioteca Padrão do MQL5 para objetos GUI e como implementar novas classes derivadas da classe: CChartObjectEdit: CChartObjectProgressBar, CChartObjectSpinner e CChartEditTable. A classe CChartEditTable usa um array de objetos dinâmico bidimensional, este é um exemplo funcional sobre como implementar um array 2D dinâmico de objetos no MQL5.

1. CChartObject e seus descendentes

Se não usamos a classe padrão da biblioteca MQL5, devemos usar Funções de Objeto para criar e manter qualquer objeto no gráfico.

Objetos são criados com a função ObjectCreate(), e o tipo de objeto é passado para a função ObjectCreate() como um valor ENUM_OBJECT. Todos os objetos no gráfico possuem suas próprias propriedades, que podem ser do tipo Integer, Double, ou String. Todas as propriedades são definidas e obtidas por meio de funções dedicadas: ObjectGetInteger(), ObjectSetInteger(), ObjectGetDouble(), ObjectSetDouble(), ObjectGetString(), ObjectSetString(). Há também funções para excluir, mover e contar objetos de um determinado gráfico.

Devido ao paradigma de POO no MQL5, a manipulação de diversos objetos de gráfico pode ser feita usando a classe CChartObject e seus descendentes.

A classe CChartObject é uma classe de base para qualquer um dos objetos gráficos que possam ser colocados em um gráfico. Por favor, observe o diagrama de herança básico para CChartObject abaixo:

Diagrama de herança para a classe CChartObject

Figura 1. Diagrama de herança para a classe CChartObject


Como podemos ver, há algumas classes que estão marcadas com um pequeno triângulo no canto inferior direito.

Estas são classes que são pais de outras classes. Basicamente, classes descendentes aprimoram possibilidades de uma classe base, adicionando novas variáveis e métodos que operam no objeto. Eles podem também diferenciarem-se em métodos Create() e Type() para criarem um objeto derivado e retornar seu tipo.

Permita que eu mostre isso com um exemplo: A classe CChartObjectTrend é pai das classes CChartObjectTrendByAngle, CChartObjectChannel, CChartObjectStdDevChannel, CChartObjectRegression e CChartObjectPitchfork.

A CChartObjectTrend é uma classe base para os objetos que têm propriedades OBJPROP_RAY_RIGHT e OBJPROP_RAY_LEFT e é definida como segue:

class CChartObjectTrend : public CChartObject
  {
public:
   //--- methods of access to properties of the object
   bool              RayLeft() const;
   bool              RayLeft(bool new_sel);
   bool              RayRight() const;
   bool              RayRight(bool new_sel);
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,
                                datetime time1,double price1,datetime time2,double price2);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_TREND); }
   //--- methods for working with files
   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
  };

Há comentários na definição que permitem distinguir diferentes tipos de métodos.

Métodos de acesso a propriedades do objeto são RayLeft() e RayRight(). Sua implementação é chamar os métodos ObjectGetInteger() e ObjectSetInteger() que operam no objeto CChartObjectTrend.

bool CChartObjectTrend::RayLeft(bool new_ray)
  {
//--- checking
   if(m_chart_id==-1) return(false);
//---
   return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_LEFT,new_ray));
  }

O método Create() é responsável por criar e anexar um objeto ao gráfico.

Ele chama o método ObjectCreate() com OBJ_TREND como um dos parâmetros:

bool CChartObjectTrend::Create(long chart_id,string name,int window,
                                   datetime time1,double price1,datetime time2,double price2)
  {
   bool result=ObjectCreate(chart_id,name,OBJ_TREND,window,time1,price1,time2,price2);
   if(result) result&=Attach(chart_id,name,window,2);
//---
   return(result);
  }

Os métodos Save() e Load() armazenam e carregam dados do objeto em um disco rígido usando as funções FileWriteInteger() e FileLoadInteger():

bool CChartObjectTrend::Save(int file_handle)
  {
   bool result;
//--- checking
   if(file_handle<=0) return(false);
   if(m_chart_id==-1) return(false);
//--- writing
   result=CChartObject::Save(file_handle);
   if(result)
     {
      //--- writing value of the "Ray left" property
      if(FileWriteInteger(file_handle,(int) ObjectGetInteger(m_chart_id,m_name,
                                                        OBJPROP_RAY_LEFT),CHAR_VALUE)!=sizeof(char))
      return(false);
      //--- writing value of the "Ray right" property
      if(FileWriteInteger(file_handle,(int) ObjectGetInteger(m_chart_id,m_name, 
                                                                OBJPROP_RAY_RIGHT),CHAR_VALUE)!=sizeof(char))
       return(false);
     }
//---
   return(result);
  }

bool CChartObjectTrend::Load(int file_handle)
  {
   bool result;
//--- checking
   if(file_handle<=0) return(false);
   if(m_chart_id==-1) return(false);
//--- reading
   result=CChartObject::Load(file_handle);
   if(result)
     {
      //--- reading value of the "Ray left" property
      if(!ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_LEFT,
                                                 FileReadInteger(file_handle,CHAR_VALUE)))return(false);
      //--- reading value of the "Ray right" property
      if(!ObjectSetInteger(m_chart_id,m_name,OBJPROP_RAY_RIGHT,
                                                 FileReadInteger(file_handle,CHAR_VALUE))) return(false);
     }
//---
   return(result);
  }

Vamos ver rapidamente as definições de classes descendentes de CChartObjectTrend.

A classe CChartObjectTrendByAngle adiciona o modificador de propriedade Angle(), e retorna o tipo de objeto OBJ_TRENDBYANGLE:

class CChartObjectTrendByAngle : public CChartObjectTrend
  {
public:
   //--- methods of access to properties of the object
   double            Angle() const;
   bool              Angle(double angle);
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2);
   //--- method of identifying the object
   virtual int       Type() { return(OBJ_TRENDBYANGLE); }
  };

A classe CChartObjectChannel retorna o tipo de objeto OBJ_CHANNEL e, uma vez que ele manipula canais, três pares de parâmetros de preço/data são passados para o método Create():

class CChartObjectChannel : public CChartObjectTrend
  {
public:
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2,datetime time3,double price3);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_CHANNEL); }
  };

A classe CChartObjectStdDevChannel adiciona o modificador de propriedade Deviations() e um parâmetro de desvio adicional no método Create():

class CChartObjectStdDevChannel : public CChartObjectTrend
  {
public:
   //--- methods of access to properties of the object
   double            Deviations() const;
   bool              Deviations(double deviation);
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,
                           datetime time1,datetime time2,double deviation);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_STDDEVCHANNEL); }
   //--- methods for working with files
   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
  };

A classe CChartObjectRegression cria uma linha de tendência de regressão, apenas os métodos Create() e Type() são sobrescritos daqueles na classe CChartObjectTrend:

class CChartObjectRegression : public CChartObjectTrend
  {
public:
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,datetime time1,datetime time2);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_REGRESSION); }
  };

A classe CChartObjectPitchfork manipula o tipo forquilha, também apenas os métodos Create() e Type() são alterados:

class CChartObjectPitchfork : public CChartObjectTrend
  {
public:
   //--- method of creating the object
   bool              Create(long chart_id,string name,int window,datetime time1,double price1,
                               datetime time2,double price2,datetime time3,double price3);
   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_CHANNEL); }
  };

Este exame rápido mostrou as regras básicas que são aplicadas quando escrevemos uma nova classe de objeto gráfico com base em alguma outra classe:

  • alterar o método Create() para criação de objeto
  • alterar o método Type() para retornar o tipo de objeto
  • adicionando modificadores de acesso a propriedades

Nem todas as regras devem ser aplicadas, apenas podem ser adicionados novos modificadores de acesso ou adicionadas novas variáveis e/ou objetos na classe.

Antes de prosseguirmos, deixe-me explicar como usar os métodos CChartObject em objetos gráficos.

Em vez de usar a família de métodos ObjectSet e ObjectGet e usar propriedades de objeto, é suficiente declarar CChartObject ou um objeto descendente e invocar métodos que alteram suas propriedades desejadas. Para tornar mais fácil, estou fornecendo um exemplo de um rótulo comum.

Em vez de escrever:

void OnStart()
  {
//---
   string label_name="my_OBJ_LABEL_object";
   if(ObjectFind(0,label_name)<0)
     {
      Print("Object ",label_name," not found. Error code = ",GetLastError());
      ObjectCreate(0,label_name,OBJ_LABEL,0,0,0);           
      ObjectSetInteger(0,label_name,OBJPROP_XDISTANCE,200);
      ObjectSetInteger(0,label_name,OBJPROP_YDISTANCE,300);
      ObjectSetInteger(0,label_name,OBJPROP_COLOR,White);
      ObjectSetString(0,label_name,OBJPROP_TEXT,UP);
      ObjectSetString(0,label_name,OBJPROP_FONT,"Wingdings");
      ObjectSetInteger(0,label_name,OBJPROP_FONTSIZE,10);
      ObjectSetDouble(0,label_name,OBJPROP_ANGLE,-45);
      ObjectSetInteger(0,label_name,OBJPROP_SELECTABLE,false);
      ChartRedraw(0);                                      
     }
  }

Podemos implementá-la usando o paradigma POO:

1. Declare o objeto CChartObjectLabel:

CChartObjectLabel label;

2. Opere no objeto:

int OnInit()
  {
//---
   label.Create(0, label_name, 0, 0);
   label.X_Distance(200);
   label.Y_Distance(300);
   label.Color(White);
   label.Description(UP);
   label.Font("Wingdings");
   label.FontSize(10);
   label.Angle(-45);
   label.Selectable(false);
//---
   return(0);
  }

Como você pode ver, a diferença principal é que não estamos mais operando em uma string label_name:

string label_name="my_OBJ_LABEL_object";

e chamamos as funções ObjectSetInteger(), ObjectGetInteger(), ObjectSetDouble(), ObjectGetDouble() com label_name como um dos parâmetros, mas declaramos o objeto CChartObjectLabel e usamos seus métodos. Isto não é apenas mais simples de lembrar e, lógico, para implementar, mas também é mais rápido para escrever.

O editor de código MQL5 nos fornece a funcionalidade de complemento de código quando colocamos um ponto (.) após a instância do objeto. Não é necessário estudar a documentação do MQL5 de ponta a ponta para ver qual propriedade OBJPROP colocar para definir ou obter uma determinada propriedade.

Da mesma maneira para a classe CChartObjectTrend que foi descrita previamente, para obter ou definir um raio à direita ou à esquerda, bastando declarar o objeto CChartObjectTrend e chamar o método RayRight() ou RayLeft():

CChartObjectTrend trendline;
trendline.RayRight(true); 


2. ProgressBar

O primeiro widget que vamos implementar é o ProgressBar (barra de progresso). Barras de progresso mostram o progresso de alguma operação, de 0 a x porcento.

Para torná-la mais robusta, não vamos restringir o valor máximo a 100, mas a qualquer valor inteiro positivo. Precisamos de uma faixa de cor que mudará de tamanho de acordo com o valor de progresso. A primeira coisa que vem à mente é usar dois retângulos, mas eu fui por outro caminho: usei dois objetos CChartObjectEdit, um dentro do outro, com diferentes cores de fundo.

Ele simplifica a codificação e adiciona texto que pode ser colocado dentro da barra de progresso para mostrar seu valor. Seria interessante se nossa barra de progresso pudesse ser horizontal ou vertical, dependendo das necessidades da pessoa.


2,1. Implementação de ProgressBar

A classe CChartObjectProgress é derivada da classe CChartObjectEdit.

Eu adicionei variáveis internas para manter o valor e restrições no valor: m_value, m_min, m_max.

A direção da barra de progresso é definida como um valor inteiro e mantida pela variável m_direction. A cor é mantida pela variável m_color. O método Type() retorna o valor de OBJ_EDIT, uma vez que não há um valor reconhecido para nosso propósito, de qualquer maneira. Alguém pode notar a variável m_bar CChartObjectEdit dentro da definição de classe - esta é a barra interior que altera seu tamanho dependendo de m_value. Variáveis adicionais m_name e m_chart mantêm valores internamente para a variável m_bar.

class CChartObjectProgressBar : public CChartObjectEdit
  {
private:
   int               m_value;
   int               m_min;
   int               m_max;
   int               m_direction;
   color             m_color;
   CChartObjectEdit  m_bar;
   string            m_name;
   long              m_chart_id;

public:
   int               GetValue();
   int               GetMin();
   int               GetMax();

   void              SetValue(int val);
   void              SetMin(int val);
   void              SetMax(int val);

   void              SetColor(color bgcol,color fgcol);
   bool              Create(long chart_id,string name,int window,int X,int Y,
                           int sizeX,int sizeY,int direction);

   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_EDIT); }
};

O método Create() cria o objeto ProgressBar e anexa-o ao gráfico.

Você pode notar que a variável Y é retirada da variável sizeY caso a barra vertical seja desenhada, isto ocorre porque normalmente o CChartObjectEdit é desenhado do topo para a parte inferior, e eu quis desenhar o retângulo interno de baixo para cima:

bool CChartObjectProgressBar::Create(long chart_id,string name,int window,int X,int Y,
                                          int sizeX,int sizeY,int direction=0)
  {
   bool result=ObjectCreate(chart_id,name,(ENUM_OBJECT)Type(),window,0,0,0);

   m_name=name;
   m_chart_id=chart_id;
   m_direction=direction;

   if(direction!=0)
     {
      Y=Y-sizeY;
     }

   ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_COLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(chart_id,name,OBJPROP_READONLY,true);

   result&=m_bar.Create(chart_id,name+"m_bar",window,X,Y,sizeX,sizeY);
   m_bar.Color(White);
   m_bar.ReadOnly(true);
   m_bar.Selectable(false);

//---
   if(result) result&=Attach(chart_id,name,window,1);
   result&=X_Distance(X);
   result&=Y_Distance(Y);
   result&=X_Size(sizeX);
   result&=Y_Size(sizeY);
//---
   return(result);
  }

O método SetColor() define as cores de frente e fundo de ambos os retângulos:

void CChartObjectProgressBar::SetColor(color bgCol,color fgCol=White)
  {
   m_color=bgCol;
   m_bar.BackColor(m_color);
   m_bar.Color(fgCol);
  }

O método SetValue() é responsável tanto por definir o valor de m_val quando por recalcular o tamanho do objecto retângulo interior.

O tamanho é calculado de maneira diferente para as barras horizontais e verticais:

void CChartObjectProgressBar::SetValue(int val)
  {
   if(m_direction==0) // horizontal ProgressBar
     {
      double sizex=(double)ObjectGetInteger(m_chart_id,m_name,OBJPROP_XSIZE,0);

      double stepSize=sizex/(m_max-m_min);

      m_value=val;
      m_bar.Create(m_bar.ChartId(),m_bar.Name(),m_bar.Window(),
                   m_bar.X_Distance(),m_bar.Y_Distance(),(int)MathFloor(stepSize*m_value),m_bar.Y_Size());
        } else {
      double sizey=(double)ObjectGetInteger(m_chart_id,m_name,OBJPROP_YSIZE,0);

      double stepSize=sizey/(m_max-m_min);
      m_value=val;
      m_bar.Create(m_bar.ChartId(),m_bar.Name(),m_bar.Window(),
                   m_bar.X_Distance(),(int)(this.Y_Distance()+sizey-MathFloor(stepSize*m_value)),
                   m_bar.X_Size(),(int)MathFloor(stepSize*m_value));

     }

   m_bar.Description(IntegerToString(m_value));
  }


2.2. Demonstração de ProgressBar

Uma vez que tenhamos implementado a classe CChartObjectProgressBar, é hora de vê-la em ação.

Para colocar uma nova barra de progresso no gráfico, basta declarar o objeto CChartObjectProgressBar e usar Create() e os métodos de propriedades apropriados.

progressBar.Create(0, "progressBar1", 0, 10, 10, 200, 40);
progressBar.SetColor(YellowGreen);
progressBar.SetMin(0);
progressBar.SetMax(100);
progressBar.SetValue(0);

Escrevi um Expert Advisor de demonstração que coloca seis barras de progresso diferentes e altera seus valores após qualquer objeto ser clicado na tela.

O código-fonte completo para esta e outras demonstrações está nos anexos, por favor, observe a apresentação abaixo:


3. Spinner

O widget Spinner é um widget que contém um campo e dois botões. é usado para incrementar ou decrementar um valor no campo de edição, clicando em um dos botões.

Enquanto estamos desenhando o objeto, eu não quis operar apenas com objetos inteiros, portanto o Spinner foi desenhado operando em um tipo double. O Spinner também tem a possibilidade de definir o tamanho do passo, que é o valor a incrementar ou decrementar o valor atual. Ele também deve possuir um valor mínimo e máximo que não deve ser ultrapassado.


3,1. Implementação do Spinner

No MQL5 temos as classes CChartObjectEdit e CChartObjectButton, que podem ser combinadas em uma, a classe CChartObjectSpinner. CChartObjectSpinner herda de CChartObjectEdit e contém dois membros de objetos CChartObjectButton privados.

Há restrições para o m_value mínimo e máximo armazenadas em variáveis membras m_min e m_max e a variável m_precision armazena a precisão de cálculo para o enésimo valor de dígito. Os métodos necessários são para acessar o valor, definir o tamanho de passo de incremento e decremento e definir o valor.

class CChartObjectSpinner: public CChartObjectEdit
  {

private:
   double            m_value;
   double            m_stepSize;
   double            m_min;
   double            m_max;
   int               m_precision;
   string            m_name;
   long              m_chart_id;
   CChartObjectButton m_up,m_down;

public:
   double            GetValue();
   double            GetMin();
   double            GetMax();

   void              SetValue(double val);
   void              SetMin(double val);
   void              SetMax(double val);

   double            Inc();
   double            Dec();

   bool              Create(long chart_id,string name,int window,int X,int Y,
                               int sizeX,int sizeY,double val,double stepSize,int precision);

   //--- method of identifying the object
   virtual int       Type() const { return(OBJ_EDIT); }
   
  };

O método Create() cria o objeto CChartObjectSpinner e anexa-o ao gráfico.

Há dois CChartObjectButtons criados do lado direito de CChartObjectEdit, cada um possuindo a altura equivalente à metade da altura de CChartObjectEdit.

O botão de incremento tem um sinal de '+' e o botão de decremento um sinal de '-'.

bool CChartObjectSpinner::Create(long chart_id,string name,int window,int X,int Y,
                                     int sizeX,int sizeY,double val=0.0,double stepSize=1.0,int precision=8)
  {
   bool result=ObjectCreate(chart_id,name,(ENUM_OBJECT)Type(),window,0,0,0);

   m_name=name;
   m_chart_id=chart_id;
   m_value=val;
   m_stepSize=stepSize;
   m_precision=precision;

   ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,White);
   ObjectSetInteger(chart_id,name,OBJPROP_COLOR,Black);
   ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(chart_id,name,OBJPROP_READONLY,true);

   result&=m_up.Create(chart_id, name+"_up", window, X+sizeX, Y, 15, sizeY/2);
   result&=m_down.Create(chart_id, name+"_down", window, X+sizeX, Y+sizeY/2, 15, sizeY/2);
   m_up.Description("+");
   m_down.Description("-");
   ObjectSetString(chart_id,name,OBJPROP_TEXT,0,(DoubleToString(m_value,precision)));

//---
   if(result) result&=Attach(chart_id,name,window,1);
   result&=X_Distance(X);
   result&=Y_Distance(Y);
   result&=X_Size(sizeX);
   result&=Y_Size(sizeY);
//---
   return(result);
  }

O método SetValue() define a variável privada m_value para o valor double, desde que esteja dentro da faixa .

void CChartObjectSpinner::SetValue(double val)
  {
   if(val>=m_min && val<=m_max) m_value=val;
   this.Description(DoubleToString(m_value));
  }

O método Inc() incrementa o valor por um determinado tamanho de passo, mas não mais que o valor m_max.

Por favor, perceba que eu tive que usar a função NormalizeDouble() para comparar valores double em determinada precisão.

double CChartObjectSpinner::Inc(void)
  {
   if(NormalizeDouble(m_max-m_value-m_stepSize,m_precision)>0.0) m_value+=m_stepSize;
   else m_value=m_max;
   this.Description(DoubleToString(m_value, m_precision));
   m_up.State(false);
   return m_value;
  }

O método Dec() decrementa o valor por um determinado tamanho de passo, mas não menos que o valor m_min.

double CChartObjectSpinner::Dec(void)
  {
   if(NormalizeDouble(m_value-m_stepSize-m_min,m_precision)>0.0)
      m_value-=m_stepSize; else m_value=m_min;
   this.Description(DoubleToString(m_value,m_precision));
   m_down.State(false);

   return m_value;
  }


3.2. Demonstração do Spinner

é hora de testar os objetos Spinner. Para usá-los, basta declarar o objeto CChartObjectSpinner e usar os métodos Create(), SetMin() e SetMax().

   spinner.Create(0, "spinner1", 0, 10, 10, 200, 40, 0.0, 0.4);
   spinner.SetMin(0);
   spinner.SetMax(100);

Preparei uma demonstração que usa três widgets Spinner e adiciona todos os valores após qualquer botão Spinner ser clicado.

Isto é feito dentro da função OnChartEvent();

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Check the event by pressing a mouse button
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
     
     if (sparam=="spinner1_up") spinner.Inc();
     if (sparam=="spinner1_down") spinner.Dec();
     if (sparam=="spinner2_up") spinner2.Inc();
     if (sparam=="spinner2_down") spinner2.Dec();
     if (sparam=="spinner3_up") spinner3.Inc();
     if (sparam=="spinner3_down") spinner3.Dec();
     
     label.Description(DoubleToString(NormalizeDouble(spinner.GetValue()+spinner2.GetValue()+spinner3.GetValue(),10),10));
   
   ChartRedraw();
     }
  }

Por favor, veja a demonstração anexa:

4. CChartObjectEditTable

Em muitos Expert Advisors com múltiplos quadros de tempo (MTF), há valores indicadores exibidos para cada quadro de tempo separadamente.

Algumas vezes, cada quadro de tempo possui diferentes configurações de indicador exibidas na forma de uma tabela 2D de retângulos ou quadrados de diferentes cores. Desenhei uma tabela 2D universal destes objetos, criando uma classe CChartObjectEditTable. Esta classe pode manter uma quantidade arbitrária de linhas e colunas, uma vez que estou usando um array dinâmico 2D de objetos.

Durante o projeto eu decidi definir uma cor para cada célula separadamente e também adicionar a possibilidade de colocar diferentes strings de texto em qualquer célula. Células possuem um tamanho igual, mas eu quis definir sua altura e largura e espaço entre as células.


4,1. Implementação de CChartObjectEditTable

A classe CChartObjectEditTable mantém um apontador CArrayObj para um array bidimensional de objetos, e variáveis membras m_rows e m_colums mantém o número de linhas e colunas na tabela.

Há uma variável membra m_baseName que mantém o prefixo de todos os objetos célula CChartObjectEdit dentro da tabela. Os métodos GetColor(), SetColor(), GetText(), SetText() são para definição e obtenção dos valores de cor e texto a qualquer célula desejada. O método Delete() apaga todos os objetos criados pelo método Create().

class CChartObjectEditTable
  {

private:
   CArrayObj        *array2D;
   int               m_rows;
   int               m_cols;
   string            m_baseName;

public:

   bool              Create(long chart_id,string name,int window,int rows,int cols,int startX,int startY,
                                int sizeX,int sizeY,color Bg,int deltaX,int deltaY);
   bool              Delete();
   bool              SetColor(int row,int col,color newColor);
   color             GetColor(int row,int col);
   bool              SetText(int row,int col,string newText);
   string            GetText(int row,int col);


  };

O método Create() cria uma tabela dinâmica bidimensional de objetos CChartObjectEdit.

Por favor, observe como criar um array 2D de objetos no MQL5: primeiro criamos um ponteiro para o array 2D e, então, preenchemos o array com um número de objetos CArrayObj(), ou seja, criamos arrays dentro do array. Todos os arrays podem ser vistos como recipientes para colunas da tabela.

Cada coluna contém linhas, que contêm objetos CChartObjectEdit, cada objeto sendo uma única célula a ser exibida.

bool CChartObjectEditTable::Create(long chart_id,string name,int window,int rows=1,int cols=1,
                                       int startX=0,int startY=0,int sizeX=15,int sizeY=15,
                                  color Bg=White,int deltaX=5,int deltaY=5)
  {
   m_rows=rows;
   m_cols=cols;
   m_baseName=name;
   int i=0,j=0;

   array2D=new CArrayObj();
   if (array2D==NULL) return false;
   
   for(j=0; j<m_cols; j++)
     {
      CArrayObj *new_array=new CArrayObj();
      if (array2D==NULL) return false;
   
      array2D.Add(new_array);
      for(i=0; i<m_rows; i++)
        {
         CChartObjectEdit *new_edit=new CChartObjectEdit();

         new_edit.Create(chart_id, name+IntegerToString(i)+":"+IntegerToString(j), window, 
                         startX+j*(sizeX+deltaX), startY+i*(sizeY+deltaY), sizeX, sizeY);
         new_edit.BackColor(Bg);
         new_edit.Color(White);
         new_edit.Selectable(false);
         new_edit.ReadOnly(true);
         new_edit.Description("");
         new_array.Add(new_edit);
        }
     }

   return true;
  }
<m_cols; j++)="" {="" carrayobj="" *new_array="<m_rows; i++)="" {="" cchartobjectedit="" *new_edit="

O método SetColor() define a cor de uma determinada célula. Primeiro ele encontra o array de colunas e, então, o enésimo elemento no array de coluna.

Então o valor da cor do elemento é alterado, invocando o método BackColor().

bool CChartObjectEditTable::SetColor(int row,int col,color newColor)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         element.BackColor(newColor);

         return true;
        }
     }

   return false;
  }
<m_cols)) {=""

O método GetColor() possui o mesmo algoritmo para encontrar a célula que o método SetColor(), mas ele retorna o valor de cor de qualquer célula.

color CChartObjectEditTable::GetColor(int row,int col)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         return element.BackColor();
        }
     }

   return NULL;
  }
<m_cols)) {=""

O método SetText() busca o elemento e define seu valor de texto invocando o método Description().

bool CChartObjectEditTable::SetText(int row,int col,string newText)
  {
   CArrayObj *sub_array;
   CChartObjectEdit *element;

   if((row>=0 && row<m_rows) && (col>=0 && col<m_cols))
     {
      if(array2D!=NULL)
        {
         sub_array=array2D.At(col);
         element=(CChartObjectEdit*)sub_array.At(row);
         element.Description(newText);

         return true;
        }
     }

   return false;
  }
<m_cols)) {=""

O método Delete() apaga todos os objetos criados pelo método Create().

Primeiro ele limpa todos os arrays de coluna e, então, apaga o objeto array2D da memória.

bool CChartObjectEditTable::Delete(void)
  {
   for(int j=0; j<m_cols; j++)
     {
      CArrayObj *column_array=array2D.At(j);
      column_array.Clear();
      delete column_array;
     }
   delete array2D;
   return true;
  }
<m_cols; j++)="" {="" carrayobj="" *column_array="array2D.At(j);" column_array.clear();=""


4.2. Demonstração de CChartObjectEditTable

Para usar o widget CChartObjectEditTable, é necessário declarar o objeto CChartEditTable e usar o método Create() com parâmetros indicando quantas linhas e colunas a tabela deve conter.

Então, usando modificadores de propriedades, é possível simplesmente alterar a cor e texto em qualquer célula.

table.Create(0,"t",0,1,10,10,10,15,15,Yellow);
table.SetColor(2,2,Red);
table.SetText(2,2,"2");

Por favor, veja o script que eu preparei, que demonstra as possibilidades ao usar o objeto CChartObjectEditTable.

O código-fonte do script está em anexo.


Conclusão

Descrevi e introduzi no artigo um processo para criar novos widgets de gráfico derivados da classe CChartObject.

O processo de usar widgets implementados é bastante simples e custa apenas algumas linhas de código.

Para utilizar os widgets, por favor, inclua o arquivo ChartObjectsExtControls.mqh no Expert Advisor ou código de indicador.


Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/196

Arquivos anexados |
progressbarea.mq5 (3.67 KB)
spinnerdemoea.mq5 (2.83 KB)
edittabledemo.mq5 (4.15 KB)
Cálculos paralelos no MetaTrader 5 Cálculos paralelos no MetaTrader 5
O tempo tem sido de grande valor por toda a história da humanidade, e tentamos não desperdiçá-lo sem necessidade. Este artigo dirá a você como acelerar o trabalho do seu Expert Advisor se seu computador tiver um processador com vários núcleos. Além disso, a implementação do método proposto não requer conhecimento de nenhuma outra linguagem além de MQL5.
Construindo um Expert Advisor de arrastar e soltar semiautomático interativo com base no risco predefinido e proporção R/R Construindo um Expert Advisor de arrastar e soltar semiautomático interativo com base no risco predefinido e proporção R/R
Alguns operadores executam todas suas negociações automaticamente, e alguns misturam negociações automáticas e manuais, com base na saída de diversos indicadores. Sendo um membro do último grupo, precisei de uma ferramenta interativa para avaliar risco dinamicamente e obter níveis de preço diretamente do gráfico. Este artigo apresentará uma maneira de implementar um Expert Advisor interativo semiautomático, com risco de equidade predefinido e proporção R/R. O risco do Expert Advisor, R/R e parâmetros de tamanho de lote podem ser alterados durante o tempo de execução no painel do EA.
Traçando canais - vista interna e externa Traçando canais - vista interna e externa
Acredito que não será exagero dizer que os canais são a ferramenta mais popular para a análise de mercado e para a tomada de decisões de negociação após as médias móveis. Sem aprofundar na variedade de estratégias de negócio que usa canais e seus componentes, vamos discutir a base matemática e a implementação prática de um indicador que traça um canal estabelecido por três extremos na tela do terminal do cliente.
Indicadores de William Blau e sistemas de comércio no MQL5. Parte 1: Indicadores Indicadores de William Blau e sistemas de comércio no MQL5. Parte 1: Indicadores
O artigo apresenta os indicadores descritos no livro de William Blau "Momentum, Direction, and Divergence". A abordagem de William Blau nos permite prontamente e precisamente aproximar as flutuações da curva de preço, para determinar a tendência de movimentos de preço e os pontos de virada e eliminar o ruído de preço. Entretanto, também somos capazes de detectar estados de excesso de compra ou venda do mercado e sinais indicando o fim da tendência e reverso do movimento de preço.