
Interfaces gráficas X: Seleção de texto na caixa de texto multilinha (build 13)
Índice
- Introdução
- Monitorando o pressionamento da tecla Shift
- Combinações de teclas para selecionar texto
- Métodos de seleção do texto
- Métodos para excluir o texto selecionado
- Classe para trabalhar com dados de imagem
- Conclusão
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.
- Interfaces gráficas X: O controle caixa de texto multilinha (build 8)
- Interfaces gráficas X: Algoritmo de quebra de linha na caixa de texto multilinha (build 12)
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.
- 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.
- 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.
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.
- 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.
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:
- 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
- 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
- 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.
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.
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.
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 pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/3197





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso