Interfaces Gráficas X: O controle Caixa de Texto Multilinha (build 8)
Anatoli Kazharski | 29 março, 2017
Conteúdo
- 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 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
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).
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.
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 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.
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. 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
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.
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.