Interfaces gráficas X: Selección del texto en el campo de edición multilínea (build 13)
Contenido
- Introducción
- Seguimiento de la pulsación de la tecla Shift
- Combinaciones de las teclas para seleccionar el texto
- Métodos para seleccionar el texto
- Métodos para eliminar el texto seleccionado
- Clase para trabajar con los datos de la imagen
- Conclusión
Introducción
El primer artículo de la serie nos cuenta con más detalles para qué sirve esta librería: Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1). Al final de los artículos de cada parte se puede encontrar la lista de los capítulos con los enlaces, así como descargar la versión completa de la librería en la fase actual del desarrollo del proyecto. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.
Para poder utilizar a 100% el campo de edición multilínea que hemos analizado en los artículos de la lista de abajo, nos queda implementar la selección del texto, ya que la eliminación de los caracteres uno por uno no es muy conveniente.
- Interfaces gráficas X: Control «Campo de edición del texto multilínea» (build 8)
- Interfaces gráficas X: Algoritmo del traslado de palabras en el campo de edición multilínea (build 12)
La selección del texto con diferentes combinaciones de teclas y la eliminación del texto seleccionado serán las mismas que en cualquier otro editor de texto. Además de eso, seguiremos optimizando el código y prepararemos las clases para el traspaso al proceso final de la segunda fase del desarrollo de la librería, cuando todos los controles estarán dibujados en las imágenes separadas (lienzos para el dibujado).
Aquí, será presentada la versión definitiva de este control de la interfaz de la librería. A continuación, los cambios van a introducirse sólo en el caso de encontrar las soluciones más eficaces respecto a algún algoritmo.
Seguimiento de la pulsación de la tecla Shift
En primer lugar, vamos a completar la clase CKeys, destinada para el trabajo con el teclado, con el método CKeys::KeyShiftState() para determinar el estado actual de la tecla Shift. Esta tecla va a utilizarse en distintas combinaciones para la selección del texto. En el listado de abajo se muestra el código de este método simple. La tecla Shift se considera pulsada si la función ::TerminalInfoInteger() con el identificador TERMINAL_KEYSTATE_SHIFT devuelve el valor inferior a cero.
//+------------------------------------------------------------------+ //| Clase para trabajar con el teclado | //+------------------------------------------------------------------+ class CKeys { public: //--- Devuelve el estado de la tecla Shift bool KeyShiftState(void); }; //+------------------------------------------------------------------+ //| Devuelve el estado de la tecla Shift | //+------------------------------------------------------------------+ bool CKeys::KeyShiftState(void) { return(::TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT)<0); }
Combinaciones de las teclas para seleccionar el texto
Vamos a considerar todas las combinaciones de las teclas para seleccionar el texto que serán implementadas en nuestro campo de edición. Empecemos con la combinación de dos teclas.
- Las combinaciones 'Shift + Left' y 'Shift + Right' desplazan el cursor del texto a la izquierda y a la derecha a un carácter, respectivamente. El texto se marca con otro color del fondo y del carácter (pueden ser ajustados por el usuario):
Fig. 1. Selección del texto con desplazamiento a un carácter a la izquierda y derecha.
- Las combinaciones 'Shift + Home' y 'Shift + End' desplazan el cursor al principio o al final de la línea marcando todos los caracteres desde la posición inicial del cursor.
Fig. 2. Selección del texto con desplazamiento desde la posición inicial del cursor hasta el principio o el final de la línea.
- Las combinaciones 'Shift + Up' y 'Shift + Down' desplazan el cursor del texto a una línea hacia arriba y hacia abajo, respectivamente. En este caso, el texto se selecciona en la línea inicial a partir del cursor hasta el principio de esta línea y hasta el cursor desde el fin de la línea final. Si entre la línea inicial y la línea final hay más líneas, el texto en ellas se selecciona por completo.
Fig. 3. Selección del texto con desplazamiento a una línea hacia arriba y hacia abajo.
A veces para seleccionar el texto se utilizan las combinaciones de tres teclas. Por ejemplo, cuando es necesario seleccionar varias palabras en una línea, la selección por caracteres será muy agotador. O si necesitamos seleccionar el texto compuesto de varias líneas, incluso la selección por líneas será inconveniente.
En combinaciones de tres teclas, a parte de la tecla Shift, se utiliza Ctrl. Vamos a considerar todas las combinaciones de este tipo que serán implementadas en este artículo:
- Las combinaciones 'Ctrl + Shift + Left' y 'Ctrl + Shift + Right' sirven para seleccionar el texto por palabras a la izquierda y a la derecha de la posición actual del cursor, respectivamente:
Fig. 4. Selección del texto con desplazamiento a una palabra a la izquierda y a la derecha.
- Las combinaciones de las teclas 'Ctrl + Shift + Home' y 'Ctrl + Shift + End' permiten seleccionar el texto entero hasta el principio de la primera línea y hasta el final de la última línea a partir de la posición actual del cursor:
Fig. 5. Selección del texto con desplazamiento del cursor hasta el principio y el final del documento.
En el siguiente apartado vamos a analizar los métodos que se utilizan para la selección del texto.
Métodos para seleccionar el texto
Por defecto, el texto seleccionado se muestra con caracteres blancos en el fondo azul. Si hace falta, se puede cambiar los colores usando los métodos CTextBox:: SelectedBackColor() y CTextBox:: SelectedTextColor().
class CTextBox : public CElement { private: //--- Color del fondo y de los caracteres del texto seleccionado color m_selected_back_color; color m_selected_text_color; //--- private: //--- Color del fondo y de los caracteres del texto seleccionado void SelectedBackColor(const color clr) { m_selected_back_color=clr; } void SelectedTextColor(const color clr) { m_selected_text_color=clr; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTextBox::CTextBox(void) : m_selected_text_color(clrWhite), m_selected_back_color(C'51,153,255') { //... }
Para seleccionar un texto, hacen falta los campos y los métodos para marcar los índices iniciales y finales de las líneas y caracteres del texto seleccionado. Además de eso, vamos a necesitar un método para resetear estos valores cuando la selección se cancela.
Cada vez, después de pulsar una combinación de teclas para seleccionar el texto, antes del desplazamiento del cursor, va a invocarse el método CTextBox::SetStartSelectedTextIndexes(). Él establece los valores iniciales para los índices de la línea y carácter donde se encuentra el cursor del texto. Los valores van a establecerse sólo en el caso si se trata de la primera llamada al método después del último reseteo de estos valores. Después de llamar a este método, el cursor se desplaza, luego se llama al método CTextBox::SetEndSelectedTextIndexes() que establece los valores finales para los índices de la línea y carácter (es decir, la posición actual del cursor). Si en el proceso del desplazamiento del cursor en el modo de selección del texto, resulta que en este momento él se encuentra en el mismo lugar de donde hemos empezado, entonces los valores se resetean con la llamada al método CTextBox::ResetSelectedText(). Los valores también se resetean en caso de cualquier desplazamiento del cursor, eliminación del texto seleccionado o desactivación del campo de edición.
class CTextBox : public CElement { private: //--- Índices iniciales y finales de las líneas y caracteres del texto seleccionado int m_selected_line_from; int m_selected_line_to; int m_selected_symbol_from; int m_selected_symbol_to; //--- private: //--- Establece los índices (1) iniciales y (2) finales para el texto seleccionado void SetStartSelectedTextIndexes(void); void SetEndSelectedTextIndexes(void); //--- Quitar la selección del texto void ResetSelectedText(void); }; //+------------------------------------------------------------------+ //| Establecer los índices iniciales para el texto seleccionado | //+------------------------------------------------------------------+ void CTextBox::SetStartSelectedTextIndexes(void) { //--- Si los índices iniciales para la selección del texto todavía no están establecidos if(m_selected_line_from==WRONG_VALUE) { m_selected_line_from =(int)m_text_cursor_y_pos; m_selected_symbol_from =(int)m_text_cursor_x_pos; } } //+------------------------------------------------------------------+ //| Establecer los índices finales para la selección del texto | //+------------------------------------------------------------------+ void CTextBox::SetEndSelectedTextIndexes(void) { //--- Establecer los índices finales para la selección del texto m_selected_line_to =(int)m_text_cursor_y_pos; m_selected_symbol_to =(int)m_text_cursor_x_pos; //--- Si todos los índices son iguales, quitar la selección if(m_selected_line_from==m_selected_line_to && m_selected_symbol_from==m_selected_symbol_to) ResetSelectedText(); } //+------------------------------------------------------------------+ //| Quitar la selección del texto | //+------------------------------------------------------------------+ void CTextBox::ResetSelectedText(void) { m_selected_line_from =WRONG_VALUE; m_selected_line_to =WRONG_VALUE; m_selected_symbol_from =WRONG_VALUE; m_selected_symbol_to =WRONG_VALUE; }
Los bloques del código que antes han sido utilizados en los métodos para el desplazamiento del cursor, ahora están implementados en unos métodos separados, porque van a usarse de nuevo en los métodos para la selección del texto. Lo mismo se refiere al código para la corrección de las barras de desplazamiento en caso cuando el cursor sale fuera del área visible.
class CTextBox : public CElement { private: //--- Desplazamiento del cursor a un carácter a la izquierda void MoveTextCursorToLeft(void); //--- Desplazamiento del cursor a un carácter a la izquierda void MoveTextCursorToRight(void); //--- Desplazamiento del cursor a un carácter hacia arriba void MoveTextCursorToUp(void); //--- Desplazamiento del cursor a un carácter hacia abajo void MoveTextCursorToDown(void); //--- Ajuste de la barra de desplazamiento horizontal void CorrectingHorizontalScrollThumb(void); //--- Ajuste de la barra de desplazamiento vertical void CorrectingVerticalScrollThumb(void); };
El código entero de los métodos del procesamiento de la pulsación de las combinaciones de teclas, una de las cuales es Shift, es prácticamente idéntico, salvo la llamada a los métodos para el desplazamiento del cursor del texto. Por eso tiene sentido crear un método adicional al que se puede simplemente pasar la dirección del desplazamiento del cursor. Al archivo Enums.mqh ha sido añadida la enumeración ENUM_MOVE_TEXT_CURSOR con varios identificadores (véase el código de abajo) que permiten indicar en qué lugar es necesario mover el cursor del texto:
- TO_NEXT_LEFT_SYMBOL — a un carácter a la izquierda.
- TO_NEXT_RIGHT_SYMBOL — a un carácter a la derecha.
- TO_NEXT_LEFT_WORD — a una palabra a la izquierda.
- TO_NEXT_RIGHT_WORD — a una palabra a la derecha.
- TO_NEXT_UP_LINE — a una línea hacia arriba.
- TO_NEXT_DOWN_LINE — a una línea hacia abajo.
- TO_BEGIN_LINE — al principio de la línea actual.
- TO_END_LINE — al final de la línea actual.
- TO_BEGIN_FIRST_LINE — al principio de la primera línea.
- TO_END_LAST_LINE — al final de la última línea.
//+----------------------------------------------------------------------+ //| Enumeración de la dirección del desplazamiento del cursor del texto | //+----------------------------------------------------------------------+ enum ENUM_MOVE_TEXT_CURSOR { TO_NEXT_LEFT_SYMBOL =0, TO_NEXT_RIGHT_SYMBOL =1, TO_NEXT_LEFT_WORD =2, TO_NEXT_RIGHT_WORD =3, TO_NEXT_UP_LINE =4, TO_NEXT_DOWN_LINE =5, TO_BEGIN_LINE =6, TO_END_LINE =7, TO_BEGIN_FIRST_LINE =8, TO_END_LAST_LINE =9 };
Ahora se puede crear el método general para el desplazamiento del cursor del texto CTextBox::MoveTextCursor(), al que será suficiente pasar uno de los identificadores de la lista arriba mencionada. El mismo método va a usarse ahora prácticamente en todos los métodos manejadores de eventos de la pulsación de las teclas en el control CTextBox.
class CTextBox : public CElement { private: //--- Desplazamiento del cursor del texto en dirección indicada void MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction); }; //+------------------------------------------------------------------+ //| Desplazamiento del cursor del texto en dirección indicada | //+------------------------------------------------------------------+ void CTextBox::MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction) { switch(direction) { //--- Mover el cursor a un carácter a la izquierda case TO_NEXT_LEFT_SYMBOL : MoveTextCursorToLeft(); break; //--- Mover el cursor a un carácter a la derecha case TO_NEXT_RIGHT_SYMBOL : MoveTextCursorToRight(); break; //--- Mover el cursor a una palabra a la izquierda case TO_NEXT_LEFT_WORD : MoveTextCursorToLeft(true); break; //--- Mover el cursor a una palabra a la derecha case TO_NEXT_RIGHT_WORD : MoveTextCursorToRight(true); break; //--- Mover el cursor a una línea hacia arriba case TO_NEXT_UP_LINE : MoveTextCursorToUp(); break; //--- Mover el cursor a una línea hacia abajo case TO_NEXT_DOWN_LINE : MoveTextCursorToDown(); break; //--- Mover el cursor al principio de la línea actual case TO_BEGIN_LINE : SetTextCursor(0,m_text_cursor_y_pos); break; //--- Movemos el cursor al final de la línea actual case TO_END_LINE : { //--- Obtenemos el número de caracteres en la línea actual uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol); //--- Mover el cursor SetTextCursor(symbols_total,m_text_cursor_y_pos); break; } //--- Mover el cursor al principio de la primera línea case TO_BEGIN_FIRST_LINE : SetTextCursor(0,0); break; //--- Mover el cursor al final de la última línea case TO_END_LAST_LINE : { //--- Obtenemos el número de las líneas y caracteres en la última línea uint lines_total =::ArraySize(m_lines); uint symbols_total =::ArraySize(m_lines[lines_total-1].m_symbol); //--- Mover el cursor SetTextCursor(symbols_total,lines_total-1); break; } } }
Se puede reducir aún más el código en este archivo ya que los métodos manejadores de eventos del desplazamiento del cursor y de la selección del texto contienen muchos bloques del código que se repiten.
Aquí tenemos el ejemplo del bloque repetido en los métodos para el desplazamiento del cursor del texto:
//+------------------------------------------------------------------+ //| Procesamiento de la pulsación de la tecla "Left" | //+------------------------------------------------------------------+ bool CTextBox::OnPressedKeyLeft(const long key_code) { //--- Salir si (1) no es la tecla "Left" o (2) ha sido pulsada la tecla "Ctrl" o (3) ha sido pulsada la tecla «Shift» if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || m_keys.KeyShiftState()) return(false); //--- Quitar la selección ResetSelectedText(); //--- Mover el cursor del texto a un carácter MoveTextCursor(TO_NEXT_LEFT_SYMBOL); //--- Ajustamos las barras de desplazamiento CorrectingHorizontalScrollThumb(); CorrectingVerticalScrollThumb(); //--- Actualizar el texto dentro del campo de edición DrawTextAndCursor(true); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo()); return(true); }
Aquí tenemos el ejemplo del bloque repetido en los métodos para la selección del texto:
//+------------------------------------------------------------------+ //| Procesamiento de la pulsación de la tecla Shift +Left | //+------------------------------------------------------------------+ bool CTextBox::OnPressedKeyShiftAndLeft(const long key_code) { //--- Salir si (1) no es la tecla "Left" o (2) ha sido pulsada la tecla "Ctrl" o (3) la tecla «Shift» no ha sido pulsada if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || !m_keys.KeyShiftState()) return(false); //--- Establecer los índices iniciales para la selección del texto SetStartSelectedTextIndexes(); //--- Mover el cursor del texto a un carácter MoveTextCursor(TO_NEXT_LEFT_SYMBOL); //--- Establecer los índices finales para la selección del texto SetEndSelectedTextIndexes(); //--- Ajustamos las barras de desplazamiento CorrectingHorizontalScrollThumb(); CorrectingVerticalScrollThumb(); //--- Actualizar el texto dentro del campo de edición DrawTextAndCursor(true); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo()); return(true); }
Vamos a implementar otro método adicional CTextBox::MoveTextCursor(), al que también habrá que pasar el identificador con la dirección del desplazamiento, así como la bandera que indique si (1) se trata del desplazamiento del cursor o (2) de la selección del texto.
class CTextBox : public CElement { private: //--- Desplazamiento del cursor del texto en dirección indicada void MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction,const bool with_highlighted_text); }; //+------------------------------------------------------------------+ //| Desplazamiento del cursor del texto en dirección indicada y | //| con la condición | //+------------------------------------------------------------------+ void CTextBox::MoveTextCursor(const ENUM_MOVE_TEXT_CURSOR direction,const bool with_highlighted_text) { //--- Si es sólo el desplazamiento del cursor del texto if(!with_highlighted_text) { //--- Quitar la selección ResetSelectedText(); //--- Mover el cursor al principio de la primera línea MoveTextCursor(direction); } //--- Si es con la selección else { //--- Establecer los índices iniciales para la selección del texto SetStartSelectedTextIndexes(); //--- Mover el cursor del texto a un carácter MoveTextCursor(direction); //--- Establecer los índices finales para la selección del texto SetEndSelectedTextIndexes(); } //--- Ajustamos las barras de desplazamiento CorrectingHorizontalScrollThumb(); CorrectingVerticalScrollThumb(); //--- Actualizar el texto dentro del campo de edición DrawTextAndCursor(true); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo()); }
Los métodos del procesamiento de las combinaciones de teclas para seleccionar el texto se muestran más abajo. Su código es prácticamente idéntico (se diferencian sólo los parámetros), por eso puede verlo en los archivos adjuntos al artículo:
class CTextBox : public CElement { private: //--- Procesamiento de la pulsación de la tecla Shift +Left bool OnPressedKeyShiftAndLeft(const long key_code); //--- Procesamiento de la pulsación de la tecla Shift + Right bool OnPressedKeyShiftAndRight(const long key_code); //--- Procesamiento de la pulsación de la tecla Shift + Up bool OnPressedKeyShiftAndUp(const long key_code); //--- Procesamiento de la pulsación de la tecla Shift + Down bool OnPressedKeyShiftAndDown(const long key_code); //--- Procesamiento de la pulsación de la tecla Shift + Home bool OnPressedKeyShiftAndHome(const long key_code); //--- Procesamiento de la pulsación de la tecla Shift + End bool OnPressedKeyShiftAndEnd(const long key_code); //--- Procesamiento de la pulsación de la combinación Ctrl + Shift + Left bool OnPressedKeyCtrlShiftAndLeft(const long key_code); //--- Procesamiento de la pulsación de la combinación Ctrl + Shift + Right bool OnPressedKeyCtrlShiftAndRight(const long key_code); //--- Procesamiento de la pulsación de la combinación Ctrl + Shift + Home bool OnPressedKeyCtrlShiftAndHome(const long key_code); //--- Procesamiento de la pulsación de la combinación Ctrl + Shift + End bool OnPressedKeyCtrlShiftAndEnd(const long key_code); };
Hasta ahora el texto se colocaba sobre el lienzo usando líneas enteras. Pero puesto que los caracteres seleccionados y el fondo debajo de él cambian su color, es necesario mostrar el texto carácter por carácter. Con este fin vamos a introducir pequeños cambios en el método CTextBox::TextOut().
Además necesitaremos el método adicional CTextBox::CheckSelectedText() para comprobar los caracteres seleccionados. Ya sabemos que durante la selección del texto se memorizan los índices de las líneas y caracteres iniciales/finales del cursor del texto. Por eso, recorriendo los caracteres en el ciclo, podemos identificar con facilidad si un carácter está seleccionado o no dentro de la línea. La lógica es muy simple:
- Si el índice inicial de la línea es inferior al índice final, el carácter está seleccionado:
- Si es la última línea y el carácter a la derecha del último seleccionado
- Si es la línea inicial y el carácter a la izquierda del inicial seleccionado
- En las líneas intermedias, todos los caracteres están seleccionados
- Si el índice inicial de la línea es superior al índice final, el carácter está seleccionado:
- Si es la última línea y el carácter a la izquierda del último seleccionado
- Si es la línea inicial y el carácter a la derecha del inicial seleccionado
- En las líneas intermedias, todos los caracteres están seleccionados
- Si el texto está seleccionado sólo dentro de una línea, el carácter está seleccionado si se encuentra en el diapasón especificado entre el índice inicial y final.
class CTextBox : public CElement { private: //--- Comprobación de la presencia del texto seleccionado bool CheckSelectedText(const uint line_index,const uint symbol_index); }; //+------------------------------------------------------------------+ //| Comprobación de la presencia del texto seleccionado | //+------------------------------------------------------------------+ bool CTextBox::CheckSelectedText(const uint line_index,const uint symbol_index) { bool is_selected_text=false; //--- Salir si no hay texto seleccionado if(m_selected_line_from==WRONG_VALUE) return(false); //--- Si el índice inicial en la línea es menor if(m_selected_line_from>m_selected_line_to) { //--- La última línea y el carácter a la derecha del último seleccionado if((int)line_index==m_selected_line_to && (int)symbol_index>=m_selected_symbol_to) { is_selected_text=true; } //--- La línea inicial y el carácter a la izquierda del inicial seleccionado else if((int)line_index==m_selected_line_from && (int)symbol_index<m_selected_symbol_from) { is_selected_text=true; } //--- Línea intermedia (se seleccionan todos los caracteres) else if((int)line_index>m_selected_line_to && (int)line_index<m_selected_line_from) { is_selected_text=true; } } //--- Si el índice inicial en la línea es mayor else if(m_selected_line_from<m_selected_line_to) { //--- La última línea y el carácter a la izquierda del último seleccionado if((int)line_index==m_selected_line_to && (int)symbol_index<m_selected_symbol_to) { is_selected_text=true; } //--- La línea inicial y el carácter a la derecha del inicial seleccionado else if((int)line_index==m_selected_line_from && (int)symbol_index>=m_selected_symbol_from) { is_selected_text=true; } //--- Línea intermedia (se seleccionan todos los caracteres) else if((int)line_index<m_selected_line_to && (int)line_index>m_selected_line_from) { is_selected_text=true; } } //--- Si el índice inicial y final están en la misma línea else { //--- Encontrada la línea a verificar if((int)line_index>=m_selected_line_to && (int)line_index<=m_selected_line_from) { //--- Si el desplazamiento del cursor va a la derecha y el carácter se encuentra en el diapasón seleccionado if(m_selected_symbol_from>m_selected_symbol_to) { if((int)symbol_index>=m_selected_symbol_to && (int)symbol_index<m_selected_symbol_from) is_selected_text=true; } //--- Si el desplazamiento del cursor va a la izquierda y el carácter se encuentra en el diapasón seleccionado else { if((int)symbol_index>=m_selected_symbol_from && (int)symbol_index<m_selected_symbol_to) is_selected_text=true; } } } //--- Devolver el resultado return(is_selected_text); }
Al método CTextBox::TextOut(), que sirve para la visualización del texto, hay que añadirle el ciclo interno con el repaso de los caracteres de la línea, en vez de la visualización de la línea entera. Ahí se determina si el carácter a comprobar está seleccionado. Si el carácter está seleccionado, se determina su color, y debajo se dibuja un rectángulo coloreado. Sólo después de eso, se visualiza el carácter.
class CTextBox : public CElement { private: //--- Visualización del texto en el lienzo void TextOut(void); }; //+------------------------------------------------------------------+ //| Visualización del texto en el lienzo | //+------------------------------------------------------------------+ void CTextBox::TextOut(void) { //--- Vaciar el lienzo m_canvas.Erase(AreaColorCurrent()); //--- Obtenemos el tamaño del array de líneas uint lines_total=::ArraySize(m_lines); //--- Ajuste en caso de salir fuera del diapasón m_text_cursor_y_pos=(m_text_cursor_y_pos>=lines_total)? lines_total-1 : m_text_cursor_y_pos; //--- Obtenemos el tamaño del array de caracteres uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol); //--- Si el modo multilínea está activado o el número de caracteres es superior a cero if(m_multi_line_mode || symbols_total>0) { //--- Obtenemos el ancho de la línea int line_width=(int)LineWidth(m_text_cursor_x_pos,m_text_cursor_y_pos); //--- Obtenemos el alto de la línea y recorremos todas las líneas en el ciclo int line_height=(int)LineHeight(); for(uint i=0; i<lines_total; i++) { //--- Obtenemos las coordenadas para el texto int x=m_text_x_offset; int y=m_text_y_offset+((int)i*line_height); //--- Obtenemos el tamaño de la línea uint string_length=::ArraySize(m_lines[i].m_symbol); //--- Dibujamos el texto for(uint s=0; s<string_length; s++) { uint text_color=TextColorCurrent(); //--- Si hay texto seleccionado, determinamos su color, así como el color del fondo del carácter actual if(CheckSelectedText(i,s)) { //--- Color del texto seleccionado text_color=::ColorToARGB(m_selected_text_color); //--- Calculamos las coordenadas para dibujar el fondo int x2=x+m_lines[i].m_width[s]; int y2=y+line_height-1; //--- Dibujamos el color del fondo del carácter m_canvas.FillRectangle(x,y,x2,y2,::ColorToARGB(m_selected_back_color)); } //--- Dibujar el carácter m_canvas.TextOut(x,y,m_lines[i].m_symbol[s],text_color,TA_LEFT); //--- Coordenada X para el carácter siguiente x+=m_lines[i].m_width[s]; } } } //--- Si el modo multilínea está desactivado y además en la línea no hay caracteres, se mostrará el texto por defecto else { //--- Dibujar el texto si está 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); } }
Los métodos para la selección del texto están implementados, pues así se mostrará todo eso en una aplicación final:
Fig. 6. Demostración de la selección del texto en el campo de edición de una aplicación MQL.
Métodos para eliminar el texto seleccionado
Ahora vamos a considerar los métodos para eliminar el texto seleccionado. Aquí hay que tomar en cuenta que al eliminar el texto seleccionado, dependiendo de que si está seleccionado en una línea o en varias, van a aplicarse los métodos diferentes.
Para eliminar el texto seleccionado en una línea, vamos a llamar al método CTextBox::DeleteTextOnOneLine(). Al principio de este método, se determina el número de caracteres a eliminar. Luego, si el índice inicial del carácter del texto seleccionado se encuentra a la derecha, los caracteres desde esta posición inicial se desplazan a la izquierda al número de caracteres para la eliminación, y después de eso el array de caracteres de la línea se disminuye en la misma cantidad.
En los casos cuando el índice inicial del carácter del texto seleccionado se encuentra a la izquierda, entonces el cursor del texto también hay que moverlo a la derecha al número de caracteres que se eliminan.
сlass CTextBox : public CElement { private: //--- Elimina el texto seleccionado en una línea void DeleteTextOnOneLine(void); }; //+------------------------------------------------------------------+ //| Elimina el texto seleccionado en una línea | //+------------------------------------------------------------------+ void CTextBox::DeleteTextOnOneLine(void) { int symbols_total =::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol); int symbols_to_delete =::fabs(m_selected_symbol_from-m_selected_symbol_to); //--- Si el índice inicial del carácter se encuentra a la derecha if(m_selected_symbol_to<m_selected_symbol_from) { //--- Desplazamos los caracteres al sitio liberado en la línea actual MoveSymbols(m_text_cursor_y_pos,m_selected_symbol_from,m_selected_symbol_to); } //--- Si el índice inicial del carácter se encuentra a la izquierda else { //--- Mover el cursor del texto a la izquierda según el número de caracteres eliminados m_text_cursor_x_pos-=symbols_to_delete; //--- Desplazamos los caracteres al sitio liberado en la línea actual MoveSymbols(m_text_cursor_y_pos,m_selected_symbol_to,m_selected_symbol_from); } //--- Reducimos el tamaño del array de la línea actual al número de caracteres extraídos de ella ArraysResize(m_text_cursor_y_pos,symbols_total-symbols_to_delete); }
Para eliminar varias líneas del texto seleccionado, se utilizará el método CTextBox::DeleteTextOnMultipleLines(). Aquí el algoritmo es más complejo. Primero, hay que determinar lo siguiente:
- El número total de caracteres en la línea inicial y final.
- La cantidad de las líneas intermedias del texto seleccionado (aparte de la línea inicial y final).
- El número de caracteres a eliminar en la línea inicial y final.
La consecuencia de siguientes acciones se muestra a continuación. Dependiendo de la dirección de la selección del texto (hacia arriba o hacia abajo), van a traspasarse los índices iniciales y finales en otros métodos.
- Los caracteres para el traspaso de una línea a la otra que se quedarán tras la eliminación se copian al array dinámico temporal.
- Se establece un tamaño nuevo para el array receptor (línea).
- Se añaden los datos a los arrays de la estructura de la línea receptora.
- Se desplazan las líneas según el número de las líneas a eliminar.
- El array de las líneas obtiene un tamaño nuevo (se disminuye según el número de las líneas a eliminar).
- En caso si la línea inicial está por encima de la final (selección del texto hacia abajo), el cursor del texto se desplaza hacia los índices iniciales (de la línea y carácter) del texto seleccionado.
class CTextBox : public CElement { private: //--- Elimina el texto seleccionado en varias líneas void DeleteTextOnMultipleLines(void); }; //+------------------------------------------------------------------+ //| Elimina el texto seleccionado en varias líneas | //+------------------------------------------------------------------+ void CTextBox::DeleteTextOnMultipleLines(void) { //--- El número total de caracteres en la línea inicial y final uint symbols_total_line_from =::ArraySize(m_lines[m_selected_line_from].m_symbol); uint symbols_total_line_to =::ArraySize(m_lines[m_selected_line_to].m_symbol); //--- Número de líneas intermedias para eliminar uint lines_to_delete =::fabs(m_selected_line_from-m_selected_line_to); //--- Número de caracteres a eliminar en la línea inicial y final uint symbols_to_delete_in_line_from =::fabs(symbols_total_line_from-m_selected_symbol_from); uint symbols_to_delete_in_line_to =::fabs(symbols_total_line_to-m_selected_symbol_to); //--- Si la línea inicial está por debajo de la final if(m_selected_line_from>m_selected_line_to) { //--- Copiamos los caracteres a traspasar al array string array[]; CopyWrapSymbols(m_selected_line_from,m_selected_symbol_from,symbols_to_delete_in_line_from,array); //--- Establecemos un tamaño nuevo para la línea receptora uint new_size=m_selected_symbol_to+symbols_to_delete_in_line_from; ArraysResize(m_selected_line_to,new_size); //--- Añadir los datos a los arrays de la estructura de la línea receptora PasteWrapSymbols(m_selected_line_to,m_selected_symbol_to,array); //--- Obtenemos el tamaño del array de líneas uint lines_total=::ArraySize(m_lines); //--- Desplazamos las líneas en el número de las líneas a eliminar MoveLines(m_selected_line_to+1,lines_total-lines_to_delete,lines_to_delete,false); //--- Establecemos nuevo tamaño para el array de la líneas ::ArrayResize(m_lines,lines_total-lines_to_delete); } //--- Si la línea inicial está por encima de la final else { //--- Copiamos los caracteres a traspasar al array string array[]; CopyWrapSymbols(m_selected_line_to,m_selected_symbol_to,symbols_to_delete_in_line_to,array); //--- Establecemos un tamaño nuevo para la línea receptora uint new_size=m_selected_symbol_from+symbols_to_delete_in_line_to; ArraysResize(m_selected_line_from,new_size); //--- Añadir los datos a los arrays de la estructura de la línea receptora PasteWrapSymbols(m_selected_line_from,m_selected_symbol_from,array); //--- Obtenemos el tamaño del array de líneas uint lines_total=::ArraySize(m_lines); //--- Desplazamos las líneas en el número de las líneas a eliminar MoveLines(m_selected_line_from+1,lines_total-lines_to_delete,lines_to_delete,false); //--- Establecemos nuevo tamaño para el array de la líneas ::ArrayResize(m_lines,lines_total-lines_to_delete); //--- Mover el cursor a la posición inicial en la selección SetTextCursor(m_selected_symbol_from,m_selected_line_from); } }
En el método principal para eliminar el texto CTextBox::DeleteSelectedText() se determina cuál de los métodos arriba mencionados se invoca. Una vez eliminado el texto seleccionado, los valores de los índices iniciales y finales se resetean. Luego hay que volver a calcular los tamaños del campo de edición porque probablemente se haya cambiado el número de las líneas. Además, es probable que se haya cambiado el ancho máximo de la línea según el que se calcula el ancho del campo de edición. Al final del método, se envía el mensaje sobre el desplazamiento del cursor del texto. Si el texto ha sido seleccionado y eliminado, el método devuelve true. Si al llamar al método resulta que no hay texto seleccionado, el método devuelve false.
class CTextBox : public CElement { private: //--- Elimina el texto seleccionado void DeleteSelectedText(void); }; //+------------------------------------------------------------------+ //| Elimina el texto seleccionado | //+------------------------------------------------------------------+ bool CTextBox::DeleteSelectedText(void) { //--- Salir si el texto no está seleccionado if(m_selected_line_from==WRONG_VALUE) return(false); //--- Si se eliminan los caracteres en una línea if(m_selected_line_from==m_selected_line_to) DeleteTextOnOneLine(); //--- Si se eliminan los caracteres en varias líneas else DeleteTextOnMultipleLines(); //--- Quitar la selección del texto ResetSelectedText(); //--- Calcular los tamaños del campo de edición CalculateTextBoxSize(); //--- Establecer nuevo tamaño para el campo de edición ChangeTextBoxSize(); //--- Ajustamos las barras de desplazamiento CorrectingHorizontalScrollThumb(); CorrectingVerticalScrollThumb(); //--- Actualizar el texto dentro del campo de edición DrawTextAndCursor(true); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_MOVE_TEXT_CURSOR,CElementBase::Id(),CElementBase::Index(),TextCursorInfo()); return(true); }
El método CTextBox::DeleteSelectedText() se invoca no sólo cuando se pulsa la tecla Backspace, sino también: (1) cuando se introduce nuevo carácter y (2) cuando se pulsa la tecla Enter. En estos casos el texto primero se elimina y luego se realiza la acción correspondiente a la tecla pulsada.
Pues así se mostrará todo eso en una aplicación final:
Fig. 7. Demostración de la eliminación del texto seleccionado.
Clase para trabajar con los datos de la imagen
Como adición, en este artículo vamos a considerar la nueva clase (CImage) para el trabajo con los datos de la imagen. Va a usarse varias veces en muchas clases de los controles de la librería donde es necesario dibujar una imagen. La clase se encuentra en el archivo Objects.mqh.
Propiedades de la clase:- arrays de los píxeles de la imagen;
- ancho de la imagen;
- alto de la imagen;
- ruta hacia el archivo de la imagen.
//+------------------------------------------------------------------+ //| Clase para almacenar los datos de la imagen | //+------------------------------------------------------------------+ class CImage { protected: uint m_image_data[]; // Array de los píxeles de la imagen uint m_image_width; // Ancho de la imagen uint m_image_height; // Alto de la imagen string m_bmp_path; // Ruta hacia el archivo de la imagen public: //--- (1) Tamaño del array de datos, (2) establecer/devolver datos (color del píxel) uint DataTotal(void) { return(::ArraySize(m_image_data)); } uint Data(const uint data_index) { return(m_image_data[data_index]); } void Data(const uint data_index,const uint data) { m_image_data[data_index]=data; } //--- Establecer/devolver el ancho de la imagen void Width(const uint width) { m_image_width=width; } uint Width(void) { return(m_image_width); } //--- Establecer/devolver el alto de la imagen void Height(const uint height) { m_image_height=height; } uint Height(void) { return(m_image_height); } //--- Establecer/devolver la ruta hacia la imagen void BmpPath(const string bmp_file_path) { m_bmp_path=bmp_file_path; } string BmpPath(void) { return(m_bmp_path); } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CImage::CImage(void) : m_image_width(0), m_image_height(0), m_bmp_path("") { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CImage::~CImage(void) { }
Para guardar la imagen y sus propiedades, se usa el método CImage::ReadImageData(). Este método lee la imagen según la ruta especificada y guarda sus datos.
class CImage { public: //--- Lee y guarda los datos de la imagen enviada bool ReadImageData(const string bmp_file_path); }; //+------------------------------------------------------------------+ //| Guarda la imagen traspasada en el array | //+------------------------------------------------------------------+ bool CImage::ReadImageData(const string bmp_file_path) { //--- Resetear el último error ::ResetLastError(); //--- Guardamos la ruta hacia la imagen m_bmp_file_path=bmp_file_path; //--- Leer y guardar los datos de la imagen if(!::ResourceReadImage(m_bmp_file_path,m_image_data,m_image_width,m_image_height)) { ::Print(__FUNCTION__," > error: ",::GetLastError()); return(false); } //--- return(true); }
A veces podemos necesitar hacer una copia de la imagen del mismo tipo (CImage). Para eso disponemos del método CImage::CopyImageData(). Al principio del método, al array receptor se le establece el tamaño del array fuente. Luego en el ciclo se copian los datos del array fuente al array receptor.
class CImage { public: //--- Copia los datos de la imagen traspasada void CopyImageData(CImage &array_source); }; //+------------------------------------------------------------------+ //| Copia los datos de la imagen traspasada | //+------------------------------------------------------------------+ void CImage::CopyImageData(CImage &array_source) { //--- Obtenemos los tamaños del array receptor y array fuente uint data_total =DataTotal(); uint source_data_total =::GetPointer(array_source).DataTotal(); //--- Cambiar el tamaño del array receptor ::ArrayResize(m_image_data,source_data_total); //--- Copiamos los datos for(uint i=0; i<source_data_total; i++) m_image_data[i]=::GetPointer(array_source).Data(i); }
Hasta esta actualización, en la clase CCanvasTable se utilizaba la estructura para almacenar los datos de la imagen. Ahora con la presencia de la clase CImage han sido introducidos los cambios correspondientes.
Conclusión
En este artículo, hemos terminado el desarrollo del control «Campo de edición del texto multilínea». Su principal particularidad consiste en el hecho de que ahora no tiene limitaciones para la cantidad de caracteres introducidos y se puede introducir varias líneas. Eso faltaba mucho en el objeto gráfico estándar del tipo OBJ_EDIT. En el siguiente artículo, seguiremos desarrollando el tema «Controles en las celdas de la tabla»: añadiremos la posibilidad de cambiar los valores en las celdas de la tabla a través del control considerado en este artículo. Además, pasaremos algunos controles en un modo nuevo: ellos van a dibujarse en vez de construirse a base de varios objetos gráficos estándar.
En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema tiene el siguiente aspecto:
Fig. 8. Estructura de la librería en la fase actual del desarrollo.
Abajo puede descargar la última versión de la librería y los archivos para las pruebas que han sido presentadas en el artículo.
Si le surgen algunas preguntas sobre el uso del material de estos archivos, puede dirigirse a la descripción detallada del proceso de desarrollo de la librería en uno de los artículos de esta serie, o bien hacer su pregunta en los comentarios para el artículo.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/3197
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso