English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas X: Control "Campo de edición del texto multilínea" (build 8)

Interfaces gráficas X: Control "Campo de edición del texto multilínea" (build 8)

MetaTrader 5Ejemplos | 20 febrero 2017, 09:49
1 327 0
Anatoli Kazharski
Anatoli Kazharski

Índice

 

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)

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.240 id: 0; lparam: 27; dparam: 1.0; sparam: 1
2017.01.20 17:53:33.739 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.772 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.805 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.837 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
2017.01.20 17:53:33.870 id: 0; lparam: 27; dparam: 1.0; sparam: 16385
...

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. 

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 caracteres y teclas de control.

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. 

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. 

class CKeys
  {
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

class CKeys
  {
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.

class CTextBox : public CElement
  {
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:

class CTextBox : public CElement
  {
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»
class CTextBox : public CElement
  {
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: 

class CTextBox : public CElement
  {
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():

class CTextBox : public CElement
  {
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

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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()
class CTextBox : public CElement
  {
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

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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. 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.

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

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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(). 

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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). 
class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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

class CTextBox : public CElement
  {
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:

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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():

class CTextBox : public CElement
  {
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

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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()

class CTextBox : public CElement
  {
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

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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. 

class CTextBox : public CElement
  {
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.

class CTextBox : public CElement
  {
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(). 

class CWndContainer
  {
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:

class CProgram : public CWndEvents
  {
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»

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.

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

Archivos adjuntos |
Canal universal con interfaz gráfica Canal universal con interfaz gráfica
Todos los indicadores de canales están constituidos por tres líneas: una central, una superior y otra inferior. Según su principio de construcción la línea central es idéntica a una media móvil, y en la mayoría de los casos para construir el canal se usa precisamente una media móvil. Las líneas superior e inferior se ubican a la misma distancia de la central. Esta distancia se puede definir simplemente en puntos, en tanto por ciento del precio (indicador Envelopes), se pueden usar valores de desviación estándar (franjas de Bollinger) o los valores del indicador ATR (canal de Keltner).
Incorpore el terminal web MetaTrader 4/5 en sus páginas web, es gratuito, y además podrá ganar dinero con ello Incorpore el terminal web MetaTrader 4/5 en sus páginas web, es gratuito, y además podrá ganar dinero con ello
Los tráders ya conocen bien el terminal web, que permite comerciar en los mercados financieros directamente desde el navegador. Le proponemos que lo incorpore en su página web, es algo totalmente gratuito. Usted tiene visitas a su página, los brókeres se interesan por clientes potenciales, y nosotros proporcionamos una solución web ya lista. Para que todo ello funcione, solo es necesario que incluya un iframe en su página web.
Interfaces gráficas X: Nuevas posibilidades para la tabla dibujada (build 9) Interfaces gráficas X: Nuevas posibilidades para la tabla dibujada (build 9)
Hasta este momento, el tipo más desarrollado de las tablas de la librería fue el tipo CTable. Esta tabla se reúne de los campos de edición tipo OBJ_EDIT y su desarrollo posterior ya resulta problemático. Por eso, desde el punto de vista de las posibilidades máximas incluso en esta fase del desarrollo de la librería, es mejor desarrollar la tabla dibujada tipo CCanvasTable. Su versión actual es absolutamente inerte, pero a partir de este artículo vamos a tratar de solucionar ese asunto.
Interfaces gráficas X: Gestión ampliada de las listas y tablas. Optimización de código (build 7) Interfaces gráficas X: Gestión ampliada de las listas y tablas. Optimización de código (build 7)
Es necesario optimizar el código de la librería: debe estar mejor ordenado, o sea, ser más comprensible y legible. Además de eso, vamos a continuar el desarrollo de los controles creados anteriormente: listas, tablas y barras de desplazamiento.