English Русский 中文 Español Deutsch 日本語
Interfaces Gráficas X: O controle Caixa de Texto Multilinha (build 8)

Interfaces Gráficas X: O controle Caixa de Texto Multilinha (build 8)

MetaTrader 5Exemplos | 29 março 2017, 09:56
2 302 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo

 

Introdução

A fim de obter uma melhor compreensão do propósito desta biblioteca, leia por favor o primeiro artigo: Interfaces Gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1). Você irã encontrar uma lista de artigos com os links no final de cada capítulo. Lá, você também pode encontrar e baixar a versão completa da biblioteca, no estágio de desenvolvimento atual. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.

Este artigo considera um novo controle: a Caixa de texto Multilinha. Ao contrário dos objetos gráficos do tipo OBJ_EDIT que é fornecido no terminal, a versão apresentada não terá restrições sobre o número de caracteres de entrada. Ele permite transformar a caixa de texto em um editor de texto simples. Ou seja, será possível introduzir várias linhas, e o cursor de texto será movido tanto pelo mouse quanto pelas teclas. Se as linhas sofrerem overflow na área visível do controle, aparecerá uma barra de rolagem. O controle da Caixa de texto Multilinha será totalmente renderizado, e ele terá uma qualidade tão próximo quanto possível do controle em sistemas operacionais.


Grupos de teclas e layouts do teclado

Antes de descrever o código do controle do tipo CTextBox (Caixa de texto), o teclado deve ser brevemente coberto, uma vez que ele irá ser o meio de entrada dos dados. Além disso, determinaremos quais teclas que pressionadas serão tratadas nesta primeira versão da classe de controle. 

As teclas do teclado podem ser divididas em vários grupos (ver notação na Fig. 1):

  • Teclas de controle (laranja)
  • Teclas de função (roxo)
  • Teclas alfanuméricas (azul)
  • Teclas de navegação (verde)
  • Teclado numérico (vermelho)

 Fig. 1. Grupos de teclas (layout do teclado QWERTY).

Fig. 1. Grupos de teclas (layout do teclado QWERTY).


Existem vários layouts do teclado latino para o idioma Inglês. O mais popular é o QWERTY. No nosso caso, a língua principal é o Russo, por isso usamos o esquema de idioma russo - ЙЦУКЕН. O esquema QWERTY foi deixado para o idioma Inglês, na qual é selecionado como um adicional. 

Começando com a build 1510, o idioma MQL inclui a função ::TranslateKey(). Ele pode ser usado para obter o caractere a partir do código transmitido através da tecla pressionada, o qual corresponde ao conjunto da língua e layout do sistema operacional. Anteriormente, os arrays de caracteres tiveram que ser gerado manualmente para cada idioma, o que causou dificuldades devido à grande quantidade de trabalho. Agora tudo está mais fácil.


Manipulação do evento tecla pressionada

Os eventos tecla pressionada podem ser rastreados na função ::OnChartEvent() do sistema usando o identificador CHARTEVENT_KEYDOWN:

//+------------------------------------------------------------------+
//| Função ChartEvent                                                |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- tecla pressionada 
   if(id==CHARTEVENT_KEYDOWN)
     {
      ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam,"; symbol: ",::ShortToString(::TranslateKey((int)lparam)));
      return;
     }
  }

Os valores seguintes vão para a função como os outros três parâmetros:

  • parâmetro long (lparam) - código da tecla pressionada, ou seja, o código do caractere ASCII ou o código de uma tecla de controle. 
  • parâmetro dparam (dparam) - o número gerado da tecla pressionada enquanto a tecla for mantida no estado pressionado. O valor é sempre igual a 1. Caso seja necessário obter o número da chamada no momento em que a tecla estava sendo pressionada, o cálculo é realizado de forma independente.
  • parâmetro sparam (sparam) - valor da string da máscara de bits, que descreve o estado das teclas do teclado. O evento é gerado imediatamente quando uma tecla for pressionada. Se a tecla for pressionada e liberada imediatamente, sem segurá-la, o valor do código de verificação será recebido aqui. Se a tecla for pressionada e mantida durante um tempo, um valor será gerado com base no código de verificação + 16384 BITS.

Por exemplo, a lista abaixo (saída no log do terminal) mostra o resultado de manter o pressionado a tecla Esc. O código desta chave é 27(lparam), o código de mapeamento no momento do pressionamento é 1 (sparam), e quando realizada durante cerca de 500 milissegundos, o terminal começa a gerar um valor de 16385 (código de verificação + 16384 bits).

2017.01.20 17:53:33.240 id: 0; lparam: 27; dparam: 1.0; sparam: 1
2017.01.20 17:53:33.739 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.772 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.805 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.837 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.870 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
...

Nem todas as teclas levam aos eventos com o identificador CHARTEVENT_KEYDOWN. Algumas delas são alocadas para as necessidades do terminal, e outros simplesmente não geram o evento de pressionamento de teclas. Eles são destacados em azul na figura abaixo:

Fig. 2. Teclas ocupadas pelo terminal, que não geram o evento CHARTEVENT_KEYDOWN. 

Fig. 2. Teclas ocupadas pelo terminal, que não geram o evento CHARTEVENT_KEYDOWN.


Códigos ASCII de caracteres e teclas de controle

Informações da Wikipedia (mais): 

ASCII, abreviação de American Standard Code for Information Interchange, é um padrão de codificação de caracteres. Os códigos ASCII representam o texto em computadores, equipamentos de telecomunicações, e outros dispositivos. A primeira edição da norma foi publicada em 1963.

A figura abaixo mostra os códigos ASCII das teclas do teclado:

 Fig. 3. Códigos ASCII de caracteres e teclas de controle.

Fig. 3. Códigos ASCII das teclas.


Todos os códigos ASCII foram colocados no arquivo KeyCodes.mqh sob a forma de macro de substituição (#define). Abaixo mostramos uma parte destes códigos:

//+------------------------------------------------------------------+
//|                                                     KeyCodes.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Códigos de caracteres ASCII e teclas de controle                 |
//| para manipular o evento tecla pressionada (parâmetro long do evento)  |
//+------------------------------------------------------------------+
#define KEY_BACKSPACE          8
#define KEY_TAB                9
#define KEY_NUMPAD_5           12
#define KEY_ENTER              13
#define KEY_SHIFT              16
#define KEY_CTRL               17
#define KEY_BREAK              19
#define KEY_CAPS_LOCK          20
#define KEY_ESC                27
#define KEY_SPACE              32
#define KEY_PAGE_UP            33
#define KEY_PAGE_DOWN          34
#define KEY_END                35
#define KEY_HOME               36
#define KEY_LEFT               37
#define KEY_UP                 38
#define KEY_RIGHT              39
#define KEY_DOWN               40
#define KEY_INSERT             45
#define KEY_DELETE             46
...

 


Código de mapeamento das teclas

Informações da Wikipedia (mais):  

Um scancode (ou código das teclas) são os dados que a maioria dos teclados de computador enviam a um computador para informar quais teclas foram pressionadas. Um número, ou sequência de números, é atribuído a cada tecla do teclado.

A figura abaixo mostra o código das teclas:

Fig. 4. Código das teclas. 

Fig. 4. Código das teclas.


Semelhante aos códigos ASCII, o código das teclas estão contidos no arquivo KeyCodes.mqh. Abaixo mostra uma parte da lista:

//+------------------------------------------------------------------+
//|                                                     KeyCodes.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Bit
#define KEYSTATE_ON            16384
//+------------------------------------------------------------------+
//| Código das teclas (parâmetro string do evento)                   |
//+------------------------------------------------------------------+
//| Pressionado uma vez: KEYSTATE_XXX                                |
//| Pressionado para baixo: KEYSTATE_XXX + KEYSTATE_ON               |
//+------------------------------------------------------------------+
#define KEYSTATE_ESC           1
#define KEYSTATE_1             2
#define KEYSTATE_2             3
#define KEYSTATE_3             4
#define KEYSTATE_4             5
#define KEYSTATE_5             6
#define KEYSTATE_6             7
#define KEYSTATE_7             8
#define KEYSTATE_8             9
#define KEYSTATE_9             10
#define KEYSTATE_0             11
//---
#define KEYSTATE_MINUS         12
#define KEYSTATE_EQUALS        13
#define KEYSTATE_BACKSPACE     14
#define KEYSTATE_TAB           15
//---
#define KEYSTATE_Q             16
#define KEYSTATE_W             17
#define KEYSTATE_E             18
#define KEYSTATE_R             19
#define KEYSTATE_T             20
#define KEYSTATE_Y             21
#define KEYSTATE_U             22
#define KEYSTATE_I             23
#define KEYSTATE_O             24
#define KEYSTATE_P             25
...

 


Classe auxiliar para trabalhar com o teclado

Para um trabalho mais conveniente com o teclado, a classe CKeys foi implementada. Ele está contido na classe Keys.mqh, e inclui o arquivo KeyCodes.mqh com todos os códigos das teclas e dos caracteres. 

//+------------------------------------------------------------------+
//|                                                         Keys.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\KeyCodes.mqh>
//+------------------------------------------------------------------+
//| Classe para trabalhar com o teclado                              |
//+------------------------------------------------------------------+
class CKeys
  {
public:
                     CKeys(void);
                    ~CKeys(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CKeys::CKeys(void)
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CKeys::~CKeys(void)
  {
  }

Para determinar uma tecla pressionada com:

(1) caracteres alfanuméricos (incluindo Espaço)

(2) caracteres do teclado numérico

ou (3) um caractere especial,

deve-se usar o método CKeys::KeySymbol(). É passado um valor do parâmetro long do evento com o identificador CHARTEVENT_KEYDOWN, ele irá retornar um caractere no formato de string ou uma string vazia (''), caso a tecla pressionada não pertença aos intervalos especificados. 

class CKeys
  {
public:
   //--- Retorna o caractere da tecla pressionada
   string            KeySymbol(const long key_code);
  };
//+------------------------------------------------------------------+
//| Retorna o caractere da tecla pressionada                         |
//+------------------------------------------------------------------+
string CKeys::KeySymbol(const long key_code)
  {
   string key_symbol="";
//--- Se for necessário introduzir um espaço (tecla Espaço)
   if(key_code==KEY_SPACE)
     {
      key_symbol=" ";
     }
//--- Se for necessário entrar com (1) um caractere alfabético, ou (2) um caractere do teclado numérico, ou (3) um caractere especial
   else if((key_code>=KEY_A && key_code<=KEY_Z) ||
           (key_code>=KEY_0 && key_code<=KEY_9) ||
           (key_code>=KEY_SEMICOLON && key_code<=KEY_SINGLE_QUOTE))
     {
      key_symbol=::ShortToString(::TranslateKey((int)key_code));
     }
//--- Retornar o caractere
   return(key_symbol);
  }

E, finalmente, será necessário um método para determinar o estado atual da tecla Ctrl. Ele será utilizado em várias combinações de pressionamento simultâneo de duas teclas ao mover o cursor de texto na caixa de texto.

Para obter o estado atual da tecla Ctrl, use a função do sistema do terminal ::TerminalInfoInteger(). Esta função tem vários identificadores para detectar o estado atual das teclas. O identificador TERMINAL_KEYSTATE_CONTROL é destinado à tecla Ctrl. Todos os outros identificadores deste tipo podem ser encontrados na linguagem de referência MQL5.

É muito fácil determinar se uma tecla está pressionada usando os identificadores. Se uma tecla é pressionada, o valor de retorno será menor que zero

class CKeys
  {
public:
   //--- Retorna o estado da tecla Ctrl
   bool              KeyCtrlState(void);
  };
//+------------------------------------------------------------------+
//| Retorna o estado da tecla Ctrl                                   |
//+------------------------------------------------------------------+
bool CKeys::KeyCtrlState(void)
  {
   return(::TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)<0);
  }

Now, everything is ready for the creating the Text box control. 

 


O controle Caixa de Texto Multilinha

O controle Caixa de Texto Multilinha também pode ser usado em controles combinados. Ele pertence ao grupo dos controles compostos, uma vez que contém barras de rolagem. Além disso, o controle Caixa de Texto Multilinha pode ser usado tanto para a entrada de texto quanto para a exibição de texto que foi salvo anteriormente em um arquivo.

Os controles com caixas de edição para a entrada de valores numéricos (a classe CSpinEdit) ou texto personalizado (CTextEdit) foram considerados anteriormente. Eles usaram um objeto gráfico do tipo OBJ_EDIT. Ele tem uma séria limitação: somente 63 caracteres podem ser inseridos, e eles devem caber em uma única linha também. Portanto, a tarefa atual é a de criar uma caixa de edição de texto sem tais limitações.  


 

Fig. 5. O controle Caixa de Texto Multilinha

Agora vamos dar uma olhada mais de perto na classe CTextBox para criar esse controle.

 

Desenvolvimento da classe CTextBox para criação do controle

Criar o arquivo TextBox.mqh com a classe CTextBox que tem métodos padrão para todos os controles da biblioteca e incluir os seguintes arquivos nela:

  • Com a classe base de controles — Element.mqh.
  • Com as classes da barra de rolagem — Scrolls.mqh.
  • Com a classe para trabalhar com o teclado — Keys.mqh.
  • Com a classe para trabalhar com o contador de tempo — TimeCounter.mqh.m
  • Com a classe para trabalhar com o gráfico, onde a MQL está localizada — Chart.mqh
//+------------------------------------------------------------------+
//|                                                      TextBox.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Scrolls.mqh"
#include "..\Keys.mqh"
#include "..\Element.mqh"
#include "..\TimeCounter.mqh"
#include <Charts\Chart.mqh>
//+------------------------------------------------------------------+
//| Classe para a criação de uma caixa de texto multilinha           |
//+------------------------------------------------------------------+
class CTextBox : public CElement
  {
private:
   //--- Instância da classe para trabalhar com o teclado
   CKeys             m_keys;
   //--- Instância de classe para gerenciar o gráfico
   CChart            m_chart;
   //--- Instância da classe para trabalhar com o contador de tempo
   CTimeCounter      m_counter;
   //---
public:
                     CTextBox(void);
                    ~CTextBox(void);
   //--- Manipulador de eventos do gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Move o elemento
   virtual void      Moving(const int x,const int y,const bool moving_mode=false);
   //--- (1) Exibe, (2) oculta, (3) reseta, (4) remove
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Definir (2), resetar as prioridades para o clique esquerdo do mouse
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Zera a cor
   virtual void      ResetColors(void) {}
   //---
private:
   //--- Altera a largura da margem direita da janela
   virtual void      ChangeWidthByRightWindowSide(void);
   //--- Altera a altura na borda inferior da janela
   virtual void      ChangeHeightByBottomWindowSide(void);
  };



Propriedades e aparência

Uma estrutura será necessário, chame ela de KeySymbolOptions, com arrays de caracteres e suas propriedades. Na versão atual, ela irá conter dois arrays dinâmicos: 

  • O m_symbol[] irá conter todos os caracteres de um texto, separadamente.
  • O array m_width[] irá conter a largura de todos os caracteres da string, separadamente.

Uma instância dessa classe também será declarada como um array dinâmico. O seu tamanho será sempre igual ao número de linhas na caixa de texto.

class CTextBox : public CElement
  {
private:
   //--- Caracteres e suas propriedades
   struct KeySymbolOptions
     {
      string            m_symbol[]; // Characters
      int               m_width[];  // Largura dos caracteres
     };
   KeySymbolOptions  m_lines[];
  };

Na primeira versão do controle, o texto será emitido como linhas inteiras. Portanto, antes de uma linha ser uma saída, ela precisa ser colocada junto do array m_symbol[]. O método CTextBox::CollectString() serve para este propósito, que precisa ser passado para o índice da linha:

class CTextBox : public CElement
  {
private:
   //--- Variável para trabalhar com uma string
   string            m_temp_input_string;
   //---
private:
   //--- Constrói uma string de caracteres
   string            CollectString(const uint line_index);
  };
//+------------------------------------------------------------------+
//| Constrói uma string de caracteres                                |
//+------------------------------------------------------------------+
string CTextBox::CollectString(const uint line_index)
  {
   m_temp_input_string="";
   uint symbols_total=::ArraySize(m_lines[line_index].m_symbol);
   for(uint i=0; i<symbols_total; i++)
      ::StringAdd(m_temp_input_string,m_lines[line_index].m_symbol[i]);
//---
   return(m_temp_input_string);
  }

Em seguida, enumere as propriedades da caixa de edição de texto, que pode ser usada para personalizar a aparência desse controle, bem como o seu estado e os modos que ela pode trabalhar em:

  • Cor de fundo em diferentes estados
  • Cor do texto em diferentes estados
  • Cor da moldura em diferentes estados
  • Texto padrão
  • Cor padrão do texto
  • Modo Multilinha
  • Modo somente leitura
class CTextBox : public CElement
  {
private:
   //--- Cor de fundo
   color             m_area_color;
   color             m_area_color_locked;
   //--- Cor do texto
   color             m_text_color;
   color             m_text_color_locked;
   //--- Cor do quadro
   color             m_border_color;
   color             m_border_color_hover;
   color             m_border_color_locked;
   color             m_border_color_activated;
   //--- Texto padrão
   string            m_default_text;
   //--- Cor padrão de texto
   color             m_default_text_color;
   //--- Modo multilinha
   bool              m_multi_line_mode;
   //--- Modo somente leitura
   bool              m_read_only_mode;
   //---
public:
   //--- Cor de fundo em diferentes estados
   void              AreaColor(const color clr)                { m_area_color=clr;                 }
   void              AreaColorLocked(const color clr)          { m_area_color_locked=clr;          }
   //--- Cor do texto em diferentes estados
   void              TextColor(const color clr)                { m_text_color=clr;                 }
   void              TextColorLocked(const color clr)          { m_text_color_locked=clr;          }
   //--- Cores da moldura em diferentes estados
   void              BorderColor(const color clr)              { m_border_color=clr;               }
   void              BorderColorHover(const color clr)         { m_border_color_hover=clr;         }
   void              BorderColorLocked(const color clr)        { m_border_color_locked=clr;        }
   void              BorderColorActivated(const color clr)     { m_border_color_activated=clr;     }
   //--- (1) Texto padrão e (2) cor do texto padrão
   void              DefaultText(const string text)            { m_default_text=text;              }
   void              DefaultTextColor(const color clr)         { m_default_text_color=clr;         }
   //--- (1) Modo Multilinha, (2) modo somente leitura
   void              MultiLineMode(const bool mode)            { m_multi_line_mode=mode;           }
   bool              ReadOnlyMode(void)                  const { return(m_read_only_mode);         }
   void              ReadOnlyMode(const bool mode)             { m_read_only_mode=mode;            }
  };

A caixa de texto em si (fundo, texto, moldura e cursor de texto piscando) será completamente desenhado em um único objeto gráfico do tipo OBJ_BITMAP_LABEL. Em essência, esta é apenas uma imagem. Ele vai ser redesenhado em dois casos:

  • quando estiver interagindo com o controle
  • em um intervalo de tempo especificado, para o cursor piscar quando a caixa de texto for ativada. 

Quando o cursor do mouse passa na área da caixa de texto, seu quadro irá mudar de cor. A fim de evitar o redesenho da imagem com demasiada frequência, é necessário controlar o momento em que o cursor cruza o quadro da caixa de texto. Ou seja, o controle deve ser redesenhado apenas uma vez, no momento em que o cursor entra ou sai da área da caixa de texto. Para estes fins, os métodos CElementBase::IsMouseFocus() foram adicionados à classe base do controle. Eles são usados ​​para definir e obter o sinalizador, que indica uma travessia:

//+------------------------------------------------------------------+
//| Classe base do controle                                          |
//+------------------------------------------------------------------+
class CElementBase
  {
protected:
   //--- Para determinar o momento em que o cursor do mouse cruza as fronteiras do controle
   bool              m_is_mouse_focus;
   //---
public:
   //--- O momento de entrar/sair o foco do controle
   bool              IsMouseFocus(void)                        const { return(m_is_mouse_focus);             }
   void              IsMouseFocus(const bool focus)                  { m_is_mouse_focus=focus;               }
  };

Para o código ser simples e legível, os métodos simples adicionais foram implementadas dentro dele, o que ajuda a obter a cor de fundo de caixa de texto, moldura e texto em relação ao estado atual do controle: 

class CTextBox : public CElement
  {
private:
   //--- Retorna a cor de fundo atual
   uint              AreaColorCurrent(void);
   //--- Retorna a cor do texto atual
   uint              TextColorCurrent(void);
   //--- Retorna a cor do quadro atual
   uint              BorderColorCurrent(void);
  };
//+------------------------------------------------------------------+
//| Retorna a cor de fundo em relação ao estado atual do controle    |
//+------------------------------------------------------------------+
uint CTextBox::AreaColorCurrent(void)
  {
   uint clr=::ColorToARGB((m_text_box_state)? m_area_color : m_area_color_locked);
//--- Retorna a cor
   return(clr);
  }
//+------------------------------------------------------------------+
//| Retorna a cor do texto em relação ao estado atual do controle    |
//+------------------------------------------------------------------+
uint CTextBox::TextColorCurrent(void)
  {
   uint clr=::ColorToARGB((m_text_box_state)? m_text_color : m_text_color_locked);
//--- Retorna a cor
   return(clr);
  }
//+------------------------------------------------------------------+
//| Retorna o quadro da cor em relação ao estado atual do controle   |
//+------------------------------------------------------------------+
uint CTextBox::BorderColorCurrent(void)
  {
   uint clr=clrBlack;
//--- Se o elemento não está bloqueado
   if(m_text_box_state)
     {
      //--- Se a caixa de texto está ativada
      if(m_text_edit_state)
         clr=m_border_color_activated;
      //--- Se não foi ativada, verifica o foco do controle
      else
         clr=(CElementBase::IsMouseFocus())? m_border_color_hover : m_border_color;
     }
//--- Se o controle está bloqueado
   else
      clr=m_border_color_locked;
//--- Retorna a cor
   return(::ColorToARGB(clr));
  }

Em muitos métodos da classe, ele será necessário para obter o valor da altura da linha da caixa de texto em pixels em relação à fonte especificada e o seu tamanho. Para estes fins, utilize o método CTextBox::LineHeight():

class CTextBox : public CElement
  {
private:
   //--- Retorna a altura da linha
   uint              LineHeight(void);
  };
//+------------------------------------------------------------------+
//| Retorna a altura da linha                                        |
//+------------------------------------------------------------------+
uint CTextBox::LineHeight(void)
  {
//--- Define a fonte a ser exibida na tela (necessário para obter a altura da linha)
   m_canvas.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Retorna a altura da linha
   return(m_canvas.TextHeight("|"));
  }

Agora, considere os métodos para desenhar o controle. Comece com o método CTextBox::DrawBorder() concebido para desenhar a borda da caixa de texto. Se o tamanho total da caixa de texto for maior do que a sua parte visível, a área de visibilidade pode ser compensada (utilizando as barras de posicionamento ou cursor). Assim sendo, o quadro deve ser desenhado com a consideração de tais compensações

class CTextBox : public CElement
  {
private:
   //--- Desenha o quadro
   void              DrawBorder(void);
  };
//+------------------------------------------------------------------+
//| Desenha o quadro da caixa de texto                               |
//+------------------------------------------------------------------+
void CTextBox::DrawBorder(void)
  {
//--- Obtém o quadro da cor em relação ao estado atual do controle
   uint clr=BorderColorCurrent();
//--- Obtém o deslocamento ao longo do eixo X
   int xo=(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
   int yo=(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Limites
   int x_size =m_canvas.X_Size()-1;
   int y_size =m_canvas.Y_Size()-1;
//--- Coordenadas: topo/direita/fundo/esquerda
   int x1[4]; x1[0]=x;         x1[1]=x_size+xo; x1[2]=xo;        x1[3]=x;
   int y1[4]; y1[0]=y;         y1[1]=y;         y1[2]=y_size+yo; y1[3]=y;
   int x2[4]; x2[0]=x_size+xo; x2[1]=x_size+xo; x2[2]=x_size+xo; x2[3]=x;
   int y2[4]; y2[0]=y;         y2[1]=y_size+yo; y2[2]=y_size+yo; y2[3]=y_size+yo;
//--- Desenha o quadro de coordenadas especificadas
   for(int i=0; i<4; i++)
      m_canvas.Line(x1[i],y1[i],x2[i],y2[i],clr);
  }

O método CTextBox::DrawBorder() também irá ser utilizado no método CTextBox::ChangeObjectsColor(), quando for necessário mudar simplesmente a cor do quadro da caixa de texto quando ele é passado pelo cursor do mouse (veja o código abaixo). Para fazer isso, simplesmente redesenhe o quadro (e não toda a caixa de texto) e atualize a imagem. O CTextBox::ChangeObjectsColor() será chamado dentro do processador de eventos do controle. Este é o local onde o ato do cursor do mouse cruzar as bordas do controle é controlado a fim de evitar redesenhos de forma demasiada.

class CTextBox : public CElement
  {
private:
   //--- Altera as cores do objeto
   void              ChangeObjectsColor(void);
  };
//+------------------------------------------------------------------+
//| Altera as cores do objeto                                        |
//+------------------------------------------------------------------+
void CTextBox::ChangeObjectsColor(void)
  {
//--- Se não estiver em foco
   if(!CElementBase::MouseFocus())
     {
      //--- Se ainda não indicou que não está no foco
      if(CElementBase::IsMouseFocus())
        {
         //--- Define o sinalizador
         CElementBase::IsMouseFocus(false);
         //--- Altera a cor
         DrawBorder();
         m_canvas.Update();
        }
     }
   else
     {
      //--- Se ainda não indicou que esta em foco
      if(!CElementBase::IsMouseFocus())
        {
         //--- Define o sinalizador
         CElementBase::IsMouseFocus(true);
         //--- Altera a cor
         DrawBorder();
         m_canvas.Update();
        }
     }
  }

O método CTextBox::TextOut() destina-se a saída de texto para uma tela. Aqui, no início, a tela é apagada através do preenchimento da cor especificada. Em seguida, o programa pode ir de duas maneiras:

  • Se o modo multilinha está desativado, e ao mesmo tempo não há nenhum caractere na linha, o texto padrão deve ser exibido (se especificado). Ele será exibido no centro da caixa de edição.
  • Se o modo de várias linhas está desativado ou se a linha contém pelo menos um caractere, obtenha a altura da linha e exiba todas as linhas em um loop, construa-os a partir do array de caracteres primeiro. Os deslocamentos de texto a partir do canto superior esquerdo da área de caixa de texto são definidos por padrão. Esses são 5 pixels ao longo do eixo X, e 4 pixels ao longo do eixo Y. Estes valores podem ser substituídos usando os métodos CTextBox::TextXOffset() e CTextBox::TextYOffset()
class CTextBox : public CElement
  {
private:
   //--- Deslocamentos do texto a partir das bordas da caixa de texto
   int               m_text_x_offset;
   int               m_text_y_offset;
   //---
public:
   //--- Deslocamentos do texto a partir das bordas da caixa de texto
   void              TextXOffset(const int x_offset)           { m_text_x_offset=x_offset;         }
   void              TextYOffset(const int y_offset)           { m_text_y_offset=y_offset;         }
   //---
private:
   //--- Sáida de Texto para a tela
   void              TextOut(void);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CTextBox::CTextBox(void) : m_text_x_offset(5),
                           m_text_y_offset(4)
  {
...
  }
//+------------------------------------------------------------------+
//| Saída de texto para a tela                                       |
//+------------------------------------------------------------------+
void CTextBox::TextOut(void)
  {
//--- Limpa a tela
   m_canvas.Erase(AreaColorCurrent());
//--- Obtém o tamanho do conjunto de caracteres
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Se o modo multilinha é ativado ou se o número de caracteres é maior do que zero
   if(m_multi_line_mode || symbols_total>0)
     {
      //--- Obtém a altura da linha
      int line_height=(int)LineHeight();
      //--- Obtém o tamanho do array de linhas
      uint lines_total=::ArraySize(m_lines);
      //---
      for(uint i=0; i<lines_total; i++)
        {
         //--- Obtém as coordenadas para o texto
         int x=m_text_x_offset;
         int y=m_text_y_offset+((int)i*line_height);
         //--- Constrói uma string a partir do array de caracteres
         CollectString(i);
         //--- Desenha o texto
         m_canvas.TextOut(x,y,m_temp_input_string,TextColorCurrent(),TA_LEFT);
        }
     }
//--- Se o modo multilinha está desativado e não há nenhum caractere, o texto padrão será exibido
   else
     {
      //--- Desenha o texto, se especificado
      if(m_default_text!="")
         m_canvas.TextOut(m_area_x_size/2,m_area_y_size/2,m_default_text,::ColorToARGB(m_default_text_color),TA_CENTER|TA_VCENTER);
     }
  }

Para desenhar o cursor de texto, os métodos de cálculo das suas coordenadas serão necessários. Para calcular a coordenada X, é necessário especificar o índice da linha e o índice do caractere onde o cursor está a ser colocado. Isto é feito usando o método CTextBox::LineWidth(): Uma vez que a largura de cada caractere é armazenado no array dinâmico m_width[] da estrutura KeySymbolOptions, só resta somar a largura dos caracteres até a posição especificada

class CTextBox : public CElement
  {
private:
   //--- Retorna a largura da linha em pixels
   uint              LineWidth(const uint line_index,const uint symbol_index);
  };
//+------------------------------------------------------------------+
//| Retorna a largura da linha do começo para a posição especificada |
//+------------------------------------------------------------------+
uint CTextBox::LineWidth(const uint line_index,const uint symbol_index)
  {
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
//--- Prevenção para exceder o tamanho do array
   uint l=(line_index<lines_total)? line_index : lines_total-1;
//--- Obtém o tamanho do array de caracteres para a linha especificada
   uint symbols_total=::ArraySize(m_lines[l].m_width);
//--- Prevenção para exceder o tamanho do array
   uint s=(symbol_index<symbols_total)? symbol_index : symbols_total;
//--- Soma a largura de todos os caracteres
   uint width=0;
   for(uint i=0; i<s; i++)
      width+=m_lines[l].m_width[i];
//--- Retorna a largura da linha
   return(width);
  }

Os métodos para obter as coordenadas do cursor de texto assumem uma forma muito simples (veja o código abaixo). As coordenadas são armazenadas nos campos m_text_cursor_x e m_text_cursor_y. O cálculo das coordenadas usa a posição atual do cursor, bem como os índices da linha e dos caracteres onde o cursor está para ser movido. Os campos m_text_cursor_x_pos e m_text_cursor_y_pos são destinados para armazenar esses valores.

class CTextBox : public CElement
  {
private:
   //--- Coordenada atual do cursor de texto
   int               m_text_cursor_x;
   int               m_text_cursor_y;
   //--- Posição atual do cursor de texto
   uint              m_text_cursor_x_pos;
   uint              m_text_cursor_y_pos;
   //---
private:
   //--- Cálculo de coordenadas para o cursor de texto
   void              CalculateTextCursorX(void);
   void              CalculateTextCursorY(void);
  };
//+------------------------------------------------------------------+
//| Cálculo da coordenada X para o cursor de texto                   |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextCursorX(void)
  {
//--- Obtém a largura da linha
   int line_width=(int)LineWidth(m_text_cursor_x_pos,m_text_cursor_y_pos);
//--- Calcula e armazena a coordenada X do cursor
   m_text_cursor_x=m_text_x_offset+line_width;
  }
//+------------------------------------------------------------------+
//| Cálculo da coordenada Y para o cursor de texto                   |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextCursorY(void)
  {
//--- Obtém a altura da linha
   int line_height=(int)LineHeight();
//--- Obtém a coordenada Y do cursor
   m_text_cursor_y=m_text_y_offset+int(line_height*m_text_cursor_y_pos);
  }

Tudo está pronto para a implementação do método CTextBox::DrawCursor() para desenhar o cursor de texto. Em muitos outros editores de texto, pode-se perceber que o cursor de texto se sobrepõe parcialmente aos pixels de alguns caracteres. Pode-se ver que o cursor de texto não se limita a obstrui-los. Os pixels cobertos do caractere são desenhados em uma cor diferente. Isto é feito para manter a legibilidade do caractere. 

Por exemplo, a imagem abaixo mostra o 'd' e os caracteres 'д' em um editor de texto sobreposto e não sobreposto pelo cursor.

 Fig. 6. Exemplo do cursor de texto sobrepondo os pixels do caractere 'd'.

Fig. 6. Exemplo do cursor de texto sobrepondo os pixels do caractere 'd'.

 Fig. 7. Exemplo de cursor de texto sobrepondo os pixels do caráter "д".

Fig. 7. Exemplo de cursor de texto sobrepondo os pixels do caráter "д".


Para o cursor e o caractere sobreposto para visível sobre um fundo de qualquer cor em todos os momentos, é suficiente para inverter as cores dos pixels sobrepostos pelo cursor. 

Agora, considere o método CTextBox::DrawCursor() para desenhar o cursor de texto. A largura do cursor será igual a um pixel, e sua altura irá coincidir com a altura da linha. Logo no início, obtenha a coordenada X a qual desenha-se o cursor, e a altura da linha. A coordenada Y será calculada em um loop, uma vez que ela irá ser desenhada em uma base per-pixel. Lembre-se, uma instância da classe CColors para trabalhar com cores foi previamente declarada na classe base de controles CElementBase. Portanto, a cor do pixel atual nas coordenadas especificadas é agora obtida em cada iteração após o cálculo da coordenada Y. Então o método CColors::Negative() inverte a cor e define para o mesmo lugar

class CTextBox : public CElement
  {
private:
   //--- Desenha o cursor de texto
   void              DrawCursor(void);
  };
//+------------------------------------------------------------------+
//| Desenha o cursor de texto                                        |
//+------------------------------------------------------------------+
void CTextBox::DrawCursor(void)
  {
//--- Obtém a altura da linha
   int line_height=(int)LineHeight();
//--- Obtém a coordenada X do cursor
   CalculateTextCursorX();
//--- Desenha o cursor de texto
   for(int i=0; i<line_height; i++)
     {
      //--- Obtém a coordenada Y do pixel
      int y=m_text_y_offset+((int)m_text_cursor_y_pos*line_height)+i;
      //--- Obtém a cor atual do pixel
      uint pixel_color=m_canvas.PixelGet(m_text_cursor_x,y);
      //--- Inverte a cor para o cursor
      pixel_color=m_clr.Negative((color)pixel_color);
      m_canvas.PixelSet(m_text_cursor_x,y,::ColorToARGB(pixel_color));
     }
  }

Dois métodos foram implementados para desenhar a caixa de texto com texto: CTextBox::DrawText() e CTextBox::DrawTextAndCursor(). 

O método CTextBox::DrawText() é para ser usado quando for necessário apenas atualizar o texto numa caixa de texto inativa. Tudo é simples aqui. Se o controle não está oculto, exibe o texto, desenha o quadro e atualiza a imagem.  

class CTextBox : public CElement
  {
private:
   //--- Desenha o texto
   void              DrawText(void);
  };
//+------------------------------------------------------------------+
//| Desenha o texto                                                  |
//+------------------------------------------------------------------+
void CTextBox::DrawText(void)
  {
//--- Sai, se o controle está oculto
   if(!CElementBase::IsVisible())
      return;
//--- Saída do texto
   CTextBox::TextOut();
//--- Desenha o quadro
   DrawBorder();
//--- Atualiza a caixa de texto
   m_canvas.Update();
  }

Se a caixa de texto está ativa, além do texto, é necessário apresentar um cursor de texto intermitente - o método CTextBox::DrawTextAndCursor(). Para piscar é necessário determinar o estado de mostrar/ocultar do cursor. Toda vez que este método é chamado, o estado será alterado para o oposto. Ele também fornece a capacidade de forçar o visor quando o valor true (o argumento show_state) é passado para o método. O visor forçado será exigido ao mover o cursor na caixa de texto enquanto ele estiver ativo. Na verdade, o piscar do cursor será levado a cabo num timer de controle no intervalo especificado no construtor da classe do contador de tempo. Aqui, o seu valor é de 200 milissegundos. O contador deve ser reposto toda vez depois da chamada do método CTextBox::DrawTextAndCursor(). 

class CTextBox : public CElement
  {
private:
   //--- Mostra o texto e o cursor piscando
   void              DrawTextAndCursor(const bool show_state=false);
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CTextBox::CTextBox(void)
  {
//--- Definindo os parâmetros de configuração para o contador de tempo
   m_counter.SetParameters(16,200);
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CTextBox::OnEventTimer(void)
  {
...
//--- Pausa entre as atualizações do cursor de texto
   if(m_counter.CheckTimeCounter())
     {
      //--- Actualiza o cursor de texto se o controle é visível e a caixa de texto é ativada
      if(CElementBase::IsVisible() && m_text_edit_state)
         DrawTextAndCursor();
     }
  }
//+------------------------------------------------------------------+
//| Exibe o texto e o cursor piscando                                |
//+------------------------------------------------------------------+
void CTextBox::DrawTextAndCursor(const bool show_state=false)
  {
//--- Determina o estado para o cursor de texto (Show/Hide)
   static bool state=false;
   state=(!show_state)? !state : show_state;
//--- Saída do texto
   CTextBox::TextOut();
//--- Desenha o cursor de texto
   if(state)
      DrawCursor();
//--- Desenha o quadro
   DrawBorder();
//--- Atualiza a caixa de texto
   m_canvas.Update();
//--- Reinicia o contador
   m_counter.ZeroTimeCounter();
  }

Para criar o controle da Caixa de Texto Multilinha, será necessário três métodos privados, dois dos quais são necessários para criar as barras de rolagem, e um método público para chamadas externas em uma classe personalizada: 

class CTextBox : public CElement
  {
private:
   //--- Objetos para criar o controle
   CRectCanvas       m_canvas;
   CScrollV          m_scrollv;
   CScrollH          m_scrollh;
   //---
public:
   //--- Métodos para criar o controle
   bool              CreateTextBox(const long chart_id,const int subwin,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateCanvas(void);
   bool              CreateScrollV(void);
   bool              CreateScrollH(void);
   //---
public:
   //--- Retorna os ponteiros para as barras de rolagem
   CScrollV         *GetScrollVPointer(void)                   { return(::GetPointer(m_scrollv));  }
   CScrollH         *GetScrollHPointer(void)                   { return(::GetPointer(m_scrollh));  }
  };

Antes de chamar o método CTextBox::CreateCanvas() para criar a caixa de texto, é necessário calcular o seu tamanho. Será aplicado aqui um método semelhante ao implementado na tabela renderizada do tipo CCanvasTable. Vamos passar por isso brevemente. Não é o tamanho total da imagem, e não é o tamanho da sua parte visível. O tamanho de um controle é igual ao tamanho da parte visível da imagem. Ao mover o cursor de texto ou barras de rolagem, as coordenadas da imagem irão mudar, enquanto que as coordenadas da parte visível (que são também as coordenadas de controle) permanecem o mesmo.

O tamanho ao longo do eixo Y pode ser calculado através da multiplicação do número de linhas por sua altura. As margens das bordas da caixa de texto e o tamanho da barra de rolagem também são considerados aqui. Para calcular o tamanho ao longo do eixo X, é necessário conhecer a largura máxima da linha de toda o array. Isto é feito usando o método CTextBox::MaxLineWidth(). Aqui, ele itera sobre o array de linhas num ciclo, armazena toda a largura da linha, se for maior do que o anterior, e retorna o valor.

class CTextBox : public CElement
  {
private:
   //--- Retorna a largura máxima da linha
   uint              MaxLineWidth(void);
  };
//+------------------------------------------------------------------+
//| Retorna a largura máxima da linha                                |
//+------------------------------------------------------------------+
uint CTextBox::MaxLineWidth(void)
  {
   uint max_line_width=0;
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
   for(uint i=0; i<lines_total; i++)
     {
      //--- Obtém o tamanho do conjunto de caracteres
      uint symbols_total=::ArraySize(m_lines[i].m_symbol);
      //--- Obtém a largura da linha
      uint line_width=LineWidth(symbols_total,i);
      //--- Armazena a largura máxima
      if(line_width>max_line_width)
         max_line_width=line_width;
     }
//--- Retorna a largura máxima da linha
   return(max_line_width);
  }

O código do método CTextBox::CalculateTextBoxSize() para calcular os tamanhos do controlo é mostrado abaixo. Este método também irá ser chamado de dentro do métodos CTextBox::ChangeWidthByRightWindowSide() e CTextBox::ChangeHeightByBottomWindowSide(). O objetivo desses métodos é redimensionar automaticamente o controle de acordo com o tamanho do formulário, se essas propriedades são definidas pelo desenvolvedor. 

class CTextBox : public CElement
  {
private:
   //--- Tamanho total e tamanho da parte visível do controle
   int               m_area_x_size;
   int               m_area_y_size;
   int               m_area_visible_x_size;
   int               m_area_visible_y_size;
   //---
private:
   //--- Calcula a largura da caixa de texto
   void              CalculateTextBoxSize(void);
  };
//+------------------------------------------------------------------+
//| Calcula o tamanho da caixa de texto                              |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextBoxSize(void)
  {
//--- Obtém a largura máxima da linha da caixa de texto
   int max_line_width=int((m_text_x_offset*2)+MaxLineWidth()+m_scrollv.ScrollWidth());
//--- Determina a largura total
   m_area_x_size=(max_line_width>m_x_size)? max_line_width : m_x_size;
//--- Determina a largura visível
   m_area_visible_x_size=m_x_size;
//--- Obtém a altura da linha
   int line_height=(int)LineHeight();
//--- Obtém o tamanho do array de linhas
   int lines_total=::ArraySize(m_lines);
//--- Calcula a altura total do controle
   int lines_height=int((m_text_y_offset*2)+(line_height*lines_total)+m_scrollh.ScrollWidth());
//--- Determina a altura total
   m_area_y_size=(m_multi_line_mode && lines_height>m_y_size)? lines_height : m_y_size;
//--- Determina a altura visível
   m_area_visible_y_size=m_y_size;
  }

Os tamanhos foram calculados. Agora, eles devem ser aplicados. Isto é feito usando o método CTextBox::ChangeTextBoxSize(). Aqui, os argumentos do método especificam se é necessário mudar a área de visibilidade para o início ou deixá-lo na mesma posição. Além disso, este método redimensiona as barras de rolagem e realiza o último ajustamento da área de visibilidade em relação aos polegares da barra de rolagem. O código desses métodos não serão cobertos aqui, porque já foi descrito um caso similar em artigos anteriores. 

class CTextBox : public CElement
  {
private:
   //--- Redimensiona a caixa de texto
   void              ChangeTextBoxSize(const bool x_offset=false,const bool y_offset=false);
  };
//+------------------------------------------------------------------+
//| Redimensiona a caixa de texto                                    |
//+------------------------------------------------------------------+
void CTextBox::ChangeTextBoxSize(const bool is_x_offset=false,const bool is_y_offset=false)
  {
//--- Redimensiona a tabela
   m_canvas.XSize(m_area_x_size);
   m_canvas.YSize(m_area_y_size);
   m_canvas.Resize(m_area_x_size,m_area_y_size);
//--- Define o tamanho da área visível
   m_canvas.SetInteger(OBJPROP_XSIZE,m_area_visible_x_size);
   m_canvas.SetInteger(OBJPROP_YSIZE,m_area_visible_y_size);
//--- Diferença entre a largura total e a área visível
   int x_different=m_area_x_size-m_area_visible_x_size;
   int y_different=m_area_y_size-m_area_visible_y_size;
//--- Ajusta o deslocamento do quadro dentro da imagem ao longo do eixos X e Y
   int x_offset=(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
   m_canvas.SetInteger(OBJPROP_XOFFSET,(!is_x_offset)? 0 : (x_offset<=x_different)? x_offset : x_different);
   m_canvas.SetInteger(OBJPROP_YOFFSET,(!is_y_offset)? 0 : (y_offset<=y_different)? y_offset : y_different);
//--- Redimensiona as barras de rolagem
   ChangeScrollsSize();
//--- Ajusta os dados
   ShiftData();
  }

Os seguintes campos e métodos destinam-se a gerir o estado do controle e para obter o seu estado atual:

  • O método CTextBox::TextEditState() recupera o estado do controle. 
  • Chamando o método CTextBox::TextBoxState() bloqueia/desbloqueia o controle. Um controle bloqueado é transferido para o modo somente leitura. As cores correspondentes serão definidas para o fundo, quadro e texto (isto pode ser feito pelo usuário antes de criar o controle). 
class CTextBox : public CElement
  {
private:
   //--- Modo somente leitura
   bool              m_read_only_mode;
   //--- Estado da caixa de edição de texto
   bool              m_text_edit_state;
   //--- Estado do Controle
   bool              m_text_box_state;
   //---
public:
   //--- (1) Estado da caixa de texto de edição, (2) obtém/define o estado de disponibilidade do controle
   bool              TextEditState(void)                 const { return(m_text_edit_state);        }
   bool              TextBoxState(void)                  const { return(m_text_box_state);         }
   void              TextBoxState(const bool state);
  };
//+------------------------------------------------------------------+
//| Define o estado de disponibilidade do controle                   |
//+------------------------------------------------------------------+
void CTextBox::TextBoxState(const bool state)
  {
   m_text_box_state=state;
//--- Define em relação ao estado atual
   if(!m_text_box_state)
     {
      //--- Prioridades
      m_canvas.Z_Order(-1);
      //--- A caixa de edição no modo somente leitura
      m_read_only_mode=true;
     }
   else
     {
      //--- Prioridades
      m_canvas.Z_Order(m_text_edit_zorder);
      //--- O controle campo de edição no modo de edição
      m_read_only_mode=false;
     }
//--- Atualiza a caixa de texto
   DrawText();
  }

 


Gerenciamento do cursor de texto

A caixa de edição de texto é ativada quando ela for clicada. As coordenadas do lugar clicado são determinados imediatamente, e o cursor de texto é movido para lá. Isto é feito pelo método CTextBox::OnClickTextBox(). Mas antes de passar para a sua descrição, primeiro considere alguns métodos auxiliares que são invocadas nele, assim como em muitos outros métodos da classe CTextBox.

O método CTextBox::SetTextCursor() para a atualização dos valores da posição do cursor de texto. No modo de linha única, a posição ao longo do eixo Y é sempre igual a 0.

class CTextBox : public CElement
  {
private:
   //--- Posição atual do cursor de texto
   uint              m_text_cursor_x_pos;
   uint              m_text_cursor_y_pos;
   //---
private:
   //--- Coloca o cursor na posição especificada
   void              SetTextCursor(const uint x_pos,const uint y_pos);
  };
//+------------------------------------------------------------------+
//| Coloca o cursor na posição especificada                          |
//+------------------------------------------------------------------+
void CTextBox::SetTextCursor(const uint x_pos,const uint y_pos)
  {
   m_text_cursor_x_pos=x_pos;
   m_text_cursor_y_pos=(!m_multi_line_mode)? 0 : y_pos;
  }

Métodos para controlar as barras de rolagem. Métodos semelhantes já foram abordados no artigo anterior da série, portanto, o código não será mostrado aqui. Um breve lembrete: Se um parâmetro não for passado, o polegar será movido para a última posição, isto é, para o fim da lista/texto/documento. 

class CTextBox : public CElement
  {
public:
   //--- Rolagem da tabela: (1) vertical e (2) horizontal
   void              VerticalScrolling(const int pos=WRONG_VALUE);
   void              HorizontalScrolling(const int pos=WRONG_VALUE);
  };

O CTextBox::DeactivateTextBox() é necessário para desativar a caixa de texto. Um novo recurso oferecido pelos programadores do terminal deve ser mencionado aqui. Mais um identificador gráfico (CHART_KEYBOARD_CONTROL) foi adicionado à enumeração ENUM_CHART_PROPERTY. Ela ativa ou desativa o gerenciamento do gráfico usando as teclas 'Left', 'Right', 'Home', 'End', 'Page Up', 'Page Down', bem com as teclas de zoom do gráfico - '+' e '-'. Assim, quando a caixa de texto está ativada, é necessário desativar a função de gestão do gráfico, de modo que as teclas listadas não são interceptadas por ela, por sua vez, não há a interrupção do funcionamento da caixa de texto. Quando a caixa de texto está desativada, é necessário reativar a gestão do gráfico usando um teclado. 

Aqui, é necessário redesenhar a caixa de texto, e se não for o modo de várias linhas, mover o cursor de texto e o indicador de deslocamento para o início da linha. 

class CTextBox : public CElement
  {
private:
   //--- Desativa a caixa de texto
   void              DeactivateTextBox(void);
  };
//+------------------------------------------------------------------+
//| Desativação da caixa de texto                                    |
//+------------------------------------------------------------------+
void CTextBox::DeactivateTextBox(void)
  {
//--- Sai, se já estiver desativado
   if(!m_text_edit_state)
      return;
//--- Desativa
   m_text_edit_state=false;
//--- Ativa o gerenciamento gráfico
   m_chart.SetInteger(CHART_KEYBOARD_CONTROL,true);
//--- Desenha o texto
   DrawText();
//--- Se o modo de várias linhas estiver desativado
   if(!m_multi_line_mode)
     {
      //--- Move o cursor para o início da linha
      SetTextCursor(0,0);
      //--- Move a barra de rolagem para o início da linha
      HorizontalScrolling(0);
     }
  }

Ao gerenciar o cursor de texto, é necessário acompanhar se ela cruzou os limites da área de visibilidade. Se uma intersecção tivesse ocorrido, o cursor tem de ser retornado para a área de visibilidade novamente. Para este efeito, os métodos reutilizáveis ​​adicionais são necessários. Os limites permitidos de caixa de texto devem ser calculados, tendo em conta o modo multilinha e a presença das barras de rolagem. 

A fim de calcular a quantidade da área de visibilidade que será deslocada, o valor da posição atual deve ser encontrado pela primeira vez: 

class CTextBox : public CElement
  {
private:
   //--- Para o cálculo dos limites da área visível da caixa de texto
   int               m_x_limit;
   int               m_y_limit;
   int               m_x2_limit;
   int               m_y2_limit;
   //---
private:
   //--- Cálculo dos limites da caixa de texto
   void              CalculateBoundaries(void);
   void              CalculateXBoundaries(void);
   void              CalculateYBoundaries(void);
  };
//+------------------------------------------------------------------+
//| Cálculo dos limites da caixa de texto ao longo dos dois eixos    |
//+------------------------------------------------------------------+
void CTextBox::CalculateBoundaries(void)
  {
   CalculateXBoundaries();
   CalculateYBoundaries();
  }
//+------------------------------------------------------------------+
//| Cálculo dos limites da caixa de texto ao longo do eixo X         |
//+------------------------------------------------------------------+
void CTextBox::CalculateXBoundaries(void)
  {
//--- Obtém a coordenada X e o deslocamento ao longo do eixo X
   int x       =(int)m_canvas.GetInteger(OBJPROP_XDISTANCE);
   int xoffset =(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
//--- Calcula os limites da porção visível da caixa de texto
   m_x_limit  =(x+xoffset)-x;
   m_x2_limit =(m_multi_line_mode)? (x+xoffset+m_x_size-m_scrollv.ScrollWidth()-m_text_x_offset)-x : (x+xoffset+m_x_size-m_text_x_offset)-x;
  }
//+------------------------------------------------------------------+
//| Cálculo dos limites da caixa de texto ao longo do eixo Y         |
//+------------------------------------------------------------------+
void CTextBox::CalculateYBoundaries(void)
  {
//--- Sai, se o modo de várias linhas estiver desativado
   if(!m_multi_line_mode)
      return;
//--- Obtém a coordenada Y e deslocamento ao longo do eixo Y
   int y       =(int)m_canvas.GetInteger(OBJPROP_YDISTANCE);
   int yoffset =(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Calcula os limites da porção visível da caixa de texto
   m_y_limit  =(y+yoffset)-y;
   m_y2_limit =(y+yoffset+m_y_size-m_scrollh.ScrollWidth())-y;
  }

Para posicionar com precisão as barras de rolagem em relação à posição atual do cursor, serão utilizados os seguintes métodos: 

class CTextBox : public CElement
  {
private:
   //--- Cálculo da posição X do indicador da barra de rolagem na borda esquerda da caixa de texto
   int               CalculateScrollThumbX(void);
   //--- Cálculo da posição X do indicador da barra de rolagem na borda direita da caixa de texto
   int               CalculateScrollThumbX2(void);
   //--- Cálculo da posição Y do indicador da barra de rolagem na borda superior da caixa de texto
   int               CalculateScrollThumbY(void);
   //--- Cálculo da posição Y do indicador da barra de rolagem na borda inferior da caixa de texto
   int               CalculateScrollThumbY2(void);
  };
//+------------------------------------------------------------------+
//| Calcula a posição X da barra de rolagem na borda esquerda da caixa de texto |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbX(void)
  {
   return(m_text_cursor_x-m_text_x_offset);
  }
//+------------------------------------------------------------------+
//| Calcula a posição X da barra de rolagem na borda direita da caixa de texto |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbX2(void)
  {
   return((m_multi_line_mode)? m_text_cursor_x-m_x_size+m_scrollv.ScrollWidth()+m_text_x_offset : m_text_cursor_x-m_x_size+m_text_x_offset*2);
  }
//+------------------------------------------------------------------+
//| Calcula a posição Y de barra de rolagem na borda superior da caixa de texto |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbY(void)
  {
   return(m_text_cursor_y-m_text_y_offset);
  }
//+------------------------------------------------------------------+
//| Calcula a posição Y de barra de rolagem na borda inferior da caixa de texto |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbY2(void)
  {
//--- Define a fonte a ser exibida na tela (necessário para obter a altura da linha)
   m_canvas.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Obtém a altura da linha
   int line_height=m_canvas.TextHeight("|");
//--- Calcula e retorna o valor
   return(m_text_cursor_y-m_y_size+m_scrollh.ScrollWidth()+m_text_y_offset+line_height);
  }

Vamos fazer com que o clique na caixa de texto gere os eventos, o que indica explicitamente que a caixa de texto foi ativada. É necessário também receber um evento que corresponde ao movimento do cursor dentro da caixa de texto. Adiciona novos identificadores para o arquivo Defines.mqh

  • ON_CLICK_TEXT_BOX para designar um evento de ativação da caixa de texto.
  • ON_MOVE_TEXT_CURSOR para designar um evento para mover o cursor de texto. 
//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_CLICK_TEXT_BOX          (31) // Ativação da caixa de texto
#define ON_MOVE_TEXT_CURSOR        (32) // Mover o cursor de texto

A localização atual do cursor de texto será colocado ao parâmetro de string como informações adicionais usando esses identificadores. Este foi implementado em muitos outros editores de texto, incluindo o MetaEditor. A imagem abaixo mostra um exemplo de como gerar uma string a ser exibida na barra de status do editor de código.


 

Fig. 8. Posição do cursor de texto no MetaEditor.

A listagem abaixo mostra o código do método CTextBox::TextCursorInfo(), que retorna uma string no formato como na imagem acima. São também mostrados os métodos adicionais que podem ser utilizados para obter o número de linhas e caracteres na linha especificada, bem como as posições atuais do cursor de texto. 

class CTextBox : public CElement
  {
private:
   //--- Retorna o índice da (1) linha (2), do caractere onde o cursor de texto está localizado,
   //    (3) o número de linhas, (4) o número de caracteres na linha especificada
   uint              TextCursorLine(void)                      { return(m_text_cursor_y_pos);      }
   uint              TextCursorColumn(void)                    { return(m_text_cursor_x_pos);      }
   uint              LinesTotal(void)                          { return(::ArraySize(m_lines));     }
   uint              ColumnsTotal(const uint line_index);
   //--- Informação sobre o cursor de texto (linha/número de linhas, coluna/número de colunas)
   string            TextCursorInfo(void);
  };
//+------------------------------------------------------------------+
//| Retorna o número de caracteres na linha especificada             |
//+------------------------------------------------------------------+
uint CTextBox::ColumnsTotal(const uint line_index)
  {
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
//--- Prevenção para exceder o tamanho do array
   uint check_index=(line_index<lines_total)? line_index : lines_total-1;
//--- Obtém o tamanho do conjunto de caracteres na linha
   uint symbols_total=::ArraySize(m_lines[check_index].m_symbol);
//--- Retorna o número de caracteres
   return(symbols_total);
  }
//+------------------------------------------------------------------+
//| Informações sobre o cursor de texto                              |
//+------------------------------------------------------------------+
string CTextBox::TextCursorInfo(void)
  {
//--- Componentes da string
   string lines_total        =(string)LinesTotal();
   string columns_total      =(string)ColumnsTotal(TextCursorLine());
   string text_cursor_line   =string(TextCursorLine()+1);
   string text_cursor_column =string(TextCursorColumn()+1);
//--- Gera uma string
   string text_box_info="Ln "+text_cursor_line+"/"+lines_total+", "+"Col "+text_cursor_column+"/"+columns_total;
//--- Retorna uma string
   return(text_box_info);
  }

Agora tudo está pronto para fornecer a descrição do método CTextBox::OnClickTextBox(), o qual foi mencionado no início desta seção (ver o código abaixo). Aqui, no início, não há uma verificação para o nome do objeto, onde o botão esquerdo do mouse foi clicado. Se verificado que o clique não estava na caixa de texto, envia uma mensagem de que a edição terminou (identificador de evento ON_END_EDIT) no caso da caixa de texto ainda estiver ativa. Depois disso, desativa a caixa de texto e deixa o método.

Se o clique foi nesta caixa de texto, em seguida, mais duas verificações estão seguindo. O programa deixa o método, se o modo somente leitura estiver habilitado ou se o controle estiver bloqueado. Se uma das condições for falsa, vá para o código principal do método.

Em primeiro lugar, o gerenciamento do gráfico usando um teclado é desativado. Então (1) obtém o deslocamento da área visível do controle atual, (2) determina as coordenadas relativas do ponto onde ocorreu o clique. Os cálculos do ciclo principal do método também requer a altura da linha. 

Em primeiro lugar, procure a linha onde ocorreu o clique em um ciclo. A busca por um caractere só é iniciada quando a coordenada Y calculada do clique está entre os limites superior e inferior da linha. Se verificar que esta linha não contém caracteres, o cursor de texto e a barra de rolagem horizontal deve ser movida para o início da linha. Isso interrompe o ciclo.

Se a linha contém caracteres, o segundo ciclo começa, que procura o caractere em que ocorreu o clique. O princípio de pesquisa aqui é quase o mesma como no caso com as linhas. A única diferença é que a largura do caractere é obtido em cada iteração, uma vez que nem todas as fontes têm a mesma largura de todos os caracteres. Se o personagem clicado for encontrado, define o cursor de texto para a posição do caractere e completa a pesquisa. Se o caractere nesta linha não foi encontrado e foi atingido o último caractere, mova o cursor para a última posição da linha, onde o caractere não está presente ainda, e completa a pesquisa. 

Em seguida, se o modo multilinha é ativado, é necessário verificar se o cursor de texto (pelo menos parcialmente) ultrapassa os limites da área visível da caixa de texto ao longo do eixo Y. Se isso acontecer, ajusta a área de visibilidade em relação à posição do cursor de texto. Depois disso, o sinalizador da caixa de texto é ativado e redesenha ele

E no final do método CTextBox::OnClickTextBox() é gerado um evento que indica que a caixa de texto foi ativada (identificador de evento ON_CLICK_TEXT_BOX). Para fornecer uma identificação inequívoca, ele também envia o (1) identificador do controle, (2) índice do controle e — adicionalmente — (3) informações sobre a posição do cursor. 

class CTextBox : public CElement
  {
private:
   //--- Manipulação do clique sobre o elemento
   bool              OnClickTextBox(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Manipulação do clique no controle                                |
//+------------------------------------------------------------------+
bool CTextBox::OnClickTextBox(const string clicked_object)
  {
//--- Sai, se ele tem um nome de objeto diferente
   if(m_canvas.Name()!=clicked_object)
     {
      //--- Envia uma mensagem sobre o fim do modo de edição da linha na caixa de texto, se a caixa de texto estava ativa
      if(m_text_edit_state)
         ::EventChartCustom(m_chart_id,ON_END_EDIT,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
      //--- Desativa a caixa de texto
      DeactivateTextBox();
      return(false);
     }
//--- Sai, se (1) o modo somente leitura está habilitado ou se (2) o controle é bloqueado
   if(m_read_only_mode || !m_text_box_state)
      return(true);
//--- Dsativa o gerenciamento do gráfico
   m_chart.SetInteger(CHART_KEYBOARD_CONTROL,false);
//--- Obtém o deslocamento ao longo do eixos X e Y
   int xoffset=(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
   int yoffset=(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Determina a coordenada da caixa de edição de texto abaixo do cursor do mouse
   int x =m_mouse.X()-m_canvas.X()+xoffset;
   int y =m_mouse.Y()-m_canvas.Y()+yoffset;
//--- Obtém a altura da linha
   int line_height=(int)LineHeight();
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
//--- Determina o caractere clicado
   for(uint l=0; l<lines_total; l++)
     {
      //--- Define as coordenadas iniciais para verificar a condição
      int x_offset=m_text_x_offset;
      int y_offset=m_text_y_offset+((int)l*line_height);
      //--- Verificação das condições ao longo do eixo Y
      bool y_pos_check=(l<lines_total-1)?(y>=y_offset && y<y_offset+line_height) : y>=y_offset;
      //--- Se o clique não foi nesta linha, ir para a próxima
      if(!y_pos_check)
         continue;
      //--- Obtém o tamanho do conjunto de caracteres
      uint symbols_total=::ArraySize(m_lines[l].m_width);
      //--- Se esta é uma linha em branco, mova o cursor para a posição especificada e deixe o ciclo
      if(symbols_total<1)
        {
         SetTextCursor(0,l);
         HorizontalScrolling(0);
         break;
        }
      //--- Encontra o caractere que foi clicado
      for(uint s=0; s<symbols_total; s++)
        {
         //--- Se o caractere foi encontrado, mova o cursor para a posição especificada e deixe o ciclo
         if(x>=x_offset && x<x_offset+m_lines[l].m_width[s])
           {
            SetTextCursor(s,l);
            l=lines_total;
            break;
           }
         //--- Adiciona a largura do caractere atual para a próxima verificação
         x_offset+=m_lines[l].m_width[s];
         //--- Se este é o último caractere, mova o cursor para a extremidade da linha e deixe o ciclo
         if(s==symbols_total-1 && x>x_offset)
           {
            SetTextCursor(s+1,l);
            l=lines_total;
            break;
           }
        }
     }
//--- Se o modo da caixa de texto multilinha está habilitado
   if(m_multi_line_mode)
     {
      //--- Obtém os limites da parte visível da caixa de texto
      CalculateYBoundaries();
      //--- Obtém a coordenada Y do cursor
      CalculateTextCursorY();
      //--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
      if(m_text_cursor_y<=m_y_limit)
         VerticalScrolling(CalculateScrollThumbY());
      else
        {
         if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
            VerticalScrolling(CalculateScrollThumbY2());
        }
     }
//--- Ativa a caixa de texto
   m_text_edit_state=true;
//--- Atualiza o texto e o cursor
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_CLICK_TEXT_BOX,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }



Inserção de um caractere

Agora considere o método CTextBox::OnPressedKey(). Ele lida com as teclas pressionadas, e se verificar que a tecla pressionada contém um caractere, então ele deve ser adicionado à linha na posição atual do cursor de texto. Métodos adicionais serão necessários para aumentar os tamanhos dos arrays na estrutura KeySymbolOptions, acrescentando o caractere digitado na caixa de texto para arrays, bem como a largura do caractere para o elemento acrescentado aos arrays.

Um método bastante simples CTextBox::ArraysResize() irá ser utilizado para redimensionar os arrays em numerosos métodos da classe CTextBox:

class CTextBox : public CElement
  {
private:
   //--- Redimensiona os arrays de propriedades para a linha especificada
   void              ArraysResize(const uint line_index,const uint new_size);
  };
//+------------------------------------------------------------------+
//| Redimensiona os arrays de propriedades para a linha especificada |
//+------------------------------------------------------------------+
void CTextBox::ArraysResize(const uint line_index,const uint new_size)
  {
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
//--- Prevenção para exceder o tamanho do array
   uint l=(line_index<lines_total)? line_index : lines_total-1;
//--- Ajusta o tamanho dos arrays da estrutura
   ::ArrayResize(m_lines[line_index].m_width,new_size);
   ::ArrayResize(m_lines[line_index].m_symbol,new_size);
  }

O método é CTextBox::AddSymbol() é destinado para a adição de um caractere novo que entrou na caixa de texto. Vamos analisar ele com mais cuidado. Ao entrar com um novo caractere, os tamanhos dos arrays devem ser incrementados por um elemento. A posição atual do cursor de texto pode estar em qualquer caractere da string. Portanto, antes de adicionar um caractere ao array, é necessário primeiro deslocar todos os caracteres à direita da posição atual do cursor de texto por um índice para a direita. Depois disso, armazene o caractere inserido na posição do cursor do cursor de texto. No final do método, desloca o cursor de texto para a direita por um caractere

class CTextBox : public CElement
  {
private:
   //--- Adiciona um caractere e suas propriedades para os arrays da estrutura
   void              AddSymbol(const string key_symbol);
  };
//+------------------------------------------------------------------+
//| Adiciona o caractere e as suas propriedades para os arrays da estrutura |
//+------------------------------------------------------------------+
void CTextBox::AddSymbol(const string key_symbol)
  {
//--- Obtém o tamanho do conjunto de caracteres
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Redimensiona os arrays
   ArraysResize(m_text_cursor_y_pos,symbols_total+1);
//--- Desloca todos os caracteres a partir da extremidade do array para o índice do caractere adicionado
   for(uint i=symbols_total; i>m_text_cursor_x_pos; i--)
     {
      m_lines[m_text_cursor_y_pos].m_symbol[i] =m_lines[m_text_cursor_y_pos].m_symbol[i-1];
      m_lines[m_text_cursor_y_pos].m_width[i]  =m_lines[m_text_cursor_y_pos].m_width[i-1];
     }
//--- Obtém o largura do caractere
   int width=m_canvas.TextWidth(key_symbol);
//--- Adiciona o personagem para o elemento desocupado
   m_lines[m_text_cursor_y_pos].m_symbol[m_text_cursor_x_pos] =key_symbol;
   m_lines[m_text_cursor_y_pos].m_width[m_text_cursor_x_pos]  =width;
//--- Aumenta o contador da posição do cursor
   m_text_cursor_x_pos++;
  }

A lista abaixo mostra o código do método CTextBox::OnPressedKey(). Se a caixa de texto estiver ativada, tente obter o caractere pelo código da tecla passada para o método. Se a tecla pressionada não conter um caractere, em seguida, o programa sai do método. Se houver um caractere, em seguida, ele é adicionado aos arrays, juntamente com as suas propriedades. O tamanho da caixa de texto pode ser alterado ao entrar um caractere, então os novos valores são calculados e ajustados. Depois disso, obtenha os limites da caixa de texto e a coordenada atual do cursor de texto. Se o cursor vai além da borda direita da caixa de texto, ajusta a posição do indicador da barra de rolagem horizontal. Depois que a caixa de texto é redesenhada forçando a exibição (true) do cursor de texto. No final do método CTextBox::OnPressedKey(), um evento para mover o cursor de texto (ON_MOVE_TEXT_CURSOR) é gerado com o identificador de controle, o índice do controle e informação adicional sobre a localização do cursor de texto. 

class CTextBox : public CElement
  {
private:
   //--- Manipulação de uma tecla pressionada
   bool              OnPressedKey(const long key_code);
  };
//+------------------------------------------------------------------+
//| Manipulação de uma tecla pressionada                             |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKey(const long key_code)
  {
//--- Sai, se a caixa de texto não está ativada
   if(!m_text_edit_state)
      return(false);
//--- Obter a tecla do caractere
   string pressed_key=m_keys.KeySymbol(key_code);
//--- Sai, se não houver nenhum caractere
   if(pressed_key=="")
      return(false);
//--- Adiciona o caractere e suas propriedades
   AddSymbol(pressed_key);
//--- Calcula o tamanho da caixa de texto
   CalculateTextBoxSize();
//--- Define o novo tamanho da caixa de texto
   ChangeTextBoxSize(true,true);
//--- Obtém os limites da parte visível da caixa de texto
   CalculateXBoundaries();
//--- Obtém a coordenada X do cursor
   CalculateTextCursorX();
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_x>=m_x2_limit)
      HorizontalScrolling(CalculateScrollThumbX2());
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Manipulação da tecla "Backspace" pressionada

Agora considere a situação quando um personagem é excluído pelo pressionamento da tecla Backspace. Neste caso, o manipulador de eventos do controle de Caixa de Texto Multilinha irá chamar o método CTextBox::OnPressedKeyBackspace(). O seu funcionamento irá exigir métodos adicionais, que não tinham sido considerados anteriormente. Em primeiro lugar, será apresentado o respectivo código.

Os caracteres são apagados usando o método CTextBox::DeleteSymbol(). No início, ele verifica se a linha atual contém pelo menos um caractere. Se não tiver mais, então o cursor de texto é colocado no início da linha e o método é encerrado. Se ainda existirem alguns caracteres, então, obtém a posição do caractere anterior. Este será o índice, a partir do qual todos os caracteres são para ser deslocados para a direita por um elemento. Depois disso, o cursor de texto também é deslocado para a esquerda por uma posição. E no final do método, os tamanhos dos arrays são diminuídos por um elemento.

class CTextBox : public CElement
  {
private:
   //--- Apaga um caractere
   void              DeleteSymbol(void);
  };
//+------------------------------------------------------------------+
//| Exclui um caractere                                              |
//+------------------------------------------------------------------+
void CTextBox::DeleteSymbol(void)
  {
//--- Obtém o tamanho do conjunto de caracteres
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Se o array está vazio
   if(symbols_total<1)
     {
      //--- Coloca o cursor para a posição zero da linha do cursor
      SetTextCursor(0,m_text_cursor_y_pos);
      return;
     }
//--- Obtém a posição do caractere anterior
   int check_pos=(int)m_text_cursor_x_pos-1;
//--- Sai, se estiver fora do intervalo
   if(check_pos<0)
      return;
//--- Desloca todos os caracteres de um elemento para a direita a partir do índice do caractere removido até o fim do array
   for(uint i=check_pos; i<symbols_total-1; i++)
     {
      m_lines[m_text_cursor_y_pos].m_symbol[i] =m_lines[m_text_cursor_y_pos].m_symbol[i+1];
      m_lines[m_text_cursor_y_pos].m_width[i]  =m_lines[m_text_cursor_y_pos].m_width[i+1];
     }
//--- Diminui o contador da posição do cursor
   m_text_cursor_x_pos--;
//--- Redimensiona os arrays
   ArraysResize(m_text_cursor_y_pos,symbols_total-1);
  }

Se o cursor de texto está no início da linha e esta não é a primeira linha, é necessário eliminar a linha atual e mover todas as linhas inferiores para cima por uma posição. Se a linha excluída tem caracteres, eles precisam ser acrescentados à linha, que está a uma posição mais elevada. Um outro método adicional será usado para esta operação — CTextBox::ShiftOnePositionUp(). Um método auxiliar CTextBox::LineCopy() também será necessário para facilitar um pouco a cópia das linhas. 

class CTextBox : public CElement
  {
private:
   //--- Faz uma cópia da linha (origem) especificada para um novo local (destino)
   void              LineCopy(const uint destination,const uint source);
  };
//+------------------------------------------------------------------+
//| Redimensiona os arrays de propriedades para a linha especificada |
//+------------------------------------------------------------------+
void CTextBox::LineCopy(const uint destination,const uint source)
  {
   ::ArrayCopy(m_lines[destination].m_width,m_lines[source].m_width);
   ::ArrayCopy(m_lines[destination].m_symbol,m_lines[source].m_symbol);
  }

O código do método CTextBox::ShiftOnePositionUp() é apresentado abaixo. O primeiro ciclo do método desloca todas as linhas abaixo da posição atual do cursor uma posição para cima. Na primeira iteração, é necessário verificar se a linha contém caracteres, e se isso acontecer, armazena-os para anexar eles a linha anterior. Uma vez que as linhas forem deslocadas, o array de linhas é reduzido por um elemento. O cursor de texto é movido para a extremidade da linha anterior. 

O último bloco do método CTextBox::ShiftOnePositionUp() destina-se a adicionar caracteres da linha excluída para a linha anterior. Se há uma linha a ser anexada, então, usa a função ::StringToCharArray() para transferir ela para um array temporário do tipo uchar sob a forma de códigos de caracteres. Em seguida, aumenta o array da linha atual pelo número de caracteres adicionados. E, como uma operação de finalização, adicione alternadamente os caracteres e suas propriedades para os arrays. A conversão dos códigos de caracteres a partir do array temporário do tipo uchar é realizado utilizando a função ::CharToString()

class CTextBox : public CElement
  {
private:
   //--- Desloca as linhas para cima por uma posição
   void              ShiftOnePositionUp(void);
  };
//+------------------------------------------------------------------+
//| Desloca as linhas para cima por uma posição                      |
//+------------------------------------------------------------------+
void CTextBox::ShiftOnePositionUp(void)
  {
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
//--- Desloca as linhas para cima a partir do próximo elemento por uma posição
   for(uint i=m_text_cursor_y_pos; i<lines_total-1; i++)
     {
      //--- Na primeira iteração
      if(i==m_text_cursor_y_pos)
        {
         //--- Obtém o tamanho do conjunto de caracteres
         uint symbols_total=::ArraySize(m_lines[i].m_symbol);
         //--- Se houver caracteres nesta linha, armazena eles a fim de acrescentá-los à linha anterior
         m_temp_input_string=(symbols_total>0)? CollectString(i) : "";
        }
      //--- Índice do próximo elemento do array de linhas
      uint next_index=i+1;
      //--- Obtém o tamanho do conjunto de caracteres
      uint symbols_total=::ArraySize(m_lines[next_index].m_symbol);
      //--- Redimensiona os arrays
      ArraysResize(i,symbols_total);
      //--- Faz uma cópia da linha
      LineCopy(i,next_index);
     }
//--- Redimensiona o array de linhas
   uint new_size=lines_total-1;
   ::ArrayResize(m_lines,new_size);
//--- Diminui o contador de linhas
   m_text_cursor_y_pos--;
//--- Obtém o tamanho do conjunto de caracteres
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Move o cursor para o fim
   m_text_cursor_x_pos=symbols_total;
//--- Obtém a coordenada X do cursor
   CalculateTextCursorX();
//--- Se há uma linha que deve ser adicionada à anterior
   if(m_temp_input_string!="")
     {
      //--- Transferir a linha para o array
      uchar array[];
      int total=::StringToCharArray(m_temp_input_string,array)-1;
      //--- Obtém o tamanho do conjunto de caracteres
      symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
      //--- Redimensiona os arrays
      new_size=symbols_total+total;
      ArraysResize(m_text_cursor_y_pos,new_size);
      //--- Adiciona os dados aos arrays da estrutura
      for(uint i=m_text_cursor_x_pos; i<new_size; i++)
        {
         m_lines[m_text_cursor_y_pos].m_symbol[i] =::CharToString(array[i-m_text_cursor_x_pos]);
         m_lines[m_text_cursor_y_pos].m_width[i]  =m_canvas.TextWidth(m_lines[m_text_cursor_y_pos].m_symbol[i]);
        }
     }
  }

Uma vez que todos os métodos auxiliares estiverem prontos, o código do método principal CTextBox::OnPressedKeyBackspace() não parece ser muito complicado. Aqui, no início, verifique se a tecla Backspace foi pressionada e se a caixa de texto é ativada. Se as verificações são passados, em seguida, veja se a posição do cursor de texto está atualmente localizada. Se ela não está no início de uma linha no momento, exclui o caractere anterior. Se, no entanto, ela está no início da linha e não é a primeira linha, então transfira todas as linhas inferiores por uma posição, apagando a linha atual

Depois disso, os novos tamanhos para a caixa de texto são calculados e ajustados. Os limites e coordenadas do cursor de texto são obtidos. Ajusta o indicador da barra de rolagem, se o cursor de texto deixar a área visível. E, finalmente, o controle é redesenhado com exposição forçada do cursor de texto e uma mensagem sobre o deslocamento do cursor é gerado. 

class CTextBox : public CElement
  {
private:
   //--- Manipulação da tecla Backspace pressionada
   bool              OnPressedKeyBackspace(const long key_code);
  };
//+------------------------------------------------------------------+
//| Manipulação da tecla Backspace pressionada                       |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyBackspace(const long key_code)
  {
//--- Sai, se não for a tecla Backspace ou se a caixa de texto não está ativada
   if(key_code!=KEY_BACKSPACE || !m_text_edit_state)
      return(false);
//--- Exclui o caractere, se a posição for maior que zero
   if(m_text_cursor_x_pos>0)
      DeleteSymbol();
//--- Exclui a linha, se a posição é igual a zero e não for a primeira linha
   else if(m_text_cursor_y_pos>0)
     {
      //--- Desloca as linhas para cima por uma posição
      ShiftOnePositionUp();
     }
//--- Calcula o tamanho da caixa de texto
   CalculateTextBoxSize();
//--- Define o novo tamanho da caixa de texto
   ChangeTextBoxSize(true,true);
//--- Obtém os limites da parte visível da caixa de texto
   CalculateBoundaries();
//--- Obtém as coordenadas X e Y do cursor
   CalculateTextCursorX();
   CalculateTextCursorY();  
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_x<=m_x_limit)
      HorizontalScrolling(CalculateScrollThumbX());
   else
     {
      if(m_text_cursor_x>=m_x2_limit)
         HorizontalScrolling(CalculateScrollThumbX2());
     }
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_y<=m_y_limit)
      VerticalScrolling(CalculateScrollThumbY());
   else
      VerticalScrolling(m_scrollv.CurrentPos());
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Manipulação da tecla "Enter" pressionada

Se o modo multilinha está habilitado e a tecla Enter é pressionada, é necessário adicionar uma nova linha, e todas as linhas abaixo da posição atual do cursor de texto devem ser deslocadas para uma posição para baixo. Deslocando as linhas aqui irá exigir um método auxiliar separado CTextBox::ShiftOnePositionDown(), assim como um método adicional para limpar as linhas - CTextBox::ClearLine().

class CTextBox : public CElement
  {
private:
   //--- Limpa a linha especificada
   void              ClearLine(const uint line_index);
  };
//+------------------------------------------------------------------+
//| Limpa a linha especificada                                       |
//+------------------------------------------------------------------+
void CTextBox::ClearLine(const uint line_index)
  {
   ::ArrayFree(m_lines[line_index].m_width);
   ::ArrayFree(m_lines[line_index].m_symbol);
  }

Agora, vamos examinar o algoritmo do método CTextBox::ShiftOnePositionDown() em detalhes. Em primeiro lugar, é necessário armazenar o número de caracteres na linha, em que a tecla Enter foi pressionada. Isto, assim como a posição na linha, em que o cursor de texto foi localizado, define a forma como o algoritmo do método CTextBox::ShiftOnePositionDown() será processado. Depois disso, move o cursor de texto para uma nova linha e aumenta o tamanho do array de linhas por um elemento. Em seguida, todas as linhas a partir da linha atual devem ser deslocadas para baixo por uma posição em um ciclo começando a partir da extremidade do array. Na última iteração, se a linha onde a tecla Enter foi pressionada não conter caracteres, então é necessário limpar a linha onde o cursor de texto está atualmente localizado. A linha que foi limpada é uma cópia da linha, o conteúdo já se encontra presente na linha seguinte, como resultado do deslocamento de uma posição para baixo.

No início do método, nós armazenamos o número de caracteres na linha onde a tecla Enter havia sido pressionada. Se acontecer da linha conter caracteres, é necessário descobrir onde que o cursor de texto foi localizado naquele momento. E acontecer que não foi no final da linha, então é necessário calcular o número de caracteres a serem movidos para a nova linha, a partir da posição atual do cursor de texto para o fim da linha. Para estes fins, o array temporário é usado aqui, onde os caracteres serão copiados, que mais tarde será movido para a nova linha.

class CTextBox : public CElement
  {
private:
   //--- Desloca as linhas para baixo por uma posição
   void              ShiftOnePositionDown(void);
  };
//+------------------------------------------------------------------+
//| Desloca as linhas para baixo por uma posição                     |
//+------------------------------------------------------------------+
void CTextBox::ShiftOnePositionDown(void)
  {
//--- Obtém o tamanho do array de caracteres a partir da linha, onde a tecla Enter foi pressionada
   uint pressed_line_symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Aumenta o contador das linhas
   m_text_cursor_y_pos++;
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
//--- Aumenta o array por um elemento
   uint new_size=lines_total+1;
   ::ArrayResize(m_lines,new_size);
//--- Desloca as linhas para baixo a partir da posição atual por um item (a partir do final do array)
   for(uint i=lines_total; i>m_text_cursor_y_pos; i--)
     {
      //--- Índice do elemento anterior do array de linhas
      uint prev_index=i-1;
      //--- Obtém o tamanho do conjunto de caracteres
      uint symbols_total=::ArraySize(m_lines[prev_index].m_symbol);
      //--- Redimensiona os arrays
      ArraysResize(i,symbols_total);
      //--- Faz uma cópia da linha
      LineCopy(i,prev_index);
      //--- Limpa a nova linha
      if(prev_index==m_text_cursor_y_pos && pressed_line_symbols_total<1)
         ClearLine(prev_index);
     }
//--- Se a tecla Enter não foi pressionada em uma linha vazia
   if(pressed_line_symbols_total>0)
     {
      //--- Índice da linha, onde a tecla Enter foi pressionada
      uint prev_line_index=m_text_cursor_y_pos-1;
      //--- Array para as cópias dos caracteres a partir da posição atual do cursor para o fim da linha
      string array[];
      //--- Define o tamanho do array igual ao número de caracteres que deve ser movido para a nova linha
      uint new_line_size=pressed_line_symbols_total-m_text_cursor_x_pos;
      ::ArrayResize(array,new_line_size);
      //--- Copia os caracteres a serem transferidos para a nova linha dentro de um array
      for(uint i=0; i<new_line_size; i++)
         array[i]=m_lines[prev_line_index].m_symbol[m_text_cursor_x_pos+i];
      //--- Redimensiona os arrays da estrutura para a linha onde a tecla Enter foi pressionada
      ArraysResize(prev_line_index,pressed_line_symbols_total-new_line_size);
      //--- Redimensiona os arrays da estrutura para a nova linha
      ArraysResize(m_text_cursor_y_pos,new_line_size);
      //--- Adiciona os dados para os arrays da estrutura para a nova linha
      for(uint k=0; k<new_line_size; k++)
        {
         m_lines[m_text_cursor_y_pos].m_symbol[k] =array[k];
         m_lines[m_text_cursor_y_pos].m_width[k]  =m_canvas.TextWidth(array[k]);
        }
     }
  }

Tudo está pronto para a manipulação do pressionamento da tecla Enter. Agora considere o método CTextBox::OnPressedKeyEnter(). Logo no início, verifique se a tecla Enter foi pressionada e se a caixa de texto foi ativada. Então, se todos as verificações passaram, e se há uma caixa de texto de linha única, então, simplesmente finalizou-se o trabalho com ela. Para fazer isso, desative através do envio de um evento com o identificador ON_END_EDIT e deixe o método.

Se o modo multilinha está habilitado, então, todas as linhas inferiores são deslocadas uma posição para baixo a partir da posição atual do cursor de texto. Depois disso, os tamanhos da caixa de texto são ajustados e éfeito uma verificação se o cursor de texto excede o limite inferior da área de visibilidade. Além disso, o cursor de texto é colocado no início da linha. No final do método, a caixa de texto é redesenhada e é enviado uma mensagem, dizendo que o cursor de texto foi movido. 

class CTextBox : public CElement
  {
private:
   //--- Manipulação da tecla Enter pressionada
   bool              OnPressedKeyEnter(const long key_code);
  };
//+------------------------------------------------------------------+
//| Manipulação da tecla Enter pressionada                           |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyEnter(const long key_code)
  {
//--- Sai, se não for a tecla Enter ou se a caixa de texto não está ativada
   if(key_code!=KEY_ENTER || !m_text_edit_state)
      return(false);
//--- Se o modo de várias linhas estiver desativado
   if(!m_multi_line_mode)
     {
      //--- Desativa a caixa de texto
      DeactivateTextBox();
      //--- Envia uma mensagem sobre ele
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
      return(false);
     }
//--- Desloca as linhas para baixo por uma posição
   ShiftOnePositionDown();
//--- Calcula o tamanho da caixa de texto
   CalculateTextBoxSize();
//--- Define o novo tamanho da caixa de texto
   ChangeTextBoxSize();
//--- Obtém os limites da parte visível da caixa de texto
   CalculateYBoundaries();
//--- Obtém a coordenada Y do cursor
   CalculateTextCursorY();
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
      VerticalScrolling(CalculateScrollThumbY2());
//--- Move o cursor para o início da linha
   SetTextCursor(0,m_text_cursor_y_pos);
//--- Move a barra de rolagem para o início
   HorizontalScrolling(0);
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Manipulação das teclas "Left" e "Right" pressionadas

Ao pressionar a tecla Left ou Right, o cursor de texto é movido por um caractere na direção correspondente. Para conseguir isso, um método adicional CTextBox::CorrectingTextCursorXPos() será necessário em primeiro lugar, que irá ajustar a localização do cursor de texto. Este método também será usado em outros métodos da classe.

class CTextBox : public CElement
  {
private:
   //--- Ajusta o cursor de texto ao longo do eixo X
   void              CorrectingTextCursorXPos(const int x_pos=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| Ajusta o cursor de texto ao longo do eixo X                      |
//+------------------------------------------------------------------+
void CTextBox::CorrectingTextCursorXPos(const int x_pos=WRONG_VALUE)
  {
//--- Obtém o tamanho do conjunto de caracteres
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_width);
//--- Determina a posição do cursor
   uint text_cursor_x_pos=0;
//--- Se a posição está disponível
   if(x_pos!=WRONG_VALUE)
      text_cursor_x_pos=(x_pos>(int)symbols_total-1)? symbols_total : x_pos;
//--- Se a posição não está disponível, define o cursor para o fim da linha
   else
      text_cursor_x_pos=symbols_total;
//--- Zera a posição, se a linha não conter caracteres
   m_text_cursor_x_pos=(symbols_total<1)? 0 : text_cursor_x_pos;
//--- Obtém a coordenada X do cursor
   CalculateTextCursorX();
  }

O código a seguir mostra o código do método CTextBox::OnPressedKeyLeft() para lidar com o pressionamento da tecla esquerda (Left). O programa deixa o método se outra tecla for pressionada ou se a caixa de texto não estiver ativada, e também se o Ctrl for pressionado no momento. A manipulação do pressionamento simultâneo das teclas com a tecla Ctrl será considerado em outra seção do artigo. 

Se as primeiras verificações são passadas, então, veja a posição do cursor de texto. Se ela não estiver localizada no início da linha, então, desloque ela para o caractere anterior. Se está no início da linha, e se esta linha não for a primeira, então o cursor de texto deve ser movido para a extremidade da linha anterior. Depois disso, ajuste os indicadores das barras de rolagem horizontal e vertical, redesenhe a caixa de texto e envie uma mensagem sobre como mover o cursor de texto.

class CTextBox : public CElement
  {
private:
   //--- Manipulação do caractere da tecla Left
   bool              OnPressedKeyLeft(const long key_code);
  };
//+------------------------------------------------------------------+
//| Manipulação do caractere da tecla Left                           |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyLeft(const long key_code)
  {
//--- Sai, se não for a tecla Left ou se a tecla Ctrl foi pressionada ou se a caixa de texto não está ativada
   if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || !m_text_edit_state)
      return(false);
//--- Se a posição do cursor de texto for maior do que zero
   if(m_text_cursor_x_pos>0)
     {
      //--- Desloca para o caractere anterior
      m_text_cursor_x-=m_lines[m_text_cursor_y_pos].m_width[m_text_cursor_x_pos-1];
      //--- Diminui o contador de caracteres
      m_text_cursor_x_pos--;
     }
   else
     {
      //--- Se esta não for a primeira linha
      if(m_text_cursor_y_pos>0)
        {
         //--- Move para o fim da linha anterior
         m_text_cursor_y_pos--;
         CorrectingTextCursorXPos();
        }
     }
//--- Obtém os limites da parte visível da caixa de texto
   CalculateBoundaries();
//--- Obtém a coordenada Y do cursor
   CalculateTextCursorY();
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_x<=m_x_limit)
      HorizontalScrolling(CalculateScrollThumbX());
   else
     {
      //--- Obtém o tamanho do conjunto de caracteres
      uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
      //---
      if(m_text_cursor_x_pos==symbols_total && m_text_cursor_x>=m_x2_limit)
         HorizontalScrolling(CalculateScrollThumbX2());
     }
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_y<=m_y_limit)
      VerticalScrolling(CalculateScrollThumbY());
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

Agora, considere o código do método CTextBox::OnPressedKeyRight() para lidar com o pressionamento da tecla Rigth. Aqui, as verificações para o código da tecla pressionada, o estado de caixa de texto e a tecla Ctrl também devem ser passados no início do método. Então veja, se o cursor de texto está no fim da linha. Se não, então mover o cursor de texto para a direita por um caractere. Se o cursor se encontra na extremidade da linha, então, veja se esta linha é a última. Se não, então mova o cursor de texto para o início da próxima linha.

Então (1) ajusta os indicadores da barra de rolagem no caso do cursor de texto ir além da área de visibilidade da caixa de texto, (2) redesenha o controle e (3) envia uma mensagem sobre como mover o cursor de texto.  

class CTextBox : public CElement
  {
private:
   //--- Manipulação da tecla Right pressionada
   bool              OnPressedKeyRight(const long key_code);
  };
//+------------------------------------------------------------------+
//| Manipulação da tecla Right pressionada                           |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyRight(const long key_code)
  {
//--- Sai, se não for a tecla Right ou se a tecla Ctrl foi pressionada ou se a caixa de texto não está ativada
   if(key_code!=KEY_RIGHT || m_keys.KeyCtrlState() || !m_text_edit_state)
      return(false);
//--- Obtém o tamanho do conjunto de caracteres
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_width);
//--- Se este é o fim da linha
   if(m_text_cursor_x_pos<symbols_total)
     {
      //--- Desloca a posição do cursor de texto para o próximo caractere
      m_text_cursor_x+=m_lines[m_text_cursor_y_pos].m_width[m_text_cursor_x_pos];
      //--- Aumenta o contador de caracteres
      m_text_cursor_x_pos++;
     }
   else
     {
      //--- Obtém o tamanho do array de linhas
      uint lines_total=::ArraySize(m_lines);
      //--- Se esta não é a última linha
      if(m_text_cursor_y_pos<lines_total-1)
        {
         //--- Move o cursor para o início da próxima linha
         m_text_cursor_x=m_text_x_offset;
         SetTextCursor(0,++m_text_cursor_y_pos);
        }
     }
//--- Obtém os limites da parte visível da caixa de texto
   CalculateBoundaries();
//--- Obtém a coordenada Y do cursor
   CalculateTextCursorY();
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_x>=m_x2_limit)
      HorizontalScrolling(CalculateScrollThumbX2());
   else
     {
      if(m_text_cursor_x_pos==0)
         HorizontalScrolling(0);
     }
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
      VerticalScrolling(CalculateScrollThumbY2());
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Manipulação das teclas "Up" e "Down" pressionadas

Pressionando as teclas Up e Down faz com que o cursor de texto se mova para cima e para baixo das linhas. Os métodos CTextBox::OnPressedKeyUp() e CTextBox::OnPressedKeyDown() são projetados para lidar com as teclas pressionadas. O código de somente uma delas será fornecido aqui, como a única diferença entre elas encontra-se apenas em duas linhas de código.

No início do código, é necessário passar por três verificações. O programa deixa o método se (1) é uma caixa de texto de linha única ou se (2) outra tecla foi pressionada ou (3) a caixa de texto não está ativada. Se a posição atual do cursor de texto não estiver na primeira linha, então mova-o para a linha anterior (para a próxima linha no método CTextBox::OnPressedKeyDown()) com o ajuste para o número de caracteres no caso de ultrapassar o intervalo de linhas do array.

Depois disso, verifique se o cursor de texto está fora da área visível da caixa de texto e ajuste os indicadores da barra de rolagem, se necessário. Aqui, a única diferença entre os dois métodos é que o método CTextBox::OnPressedKeyUp() verifica se o limite superior foi excedido, e o método CTextBox::OnPressedKeyDown() — se o limite inferior foi excedido. No final, a caixa de texto é redesenhada e a mensagem sobre como mover o cursor de texto é enviada.

class CTextBox : public CElement
  {
private:
   //--- Manipulação da tecla Up pressionada
   bool              OnPressedKeyUp(const long key_code);
   //--- Manipulação da tecla Down pressionada
   bool              OnPressedKeyDown(const long key_code);
  };
//+------------------------------------------------------------------+
//| Manipulação da tecla Up pressionada                              |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyUp(const long key_code)
  {
//--- Sai, se o modo de várias linhas estiver desativado
   if(!m_multi_line_mode)
      return(false);
//--- Sai, se não for a tecla Up ou se a caixa de texto não está ativada
   if(key_code!=KEY_UP || !m_text_edit_state)
      return(false);
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
//--- Verifica se o tamanho do array não excedeu
   if(m_text_cursor_y_pos-1<lines_total)
     {
      //--- Move para a linha anterior
      m_text_cursor_y_pos--;
      //--- Ajusta o cursor de texto ao longo do eixo X
      CorrectingTextCursorXPos(m_text_cursor_x_pos);
     }
//--- Obtém os limites da parte visível da caixa de texto
   CalculateBoundaries();
//--- Obtém a coordenada Y do cursor
   CalculateTextCursorY();
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_x<=m_x_limit)
      HorizontalScrolling(CalculateScrollThumbX());
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_y<=m_y_limit)
      VerticalScrolling(CalculateScrollThumbY());
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }



Manipulação das teclas "Home" e "End" pressionadas

O pressionamento das teclas Home e End move o cursor de texto para o início e final da linha, respectivamente. Os métodos CTextBox::OnPressedKeyHome() e CTextBox::OnPressedKeyEnd() são projetados para lidar com esses eventos. Seu código é fornecido abaixo e não exige qualquer explicação adicional, uma vez que ele é bastante simples e apresenta observações detalhadas. 

class CTextBox : public CElement
  {
private:
   //--- Manipulação da tecla Home pressionada
   bool              OnPressedKeyHome(const long key_code);
   //--- Manipulação da tecla End pressionada
   bool              OnPressedKeyEnd(const long key_code);
  };
//+------------------------------------------------------------------+
//| Manipulação da tecla Home pressionada                            |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyHome(const long key_code)
  {
//--- Sai, se não for a tecla Home ou se a tecla Ctrl foi pressionada ou se a caixa de texto não está ativada
   if(key_code!=KEY_HOME || m_keys.KeyCtrlState() || !m_text_edit_state)
      return(false);
//--- Move o cursor para o início da linha atual
   SetTextCursor(0,m_text_cursor_y_pos);
//--- Move a barra de rolagem para o início
   HorizontalScrolling(0);
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }
//+------------------------------------------------------------------+
//| Manipulação da tecla End pressionada                             |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyEnd(const long key_code)
  {
//--- Sai, se não for a tecla End ou se a tecla Ctrl foi pressionada ou se a caixa de texto não está ativada
   if(key_code!=KEY_END || m_keys.KeyCtrlState() || !m_text_edit_state)
      return(false);
// --- Obter o número de caracteres na linha atual
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Move o cursor para o final da linha atual
   SetTextCursor(symbols_total,m_text_cursor_y_pos);
//--- Obtém a coordenada X do cursor
   CalculateTextCursorX();
//--- Obtém os limites da parte visível da caixa de texto
   CalculateXBoundaries();
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_x>=m_x2_limit)
      HorizontalScrolling(CalculateScrollThumbX2());
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

 


Manipulação do pressionamento simultâneo das teclas em combinação com a tecla "Ctrl"

Agora, vamos considerar os métodos para lidar com as seguintes combinações de teclas:

  • 'Ctrl' + 'Left' – move o cursor de texto de uma palavra para uma palavra à esquerda.
  • 'Ctrl' + 'Right' – move o cursor de texto de uma palavra para uma palavra à direita.
  • 'Ctrl' + 'Home' – move o cursor de texto para o início da primeira linha. 
  • 'Ctrl' + 'End' – move o cursor de texto para o fim da última linha.

Como um exemplo, apenas um dos métodos serão considerados — CTextBox::OnPressedKeyCtrlAndLeft(), para mover o cursor de texto de uma palavra para uma palavra à esquerda. No início do método, há uma verificação se houve o pressionamento simultâneo das teclas CTRL e Left. Se qualquer uma destas teclas não forem pressionadas, o programa sairá do método. Além disso, a caixa de texto deve ser ativada.

Em caso da posição atual do cursor de texto estiver no início da linha, e não for a primeira linha, mova-o para o fim da linha anterior. Se o cursor de texto não estiver no início da linha atual, então, é necessário localizar o início de uma sequência ininterrupta de caracteres. O caractere de espaço (' ') serve como o caractere de interrupção. Aqui, em um ciclo, mova ao longo da linha atual a partir da direita para a esquerda, e uma vez que a combinação é encontrada, quando o próximo caractere for um espaço e o atual for qualquer outro caractere, então, se este não é o ponto de partida, define o cursor de texto para essa posição.

Depois disso, como em todos os outros métodos, existe uma verificação se o cursor de texto está fora da área visível da caixa de texto e os indicadores na barra de rolagem são ajustadas, se necessário. No final, a caixa de texto é redesenhada e é enviada uma mensagem, dizendo que o cursor de texto foi movido.

class CTextBox : public CElement
  {
private:
   //--- Manipulação das teclas Ctrl + Left pressionadas
   bool              OnPressedKeyCtrlAndLeft(const long key_code);
   //--- Manipulação das teclas Ctrl + Right pressionadas
   bool              OnPressedKeyCtrlAndRight(const long key_code);
   //--- Manipulação das teclas Ctrl + Home pressionadas
   bool              OnPressedKeyCtrlAndHome(const long key_code);
   //--- Manipulação das teclas Ctrl + End pressionadas
   bool              OnPressedKeyCtrlAndEnd(const long key_code);
  };
//+------------------------------------------------------------------+
//| Manipulação das teclas Ctrl + Left pressionadas simultaneamente  |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyCtrlAndLeft(const long key_code)
  {
//--- Sai, se (1) não for a tecla Left ou se (2) a tecla Ctrl foi pressionada ou se (3) a caixa de texto não está ativada
   if(!(key_code==KEY_LEFT && m_keys.KeyCtrlState()) || !m_text_edit_state)
      return(false);
//--- Caractere de espaço
   string SPACE=" ";
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
// --- Obter o número de caracteres na linha atual
   uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Se o cursor estiver no início da linha atual, e ela não for a primeira linha,
//    move o cursor para o final da linha anterior
   if(m_text_cursor_x_pos==0 && m_text_cursor_y_pos>0)
     {
      //--- Obtém o índice da linha anterior
      uint prev_line_index=m_text_cursor_y_pos-1;
      //--- Obtém o número de caracteres na linha anterior
      symbols_total=::ArraySize(m_lines[prev_line_index].m_symbol);
      //--- Move o cursor para o final da linha anterior
      SetTextCursor(symbols_total,prev_line_index);
     }
//--- Se o cursor estiver no início da linha atual ou o cursor está na primeira linha
   else
     {
      //--- Localiza o início de uma sequência contínua de caracteres (da direita para a esquerda)
      for(uint i=m_text_cursor_x_pos; i<=symbols_total; i--)
        {
         //--- Vai para o próximo, se o cursor estiver na extremidade da linha
         if(i==symbols_total)
            continue;
         //--- Se este for o primeiro caractere da linha
         if(i==0)
           {
            //--- Coloca o cursor no início da linha
            SetTextCursor(0,m_text_cursor_y_pos);
            break;
           }
         //--- Se este não for o primeiro caractere da linha
         else
           {
            //--- Se foi encontrado no início de uma sequência contínua pela primeira vez.
            //    O início é considerado para ser o espaço para o próximo índice.
            if(i!=m_text_cursor_x_pos &&  
               m_lines[m_text_cursor_y_pos].m_symbol[i]!=SPACE &&  
               m_lines[m_text_cursor_y_pos].m_symbol[i-1]==SPACE)
              {
               //--- Coloca o cursor para no início de uma nova sequência contínua
               SetTextCursor(i,m_text_cursor_y_pos);
               break;
              }
           }
        }
     }
//--- Obtém os limites da parte visível da caixa de texto
   CalculateBoundaries();
//--- Obtém a coordenada X do cursor
   CalculateTextCursorX();
//--- Obtém a coordenada Y do cursor
   CalculateTextCursorY();
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_x<=m_x_limit)
      HorizontalScrolling(CalculateScrollThumbX());
   else
     {
      //--- Obtém o tamanho do conjunto de caracteres
      symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
      //---
      if(m_text_cursor_x_pos==symbols_total && m_text_cursor_x>=m_x2_limit)
         HorizontalScrolling(CalculateScrollThumbX2());
     }
//--- Move a barra de rolagem se o cursor de texto deixar a área de visibilidade
   if(m_text_cursor_y<=m_y_limit)
      VerticalScrolling(CalculateScrollThumbY());
//--- Atualiza o texto na caixa de texto
   DrawTextAndCursor(true);
//--- Envia uma mensagem sobre ele
   ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
   return(true);
  }

O estudo de todos os outros métodos da lista no início desta seção é deixado para o leitor. 

 


Integração do controle no motor da biblioteca

Para o controle Caixa de Texto Multilinha funcionar corretamente, um array privado será exigido na estrutura WindowElements da classe CWndContainer. Inclua o arquivo com a classe CTextBox no arquivo WndContainer.mqh:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Controls\TextBox.mqh"

Adiciona um matriz privado para o novo controle ao WindowElements estrutura: 

//+------------------------------------------------------------------+
//| Classe para armazenar todos os objetos da interface              |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Estrutura de arrays de elementos
   struct WindowElements
     {
      //--- Caixas de texto multilinha
      CTextBox         *m_text_boxes[];
     };
   //--- Array dos arrays de elemento para cada janela
   WindowElements    m_wnd[];
  };

Já que os controles do tipo CTextBox são compostos e contêm controles de outros tipos (neste caso, barras de rolagem), é necessário um método, em que os ponteiros para esses controles serão distribuídos para os arrays privadas correspondentes. Abaixo é exibido o código do método CWndContainer::AddTextBoxElements(), que é concebido para este propósito. Este método é chamado no mesmo local que qualquer outro método semelhante, ou seja, no CWndContainer::AddToElementsArray(). 

class CWndContainer
  {
private:
   //--- Armazena ponteiros aos objetos da caixa de texto multilinha
   bool              AddTextBoxElements(const int window_index,CElementBase &object);
  };
//+------------------------------------------------------------------+
//| Armazena ponteiros aos objetos da caixa de texto multilinha      |
//+------------------------------------------------------------------+
bool CWndContainer::AddTextBoxElements(const int window_index,CElementBase &object)
  {
//--- Sai, se não for uma caixa de texto multilinha
   if(dynamic_cast<CTextBox *>(&object)==NULL)
      return(false);
//--- Obtém o ponteiro para o controle
   CTextBox *tb=::GetPointer(object);
   for(int i=0; i<2; i++)
     {
      int size=::ArraySize(m_wnd[window_index].m_elements);
      ::ArrayResize(m_wnd[window_index].m_elements,size+1);
      if(i==0)
        {
         //--- Obtém o ponteiro da barra de rolagem
         CScrollV *sv=tb.GetScrollVPointer();
         m_wnd[window_index].m_elements[size]=sv;
         AddToObjectsArray(window_index,sv);
         //--- Adiciona o ponteiro para o array privado
         AddToRefArray(sv,m_wnd[window_index].m_scrolls);
        }
      else if(i==1)
        {
         CScrollH *sh=tb.GetScrollHPointer();
         m_wnd[window_index].m_elements[size]=sh;
         AddToObjectsArray(window_index,sh);
         //--- Adiciona o ponteiro para o array privado
         AddToRefArray(sh,m_wnd[window_index].m_scrolls);
        }
     }
//--- Adiciona o ponteiro para o array privado
   AddToRefArray(tb,m_wnd[window_index].m_text_boxes);
   return(true);
  }

Agora é necessário adicionar algo ao método CWndEvents::OnTimerEvent(). Lembre-se que a interface gráfica é redesenhada somente quando o cursor do mouse se move e é suspenso por um certo tempo após o movimento do cursor do mouse parar. Uma exceção deve ser feita para os controles do tipo CTextBox. Caso contrário, o cursor de texto não piscará quando a caixa de texto for ativada. 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CWndEvents::OnTimerEvent(void)
  {
//--- Sai, se o cursor do mouse está em repouso (se a diferença entre a chamada >300 ms) e o botão esquerdo do mouse está liberado
   if(m_mouse.GapBetweenCalls()>300 && !m_mouse.LeftButtonState())
     {
      int text_boxes_total=CWndContainer::TextBoxesTotal(m_active_window_index);
      for(int e=0; e<text_boxes_total; e++)
         m_wnd[m_active_window_index].m_text_boxes[e].OnEventTimer();
      //---
      return;
     }
//--- Se o array estiver vazio, retorna  
   if(CWndContainer::WindowsTotal()<1)
      return;
//--- Verificação dos eventos de todos os controles pelo timer
   CheckElementsEventsTimer();
//--- Redesenha o gráfico
   m_chart.Redraw();
  }

Agora, vamos criar um aplicativo MQL para teste, na qual permitirá testar o controle Caixa de Texto multilinha. 

 


Aplicação para testar o controle

Para o teste, crie um aplicativo MQL com uma interface gráfica que irá conter duas caixas de texto. Um deles será de uma única linha, e o outro — multilinha. Em adição a estas caixas de texto, a interface gráfica do exemplo conterá um menu principal com os menus de contexto e uma barra de estado. O segundo elemento da barra de estado irá transmitir a posição do cursor de texto da caixa de texto multilinha.

Crie duas instâncias da classe CTextBox e declare dois métodos para a criação da caixa de texto:

class CProgram : public CWndEvents
  {
protected:
   //--- Campos de Edição
   CTextBox          m_text_box1;
   CTextBox          m_text_box2;
   //---
protected:
   //--- Campos de Edição
   bool              CreateTextBox1(const int x_gap,const int y_gap);
   bool              CreateTextBox2(const int x_gap,const int y_gap);
  };

A lista abaixo mostra o código do segundo método para a criação de uma caixa de texto multilinha. Para ativar o modo multilinha, use o método CTextBox::MultiLineMode(). Para a área se ajustar automaticamente ao tamanho do formulário, isso deve ser feito usando o método CElementBase::AutoXResizeXXX(). Como exemplo, vamos adicionar o conteúdo deste artigo para a caixa de texto multilinha. Para fazer isso, prepare um array de linhas, que possa ser adicionado posteriormente a um ciclo utilizando os métodos especiais da classe CTextBox

//+------------------------------------------------------------------+
//| Cria uma caixa de texto multilinha                               |
//+------------------------------------------------------------------+
bool CProgram::CreateTextBox2(const int x_gap,const int y_gap)
  {
//--- Armazena o ponteiro da janela
   m_text_box2.WindowPointer(m_window);
//--- Define as propriedades antes da criação
   m_text_box2.FontSize(8);
   m_text_box2.Font("Calibri"); // Consolas|Calibri|Tahoma
   m_text_box2.AreaColor(clrWhite);
   m_text_box2.TextColor(clrBlack);
   m_text_box2.MultiLineMode(true);
   m_text_box2.AutoXResizeMode(true);
   m_text_box2.AutoXResizeRightOffset(2);
   m_text_box2.AutoYResizeMode(true);
   m_text_box2.AutoYResizeBottomOffset(24);
//--- Array de linhas
   string lines_array[]=
     {
      "Introdução",
      "Grupos de teclas e layouts do teclado",
      "Manipulação do evento tecla pressionada",
      "Códigos ASCII de caracteres e teclas de controle.",
      "Código de mapeamento das teclas",
      "Classe auxiliar para se trabalhar com o teclado",
      "O controle Caixa de Texto Multilinha",
      "Desenvolvimento da classe CTextBox para criação do controle",
      "Propriedades e aparência",
      "Gerenciamento do cursor de texto",
      "Inserção de um caractere",
      "Manipulação da tecla Backspace pressionada",
      "Manipulação da tecla Enter pressionada",
      "Manipulação das teclas Left e Right pressionadas",
      "Manipulação das teclas Up e Down pressionadas",
      "Manipulação das teclas Home e End pressionadas",
      "Manipulação do pressionamento simultâneo das teclas em combinação com a tecla Ctrl",
      "Integração do controle no motor da biblioteca",
      "Aplicação para testar o controle",
      "Conclusão"
     };
//--- Adiciona o texto para a caixa de texto
   int lines_total=::ArraySize(lines_array);
   for(int i=0; i<lines_total; i++)
     {
      //--- Adiciona o texto para a primeira linha
      if(i==0)
         m_text_box2.AddText(0,lines_array[i]);
      //--- Adiciona uma linha para a caixa de texto
      else
         m_text_box2.AddLine(lines_array[i]);
     }
//--- Cria o controle
   if(!m_text_box2.CreateTextBox(m_chart_id,m_subwin,x_gap,y_gap))
      return(false);
//--- Adiciona o objeto para o array comum dos grupos de objetos
   CWndContainer::AddToElementsArray(0,m_text_box2);
//--- Define o texto para os itens da barra de estado
   m_status_bar.ValueToItem(1,m_text_box2.TextCursorInfo());
   return(true);
  }

Adicione o seguinte código ao manipulador de eventos da aplicação MQL, a fim de receber mensagens da caixas de texto:

//+------------------------------------------------------------------+
//| Manipulador de eventos do gráfico                                |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Evento de (1) inserir um valor ou (2) ativar a caixa de texto ou (3) mover o cursor de texto
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT ||
      id==CHARTEVENT_CUSTOM+ON_CLICK_TEXT_BOX ||
      id==CHARTEVENT_CUSTOM+ON_MOVE_TEXT_CURSOR)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);

      //--- Se os identificadores corresponderem (mensagem da caixa de texto multilinha)
      if(lparam==m_text_box2.Id())
        {
         //--- Atualiza o segundo item da barra de estado
         m_status_bar.ValueToItem(1,sparam);
        }
      //--- Redesenha o gráfico
      m_chart.Redraw();
      return;
     }
  }


Depois de compilar a aplicação e carregá-la ao gráfico, podemos ver o seguinte:

 Fig. 9. Demonstração da interface gráfica com o controle da caixa de texto

Fig. 9. Demonstração da interface gráfica com o controle da caixa de texto

 

O aplicativo de teste apresentado no artigo pode ser baixado usando o link abaixo para estudá-lo ainda mais. 

 


Conclusão

A esquemática da biblioteca para a criação das interfaces gráficas no atual estágio de desenvolvimento é parecido com a imagem abaixo:

 Fig. 10. Estrutura da biblioteca no atual estágio de desenvolvimento.

Fig. 10. Estrutura da biblioteca no atual estágio de desenvolvimento.

 

A próxima versão da biblioteca irá continuar com o desenvolvimento, novas funcionalidades serão adicionadas aos controles já implementados. Abaixo, você pode baixar a versão mais recente da biblioteca e seus arquivos de teste.

Se você tiver dúvidas sobre a utilização do material a partir desses arquivos, você poderá consultar a descrição detalhada do desenvolvimento da biblioteca em um dos artigos da lista abaixo ou fazer sua pergunta nos comentários deste artigo. 


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

Arquivos anexados |
Interfaces Gráficas X: Novos recursos para a tabela Renderizada (build 9) Interfaces Gráficas X: Novos recursos para a tabela Renderizada (build 9)
Até agora, o tipo mais avançado de tabelas já desenvolvido em nossa biblioteca foi a CTable. Esta tabela é montada a partir de caixas de edição do tipo OBJ_EDIT, e seu posterior desenvolvimento tornou-se problemático. Portanto, em termos de capacidades máximas, é melhor desenvolver tabelas renderizadas do tipo CCanvasTable mesmo no atual estágio de desenvolvimento da biblioteca. Sua versão atual está completamente inerte, mas a partir deste artigo, nós vamos tentar corrigir esta situação.
Cálculo do coeficiente de Hurst Cálculo do coeficiente de Hurst
No artigo são apresentados em detalhes o propósito do expoente de Hurst, a interpretação de seus valores e o algoritmo de cálculo. São ilustrados os resultados da análise de alguns segmentos dos mercados financeiros e é apresentado o método de trabalho com softwares MetaTrader 5 que implementam a ideia da análise fractal.
Padrões disponíveis para negociação de cestas de moedas. Parte II Padrões disponíveis para negociação de cestas de moedas. Parte II
Continuação da conversa sobre padrões que podem ser detectados pelo trader ao operar pares de moedas. Esta parte descreve os padrões formados durante o uso combinado de indicadores de tendência. Como ferramentas de análise são usados indicadores construídos com base num índice de moeda.
Visualize isto! Biblioteca gráfica em linguagem MQL5 como equivalente a plot de R Visualize isto! Biblioteca gráfica em linguagem MQL5 como equivalente a plot de R
A exibição visual usando gráficos desempenha um importante papel na exploração e estudo de padrões regulares. Nas populares linguagens de programação entre a comunidade científica, tais como R e Python, a função especial plot é destinada para visualização. Com ela você pode desenhar linhas, gráficos de dispersão e histogramas para visualizar padrões. Em linguagem MQL5 você pode fazer a mesma coisa usando a classe CGraphics.