Download MetaTrader 5

Interfaces gráficas X: Seleção de texto na caixa de texto multilinha (build 13)

3 julho 2017, 09:39
Anatoli Kazharski
0
433

Índice

Introdução

O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) considera em detalhes a finalidade desta biblioteca. A lista completa dos links para os artigos da primeira parte se encontram 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.

Para a utilização completa da caixa de texto multilinha, considerada nos artigos listados abaixo, é necessário implementar a seleção de texto, pois a exclusão de texto de um caractere por vez é muito inconveniente.

A seleção do texto usando as combinações de teclas e a exclusão do texto selecionado será exatamente o mesmo que em qualquer outro editor de texto. Além disso, vamos continuar com a otimização do código e prepararemos as classes para avançar para o processo final do segundo estágio da evolução da biblioteca, onde todos os controles serão renderizados como imagens separadas (telas). 

A versão final deste controle será apresentada aqui. Modificações subsequentes serão realizadas somente se for encontrado soluções mais eficientes em relação a determinado algoritmo.

Monitorando o pressionamento da tecla Shift

Primeiramente, nós vamos adicionar o método CKeys::KeyShiftState() para determinar o estado atual da tecla Shift, considerada anteriormente na classe CKeys, que foi projetada para trabalhar com o teclado. Esta tecla será usada em várias combinações para seleção do texto. Abaixo é exibido o código deste método simples. A tecla Shift é considerada pressionada, se a função ::TerminalInfoInteger() com o identificador TERMINAL_KEYSTATE_SHIFT retornar um valor inferior a zero.

//+------------------------------------------------------------------+
//| Classe para trabalhar com o teclado                              |
//+------------------------------------------------------------------+
class CKeys
  {
public:
   //--- Retorna o estado da tecla Shift
   bool              KeyShiftState(void);
  };
//+------------------------------------------------------------------+
//| Retorna o estado da tecla Shift                                  |
//+------------------------------------------------------------------+
bool CKeys::KeyShiftState(void)
  {
   return(::TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT)<0);
  }


Combinações de teclas para selecionar texto

Vamos considerar todas as combinações de teclas para seleção do texto, que será implementado na caixa de texto. Começaremos com uma combinação de duas teclas.

  • As combinações 'Shift + Left' e 'Shift + Right' deslocam o cursor de texto para a esquerda e para a direita por um caractere, respectivamente. O texto é destacado com uma cor diferente do fundo e do caractere (podendo ser ajustado pelo usuário):

 Fig. 1 Seleção do texto com um deslocamento do lado esquerdo e direito por um caractere.

Fig. 1 Seleção do texto com um deslocamento do lado esquerdo e direito por um caractere.

  • As combinações 'Shift + Home' e 'Shift + End' deslocam o cursor de texto para o início e o fim da linha, selecionando todos os caracteres da posição inicial do cursor.

 Fig. 2. Seleção de texto com o cursor deslocado da posição inicial para o começo e fim da linha.

Fig. 2. Seleção de texto com o cursor deslocado da posição inicial para o começo e fim da linha.

  • As combinações 'Shift + Up' e 'Shift + Down' deslocam o cursor de texto para cima e para baixo por uma linha, respectivamente. O texto é selecionado do cursor até o começo da linha inicial e do final da última linha até o cursor. Se houver mais linhas entre o início e o final das linhas selecionadas, o texto será completamente selecionado.

 Fig. 3. Seleção de texto com deslocamento de uma linha para cima e para baixo.

Fig. 3. Seleção de texto com deslocamento de uma linha para cima e para baixo.

As combinações de três teclas são, às vezes, usadas para selecionar o texto. Por exemplo, quando é necessário selecionar rapidamente várias palavras em uma linha, uma seleção de caractere por caractere será muito tediosa. E se também é necessário selecionar o texto que consiste de várias linhas, mesmo uma seleção linha por linha não será conveniente. 

Em combinações de três teclas, é utilizado a tecla Ctrl além da Shift. Vamos considerar todas essas combinações, que serão implementadas neste artigo:

  • As combinações 'Ctrl + Shift + Left' e 'Ctrl + Shift + Right' têm o objetivo de selecionar o texto em palavras inteiras para a esquerda e a direita da posição atual do cursor de texto, respectivamente:

 Fig. 4. Seleção de texto com deslocamento para a esquerda e para a direita por uma palavra.

Fig. 4. Seleção de texto com deslocamento para a esquerda e para a direita por uma palavra.

  • As combinações 'Ctrl + Shift + Home' e 'Ctrl + Shift + End' permitem selecionar todo o texto até o início da primeira e o final das últimas linhas a partir da posição atual do cursor de texto:

 Fig. 5. Seleção de texto com o cursor deslocado para o início e fim do documento.

Fig. 5. Seleção de texto com o cursor deslocado para o início e fim do documento.

A próxima seção considera os métodos usados ​​na seleção de texto.


Métodos de seleção do texto

Por padrão, o texto selecionado é exibido com caracteres brancos em um fundo azul. Se necessário, os métodos CTextBox::SelectedBackColor() e CTextBox::SelectedTextColor() podem ser usados ​​para mudar as cores. 

class CTextBox : public CElement
  {
private:
   //--- Cor do caractere e de fundo do texto selecionado
   color             m_selected_back_color;
   color             m_selected_text_color;
   //---
private:
   //--- Cor do caractere e de fundo do texto selecionado
   void              SelectedBackColor(const color clr)        { m_selected_back_color=clr;       }
   void              SelectedTextColor(const color clr)        { m_selected_text_color=clr;       }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CTextBox::CTextBox(void) : m_selected_text_color(clrWhite),
                           m_selected_back_color(C'51,153,255')
  {
//...
  }

Para selecionar o texto, são necessários os campos e métodos para designar os índices inicial e final das linhas e os caracteres do texto selecionado. Além disso, um método será necessário para redefinir esses valores quando a seleção for cancelada.

Toda vez que for pressionado uma combinação de teclas para selecionar o texto, o método CTextBox::SetStartSelectedTextIndexes() será chamado antes de mudar o cursor de texto. Ele define a linha inicial e os valores do índice de caracteres, onde o cursor de texto está localizado. Os valores serão definidos apenas se for a primeira chamada para o método após o último reset desses valores. O cursor é deslocado depois que este método é chamado. Então o método CTextBox::SetEndSelectedTextIndexes() é chamado, que define os valores finais da linha e dos índices de caracteres (ou seja, a posição do cursor do cursor de texto). Se durante o processo de mover o cursor de texto no modo de seleção de texto for verificado que o cursor está localizado logo no começo, os valores são redefinidos chamando o método CTextBox::ResetSelectedText(). Os valores também são redefinidos em qualquer movimento do cursor de texto, exclusão do texto selecionado ou desativação da caixa de texto.

class CTextBox : public CElement
  {
private:
   //--- Os índices de início e fim das linhas e caracteres (do texto selecionado)
   int               m_selected_line_from;
   int               m_selected_line_to;
   int               m_selected_symbol_from;
   int               m_selected_symbol_to;
   //---
private:
   //--- Define o índice de (1) início e (2) fim para a seleção de texto
   void              SetStartSelectedTextIndexes(void);
   void              SetEndSelectedTextIndexes(void);
   //--- Redefine o texto selecionado
   void              ResetSelectedText(void);
  };
//+------------------------------------------------------------------+
//| Define os índices de início para selecionar o texto              |
//+------------------------------------------------------------------+
void CTextBox::SetStartSelectedTextIndexes(void)
  {
//--- Se os índices iniciais para a seleção do texto ainda não foram definidos
   if(m_selected_line_from==WRONG_VALUE)
     {
      m_selected_line_from   =(int)m_text_cursor_y_pos;
      m_selected_symbol_from =(int)m_text_cursor_x_pos;
     }
  }
//+------------------------------------------------------------------+
//| Define os índices finais para selecionar texto                   |
//+------------------------------------------------------------------+
void CTextBox::SetEndSelectedTextIndexes(void)
  {
//--- Define os índices finais para selecionar o texto
   m_selected_line_to   =(int)m_text_cursor_y_pos;
   m_selected_symbol_to =(int)m_text_cursor_x_pos;
//--- Se todos os índices forem iguais, limpa a seleção
   if(m_selected_line_from==m_selected_line_to && m_selected_symbol_from==m_selected_symbol_to)
      ResetSelectedText();
  }
//+------------------------------------------------------------------+
//| Redefine o texto selecionado                                     |
//+------------------------------------------------------------------+
void CTextBox::ResetSelectedText(void)
  {
   m_selected_line_from   =WRONG_VALUE;
   m_selected_line_to     =WRONG_VALUE;
   m_selected_symbol_from =WRONG_VALUE;
   m_selected_symbol_to   =WRONG_VALUE;
  }

Os blocos de código anteriormente utilizados nos métodos para mover o cursor agora são implementados como métodos separados, pois serão usados ​​repetidamente nos métodos de seleção de texto. O mesmo se aplica ao código para ajustar as barras de rolagem nos casos em que o cursor de texto ultrapassar a área visível. 

class CTextBox : public CElement
  {
private:
   //--- Movendo o cursor de texto para a esquerda por um caractere
   void              MoveTextCursorToLeft(void);
   //--- Movendo o cursor de texto para a direita por um caractere
   void              MoveTextCursorToRight(void);
   //--- Movendo o cursor do texto para cima por um caractere
   void              MoveTextCursorToUp(void);
   //--- Movendo o cursor do texto para baixo por um caractere
   void              MoveTextCursorToDown(void);

   //--- Ajustando a barra de rolagem horizontal
   void              CorrectingHorizontalScrollThumb(void);
   //--- Ajustando a barra de rolagem vertical
   void              CorrectingVerticalScrollThumb(void);
  };

Todos os métodos para lidar com o pressionamento das combinações de teclas (uma deles sendo a Shift) contém praticamente o mesmo código, exceto para chamar o método para mover o cursor de texto. Portanto, é razoável criar um método adicional que simplesmente possa ser passado a direção do movimento do cursor do texto. A enumeração ENUM_MOVE_TEXT_CURSOR com vários identificadores (veja a lista abaixo) foi adicionada ao arquivo Enums.mqh. Eles podem ser usados ​​para indicar onde o cursor do texto precisa ser movido:

  • TO_NEXT_LEFT_SYMBOL — um caractere à esquerda.
  • TO_NEXT_RIGHT_SYMBOL — um caractere à direita.
  • TO_NEXT_LEFT_WORD — uma palavra à esquerda.
  • TO_NEXT_RIGHT_WORD — uma palavra à direita.
  • TO_NEXT_UP_LINE — uma linha para acima.
  • TO_NEXT_DOWN_LINE — uma linha para baixo.
  • TO_BEGIN_LINE — o começo da linha atual.
  • TO_END_LINE — o fim da linha atual.
  • TO_BEGIN_FIRST_LINE — o começo da primeira linha.
  • TO_END_LAST_LINE — o fim da última linha.
//+------------------------------------------------------------------+
//| Enumeração da direção do movimento do cursor de texto            |
//+------------------------------------------------------------------+
enum ENUM_MOVE_TEXT_CURSOR
  {
   TO_NEXT_LEFT_SYMBOL  =0,
   TO_NEXT_RIGHT_SYMBOL =1,
   TO_NEXT_LEFT_WORD    =2,
   TO_NEXT_RIGHT_WORD   =3,
   TO_NEXT_UP_LINE      =4,
   TO_NEXT_DOWN_LINE    =5,
   TO_BEGIN_LINE        =6,
   TO_END_LINE          =7,
   TO_BEGIN_FIRST_LINE  =8,
   TO_END_LAST_LINE     =9
  };

Agora podemos criar um método geral comum para mover o cursor de texto — CTextBox::MoveTextCursor(), onde passar um dos identificadores da lista acima é suficiente. O mesmo método agora será usado em praticamente todos os métodos que manipulam os eventos de pressionamento das teclas do controle CTextBox.

class CTextBox : public CElement
  {
private:
   //--- Move o cursor do texto na direção especificada
   void              MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction);
  };
//+------------------------------------------------------------------+
//| Move o cursor do texto na direção especificada                   |
//+------------------------------------------------------------------+
void CTextBox::MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction)
  {
   switch(direction)
     {
      //--- Move o cursor para um caractere à esquerda
      case TO_NEXT_LEFT_SYMBOL  : MoveTextCursorToLeft();        break;
      //--- Move o cursor para um caractere à direita
      case TO_NEXT_RIGHT_SYMBOL : MoveTextCursorToRight();       break;
      //--- Move o cursor uma palavra para a esquerda
      case TO_NEXT_LEFT_WORD    : MoveTextCursorToLeft(true);    break;
      //--- Move o cursor uma palavra para a direita
      case TO_NEXT_RIGHT_WORD   : MoveTextCursorToRight(true);   break;
      //--- Move o cursor para uma linha acima
      case TO_NEXT_UP_LINE      : MoveTextCursorToUp();          break;
      //--- Move o cursor para uma linha abaixo
      case TO_NEXT_DOWN_LINE    : MoveTextCursorToDown();        break;
      //--- Move o cursor para o início da linha atual
      case TO_BEGIN_LINE : SetTextCursor(0,m_text_cursor_y_pos); break;
      //--- Move o cursor para o final da linha atual
      case TO_END_LINE :
        {
         // --- Obter o número de caracteres na linha atual
         uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
         //--- Move the cursor
         SetTextCursor(symbols_total,m_text_cursor_y_pos);
         break;
        }
      //--- Move o cursor para o início da primeira linha
      case TO_BEGIN_FIRST_LINE : SetTextCursor(0,0); break;
      //--- Move o cursor para o final da última linha
      case TO_END_LAST_LINE :
        {
         //--- Obtém o número de linhas e caracteres na última linha
         uint lines_total   =::ArraySize(m_lines);
         uint symbols_total =::ArraySize(m_lines[lines_total-1].m_symbol);
         //--- Move the cursor
         SetTextCursor(symbols_total,lines_total-1);
         break;
        }
     }
  }

O código neste arquivo pode ser significativamente reduzido, pois os métodos do manipulador do movimento do cursor do texto e os eventos de seleção de texto possuem muitos blocos de código repetidos. 

Exemplo de um bloco de código repetido nos métodos para mover o cursor de texto:

//+------------------------------------------------------------------+
//| Manipulação do pressionamento da tecla Left                      |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyLeft(const long key_code)
  {
//--- Retorna, se (1) não é a tecla Left ou (2), a tecla Ctrl é pressionada ou (3) a tecla Shift é pressionada
   if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || m_keys.KeyShiftState())
      return(false);
//--- Redefine a seleção
   ResetSelectedText();
//--- Desloca o cursor de texto para a esquerda por um caractere
   MoveTextCursor(TO_NEXT_LEFT_SYMBOL);
//--- Ajusta as barras de rolagem
   CorrectingHorizontalScrollThumb();
   CorrectingVerticalScrollThumb();
//--- 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);
  }

Exemplo de bloco de código repetido nos métodos para selecionar o texto:

//+------------------------------------------------------------------+
//| Manipulação do pressionamento das teclas Shift + Left            |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyShiftAndLeft(const long key_code)
  {
//--- Retorna, se (1) não é a tecla Left ou (2), a tecla Ctrl é pressionada ou (3) a tecla Shift não está pressionada
   if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || !m_keys.KeyShiftState())
      return(false);
//--- Define os índices de início para selecionar o texto
   SetStartSelectedTextIndexes();
//--- Desloca o cursor de texto para a esquerda por um caractere
   MoveTextCursor(TO_NEXT_LEFT_SYMBOL);
//--- Define os índices finais para selecionar o texto
   SetEndSelectedTextIndexes();
//--- Ajusta as barras de rolagem
   CorrectingHorizontalScrollThumb();
   CorrectingVerticalScrollThumb();
//--- 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);
  }


Vamos implementar mais um método adicional (sobrecarregado) CTextBox::MoveTextCursor(). O método deverá passar o identificador com a direção do movimento, bem como o sinalizador indicando (1) se foi um movimento do cursor de texto ou (2) uma seleção de texto.

class CTextBox : public CElement
  {
private:
   //--- Move o cursor do texto na direção especificada
   void              MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction,const bool with_highlighted_text);
  };
//+------------------------------------------------------------------+
//| Movendo o cursor de texto na direção especificada e              |
//| com uma condição                                                 |
//+------------------------------------------------------------------+
void CTextBox::MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction,const bool with_highlighted_text)
  {
//--- Se é apenas um movimento do cursor de texto
   if(!with_highlighted_text)
     {
      //--- Redefine a seleção
      ResetSelectedText();
      //--- Move o cursor para o início da primeira linha
      MoveTextCursor(direction);
     }
//--- Se a seleção de texto estiver ativada
   else
     {
      //--- Define os índices de início para selecionar o texto
      SetStartSelectedTextIndexes();
      //--- Desloca o cursor de texto para a esquerda por um caractere
      MoveTextCursor(direction);
      //--- Define os índices finais para selecionar o texto
      SetEndSelectedTextIndexes();
     }
//--- Ajusta as barras de rolagem
   CorrectingHorizontalScrollThumb();
   CorrectingVerticalScrollThumb();
//--- 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());
  }


Os métodos para manipular as combinações de teclas para selecionar o texto são exibidas abaixo. Seu código é quase idêntico (eles apenas diferem em parâmetros). Portanto, você pode estudar o código nos arquivos anexados ao artigo:

class CTextBox : public CElement
  {
private:
   //--- Manipulação do pressionamento das teclas Shift + Left
   bool              OnPressedKeyShiftAndLeft(const long key_code);
   //--- Manipulação do pressionamento das teclas Shift + Right
   bool              OnPressedKeyShiftAndRight(const long key_code);
   //--- Manipulação do pressionamento das teclas Shift + Up
   bool              OnPressedKeyShiftAndUp(const long key_code);
   //--- Manipulação do pressionamento das teclas Shift + Down
   bool              OnPressedKeyShiftAndDown(const long key_code);
   //--- Manipulação do pressionamento das teclas Shift + Home
   bool              OnPressedKeyShiftAndHome(const long key_code);
   //--- Manipulação do pressionamento das teclas Shift + End
   bool              OnPressedKeyShiftAndEnd(const long key_code);

   //--- Manipulação do pressionamento das teclas Ctrl + Shift + Left
   bool              OnPressedKeyCtrlShiftAndLeft(const long key_code);
   //--- Manipulação do pressionamento das teclas Ctrl + Shift + Right
   bool              OnPressedKeyCtrlShiftAndRight(const long key_code);
   //--- Manipulação do pressionamento das teclas Ctrl + Shift + Home
   bool              OnPressedKeyCtrlShiftAndHome(const long key_code);
   //--- Manipulação do pressionamento das teclas Ctrl + Shift + End
   bool              OnPressedKeyCtrlShiftAndEnd(const long key_code);
  };


Até agora, o texto foi aplicado a tela em linhas inteiras. Mas, uma vez que os caracteres selecionados e o fundo sob eles mudam de cor, o texto deve ser exibido em caracteres. Para fazer isso, permita-nos realizar algumas pequenas mudanças no método CTextBox::TextOut(). 

Ele também exigirá um método adicional CTextBox::CheckSelectedText() para verificar os caracteres selecionados. Nós já sabemos que durante a seleção de texto, que os índices das linhas iniciais e finais e os caracteres do cursor de texto são armazenados. Portanto, é fácil determinar se um caractere em uma linha é selecionado ou não através da iteração sobre os caracteres em um loop. A lógica é simples:

  1. Se o índice inicial da linha for menor do que a final, o caractere será selecionado:
    • Se esta é a linha final e o caractere está à direita da seleção final selecionada
    • Se esta for a linha inicial e o caractere estiver à esquerda da seleção inicial selecionada
    • Todos os caracteres são selecionados nas linhas intermediárias
  2. Se o índice inicial da linha for maior do que o final, o caractere será selecionado:
    • Se esta é a linha final e o caractere está à esquerda da seleção final selecionada
    • Se esta for a linha inicial e o caractere estiver à direita da seleção inicial selecionada
    • Todos os caracteres são selecionados nas linhas intermediárias
  3. Se o texto for selecionado apenas em uma linha, um caractere será selecionado se estiver dentro do intervalo designado entre os índices de caracteres inicial e final.
class CTextBox : public CElement
  {
private:
   //--- Verifica a presença do texto selecionado
   bool              CheckSelectedText(const uint line_index,const uint symbol_index);
  };
//+------------------------------------------------------------------+
//| Verifica a presença do texto selecionado                         |
//+------------------------------------------------------------------+
bool CTextBox::CheckSelectedText(const uint line_index,const uint symbol_index)
  {
   bool is_selected_text=false;
//--- Retorna, se não houver nenhum texto selecionado
   if(m_selected_line_from==WRONG_VALUE)
      return(false);
//--- Se o índice inicial estiver na linha abaixo
   if(m_selected_line_from>m_selected_line_to)
     {
      //--- A linha final e o caractere à direita daquele último selecionado
      if((int)line_index==m_selected_line_to && (int)symbol_index>=m_selected_symbol_to)
        { is_selected_text=true; }
      //--- A linha inicial e o caractere à esquerda da seleção inicial selecionada
      else if((int)line_index==m_selected_line_from && (int)symbol_index<m_selected_symbol_from)
        { is_selected_text=true; }
      //--- Linha intermediária (todos os caracteres são selecionados)
      else if((int)line_index>m_selected_line_to && (int)line_index<m_selected_line_from)
        { is_selected_text=true; }
     }
//--- Se o índice inicial estiver na linha acima
   else if(m_selected_line_from<m_selected_line_to)
     {
      //--- A linha final e o caractere à esquerda daquele último selecionado
      if((int)line_index==m_selected_line_to && (int)symbol_index<m_selected_symbol_to)
        { is_selected_text=true; }
      //--- A linha inicial e o caractere à direita da seleção inicial selecionada
      else if((int)line_index==m_selected_line_from && (int)symbol_index>=m_selected_symbol_from)
        { is_selected_text=true; }
      //--- Linha intermediária (todos os caracteres são selecionados)
      else if((int)line_index<m_selected_line_to && (int)line_index>m_selected_line_from)
        { is_selected_text=true; }
     }
//--- Se os índices inicial e final estiverem na mesma linha
   else
     {
      //--- Encontre a linha marcada
      if((int)line_index>=m_selected_line_to && (int)line_index<=m_selected_line_from)
        {
         //--- Se o cursor for deslocado para a direita e o caractere estiver dentro do intervalo selecionado
         if(m_selected_symbol_from>m_selected_symbol_to)
           {
            if((int)symbol_index>=m_selected_symbol_to && (int)symbol_index<m_selected_symbol_from)
               is_selected_text=true;
           }
         //--- Se o cursor for deslocado para a esquerda e o caractere estiver dentro do intervalo selecionado
         else
           {
            if((int)symbol_index>=m_selected_symbol_from && (int)symbol_index<m_selected_symbol_to)
               is_selected_text=true;
           }
        }
     }
//--- Retorna o resultado
   return(is_selected_text);
  }

No método CTextBox::TextOut() projetado para a saída de texto, é necessário adicionar um loop interno com uma iteração sobre os caracteres da linha em vez de exibir toda a linha. Ele determina se o caractere verificado está selecionado. Caso o caractere seja selecionado, sua cor é determinada e um retângulo preenchido é desenhado sob o caractere. Depois de tudo isso, o próprio caractere é a saída

class CTextBox : public CElement
  {
private:
   //--- Sáida de texto para a tela
   void              TextOut(void);
  };
//+------------------------------------------------------------------+
//| Saída de texto para a tela                                       |
//+------------------------------------------------------------------+
void CTextBox::TextOut(void)
  {
//--- Limpa a tela
   m_canvas.Erase(AreaColorCurrent());
//--- Obtém o tamanho do array de linhas
   uint lines_total=::ArraySize(m_lines);
//--- Corrige em caso do tamanho ter sido excedido
   m_text_cursor_y_pos=(m_text_cursor_y_pos>=lines_total)? lines_total-1 : 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);
//--- 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 largura da linha
      int line_width=(int)LineWidth(m_text_cursor_x_pos,m_text_cursor_y_pos);
      //--- Obtém a altura da linha e itera sobre todas as linhas em um loop
      int line_height=(int)LineHeight();
      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);
         //--- Obtém o tamanho da string
         uint string_length=::ArraySize(m_lines[i].m_symbol);
         //--- Desenha o texto
         for(uint s=0; s<string_length; s++)
           {
            uint text_color=TextColorCurrent();
            //--- Se houver um texto selecionado, determina sua cor e a cor de fundo do caractere atual
            if(CheckSelectedText(i,s))
              {
               //--- Cor do texto selecionado
               text_color=::ColorToARGB(m_selected_text_color);
               //--- Calcula as coordenadas para desenhar o fundo
               int x2=x+m_lines[i].m_width[s];
               int y2=y+line_height-1;
               //--- Desenha a cor de fundo do caractere
               m_canvas.FillRectangle(x,y,x2,y2,::ColorToARGB(m_selected_back_color));
              }
            //--- Desenha o caractere
            m_canvas.TextOut(x,y,m_lines[i].m_symbol[s],text_color,TA_LEFT);
            //--- Coordenada X para o próximo caractere
            x+=m_lines[i].m_width[s];
           }
        }
     }
//--- 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);
     }
  }

Os métodos para selecionar texto foram implementados, e é assim que ele se parece no aplicativo concluído:

 Fig. 6. Demonstração de seleção de texto na caixa de texto implementada do aplicativo MQL.

Fig. 6. Demonstração de seleção de texto na caixa de texto implementada do aplicativo MQL.


Métodos para excluir o texto selecionado

Agora, considere os métodos para excluir o texto selecionado. É importante notar aqui que diferentes métodos serão aplicados ao excluir o texto selecionado, dependendo se ele for selecionado em uma ou várias linhas. 

O método CTextBox::DeleteTextOnOneLine() será chamado para excluir o texto selecionado em uma única linha. O número de caracteres a serem excluídos é determinado no início do método. Então, se o índice inicial do caractere de texto selecionado estiver à direita, os caracteres serão deslocados diretamente a partir dessa posição inicial pelo número de caracteres a serem excluídos. Depois disso, o array de caracteres da linha é decrementado pela mesma quantidade. 

Nos casos em que o índice inicial do caractere do texto selecionado está à esquerda, o o cursor de texto também precisa ser deslocado para a direita pelo número de caracteres a serem excluídos.

class CTextBox : public CElement
  {
private:
   //--- Exclui o texto selecionado em uma linha
   void              DeleteTextOnOneLine(void);
  };
//+------------------------------------------------------------------+
//| Exclui o texto selecionado em uma linha                          |
//+------------------------------------------------------------------+
void CTextBox::DeleteTextOnOneLine(void)
  {
   int symbols_total     =::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
   int symbols_to_delete =::fabs(m_selected_symbol_from-m_selected_symbol_to);
//--- Se o índice inicial do caractere estiver à direita
   if(m_selected_symbol_to<m_selected_symbol_from)
     {
      //--- Desloca os caracteres para a área liberada na linha atual
      MoveSymbols(m_text_cursor_y_pos,m_selected_symbol_from,m_selected_symbol_to);
     }
//--- Se o índice inicial do caractere estiver à esquerda
   else
     {
      //--- Desloca o cursor de texto para a esquerda pelo número de caracteres a serem excluídos
      m_text_cursor_x_pos-=symbols_to_delete;
      //--- Desloca os caracteres para a área liberada na linha atual
      MoveSymbols(m_text_cursor_y_pos,m_selected_symbol_to,m_selected_symbol_from);
     }
//--- Diminui o tamanho do array da linha atual pelo número de caracteres extraídos
   ArraysResize(m_text_cursor_y_pos,symbols_total-symbols_to_delete);
  }


O método CTextBox::DeleteTextOnMultipleLines() será usado para excluir várias linhas de texto selecionado. O algoritmo é mais complicado aqui. Primeiro, é necessário determinar:

  • O número total de caracteres nas linhas inicial e final
  • O número de linhas intermediárias do texto selecionado (exceto as linhas inicial e final)
  • O número de caracteres a serem excluídos nas linhas inicial e final.

A sequência de outras ações é dada abaixo. Dependendo da direção em que o texto é selecionado (para cima ou para baixo), os índices inicial e final serão passados ​​para outros métodos.

  • Os caracteres a serem movidos de uma linha para outra, que permanecerão após a exclusão, são copiados para um array dinâmico temporário.
  • O array receptor (linha) é redimensionado.
  • Os dados são adicionados aos arrays da estrutura da linha do receptor.
  • As linhas são deslocadas pelo número de linhas excluídas.
  • O array de linhas é redimensionado (decrementado pelo número de linhas excluídas).
  • Caso a linha inicial esteja acima da linha final (a seleção de texto é para baixo), o cursor de texto é deslocado para os índices iniciais (linha e caractere) do texto selecionado.
class CTextBox : public CElement
  {
private:
   //--- Exclui o texto selecionado em várias linhas
   void              DeleteTextOnMultipleLines(void);
  };
//+------------------------------------------------------------------+
//| Exclui o texto selecionado em várias linhas                      |
//+------------------------------------------------------------------+
void CTextBox::DeleteTextOnMultipleLines(void)
  {
//--- O número total de caracteres nas linhas inicial e final
   uint symbols_total_line_from =::ArraySize(m_lines[m_selected_line_from].m_symbol);
   uint symbols_total_line_to   =::ArraySize(m_lines[m_selected_line_to].m_symbol);
//--- O número de linhas intermediárias a serem excluídas
   uint lines_to_delete =::fabs(m_selected_line_from-m_selected_line_to);
//--- O número de caracteres a serem excluídos nas linhas inicial e final
   uint symbols_to_delete_in_line_from =::fabs(symbols_total_line_from-m_selected_symbol_from);
   uint symbols_to_delete_in_line_to   =::fabs(symbols_total_line_to-m_selected_symbol_to);
//--- Se a linha inicial estiver abaixo da linha final
   if(m_selected_line_from>m_selected_line_to)
     {
      //--- Copia os caracteres a serem movidos para o array
      string array[];
      CopyWrapSymbols(m_selected_line_from,m_selected_symbol_from,symbols_to_delete_in_line_from,array);
      //--- Redimensiona a linha do receptor
      uint new_size=m_selected_symbol_to+symbols_to_delete_in_line_from;
      ArraysResize(m_selected_line_to,new_size);
      //--- Adiciona os dados aos arrays da estrutura da linha do receptor
      PasteWrapSymbols(m_selected_line_to,m_selected_symbol_to,array);
      //--- Obtém o tamanho do array de linhas
      uint lines_total=::ArraySize(m_lines);
      //--- Desloca as linhas pelo número de linhas a serem excluídas
      MoveLines(m_selected_line_to+1,lines_total-lines_to_delete,lines_to_delete,false);
      //--- Redimensiona o array de linhas
      ::ArrayResize(m_lines,lines_total-lines_to_delete);
     }
//--- Se a linha inicial estiver acima da linha final
   else
     {
      //--- Copia os caracteres a serem movidos para o array
      string array[];
      CopyWrapSymbols(m_selected_line_to,m_selected_symbol_to,symbols_to_delete_in_line_to,array);
      //--- Redimensiona a linha do receptor
      uint new_size=m_selected_symbol_from+symbols_to_delete_in_line_to;
      ArraysResize(m_selected_line_from,new_size);
      //--- Adiciona os dados aos arrays da estrutura da linha do receptor
      PasteWrapSymbols(m_selected_line_from,m_selected_symbol_from,array);
      //--- Obtém o tamanho do array de linhas
      uint lines_total=::ArraySize(m_lines);
      //--- Desloca as linhas pelo número de linhas a serem excluídas
      MoveLines(m_selected_line_from+1,lines_total-lines_to_delete,lines_to_delete,false);
      //--- Redimensiona o array de linhas
      ::ArrayResize(m_lines,lines_total-lines_to_delete);
      //--- Move o cursor para a posição inicial na seleção
      SetTextCursor(m_selected_symbol_from,m_selected_line_from);
     }
  }

Qual dos métodos acima para chamar é determinado no método principal para excluir o texto — CTextBox::DeleteSelectedText(). Uma vez que o texto selecionado é excluído, os valores dos índices inicial e final são reiniciados. Depois disso, é necessário recalcular as dimensões da caixa de texto, pois o número de linhas pode ter mudado. Além disso, a largura máxima da linha poderia ter sido alterada, que é usada no cálculo da caixa de texto. No final, o método envia uma mensagem de que o cursor de texto se moveu. O método retorna true se o texto foi selecionado e removido. Se descobrir que não há texto selecionado no momento em que o método é chamado, ele retorna false

class CTextBox : public CElement
  {
private:
   //--- Exclui o texto selecionado
   void              DeleteSelectedText(void);
  };
//+------------------------------------------------------------------+
//| Exclui o texto selecionado                                       |
//+------------------------------------------------------------------+
bool CTextBox::DeleteSelectedText(void)
  {
//--- Retorna, se nenhum texto for selecionado
   if(m_selected_line_from==WRONG_VALUE)
      return(false);
//--- Se os caracteres são excluídos de uma linha
   if(m_selected_line_from==m_selected_line_to)
      DeleteTextOnOneLine();
//--- Se os caracteres são excluídos de várias linhas
   else
      DeleteTextOnMultipleLines();
//--- Redefine o texto selecionado
   ResetSelectedText();
//--- Calcula o tamanho da caixa de texto
   CalculateTextBoxSize();
//--- Define o novo tamanho da caixa de texto
   ChangeTextBoxSize();
//--- Ajusta as barras de rolagem
   CorrectingHorizontalScrollThumb();
   CorrectingVerticalScrollThumb();
//--- 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 método CTextBox::DeleteSelectedText() é chamado não apenas ao pressionar a tecla Backspace, mas também: (1) ao inserir um novo caractere (2) ao pressionar a tecla Enter. Nesses casos, o texto é excluído primeiro e, em seguida, a ação correspondente à tecla pressionada é executada.

É assim que se parece no aplicativo concluído:

 Fig. 7. Demonstração da exclusão do texto selecionado.

Fig. 7. Demonstração da exclusão do texto selecionado.

Classe para trabalhar com dados de imagem

Como complemento deste artigo, consideremos uma nova classe (CImage) para trabalhar com os dados de imagem. Ela será repetidamente usada em muitas classes dos controles da biblioteca, que precisam desenhar uma imagem. A classe está contida no arquivo Objects.mqh

Propriedades da classe:
  • array de pixels de imagem;
  • largura da imagem;
  • altura da imagem;
  • caminho do arquivo de imagem.
//+------------------------------------------------------------------+
//| Classe para armazenar os dados da imagem                         |
//+------------------------------------------------------------------+
class CImage
  {
protected:
   uint              m_image_data[]; // Array de pixels da imagem
   uint              m_image_width;  // Largura da imagem
   uint              m_image_height; // Altura da imagem
   string            m_bmp_path;     // Caminho do arquivo da imagem
public:
   //--- (1) Tamanho do array de dados, (2) define/retorna os dados (cor de pixel)
   uint              DataTotal(void)                              { return(::ArraySize(m_image_data)); }
   uint              Data(const uint data_index)                  { return(m_image_data[data_index]);  }
   void              Data(const uint data_index,const uint data)  { m_image_data[data_index]=data;     }
   //--- Define/retorna a largura da imagem
   void              Width(const uint width)                      { m_image_width=width;               }
   uint              Width(void)                                  { return(m_image_width);             }
   //--- Define/retorna a altura da imagem
   void              Height(const uint height)                    { m_image_height=height;             }
   uint              Height(void)                                 { return(m_image_height);            }
   //--- Define/retorna o caminho para a imagem
   void              BmpPath(const string bmp_file_path)          { m_bmp_path=bmp_file_path;          }
   string            BmpPath(void)                                { return(m_bmp_path);                }

  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CImage::CImage(void) : m_image_width(0),
                       m_image_height(0),
                       m_bmp_path("")
  {
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CImage::~CImage(void)
  {
  }

The CImage::ReadImageData() method is intended for saving the image and its properties. Este método lê a imagem no caminho especificado e armazena seus dados.

class CImage
  {
public:
   //--- Lê e armazena os dados da imagem passada
   bool              ReadImageData(const string bmp_file_path);
  };
//+------------------------------------------------------------------+
//| Armazena a imagem passada para um array                          |
//+------------------------------------------------------------------+
bool CImage::ReadImageData(const string bmp_file_path)
  {
//--- Reseta o último erro
   ::ResetLastError();
//--- Armazena o caminho para a imagem
   m_bmp_file_path=bmp_file_path;
//--- Lê e armazena os dados da imagem
   if(!::ResourceReadImage(m_bmp_file_path,m_image_data,m_image_width,m_image_height))
     {
      ::Print(__FUNCTION__," > erro: ",::GetLastError());
      return(false);
     }
//---
   return(true);
  }

Às vezes, pode ser necessário fazer uma cópia de uma imagem do mesmo tipo (CImage). Para esse fim, o método CImage::CopyImageData() foi implementado. No início do método, o tamanho do array do receptor é definido como o array de origem. Em seguida, os dados são copiados do array de origem para o array do receptor em um loop.

class CImage
  {
public:
   //--- Copia os dados da imagem passada
   void              CopyImageData(CImage &array_source);
  };
//+------------------------------------------------------------------+
//| Copia os dados da imagem passada                                 |
//+------------------------------------------------------------------+
void CImage::CopyImageData(CImage &array_source)
  {
//--- Obtém os tamanhos do array do receptor e da array de origem
   uint data_total        =DataTotal();
   uint source_data_total =::GetPointer(array_source).DataTotal();
//--- Redimensiona o array do receptor
   ::ArrayResize(m_image_data,source_data_total);
//--- Copia os dados
   for(uint i=0; i<source_data_total; i++)
      m_image_data[i]=::GetPointer(array_source).Data(i);
  }

Antes desta atualização, a classe CCanvasTable usava uma estrutura para armazenar os dados da imagem. Agora, com a introdução do classe CImage, as mudanças correspondentes foram feitas. 


Conclusão

Este artigo conclui o desenvolvimento do controle da Caixa de Texto Multilinha. Sua característica principal é que não há mais restrições no número de caracteres inseridos e várias linhas podem ser digitadas, o que sempre faltou no objeto gráfico padrão do tipo OBJ_EDIT. No próximo artigo, continuaremos desenvolvendo o tópico "Controles nas células da tabela", adicionando a capacidade de alterar valores nas células da tabela usando o controle discutido nesse artigo. Além disso, vários controles serão alternados para um novo modo: eles serão renderizados e não serão construídos a partir de vários objetos gráficos padrã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. 8. Estrutura da biblioteca no atual estágio de desenvolvimento.

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

Abaixo está a versão mais recente da biblioteca e os arquivos para testes, que foram demonstrados no artigo.

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 por MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/3197

Arquivos anexados |
Quanto dura a tendência? Quanto dura a tendência?

No artigo, são selecionadas várias maneiras de identificar a tendência, a fim de definir sua duração em relação ao estado de correção do mercado. Na teoria, acredita-se numa correlação tendência-fase de correção de 30% para 70%. Nós temos que verificar isso.

Criação de indicadores personalizados usando a classe CCanvas Criação de indicadores personalizados usando a classe CCanvas

O artigo descreve um exemplo de criação de indicadores personalizados usando as primitivas gráficas da classe CCanvas.

Exemplo de indicador que constrói uma linha de suporte e resistência Exemplo de indicador que constrói uma linha de suporte e resistência

O artigo mostra a implementação de um indicador para construção de linhas de suporte e de resistência com base em condições formais. Você não só poderá aplicar o indicador, mas também entenderá quão fácil é realizá-lo. Agora você será capaz de formular as condições para desenhar linhas alterando o código do indicador ligeiramente para atender às suas necessidades.

Previsão de movimentos do mercado utilizando a classificação Bayesiana e indicadores com base na análise de espectro singular Previsão de movimentos do mercado utilizando a classificação Bayesiana e indicadores com base na análise de espectro singular

Nesta pesquisa, são consideradas uma ideologia e metodologia a fim de construir um sistema de recomendação para negociar rápido com base na combinação de possibilidades de previsão com ajuda da Análise de Espetro Singular (SSA) e o método de aprendizado de máquina baseado no teorema de Bayes.