English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Controles gráficos personalizados. Parte 1: criando um controle simples

Controles gráficos personalizados. Parte 1: criando um controle simples

MetaTrader 5Exemplos | 21 janeiro 2014, 13:16
4 172 3
Dmitry Fedoseev
Dmitry Fedoseev

Introdução

A linguagem MQL5 fornece aos desenvolvedores uma ampla gama de objetos gráficos controlados programaticamente: um botão, um rótulo de texto, um campo de edição, um rótulo bitmap (fig. 1), ferramentas gráficas diferentes para análise (fig. 2).


Fig. 1. Objetos gráficos: um botão, um rótulo de texto, um campo de edição, um rótulo bitmap


Fig. 2. Alguns objetos gráficos para análise: uma elipse, o Fibonacci Fan, a Fibonacci Expansion

Há mais de quarenta objetos gráficos no terminal do cliente MetaTrader 5 no total. Todos esses objetos podem ser usados separadamente, mas mais frequentemente são usados​em uma cadeia de objetos interligados. Por exemplo, quando um campo de edição (OBJ_EDIT) é utilizado um rótulo bitmap (OBJ_LABEL) é usado junto com ele muitas vezes para indicar a função do campo de edição.

Quando se utiliza um campo de edição, muitas vezes você tem que verificar a correção de entrada de dados por um usuário, bem como, para fornecer a possibilidade de utilizar tanto um ponto quanto uma vírgula como separador decimal.

Ao usar uma saída programática de dados, você deve formatar os dados. Por exemplo, você deve excluir zeros desnecessários. Assim, seria mais fácil ter um único objeto, que inclui o campo de edição, o rótulo de bitmap e algumas outras características funcionais.

Atualmente, existe um certo conjunto de controles gráficos no mundo da programação, que é utilizado em quase todas as aplicações: uma forma (na base de uma interface de aplicação, onde todos os elementos de controle estão localizados), um quadro (que permite o agrupamento e separação dos conjuntos de elementos que têm uma finalidade funcional), um botão, um campo de edição, um rótulo, uma caixa de seleção, botões de rádio, barras de rolagem vertical e horizontal, uma lista, uma lista suspensa, uma linha de menu e um guia de menu (fig. 3).


Fig. 3. A forma com os controles padrão mais comuns

A forma como os elementos acima mencionados são representados em MQL5 é semelhante a outras linguagens de programação (um botão e um campo de edição). Seria conveniente ter outros controles amplos generalizados em seu arsenal.

Vários ambientes de desenvolvimento fornecem programadores com ferramentas especiais para a criação de controles personalizados. A MQL5 não fornece tal função. No entanto, desde que a MQL5 é uma linguagem orientada a objetos, não é necessário tê-la. Tudo pode ser feito sob a forma de objetos programados separados.

Os princípios e a metodologia de criação de controles personalizados serão discutidos mais adiante neste artigo. Em sua base, todo mundo que é especializado em programação pode criar um conjunto necessário de controles que podem ser usados repetidamente nas aplicações.


1. O que deveria ser um controle gráfico

1.1. Requisitos e princípios gerais

Para ser útil, os controles gráficos devem fazer o desenvolvimento de aplicações mais fáceis. Para ser assim, eles devem satisfazer os seguintes requisitos:

  1. Deve ser possível criar rapidamente um controle durante o desenvolvimento. Este problema é resolvido usando a abordagem orientada a objetos para programação. Um controle gráfico é representado como um objeto programado.

  2. Um controle deverá ser flexível. Em outras palavras, deve ser possível alterar as suas propriedades: tamanho, posição, cor, etc.

  3. Um controle deve ser fácil de usar - ele deve ter somente propriedades e métodos necessários, cuja finalidade é compreensível a partir do propósito do elemento e os nomes dos métodos. Agora vamos dividir as propriedades de controles em categorias:

    1. Propriedades sem controle. Essas propriedades incluem o esquema de cor. Todos os controlos que são utilizados numa aplicação devem ter um modelo geral. É por isso que a criação de cores para cada controle individualmente seria desgastante.
      Além disso, buscar uma cor para algum controle é uma tarefa muito difícil a qual não quero perder tempo. Por exemplo, uma barra de rolagem. Alguns desenvolvedores da web podem ter enfrentado essa tarefa interessante.

    2. Propriedades definidas na fase de criação de um controle ou alteradas raramente. Por exemplo, o tamanho de um controle. A posição equilibrada e conveniente de todos os controles usados em uma aplicação é uma tarefa especial e difícil que é resolvida na fase de criação da interface.
      Em ligação a ela, o tamanho dos controles normalmente não é alterado durante a operação do programa. No entanto, às vezes, você pode precisar alterar essas propriedades. É por isso que você deve fornecer a possibilidade de alterar essas propriedades durante a operação do programa.

    3. Propriedades operacionais principais. Propriedades que são frequentemente alteradas a partir do programa. As propriedades que formam o propósito de um controle. Estas propriedades podem ser divididas em duas categorias:

      1. Propriedades com atualização automática de exibição de um controle. Por exemplo, uma edição. Uma vez que um valor é definido pelo programa, as alterações devem ser exibidas no ecrã. Na programação, ela deve ser efetuada utilizando uma única linha de código.

      2. Propriedades que necessitam de uma atualização compulsória de exibição. Por exemplo, uma lista. As listas implicam o trabalho com as matrizes de dados, é por isso que a lista não deve ser atualizada depois de trabalhar com um único elemento da lista. Aqui, é melhor executar uma atualização compulsória no fim do trabalho com todos os elementos da lista. Esta abordagem aumenta o desempenho do aplicativo de forma significativa.

  4. A possibilidade de ocultar e mostrar de forma rápida e simples do controle. Fazer um controle visível não deve exigir definições repetidas das propriedades visualizadas, o acesso às propriedades do objeto devem ser independente da visibilidade dos objetos gráficos deste controle. Em outras palavras, um objeto programado deve conter todas as propriedades do controle nele e não deve usar as propriedades dos objetos gráficos.

  5. A fim de ter uma saída de eventos que correspondem ao controle, deve haver processamento de eventos dos objetos gráficos individuais que estão incluídos no controle.

1.2. A maneira de usar um controle e métodos necessários

Considerando os requisitos acima mencionados, temos o seguinte esquema de criação de uma interface gráfica, bem como o conjunto de propriedades e métodos necessários de um objeto programado:

  1. Inicialização de um controle e ajuste paralelo de propriedades raramente alteradas. Este método irá ser chamado de Init(), ele tem vários parâmetros. O primeiro obrigatório - o nome de um controle. Este parâmetro será usado como um prefixo para os nomes de todos os objetos gráficos incluídos no controle. Além disso, os parâmetros que definem o tamanho do controle e outros parâmetros podem ser incluídos (dependendo da finalidade do controle).

  2. Os controles podem ter uma localização fixa num gráfico, bem como podem exigir a possibilidade de serem deslocados. Assim, vamos usar métodos diferentes para a determinação de coordenadas: SetPosLeft() - definição da coordenada X, SetPosTop() - definição da coordenada Y. Cada um destes métodos deve ter um parâmetro. Muitas vezes é preciso mudar ambas as coordenadas, por isso é bom ter o método SetPos() com dois parâmetros que alteram as coordenadas X e Y simultaneamente.
    Para calcular a localização de um controle, você pode precisar obter informações sobre o tamanho e a localização de outro controle. Para isso, vamos usar os seguintes métodos: Largura() - largura, altura() - altura, esquerdo() - a coordenada X, topo () - a coordenada Y. Nesta fase da operação, as coordenadas do controle são calculadas e os métodos de definição das mesmas são chamados.

  3. Logo após a criação ou em outro estágio de funcionamento da aplicação, precisamos fazer o controle visível. Para fazê-lo, o método Show() é usado. Para ocultar o controle, o método Hide() é usado.

  4. Como foi mencionado antes, durante a operação do programa talvez seja necessário alterarmos o tamanho do controle. É por isso que o objeto programado deve ter métodos diferentes para definir o tamanho - SetWidth() e/ou SetHeght(). Uma vez que são propriedades alteradas raramente, para que as alterações entrem em vigor precisamos chamar o método Refresh(), que atualiza a exibição.

  5. Os eventos dos objetos gráficos individuais serão processados no método do Event(), que vai devolver um valor que corresponde a um evento específico do controle. O método Event() deve ser chamado a partir da função OnChartEvent(), ela terá o mesmo conjunto de parâmetros como a função OnChartEvent().

Assim, temos o conjunto de métodos necessários de um objeto programado:

  • void Init(string aName...) - inicialização do controle;
  • void SetPosLeft(int aLeft) - definição da coordenada X;
  • void SetPosTop(int aTop) - definição da coordenada Y;
  • void SetPos(int aLeft, int aTop) - definição simultânea das coordenadas X e Y;
  • void SetWidth(int aWidth) - definição da largura;
  • void SetHeght(int aHeight) - definição da altura;
  • int Width() - obtenção da largura;
  • int Height() - obtenção da altura;
  • int Left() - obtenção da coordenada X;
  • int Top() - obtenção da coordenada Y;
  • void Refresh() - atualização completa da exibição;
  • void Show() - exibição;
  • void Hide() - ocultação;
  • int Event(const int id, const long & lparam, const double & dparam, const string & sparam) - processamento de eventos do gráfico;

A presença dos outros métodos (ou a ausência de alguns dos métodos listados) depende do próprio controle e da sua finalidade.

Mais adiante neste artigo, vamos tentar implementar os princípios descritos acima - vamos criar um controle para que o usuário insira um texto ou um número.

Antes disso, precisamos fornecer para nós mesmos as ferramentas para trabalhar de forma rápida e conveniente com objetos gráficos.


2. Como trabalhar com objetos gráficos de forma rápida e conveniente

Para trabalhar com objetos gráficos, o MQL5 oferece as seguintes funções básicas: ObjectCreate(), ObjectDelete(), ObjectSetDouble(), ObjectSetInteger(), ObjectSetString(), ObjectGetDouble(), ObjectGetInteger() e ObjectGetString(). Podemos usar essas funções diretamente, mas o processo de programação é muito trabalhoso e longo. As funções têm nomes longos, um monte de identificadores que têm nomes longos devem ser passados para as funções.

Para tornar o trabalho com objetos gráficos mais conveniente, podemos usar uma classe já pronta, que está incluída no pacote do terminal do cliente MetaTrader 5 (a classe CChartObject do arquivo MMQL5/Include/ChartObjects/ChartObject.mqh), ou podemos escrever nossa própria classe e dar-lhe todos os métodos necessários.

Comicamente, esta abordagem de programação consiste em pressionar uma tecla de função com um ponto depois. Depois de especificar o nome de um objeto, resta colocar um ponto e a lista de suas propriedades e métodos é aberta, basta escolher um item necessário da lista (fig. 4).


Fig. 4. A lista de propriedades e métodos do objeto

Há duas variantes da gestão de um objeto gráfico usando a classe auxiliar:

  1. Uma instância de classe individual é criada para cada objeto gráfico. Método muito conveniente, mas não econômico, do ponto de vista da quantidade de memória consumida. Para esta variante, é melhor escrever classes especiais para cada tipo de objeto gráfico. Porém, esta abordagem não é a mais adequada, uma vez que é muito trabalhosa. Em distinção da programação do Expert Advisors, não há nenhuma exigência rigorosa sobre o desempenho máximo ao criar uma interface de usuário.
  2. Use uma instância de classe. Se for necessário gerenciar um objeto gráfico, o objeto é adicionado à classe. Nós vamos usar a segunda variante.

Vamos criar uma classe universal que é mais adequada para o segundo tipo de gerenciamento de objetos gráficos.


3. A classe universal para gerenciamento de objetos gráficos

Durante a programação, o trabalho com cada objeto gráfico consiste de três estágios: criação, leitura/definição de propriedades, deleção no final da operação do aplicativo.

Então, em primeiro lugar, a classe de gestão dos objetos gráficos deve conter os métodos que os criam. Um parâmetro obrigatório para a criação de um objeto gráfico é o seu nome, assim os métodos de criação de objetos terão um parâmetro obrigatório para especificar um nome de um objeto criado.

Normalmente, os objetos gráficos são criados no gráfico, onde o programa (Expert Advisor, Indicator ou Script) é executado. Um caso mais raro é uma subjanela, e um caso ainda mais raro é outra janela de gráfico do terminal. Assim, o segundo parâmetro opcional, será aquele que especifica o número de uma subjanela, e o terceiro é o identificador de um gráfico.

Por padrão, ambos os parâmetros opcionais serão iguais a 0 (tabela de preços está em sua tabela "própria"). Olhe a lista dos tipos de objetos gráficos na documentação. Adicione o método Create para cada tipo.

Antes disso, precisamos criar um arquivo. Para fazê-lo, abra o MetaEditor, crie um novo arquivo de inclusão e o chame de IncGUI.mqh. No arquivo aberto, crie a classe CGraphicObjectShell com as seções protegidas e públicas. Na seção protegida, declare as variáveis para o nome de um objeto e o identificador de um gráfico.

Nos métodos de criação de objetos, essas variáveis​ serão atribuídas aos valores passados ​por meio de métodos como parâmetros, a fim de ter a possibilidade de gerenciá-los após a criação do objeto sem especificar um nome e um identificador gráfico. Assim, também podemos usar a classe para a primeira variante de gerenciamento de objetos gráficos.

Para ter uma possibilidade de usar a classe para a segunda variante (para gerenciar qualquer objeto gráfico), o dê um método de junção de um objeto gráfico (o método Attach()). Este método terá um parâmetro obrigatório - o nome de um objeto gráfico, e um parâmetro opcional - um identificador de gráfico. Pode ser que você precise saber o nome e o identificador de um objeto gráfico anexado. Para fazer isso, adicione os seguintes métodos ao objeto: Name() e ChartID().

Como resultado, temos a seguinte: "peça de trabalho" da classe:

class CGraphicObjectShell
  {
protected:
   string            m_name;
   long              m_id;
public:
   void Attach(string aName,long aChartID=0)
     {
      m_name=aName;
      m_id=aChartID;
     }
   string Name()
     {
      return(m_name);
     }    
   long ChartID()
     {
      return(m_id);
     }
  };

Adicione os métodos acima mencionados de criação de objetos gráficos. Os nomes destes métodos começarão com "Create".

Os leitores podem ignorá-lo, o arquivo IncGUI.mqh, anexo ao artigo, contém a classe já pronta do CGraphicObjectShell.

Como exemplo, aqui está um método de criação do objeto do gráfico da linha vertical (OBJ_VLINE):

void CreateVLine(string aName,int aSubWindow=0,long aChartID=0)
  {
   ObjectCreate(m_id,m_name,OBJ_VLINE,aSubWindow,0,0);
   Attach(aName,aChartID);
  }

Agora, abra a lista de propriedades dos objetos gráficos no guia do usuário, e os métodos escritos de definição de um valor para cada propriedade usando as funções ObjectSetDouble(), ObjectSetInteger() e ObjectSetString(). O nome dos métodos vai começar com "Set". Em seguida, escreva métodos para ler as propriedades usando as funções ObjectGetDouble(), ObjectGetInteger() e ObjectGetString().

Como exemplo, aqui estão os métodos para definir e obter a cor:

void SetColor(color aColor)
  {
   ObjectSetInteger(m_id,m_name,OBJPROP_COLOR,aColor);
  }
color Color()
  {
   return(ObjectGetInteger(m_id,m_name,OBJPROP_COLOR));
  }

Bem, agora parece que temos as ferramentas mínimas necessárias para trabalhar com objetos gráficos, mas não todas elas.

Às vezes, quando se trabalha com um objeto gráfico, você pode precisar executar apenas uma ação com um objeto. Neste caso, não será conveniente executar o método Attach() para o objeto, e depois voltar para o objeto principal e executar Attach() para ele novamente.

Vamos adicionar mais duas variantes de todos os métodos de definição/obtenção de propriedades para a classe.

A primeira delas - pelo nome em sua "própria" tabela:

void SetColor(string aName,color aColor)
  {
   ObjectSetInteger(0,aName,OBJPROP_COLOR,aColor);
  }
color Color(string aName)
  {
   return(ObjectGetInteger(0,aName,OBJPROP_COLOR));
  }

O segundo - pelo nome e identificador de um gráfico:

void SetColor(long aChartID,string aName,color aColor)
  {
   ObjectSetInteger(aChartID,aName,OBJPROP_COLOR,aColor);
  }
color Color(long aChartID,string aName)
  {
   return(ObjectGetInteger(aChartID,aName,OBJPROP_COLOR));
  }

Além do ObjectGet e das funções ObjectSet, existem outras funções para trabalhar com objetos gráficos: ObjectDelete(), ObjectMove(), ObjectFind(), ObjectGetTimeByValue(), ObjectGetValueByTime() e ObjectsTotal(). Eles também podem ser adicionados à classe com três variantes de ligação de cada um.

Finalmente, declare a classe CGraphicObjectShell com um nome simples e curto "g" neste arquivo.

CGraphicObjectShell g;

Agora, para começar a trabalhar com objetos gráficos, é suficiente conectar o arquivo IncGUI.mqh, assim teremos a classe "g" para trabalhar. Com ele, é fácil gerenciar todos os objetos gráficos disponíveis.


4. Peças de trabalho para controles

Apesar de termos a classe para trabalhar rápido com objetos gráficos, podemos criar controles de uma forma mais fácil. Todos os controles podem ser criados a partir de quatro objetos gráficos:

  1. Rótulo do retângulo (OBJ_RECTANGLE_LABEL).
  2. Rótulo do texto (OBJ_LABEL).
  3. Campo de edição (OBJ_EDIT).
  4. Botão (OBJ_BUTTON).

Após a criação de objetos gráficos, uma grande quantidade de propriedades devem ser definidas por eles: coordenadas, tamanho, cor, tamanho da fonte, etc. Para acelerar o processo, vamos criar uma outra classe e nomear CWorkPiece (peças de trabalho), e fornecê-la métodos para criação de objetos gráficos com propriedades passadas em parâmetros.

Para os controles de trabalho, você precisa lidar com os eventos do gráfico. Os eventos dos outros gráficos não estão disponíveis, assim vamos trabalhar apenas com o próprio gráfico - não haverá nenhum identificador gráfico nos parâmetros de métodos da classe CWorkPiece. 0 (próprio gráfico) será usado em todos os lugares.

O parâmetro que especifica um número de subjanela será utilizado para proporcionar a possibilidade de criação de controles, tanto na tabela de preços e nas suas subjanelas. Os objetos gráficos só serão vinculados ao canto superior esquerdo, se for necessário mudar um controle relativamente para qualquer outro canto, é muito mais fácil recalcular as coordenadas do controle inteiro considerando o tamanho do gráfico. Para controlar as mudanças do tamanho do gráfico, você pode manipular o evento CHARTEVENT_CHART_CHANGE.

Como base para muitos controles, vamos usar o objeto do "rótulo retângulo"; adicionar o método de criação deste objeto à classe CWorkPiece, o método é chamado Canvas():

void Canvas(string aName="Canvas",
             int aSubWindow=0,
             int aLeft=100,
             int aTop=100,
             int aWidth=300,
             int aHeight=150,
             color aColorBg=clrIvory,
             int aColorBorder=clrDimGray)
  {
   g.CreateRectangleLabel(aName,aSubWindow); // Creation of rectangle label
   g.SetXDistance(aLeft);                    // Setting of the X coordinate
   g.SetYDistanse(aTop);                        // Setting of the Y coordinate
   g.SetXSize(aWidth);                          // Setting of width
   g.SetYSize(aHeight);                         // Setting of height
   g.SetBgColor(aColorBg);                   // Setting of background color
   g.SetColor(aColorBorder);                 // Setting of border color
   g.SetCorner(CORNER_LEFT_UPPER);             // Setting of a anchor point
   g.SetBorderType(BORDER_FLAT);             // Setting of border type
   g.SetTimeFrames(OBJ_ALL_PERIODS);            // Setting visibility at all timeframes
   g.SetSelected(false);                        // Disabling selection
   g.SetSelectable(false);                   // Disabling of selection possibility
   g.SetWidth(1);                               // Setting of border width
   g.SetStyle(STYLE_SOLID);                  // Setting of border style
  }

Preste atenção: o método consiste em 14 linhas de código, seria muito frustrante escrever todos eles cada vez que você criar esse objeto. Agora é suficiente para escrever apenas uma linha, todos os parâmetros do método são opcionais e estão listados na ordem da sua frequência de utilização: posição, tamanho, cor, etc.

Da mesma forma que o método Canvas(), escreva os métodos de criação de um rótulo de texto, um botão e um campo de edição: Label(), Button() e Edit(). A classe CWorkPiece já pronta é anexada ao artigo no arquivo IncGUI.mqh. Além dos métodos acima mencionados, a classe contém vários outros métodos: Frame() e DeleteFrame() - os métodos de criação e exclusão de um quadro (fig. 5). Um quadro é um rótulo retângulo com uma legenda no canto superior esquerdo.

Os quadros estão planejados para serem usados​para agrupar controles em um formulário.


Fig. 5. A moldura com legenda

A lista de todos os métodos da classe CWorkPiece está anexa ao artigo.

Da mesma forma que a classe CGraphicObjectShell, declare a classe CWorkPiece com o nome curto "w" para ser capaz de usá-la depois de conectar o arquivo IncGUI.mqh.

CWorkPiece w;

Todas as ferramentas auxiliares estão prontas, então podemos continuar com o assunto do artigo - a criação de um controle personalizado.


5. Criação de um controle "Edit"

Primeiro de tudo, para não confundir a terminologia, vamos chamar o objeto gráfico OBJ_EDIT como um campo de texto, o objeto OBJ_LABEL - como um rótulo, e os controles criados - como um campo de edição. O controle criado é composto por dois objetos gráficos: um campo de edição (OBJ_EDIT) e um rótulo de texto (OBJ_LABEL).

O controle apoiará dois modos de operação: de entrada de dados de texto e entrada de dados numéricos. No modo de entrada dos dados numéricos, haverá uma limitação da gama dos valores de entrada e tanto uma vírgula quanto um ponto serão aceitáveis como separadores decimais. Em uma saída programada de um valor no campo de edição, ele é formatado de acordo com um número especificado de casas decimais.

Assim, ao inicializar um controle, devemos especificar o seu modo de operação: texto ou numérico, o modo é especificado usando o parâmetro aDigits. Um valor que é maior do que zero define o modo numérico com o número especificado de casas decimais, um valor negativo define o modo de texto.

Por defeito, a gama de valores aceitáveis ​é de DBL_MAX a DBL_MAX (toda a gama de valores de uma variável dupla). Se necessário, você pode definir outra gama chamando os métodos SetMin() e SetMax(). Entre os parâmetros de tamanho, apenas a largura será definida para o controle. Para o campo de edição parecer equilibrado, uma altura e largura correspondente deve ser definido para ele.

Uma alteração do tamanho da fonte requer a correspondente alteração da altura do objeto gráfico. E isso exige mudança de localização de todos os outros controles, ninguém faria isso. Supomos o uso de um tamanho de fonte constante para todos os controles e os correspondentes campos de edição. No entanto, a classe de controle terá um método que retorna a sua altura para facilidade de cálculo de coordenadas de outros elementos.

Haverá quatro parâmetros de cor: cor de fundo, cor do texto, cor de legenda e cor de advertência (será possível mudar a cor de fundo do campo de texto para chamar a atenção de um usuário, por exemplo, no caso de introduzir um valor incorreto).

Como foi mencionado antes, os controles em uma subjanela são suportados. Além dos principais parâmetros que são necessários para um controle trabalhar, vamos usar o outro parâmetro Tag, ele é um valor de texto simples, que é armazenado em uma instância da classe. A tag é uma ferramenta auxiliar conveniente.

A classe será chamada de CInputBox. Então, temos o seguinte conjunto de variáveis da classe (localizado no setor privado):

string m_NameEdit;    // Name of the Edit object
string m_NameLabel;   // Name of the Label object
int m_Left;           // X coordinate
int m_Top;            // Y coordinate
int m_Width;           // Width
int m_Height;          // Height
bool m_Visible;        // Visibility flag of the control
int m_Digits;          // Number of decimal places for the double number; -1 set the text mode
string m_Caption;      // Caption
string m_Value;        // Value
double m_ValueMin;     // Minimum value
double m_ValueMax;     // Maximum value
color m_BgColor;       // Background color
color m_TxtColor;     // Text color
color m_LblColor;      // Caption color
color m_WarningColor; // Warning font color
bool m_Warning;        // Flag of warning
int m_SubWindow;       // Subwindow
string m_Tag;           // Tag

Ao utilizar um controle, o primeiro método que é chamado é o Init().

Neste método, preparamos os valores de todos os parâmetros determinados antes:

// The initialization method
void Init(string aName="CInputBox",
           int aWidth=50,
           int aDigits=-1,
           string aCaption="CInputBox")
 { 
   m_NameEdit=aName+"_E";  // Preparing the name of the text field
   m_NameLabel=aName+"_L"; // Preparing the caption name
   m_Left=0;                 // X coordinate
   m_Top=0;                  // Y coordinate
   m_Width=aWidth;          // Width
   m_Height=15;             // Height
   m_Visible=false;         // Visibility
   m_Digits=aDigits;       // The mode of operation and the number of decimal places
   m_Caption=aCaption;     // Caption text
   m_Value="";              // Value in the text mode
   if(aDigits>=0)m_Value=DoubleToString(0,m_Digits); // Value in the numeric mode
   m_ValueMin=-DBL_MAX;                   // Minimal value
   m_ValueMax=DBL_MAX;                  // Maximal value
   m_BgColor=ClrScheme.Color(0);       // Background color of the text field
   m_TxtColor=ClrScheme.Color(1);      // Color of text and frame of the text field
   m_LblColor=ClrScheme.Color(2);      // Caption color
   m_WarningColor=ClrScheme.Color(3); // Warning color
   m_Warning=false;                      // Mode: warning, normal
   m_SubWindow=0; // Number of subwindow
   m_Tag=""; // Tag
 }

Se um controle funciona em modo texto, a variável m_Value é atribuída ao valor "," se ele funciona no modo numérico - um zero com o número especificado de casas decimais. Os parâmetros de cor são ajustados para seus padrões, vamos lidar com os esquemas de cores no último estágio.

As variáveis ​que determinam as coordenadas de controle são ajustadas para zero porque o controle não é visível ainda. Após chamar o método Init() (se estiver planejado que o controle terá uma posição fixa no gráfico) podemos definir as coordenadas usando o método StPos():

// Setting the X and Y coordinates
void SetPos(int aLeft,int aTop)
{ 
   m_Left=aLeft;
   m_Top=aTop;
}

Depois, podemos tornar o controle visível (o método Show()):

// Enable visibility on the previously specified position
void Show()
{ 
   m_Visible=true; // Registration of visibility
   Create();       // Creation of graphical objects
   ChartRedraw();   // Refreshing of the chart
}

A função Create() é chamada a partir do método Show(), que cria objetos gráficos (localizado no setor privado), então o gráfico é atualizado (ChartRedraw ()). O código da função Create() é dado abaixo:

// The function of creation of graphical objects
void Create(){ 
   color m_ctmp=m_BgColor;  // Normal background color
      if(m_Warning){ // The warning method is set
         m_ctmp=m_WarningColor; // The text field will be color in the warning color
      }
    // Creation of the text field
   w.Edit(m_NameEdit,m_SubWindow,m_Left,m_Top,m_Width,m_Height,m_Value,m_ctmp,m_TxtColor,7,"Arial"); 
      if(m_Caption!=""){ // There is a caption
          // Creation of caption
         w.Label(m_NameLabel,m_SubWindow,m_Left+m_Width+1,m_Top+2,m_Caption,m_LblColor,7,"Arial"); 
      } 
}   

Ao criar objetos gráficos na função Create() dependendo do valor da m_Warning, o campo de texto é atribuído a cor de fundo correspondente. Se a variável m_caption tem um valor, a legenda é criada (você pode criar um controle sem legenda).

Se você pretende fazer um controle móvel, use a segunda variante do método Show() - com a especificação de coordenadas. Neste método, as coordenadas são definidas e a primeira variante do método Show() é chamada:

// Setting the X and Y coordinates
void SetPos(int aLeft,int aTop){ 
   m_Left=aLeft;
   m_Top=aTop;
}

Depois que o controle é exibido, você precisa escondê-lo algum tempo.

O método Hide() é usado para este propósito:

// Hiding (deletion of graphical objects)
void Hide()
{ 
   m_Visible=false; // Registration of the invisible state
   Delete();        // Deletion of graphical objects
   ChartRedraw();    // Refreshing of the chart
}  

O método Hide() chama a função Delete() que exclui os objetos gráficos e, em seguida, ele chama a função ChartRedraw () para atualizar o gráfico. A função Delete() está no setor privado:

// The function of deletion of graphical objects
void Delete()
{ 
   ObjectDelete(0,m_NameEdit);  // Deletion of the text field
   ObjectDelete(0,m_NameLabel); // Deletion of caption
}   

Desde que nós já encontramos um método que apenas define os valores das propriedades sem alterar a exibição de um controle (o método SetPos()), é lógico criar um método para uma atualização forçada de um controle - o método Refresh():

// Refreshing of displaying (deletion and creation)
void Refresh()
{ 
   if(m_Visible)
   {   // Visibility enabled
      Delete();     // Deletion of graphical object
      Create();     // Creation of graphical objects
      ChartRedraw(); // Redrawing of the chart 
   }            
}   

O controle é muito simples, é por isso que usamos o método simples de atualização - exclusão e criação. Se fosse um controle mais complexo, como uma lista que consiste de muitos campos de edição, então poderíamos escolher uma abordagem mais inteligente.

Então, terminamos com a colocação do controle. Agora vamos prosseguir com a definição de um valor - o método SetValue(). Uma vez que o controle pode funcionar em dois modos, haverá duas variantes do método SetValue(): com a sequência e o tipo duplo. No modo de texto, o valor é utilizado como:

// Setting a text value
void SetValue(string aValue)
{ 
   m_Value=aValue; // Assigning a value to variable to store it
      if(m_Visible)
      { // The visibility of the control is enabled
          // Assigning the text field to the object for managing graphical objects
         g.Attach(m_NameEdit); 
         g.SetText(m_Value); // Setting the value for the text field
         ChartRedraw();        // Redrawing the chart
      }
} 

O argumento obtido é atribuído à variável m_Value, e se o controle é visível, ele é exibido no campo de texto.

No modo numérico, o argumento obtido é normalizado de acordo com o valor m_Digits, então é corrigido de acordo com o valor máximo e mínimo (m_MaxValue, m_MinValue), transformado numa sequência e, em seguida, o primeiro método SetValue() é chamado.

// Setting a number value
void SetValue(double aValue)
{ 
   if(m_Digits>=0)
   {  // In the numeric mode
       // Normalization of the number according to the specified accuracy
      aValue=NormalizeDouble(aValue,m_Digits);
      // "Alignment" of the value according to the minimal acceptable value
      aValue=MathMax(aValue,m_ValueMin); 
       // "Alignment" of the value according to the maximal acceptable value
      aValue=MathMin(aValue,m_ValueMax); 
       // Setting the obtained value as a string
      SetValue(DoubleToString(aValue,m_Digits)); 
   }
   else
   { // In the text mode
      SetValue((string)aValue); // Assigning the value to the variable to store it as is
   }            
}

Vamos escrever dois métodos para a obtenção de valores: um é para obter um valor de sequência, o outro é para obter um valor duplo:

// Getting a text value
string ValueStrind()
{ 
   return(m_Value);
}

// Getting a numeric value
double ValueDouble()
{ 
   return(StringToDouble(m_Value));
}

O valor que for definido para o controle é corrigido de acordo com os valores máximos e mínimos aceitáveis; vamos adicionar métodos para obter e os definir:

// Setting the maximal acceptable value
void SetMaxValue(double aValue)
{ 
   m_ValueMax=aValue; // Registration of the new maximal accepted value
      if(m_Digits>=0)
     { // The control works in the numeric mode
         if(StringToDouble(m_Value)>m_ValueMax)
         { /* The current value of the control is greater than the new maximal acceptable value*/
            SetValue(m_ValueMax); // Setting the new value that is equal to the maximal accepted value
         }
      }         
}

// Setting the minimal acceptable value
void SetMinValue(double aValue)
{ 
   m_ValueMin=aValue; // Registration of the new minimal acceptable value     
      if(m_Digits>=0)
      { // The control works in the numeric mode
         if(StringToDouble(m_Value)<m_ValueMin)
         { /* The current value of the control is less than the new minimal acceptable value*/
            SetValue(m_ValueMin); // Setting the new value that is equal to the minimum acceptable value
         }
      }
}

// Getting the maximal accepted value
double MaxValue()
{ 
   return(m_ValueMax); 
}

// Getting the minimal accepted value
double MinValue()
{ 
   return(m_ValueMin);
}

Se o controle funciona no modo numérico, a verificação e correção (se necessário) do valor atual é realizada ao definir os valores aceitáveis máximos e mínimos ​novos​.

Agora vamos lidar com a entrada de um valor por um usuário, o método Event(). A verificação da entrada de dados por um usuário será realizada utilizando o evento CHARTEVENT_OBJECT_ENDEDIT. Ao trabalhar em modo de texto, se um valor especificado pelo usuário não é igual ao m_Value, então o novo valor é atribuído ao m_Value, assim como o valor 1 é atribuído a variável m_event devolvida a partir do método Event().

Ao trabalhar no modo numérico, memorize o valor anterior do m_Value na variável m_OldValue, substitua a vírgula por um ponto, converta a sequência em número e a passe para a função SetValue(). Então, se m_Value e m_OldValue não são iguais, "gere" o evento (defina o valor 1 para a variável m_event).

// Handling of events
int Event(const int id,
           const long & lparam,
           const double & dparam,
           const string & sparam)
{ 
   bool m_event=0; // Variable for an event of this control
      if(id==CHARTEVENT_OBJECT_ENDEDIT)
      { // There has been an event of end of editing the text field
         if(sparam==m_NameEdit)
         { // The text field with the name m_NameEdit has been modified
            if(m_Digits<0)
            { // In the text mode
               g.Attach(m_NameEdit); // Assigning the text field for controlling it
                  if(g.Text()!=m_Value)
                  { // New value in the text field
                     m_Value=g.Text(); // Assigning the value to the variable to store it
                     m_event=1;         // There has been an event
                  }
            }
            else
            { // In the numeric mode
               string m_OldValue=m_Value; // The variable with the previous value of the control
               g.Attach(m_NameEdit);      // Attaching the text field for controlling it
               string m_stmp=g.Text();     // Getting text specified by a user in the text field
               StringReplace(m_stmp,",",".");       // Replacing comma with a dot
               double m_dtmp=StringToDouble(m_stmp); // Conversion to a number
               SetValue(m_dtmp);                     // Setting the new numeric value
                     // Comparing the new value with the previous one
                  if(StringToDouble(m_Value)!=StringToDouble(m_OldValue))
                  { 
                     m_event=1; // There has been an event 
                  }
            }
         }
      }               
   return(m_event); // Return the event. 0 - there is no event, 1 - there is an event
}

Suporte de trabalho do controle em subjanelas. Para fornecer, adicione o método SetSubWindow() que deve ser chamado da função OnChartEvent() no caso de um evento CHARTEVENT_CHART_CHANGE. Se você pretende usar o controle somente sobre uma tabela de preços, não há necessidade de chamar esse método.

A variável m_SubWindow já está declarada, ela é igual a 0 no padrão e é passada para os métodos Edit() e Label() da classe "w" na criação de objetos gráficos de um controle. O número de uma subjanela será passado para o método SetSubWindowName(), se o número for alterado, altere o valor da variável m_SubWindow e execute o método Refresh().

// Setting a subwindow by number
void SetSubWindow(int aNumber)
{ 
   int m_itmp=(int)MathMax(aNumber,0); /* If the number is negative, 0 will be used - the price chart*/
      if(m_itmp!=m_SubWindow)
      { /* The specified number doesn't correspond the number where the control is located*/
         m_SubWindow=m_itmp; // Registration of the new number of subwindow
         Refresh(); // Recreation of the graphical objects
      }
} 

Provavelmente, será mais conveniente transmitir o nome de uma subjanela, em vez do seu número para a função. Adicionar outra variante do método SetSubWindow():

// Setting a subwindow by name
void SetSubWindow(string aName)
{ 
   SetSubWindow(ChartWindowFind(0,aName)); // Determination of the number of the subwindow by its name and setting the subwindow by number
}

O fornecimento da classe de controle com os outros métodos em falta, concorda com o conceito mencionado no início do artigo.

Assim que tivermos o método SetPos(), que permite definir ambas as coordenadas do controle ao mesmo tempo, adicione os métodos para a configuração separada das coordenadas:

// Setting the X coordinate
void SetPosLeft(int aLeft)
{ 
   m_Left=aLeft;
}      

// Setting the Y coordinate
void SetPosTop(int aTop)
{ 
   m_Top=aTop;
}  

Método de configuração de largura:

// Setting the width
void SetWidth(int aWidth)
{ 
   m_Width=aWidth;
}

Método de obtenção das coordenadas e tamanho:

// Getting the X coordinate
int Left()
{ 
   return(m_Left);
}

// Getting the Y coordinate
int Top()
{ 
   return(m_Top);
}

// Getting the width
int Width()
{ 
   return(m_Width);
}

// Getting the height
int Height()
{
   return(m_Height); 
}

Os métodos para trabalhar com as marcas:

// Setting the tag
void SetTag(string aValue)
{ 
   m_Tag=aValue;
}

// Getting the tag
string Tag()
{ 
   return(m_Tag);
}  

Os métodos para aviso:

// Setting the warning mode
void SetWarning(bool aValue)
{ 
      if(m_Visible)
      { // Visibility is enabled
         if(aValue)
         { // We need to turn on the warning mode
            if(!m_Warning)
            { // The warning mode has not been enabled
               g.Attach(m_NameEdit);         // Attaching the text field for controlling
               g.SetBgColor(m_WarningColor); // Setting the warning color of text in the text field
            }
         }
         else
         { // We need to disable the warning mode
            if(m_Warning)
            { // The warning mode is enabled
               g.Attach(m_NameEdit);    // Attach the text field for controlling 
               g.SetBgColor(m_BgColor); // Setting the normal font color                
            }
         }
      }
   m_Warning=aValue; // Registration of the current mode
}

// Getting the warning mode
bool Warning()
{ 
   return(m_Warning);
}

Se o controle é visível ao definir o modo de aviso, o valor do parâmetro transmitido ao método SetWarning é verificado, se o seu valor não corresponde ao estado atual do controle, a cor de fundo do campo de texto é alterada.

De qualquer forma, o modo de ajuste é registrado, não para definir a cor correspondente ao campo de texto, no caso o controle esteja invisível.

Há uma propriedade esquerda - m_Digits. Vamos adicionar métodos para obter e definir o seu valor:

// Setting the number of decimal places
void SetDigits(int aValue)
{ 
   m_Digits=aValue; // Registration of the new value
      if(m_Digits>=0)
      { // The numeric mode
         SetValue(ValueDouble()); // Resetting of the current value
      }
}  

// Getting the m_Digits value
int Digits()
{ 
   return(m_Digits);
}  

Bem, terminamos com a parte mais interessante. Agora é vez da mais bonita.


6. Esquemas de cores

Os esquemas de cores serão armazenados nas variáveis​ da classe CСolorSchemes.

A classe será declarada previamente no arquivo IncGUI.mqh com o nome ClrScheme. Para definir um esquema de cor, vamos chamar o método SetScheme() com o número de um esquema de cores especificados como um parâmetro. Se o método SetScheme() não for chamado, será utilizado o esquema de cores com o número 0.

Para obter uma cor, vamos utilizar o método de Color(), com um número especificado de cor do esquema de cores. Vamos escrever a classe CСolor Schemes com os setores público e privado. No setor privado, declare a variável m_ShemeIndex para armazenar o índice de um esquema de cores. No setor público, escreva o método SetScheme():

// Setting the color scheme number
void SetScheme(int aShemeIndex)
{ 
   m_ShemeIndex=aShemeIndex;
}

O método Color(). A matriz bidimensional é declarada no método: a primeira dimensão é o número do esquema de cores, o segundo é o número de cores no esquema. Dependendo do número especificado de um esquema de cores, ele retorna a cor pelo número especificado nos parâmetros do método.

color Color(int aColorIndex)
{
   color m_Color[3][4];  // The first dimension - the color scheme number, the second one - the number of the color in the color scheme
   // default
   m_Color[0][0]=clrSnow;
   m_Color[0][1]=clrDimGray;
   m_Color[0][2]=clrDimGray;
   m_Color[0][3]=clrPink;
   // yellow-black
   m_Color[1][0]=clrLightYellow;
   m_Color[1][1]=clrBrown;
   m_Color[1][2]=clrBrown;
   m_Color[1][3]=clrPink;
   // blue
   m_Color[2][0]=clrAliceBlue;
   m_Color[2][1]=clrNavy;
   m_Color[2][2]=clrNavy;
   m_Color[2][3]=clrPink;
   return(m_Color[m_ShemeIndex][aColorIndex]); // Returning a value according to the scheme number and the number of color in the scheme
}

Por enquanto, os esquemas de cor incluem quatro cores cada, em que dois deles têm os mesmos valores. Além disso, ao criar outros controles, podemos precisar de mais cores.

Para encontrar facilmente uma cor apropriada no plano ou decidir sobre a adição de uma nova cor, a classe inclui um método que permite a visualização das cores - o método Show() (fig. 6). E também há o método reverso Hide() para apagar exemplos de cores a partir do gráfico.


Fig. 6. Visualização dos esquemas de cores utilizando o método Show()

O artigo tem ColorSchemesView.mq5 anexo. Ele é um Expert Advisor para ver esquemas de cores (ColorSchemesView.mq5).

Vamos modificar o método Init() na classe CInputBox ligeiramente. Substitua suas cores com as cores da classe ClrScheme:

m_BgColor=ClrScheme.Color(0);       // Background color of the text field
m_TxtColor=ClrScheme.Color(1);      // Font color and frame color of the text field
m_LblColor=ClrScheme.Color(2);     // Caption color
m_WarningColor=ClrScheme.Color(3); // Warning color

Este é o fim da criação de um controle e, agora, temos a base para o desenvolvimento de todos os outros controles.


7. Utilização do controle

Vamos criar o Expert Advisor e nomeá-lo GUITest; conectar o arquivo IncGUI.mqh:

#include 
Declare a classe CInputBox com o nome ib:
CInputBox ib;

No OnInit() da EA, chame o método Init() do objeto ib:

ib.Init("InpytBox",50,4,"input");

Faça o controle visível e defina uma posição para ele:

ib.Show(10,20);

Na função OnDeinit() da EA, exclua o controle:

ib.Hide(); 

Compilar e anexar o Expert Advisor a um gráfico. Você verá nosso controle (fig. 7).


Fig. 7. O controle InputBox

Adicione a possibilidade de mudar o esquema de cores para o Expert Advisor.

Neste momento, temos três esquemas de cores. Vamos fazer uma enumeração e uma variável externa para a escolha de um esquema de cores:

enum eColorScheme
  {
   DefaultScheme=0,
   YellowBrownScheme=1,
   BlueScheme=2
  };

input eColorScheme ColorScheme=DefaultScheme;

No início da função OnInit() do Expert Advisor, adicione definição de um esquema de cores:

ClrScheme.SetScheme(ColorScheme);

Agora, na janela de propriedades da EA, podemos escolher um dos três esquemas de cores (fig. 8).




Fig. 8. Esquemas de cores diferentes

Para lidar com o evento de especificar um novo valor, adicione o seguinte código na função OnChartEvent() do EA:

if(ib.Event(id,lparam,dparam,sparam)==1)
  {
   Alert("Entered value "+ib.ValueStrind());
  }

Agora, quando um novo valor é especificado no campo de edição, uma janela de mensagem informando sobre o valor especificado é aberta.

Forneça Expert Advisor com a possibilidade de criação de controles em uma subjanela.

Em primeiro lugar, crie um indicador de teste TestSubWindow (anexado no arquivo TestSubWindow.mq5). Ao criar o indicador no Assistente MQL5, especifique que ele deve trabalhar em uma subjanela separada. Adicione o seguinte código à função OnChartEvent() de EA:

if(CHARTEVENT_CHART_CHANGE)
  {
   ip.SetSubWindow("TestSubWindow");
  }

Agora, se o indicador não está no gráfico, o controle é criado na tabela de preços. Se você conectar o indicador ao gráfico, o controle passará para a subjanela (fig. 9). Se você excluir o indicador, o controle vai voltar para a tabela de preços.


Fig. 9. O controle na subjanela

Conclusão

Como resultado do trabalho feito, temos o arquivo de inclusão IncGUI.mqh que contém as seguintes classes: CGraphicObjectShell (criação e gestão de objetos gráficos), CWorkPiece (criação rápida de vários objetos gráficos com definição de suas propriedades utilizando parâmetros), CColorSchemes (definição de um esquema de cores e obtenção da cor do esquema de cores atual) e uma classe de controle - CInputBox.

As classes CGraphicObjectShell, CWorkPiece e CColorSchemes já estão declaradas no arquivo com os nomes de "g", "w" e "ClrScheme", ou seja, estão prontas para uso logo depois da conexão do arquivo IncGUI.mqh.

Vamos repetir como usar classe CInputBox:

  1. Conecte o arquivo IncGUI.mqh.
  2. Declare uma classe do tipo CInputBox.
  3. Chame o método Init().
  4. Defina as coordenadas utilizando o método SetPos(), ative a visibilidade usando Show(), se necessário. A segunda variante: ativar a visibilidade usando Show() com especificação de coordenadas.
  5. Se necessário ou no final do trabalho do Expert Advisor, oculte o controle usando o método Hide().
  6. Adicione a chamada do método Event() à função OnChartEvent().
  7. Se você precisa criar um controle em uma subjanela, forneça a função OnChartEvent() com uma chamada do método SetSubWindow() quando ocorre um evento CHARTEVENT_CHART_CHANGE.
  8. Para usar esquemas de cores, chame o método SetScheme() da classe ClrScheme antes de chamar o método Init().


Anexos

  • IncGUI.mqh - principal arquivo de inclusão. O arquivo deve ser colocado na pasta MQL5/Include da pasta de dados do terminal do cliente.
  • GUITest.mq5 - o Expert Advisor com um exemplo do controle CInputBox. O arquivo deve ser colocado na pasta MQL5/Experts da pasta de dados do terminal do cliente.
  • TestSubWindow.mq5 - o indicador para testar a função para a exibição de um controle em uma subjanela. O arquivo deve ser colocado na pasta MQL5/Indicators da pasta de dados do terminal do cliente.
  • ColorSchemesView.mq5 - o Expert Advisor para ver os esquemas de cores. Uma ferramenta auxiliar para a criação de controles. O arquivo deve ser colocado na pasta MQL5/Experts da pasta de dados do terminal do cliente.
  • IncGUImqh.chm - documentação para o arquivo IncGUI.mqh.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/310

Arquivos anexados |
Últimos Comentários | Ir para discussão (3)
Andriolo
Andriolo | 22 mar 2014 em 20:02

Não entendi o quarto princípio. Vocês poderiam citar algum exemplo? 

Rodrigo Malacarne
Rodrigo Malacarne | 23 mar 2014 em 02:03
LeoAndriolo:

Não entendi o quarto princípio. Vocês poderiam citar algum exemplo? 

Olá Leo, quando você se refere ao quarto princípio, você está se referindo exatamente ao quê? Aqui me consta que o tópico 4 desse artigo diz respeito a "Peças de trabalho para controles". Você poderia especificar um pouco mais sua dúvida?
Andriolo
Andriolo | 25 mar 2014 em 20:13
Malacarne:
Olá Leo, quando você se refere ao quarto princípio, você está se referindo exatamente ao quê? Aqui me consta que o tópico 4 desse artigo diz respeito a "Peças de trabalho para controles". Você poderia especificar um pouco mais sua dúvida?
1.1.4
Controles gráficos personalizados. Parte 2. Biblioteca de controles Controles gráficos personalizados. Parte 2. Biblioteca de controles
O segundo artigo da série "Controles gráficos personalizados" apresenta uma biblioteca para manusear os principais problemas que surgem da interação entre um programa (Expert Advisor, script, indicador) e um usuário. A biblioteca contém um grande número de classes (CInputBox, CSpinInputBox, CCheckBox, CRadioGroup, CVSсrollBar, CHSсrollBar, CList, CListMS, CComBox, CHMenu, CVMenu, CHProgress, CDialer, CDialerInputBox, CTable) e exemplos de seu uso.
MQL5 Wizard: nova versão MQL5 Wizard: nova versão
O artigo contém descrições dos novos recursos disponíveis no MQL5 Wizard. A arquitetura modificada dos sinais permite criar robôs de negócio com base na combinação de vários padrões de mercado. O exemplo contido no artigo explica o procedimento da criação interativa de um Expert Advisor.
Controles gráficos personalizados. Parte 3. Formas Controles gráficos personalizados. Parte 3. Formas
Este é o último dos três artigos dedicados a controles gráficos. Ele cobre a criação do principal componente da interface gráfica - a forma - e seu uso em combinação com outros controles. Além das classes de forma, as classes CFrame, CButton, CLabel foram adicionadas à biblioteca de controle.
Uma rápida inicialização ou um guia breve para iniciantes Uma rápida inicialização ou um guia breve para iniciantes
Olá, caro leitor! Neste artigo, tentaremos explicar e mostrar como você pode facilmente e rapidamente compreender os princípios da criação de Expert Advisors, trabalhar com indicadores, etc. Este artigo é destinado a iniciantes e não apresentará nenhuma dificuldade ou exemplos de difícil entendimento.