Interfaces gráficas X: Control "Campo de edición del texto multilínea" (build 8)
Índice
- Introducción
- Grupos de teclas y distribuciones del teclado
- Procesamiento del evento de pulsación de teclas
- Códigos ASCII de caracteres y teclas de control
- Códigos de Scan de las teclas
- Clase auxiliar para trabajar con el teclado
- Control «Campo de edición del texto multilínea»
- Desarrollo de la clase CTextBox para la creación del control
- Propiedades y apariencia
- Control del cursor del texto
- Introducción del carácter
- Procesamiento de la pulsación de la tecla 'Backspace'
- Procesamiento de la pulsación de la tecla 'Enter'
- Procesamiento de la pulsación de las teclas 'Left' y 'Right'
- Procesamiento de la pulsación de las teclas 'Up' y 'Down'
- Procesamiento de la pulsación de las teclas 'Home' y 'End'
- Procesamiento de la pulsación simultánea de las teclas en combinación con la tecla 'Ctrl'
- Integración del control en el motor de la librería
- Aplicación para la prueba del control
- 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 cada artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede 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.
En este artículo vamos a conocer un control nuevo: «Campo de edición multilínea» A diferencia del objeto gráfico OBJ_EDIT que ofrece el terminal, en esta versión no habrá limitación alguna para el número de los caracteres a introducir. Se hace disponible el modo de conversión del campo de edición en un sencillo editor de texto. Es decir, Usted podrá introducir varias líneas en este campo, pudiendo mover el cursor de texto tanto con el ratón, como con el teclado. Si la parte visible del control se sobrellena con las líneas, aparecerá ña barra de desplazamiento. El control «Campo de edición del texto multilínea» será dibujado completamente, y su calidad estará acercada al máximo al semejante control en los sistemas operativos.
Grupos de teclas y distribuciones del teclado
Antes de empezar con la descripción del código del control tipo CTextBox (área del texto), hablaremos un poco sobre el teclado, ya que precisamente este dispositivo de entrada será nuestra herramienta para introducción de datos. Además, determinaremos las teclas cuya pulsación va a procesarse en la primera versión de la clase del control.
Podemos dividir las teclas del teclado en varios grupos (véase los colores en la Fig. 1):
- Teclas de control (color naranja)
- Teclas de función (color violeta)
- Teclas de introducción (color azul)
- Teclas de desplazamiento (color verde)
- Teclado numérico (rojo)
Fig. 1. Grupos de teclas (distribución del teclado QWERTY)
Existen varias distribuciones del teclado latino para el idioma inglés. QWERTY es la más famosa entre ellas. En nuestro caso, el idioma principal es ruso, por eso usamos la distribución rusa ЙЦУКЕН. La distribución QWERTY la dejaremos para el inglés que nos sirvirá del idioma auxiliar.
Comenzando de la actualización 1510, el lenguaje MQL cuenta con la función ::TranslateKey(). Esta función nos permite obtener el carácter correspondiente a la distribución del teclado y el idioma del Sistema Operativo, según el código de la tecla pulsada que ha sido pasado. Antes, había que formar los arrays de los caracteres para cada idioma y distribución personalmente, lo que era complicado debido a un elevado volumen de trabajos. Ahora todo eso es mucho más fácil.
Procesamiento del evento de pulsación de teclas
Se puede capturar el evento de la pulsación de la tecla en la función de sistema ::OnChartEvent(), según el identificador CHARTEVENT_KEYDOWN:
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- Pulsación de la tecla en el teclado
if(id==CHARTEVENT_KEYDOWN)
{
::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam,"; symbol: ",::ShortToString(::TranslateKey((int)lparam)));
return;
}
}
En calidad de los demás tres parámetros, en la función llegan los siguientes valores:
- parámetro long (lparam) – código de la tecla pulsada, es decir, el código del caracter ASCII o el código de la tecla que pertenece al grupo de las teclas de control.
- parámetro dparam (dparam) – número de pulsaciones de la tecla generadas mientras ésta se mantenía pulsada. Este valor siempre es igual a 1. Si necesita obtener el número de llamadas a partir del momento del apretón de la tecla, tiene que realizar el cálculo por sí mismo.
- parámetro sparam (sparam) – valor string de la máscara de bits que describe el estatus de las teclas del teclado. El evento se genera inmediatamente, en el momento de la pulsación de la tecla. Si el botón se pulsa y se suelta sin quedarse pulsado, aquí habrá el valor del código Scan de esta tecla. Cuando la tecla se pulsa y se mantiene pulsada, se genera el valor que se forma del código Scan + 16384 bits.
Por ejemplo, en el código de abajo (visualización en el registro del terminal) se muestra el resultado de la pulsación y mantenimiento de la tecla «Esc» en estado pulsado. Este código tiene el código 27 (lparam), el código Scan es igual a 1 (sparam), y si la tecla se mantiene pulsada durante aproximadamente unos 500 milisegundos, empieza a generarse el valor 16385 (скан-код + 16384 бита).
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
...
No todas las teclas generan el evento con el identificador CHARTEVENT_KEYDOWN. Algunas de ellas están asignadas para las necesidades del terminal, otras simplemente no generan el evento de la pulsación de la tecla. En la imagen de abajo, están marcadas con el color azul:
Fig. 2. Botones ocupados por el terminal o los que no generan el evento CHARTEVENT_KEYDOWN.
Códigos ASCII de caracteres y teclas de control
Información de Wikipedia (más detalles):
ASCII (acrónimo inglés de American standard code for information interchange) es el nombre de la tabla (codificaciones, composición) en la que algunos símbolos impresos y no impresos reciben los códigos numéricos. Esta tabla fue desarrollada y estandartizada por los Estados Unidos en el año 1963.
En la imagen de abajo se muestran los códigos ASCII de las teclas:
Fig. 3. Códigos ASCII de las teclas.
Todos los códigos ASCII han sido ubicados en el archivo KeyCodes.mqh en forma de macro sustituciones (#define). Abajo se muestra una parte de estos códigos:
//| KeyCodes.mqh |
//| Copyright 2016, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Códigos de los caracteres ASCII y teclas de control |
//| para el procesamiento del evento de pulsación de teclas (parámetro long del 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ódigos Scan de las teclas
Información de Wikipedia (más detalles):
El código Scan (en inglés, Scan Code) es un código en los ordenadores compatibles IBM que se asigna a cada tecla y que permite al controlador del teclado distinguir la tecla que ha sido pulsada.
En la imagen de abajo se muestran los códigos Scan de las teclas:
Fig. 4. Códigos Scan de las teclas.
Igual que los códigos ASCII, los códigos Scan se almacenan en el archivo KeyCodes.mqh. En el código de abajo se muestra sólo una parte de esta lista:
//| KeyCodes.mqh |
//| Copyright 2016, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
...
//--- Бит
#define KEYSTATE_ON 16384
//+------------------------------------------------------------------+
//| Códigos Scan de las teclas (parámetro string del evento) |
//+------------------------------------------------------------------+
//| Pulsada sólo una vez: KEYSTATE_XXX |
//| Se mantiene pulsada: 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
...
Clase auxiliar para trabajar con el teclado
La clase CKeys ha sido implementada para facilitar el trabajo con el teclado. Se ubica en el archivo Keys.mqh, y tiene incluido el archivo файл KeyCodes.mqh con todos los códigos de las teclas y caracteres.
//| Keys.mqh |
//| Copyright 2016, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\KeyCodes.mqh>
//+------------------------------------------------------------------+
//| Clase para trabajar con el teclado |
//+------------------------------------------------------------------+
class CKeys
{
public:
CKeys(void);
~CKeys(void);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CKeys::CKeys(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CKeys::~CKeys(void)
{
}
Para determinar la pulsación de la tecla con:
(1) símbolo alfabético (inclusive espacio)
(2) símbolo de una tecla numérica
o (3) símbolo especial,
hay que usar el método CKeys::KeySymbol(). Si le pasamos el valor del parámetro long del evento con el identificador CHARTEVENT_KEYDOWN, devolverá el carácter en el formato de cadena (string), o la cadena vacía ('') si ha sido pulsada la tecla fuera de los diapasones especificados.
{
public:
//--- Devuelve el carácter de la tecla pulsada
string KeySymbol(const long key_code);
};
//+------------------------------------------------------------------+
//| Devuelve el carácter de la tecla pulsada |
//+------------------------------------------------------------------+
string CKeys::KeySymbol(const long key_code)
{
string key_symbol="";
//--- Si es necesario introducir el espacio (tecla "Space")
if(key_code==KEY_SPACE)
{
key_symbol=" ";
}
//--- Si es necesario introducir (1) el carácter alfabético, o (2) carácter de una tecla numérica, o (3) un símbolo 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));
}
//--- Devolver carácter
return(key_symbol);
}
Y finalmente, necesitaremos un método para determinar el estado actual de la tecla 'Ctrl'. Esta tecla va a usarse en diferentes combinaciones de la pulsación simultánea de dos teclas al situar el cursor en el campo de edición.
Para obtener el estado actual de la tecla 'Ctrl', vamos a usar la función de sistema del lenguaje ::TerminalInfoInteger(). Esta función tiene varios identificadores para determinar el estado actual de las teclas. Para la tecla 'Ctrl' está destinado para el identificador TERMINAL_KEYSTATE_CONTROL. Para conocer los demás identificadores de este tipo, diríjase al manual de referencia del lenguaje MQL5.
Es muy fácil determinar a través de este identificador si una u otra tecla está pulsada. Si la tecla está pulsada, el valor devuelto será inferior a cero:
{
public:
//--- Devuelve el estado de la tecla Ctrl
bool KeyCtrlState(void);
};
//+------------------------------------------------------------------+
//| Devuelve el estado de la tecla Ctrl |
//+------------------------------------------------------------------+
bool CKeys::KeyCtrlState(void)
{
return(::TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)<0);
}
Ahorra tenemos todo listo para el desarrollo de la clase para la creación del control «Campo de edición del texto»
Control «Campo de edición del texto multilínea»
El control «Campo de edición del texto multilínea» puede ser usado en los controles combinados. Pertenece al grupo de los controles compuestos complejos, ya que incluye las barras de desplazamiento. El campo de edición del texto multilínea se puede usar también para la introducción del texto y para la visualización del texto guardado anteriormente en un archivo.
Antes, ya hemos considerado los controles con los campos de edición para la introducción de los valores numéricos (clase CSpinEdit) o un texto aleatorio (CTextEdit). En ellos se utilizaba el objeto gráfico tipo OBJ_EDIT. Tiene unas limitaciones bastante estrictas, se puede introducir solamente 63 caracteres, además hay que meterlos en una sola línea. Por eso, ahora nuestra tarea consiste en crear un campo de edición sin estas limitaciones.
Fig. 5. Control «Campo de edición del texto multilínea».
Vamos a ver con más detalles cómo está organizada la clase CTextBox para la creación de este control.
Desarrollo de la clase CTextBox para la creación del control
Creamos el archivo TextBox.mqh con la clase CTextBox con todos los métodos estándar de cada control de la librería, e incluimos los siguientes archivos dentro de él:
- Con la clase base del control— Element.mqh.
- Con las clases de las barras de desplazamiento— Scrolls.mqh.
- Con la clase para trabajar con el teclado— Keys.mqh.
- Con la clase para trabajar con el contador de tiempo— TimeCounter.mqh.
- Con la clase para trabajar con el gráfico en el que se ubica la aplicación MQL— 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>
//+------------------------------------------------------------------+
//| Clase para crear el campo de edición multilínea |
//+------------------------------------------------------------------+
class CTextBox : public CElement
{
private:
//--- Instancia de la clase para trabajar con el teclado
CKeys m_keys;
//--- Instancia de la clase para controlar el gráfico
CChart m_chart;
//--- Instancia de la clase para trabajar con el contador del temporizador
CTimeCounter m_counter;
//---
public:
CTextBox(void);
~CTextBox(void);
//--- Manejador de eventos del gráfico
virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
//--- Temporizador
virtual void OnEventTimer(void);
//--- Desplazamiento del control
virtual void Moving(const int x,const int y,const bool moving_mode=false);
//--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar
virtual void Show(void);
virtual void Hide(void);
virtual void Reset(void);
virtual void Delete(void);
//--- (1) Establecer, (2) resetear las prioridades para el clic izquierdo del ratón
virtual void SetZorders(void);
virtual void ResetZorders(void);
//--- Restablecer el color
virtual void ResetColors(void) {}
//---
private:
//--- Cambiar el ancho por el lado derecho de la ventana
virtual void ChangeWidthByRightWindowSide(void);
//--- Cambiar el alto por el borde inferior de la ventana
virtual void ChangeHeightByBottomWindowSide(void);
};
Propiedades y apariencia
Vamos a necesitar una estructura con los arrays de caracteres y sus propiedades, la llamaremos KeySymbolOptions. En esta versión, ella va a incluir dos arrays dinámicos:
- El array m_symbol[] va a contener todos los caracteres de la línea por separado.
- El array m_width[] va a contener el ancho de todos los caracteres de la línea por separado.
Declaramos la instancia de esta estructura también como un array dinámico. Su tamaño siempre será igual al número de las líneas en el campo de edición.
{
private:
//--- Caracteres y sus propiedades
struct KeySymbolOptions
{
string m_symbol[]; // Caracteres
int m_width[]; // Ancho de los caracteres
};
KeySymbolOptions m_lines[];
};
En la primera versión del control, el texto va a visualizarse en cadenas enteras, por eso antes de mostrar el texto, hay que recopilarla desde el array m_symbol[]. Para este propósito se utiliza el método CTextBox::CollectString(), en el que es necesario pasar el índice de la cadena:
{
private:
//--- Variable para trabajar con la cadena
string m_temp_input_string;
//---
private:
//--- Reúne la cadena de los caracteres
string CollectString(const uint line_index);
};
//+------------------------------------------------------------------+
//| Reúne la cadena de los 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);
}
A continuación, vamos a mencionar las propiedades del campo de edición que permiten ajustar la apariencia de este control, así como su estado y los modos en los que puede trabajar:
- Color del fondo en diferentes estados
- Color del texto en diferentes estados
- Color del borde en diferentes estados
- Texto por defecto
- Color del texto por defecto
- Modo multilínea
- Modo «Sólo lectura»
{
private:
//--- Color del fondo
color m_area_color;
color m_area_color_locked;
//--- Color del texto
color m_text_color;
color m_text_color_locked;
//--- Color del borde
color m_border_color;
color m_border_color_hover;
color m_border_color_locked;
color m_border_color_activated;
//--- Texto por defecto
string m_default_text;
//--- Color del texto por defecto
color m_default_text_color;
//--- Modo multilínea
bool m_multi_line_mode;
//--- Modo «Sólo lectura»
bool m_read_only_mode;
//---
public:
//--- Color del fondo en diferentes estados
void AreaColor(const color clr) { m_area_color=clr; }
void AreaColorLocked(const color clr) { m_area_color_locked=clr; }
//--- Color del texto en diferentes estados
void TextColor(const color clr) { m_text_color=clr; }
void TextColorLocked(const color clr) { m_text_color_locked=clr; }
//--- Colores del borde en 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 por defecto y (2) color del texto por defecto
void DefaultText(const string text) { m_default_text=text; }
void DefaultTextColor(const color clr) { m_default_text_color=clr; }
//--- (1) Modo multilínea, (2) modo «Sólo lectura»
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; }
};
El campo de edición (fondo, texto, borde y el cursor de texto parpadeante) va a dibujarse en un solo objeto gráfico tipo OBJ_BITMAP_LABEL. En realidad, simplemente es una imagen. Ella va a redibujarse en dos ocasiones:
- en caso de interacción con el control
- dentro de un intervalo de tiempo establecido para el parpadeo del cursor, cuando el campo de edición está activado.
Cuando el cursor se sitúa en el área del campo de edición, su borde cambia de color. Para que la imagen no se redibuje muy a menudo, hay que seguir el momento del cruce del borde del campo de edición por el cursor. O sea, el control debe redibujarse sólo una vez, en el momento cuando el cursor entra en el área del campo de edición o cuando sale fuera. Para eso en la clase base del control han sido incluidos los métodos CElementBase::IsMouseFocus(). A través de estos métodos se establece y se obtiene la bandera que indica en el hecho del cruzamiento:
//| Clase base del control |
//+------------------------------------------------------------------+
class CElementBase
{
protected:
//--- Para detectar el momento del cruzamiento de los bordes del control por el cursor del ratón
bool m_is_mouse_focus;
//---
public:
//--- Momento de la entrada/salida en/del foco del control
bool IsMouseFocus(void) const { return(m_is_mouse_focus); }
void IsMouseFocus(const bool focus) { m_is_mouse_focus=focus; }
};
Para que el código sea simple y legible, dentro de él han sido implementados los métodos simples adicionales a través de los cuales se puede obtener el color del fondo del campo de edición, del borde y del texto en relación al estado actual del control:
{
private:
//--- Devuelve el color actual del fondo
uint AreaColorCurrent(void);
//--- Devuelve el color actual del texto
uint TextColorCurrent(void);
//--- Devuelve el color actual del borde
uint BorderColorCurrent(void);
};
//+------------------------------------------------------------------+
//| Devuelve el color del fondo respecto al estado actual del control |
//+------------------------------------------------------------------+
uint CTextBox::AreaColorCurrent(void)
{
uint clr=::ColorToARGB((m_text_box_state)? m_area_color : m_area_color_locked);
//--- Devolver el color
return(clr);
}
//+------------------------------------------------------------------+
//| Devuelve el color del texto respecto al estado actual del control |
//+------------------------------------------------------------------+
uint CTextBox::TextColorCurrent(void)
{
uint clr=::ColorToARGB((m_text_box_state)? m_text_color : m_text_color_locked);
//--- Devolver el color
return(clr);
}
//+------------------------------------------------------------------+
//| Devuelve el color del borde respecto al estado actual del control |
//+------------------------------------------------------------------+
uint CTextBox::BorderColorCurrent(void)
{
uint clr=clrBlack;
//--- Si el control no está bloqueado
if(m_text_box_state)
{
//--- Si el campo de edición está activado
if(m_text_edit_state)
clr=m_border_color_activated;
//--- Si no está activado, comprobamos el foco del control
else
clr=(CElementBase::IsMouseFocus())? m_border_color_hover : m_border_color;
}
//--- Si el control está bloqueado
else
clr=m_border_color_locked;
//--- Devolver el color
return(::ColorToARGB(clr));
}
En muchos métodos de la clase, será necesario obtener el valor del alto de la línea del campo de edición (en píxeles) en función de la fuente establecida y su tamaño. Para eso se utiliza el método CTextBox::LineHeight():
{
private:
//--- Devuelve el alto de la línea
uint LineHeight(void);
};
//+------------------------------------------------------------------+
//| Devuelve el alto de la línea |
//+------------------------------------------------------------------+
uint CTextBox::LineHeight(void)
{
//--- Establecemos la fuente para la visualización en el lienzo (es necesario para obtener el alto de la línea)
m_canvas.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Devolvemos el alto de la línea
return(m_canvas.TextHeight("|"));
}
Ahora hablaremos de los métodos para dibujar el control. Empezamos con el método CTextBox::DrawBorder() destinado para dibujar el borde del campo de edición. Si las dimensiones del campo de edición superan su parte visible, el área de visibilidad puede desplazarse (mediante las barras de desplazamiento o el cursor). Por eso, es necesario dibujar el borde tomando en cuenta estos desplazamientos.
{
private:
//--- Dibuja el borde
void DrawBorder(void);
};
//+------------------------------------------------------------------+
//| Dibuja el borde del campo de edición |
//+------------------------------------------------------------------+
void CTextBox::DrawBorder(void)
{
//--- Obtenemos el color del borde respecto el estado actual del control
uint clr=BorderColorCurrent();
//--- Obtenemos el desplazamiento por el eje X
int xo=(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
int yo=(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Límites
int x_size =m_canvas.X_Size()-1;
int y_size =m_canvas.Y_Size()-1;
//--- Coordenadas: Arriba/Derecha/Abajo/Izquierda
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;
//--- Dibujamos el marco según las coordenadas especificadas
for(int i=0; i<4; i++)
m_canvas.Line(x1[i],y1[i],x2[i],y2[i],clr);
}
El método CTextBox::DrawBorder() va a utilizarse también dentro del método CTextBox::ChangeObjectsColor(), cuando simplemente hay que cambiar el color del borde del campo de edición al situar el cursor encima (véase el código de abajo). Para eso será suficiente redibujar el marco (en vez del campo entero) y actualizar la imágen. El método CTextBox::ChangeObjectsColor() va a invocarse en el manejador de eventos del control. Precisamente aquí se monitorea el hecho de la intersección del borde del control por el cursor, para que la imagen no se redibuje muy a menudo.
{
private:
//--- Cambio del color de objetos
void ChangeObjectsColor(void);
};
//+------------------------------------------------------------------+
//| Cambio del color de objetos |
//+------------------------------------------------------------------+
void CTextBox::ChangeObjectsColor(void)
{
//--- Si no se encuentra en el foco
if(!CElementBase::MouseFocus())
{
//--- Si todavía no está indicado que no se encuentra en el foco
if(CElementBase::IsMouseFocus())
{
//--- Colocar la bandera
CElementBase::IsMouseFocus(false);
//--- Cambiar el color
DrawBorder();
m_canvas.Update();
}
}
else
{
//--- Si todavía no está indicado que se encuentra en el foco
if(!CElementBase::IsMouseFocus())
{
//--- Colocar la bandera
CElementBase::IsMouseFocus(true);
//--- Cambiar el color
DrawBorder();
m_canvas.Update();
}
}
}
El método CTextBox::TextOut() se utiliza para visualizar el texto en el lienzo. Aquí, primero vaciamos el lienzo, coloreándolo en el color especificado. Luego, el programa puede seguir dos caminos:
- Si el modo multilínea está desactivado y además en la línea no hay caracteres, hay que mostrar el texto por defecto (si está especificado). Va a visualizarse en el centro del campo de edición.
- Si el modo multilínea está activado o la línea contiene por lo menos un carácter, obtenemos en el ciclo el alto de la línea y mostramos todas las líneas, recopilándolas previamente desde el array de los caracteres. Por defecto, para el texto se establecen los márgenes desde la esquina superior izquierda del campo de edición. En el eje X son 5 píxeles, y en el eje Y son 4 píxeles. Estos valores pueden ser reajustados a través de los métodos CTextBox::TextXOffset() y CTextBox::TextYOffset().
{
private:
//--- Márgenes para el texto desde los bordes del campo de edición
int m_text_x_offset;
int m_text_y_offset;
//---
public:
//--- Márgenes para el texto desde los bordes del campo de edición
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:
//--- Visualización del texto en el lienzo
void TextOut(void);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CTextBox::CTextBox(void) : m_text_x_offset(5),
m_text_y_offset(4)
{
...
}
//+------------------------------------------------------------------+
//| 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 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 alto de la línea
int line_height=(int)LineHeight();
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//---
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);
//--- Componemos la línea desde el array de caracteres
CollectString(i);
//--- Dibujar el texto
m_canvas.TextOut(x,y,m_temp_input_string,TextColorCurrent(),TA_LEFT);
}
}
//--- 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);
}
}
Para dibujar el cursor de texto, vamos a necesitar los métodos para calcular sus coordenadas. Para calcular la coordenada X, hay que indicar el índice de la línea y el índice del carácter sobre el que es necesario situar el cursor. Para eso se utiliza el método CTextBox::LineWidth(). Dado que el ancho de cada carácter se guarda en el array dinámico m_width[] de la estructura KeySymbolOptions, aquí sólo no queda sumar el ancho de los caracteres hasta la posición indicada.
{
private:
//--- Devuelve el ancho de la línea en píxeles
uint LineWidth(const uint line_index,const uint symbol_index);
};
//+------------------------------------------------------------------+
//| Devuelve el ancho de la línea desde el principio hasta la posición indicada |
//+------------------------------------------------------------------+
uint CTextBox::LineWidth(const uint line_index,const uint symbol_index)
{
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Prevención de superación del rango
uint l=(line_index<lines_total)? line_index : lines_total-1;
//--- Obtenemos el tamaño del array de caracteres de la línea especificada
uint symbols_total=::ArraySize(m_lines[l].m_width);
//--- Prevención de superación del rango
uint s=(symbol_index<symbols_total)? symbol_index : symbols_total;
//--- Sumamos el ancho de todos los caracteres
uint width=0;
for(uint i=0; i<s; i++)
width+=m_lines[l].m_width[i];
//--- Devolver el ancho de la línea
return(width);
}
Los métodos para la obtención de las coordenadas del cursor del texto adquieren una apariencia muy simple (véase el código de abajo). Las coordenadas se cuardan en los campos m_text_cursor_x y m_text_cursor_y. Para los cálculos de las coordenadas también se utiliza la posición actual del cursor, los índices de la línea y del carácter a donde es necesario moverlo. Para el almacenamiento de estos valores se utilizan los campos m_text_cursor_x_pos y m_text_cursor_y_pos.
{
private:
//--- Coordenadas actuales del cursor del texto
int m_text_cursor_x;
int m_text_cursor_y;
//--- Posición actual del cursor del texto
uint m_text_cursor_x_pos;
uint m_text_cursor_y_pos;
//---
private:
//--- Cálculo de las coordenadas para el cursor del texto
void CalculateTextCursorX(void);
void CalculateTextCursorY(void);
};
//+------------------------------------------------------------------+
//| Cálculo de la coordenada X para el cursor del texto |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextCursorX(void)
{
//--- Obtenemos el ancho de la línea
int line_width=(int)LineWidth(m_text_cursor_x_pos,m_text_cursor_y_pos);
//--- Calcular y guardar la coordenada X del cursor
m_text_cursor_x=m_text_x_offset+line_width;
}
//+------------------------------------------------------------------+
//| Cálculo de la coordenada Y para el cursor del texto |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextCursorY(void)
{
//--- Obtenemos el alto de la línea
int line_height=(int)LineHeight();
//--- Obtenemos la coordenada Y del cursor
m_text_cursor_y=m_text_y_offset+int(line_height*m_text_cursor_y_pos);
}
Tenemos todo preparado para la implementación del método CTextBox::DrawCursor() a través del cual va a dibujarse el cursor del texto. En muchos otros editores de texto, se puede notar que el cursor y algunos caracteres parcialmente se solapan. So ve que el cursor no sólo los tapa. Los píxeles del carácter solapados por el cursor se dibujan con otro color. Eso está hecho para no perder la legibilidad del carácter.
Como ejemplo, en las capturas de pantalla se muestran los caracteres 'd' и 'д' solapados y no solapados que han sido aumentados en el editor de texto.
Fig. 6. Ejemplo de solapamiento de los píxeles del carácter 'd' por el cursor.
Fig. 7. Ejemplo de solapamiento de los píxeles del carácter 'д' por el cursor.
Para que el cursor y los píxeles del carácter solapados por él siempre se vean sobre el fondo de cualquier color, será suficiente simplemente invertir el color de aquellos píxeles que serán solapados por el cursor.
Ahora vamos a considerar el método CTextBox::DrawCursor() para dibujar el cursor del texto. El ancho del cursor será igual a un píxel, su alto coincidirá con el alto de la línea. Al principio, obtenemos la coordenada X para dibujar el cursor y el alto de la línea. La coordenada Y va a calcularse en ciclo porque el dibujo va a ir por píxeles. Recordamos que en la clase base de los controles CElementBase ya ha sido declarada la instancia de la clase CColors para trabajar con el color. Por eso ahora, en cada iteración, después de calcular la coordenada Y, obtenemos el color del píxel actual en las coordenadas especificadas, y luego a través del método CColors::Negative() lo invertimos y colocamos en el mismo sitio.
{
private:
//--- Dibuja el cursor del texto
void DrawCursor(void);
};
//+------------------------------------------------------------------+
//| Dibuja el cursor del texto |
//+------------------------------------------------------------------+
void CTextBox::DrawCursor(void)
{
//--- Obtenemos el alto de la línea
int line_height=(int)LineHeight();
//--- Obtenemos la coordenada X del cursor
CalculateTextCursorX();
//--- Dibujamos el cursor del texto
for(int i=0; i<line_height; i++)
{
//--- Obtenemos la coordenada Y para el píxel
int y=m_text_y_offset+((int)m_text_cursor_y_pos*line_height)+i;
//--- Obtenemos el color actual del píxel
uint pixel_color=m_canvas.PixelGet(m_text_cursor_x,y);
//--- Invertimos el color para el cursor
pixel_color=m_clr.Negative((color)pixel_color);
m_canvas.PixelSet(m_text_cursor_x,y,::ColorToARGB(pixel_color));
}
}
Han sido implementados dos métodos para dibujar el campo de edición del texto: CTextBox::DrawText() y CTextBox::DrawTextAndCursor().
El método CTextBox::DrawText() debe utilizarse cuando es necesario simplemente actualizar el texto en el campo no activo. Aquí todo es muy fácil. Si el control no está ocultado, visualizamos el texto, dibujamos el borde y actualizamos la imagen.
{
private:
//--- Dibuja el texto
void DrawText(void);
};
//+------------------------------------------------------------------+
//| Dibuja el texto |
//+------------------------------------------------------------------+
void CTextBox::DrawText(void)
{
//--- Salir si el control está ocultado
if(!CElementBase::IsVisible())
return;
//--- Mostramos el texto
CTextBox::TextOut();
//--- Рисуем рамку
DrawBorder();
//--- Actualizar el campo de edición
m_canvas.Update();
}
Si el campo de edición está activado, aparte del texto hay que visualizar el cursor parpadeante, el método CTextBox::DrawTextAndCursor(). Para el parpadeo, es necesario determinar el estado mostrar/ocultar para el cursor. Con cada llamada a este método, el estado va a cambiar por el opuesto. Además, se ha tomado en cuenta la posibilidad de visualización forzada cuando en el método (argumento show_state) se pasa el valor true. La visualización forzada será necesaria durante el desplazamiento del cursor por el campo de edición activado. Prácticamente, el parpadeo del cursor se realizará en el temporizador del control según el intervalo establecido en el constructor de la clase para el contador de tiempo, 200 milisegundos. Hay que resetear el contador cada vez después de llamar al método CTextBox::DrawTextAndCursor().
{
private:
//--- Mostrar el texto y el cursor parpadeante
void DrawTextAndCursor(const bool show_state=false);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CTextBox::CTextBox(void)
{
//---
//--- Establecimiento de parámetros para el contador del temporizador
m_counter.SetParameters(16,200);
}
//+------------------------------------------------------------------+
//| Temporizador |
//+------------------------------------------------------------------+
void CTextBox::OnEventTimer(void)
{
...
//--- Pausa entre la actualización del cursor
if(m_counter.CheckTimeCounter())
{
//--- Actualizamos el cursor del texto si el control está visible y el campo de edición está activado
if(CElementBase::IsVisible() && m_text_edit_state)
DrawTextAndCursor();
}
}
//+------------------------------------------------------------------+
//| Mostrar el texto y el cursor parpadeante |
//+------------------------------------------------------------------+
void CTextBox::DrawTextAndCursor(const bool show_state=false)
{
//--- Determinamos el estado para el cursor del texto (mostrar/ocultar)
static bool state=false;
state=(!show_state)? !state : show_state;
//--- Выводим текст
CTextBox::TextOut();
//--- Dibujar el cursor del texto
if(state)
DrawCursor();
//--- Рисуем рамку
DrawBorder();
//--- Actualizar el campo de edición
m_canvas.Update();
//--- Poner a cero el contador
m_counter.ZeroTimeCounter();
}
Para crear el control «Campo de edición del texto multilínea», necesitaremos tres métodos privados (private) dos de los cuales se necesitan para la creación de las barras de desplazamiento, y uno público (public) para la llamada externa.
{
private:
//--- Objetos para crear el control
CRectCanvas m_canvas;
CScrollV m_scrollv;
CScrollH m_scrollh;
//---
public:
//--- Métodos para crear el control
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:
//--- Devuelve los punteros a las barras de desplazamiento
CScrollV *GetScrollVPointer(void) { return(::GetPointer(m_scrollv)); }
CScrollH *GetScrollHPointer(void) { return(::GetPointer(m_scrollh)); }
};
Antes de llamar al método CTextBox::CreateCanvas() para la creación del campo de edición, es necesario calcular primero su tamaño. Aquí vamos a utilizar el mismo método que ha sido implementado en la tabla dibujada tipo CCanvasTable. Vamos a repasarlo rápidamente. Hay tamaño total de la imagen y hay tamaño de su parte visible. El tamaño del control es el mismo que el tamaño de la parte visible de la imagen. Al desplazar el cursor del texto o las barras de desplazamiento, las coordenadas de la imagen van a cambiar, y las coordenadas de la parte visible (son las coordenadas del control) van a quedarse en su sitio.
Se puede calcular los tamaños por el eje Y multiplicando el número de las líneas por su altura. Aquí mismo se consideran los márgenes desde los bordes del campo de edición y el tamaño de la barra de desplzamiento. Para calcular los tamaños por el eje X, hay que saber el ancho máximo de la línea de todo el bloque. Utilizaremos para eso el método CTextBox::MaxLineWidth(). Aquí, recorremos en ciclo el array de las líneas, guardamos el ancho total de la línea si es superior al ancho anterior, y devolvemos el valor.
{
private:
//--- Devuelve el ancho máximo de la línea
uint MaxLineWidth(void);
};
//+------------------------------------------------------------------+
//| Devuelve el ancho máximo de la línea |
//+------------------------------------------------------------------+
uint CTextBox::MaxLineWidth(void)
{
uint max_line_width=0;
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
for(uint i=0; i<lines_total; i++)
{
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[i].m_symbol);
//--- Obtenemos el ancho de la línea
uint line_width=LineWidth(symbols_total,i);
//--- Guardamos el ancho máximo de la línea
if(line_width>max_line_width)
max_line_width=line_width;
}
//--- Devolver el ancho máximo de la línea
return(max_line_width);
}
Entonces, el código del método CTextBox::CalculateTextBoxSize() para el cálculo de los tamaños del control va a tener el aspecto que se muestra más abajo. La llamada a este método también va a realizarse dentro de los métodos CTextBox::ChangeWidthByRightWindowSide() y CTextBox::ChangeHeightByBottomWindowSide(), cuya destinación se reduce a que los los tamaños del control se ajusten automáticamente a los tamaños del formulario, si estas propiedades han sido establecidas por el desarrollador.
{
private:
//--- Tamaño total y tamaño de la parte visible del control
int m_area_x_size;
int m_area_y_size;
int m_area_visible_x_size;
int m_area_visible_y_size;
//---
private:
//--- Calcula el ancho del campo de edición del texto
void CalculateTextBoxSize(void);
};
//+------------------------------------------------------------------+
//| Calcula el ancho del campo de edición del texto |
//+------------------------------------------------------------------+
void CTextBox::CalculateTextBoxSize(void)
{
//--- Obtenemos el ancho máximo de la línea desde el campo de edición del texto
int max_line_width=int((m_text_x_offset*2)+MaxLineWidth()+m_scrollv.ScrollWidth());
//--- Determinamos el ancho total
m_area_x_size=(max_line_width>m_x_size)? max_line_width : m_x_size;
//--- Determinamos el ancho visible
m_area_visible_x_size=m_x_size;
//--- Obtenemos el alto de la línea
int line_height=(int)LineHeight();
//--- Obtenemos el tamaño del array de líneas
int lines_total=::ArraySize(m_lines);
//--- Calculamos el alto total del control
int lines_height=int((m_text_y_offset*2)+(line_height*lines_total)+m_scrollh.ScrollWidth());
//--- Determinamos el alto total
m_area_y_size=(m_multi_line_mode && lines_height>m_y_size)? lines_height : m_y_size;
//--- Determinamos el alto visible
m_area_visible_y_size=m_y_size;
}
Hemos calculado los tamaños. Ahora es necesario aplicarlos. Para eso se utiliza el método CTextBox::ChangeTextBoxSize(). Aquí, a través de los argumentos del método, se puede indicar si es necesario desplazar el área de visibilidad al inicio o dejarla en las mismas posiciones. Además de eso, aquí también se cambian los tamaños de las barras de desplazamiento y se realiza la corrección final del área de visibilidad respecto a los deslizadores de estas barras. No vamos a estudiar aquí el código de estos métodos, porque lo hemos hecho en los artículos anteriores.
{
private:
//--- Cambiar los tamaños del campo de edición
void ChangeTextBoxSize(const bool x_offset=false,const bool y_offset=false);
};
//+------------------------------------------------------------------+
//| Cambiar los tamaños del campo de edición |
//+------------------------------------------------------------------+
void CTextBox::ChangeTextBoxSize(const bool is_x_offset=false,const bool is_y_offset=false)
{
//--- Establecer nuevo tamaño de la tabla
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);
//--- Establecemos los tamaños del área visible
m_canvas.SetInteger(OBJPROP_XSIZE,m_area_visible_x_size);
m_canvas.SetInteger(OBJPROP_YSIZE,m_area_visible_y_size);
//--- Diferencia entre el ancho total y la parte visible
int x_different=m_area_x_size-m_area_visible_x_size;
int y_different=m_area_y_size-m_area_visible_y_size;
//--- Establecemos el desplazamiento del frame dentro de la imagen por los ejes X y 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);
//--- Cambiar los tamaños de las barras de desplazamiento
ChangeScrollsSize();
//--- Corrección de datos
ShiftData();
}
Para gestionar el estado del control y para obtener su estado actual, se utilizan los siguientes campos y métodos:
- El método CTextBox::TextEditState() obtiene el estado del campo de edición.
- La llamada al método CTextBox::TextBoxState() bloquea/desbloquea el control. El control bloqueado se pasa al modo «Sólo lectura». En este caso, para el fondo, borde y el texto serán establecidos los colores correspondientes (el usuario puede hacerlo antes de crear el control).
{
private:
//--- Modo «Sólo lectura»
bool m_read_only_mode;
//--- Estado del campo de edición
bool m_text_edit_state;
//--- Estado del control
bool m_text_box_state;
//---
public:
//--- (1) Estado del campo de edición, (2) devolver/establecer el estado de disponibilidad del control
bool TextEditState(void) const { return(m_text_edit_state); }
bool TextBoxState(void) const { return(m_text_box_state); }
void TextBoxState(const bool state);
};
//+------------------------------------------------------------------+
//| Establecer el estado de disponibilidad del control |
//+------------------------------------------------------------------+
void CTextBox::TextBoxState(const bool state)
{
m_text_box_state=state;
//--- Ajuste respecto el estado actual
if(!m_text_box_state)
{
//--- Prioridades
1. //--- Campo de edición en el modo “Sólo lectura”
m_read_only_mode=true;
}
else
{
//--- Prioridades
m_canvas.Z_Order(m_text_edit_zorder);
//--- Campo de edición en modo de edición
m_read_only_mode=false;
}
//--- Actualizar el campo de edición
DrawText();
}
Control del cursor del texto
El campo de edición se activa al pincharlo con el ratón. Enseguida se identifican las coordenadas del punto de pulsación y el cursor se coloca en este punto. De eso se encarga el método CTextBox::OnClickTextBox(). Pero antes de pasar a su descripción, primero vamos a considerar unos métodos auxiliares que van a llamarse dentro de él y en muchos otros métodos de la clase CTextBox.
El método CTextBox::SetTextCursor() se usa para actualizar los valores de la posición del cursor del texto. En el modo del campo multilínea, la posición en el eje Y siempre es igual a 0.
{
private:
//--- Posición actual del cursor del texto
uint m_text_cursor_x_pos;
uint m_text_cursor_y_pos;
//---
private:
//--- Coloca el cursor según las posiciones especificadas
void SetTextCursor(const uint x_pos,const uint y_pos);
};
//+------------------------------------------------------------------+
//| Coloca el cursor según las posiciones especificadas |
//+------------------------------------------------------------------+
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 gestionar las barras de desplazamiento. Ya hemos hablado de métodos semejantes en el artículo anterior de la serie, por eso no vamos a mostrar su código aquí. Sólo recordaré un poco que si el parámetro no se envía, el deslizador será desplazado a la última posición, o sea al final de la lista/texto/documento.
{
public:
//--- Desplazamiento de la tabla: (1) horizontal y (2) vertical
void VerticalScrolling(const int pos=WRONG_VALUE);
void HorizontalScrolling(const int pos=WRONG_VALUE);
};
El método CTextBox::DeactivateTextBox() es necesario para la desactivación del campo de edición. Aquí cabe mencionar sobre la nueva posibilidad proporcionada hace poco por los desarrolladores del terminal. Ha sido añadido un identificador más del gráfico (CHART_KEYBOARD_CONTROL) que pertenece a la familia de las enumeraciones ENUM_CHART_PROPERTY. Activa o desactiva la posibilidad de controlar el gráfico desde el teclado usando las teclas 'Left', 'Right', 'Home', 'End', 'Page Up', 'Page Down', así como a través de las teclas para el escalamiento del gráfico '+' и '-'. De esta manera, cuando el campo de edición se activa, hay que deshabilitar la gestión del gráfico para que las pulsaciones de las teclas mencionadas no se interfieren por el terminal y eso, en su lugar, no afecte el trabajo del campo de edición. En el momento de la desactivación del campo de edición, se puede volver a activar el control del gráfico desde el teclado.
Aquí mismo es necesario redibujar el campo de edición, y si no se trata del modo multilínea, hay que mover el cursor del texto y el deslizador de la barra de desplazamiento al principio de la línea.
{
private:
//--- Desactiva el campo de edición
void DeactivateTextBox(void);
};
//+------------------------------------------------------------------+
//| Desactivación del campo de edición |
//+------------------------------------------------------------------+
void CTextBox::DeactivateTextBox(void)
{
//--- Salir si ya está desactivado
if(!m_text_edit_state)
return;
//--- Desactivar
m_text_edit_state=false;
//--- Activamos la gestión del gráfico
m_chart.SetInteger(CHART_KEYBOARD_CONTROL,true);
//--- Dibujar el texto
DrawText();
//--- Si el modo multilínea está deshabilitado
if(!m_multi_line_mode)
{
//--- Mover el cursor al principio de la línea
SetTextCursor(0,0);
//--- Mover la barra de desplazamiento al principio de la línea
HorizontalScrolling(0);
}
}
Controlando el cursor del texto, hay que monitorear si ha cruzado o no los límites del área de visibilidad. Si el cruzamiento ha tenido lugar, hay que devolver el cursor en el área de visibilidad. Para eso vamos a necesitar los métodos adicionales de uso múltiple. Es necesario calcular los límites admisibles del campo de edición tomando en cuenta el modo multilínea y la presencia de las barras de desplazamiento.
Con el fin de calcular la distancia necesaria del desplazamiento del área de visibilidad, en primer lugar, es necesario averiguar los valores del desplazamiento actual:
{
private:
//--- Para el cálculo de los límites de la parte visible del campo de edición
int m_x_limit;
int m_y_limit;
int m_x2_limit;
int m_y2_limit;
//---
private:
//--- Cálculo de los límites del campo de edición
void CalculateBoundaries(void);
void CalculateXBoundaries(void);
void CalculateYBoundaries(void);
};
//+------------------------------------------------------------------+
//| Cálculo de los límites del campo de edición por dos ejes |
//+------------------------------------------------------------------+
void CTextBox::CalculateBoundaries(void)
{
CalculateXBoundaries();
CalculateYBoundaries();
}
//+------------------------------------------------------------------+
//| Cálculo de los límites del campo de edición por el eje X |
//+------------------------------------------------------------------+
void CTextBox::CalculateXBoundaries(void)
{
//--- Obtenemos la coordenada X y el desplazamiento por el eje X
int x =(int)m_canvas.GetInteger(OBJPROP_XDISTANCE);
int xoffset =(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
//--- Calculamos los límites de la parte visible del campo de edición
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 de los límites del campo de edición por el eje Y |
//+------------------------------------------------------------------+
void CTextBox::CalculateYBoundaries(void)
{
//--- Salir si el modo multilínea está desactivado
if(!m_multi_line_mode)
return;
//--- Obtenemos la coordenada Y y el desplazamiento por el eje Y
int y =(int)m_canvas.GetInteger(OBJPROP_YDISTANCE);
int yoffset =(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Calculamos los límites de la parte visible del campo de edición
m_y_limit =(y+yoffset)-y;
m_y2_limit =(y+yoffset+m_y_size-m_scrollh.ScrollWidth())-y;
}
Con el fin de posicionar las barras de desplazamiento con precisión respecto a la ubicación actual del cursor, van a usarse los siguientes métodos:
{
private:
//--- Cálculo de la posición X del deslizador de la barra en el borde izquierdo del campo de edición
int CalculateScrollThumbX(void);
//--- Cálculo de la posición X del deslizador de la barra en el borde derecho del campo de edición
int CalculateScrollThumbX2(void);
//--- Cálculo de la posición Y del deslizador de la barra en el borde superior del campo de edición
int CalculateScrollThumbY(void);
//--- Cálculo de la posición Y del deslizador de la barra en el borde inferior del campo de edición
int CalculateScrollThumbY2(void);
};
//+------------------------------------------------------------------+
//| Cálculo de la posición X de la barra de desplazamiento en el borde izquierdo del campo de edición |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbX(void)
{
return(m_text_cursor_x-m_text_x_offset);
}
//+------------------------------------------------------------------+
//| Cálculo de la posición X de la barra de desplazamiento en el borde derecho del campo de edición |
//+------------------------------------------------------------------+
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);
}
//+------------------------------------------------------------------+
//| Cálculo de la posición Y de la barra de desplazamiento en el borde superior del campo de edición |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbY(void)
{
return(m_text_cursor_y-m_text_y_offset);
}
//+------------------------------------------------------------------+
//| Cálculo de la posición Y de la barra de desplazamiento en el borde inferior del campo de edición |
//+------------------------------------------------------------------+
int CTextBox::CalculateScrollThumbY2(void)
{
//--- Establecemos la fuente para la visualización en el lienzo (es necesario para obtener el alto de la línea)
m_canvas.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Obtenemos el alto de la línea
int line_height=m_canvas.TextHeight("|");
//--- Calcular y devolver el valor
return(m_text_cursor_y-m_y_size+m_scrollh.ScrollWidth()+m_text_y_offset+line_height);
}
Hagamos que la pulsación en el campo de edición genere un evento que daría a entender claramente que este campo ha sido activado. Además, necesitamos obtener el evento que corresponda al desplazamiento del cursor en el campo de edición. Vamos a añadir nuevos identificadores al archivo Defines.mqh:
- ON_CLICK_TEXT_BOX para señalar el evento de activación del campo de edición del texto.
- ON_MOVE_TEXT_CURSOR para señalar el evento del desplazamiento del cursor del texto.
//| Defines.mqh |
//| Copyright 2015, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_CLICK_TEXT_BOX (31) // Activación del campo de edición del texto
#define ON_MOVE_TEXT_CURSOR (32) // Desplazamiento del cursor del texto
Como información adicional, vamos a colocar la posición actual del cursor del texto en el parámetro string de los eventos con estos identificadores. Esto está implementado en muchos otros editores del texto, incluyendo MetaEditor. En la imagen de abajo, se muestra cómo está formada la cadena para la visualización en la barra de estado del editor del código.
Fig. 8. Posición del cursor de texto en el editor del código MetaEditor.
Abajo se muestra el código del método CTextBox::TextCursorInfo() que devuelve la cadena en el formato como en el pantallazo de arriba. También se muestran los métodos adicionales que permiten obtener el número de las líneas y caracteres en la línea especificada y la posición actual del cursor del texto.
{
private:
//--- Devuelve el índice de la (1) línea, (2) símbolo en el que se encuentra el cursor del texto,
// (3) número de líneas, (4) número de símbolos en la línea 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);
//--- Información sobre el cursor del texto (línea/número de líneas, columna/número de columnas)
string TextCursorInfo(void);
};
//+------------------------------------------------------------------+
//| Devuelve el número de caracteres en la línea especificada |
//+------------------------------------------------------------------+
uint CTextBox::ColumnsTotal(const uint line_index)
{
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Prevención de superación del rango
uint check_index=(line_index<lines_total)? line_index : lines_total-1;
//--- Obtenemos el tamaño del array de caracteres en la línea
uint symbols_total=::ArraySize(m_lines[check_index].m_symbol);
//--- Devolver el número de caracteres
return(symbols_total);
}
//+------------------------------------------------------------------+
//| Información del cursor del texto |
//+------------------------------------------------------------------+
string CTextBox::TextCursorInfo(void)
{
//--- Componentes para la cadena
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);
//--- Formaremos la cadena
string text_box_info="Ln "+text_cursor_line+"/"+lines_total+", "+"Col "+text_cursor_column+"/"+columns_total;
//--- Devolver la línea
return(text_box_info);
}
Ahora tenemos todo preparado para mostrar la descripción del método CTextBox::OnClickTextBox(), el que hemos mencionado empezando esta sección (véase el código de abajo). Aquí, primero se realiza la comprobación del nombre del objeto en el que ha sido hecho el clic izquierdo. Si ha resultado que el clic ha sido hecho fuera del campo de edición, enviamos el mensaje sobre la finalización del proceso de edición (identificador del evento ON_END_EDIT), en caso cuando el campo todavía sigue activado. Luego desactivamos el campo de edición y salimos del método.
Si el clic ha sido hecho en el campo de edición, nos esperan otras dos comprobaciones. El programa saldrá del método si el modo «Sólo lectura» está activado o si el control está bloqueado. Si una de estas condiciones es falsa, pasamos al código principal del método.
En primer lugar, se desactiva la gestión del gráfico desde el teclado. Luego, (1) obtenemos el desplazamiento actual del área del control, (2) determinamos las coordenadas relativas donde ha sido hecho el clic. Además, necesitaremos el alto de la línea en el ciclo principal del método para los cálculos.
Primero, buscamos en el ciclo la línea que ha sido pulsada. Empezamos a buscar el carácter sólo cuando la coordenada Y calculada de la pulsación se ha encontrado entre el límite superior e inferior de la línea. Si resulta que esta línea no contiene ningún carácter, es necesario mover el cursor del texto y la barra de desplazamiento en el comienzo de la línea. Aquí, el ciclo se detiene.
Si la línea contiene caracteres, se empieza el segundo ciclo en el que se realiza la búsqueda del carácter que ha sido pulsado. Aquí, el principio de la búsqueda es casi el mismo, como en el caso de las líneas. Lo único que ahora también obtenemos el ancho del carácter en cada iteración, puesto que el ancho de caracteres no es el mismo en todas las fuentes. Si encontramos el carácter que ha sido pulsado, colocamos el cursor del texto en la posición de este carácter y terminamos la búsqueda. Si el carácter no ha sido encontrado en esta línea y hemos llegado al último, movemos el cursor a la última posición de la línea donde el carácter no está presente, y terminamos la búsqueda.
Luego, si el modo multilínea está activado, tenemos que comprobar si sale el propio cursor (aunque sea parcialmente) fuera de los límites del área visible del campo de edición por el eje Y. Si sale, corregimos el área de visibilidad respecto a la posición del cursor del texto. A continuación, colocamos la bandera del campo de edición activado y lo redibujamos.
Y al final del método CTextBox::OnClickTextBox(), se genera el evento que avisa sobre la activación del campo de edición (identificador del evento ON_CLICK_TEXT_BOX), enviando para una identificación unívoca (1) el identificador del control, (2) índice del control y como adición, (3) la información sobre la posición del cursor.
{
private:
//--- Procesamiento del clic en el control
bool OnClickTextBox(const string clicked_object);
};
//+------------------------------------------------------------------+
//| Procesamiento del clic en el control |
//+------------------------------------------------------------------+
bool CTextBox::OnClickTextBox(const string clicked_object)
{
//--- Salimos si el nombre del objeto no coincide
if(m_canvas.Name()!=clicked_object)
{
//--- Enviamos el mensaje sobre el fin de la introducción de la linea en el campo de edición, si el campo ha sido activado
if(m_text_edit_state)
::EventChartCustom(m_chart_id,ON_END_EDIT,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
//--- Desactivar el campo de edición
DeactivateTextBox();
return(false);
}
//--- Salir si (1) el modo «Sólo lectura» está activado o si (2) el control está bloqueado
if(m_read_only_mode || !m_text_box_state)
return(true);
//--- Desactivamos la gestión del gráfico
m_chart.SetInteger(CHART_KEYBOARD_CONTROL,false);
//--- Obtenemos el desplazamiento por los ejes X y Y
int xoffset=(int)m_canvas.GetInteger(OBJPROP_XOFFSET);
int yoffset=(int)m_canvas.GetInteger(OBJPROP_YOFFSET);
//--- Determinamos las coordenadas para el campo de edición debajo del cursor del ratón
int x =m_mouse.X()-m_canvas.X()+xoffset;
int y =m_mouse.Y()-m_canvas.Y()+yoffset;
//--- Obtenemos el alto de la línea
int line_height=(int)LineHeight();
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Determinamos el carácter de la pulsación
for(uint l=0; l<lines_total; l++)
{
//--- Establecemos las coordenadas iniciales para comprobar las condiciones
int x_offset=m_text_x_offset;
int y_offset=m_text_y_offset+((int)l*line_height);
//--- Comprobación de la condición en el eje Y
bool y_pos_check=(l<lines_total-1)?(y>=y_offset && y<y_offset+line_height) : y>=y_offset;
//--- Si la pulsación ha sido hecha en esta línea, vamos a la siguiente
if(!y_pos_check)
continue;
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[l].m_width);
//--- Si es una línea vacía, mover el cursor en la posición indicada y salir del ciclo
if(symbols_total<1)
{
SetTextCursor(0,l);
HorizontalScrolling(0);
break;
}
//--- Buscamos el carácter que hasido pulsado
for(uint s=0; s<symbols_total; s++)
{
//--- Si hemos encontrado el carácter, mover el cursor en la posición indicada y salir del ciclo
if(x>=x_offset && x<x_offset+m_lines[l].m_width[s])
{
SetTextCursor(s,l);
l=lines_total;
break;
}
//--- Añadir el ancho del carácter actual para la siguiente comprobación
x_offset+=m_lines[l].m_width[s];
//--- Si es el último carácter, movemos el cursor al final de la línea y salimos del ciclo
if(s==symbols_total-1 && x>x_offset)
{
SetTextCursor(s+1,l);
l=lines_total;
break;
}
}
}
//--- Si el modo del campos de edición multilínea está activado
if(m_multi_line_mode)
{
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateYBoundaries();
//--- Obtenemos la coordenada Y del cursor
CalculateTextCursorY();
//--- Mover la barra del desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_y<=m_y_limit)
VerticalScrolling(CalculateScrollThumbY());
else
{
if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
VerticalScrolling(CalculateScrollThumbY2());
}
}
//--- Activar el campo de edición
m_text_edit_state=true;
//--- Actualizar el texto y el cursor
DrawTextAndCursor(true);
//--- Enviamos el mensaje sobre ello
::EventChartCustom(m_chart_id,ON_CLICK_TEXT_BOX,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
return(true);
}
Introducción del carácter
Ahora hablaremos del método CTextBox::OnPressedKey(). Este método procesa la pulsación en la tecla, y si resulta que la tecla pulsada contiene algún carácter, hay que insertarlo en la línea desde la posición actual del cursor del texto. Aquí necesitaremos unos métodos adicionales para el aumento del tamaño de los arrays de la estructura KeySymbolOptions añadiendo en ellos el carácter introducido en el campo de edición, así como su ancho en el elemento añadido de los arrays.
Para el cambio del tamaño de los arrays, en muchos métodos de la clase CTextBox va a utilizarse un método bastante simple, CTextBox::ArraysResize():
{
private:
//--- Establece nuevo tamaño para los arrays de las propiedades de la línea especificada
void ArraysResize(const uint line_index,const uint new_size);
};
//+------------------------------------------------------------------+
//| Establece nuevo tamaño para los arrays de las propiedades de la línea especificada |
//+------------------------------------------------------------------+
void CTextBox::ArraysResize(const uint line_index,const uint new_size)
{
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Prevención de superación del rango
uint l=(line_index<lines_total)? line_index : lines_total-1;
//--- Establecemos el tamaño para los arrays de la estructura
::ArrayResize(m_lines[line_index].m_width,new_size);
::ArrayResize(m_lines[line_index].m_symbol,new_size);
}
Para la inserción del nuevo carácter en el campo de edición se utiliza el método CTextBox::AddSymbol(). Analizaremos este método más detalladamente. Al insertar un carácter nuevo, hay que aumentar el tamaño de los arrays a un elemento. El cursor del texto puede ubicarse en cualquier carácter de la línea. Por eso, antes de añadir el carácter al array, primero hay que desplazar todos los caracteres ubicados del lado derecho de la posición actual del cursor a un índice a la derecha. Después de eso, guardamos el carácter introducido en la posición actual del cursor del texto. Al final del método, desplazamos el cursor del texto a un carácter hacia la derecha.
{
private:
//--- Añade el carácter y sus propiedades a los arrays de la estructura
void AddSymbol(const string key_symbol);
};
//+------------------------------------------------------------------+
//| Añade el carácter y sus propiedades a los arrays de la estructura |
//+------------------------------------------------------------------+
void CTextBox::AddSymbol(const string key_symbol)
{
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Establecemos nuevo tamaño par los arrays
ArraysResize(m_text_cursor_y_pos,symbols_total+1);
//--- Desplazar todos los caracteres desde el fin del array hacia el índice del carácter añadido
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];
}
//--- Obtenemos el ancho del carácter
int width=m_canvas.TextWidth(key_symbol);
//--- Añadir el carácter al elemento liberado
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;
//--- Aumentar el contador de la posición del cursor
m_text_cursor_x_pos++;
}
Abajo se muestra el código del método CTextBox::OnPressedKey(). Si el campo de edición está activado, intentamos obtener el carácter usando el código de la tecla enviado al método. Si resulta que la tecla pulsada no contiene carácter, el programa sale del método. Si lo contiene, entonces este carácter y sus propiedades se añaden a los arrays. Al introducir el carácter, los tamaños del campo de edición pueden cambiar, por eso se calculan y se establecen nuevos valores. Después de eso, obtenemos los borden del campo de edición y la coordenada actual del cursor del texto. Si el cursor sale fuera del borde derecho del campo, corregimos la posición del deslizador de la barra de desplazamiento horizontal. Luego, el campo de edición se redibuja mostrando forzosamente (true) el cursor del texto. Al final del método CTextBox::OnPressedKey(), se genera el evento del desplazamiento del cursor del texto (ON_MOVE_TEXT_CURSOR) con el identificador del control, índice del control y la información adicional sobre la posición del cursor del texto.
{
private:
//--- Procesamiento de la pulsación de la tecla
bool OnPressedKey(const long key_code);
};
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la tecla |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKey(const long key_code)
{
//--- Salir si el campo de edición no está activado
if(!m_text_edit_state)
return(false);
//--- Obtenemos el carácter de la tecla
string pressed_key=m_keys.KeySymbol(key_code);
//--- Salir si no hay carácter
if(pressed_key=="")
return(false);
//--- Añadimos el carácter y sus propiedades
AddSymbol(pressed_key);
//--- Calcular los tamaños del campo de edición
CalculateTextBoxSize();
//--- Establecer nuevo tamaño para el campo de edición
ChangeTextBoxSize(true,true);
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateXBoundaries();
//--- Obtenemos la coordenada X del cursor
CalculateTextCursorX();
//--- Mover la barra del desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_x>=m_x2_limit)
HorizontalScrolling(CalculateScrollThumbX2());
//--- 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);
}
Procesamiento de la pulsación de la tecla 'Backspace'
Ahora veremos la situación cuando el carácter se elimina al pulsar la tecla 'Backspace'. En este caso, en el manejador de eventos del control «Campo de edición multilínea» va a invocarse el método CTextBox::OnPressedKeyBackspace(). Para su trabajo, también necesitaremos los métodos adicionales que hemos analizado anteriormente. Primero, se mostrará su código.
Los caracteres se eliminan a través del método CTextBox::DeleteSymbol(). Primero, comprobamos si la línea actual contiene por lo menos un carácter. Si no lo contiene, colocamos el cursor del texto en el comienzo de la línea y salimos del método. Si hay caracteres, obtenemos la posición del carácter anterior. Eso será el índice a partir del cual hay que desplazar todos los caracteres de la derecha a un elemento a la izquierda. Después de eso, el cursor del texto también se desplaza a una posición a la izquierda. Al final del método, el tamaño de los arrays se reduce a un elemento.
{
private:
//--- Elimina el carácter
void DeleteSymbol(void);
};
//+------------------------------------------------------------------+
//| Elimina el carácter |
//+------------------------------------------------------------------+
void CTextBox::DeleteSymbol(void)
{
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Si el array está vacío
if(symbols_total<1)
{
//--- Colocamos el cursor en la posición cero de la línea actual
SetTextCursor(0,m_text_cursor_y_pos);
return;
}
//--- Obtenemos la posición del carácter anterior
int check_pos=(int)m_text_cursor_x_pos-1;
//--- Salir si se supera el rango
if(check_pos<0)
return;
//--- Desplazar todos los caracteres a un elemento a la izquierda desde el índice del carácter eliminado hacia el fin del 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];
}
//--- Disminuir el contador de la posición del cursor
m_text_cursor_x_pos--;
//--- Establecemos nuevo tamaño para los arrays
ArraysResize(m_text_cursor_y_pos,symbols_total-1);
}
Si el cursor del texto se encuentra al principio de la línea, y además no es la primera línea, hay que eliminar la línea actual y subir todas las líneas inferiores a una posición. Si la línea a eliminar contiene caracteres, hay que añadirlos a la línea que se encuentra en una posición arriba. Para esta operación se utiliza el método adicional CTextBox::ShiftOnePositionUp(). También vamos a necesitar el método adicional CTextBox::LineCopy(), que facilitará un poco el copiado de las líneas.
{
private:
//--- Hace la copia de la línea especificada (source) insertándola en nuevo lugar (destination)
void LineCopy(const uint destination,const uint source);
};
//+------------------------------------------------------------------+
//| Establece nuevo tamaño para los arrays de las propiedades de la línea 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);
}
Abajo se muestra el código del método CTextBox::ShiftOnePositionUp(). En el primer ciclo del método, todas las líneas por debajo de la posición actual del cursor suben a una posición. En la primera iteración es necesario comprobar si la línea contiene caracteres, y si los contiene, recordarlos con el fin de añadir a la línea anterior.. Una vez desplazadas las líneas, el array de las líneas se reduce a un elemento. El cursor del texto se desplaza al final de la línea anterior.
El último bloque del código del método CTextBox::ShiftOnePositionUp() sirve para añadir los caracteres de la línea eliminada a la línea anterior. Si hay una línea para la adición, usamos la función ::StringToCharArray() para trasladarla en el array temporal tipo uchar, como los códigos de los caracteres. Luego, aumentamos el array de la línea actual al número de los caracteres a insertar.. Como operación final, añadimos en ciclo los caracteres y sus propiedades a los arrays uno por uno. La conversión de los códigos de los caracteres del array temporal tipo uchar se realiza a través de la funión ::CharToString().
{
private:
//--- Sube las líneas a una posición
void ShiftOnePositionUp(void);
};
//+------------------------------------------------------------------+
//| Sube las líneas a una posición |
//+------------------------------------------------------------------+
void CTextBox::ShiftOnePositionUp(void)
{
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Desplazamos las líneas desde el siguiente elemento a una posición hacia arriba
for(uint i=m_text_cursor_y_pos; i<lines_total-1; i++)
{
//--- En la primera iteración
if(i==m_text_cursor_y_pos)
{
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[i].m_symbol);
//--- Si en esta línea hay caracteres, los recordamos para añadir a la línea anterior
m_temp_input_string=(symbols_total>0)? CollectString(i) : "";
}
//--- Indice del siguiente elemento del array de las líneas
uint next_index=i+1;
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[next_index].m_symbol);
//--- Establecemos nuevo tamaño para los arrays
ArraysResize(i,symbols_total);
//--- Hacer una copia de la línea
LineCopy(i,next_index);
}
//--- Establecemos nuevo tamaño para el array de la líneas
uint new_size=lines_total-1;
::ArrayResize(m_lines,new_size);
//--- Bajamos el contador de las líneas
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);
//--- Movemos el cursor al final
m_text_cursor_x_pos=symbols_total;
//--- Obtenemos la coordenada X del cursor
CalculateTextCursorX();
//--- Si hay una línea que es necesario añadir a la anterior
if(m_temp_input_string!="")
{
//--- Trasladamos la línea al array
uchar array[];
int total=::StringToCharArray(m_temp_input_string,array)-1;
//--- Obtenemos el tamaño del array de caracteres
symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Establecemos nuevo tamaño para los arrays
new_size=symbols_total+total;
ArraysResize(m_text_cursor_y_pos,new_size);
//--- Añadir los datos en los arrays de la estructura
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]);
}
}
}
Cuando los métodos adicionales ya están listos, el código del método principal CTextBox::OnPressedKeyBackspace() ya no parece tan complicado. Aquí, primero comprobamos si la tecla 'Backspace' ha sido pulsada y el campo de edición ha sido activado. Si las comprobaciones han sido superadas, vemos en que posición se encuentra el cursor del texto. Si ahora no está al principio de la línea, eliminamos el carácter anterior. Si se encuentra al principio de la línea, y además no es la primera línea, hay que eliminar la línea actual y subir todas las líneas inferiores a una posición.
Después de eso, se calculan y se establecen nuevos tamaños para el campo de edición. Obtenemos los límites y las coordenadas del cursor del texto. Corregimos los deslizadores de las barras de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad. Finalmente, el control se redibuja con la visualización forzada del cursor del texto, y se envía el mensaje sobre el hecho del desplazamiento del cursor.
{
private:
//--- Procesamiento de la pulsación de la tecla "Backspace"
bool OnPressedKeyBackspace(const long key_code);
};
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la tecla "Backspace" |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyBackspace(const long key_code)
{
//--- Salir si no es la tecla "Backspace" o el campo de edición no está activado
if(key_code!=KEY_BACKSPACE || !m_text_edit_state)
return(false);
//--- Eliminar el carácter si la posición es más de cero
if(m_text_cursor_x_pos>0)
DeleteSymbol();
//--- Eliminar la línea si es la posición cero y no es la primera línea
else if(m_text_cursor_y_pos>0)
{
//--- Desplazamos las líneas a una posición hacia arriba
ShiftOnePositionUp();
}
//--- Calcular los tamaños del campo de edición
CalculateTextBoxSize();
//--- Establecer nuevo tamaño para el campo de edición
ChangeTextBoxSize(true,true);
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateBoundaries();
//--- Obtenemos las coordenadas X y Y del cursor
CalculateTextCursorX();
CalculateTextCursorY();
//--- Mover la barra del desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_x<=m_x_limit)
HorizontalScrolling(CalculateScrollThumbX());
else
{
if(m_text_cursor_x>=m_x2_limit)
HorizontalScrolling(CalculateScrollThumbX2());
}
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_y<=m_y_limit)
VerticalScrolling(CalculateScrollThumbY());
else
VerticalScrolling(m_scrollv.CurrentPos());
//--- Actualizar el texto en el 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);
}
Procesamiento de la pulsación de la tecla 'Enter'
Si tenemos activado el modo multilínea y pulsamos la tecla 'Enter', hay que añadir nueva línea, desplazando todas las líneas que se encuentran por debajo de la posición del cursor del texto a una posición hacia abajo. Para mover las líneas, necesitaremos el método auxiliar CTextBox::ShiftOnePositionDown(), así como el método adicional CTextBox::ClearLine() para vaciar las líneas.
{
private:
//--- Limpia la línea especificada
void ClearLine(const uint line_index);
};
//+------------------------------------------------------------------+
//| Vacía la línea especificada |
//+------------------------------------------------------------------+
void CTextBox::ClearLine(const uint line_index)
{
::ArrayFree(m_lines[line_index].m_width);
::ArrayFree(m_lines[line_index].m_symbol);
}
Ahora, describiremos el algoritmo del método CTextBox::ShiftOnePositionDown() con más detalles. En primer lugar, es necesario guardar el número de los caracteres de la línea en la que ha sido pulsada la tecla 'Enter'. De eso, así como de la posición del cursor del texto en la línea, depende el modo del procesamiento del algoritmo del método CTextBox::ShiftOnePositionDown(). Luego, movemos el cursor del texto en la línea nueva y aumentamos el tamaño del array de las líneas a un elemento. Después, hay que desplazar en el ciclo todas las líneas (empezando de la línea actual) a una posición hacia abajo empezando del fin del array. En la última iteración, si la línea en la que ha sido pulsada la tecla 'Enter' no contenía ningún carácter, hay que vaciar la línea en la que ahora se encuentra el cursor del texto. La línea vaciada es la copia de la línea cuyo contenido ya se encuentra en la siguiente línea, como resultado del desplazamiento a una posición hacia abajo.
Al principio del método, hemos retenido el número de los caracteres de la línea en la que ha sido pulsada la tecla 'Enter'. Si esta línea contenía caracteres, hay que averiguar la posición del cursor en aquel momento. Y si resulta que no estaba al final de la línea, es necesario calcular el número de los caracteres a trasladar a la línea nueva empezando de la posición actual del cursor hasta el final de la línea. Para eso aquí se utiliza el array temporal en el que van a copiarse los caracteres que luego serán trasladados a la línea nueva.
{
private:
//--- Desplaza las líneas a una posición hacia abajo
void ShiftOnePositionDown(void);
};
//+------------------------------------------------------------------+
//| Desplaza las líneas a una posición hacia abajo |
//+------------------------------------------------------------------+
void CTextBox::ShiftOnePositionDown(void)
{
//--- Obtenemos el tamaño del array de caracteres desde la línea en la que ha sido pulsada la tecla "Enter"
uint pressed_line_symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Aumentamos el contador de las líneas
m_text_cursor_y_pos++;
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Aumentamos el array a un elemento
uint new_size=lines_total+1;
::ArrayResize(m_lines,new_size);
//--- Desplazamos las líneas desde la posición actual a un punto hacia abajo (empezando del fin del array)
for(uint i=lines_total; i>m_text_cursor_y_pos; i--)
{
//--- Indice del elemento anterior del array de las líneas
uint prev_index=i-1;
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[prev_index].m_symbol);
//--- Establecemos nuevo tamaño para los arrays
ArraysResize(i,symbols_total);
//--- Hacer una copia de la línea
LineCopy(i,prev_index);
//--- Vaciar nueva línea
if(prev_index==m_text_cursor_y_pos && pressed_line_symbols_total<1)
ClearLine(prev_index);
}
//--- Si la pulsación de la tecla "Enter" no ha sido hecho en la línea vacía
if(pressed_line_symbols_total>0)
{
//--- Indice de la línea en la que ha sido pulsada la tecla "Enter"
uint prev_line_index=m_text_cursor_y_pos-1;
//--- Array para las copias de los caracteres desde la posición actual del cursor hasta el final de la línea
string array[];
//--- Establecemos el tamaño para el array como el número de caracteres a pasar a la línea nueva
uint new_line_size=pressed_line_symbols_total-m_text_cursor_x_pos;
::ArrayResize(array,new_line_size);
//--- Copiamos al array los caracteres que es necesario pasar a la línea nueva
for(uint i=0; i<new_line_size; i++)
array[i]=m_lines[prev_line_index].m_symbol[m_text_cursor_x_pos+i];
//--- Establecemos nuevo tamaño para los arrays de la estructura en la línea en la que ha sido pulsada la tecla "Enter"
ArraysResize(prev_line_index,pressed_line_symbols_total-new_line_size);
//--- Establecemos nuevo tamaño para los arrays de la estructura en la línea nueva
ArraysResize(m_text_cursor_y_pos,new_line_size);
//--- Añadir los datos a los arrays de la estructura de la línea nueva
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]);
}
}
}
Ahora tenemos todo preparado para el procesamiento de la pulsación en la tecla 'Enter'. Vamos a analizar el método CTextBox::OnPressedKeyEnter(). Primero comprobamos si la tecla 'Enter' ha sido pulsada y el campo de edición ha sido activado. Luego, si estas comprobaciones han sido superadas, entonces si se trata del campo de edición de una línea, simplemente finalizamos el trabajo con él. Para eso lo desactivamos, enviamos el mensaje con el identificador ON_END_EDIT y salimos del método.
Si tenemos activado el modo multilínea, desde la posición actual del cursor del texto se desplazan todas las líneas inferiores a una posición hacia abajo. Luego, se corrigen los tamaños del campo de edición y se comprueba la salida del cursor fuera del límite inferior del área de visibilidad. Aparte de eso, el cursor del texto se coloca al principio de la línea. Al final del método, el campo de edición se redibuja y se envía el mensaje sobre el desplazamiento del cursor.
{
private:
//--- Procesamiento de la pulsación de la tecla "Enter"
bool OnPressedKeyEnter(const long key_code);
};
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la tecla "Enter" |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyEnter(const long key_code)
{
//--- Salir si no es la tecla "Enter" o el campo de edición no está activado
if(key_code!=KEY_ENTER || !m_text_edit_state)
return(false);
//--- Si el modo multilínea está desactivado
if(!m_multi_line_mode)
{
//--- Desactivar el campo de edición
DeactivateTextBox();
//--- Enviamos el mensaje sobre ello
::EventChartCustom(m_chart_id,ON_END_EDIT,CElementBase::Id(),CElementBase::Index(),TextCursorInfo());
return(false);
}
//--- Desplazamos las líneas a una posición hacia abajo
ShiftOnePositionDown();
//--- Calcular los tamaños del campo de edición
CalculateTextBoxSize();
//--- Establecer nuevo tamaño para el campo de edición
ChangeTextBoxSize();
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateYBoundaries();
//--- Obtenemos la coordenada Y del cursor
CalculateTextCursorY();
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
VerticalScrolling(CalculateScrollThumbY2());
//--- Mover el cursor al principio de la línea
SetTextCursor(0,m_text_cursor_y_pos);
//--- Mover la barra de desplazamiento al principio
HorizontalScrolling(0);
//--- 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);
}
Procesamiento de la pulsación de las teclas 'Left' y 'Right'
Al pulsar la tecla «Izquierda» o «Derecha», desplazamos el cursor del texto a la dirección correspondiente a un carácter. Para implementar eso, primero necesitaremos el método adicional CTextBox::CorrectingTextCursorXPos() que va a corregir la posición del cursor del texto. Este método va a utilizarse en otros métodos de la clase.
{
private:
//--- Corrección del cursor del texto en el eje X
void CorrectingTextCursorXPos(const int x_pos=WRONG_VALUE);
};
//+------------------------------------------------------------------+
//| Corrección del cursor del texto en el eje X |
//+------------------------------------------------------------------+
void CTextBox::CorrectingTextCursorXPos(const int x_pos=WRONG_VALUE)
{
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_width);
//--- Determinamos la posición del cursor
uint text_cursor_x_pos=0;
//--- Si la posición está especificada
if(x_pos!=WRONG_VALUE)
text_cursor_x_pos=(x_pos>(int)symbols_total-1)? symbols_total : x_pos;
//--- Si la posición no está especificada, colocamos el cursor al final de la línea
else
text_cursor_x_pos=symbols_total;
//--- Posición cero si la línea no contiene caracteres
m_text_cursor_x_pos=(symbols_total<1)? 0 : text_cursor_x_pos;
//--- Obtenemos la coordenada X del cursor
CalculateTextCursorX();
}
Abajo se muestra el código del método CTextBox::OnPressedKeyLeft() para el procesamiento de la pulsación de la tecla «Izquierda». El programa saldrá del método si ha sido pulsada cualquier otra tecla o el campo de edición no está activado, o si se mantiene pulsada la tecla «Ctrl» en este momento. Sobre el procesamiento de la pulsación simultánea con la tecla «Ctrl» hablaremos en otro apartado del artículo.
Si las primeras comprobaciones han sido superadas, nos fijamos en la posición del cursor del texto. Si no está al principio de la línea, lo movemos al carácter anterior. Se se encuentra al principio de la línea, y esta línea no es la primera, hay que mover el cursor al final de la línea anterior. Después de eso, corregimos los deslizadores de las barras de desplazamiento vertical y horizontal, redibujamos el campo de edición y enviamos el mensaje sobre el desplazamiento del cursor del texto.
{
private:
//--- Procesamiento de la pulsación de la tecla "Left"
bool OnPressedKeyLeft(const long key_code);
};
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la tecla "Left" |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyLeft(const long key_code)
{
//--- Salir si no es la tecla "Left" o ha sido pulsada la tecla "Ctrl" o el campo de edición no está activado
if(key_code!=KEY_LEFT || m_keys.KeyCtrlState() || !m_text_edit_state)
return(false);
//--- Si la posición del cursor del texto es superior a cero
if(m_text_cursor_x_pos>0)
{
//--- Lo movemos al carácter anterior
m_text_cursor_x-=m_lines[m_text_cursor_y_pos].m_width[m_text_cursor_x_pos-1];
//--- Bajamos el contador de los caracteres
m_text_cursor_x_pos--;
}
else
{
//--- Si no es la primera línea
if(m_text_cursor_y_pos>0)
{
//--- Vamos al final de la línea anterior
m_text_cursor_y_pos--;
CorrectingTextCursorXPos();
}
}
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateBoundaries();
//--- Obtenemos la coordenada Y del cursor
CalculateTextCursorY();
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_x<=m_x_limit)
HorizontalScrolling(CalculateScrollThumbX());
else
{
//--- Obtenemos el tamaño del array 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());
}
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_y<=m_y_limit)
VerticalScrolling(CalculateScrollThumbY());
//--- 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);
}
Ahora, vamos a considerar el código del método CTextBox::OnPressedKeyRight() para procesar la pulsación de la tecla «Derecha». Aquí al principio también es necesario superar las comprobaciones de la tecla pulsada, estado del campo de edición y la tecla «Ctrl». Luego comprobamos ¿se encuentra el cursor al final de la línea?. Si no es así, movemos el cursor a un carácter a la derecha. Si el cursor se encuentra al final de la línea, comprobamos si es la última línea Si no es así, movemos el cursor al comienzo de la siguiente línea.
Luego, (1) corregimos los deslizadores de las barras de desplazamiento en caso si el cursor sale fuera de los límites del área de visibilidad del campo de edición, (2) redibujamos el control y (3) enviamos el mensaje sobre el desplazamiento del cursor del texto.
{
private:
//--- Procesamiento de la pulsación de la tecla "Right"
bool OnPressedKeyRight(const long key_code);
};
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la tecla "Right" |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyRight(const long key_code)
{
//--- Salir si no es la tecla «Right" o ha sido pulsada la tecla "Ctrl" o el campo de edición no está activado
if(key_code!=KEY_RIGHT || m_keys.KeyCtrlState() || !m_text_edit_state)
return(false);
//--- Obtenemos el tamaño del array de caracteres
uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_width);
//--- Si no es el fin de la línea
if(m_text_cursor_x_pos<symbols_total)
{
//--- Desplazamos la posición del cursor del texto en el siguiente carácter
m_text_cursor_x+=m_lines[m_text_cursor_y_pos].m_width[m_text_cursor_x_pos];
//--- Establecemos el contador de caracteres
m_text_cursor_x_pos++;
}
else
{
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Si no es la última línea
if(m_text_cursor_y_pos<lines_total-1)
{
//--- Mover el cursor al principio de la siguiente línea
m_text_cursor_x=m_text_x_offset;
SetTextCursor(0,++m_text_cursor_y_pos);
}
}
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateBoundaries();
//--- Obtenemos la coordenada Y del cursor
CalculateTextCursorY();
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_x>=m_x2_limit)
HorizontalScrolling(CalculateScrollThumbX2());
else
{
if(m_text_cursor_x_pos==0)
HorizontalScrolling(0);
}
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_y+(int)LineHeight()>=m_y2_limit)
VerticalScrolling(CalculateScrollThumbY2());
//--- 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);
}
Procesamiento de la pulsación de las teclas 'Up' y 'Down'
La pulsación de las teclas «Arriba» y «Abajo» desplaza el cursor del texto por las líneas. Para el procesamiento de las pulsaciones de estas teclas se utilizan los métodos CTextBox::OnPressedKeyUp() y CTextBox::OnPressedKeyDown(). Aquí vamos a mostrar el código de uno de estos métodos, porque se diferencian sólo en dos cadenas del código.
Al principio de los códigos de ambos métodos, hay que superar sólo tres pruebas. El programa sale del método si (1) es el campo de edición de una línea, (2) ha sido pulsada otra tecla o (3) si el campo de edición no esta activado. Si en este momento el cursor no se encuentra en la primera línea, lo movemos a la línea anterior (en el método CTextBox::OnPressedKeyDown() a la siguiente) corrigiendo el número de caracteres en caso de salir fuera de los límites de la línea.
Después de eso, comprobamos la salida del cursor fuera de los límites del área visible del campo de edición, y si hace falta, corregimos los deslizadores de las barras de desplazamiento. Aquí, la única diferencia entre estos dos métodos consiste en sólo lo siguiente: en el método CTextBox::OnPressedKeyUp() se comprueba la salida fuera del límite superior, y en el método CTextBox::OnPressedKeyDown(), la salida fuera del límite inferior. Al final del método, redibujamos el campo de edición y enviamos el mensaje sobre el desplazamiento del cursor del texto.
{
private:
//--- Procesamiento de la pulsación de la tecla "Up"
bool OnPressedKeyUp(const long key_code);
//--- Procesamiento de la pulsación de la tecla "Down"
bool OnPressedKeyDown(const long key_code);
};
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la tecla "Up" |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyUp(const long key_code)
{
//--- Salir si el modo multilínea está desactivado
if(!m_multi_line_mode)
return(false);
//--- Salir si no es la tecla "Up" o el campo de edición no está activado
if(key_code!=KEY_UP || !m_text_edit_state)
return(false);
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Si no salimos de los límites del array
if(m_text_cursor_y_pos-1<lines_total)
{
//--- Paso a la línea siguiente
m_text_cursor_y_pos--;
//--- Corrección del cursor del texto en el eje X
CorrectingTextCursorXPos(m_text_cursor_x_pos);
}
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateBoundaries();
//--- Obtenemos la coordenada Y del cursor
CalculateTextCursorY();
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_x<=m_x_limit)
HorizontalScrolling(CalculateScrollThumbX());
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_y<=m_y_limit)
VerticalScrolling(CalculateScrollThumbY());
//--- 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);
}
Procesamiento de la pulsación de las teclas 'Home' y 'End'
La pulsación de las teclas 'Home' y 'End' desplaza el cursor del texto al comienzo y al final de la línea, respectivamente. Para el procesamiento de estos eventos se utilizan los métodos CTextBox::OnPressedKeyHome() y CTextBox::OnPressedKeyEnd(). Su código se muestra a continuación, y no requiere ningunas explicaciones adicionales debido a los comentarios detallados que incluye.
{
private:
//--- Procesamiento de la pulsación de la tecla "Home"
bool OnPressedKeyHome(const long key_code);
//--- Procesamiento de la pulsación de la tecla "End"
bool OnPressedKeyEnd(const long key_code);
};
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la tecla "Home" |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyHome(const long key_code)
{
//--- Salir si no es la tecla "Home", la tecla "Ctrl" ha sido pulsada o el campo de edición no está activado
if(key_code!=KEY_HOME || m_keys.KeyCtrlState() || !m_text_edit_state)
return(false);
//--- Mover el cursor al principio de la línea actual
SetTextCursor(0,m_text_cursor_y_pos);
//--- Mover la barra de desplazamiento a la primera posición
HorizontalScrolling(0);
//--- 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);
}
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la tecla "End" |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyEnd(const long key_code)
{
//--- Salir si (1) no es la tecla "End", (2) la tecla "Ctrl" ha sido pulsada o (3) el campo de edición no está activado
if(key_code!=KEY_END || m_keys.KeyCtrlState() || !m_text_edit_state)
return(false);
//--- Obtenemos el número de caracteres en la línea actual
uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Movemos el cursor al final de la línea actual
SetTextCursor(symbols_total,m_text_cursor_y_pos);
//--- Obtenemos la coordenada X del cursor
CalculateTextCursorX();
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateXBoundaries();
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_x>=m_x2_limit)
HorizontalScrolling(CalculateScrollThumbX2());
//--- 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);
}
Procesamiento de la pulsación simultánea de las teclas en combinación con la tecla 'Ctrl'
Ahora, hablaremos de los métodos que procesan las pulsaciones de las siguientes combinaciones simultáneas de las teclas:
- 'Ctrl' + 'Left' – mover el cursor de una palabra a la otra a la izquierda.
- 'Ctrl' + 'Right' – mover el cursor de una palabra a la otra a la derecha.
- 'Ctrl' + 'Home' – mover el cursor al principio de la primera línea.
- 'Ctrl' + 'End' – mover el cursor al final de la última línea.
Como ejemplo, vamos a analizar sólo uno de estos métodos— CTextBox::OnPressedKeyCtrlAndLeft(), para el desplazamiento del cursor de una palabra a la otra a la izquierda. Al principio del método, se comprueban las pulsaciones simultáneas de las teclas 'Ctrl' y 'Left'. Si alguna de estas teclas no ha sido pulsada, el programa saldrá del método. Además de eso, el campo de edición debe estar activado.
Si la posición actual del cursor del texto es el principio de la línea, hay que moverlo al final de la línea anterior. Si el cursor no se encuentra al principio de la línea actual, es necesario encontrar el comienzo de la secuencia interrumpida de caracteres. Aquí la interrupción es el carácter de espacio ' '. Nos desplazamos en el ciclo por la línea actual de derecha a izquierda. En cuanto encontremos la combinación cuando el siguiente carácter es el espacio y el actual es cualquier otro carácter, entonces, a no ser éste el punto de inicio, colocamos el cursor en esta posición.
Luego, igual como en los demás métodos, comprobamos la salida del cursor fuera de los límites del área visible del campo de edición, y si hace falta, corregimos los deslizadores de las barras de desplazamiento. Al final del método, el campo de edición se redibuja y se envía el mensaje sobre el desplazamiento del cursor.
{
private:
//--- Procesamiento de la pulsación de la combinación Ctrl + Left
bool OnPressedKeyCtrlAndLeft(const long key_code);
//--- Procesamiento de la pulsación de la combinación Ctrl + Right
bool OnPressedKeyCtrlAndRight(const long key_code);
//--- Procesamiento de la pulsación de la combinación Ctrl + Home
bool OnPressedKeyCtrlAndHome(const long key_code);
//--- Procesamiento de la pulsación de la combinación Ctrl + End
bool OnPressedKeyCtrlAndEnd(const long key_code);
};
//+------------------------------------------------------------------+
//| Procesamiento de la pulsación de la combinación Ctrl + Left |
//+------------------------------------------------------------------+
bool CTextBox::OnPressedKeyCtrlAndLeft(const long key_code)
{
//--- Salir si (1) no es la tecla "Left" y (2) la tecla "Ctrl" no se mantiene pulsada, o (3) el campo de edición no está activado
if(!(key_code==KEY_LEFT && m_keys.KeyCtrlState()) || !m_text_edit_state)
return(false);
//--- Signo del espacio
string SPACE=" ";
//--- Obtenemos el tamaño del array de líneas
uint lines_total=::ArraySize(m_lines);
//--- Obtenemos el número de caracteres en la línea actual
uint symbols_total=::ArraySize(m_lines[m_text_cursor_y_pos].m_symbol);
//--- Si el cursor se encuentra en el comienzo de la línea actual y no es la primera línea,
// desplazamos el cursor al final de la línea anterior
if(m_text_cursor_x_pos==0 && m_text_cursor_y_pos>0)
{
//--- Obtenemos el índice de la línea anterior
uint prev_line_index=m_text_cursor_y_pos-1;
//--- Obtenemos el número de caracteres en la línea anterior
symbols_total=::ArraySize(m_lines[prev_line_index].m_symbol);
//--- Desplazamos el cursor al final de la línea anterior
SetTextCursor(symbols_total,prev_line_index);
}
//--- Si el cursor no se encuentra al principio de la línea actual o se encuentra en la primera línea
else
{
//--- Buscamos el comienzo de la secuencia interrumpida de caracteres (de derecha a izquierda)
for(uint i=m_text_cursor_x_pos; i<=symbols_total; i--)
{
//--- Ir al siguiente si el cursor se encuentra al final de la línea
if(i==symbols_total)
continue;
//--- Si es el primer carácter de la línea
if(i==0)
{
//--- Colocamos el cursor al principio de la línea
SetTextCursor(0,m_text_cursor_y_pos);
break;
}
//--- Si no es el primer carácter de la línea
else
{
//--- Si hemos encontrado el comienzo de la secuencia interrumpida en la que nos encontramos por primera vez.
// El comienzo es el espacio en el siguiente í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)
{
//--- Colocamos el cursor al principio de la nueva secuencia interrumpida
SetTextCursor(i,m_text_cursor_y_pos);
break;
}
}
}
}
//--- Obtenemos los límites de la parte visible del campo de edición
CalculateBoundaries();
//--- Obtenemos la coordenada X del cursor
CalculateTextCursorX();
//--- Obtenemos la coordenada Y del cursor
CalculateTextCursorY();
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_x<=m_x_limit)
HorizontalScrolling(CalculateScrollThumbX());
else
{
//--- Obtenemos el tamaño del array 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());
}
//--- Mover la barra de desplazamiento si el cursor del texto ha salido fuera del área de visibilidad
if(m_text_cursor_y<=m_y_limit)
VerticalScrolling(CalculateScrollThumbY());
//--- 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);
}
Propongo estudiar personalmente todos los demás métodos de la lista al principio de este apartado.
Integración del control en el motor de la librería
Para un trabajo correcto del control «Campo de edición multilínea», vamos a necesitar el array personal en la estructura WindowElements de la clase CWndContainer. Incluimos el archivo con la clase CTextBox en el archivo WndContainer.mqh:
//| WndContainer.mqh |
//| Copyright 2015, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Controls\TextBox.mqh"
Añadimos el array personal para nuevo control a la estructura WindowElements:
//| Clase para almacenar todos los objetos de la interfaz |
//+------------------------------------------------------------------+
class CWndContainer
{
protected:
//--- Estructura de los arrays de controles
struct WindowElements
{
//--- Campos de edición multilínea
CTextBox *m_text_boxes[];
};
//--- Array de los arrays de los controles para cada ventana
WindowElements m_wnd[];
};
Puesto que el control tipo CTextBox es compuesto e incluye los controles de otro tipo (en este caso son las barras de desplazamiento), necesitamos el método en el que los apuntadores a estos controles van a distribuirse por sus arrays personales. Abajo se muestra el código del método CWndContainer::AddTextBoxElements() que sirve para estos propósitos. La llamada a este método se realiza en el mismo lugar donde se invocan los métodos semejantes, es decir en CWndContainer::AddToElementsArray().
{
private:
//--- Guarda los punteros a los objetos del campo de edición multilínea
bool AddTextBoxElements(const int window_index,CElementBase &object);
};
//+------------------------------------------------------------------+
//| Guarda los punteros a los objetos del campo de edición multilínea |
//+------------------------------------------------------------------+
bool CWndContainer::AddTextBoxElements(const int window_index,CElementBase &object)
{
//--- Salimos si no es el campo de edición multilínea
if(dynamic_cast<CTextBox *>(&object)==NULL)
return(false);
//--- Obtenemos el puntero al control
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)
{
//--- Obtenemos el puntero de la barra de desplazamiento
CScrollV *sv=tb.GetScrollVPointer();
m_wnd[window_index].m_elements[size]=sv;
AddToObjectsArray(window_index,sv);
//--- Añadimos el puntero al array personal
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);
//--- Añadimos el puntero al array personal
AddToRefArray(sh,m_wnd[window_index].m_scrolls);
}
}
//--- Añadimos el puntero al array privado
AddToRefArray(tb,m_wnd[window_index].m_text_boxes);
return(true);
}
Ahora, hay que introducir algunas adiciones al método CWndEvents::OnTimerEvent(). Recordamos que el redibujo de la interfaz gráfica se realiza sólo cuando el cursor del ratón se encuentra en movimiento, y se detiene dentro de un intervalo especificado tras la detención del movimiento del cursor. Para el control tipo CTextBox se hace una excepción. De lo contrario, el cursor no va a parpadear cuando el campo de edición está activado.
//| Temporizador |
//+------------------------------------------------------------------+
void CWndEvents::OnTimerEvent(void)
{
//--- Salir si el cursor está en estado de reposo (la diferencia entre las llamadas es >300 ms) y el botón izquierdo está suelto
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;
}
//--- Si el array está vacío, salimos
if(CWndContainer::WindowsTotal()<1)
return;
//--- Comprobación de los eventos de todos los controles por el temporizador
CheckElementsEventsTimer();
//--- Redibujamos el gráfico
m_chart.Redraw();
}
A continuación, vamos a crear la aplicación MQL que nos permitirá probar el control «Campo de edición multilínea».
Aplicación para la prueba del control
Para nuestra prueba vamos a crear una aplicación MQL con la interfaz gráfica que tendrá dos campos de edición del texto. Uno de ellos tendrá una línea y el otro, varias líneas. Aparte de estos campos de edición, en la interfaz gráfica de la aplicación habrá el menú principal con el menú contextual y la barra de estado, como ejemplo. En el segundo elemento de la barra de estado va a mostrarse la posición del cursor del texto del campo de edición multilínea.
Creamos dos instancias de la clase CTextBox y declaramos dos métodos para la creación del campo de edición:
{
protected:
//--- Campo de edición
CTextBox m_text_box1;
CTextBox m_text_box2;
//---
protected:
//--- Campo de edición
bool CreateTextBox1(const int x_gap,const int y_gap);
bool CreateTextBox2(const int x_gap,const int y_gap);
};
Abajo se muestra el código del segundo método para la creación del campo de edición multilínea. Para activar el modo multilínea, usaremos eel método CTextBox::MultiLineMode(). Para que el campo se ajuste automáticamente a los tamaños del formulario, es necesario indicar eso a través de los métodos CElementBase::AutoXResizeXXX(). Como ejemplo, añadimos el contenido de este artículo al campo de edición multilínea. Para eso preparamos el array de las líneas con la posibilidad de añadirlas luego en el ciclo a través de los métodos especiales de la clase CTextBox.
//| Crea el campo de edición multilínea |
//+------------------------------------------------------------------+
bool CProgram::CreateTextBox2(const int x_gap,const int y_gap)
{
//--- Guardamos el puntero a la ventana
m_text_box2.WindowPointer(m_window);
//--- Establecemos las propiedades antes de la creación
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 líneas
string lines_array[]=
{
«Introducción»,
«Grupos de teclas y distribuciones del teclado»
«Procesamiento del evento de pulsación de teclas»
«Códigos ASCII de caracteres y teclas de control»,
«Códigos Scan de las teclas»,
«Clase auxiliar para trabajar con el teclado»,
«Control «Campo de edición del texto multilínea»,
«Desarrollo de la clase CTextBox para la creación del control»,
«Propiedades y apariencia»,
«Control del cursor del texto»,
«Introducción del carácter»,
«Procesamiento de la pulsación de la tecla 'Backspace'»,
«Procesamiento de la pulsación de la tecla 'Enter'»
"Procesamiento de las teclas «Left» y «Right»",
«Procesamiento de la pulsación de las teclas 'Up' y 'Down'»,
Procesamiento de la pulsación de las teclas «Home» y «End»,
«Procesamiento de la pulsación simultánea de las teclas en combinación con la tecla 'Ctrl'»,
«Integración del control en el motor de la librería»,
«Aplicación para la prueba del control»,
«Conclusión»
};
//--- Insertamos el texto en el campo de edición
int lines_total=::ArraySize(lines_array);
for(int i=0; i<lines_total; i++)
{
}; //--- Insertamos el texto en la primera línea
if(i==0)
m_text_box2.AddText(0,lines_array[i]);
//--- Insertamos la línea en el campo de edición
else
m_text_box2.AddLine(lines_array[i]);
}
//--- Creamos el control
if(!m_text_box2.CreateTextBox(m_chart_id,m_subwin,x_gap,y_gap))
return(false);
//--- Añadimos el objeto al array común de los grupos de objetos
CWndContainer::AddToElementsArray(0,m_text_box2);
//--- Colocamos el texto en el elemento de la barra de estado
m_status_bar.ValueToItem(1,m_text_box2.TextCursorInfo());
return(true);
}
Añadimos el código al manejador de eventos de la aplicación MQL para recibir los mensajes de los campos de edición:
//| Manejador de eventos del gráfico |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- Evento de (1) la introducción del valor o (2) de la activación del campo de edición o (3) del desplazamiento del cursor del 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);
//--- Si los identificadores coinciden (evento del campo de edición multilínea)
if(lparam==m_text_box2.Id())
{
//--- Actualización del segundo elemento de la barra de estado
m_status_bar.ValueToItem(1,sparam);
}
//--- redibujar el gráfico
m_chart.Redraw();
return;
}
}
Después de compilar la aplicación y cargarla en el gráfico, Usted verá lo siguiente:
Fig. 9. Interfaz gráfica con demostración del control «Campo de edición del texto»
Puede descargar esta aplicación de prueba al final del artículo para estudiarla más detalladamente.
Conclusión
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. 10 Estructura de la librería en la fase actual del desarrollo.
En la siguiente versión de la librería, vamos a desarrollar los controles ya existentes y completarlos con nuevas funcionalidades. Abajo puede descargar la última versión de la librería y los archivos para las pruebas.
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/3004
- 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