Interfaces gráficas VII: Control "Tablas" (Capítulo 1)

Anatoli Kazharski | 19 julio, 2016

Í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 considerar tres clases que permiten crear diferentes tipos de tablas para visualizar los arrays bidimensionales de datos, como parte de la interfaz gráfica de las aplicaciones MQL:

  • tabla a base de las etiquetas de texto;
  • tabla a base de los campos de edición;
  • tabla dibujada.

Cada una de estas tablas posee sus propias capacidades y ventajas únicas ante otros tipos. Más detalles a continuación.

En el siguiente artículo (capítulo 2 de la parte VII) vamos a considerar los siguientes controles:

  • pestañas;
  • pestañas con imágenes.

 


Control “Tabla a base de etiquetas de texto”

Una tabla es un control compuesto complejo de la interfaz gráfica porque incluye otros controles: las barra de desplazamiento vertical y horizontal. Puesto que puede haber mucho más datos que caben en el área determinada del control, aquí las barras de desplazamiento permiten desplazar los arrays de datos visualizados tanto por la vertical, como por la horizontal.

Vamos a nombrar todas las partes integrantes de este tipo de la tabla.

  1. Fondo.
  2. Etiquetas de texto.
  3. Barra de desplazamiento vertical.
  4. Barra de desplazamiento horizontal.

 

Fig. 1. Partes integrantes del control “Tabla de las etiquetas de texto”

A continuación, vamos a ver cómo está organizada la clase de este control.

 


Desarrollo de la clase CLabelsTable

Creamos el archivo LabelsTable.mqh y lo incluimos en la librería (archivo WndContainer.mqh):

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "LabelsTable.mqh"

En el archivo LabelsTable.mqh, hay que crear la clase CLabelsTable con los métodos estándar para todos los controles de la librería, así como con el método para guardar el puntero al formulario al que será adjuntado este control. Habrá que hacer lo mismo para todos los controles de este artículo.

//+------------------------------------------------------------------+
//| Clase par crear la tabla de las etiquetas de texto                    |
//+------------------------------------------------------------------+
class CLabelsTable : public CElement
  {
private:
   //--- Puntero al formulario al que está adjuntado el control
   CWindow          *m_wnd;
   //---
public:
                     CLabelsTable(void);
                    ~CLabelsTable(void);
    //--- (1) Guarda el puntero del formulario, (2) devuelve los punteros a las barras de desplazamiento
   void              WindowPointer(CWindow &object)               { m_wnd=::GetPointer(object);      }
   //---
public:
   //--- 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);
   //--- (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);
   //--- Resetear color
   virtual void      ResetColors(void) {}
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLabelsTable::CLabelsTable(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLabelsTable::~CLabelsTable(void)
  {
  }

Antes de crear la tabla, hay que disponer de la posibilidad de especificar algunas de sus propiedades. Son las siguientes:

  • Alto de la fila
  • Margen de la primera columna desde el borde izquierdo del control
  • Distancia entre las columnas
  • Color del fondo
  • Color del texto
  • Modo de fijación de la primera fila
  • Modo de fijación de la primera columna
  • Número total de columnas
  • Número total de filas
  • Número de columnas en la parte visible de la tabla
  • Número de filas en la parte visible de la tabla

En este tipo de la tabla, las coordenadas de las etiquetas de texto van a calcularse desde el punto de anclaje central (ANCHOR_CENTER). Por eso el margen desde el borde izquierdo del control para las etiquetas de texto de la primera columna y la distancia entre las etiquetas de texto de cada columna van a calcularse desde el centro de cada una de ellas.

En cuanto a los modos de fijación, puede surgir la necesidad que la primera fila y/o la primera columna de la tabla (donde el usuario puede poner el nombre para cada columna y/o fila de datos) siempre estén visibles, incluso cuando los deslizadores de la barra de desplazamiento vertical y/u horizontal han sido desplazados de la primera posición. Usted puede usar los modos de fijación de encabezados junto, o por separado. 

class CLabelsTable : public CElement
  {
private:
   //--- Alto de la fila
   int               m_row_y_size;
   //--- Color del fondo de la tabla
   color             m_area_color;
   //--- Color del texto en la tabla por defecto
   color             m_text_color;
   //--- Distancia entre el punto de anclaje de la primera columna y el borde izquierdo del control
   int               m_x_offset;
   //--- Distancia entre los puntos de anclaje de las columnas
   int               m_column_x_offset;
   //--- Modo de fijación de la primera fila
   bool              m_fix_first_row;
   //--- Modo de fijación de la primera columna
   bool              m_fix_first_column; 
   //--- Prioridades para el clic izquierdo del ratón
   int               m_zorder;
   int               m_area_zorder;
   //---
public:
   //--- (1) Color del fondo, (2) color del texto
   void              AreaColor(const color clr)                   { m_area_color=clr;                }
   void              TextColor(const color clr)                   { m_text_color=clr;                }
   //--- (1) Alto de la fila, (2) establece la distancia entre el punto de anclaje de la primera columna y el borde izquierdo de la tabla,
       //--- (3) establece la distancia entre los puntos de anclaje de las columnas
   void              RowYSize(const int y_size)                   { m_row_y_size=y_size;             }
   void              XOffset(const int x_offset)                  { m_x_offset=x_offset;             }
   void              ColumnXOffset(const int x_offset)            { m_column_x_offset=x_offset;      }
   //--- (1) Devuelve y (2) establece el modo de fijación de la primera fila
   bool              FixFirstRow(void)                      const { return(m_fix_first_row);         }
   void              FixFirstRow(const bool flag)                 { m_fix_first_row=flag;            }
   //--- (1) Devuelve y (2) establece el modo de fijación de la primera columna
   bool              FixFirstColumn(void)                   const { return(m_fix_first_column);      }
   void              FixFirstColumn(const bool flag)              { m_fix_first_column=flag;         }
  };

Para establecer el número total y el número visible de las columnas y las filas de la tabla, escribiremos los métodos CLabelsTable::TableSize() y CLabelsTable::VisibleTableSize(). Además de eso, necesitaremos los arrays dinámicos bidimensionales en forma de las estructuras. Una estructura (LTLabels) estará destinada para crear el array de las etiquetas de texto para la parte visible de la tabla. La segunda estructura (LTOptions) es necesaria para almacenar todos los valores y propiedades para cada celda de la tabla. En esta implementación van a mostrarse los valores (filas) y el color del texto.

En los métodos CLabelsTable::TableSize() y CLabelsTable::VisibleTableSize() hay que pasar dos argumentos: el número de columnas y filas. En el inicio de estos métodos se realiza la corrección de valores pasados si ha sido pasado menos de una columna y menos de dos filas. Luego se establecen los tamaños para todos los arrays y se inicializan los arrays de propiedades.

Además de los métodos para establecer los tamaños de la tabla, vamos a crear los métodos para obtener sus tamaños totales y visibles para las columnas y filas. 

class CLabelsTable : public CElement
  {
private:
   //--- Array de objetos de la parte visible de la tabla
   struct LTLabels
     {
      CLabel            m_rows[];
     };
   LTLabels          m_columns[];
   //--- Arrays de los valores y de las propiedades de la tabla
   struct LTOptions
     {
      string            m_vrows[];
      color             m_colors[];
     };
   LTOptions         m_vcolumns[];
   //---
public:
   //--- Devuelve el número total de (1) filas y (2) columnas
   int               RowsTotal(void)                        const { return(m_rows_total);            }
   int               ColumnsTotal(void)                     const { return(m_columns_total);         }
   //--- Devuelve el número de (1) filas y (2) columnas en la parte visible de la tabla
   int               VisibleRowsTotal(void)                 const { return(m_visible_rows_total);    }
   int               VisibleColumnsTotal(void)              const { return(m_visible_columns_total); }

   //--- Establece el (1) tamaño de la tabla y el (2) tamaño de su parte visible
   void              TableSize(const int columns_total,const int rows_total);
   void              VisibleTableSize(const int visible_columns_total,const int visible_rows_total);
  };
//+------------------------------------------------------------------+
//| Establece el tamaño de la tabla                                    |
//+------------------------------------------------------------------+
void CLabelsTable::TableSize(const int columns_total,const int rows_total)
  {
//--- Tiene que haber no menos de una columna
   m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- Tiene que haber no menos de dos filas
   m_rows_total=(rows_total<2) ? 2 : rows_total;
//--- Establecer el tamaño de para el array de columnas
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Establecer el tamaño de para el array de filas
   for(int i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_colors,m_rows_total);
      //--- Inicialización del array del color del texto con los valores predefinidos
      ::ArrayInitialize(m_vcolumns[i].m_colors,m_text_color);
     }
  }
//+------------------------------------------------------------------+
//| Establece el tamaño de la parte visible de la tabla                       |
//+------------------------------------------------------------------+
void CLabelsTable::VisibleTableSize(const int visible_columns_total,const int visible_rows_total)
  {
//--- Tiene que haber no menos de una columna
   m_visible_columns_total=(visible_columns_total<1) ? 1 : visible_columns_total;
//--- Tiene que haber no menos de dos filas
   m_visible_rows_total=(visible_rows_total<2) ? 2 : visible_rows_total;
//--- Establecer el tamaño de para el array de columnas
   ::ArrayResize(m_columns,m_visible_columns_total);
//--- Establecer el tamaño de para el array de filas
   for(int i=0; i<m_visible_columns_total; i++)
      ::ArrayResize(m_columns[i].m_rows,m_visible_rows_total);
  }

Todas las propiedades tienen que inicializarse con valores predefinidos hasta la creación del control, y es mejor hacerlo directamente en el constructor de la clase:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLabelsTable::CLabelsTable(void) : m_fix_first_row(false),
                                   m_fix_first_column(false),
                                   m_row_y_size(18),
                                   m_x_offset(30),
                                   m_column_x_offset(60),
                                   m_area_color(clrWhiteSmoke),
                                   m_text_color(clrBlack),
                                   m_rows_total(2),
                                   m_columns_total(1),
                                   m_visible_rows_total(2),
                                   m_visible_columns_total(1)
  {
//--- Guardamos el nombre de la clase del control en la clase base
   CElement::ClassName(CLASS_NAME);
//--- Establecemos las prioridades para el clic izquierdo del ratón
   m_zorder      =0;
   m_area_zorder =1;
//--- Establecemos el tamaño de la tabla y de su parte visible
   TableSize(m_columns_total,m_rows_total);
   VisibleTableSize(m_visible_columns_total,m_visible_rows_total);
  }

Para diseñar este control, vamos a crear cuatro métodos privados (private) y uno público (public) para la llamada externa. Para que el usuario pueda configurar las barras de desplazamiento de esta tabla, añadimos los métodos que devuelven sus punteros. 

class CLabelsTable : public CElement
  {
private:
   //--- Objetos para crear la tabla
   CRectLabel        m_area;
   CScrollV          m_scrollv;
   CScrollH          m_scrollh;
   //--- Array de objetos de la parte visible de la tabla
   struct LTLabels
     {
      CLabel            m_rows[];
     };
   LTLabels          m_columns[];
   //---
public:
   //--- Devuelve los punteros a las barras de desplazamiento
   CScrollV         *GetScrollVPointer(void)                const { return(::GetPointer(m_scrollv)); }
   CScrollH         *GetScrollHPointer(void)                const { return(::GetPointer(m_scrollh)); }
   //--- Métodos para crear la tabla
   bool              CreateLabelsTable(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateLabels(void);
   bool              CreateScrollV(void);
   bool              CreateScrollH(void);
  };

De los métodos para la creación de los objetos del control, aquí vamos a mencionar sólo CLabelsTable::CreateLabels(), que es necesario para la creación de las etiquetas de texto. Al formar el nombre del objeto, añadimos los índices de la columna y fila. Se puede conocer los demás métodos más detalladamente en los archivos adjuntos al artículo. 

//+------------------------------------------------------------------+
//| Crea el array de las etiquetas de texto                                   |
//+------------------------------------------------------------------+
bool CLabelsTable::CreateLabels(void)
  {
//--- Las coordenadas y el margen
   int x      =CElement::X();
   int y      =0;
   int offset =0;
//--- Columnas
   for(int c=0; c<m_visible_columns_total; c++)
     {
      //--- Cálculo del margen de la columna
      offset=(c>0) ? m_column_x_offset : m_x_offset;
      //--- Cálculo de la coordenada X
      x=x+offset;
      //--- Filas
      for(int r=0; r<m_visible_rows_total; r++)
        {
         //--- Formación del nombre del onjeto
         string name=CElement::ProgramName()+"_labelstable_label_"+(string)c+"_"+(string)r+"__"+(string)CElement::Id();
        //--- Cálculo de la coordenada Y
         y=(r>0) ? y+m_row_y_size-1 : CElement::Y()+10;
        //--- Creación del objeto
         if(!m_columns[c].m_rows[r].Create(m_chart_id,name,m_subwin,x,y))
            return(false);
        //--- Establecemos las propiedades
         m_columns[c].m_rows[r].Description(m_vcolumns[c].m_vrows[r]);
         m_columns[c].m_rows[r].Font(FONT);
         m_columns[c].m_rows[r].FontSize(FONT_SIZE);
         m_columns[c].m_rows[r].Color(m_text_color);
         m_columns[c].m_rows[r].Corner(m_corner);
         m_columns[c].m_rows[r].Anchor(ANCHOR_CENTER);
         m_columns[c].m_rows[r].Selectable(false);
         m_columns[c].m_rows[r].Z_Order(m_zorder);
         m_columns[c].m_rows[r].Tooltip("\n");
         //--- Márgenes desde el punto extremo del formulario
         m_columns[c].m_rows[r].XGap(x-m_wnd.X());
         m_columns[c].m_rows[r].YGap(y-m_wnd.Y());
        //--- Coordenadas
         m_columns[c].m_rows[r].X(x);
         m_columns[c].m_rows[r].Y(y);
        //--- Guardamos el puntero del objeto
         CElement::AddToArray(m_columns[c].m_rows[r]);
        }
     }
//---
   return(true);
  }

Vamos a necesitar los métodos que permitan cambiar los valores y las propiedades en cualquier celda de la tabla y en cualquier momento, ya después de la creación de la tabla. Para modificar y obtener el valor de la celda, escribiremos los métodos CLabelsTable::SetValue() y CLabelsTable::GetValue(). Para ambos métodos, hay que pasar los índices de la columna y la fila, como sus dos primeros argumentos. En el método CLabelsTable::SetValue(), como su tercer argumento, es necesario pasar el valor que hay que introducir en el array según los índices especificados. En el inicio de estos métodos se realiza la comprobación obligatoria respecto a la superación del rango de los arrays. 

class CLabelsTable : public CElement
  {
public:
   //--- Establece el valor en la celda especificada de la tabla
   void              SetValue(const int column_index,const int row_index,const string value);
   //--- Obtiene el valor desde la celda especificada de la tabla
   string            GetValue(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Establece el valor según los índices especificados                     |
//+------------------------------------------------------------------+
void CLabelsTable::SetValue(const int column_index,const int row_index,const string value)
  {
//--- Comprobar la superación del rango de las columnas
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return;
//--- Comprobar la superación del rango de las filas
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return;
//--- Establecer el valor
   m_vcolumns[column_index].m_vrows[row_index]=value;
  }
//+------------------------------------------------------------------+
//| Devuelve el valor según los índices especificados                         |
//+------------------------------------------------------------------+
string CLabelsTable::GetValue(const int column_index,const int row_index)
  {
//--- Comprobar la superación del rango de las columnas
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return("");
//--- Comprobar la superación del rango de las filas
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return("");
//--- Devolver el valor
   return(m_vcolumns[column_index].m_vrows[row_index]);
  }

Aparte de modificar los valores en la tabla, el usuario puede necesitar cambiar el color del texto. Por ejemplo, se puede mostrar los valores positivos en verde, y los negativos en rojo. Para eso escribiremos el método CLabelsTable::TextColor(). Es parecido al método CLabelsTable::SetValue(), pero aquí hay que pasar el color como el tercer argumento. 

class CLabelsTable : public CElement
  {
public:
   //--- Cambia el color del texto en la celda especificada de la tabla
   void              TextColor(const int column_index,const int row_index,const color clr);
  };
//+------------------------------------------------------------------+
//| Cambia el color según los índices especificados                               |
//+------------------------------------------------------------------+
void CLabelsTable::TextColor(const int column_index,const int row_index,const color clr)
  {
//--- Comprobar la superación del rango de las columnas
   int csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index<0 || column_index>=csize)
      return;
//--- Comprobar la superación del rango de las filas
   int rsize=::ArraySize(m_vcolumns[column_index].m_vrows);
   if(rsize<1 || row_index<0 || row_index>=rsize)
      return;
//--- Establecer el color
   m_vcolumns[column_index].m_colors[row_index]=clr;
  }

Los cambios introducidos serán visualizados sólo después de la actualización de la tabla. Para ese propósito crearemos un método universal que va a utilizarse también para el desplazamiento de datos respecto a la posición de los deslizadores de las barras de desplazamiento. 

Este método va a llamarse CLabelsTable::UpdateTable(). Si el modo de encabezados fijos está activado, entonces para que la primera fila siempre se encuentre arriba y/o la columna izquierda se encuentre a la izquierda, hay que empezar a mover los datos a partir del segundo (1) índice de los arrays. En el inicio del método se declaran las variables t y l que reciben 1 o 0, dependiendo del modo que se usa ahora.

Para determinar desde qué índice hay que empezar a mover/actualizar los datos, hay que recibir las posiciones actuales de los deslizadores de las barras de desplazamiento. El desplazamiento de los encabezados en la columna izquierda y en la fila superior (si estos modos están activados) se realiza en ciclos separados.

Al final del método, se realiza el desplazamiento de los datos de la tabla, así como el color de las celdas, en el ciclo doble. 

class CLabelsTable : public CElement
  {
public:
   //--- Actualización de datos de la tabla de acuerdo con últimos cambios
   void              UpdateTable(void);
  };
//+------------------------------------------------------------------+
//| Actualización de datos de la tabla de acuerdo con últimos cambios           |
//+------------------------------------------------------------------+
void CLabelsTable::UpdateTable(void)
  {
//--- Desplazamiento a un índice si el modo de encabezados fijos está activado
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtenemos las posiciones actuales de los deslizadores de la barra de desplazamiento vertical y horizontal
   int h=m_scrollh.CurrentPos()+l;
   int v=m_scrollv.CurrentPos()+t;
//--- Mover los encabezados en la columna izquierda
   if(m_fix_first_column)
     {
      m_columns[0].m_rows[0].Description(m_vcolumns[0].m_vrows[0]);
      //--- Filas
      for(int r=t; r<m_visible_rows_total; r++)
        {
         if(r>=t && r<m_rows_total)
            m_columns[0].m_rows[r].Description(m_vcolumns[0].m_vrows[v]);
         //---
         v++;
        }
     }
//--- Mover los encabezados en la fila superior
   if(m_fix_first_row)
     {
      m_columns[0].m_rows[0].Description(m_vcolumns[0].m_vrows[0]);
      //--- Columnas
      for(int c=l; c<m_visible_columns_total; c++)
        {
         if(h>=l && h<m_columns_total)
            m_columns[c].m_rows[0].Description(m_vcolumns[h].m_vrows[0]);
         //---
         h++;
        }
     }
//--- Obtenemos la posición actual del deslizador de la barra de desplazamiento horizontal
   h=m_scrollh.CurrentPos()+l;
//--- Columnas
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Obtenemos la posición actual del deslizador de la barra de desplazamiento vertical
      v=m_scrollv.CurrentPos()+t;
      //--- Filas
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Desplazamiento de datos de la tabla
         if(v>=t && v<m_rows_total && h>=l && h<m_columns_total)
           {
            //--- Corrección del color
            m_columns[c].m_rows[r].Color(m_vcolumns[h].m_colors[v]);
            //--- Corrección de valores
            m_columns[c].m_rows[r].Description(m_vcolumns[h].m_vrows[v]);
            v++;
           }
        }
      //---
      h++;
     }
  }

Igual como ha sido hecho en los campos de edición y en las listas, implementaremos aquí el avance/retroceso rápido al apretar el botón izquierdo del ratón sobre los botones de las barras de desplazamiento. No vamos a mostrar aquí el código del método CLabelsTable::FastSwitching(), ya que parece mucho a los métodos homónimos que ya han sido considerados en los artículos anteriores en la clases CListView, CSpinEdit y CCheckBoxEdit

Ahora podemos visualizar el código de los métodos de procesamiento de los eventos del control tal como se muestra a continuación: 

//+------------------------------------------------------------------+
//| Manejador de eventos                                               |
//+------------------------------------------------------------------+
void CLabelsTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del desplazamiento del cursor
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Salir si el control está ocultado
      if(!CElement::IsVisible())
         return;
       //--- Coordenadas y el estado del botón izquierdo del ratón
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Comprobación del foco sobre la tabla
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Desplazamos la lista si el manejo del deslizador está activado
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state) || m_scrollh.ScrollBarControl(x,y,m_mouse_state))
         UpdateTable();
      //---
      return;
     }
//--- Procesamiento del clic en los objetos
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
       //--- Si han sido pulsados uno de los botones de las barras de desplazamiento de la tabla
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam) ||
         m_scrollh.OnClickScrollInc(sparam) || m_scrollh.OnClickScrollDec(sparam))
         //--- Mueve la tabla respecto a la barra de desplazamiento
         UpdateTable();
      //---
      return;
     }
  }
//+------------------------------------------------------------------+
//| Temporizador                                                           |
//+------------------------------------------------------------------+
void CLabelsTable::OnEventTimer(void)
  {
//--- Si este control es desplegable
   if(CElement::IsDropdown())
      FastSwitching();
 //--- Si el control no es desplegable, tomamos en cuenta la disponibilidad del formulario en este momento
   else
     {
      //--- Seguimos el avance/retroceso de la tabla sólo si el formulario no está bloqueado
      if(!m_wnd.IsLocked())
         FastSwitching();
     }
  }

La tabla es un control complejo (compuesto) de la interfaz gráfica. Por eso, hay que asegurar la introducción de los punteros de otros controles suyos (en este caso, la barra de desplazamiento vertical y horizontal) en la base de los punteros. Vamos a crear el array personal de punteros para las tablas. Puede ver todos estos cambios en el archivo WndContainer.mqh en la clase CWndContainer. Lo que necesitamos hacer para ello ya ha sido mostrado en otros artículos de la serie, por eso no vamos a repetirlo y pasaremos directamente al testeo de nuestra tabla a base de las etiquetas de texto. 

 


Prueba de la tabla a base de etiquetas de texto

Para la prueba vamos a usar el Asesor Experto del artículo anterior dejando ahí sólo el menú principal y la barra de estado. Para añadir la tabla de las etiquetas de texto a la interfaz gráfica, hay que declarar la instancia de la clase tipo CLabelsTable, así como el método y los márgenes desde el punto extremo del formulario (véase el código de abajo).

class CProgram : public CWndEvents
  {
private:
   //--- Tabla de las etiquetas de texto
   CLabelsTable      m_labels_table;
   //---
private:
   //--- Tabla de las etiquetas de texto
#define LABELS_TABLE1_GAP_X   (1)
#define LABELS_TABLE1_GAP_Y   (42)
   bool              CreateLabelsTable(void);
  };

Ahora analizaremos más detalladamente el código del método CProgram::CreateLabelsTable(). Creamos la tabla con 21 columnas y 100 filas. Que tenga visibles 5 columnas y 10 filas. Protegemos (fijamos) la fila superior y la primera columna de la tabla contra el desplazamiento. Después de crear la tabla, la llenamos con valores aleatorios (de -1000 a 1000) y establecemos los colores. Los valores positivos tendrán el color verde, los negativos, el rojo. Después de eso, como recordamos, hay que actualizar la tabla para mostrar los últimos cambios

//+------------------------------------------------------------------+
//| Crea la tabla a base de las etiquetas de texto                               |
//+------------------------------------------------------------------+
bool CProgram::CreateLabelsTable(void)
  {
#define COLUMNS1_TOTAL (21)
#define ROWS1_TOTAL    (100)
//--- Guardamos el puntero al formulario
   m_labels_table.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+LABELS_TABLE1_GAP_X;
   int y=m_window1.Y()+LABELS_TABLE1_GAP_Y;
//--- Número de columnas y filas visibles
   int visible_columns_total =5;
   int visible_rows_total    =10;
//--- Establecemos las propiedades
   m_labels_table.XSize(400);
   m_labels_table.XOffset(40);
   m_labels_table.ColumnXOffset(75);
   m_labels_table.FixFirstRow(true);
   m_labels_table.FixFirstColumn(true);
   m_labels_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_labels_table.VisibleTableSize(visible_columns_total,visible_rows_total);
//--- Creamos la tabla
   if(!m_labels_table.CreateLabelsTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Llenamos la tabla:
//    La primera celda vacía
   m_labels_table.SetValue(0,0,"-");
//--- Encabezados para las columnas
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=0; r<1; r++)
         m_labels_table.SetValue(c,r,"SYMBOL "+string(c));
     }
//--- Encabezados para las filas, con la alineación por la derecha
   for(int c=0; c<1; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        m_labels_table.SetValue(c,r,"PARAMETER "+string(r));
     }
//--- Datos y formateo de la tabla (color del fondo y color de las celdas)
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
         m_labels_table.SetValue(c,r,string(::rand()%1000-::rand()%1000));
     }
//--- Establecemos el color del texto en las celdas de la tabla
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.TextColor(c,r,((double)m_labels_table.GetValue(c,r)>=0) ? clrGreen : clrRed);
//--- Actualizar la tabla
   m_labels_table.UpdateTable();
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_labels_table);
   return(true);
  }

Además, vamos a probar cómo se cambian los valores en la tabla durante la ejecución del programa en el gráfico del terminal. Para eso, en el temporizador de la clase de la aplicación MQL CProgram::OnTimerEvent() insertamos el código, tal como se muestra más abajo: 

//+------------------------------------------------------------------+
//| Temporizador                                                           |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
//--- El segundo elemento de la barra de estado va a actualizarse cada 500 milisegundos
   static int count=0;
   if(count<500)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//--- Poner a cero el contador
   count=0;
//--- Cambiar el valor en el segundo elemento de la barra de estado
   m_status_bar.ValueToItem(1,::TimeToString(::TimeLocal(),TIME_DATE|TIME_SECONDS));
//--- Llenamos la tabla con datos
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.SetValue(c,r,string(::rand()%1000-::rand()%1000));
//--- Establecemos el color del texto en las celdas de la tabla
   for(int c=1; c<m_labels_table.ColumnsTotal(); c++)
      for(int r=1; r<m_labels_table.RowsTotal(); r++)
         m_labels_table.TextColor(c,r,((double)m_labels_table.GetValue(c,r)>=0) ? clrGreen : clrRed);
//--- Actualizar la tabla
   m_labels_table.UpdateTable();
  }

La llamada al método de la creación de la tabla de las etiquetas de texto debe realizarse en el método principal de la creación de la interfaz gráfica del programa CProgram::CreateExpertPanel(). Abajo se muestra la versión reducida de este método: 

//+------------------------------------------------------------------+
//| Crea el panel del EA                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Creación del formulario 1 para los controles
//--- Creación de controles:
//    Menú principal
//--- Menús contextuales
//--- Creación de la barra de estado
//--- Tabla de las etiquetas de texto
   if(!CreateLabelsTable())
      return(false);
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Ahora nos queda compilar el código y cargar el programa en el gráfico. En la captura de abajo se muestra el resultado que se debe obtener:

 Fig. 2. Prueba del control “Tabla a base de etiquetas de texto”.

Fig. 2. Prueba del control “Tabla a base de etiquetas de texto”.

Todo funciona perfectamente. Ahora vamos a considerar la clase para la creación del segundo tipo de tablas.


 


Control “Tabla a base de campos de edición”

A diferencia de la tabla de las etiquetas de texto, la tabla a base de los campos de edición da más flexibilidad y más posibilidades. Aquí se puede no sólo cambiar el color del texto, sino también: 

  • establecer el modo de alineación del texto dentro de la celda (a la izquierda/a la derecha/centrado);
  • cambiar el color del fondo y del marco de los campos de edición;
  • incluso modificar manualmente los valores dentro de los campos de edición, activando el modo correspondiente.

Todo eso permitirá formatear la tabla asignándole un aspecto más comprensible para el usuario final, y dará la posibilidad de usarla para la solución de un conjunto de tareas más amplio. Vamos a nombrar todas las partes integrantes de este tipo de la tabla.

  1. Fondo
  2. Campos de edición
  3. Barra de desplazamiento vertical
  4. Barra de desplazamiento horizontal

 

Fig. 3. Partes integrantes del control “Tabla de los campos de edición”.


Vamos a ver en qué se diferencia esta tabla en cuanto a la clase del código de la que hemos hablado anteriormente.

 


Desarrollo de la clase CTable

Iremos directamente a la enumeración de las propiedades para este tipo de tablas, y de paso contaremos sobre las diferencias de la tabla de las etiquetas de texto. Primero, el array para los objetos de la parte visible de la tabla es de otro tipo, (CEdit). Es decir, en vez de las etiquetas de texto aquí se trata de los campos de edición (véase el código de abajo).

class CTable : public CElement
  {
private:
   //--- Array de objetos de la parte visible de la tabla
   struct TEdits
     {
      CEdit             m_rows[];
     };
   TEdits            m_columns[];
  };

Aquí hay más arrays de las propiedades únicas para cada celda. Es que en esta tabla, aparte del color del texto, se podrá establecer/modificar el modo de alineación del texto y el color del fondo de los campos.

class CTable : public CElement
  {
private:
   //--- Arrays de los valores y de las propiedades de la tabla
   struct TOptions
     {
      string            m_vrows[];
      ENUM_ALIGN_MODE   m_text_align[];
      color             m_text_color[];
      color             m_cell_color[];
     };
   TOptions          m_vcolumns[];
  };

Vamos nombrar aquellos modos y las propiedades que no están presentes en la tabla de las etiquetas de texto.

  • Modo de la tabla editable
  • Modo del resalto de la línea al apuntarla con el cursor
  • Modo de la fila seleccionada
  • Alto de las filas
  • Color de la cuadrícula
  • Color del fondo de encabezados
  • Color del texto de encabezados
  • Color de celdas al situar el cursor sobre ellas
  • Color del texto en las celdas por defecto
  • Modo de alineación del texto en las celdas por defecto
  • Color del fondo de la línea seleccionada
  • Color del texto de la línea seleccionada

En este tipo de tablas también se puede fijar los encabezados en la primera columna y en la primera fila. Cuando los deslizadores de las barras de desplazamiento se mueven, ellos van a quedarse en su sitio, si estos modos están activados. El código de abajo contiene la lista completa de los campos y métodos para establecer las propiedades de la tabla: 

class CTable : public CElement
  {
private:
   //--- Alto de las filas de la tabla
   int               m_row_y_size;
   //--- (1) Color del fondo y (2) del marco del fondo de la tabla
   color             m_area_color;
   color             m_area_border_color;
   //--- Color de la cuadrícula
   color             m_grid_color;
   //--- Color del fondo de encabezados
   color             m_headers_color;
   //--- Color del texto de encabezados
   color             m_headers_text_color;
   //--- Color de las celdas en diferentes estados
   color             m_cell_color;
   color             m_cell_color_hover;
   //--- Color del texto en las celdas por defecto
   color             m_cell_text_color;
   //--- Color del (1) fondo y del (2) texto de la líneas seleccionada
   color             m_selected_row_color;
   color             m_selected_row_text_color;
   //--- Modo de la tabla editable
   bool              m_read_only;
   //--- Modo para resaltar la línea al situar el cursor encima
   bool              m_lights_hover;
   //--- Modo de la línea seleccionada
   bool              m_selectable_row;
   //--- Modo de fijación de la primera línea
   bool              m_fix_first_row;
   //--- Modo de fijación de la primera columna
   bool              m_fix_first_column;
   //--- Modo de alineación del texto en los campos de edición por defecto
   ENUM_ALIGN_MODE   m_align_mode;
   //---
public:
   //--- Color del (1) fondo y del (2) marco de la tabla
   void              AreaColor(const color clr)                        { m_area_color=clr;                }
   void              BorderColor(const color clr)                      { m_area_border_color=clr;         }
   //--- (1) Devuelve y (2) establece el modo de fijación de la primera fila
   bool              FixFirstRow(void)                           const { return(m_fix_first_row);         }
   void              FixFirstRow(const bool flag)                      { m_fix_first_row=flag;            }
   //--- (1) Devuelve y (2) establece el modo de fijación de la primera columna
   bool              FixFirstColumn(void)                        const { return(m_fix_first_column);      }
   void              FixFirstColumn(const bool flag)                   { m_fix_first_column=flag;         }
   //--- Color del (1) fondo de encabezados, (2) texto de encabezados y (3) cuadricula de la tabla
   void              HeadersColor(const color clr)                     { m_headers_color=clr;             }
   void              HeadersTextColor(const color clr)                 { m_headers_text_color=clr;        }
   void              GridColor(const color clr)                        { m_grid_color=clr;                }
   //--- Tamaño de las filas por el eje Y
   void              RowYSize(const int y_size)                        { m_row_y_size=y_size;             }
   void              CellColor(const color clr)                        { m_cell_color=clr;                }
   void              CellColorHover(const color clr)                   { m_cell_color_hover=clr;          }
   //--- Modos (1) “Sólo lectura”, (2) resaltar al situar el cursos encima, (3) seleccionar la fila
   void              ReadOnly(const bool flag)                         { m_read_only=flag;                }
   void              LightsHover(const bool flag)                      { m_lights_hover=flag;             }
   void              SelectableRow(const bool flag)                    { m_selectable_row=flag;           }
   //--- Modo de alineación del texto en las celdas
   void              TextAlign(const ENUM_ALIGN_MODE align_mode)       { m_align_mode=align_mode;         }
  };

Abajo mostramos la funcionalidad de los métodos que se utilizan para establecer las propiedades y obtener los valores de la tabla según los índices de las columnas y filas.

  • Tamaño general de la tabla (número total de columnas y filas)
  • Tamaño visible de la tabla (número visible de columnas y filas)
  • Modo de alineación del texto dentro de la celda (a la izquierda/a la derecha/centrado);
  • Color del texto
  • Color del fondo
  • Establecer/modificar el valor
  • Obtener el valor

Aquí no vamos a mostrar el código de estos métodos, porque ya hemos considerado los métodos similares en el ejemplo de la tabla de etiquetas de texto. Después de establecer las propiedades, es obligatorio actualizar la tabla llamando al método CTable::UpdateTable(), para que los cambios realizados se reflejen en ella. 

class CTable : public CElement
  {
public:
   //--- Establece el (1) tamaño de la tabla y el (2) tamaño de su parte visible
   void              TableSize(const int columns_total,const int rows_total);
   void              VisibleTableSize(const int visible_columns_total,const int visible_rows_total);
   //--- Establecer el (1) modo de alineación del texto, (2) color del texto, (3) color del fondo de la celda
   void              TextAlign(const int column_index,const int row_index,const ENUM_ALIGN_MODE mode);
   void              TextColor(const int column_index,const int row_index,const color clr);
   void              CellColor(const int column_index,const int row_index,const color clr);
   //--- Establece el valor en la celda especificada de la tabla
   void              SetValue(const int column_index,const int row_index,const string value);
   //--- Obtiene el valor desde la celda especificada de la tabla
   string            GetValue(const int column_index,const int row_index);
   //--- Actualización de datos de la tabla de acuerdo con últimos cambios
   void              UpdateTable(void);
  };

A continuación, hablaremos de los métodos para la gestión de la tabla. Todos los métodos de la clase serán privados (private) para el uso interno. Van a encargarse de las siguientes tareas:

  • Procesamiento del clic en la fila de la tabla.
  • Procesamiento de la introducción del valor en la celda de la tabla.
  • Obtención del identificador desde el nombre del objeto.
  • Extracción del índice de la columna desde el nombre del objeto.
  • Extracción del índice de la fila desde el nombre del objeto.
  • Resalto de la fila seleccionada.
  • Cambio del color de la fila al situar el cursor sobre ella.
  • Avance/retroceso rápido de datos de la tabla.

Empezamos con el método CTable::OnClickTableRow() para el procesamiento del clic en la fila de la tabla. Al principio del método hay que realizar una serie de comprobaciones. El programa sale del método en las siguientes situaciones:

  • está activado el modo de la tabla editable;
  • una de las barras de desplazamiento se encuentra en estado activo (el deslizador se desplaza);
  • el clic no ha sido hecho en la celda de la tabla. Eso se determina por la presencia del nombre del programa y del indicio de la pertenencia a la celda en el nombre del objeto;
  • Identificador del control no coincide. Para extraer el identificador desde el nombre del objeto, se utiliza el método CTable::IdFromObjectName(), que ya ha sido considerado antes en el ejemplo de otros controles.

Luego, tomando en cuenta el modo actual de encabezados fijos (de la primera fila) y la posición actual del deslizador de la barra vertical, repasamos todas las celdas buscando por el nombre la celda en la que hemos pulsado. Si la encontramos, guardamos el índice de la fila y el valor actual de la celda en los campos de la clase.

Si resulta que el clic ha sido hecho en los encabezados (la primera fila), el programa sale del método. De lo contrario, se genera el mensaje personalizado que contiene el (1) identificador del gráfico, el (2) identificador del evento (ON_CLICK_LIST_ITEM), el (3) identificado del control y el (4) índice seleccionado de la fila. 

class CTable : public CElement
  {
private:
   //--- Procesamiento del clic en la fila de la tabla
   bool              OnClickTableRow(const string clicked_object);
   //--- Obtención del identificador desde el nombre del objeto
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Procesamiento del clic en la fila de la tabla                                |
//+------------------------------------------------------------------+
bool CTable::OnClickTableRow(const string clicked_object)
  {
//--- Salir si el modo de la tabla editable está activado
   if(!m_read_only)
      return(false);
//--- Salimos si la barra de desplazamiento se encuentra en acción
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Salimos si el clic no ha sido hecho en la celda de la tabla
   if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- Obtenemos el identificador del nombre del objeto
   int id=IdFromObjectName(clicked_object);
//--- Salir si el identificador no coincide
   if(id!=CElement::Id())
      return(false);
//--- Para buscar el índice de la fila
   int row_index=0;
//--- Desplazamiento a un índice si el modo de encabezados fijos está activado
   int t=(m_fix_first_row) ? 1 : 0;
//--- Columnas
   for(int c=0; c<m_visible_columns_total; c++)
     {
      //--- Obtenemos la posición actual del deslizador de la barra de desplazamiento vertical
      int v=m_scrollv.CurrentPos()+t;
      //--- Filas
      for(int r=t; r<m_visible_rows_total; r++)
        {
        //--- Si el clic ha sido hecho en esta celda
         if(m_columns[c].m_rows[r].Name()==clicked_object)
           {
            //--- Guardamos el índice de la fila
            m_selected_item=row_index=v;
            //--- Guardamos la línea de la celda
            m_selected_item_text=m_columns[c].m_rows[r].Description();
            break;
           }
         //--- Aumentamos el contador de la fila
         if(v>=t && v<m_rows_total)
            v++;
        }
     }
//--- Salir si el clic ha sido en el encabezado
   if(m_fix_first_row && row_index<1)
      return(false);
//--- Enviamos el mensaje sobre ello
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElement::Id(),m_selected_item,"");
   return(true);
  }

Para el procesamiento de la introducción del valor en la celda de la tabla, cuando el modo editable de la tabla está activado, escribiremos el método CTable::OnEndEditCell(). En el inicio de este método, el programa debe pasar una serie de comprobaciones. El programa saldrá del método en las siguientes situaciones:

  • el modo de la tabla editable está desactivado;
  • el nombre del programa o el indicio de la pertenencia a la celda no coincide;
  • el identificador del control no coincide.

Si todas las comprobaciones han sido superadas con éxito, usamos los métodos auxiliares CTable::ColumnIndexFromObjectName() y CTable::RowIndexFromObjectName() para obtener los índices de la columna y fila de la celda en la parte visible de la tabla (índices de los arrays de objetos gráficos). Ahora, para obtener los índices del array de datos, hay que añadir las posiciones actuales de los deslizadores a los índices de los objetos. Luego, hay que corregir el índice de la fila, si el modo de encabezados fijos está activado y el índice del array de objetos es igual a cero. Luego, hay que comprobar si el valor en la celda ha sido cambiado. Si es así, entonces el valor nuevo se guarda en el array correspondiente de datos y se genera el mensaje con el (1) identificador del gráfico, (2) identificador del evento (ON_END_EDIT), (3) identificador del control y la (4) línea que se forma de los índices de la columna y la fila, y del valor actual en la ceda. Vamos a usar el guión bajo «_» como separador en esta línea. 

class CTable : public CElement
  {
private:
   //--- Procesamiento de la introducción del valor en la celda de la tabla
   bool              OnEndEditCell(const string edited_object);
   //--- Extrae el índice de la columna desde el nombre del objeto
   int               ColumnIndexFromObjectName(const string object_name);
   //--- Extrae el índice de la fila desde el nombre del objeto
   int               RowIndexFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Evento del fin de la edición del valor en la celda               |
//+------------------------------------------------------------------+
bool CTable::OnEndEditCell(const string edited_object)
  {
//--- Salir si el modo de la tabla editable está desactivado
   if(m_read_only)
      return(false);
//--- Salimos si el clic no ha sido hecho en la celda de la tabla
   if(::StringFind(edited_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- Obtenemos el identificador desde el nombre del objeto
   int id=IdFromObjectName(edited_object);
//--- Salir si el identificador no coincide
   if(id!=CElement::Id())
      return(false);
//--- Obtenemos los índices de la columna y fila de la celda
   int c =ColumnIndexFromObjectName(edited_object);
   int r =RowIndexFromObjectName(edited_object);
//--- Obtenemos los índices de la columna y fila en el array de datos
   int vc =c+m_scrollh.CurrentPos();
   int vr =r+m_scrollv.CurrentPos();
//--- Corregir el índice de la fila si el clic ha sido hecho en el encabezado
   if(m_fix_first_row && r==0)
      vr=0;
//--- Obtenemos el valor introducido
   string cell_text=m_columns[c].m_rows[r].Description();
//--- Si el valor de la celda ha sido cambiado
   if(cell_text!=m_vcolumns[vc].m_vrows[vr])
     {
      //--- Guardamos el valor nuevo en el array
      SetValue(vc,vr,cell_text);
      //--- Enviamos el mensaje sobre ello
      ::EventChartCustom(m_chart_id,ON_END_EDIT,CElement::Id(),0,string(vc)+"_"+string(vr)+"_"+cell_text);
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Extrae el índice de la columna desde el nombre del objeto                        |
//+------------------------------------------------------------------+
int CTable::ColumnIndexFromObjectName(const string object_name)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Obtenemos el código del separador
   u_sep=::StringGetCharacter("_",0);
//--- Dividimos la línea
   ::StringSplit(object_name,u_sep,result);
   array_size=::ArraySize(result)-1;
//--- Comprobar la superación del rango del array
   if(array_size-3<0)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//--- Devolver el índice del punto
   return((int)result[array_size-3]);
  }
//+------------------------------------------------------------------+
//| Extrae el índice de la fila desde el nombre del objeto                          |
//+------------------------------------------------------------------+
int CTable::RowIndexFromObjectName(const string object_name)
  {
   ushort u_sep=0;
   string result[];
   int    array_size=0;
//--- Obtenemos el código del separador
   u_sep=::StringGetCharacter("_",0);
//--- Dividimos la línea
   ::StringSplit(object_name,u_sep,result);
   array_size=::ArraySize(result)-1;
//--- Comprobar la superación del rango del array
   if(array_size-2<0)
     {
      ::Print(PREVENTING_OUT_OF_RANGE);
      return(WRONG_VALUE);
     }
//--- Devolver el índice del punto
   return((int)result[array_size-2]);
  }

Cuando el modo correspondiente está activado, para la selección de la línea se usa el método CTable::HighlightSelectedItem(). La fila seleccionada va a tener el color del fondo y del texto diferente. Estas propiedades están disponibles para la modificación por el usuario y han sido consideradas anteriormente.

Al principio del método hay dos comprobaciones (véase el código de abajo). El programa sale si:

  • el modo de edición de las celdas está activado;
  • el modo de selección de la fila está desactivado.

Si las comprobaciones han sido superadas con éxito, para las columnas y filas se realiza el desplazamiento a un índice si el modo de encabezados fijos está activado. Luego, a través de dos contadores (para columnas y filas) y tomando en cuenta la posición actual de los deslizadores, buscamos en el ciclo doble la fila seleccionada y establecemos el color correspondiente para el fondo y el texto de todas sus celdas. Para los objetos de otras filas se establece el color desde los arrays. 

class CTable : public CElement
  {
private:
   //--- Resalto de la fila seleccionada
   void              HighlightSelectedItem(void);
  };
//+------------------------------------------------------------------+
//| Resalto de la fila seleccionada                                       |
//+------------------------------------------------------------------+
void CTable::HighlightSelectedItem(void)
  {
//--- Salir si uno de los modos (“Sólo lectura”, “Selección de la línea”) está desactivado
   if(!m_read_only || !m_selectable_row)
      return;
//--- Desplazamiento a un índice si el modo de encabezados fijos está activado
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtenemos la posición actual del deslizador de la barra de desplazamiento horizontal
   int h=m_scrollh.CurrentPos()+l;
//--- Columnas
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Obtenemos la posición actual del deslizador de la barra de desplazamiento vertical
      int v=m_scrollv.CurrentPos()+t;
      //--- Filas
      for(int r=t; r<m_visible_rows_total; r++)
        {
         //--- Desplazamiento de datos de la tabla
         if(v>=t && v<m_rows_total)
           {
            //--- Corrección tomando en cuenta la línea seleccionada
            color back_color=(m_selected_item==v) ? m_selected_row_color : m_vcolumns[h].m_cell_color[v];
            color text_color=(m_selected_item==v) ? m_selected_row_text_color : m_vcolumns[h].m_text_color[v];
            //--- Corrección del color del texto y fondo de la celda
            m_columns[c].m_rows[r].Color(text_color);
            m_columns[c].m_rows[r].BackColor(back_color);
            v++;
           }
        }
      //---
      h++;
     }
  }

Otro modo útil puede ser el resalto de la fila al situar el cursor sobre ella. Para este caso se usa el método CTable::RowColorByHover(). Aquí también hay unas comprobación que provocan la salida del método en caso de no superarlas con éxito. El programa sale si:

  • el modo para resaltar la fila al situar el cursos encima está desactivado;
  • el modo de edición de la tabla está activado;
  • el formulario al que está adjuntado el control está bloqueado;
  • una de las barras de desplazamiento se encuentra en estado activo (el deslizador se desplaza).

Luego, si todas las pruebas han sido superadas, repasamos en el ciclo doble todas las celdas y cambiamos su color dependiendo de la celda sobre cuya fila se encuentra el cursor. La excepción sólo es la fila seleccionada. Al llegar a ella, el contador de las fijas será aumentado, pero el color de las celdas de esta fila no va a cambiarse. 

class CTable : public CElement
  {
private:
   //--- Cambio del color de la fila al situar el cursor sobre ella
   void              RowColorByHover(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Cambio del color de la fila al situar el cursor sobre ella          |
//+------------------------------------------------------------------+
void CTable::RowColorByHover(const int x,const int y)
  {
//--- Salir si el modo de resaltar la fila al situar el cursos encima está desactivado o el formulario está bloqueado
   if(!m_lights_hover || !m_read_only || m_wnd.IsLocked())
      return;
//--- Salimos si la barra de desplazamiento se encuentra en acción
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return;
//--- Desplazamiento a un índice si el modo de encabezados fijos está activado
   int t=(m_fix_first_row) ? 1 : 0;
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtenemos la posición actual del deslizador de la barra de desplazamiento horizontal
   int h=m_scrollh.CurrentPos()+l;
//--- Columnas
   for(int c=l; c<m_visible_columns_total; c++)
     {
      //--- Obtenemos la posición actual del deslizador de la barra de desplazamiento vertical
      int v=m_scrollv.CurrentPos()+t;
      //--- Filas
      for(int r=t; r<m_visible_rows_total; r++)
        {
        //--- Comprobación para la prevención de superar el rango
         if(v>=t && v<m_rows_total)
           {
            //--- Omitir si es el modo “Sólo lectura”, selección de la fila está activada y llegamos a la línea seleccionada
            if(m_selected_item==v && m_read_only && m_selectable_row)
              {
               v++;
               continue;
              }
            //--- Resaltar la fila si el cursor se encuentra sobre ella
            if(x>m_columns[0].m_rows[r].X() && x<m_columns[m_visible_columns_total-1].m_rows[r].X2() &&
               y>m_columns[c].m_rows[r].Y() && y<m_columns[c].m_rows[r].Y2())
              {
               m_columns[c].m_rows[r].BackColor(m_cell_color_hover);
              }
            //--- Devolver el color predefinido si el cursor está fuera del área de esta fila
            else
              {
               if(v>=t && v<m_rows_total)
                  m_columns[c].m_rows[r].BackColor(m_vcolumns[h].m_cell_color[v]);
              }
            //---
            v++;
           }
        }
      //---
      h++;
     }
  }

Hemos analizado todos los métodos para gestionar la tabla. Más abajo se puede ver el código del manejador de eventos del control CTable::OnEvent() con más detalles. Preste atención en que el método CTable::HighlightSelectedItem() se utiliza también después del procesamiento del desplazamiento del deslizador de la barra vertical.

//+------------------------------------------------------------------+
//| Manejador de eventos                                               |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del desplazamiento del cursor
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Salir si el control está ocultado
      if(!CElement::IsVisible())
         return;
       //--- Coordenadas y el estado del botón izquierdo del ratón
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- Si la barra de desplazamiento se encuentra en acción
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state) || m_scrollh.ScrollBarControl(x,y,m_mouse_state))
         //--- Desplazamos la tabla
         UpdateTable();
      //--- Resalto de la fila seleccionada
      HighlightSelectedItem();
      //--- Cambia el color de la fila cuando el cursor se sitúa sobre ella
      RowColorByHover(x,y);
      return;
     }
//--- Procesamiento del clic en los objetos
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Si el clic ha sido hecho en la fila de la tabla
      if(OnClickTableRow(sparam))
        {
        //--- Resalto de la fila seleccionada
         HighlightSelectedItem();
         return;
        }
      //--- Si el clic ha sido hecho en el botón de la barra de desplazamiento
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam) ||
         m_scrollh.OnClickScrollInc(sparam) || m_scrollh.OnClickScrollDec(sparam))
        {
        //--- Actualización de datos de la tabla de acuerdo con últimos cambios
         UpdateTable();
        //--- Resalto de la fila seleccionada
         HighlightSelectedItem();
         return;
        }
      return;
     }
//--- Procesamiento del evento del cambio del valor en el campo de edición
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      OnEndEditCell(sparam);
       //--- Resetear los colores de la tabla
      ResetColors();
      return;
     }
  }

 


Prueba de la tabla a base de campos de edición

Todo está listo para probar la tabla de los campos de edición. Hacemos la copia del Asesor Experto anterior y eliminamos de ahí todo lo que está relacionado con la tabla de las etiquetas de texto. Luego creamos la instancia de la clase tipo CTable en la clase personalizada CProgram, declaramos el método para la creación de la tabla y determinamos los márgenes desde el punto extremo del formulario:

class CProgram : public CWndEvents
  {
private:
   //--- Tabla a base de los campos de edición
   CTable            m_table;
   //---
private:
   //--- Tabla a base de los campos de edición
#define TABLE1_GAP_X          (1)
#define TABLE1_GAP_Y          (42)
   bool              CreateTable(void);
  };

Creamos la tabla compuesta de 100 columnas y 1000 filas. 6 columnas y 15 filas serán visibles. Vamos a fijar los encabezados de la tabla (la primera columna y la primera fila) para que no se muevan cuando vamos a mover los deslizadores de las barras de desplazamiento. Activamos los modos para la selección de la fila y el resalto de las filas al situar el cursor sobre ellas. 

Después de crear el control, llenamos los arrays de la tabla con los datos y la fomateamos. Por ejemplo, al texto en las celdas de la primera columna le aplicaremos el modo de alineación ALIGN_RIGHT (por la derecha). Formateamos el color del fondo de las filas en el estilo “cebra”, y que el color del texto de las columnas se alterne con dos colores: rojo y azul. No olvidamos actualizar la tabla para visualizar los cambios. 

//+------------------------------------------------------------------+
//| Crea la tabla                                                   |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(void)
  {
#define COLUMNS1_TOTAL (100)
#define ROWS1_TOTAL    (1000)
//--- Guardamos el puntero al formulario
   m_table.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+TABLE1_GAP_X;
   int y=m_window1.Y()+TABLE1_GAP_Y;
//--- Número de columnas y filas visibles
   int visible_columns_total =6;
   int visible_rows_total    =15;
//--- Establecemos las propiedades antes de la creación
   m_table.XSize(600);
   m_table.RowYSize(20);
   m_table.FixFirstRow(true);
   m_table.FixFirstColumn(true);
   m_table.LightsHover(true);
   m_table.SelectableRow(true);
   m_table.TextAlign(ALIGN_CENTER);
   m_table.HeadersColor(C'255,244,213');
   m_table.HeadersTextColor(clrBlack);
   m_table.GridColor(clrLightGray);
   m_table.CellColorHover(clrGold);
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.VisibleTableSize(visible_columns_total,visible_rows_total);
//--- Creamos el control
   if(!m_table.CreateTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Llenamos la tabla:
//    La primera celda vacía
   m_table.SetValue(0,0,"-");
//--- Encabezados para las columnas
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=0; r<1; r++)
         m_table.SetValue(c,r,"SYMBOL "+string(c));
     }
//--- Encabezados para las filas, con la alineación por la derecha
   for(int c=0; c<1; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        {
         m_table.SetValue(c,r,"PARAMETER "+string(r));
         m_table.TextAlign(c,r,ALIGN_RIGHT);
        }
     }
//--- Datos y formateo de la tabla (color del fondo y color de las celdas)
   for(int c=1; c<COLUMNS1_TOTAL; c++)
     {
      for(int r=1; r<ROWS1_TOTAL; r++)
        {
         m_table.SetValue(c,r,string(c)+":"+string(r));
         m_table.TextColor(c,r,(c%2==0)? clrRed : clrRoyalBlue);
         m_table.CellColor(c,r,(r%2==0)? clrWhiteSmoke : clrWhite);
        }
     }
//--- Actualizamos la tabla para visualizar los cambios
   m_table.UpdateTable();
//--- Añadimos el objeto al array común de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
  }

La llamada al método CProgram::CreateTable() debe realizarse en el método principal de la creación de la interfaz gráfica (véase la versión reducida más abajo): 

//+------------------------------------------------------------------+
//| Crea el panel del EA                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Creación del formulario 1 para los controles
//--- Creación de controles:
//    Menú principal
//--- Menús contextuales
//--- Creación de la barra de estado
 //--- Tabla a base de los campos de edición
   if(!CreateTable())
      return(false);
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Compile el programa y cárguelo en el gráfico. Si ha hecho todo bien, verá el resultado como en la captura de abajo:

 Fig. 4. Prueba del control “Tabla a base de campos de edición”.

Fig. 4. Prueba del control “Tabla a base de campos de edición”.

Todo funciona perfectamente. Pero si le surge la necesidad de introducir el texto más de 63 caracteres en una celda, verá que el texto va a mostrarse de forma incompleta. Hay una limitación de 63 caracteres en todos los objetos gráficos del terminal donde se puede mostrar el texto. Para prescindir de esta limitación, hay que usar la clase CCanvas para el dibujo. En esta clase hay métodos para visualizar el texto, y no hay limitaciones algunas. Ya hemos utilizado esta clase para dibujar algunos controles (“línea separadora” y “descripción emergente”) en los siguientes artículos:

Puesto que 63 caracteres pueden ser insuficientes para mostrar los datos, el tercer tipo de las tablas lo vamos a dibujar usando la clase CCanvas

 


Control “Tabla dibujada”

Aparte de que la tabla dibujada no vaya a tener las limitaciones para el número de los caracteres en cada celda, aparece una posibilidad más: el ajuste del ancho de cada columna sin cambiar el ancho visible de la tabla. En otros tipos de las tablas era bastante problemático realizar eso. Pues, ya se puede nombrar algunas ventajas de la tabla dibujada respecto a otros tipos de este control:

  • no hay limitación para el número de caracteres en cada celda;
  • se puede ajustar el ancho para cada columna individualmente;
  • para la creación de la tabla, en vez de varios objetos como las etiquetas de texto (OBJ_LABEL) o campos de edición (OBJ_EDIT), se utiliza sólo uno, la imagen (OBJ_BITMAP_LABEL).

Vamos a nombrar todas las partes integrantes de este tipo de la tabla.

  1. Fondo
  2. Imagen con la tabla tibujada
  3. Barra de desplazamiento vertical
  4. Barra de desplazamiento horizontal


 

Fig. 5. Partes integrantes del control “Tabla dibujada”.

Vamos a ver con más detalles la organización del código para la creación de este tipo de la tabla.

 


Desarrollo de la clase CCanvasTable

Para almacenar los valores y las propiedades de las celdas de la tabla, vamos a crear la estructura CTOptions:

//+------------------------------------------------------------------+
//| Clase par crear la tabla dibujada                          |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- Array de los valores y las propiedades de la tabla
   struct CTOptions
     {
      string            m_vrows[];
      int               m_width;
      ENUM_ALIGN_MODE   m_text_align;
     };
   CTOptions         m_vcolumns[];
  };

La inicialización de los campos de la estructura CTOptions con valores predefinidos se realiza cuando se establece el tamaño principal de la tabla (número total de columnas y filas). Que el ancho de todas las columnas sea de 100 píxeles, y el modo de alineación del texto dentro de las celdas sea ALIGN_CENTER (centrado). 

class CCanvasTable : public CElement
  {
public:
   //--- Establecer el tamaño principal de la tabla
   void              TableSize(const int columns_total,const int rows_total);
  };
//+------------------------------------------------------------------+
//| Establece el tamaño de la tabla                                    |
//+------------------------------------------------------------------+
void CCanvasTable::TableSize(const int columns_total,const int rows_total)
  {
//--- Tiene que haber no menos de una columna
   m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- Tiene que haber no menos de dos filas
   m_rows_total=(rows_total<2) ? 2 : rows_total;
//--- Establecer el tamaño para el array de columnas
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Establecer el tamaño para el array de filas
   for(int i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      //--- Inicialización de las propiedades de las columnas con valores predefinidos
      m_vcolumns[i].m_width      =100;
      m_vcolumns[i].m_text_align =ALIGN_CENTER;
     }
  }

Vamos a crear los métodos que permitirán cambiar el ancho o el modo de alineación del texto de algunas columnas (si surge esta necesidad) después de establecer los tamaños de la tabla. Para eso el usuario tendrá que inicializar su array y simplemente rehacerlo en el método correspondiente. Luego mostraremos el ejemplo.

class CCanvasTable : public CElement
  {
public:
   //--- Establecer el (1) modo de alineación del texto y el (2) ancho para cada columna
   void              TextAlign(const ENUM_ALIGN_MODE &array[]);
   void              ColumnsWidth(const int &array[]);
  };
//+------------------------------------------------------------------+
//| Llena el array con el modo de alineación del texto                    |
//+------------------------------------------------------------------+
void CCanvasTable::TextAlign(const ENUM_ALIGN_MODE &array[])
  {
   int total=0;
   int array_size=::ArraySize(array);
//--- Salir si ha sido pasado el array con el tamaño cero
   if(array_size<1)
      return;
//--- Corregir el valor para prevenir la superación del rango
   total=(array_size<m_columns_total)? array_size : m_columns_total;
//--- Guardar el valor en la estructura
   for(int c=0; c<total; c++)
      m_vcolumns[c].m_text_align=array[c];
  }
//+------------------------------------------------------------------+
//| Llena el array con el ancho de las columnas                                 |
//+------------------------------------------------------------------+
void CCanvasTable::ColumnsWidth(const int &array[])
  {
   int total=0;
   int array_size=::ArraySize(array);
//--- Salir si ha sido pasado el array con el tamaño cero
   if(array_size<1)
      return;
//--- Corregir el valor para prevenir la superación del rango
   total=(array_size<m_columns_total)? array_size : m_columns_total;
//--- Guardar el valor en la estructura
   for(int c=0; c<total; c++)
      m_vcolumns[c].m_width=array[c];
  }

Para la creación de la tabla es necesario calcular sus tamaños totales, así como los tamaños de su parte visible, en relación a los parámetros establecidos (ancho de todas las columnas, alto de todas las filas y la presencia de las barras de desplazamiento). Para esta tarea escribiremos el método CCanvasTable::CalculateTableSize() cuyo código se muestra a continuación: 

class CCanvasTable : public CElement
  {
private:
   //--- Tamaño total y tamaño de la parte visible de la tabla
   int               m_table_x_size;
   int               m_table_y_size;
   int               m_table_visible_x_size;
   int               m_table_visible_y_size;
//---
private:
   //--- Calcula los tamaños de la tabla
   void              CalculateTableSize(void);
  };
//+------------------------------------------------------------------+
//| Calcula los tamaños de la tabla                                     |
//+------------------------------------------------------------------+
void CCanvasTable::CalculateTableSize(void)
  {
//--- Calculamos el ancho total de la tabla
   m_table_x_size=0;
   for(int c=0; c<m_columns_total; c++)
      m_table_x_size=m_table_x_size+m_vcolumns[c].m_width;
//--- Ancho de la tabla tomando en cuenta la presencia de la barra vertical
   int x_size=(m_rows_total>m_visible_rows_total) ? m_x_size-m_scrollh.ScrollWidth() : m_x_size-2;
//--- Si el ancho de todas las columnas es menos que el ancho de la tabla, vamos a usar el ancho de la tabla
   if(m_table_x_size<m_x_size)
      m_table_x_size=x_size;
//--- Calculamos el alto total de la tabla
   m_table_y_size=m_cell_y_size*m_rows_total-(m_rows_total-1);
//--- Establecemos el tamaño del frame para mostrar el fragmento de la imagen (parte visible de la tabla)
   m_table_visible_x_size=x_size;
   m_table_visible_y_size=m_cell_y_size*m_visible_rows_total-(m_visible_rows_total-1);
//--- Si hay barra de desplazamiento vertical, corregimos el tamaño del control por el Y
   int y_size=m_cell_y_size*m_visible_rows_total+2-(m_visible_rows_total-1);
   m_y_size=(m_table_x_size>m_table_visible_x_size) ? y_size+m_scrollh.ScrollWidth()-1 : y_size;
  }

Después de calcular los tamaños de la tabla y crear el lienzo para dibujar, necesitaremos los métodos mediante los cuales se puede dibujar la cuadrícula (marco) y el texto en las celdas. Para dibujar la cuadrícula, escribiremos el método CCanvasTable::DrawGrid(). Aquí, en el primer ciclo se dibujan las líneas horizontales de la cuadrícula, y en el segundo ciclo, las verticales.  

class CCanvasTable : public CElement
  {
private:
   //--- Color de la cuadrícula
   color             m_grid_color;
   //--- Tamaño (alto) de las celdas
   int               m_cell_y_size;
//---
public:
   //--- Color de la cuadrícula
   void              GridColor(const color clr)           { m_grid_color=clr;                }
//---
private:
   //--- Dibuja la cuadrícula
   void              DrawGrid(void);
  };
//+------------------------------------------------------------------+
//| Dibuja la cuadrícula                                                     |
//+------------------------------------------------------------------+
void CCanvasTable::DrawGrid(void)
  {
//--- Color de la cuadrícula
   uint clr=::ColorToARGB(m_grid_color,255);
//--- Tamaño del lienzo para el dibujo
   int x_size =m_canvas.XSize()-1;
   int y_size =m_canvas.YSize()-1;
//--- Coordenadas
   int x1=0,x2=0,y1=0,y2=0;
//--- Líneas horizontales
   x1=0;
   y1=0;
   x2=x_size;
   y2=0;
   for(int i=0; i<=m_rows_total; i++)
     {
      m_canvas.Line(x1,y1,x2,y2,clr);
      y2=y1+=m_cell_y_size-1;
     }
//--- Líneas verticales
   x1=0;
   y1=0;
   x2=0;
   y2=y_size;
   for(int i=0; i<m_columns_total; i++)
     {
      m_canvas.Line(x1,y1,x2,y2,clr);
      x2=x1+=m_vcolumns[i].m_width;
     }
//--- A la derecha
   x1=x_size;
   y1=0;
   x2=x_size;
   y2=y_size;
   m_canvas.Line(x1,y1,x2,y2,clr);
  }

El método CCanvasTable::DrawText() para dibujar el texto es más complicado que el método para dibujar la cuadrícula. Aquí hay que tomar en cuenta no sólo el modo de alineación del texto de la columna actual, sino también de la columna anterior, para calcular correctamente los márgenes. Vamos a establecer el margen en 10 píxeles desde el borde de la celda para los modos de alineación por la izquierda y por la derecha. El margen desde el borde superior de la celda será de 3 píxeles. Abajo se muestran más detalles sobre el código de este método: 

class CCanvasTable : public CElement
  {
private:
   //--- Color del texto
   color             m_cell_text_color;
//---
public:
   //--- Color del texto de la tabla
   void              TextColor(const color clr)           { m_cell_text_color=clr;           }
//---
private:
   //--- Dibuja el texto
   void              DrawText(void);
  };
//+------------------------------------------------------------------+
//| Dibuja el texto                                                     |
//+------------------------------------------------------------------+
void CCanvasTable::DrawText(void)
  {
//--- Para calcular las coordenadas y los mérgenes
   int  x             =0;
   int  y             =0;
   uint text_align    =0;
   int  column_offset =0;
   int  cell_x_offset =10;
   int  cell_y_offset =3;
//--- Color del texto
   uint clr=::ColorToARGB(m_cell_text_color,255);
//--- Propiedades de la fuente
   m_canvas.FontSet(FONT,-80,FW_NORMAL);
//--- Columnas
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Cálculos del margen para la primera columna
      if(c==0)
        {
         //--- Alineación del texto en las celdas según el modo establecido para cada columna
         switch(m_vcolumns[0].m_text_align)
           {
            //--- Por el centro
            case ALIGN_CENTER :
               column_offset=column_offset+m_vcolumns[0].m_width/2;
               x=column_offset;
               break;
               //--- Por la derecha
            case ALIGN_RIGHT :
               column_offset=column_offset+m_vcolumns[0].m_width;
               x=column_offset-cell_x_offset;
               break;
               //--- Por la izquierda
            case ALIGN_LEFT :
               x=column_offset+cell_x_offset;
               break;
           }
        }
      //--- Cálculo de márgenes para todas las columnas a excepción de la primera
      else
        {
         //--- Alineación del texto en las celdas según el modo establecido para cada columna
         switch(m_vcolumns[c].m_text_align)
           {
            //--- Por el centro
            case ALIGN_CENTER :
               //--- Cálculo del margen respecto a la alineación en la primera columna
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2)+(m_vcolumns[c].m_width/2);
                     break;
                  case ALIGN_RIGHT :
                     column_offset=column_offset+(m_vcolumns[c].m_width/2);
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width+(m_vcolumns[c].m_width/2);
                     break;
                 }
               //---
               x=column_offset;
               break;
               //--- A la derecha
            case ALIGN_RIGHT :
               //--- Cálculo del margen respecto a la alineación en la primera columna
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2)+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                  case ALIGN_RIGHT :
                     column_offset=column_offset+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width+m_vcolumns[c].m_width;
                     x=column_offset-cell_x_offset;
                     break;
                 }
               //---
               break;
               //--- Por la izquierda
            case ALIGN_LEFT :
               //--- Cálculo del margen respecto a la alineación en la primera columna
               switch(m_vcolumns[c-1].m_text_align)
                 {
                  case ALIGN_CENTER :
                     column_offset=column_offset+(m_vcolumns[c-1].m_width/2);
                     x=column_offset+cell_x_offset;
                     break;
                  case ALIGN_RIGHT :
                     x=column_offset+cell_x_offset;
                     break;
                  case ALIGN_LEFT :
                     column_offset=column_offset+m_vcolumns[c-1].m_width;
                     x=column_offset+cell_x_offset;
                     break;
                 }
               //---
               break;
           }
        }
      //--- Filas
      for(int r=0; r<m_rows_total; r++)
        {
         //---
         y+=(r>0) ? m_cell_y_size-1 : cell_y_offset;
         //---
         switch(m_vcolumns[c].m_text_align)
           {
            case ALIGN_CENTER :
               text_align=TA_CENTER|TA_TOP;
               break;
            case ALIGN_RIGHT :
               text_align=TA_RIGHT|TA_TOP;
               break;
            case ALIGN_LEFT :
               text_align=TA_LEFT|TA_TOP;
               break;
           }
         //--- Dibujar el texto
         m_canvas.TextOut(x,y,m_vcolumns[c].m_vrows[r],clr,text_align);
        }
       //--- Resetear la coordenada Y para el siguiente ciclo
0.
     }
  }

En dos primeros tipos de las tablas, el desplazamiento de datos mediante las barras de desplazamiento se realizaba usando el método del cambio de valores en los objetos (etiquetas de texto y campos de edición) de la parte visible de la tabla. Aquí vamos a usar el método de desplazamiento del área rectangular de la visibilidad de la imagen. Es decir, las dimensiones de la tabla (que en este caso es la imagen) son iguales a la suma del ancho de todas las columnas y el alto de todas las filas. Son tamaños de la imagen inicial, dentro de la cual se puede mover el área de visibilidad. Las dimensiones del área de visibilidad pueden ser cambiados en cualquier momento, pero aquí serán establecidas inmediatamente después de la creación del control en el método CCanvasTable::CreateCells(). 

Usted puede establecer el área de visibilidad usando las propiedades OBJPROP_XSIZE y OBJPROP_YSIZE, y realizar el desplazamiento de esta área (dentro de los márgenes de la imagen original) usando las propiedades OBJPROP_XOFFSET y OBJPROP_YOFFSET (véase el código de abajo): 

//--- Establecemos el área visible
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_XSIZE,m_table_visible_x_size);
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_YSIZE,m_table_visible_y_size);
//--- Establecemos el desplazamiento del frame dentro de la imagen por los ejes X y Y
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_XOFFSET,0);
   ::ObjectSetInteger(m_chart_id,name,OBJPROP_YOFFSET,0);

Para mover el área de visibilidad respecto a la posición actual de los deslizadores, escribiremos un simple método CCanvasTable::ShiftTable(). El desplazamiento por la vertical va a realizarse con el paso igual al alto de la fila, por la horizontal, por los píxeles (véase el código de abajo): 

class CCanvasTable : public CElement
  {
public:
   //--- Desplazamiento de la tabla respecto a las posiciones de las barras de desplazamiento
   void              ShiftTable(void);
  };
//+------------------------------------------------------------------+
//| Mueve la tabla respecto a la barras de desplazamiento                    |
//+------------------------------------------------------------------+
void CCanvasTable::ShiftTable(void)
  {
//--- Obtenemos las posiciones actuales de los deslizadores de la barra de desplazamiento vertical y horizontal
   int h=m_scrollh.CurrentPos();
   int v=m_scrollv.CurrentPos();
//--- Cálculo de la posición de la tabla respecto a los deslizadores de las barras
   long c=h;
   long r=v*(m_cell_y_size-1);
//--- Desplazamiento de la tabla
   ::ObjectSetInteger(m_chart_id,m_canvas.Name(),OBJPROP_XOFFSET,c);
   ::ObjectSetInteger(m_chart_id,m_canvas.Name(),OBJPROP_YOFFSET,r);
  }

Entonces, el código del método CCanvasTable::DrawTable() para el dibujo de la tabla será como se muestra a continuación: 

class CCanvasTable : public CElement
  {
public:
   //--- Dibuja la tabla de acuerdo con últimos cambios introducidos
   void              DrawTable(void);
  };
//+------------------------------------------------------------------+
//| Dibuja la tabla                                                   |
//+------------------------------------------------------------------+
void CCanvasTable::DrawTable(void)
  {
//--- Hacer el fondo transparente
   m_canvas.Erase(::ColorToARGB(clrNONE,0));
//--- Dibujar la cuadrícula
   DrawGrid();
//--- Dibujar el texto
   DrawText();
//--- Mostrar los últimos cambios dibujados
   m_canvas.Update();
//--- Desplazamiento de la tabla respecto a la barras de desplazamiento
   ShiftTable();
  }

Todo está listo para probar este tipo de la tabla. 

 


Prueba de la tabla dibujada

Hacemos la copia del Asesor Experto anterior para nuestra prueba y eliminamos de ahí todo lo que está relacionado con la tabla tipo CTable. Creamos la instancia de la clase tipo CCanvasTable en la clase personalizada CProgram, declaramos el (1) método CProgram::CreateCanvasTable() para la creación de la tabla y (2) determinamos los márgenes desde el punto extremo del formulario, tal como se muestra a continuación:

class CProgram : public CWndEvents
  {
private:
   //--- Tabla dibujada
   CCanvasTable      m_canvas_table;
   //---
private:
   //--- Tabla dibujada
#define TABLE1_GAP_X          (1)
#define TABLE1_GAP_Y          (42)
   bool              CreateCanvasTable(void);
  };

Creamos la tabla con 15 columnas y 1000 filas. El número de filas visibles va a ser 16. No es necesario indicar el número de columnas visibles en esta versión porque el desplazamiento por la horizontal va a realizarse por píxeles. Tenemos que indicar el ancho del área visible de forma explícita, en este caso será de 601 píxeles. 

De ejemplo, hagamos el ancho de todas las columnas (salvo dos primeras) igual a 70 píxeles. Establecemos el ancho para la primera y la segunda columna igual a 100 y 90 píxeles, respectivamente. El modo de alineación del texto será por el centro en todas las columnas, salvo la primera (por la derecha), la segunda (por la izquierda) y la tercera (por la derecha). Abajo se muestra el código completo del método CProgram::CreateCanvasTable():

//+------------------------------------------------------------------+
//| Crea la tabla dibujada                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateCanvasTable(void)
  {
#define COLUMNS1_TOTAL 15
#define ROWS1_TOTAL    1000
//--- Guardamos el puntero al formulario
   m_canvas_table.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+TABLE1_GAP_X;
   int y=m_window1.Y()+TABLE1_GAP_Y;
//--- Número de filas visibles
   int visible_rows_total=16;
//--- Array del ancho de las columnas
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,70);
   width[0]=100;
   width[1]=90;
//--- Array de la alineación del texto en las columnas
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_RIGHT;
   align[1]=ALIGN_LEFT;
   align[2]=ALIGN_RIGHT;
//--- Establecemos las propiedades antes de la creación
   m_canvas_table.XSize(601);
   m_canvas_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_canvas_table.VisibleTableSize(0,visible_rows_total);
   m_canvas_table.TextAlign(align);
   m_canvas_table.ColumnsWidth(width);
   m_canvas_table.GridColor(clrLightGray);
//--- Llenamos la tabla con datos
   for(int c=0; c<COLUMNS1_TOTAL; c++)
      for(int r=0; r<ROWS1_TOTAL; r++)
         m_canvas_table.SetValue(c,r,string(c)+":"+string(r));
//--- Creamos el control
   if(!m_canvas_table.CreateTable(m_chart_id,m_subwin,x,y))
      return(false);
//--- Añadimos el objeto al array común de los grupos de objetos
   CWndContainer::AddToElementsArray(0,m_canvas_table);
   return(true);
  }

La llamada se realiza en el método de la creación de la interfaz gráfica: 

//+------------------------------------------------------------------+
//| Crea el panel de trading                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Creación del formulario 1 para los controles
//--- Creación de controles:
//    Menú principal
//--- Menús contextuales
//--- Creación de la barra de estado
//--- Creación de la tabla dibujada
   if(!CreateCanvasTable())
      return(false);
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Ahora hay que compilar el programa e iniciarlo en el gráfico. El resultado se muestra en la captura de pantalla de abajo:

 Fig. 6. Prueba del control “Tabla dibujada” en el EA.

Fig. 6. Prueba del control “Tabla dibujada” en el EA.

En el quinto capítulo de la primera parte de la serie hemos probado la colocación del formulario en los scripts. Se puede utilizar las tablas sin las barras de desplazamiento en este tipo de aplicaciones MQL. Como ejemplo, añadimos la tabla dibujada al formulario en el script, y vamos a actualizar los datos en ella cada 250 milisegundos. Por favor, añada el código para la creación de la tabla a la clase personalizada, tal como ha sido mostrado anteriormente. Además, al manejador de eventos del script CProgram::OnEvent() hay que añadirle el código, tal como se muestra más abajo. Ahora, los datos en la segunda columna van a cambiarse según el intervalo especificado. 

//+------------------------------------------------------------------+
//| Eventos                                                          |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int milliseconds)
  {
   static int count =0;  // Contador
   string     str   =""; // Fila de encabezado
//--- Formación del encabezado con la marcha del proceso
   switch(count)
     {
      case 0 : str="SCRIPT PANEL";     break;
      case 1 : str="SCRIPT PANEL .";   break;
      case 2 : str="SCRIPT PANEL ..";  break;
      case 3 : str="SCRIPT PANEL ..."; break;
     }
//--- Actualizar la barra del encabezado
   m_window.CaptionText(str);
//--- Cambiar los datos en la primera columna
   for(int r=0; r<13; r++)
      m_canvas_table.SetValue(1,r,string(::rand()));
//--- Visualizar nuevos datos
   m_canvas_table.DrawTable();
//--- Redibujar el gráfico
   m_chart.Redraw();
//--- Establecemos el contador
   count++;
//--- Si es más de tres, resetear
   if(count>3)
      count=0;
//--- Pausa
   ::Sleep(milliseconds);
  }

Por favor, compile el programa e inícielo en el gráfico. El resultado tiene que ser el siguiente: 

 Fig. 7. Prueba del control “Tabla dibujada” en el script.

Fig. 7. Prueba del control “Tabla dibujada” en el script.

Hemos terminado el desarrollo de la clase CCanvasTable para la creación de la tabla dibujada. Ahora se puede hacer un resumen intermedio. 

 


Conclusión

En este artículo han sido demostradas tres clases para la creación de los controles de la interfaz tan importantes como las “Tablas”. Cada una de estas clases tiene sus posibilidades únicas, cada una de las cuales conviene para la mejor solución de determinadas tareas. Por ejemplo, la clase CTable permite crear una tabla con los campos que se puede editar, pudiendo formatear la tabla dándole un aspecto más amigable y más comprensible para el usuario final. Usando la clase CCanvasTable para crear la tabla, Usted no se enfrentará con el problema de limitación del número de caracteres en las celdas, y la tecnología del desplazamiento de la tabla en el área de visibilidad de la imagen permite establecer el ancho diferente para las columnas. No son versiones definitivas de las tablas. Se puede y se debe seguir desarrollándolas.

En el segundo artículo demostraremos las descripciones detalladas de dos clases para la creación de los controles “Pestañas” que también serán útiles con mucha frecuencia durante el diseño de las interfaces gráficas.

Más abajo puede descargar el material de la séptima parte de la serie para poder probar cómo funciona todo eso. 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 listados más abajo, o bien hacer su pregunta en los comentarios para el artículo.

Lista de artículos (capítulos) de la séptima parte: