English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas X: Nuevas posibilidades para la tabla dibujada (build 9)

Interfaces gráficas X: Nuevas posibilidades para la tabla dibujada (build 9)

MetaTrader 5Ejemplos | 27 febrero 2017, 09:38
970 0
Anatoli Kazharski
Anatoli Kazharski

Índice


Introducción

El primer artículo de la serie nos cuenta con más detalles para qué sirve esta librería: Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1). Al final de cada artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede descargar la versión completa de la librería en la fase actual del desarrollo del proyecto. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.

Hasta este momento, el tipo más desarrollado de las tablas en nuestra librería fue el tipo CTable. Esta tabla se reúne de los campos de edición tipo OBJ_EDIT y su desarrollo posterior ya resulta problemático. Por ejemplo, resulta complicado implementar el cambio manual del ancho de las columnas con la captura de los encabezados, es que no podemos controlar el área de la visibilidad de los objetos gráficos separados en la tabla. Aquí ya hemos alcanzado el límite de nuestras posibilidades.

Por eso, en esta fase del desarrollo de nuestra librería, el diseño de una tabla dibujada tipo CCanvasTable resulta más prometedor. Usted puede leer sobre las versiones anteriores y las actualizaciones de la tabla dibujada en los siguientes artículos:

En la captura de pantalla de abajo, se muestra la apariencia de la última versión de la tabla dibujada en la que hemos parado.  Como podemos observar, es absolutamente inerte en este momento. Simplemente es una cuadrícula compuesta de las celdas de la tabla con los datos dentro de ellas. Podemos indicar el modo de alineación para las celdas. Además de las barras de desplazamiento y el modo del ajuste automático de los tamaños al formulario, en esta tabla no hay ninguna otra interactividad.

 Fig. 1. Versión anterior de la tabla dibujada.

Fig. 1. Versión anterior de la tabla dibujada.

Vamos a solucionar esta situación y completar nuestra tabla dibujada con nuevas posibilidades. En esta actualización, nos ocuparemos de las siguientes funciones:

  • Formateo en el estilo «Cebra»
  • Seleccionar una fila de la tabla y quitar la selección al volver a pulsarla
  • Han sido agregados los encabezados para las columnas con posibilidad de cambiar su color al situar el cursor encima y hacer clic en ellos
  • Ajuste automático del texto al ancho de las columnas en caso si en la celda falta espacio
  • Posibilidad de cambiar el ancho de los encabezados de cada columna agarrando y arrastrando su borde

Formateo en el estilo «Cebra»

En uno de los últimos artículos, hemos añadido el modo «Cebra» a la tabla CTable. Este modo permite orientarse mejor en una tabla que contiene muchas celdas. Vamos a implementar este modo también en una tabla dibujada.

Para activar el modo, usamos el método CCanvasTable::IsZebraFormatRows(). Le pasaremos el segundo color del estilo, y el color común de las celdas nos servirá del primer color.

//+------------------------------------------------------------------+
//| Clase par crear la tabla dibujada                                |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- Modo del formateo en el estilo «Cebra»
   color             m_is_zebra_format_rows;
   //---
public:
   //--- Formateo en el estilo «Cebra»
   void              IsZebraFormatRows(const color clr)   { m_is_zebra_format_rows=clr;      }
  };

Los métodos para la visualización de este estilo son diferentes en las tablas de diferentes tipos. En caso de CCanvasTable, si usamos el modo habitual, el fondo de la tabla (lienzo para el dibujo) se colorea completamente con el color común de las celdas. Al ser activado el modo «Cebra», se inicia el trabajo del ciclo. En cada de sus iteraciones se calculan las coordenadas para cada fila, y las áreas se colorean consecutivamente con dos colores. De eso se encarga el método FillRectangle() que se utiliza para dibujar los rectángulos coloreados

class CCanvasTable : public CElement
  {
public:
   //--- Dibuja el fondo de las filas de la tabla
   void              DrawRows(void);
  };
//+------------------------------------------------------------------+
//| Dibuja el fondo de las filas de la tabla                         |
//+------------------------------------------------------------------+
void CCanvasTable::DrawRows(void)
  {
//--- Si el modo del formateo en el estilo «Cebra» está desactivado
   if(m_is_zebra_format_rows==clrNONE)
     {
      //--- Colorear el lienzo con un color
      m_table.Erase(::ColorToARGB(m_cell_color));
      return;
     }
//--- Coordenadas de los encabezados
   int x1=0,x2=m_table_x_size;
   int y1=0,y2=0;
//--- Formateo en el estilo «Cebra»
   for(int r=0; r<m_rows_total; r++)
     {
      //--- Cálculo de coordenadas
      y1=(r*m_cell_y_size)-r;
      y2=y1+m_cell_y_size;
      //--- Color de la fila
      uint clr=::ColorToARGB((r%2!=0)? m_is_zebra_format_rows : m_cell_color);
      //--- Dibujar el fondo de la fila
      m_table.FillRectangle(x1,y1,x2,y2,clr);
     }
  }

Usted puede establecer el color de las filas que quiera. Como resultado, la tabla dibujada en el modo «Cebra» tendrá el siguiente aspecto:

 Fig. 2. Tabla dibujada en el modo del formateo en el estilo «Cebra».

Fig. 2. Tabla dibujada en el modo del formateo en el estilo «Cebra». 

 


Seleccionar y quitar la selección de las filas de la tabla

Para seleccionar una fila, nos harán falta los campos adicionales y los métodos para almacenar y establecer los siguientes parámetros:

  • Colores del fondo y del texto de la fila seleccionada
  • Indice y texto

class CCanvasTable : public CElement
  {
private:
   //--- Color del (1) fondo y del (2) texto de la fila seleccionada
   color             m_selected_row_color;
   color             m_selected_row_text_color;
   //--- (1) Índice y (2) texto de la fila seleccionada
   int               m_selected_item;
   string            m_selected_item_text;
   //---
public:
   //--- Devuelve el (1) índice y el (2) texto de la fila seleccionada en la tabla
   int               SelectedItem(void)             const { return(m_selected_item);         }
   string            SelectedItemText(void)         const { return(m_selected_item_text);    }
   //---
private:
   //--- Dibuja el fondo de las filas de la tabla
   void              DrawRows(void);
  };

Usando el método CCanvasTable::SelectableRow(), se puede activar/desactivar el modo de selección de la fila:

class CCanvasTable : public CElement
  {
private:
   //--- Modo de la línea seleccionada
   bool              m_selectable_row;
   //---
public:
   //--- Selección de la fila
   void              SelectableRow(const bool flag)       { m_selectable_row=flag;           }
  };

Para seleccionar una fila, hace falta un método separado para el dibujo del área especificada por el usuario. Abajo se muestra el código del método CCanvasTable::DrawSelectedRow(). Ahí se calculan las coordenadas para el área seleccionada en el lienzo, según las cuales se dibuja el rectángulo coloreado

class CCanvasTable : public CElement
  {
private:
   //--- Dibuja la fila seleccionada
   void              DrawSelectedRow(void);
  };
//+------------------------------------------------------------------+
//| Dibuja la fila seleccionada                                      |
//+------------------------------------------------------------------+
void CCanvasTable::DrawSelectedRow(void)
  {
//--- Establecemos las coordenadas iniciales para comprobar la condición
   int y_offset=(m_selected_item*m_cell_y_size)-m_selected_item;
//--- Coordenadas
   int x1=0,x2=0,y1=0,y2=0;
//---
   x1=0;
   y1=y_offset;
   x2=m_table_x_size;
   y2=y_offset+m_cell_y_size-1;
//--- Dibujar el rectángulo con el relleno
   m_table.FillRectangle(x1,y1,x2,y2,::ColorToARGB(m_selected_row_color));
  }

Al redibujar el texto, se usa el método auxiliar CCanvasTable::TextColor() que determina el color del texto en las celdas: 

class CCanvasTable : public CElement
  {
private:
   //--- Devuelve el color del texto de la celda
   uint              TextColor(const int row_index);
  };
//+------------------------------------------------------------------+
//| Devuelve el color del texto de la celda                          |
//+------------------------------------------------------------------+
uint CCanvasTable::TextColor(const int row_index)
  {
   uint clr=::ColorToARGB((row_index==m_selected_item)? m_selected_row_text_color : m_cell_text_color);
//--- Devolver el color del encabezado
   return(clr);
  }

Para seleccionar una fila de la tabla, hay que hacer clic izquierdo en ella. Para eso vamos a necesitar el método CCanvasTable::OnClickTable() que va a invocarse en el manejador de eventos del control por el identificador CHARTEVENT_OBJECT_CLICK.

Al principio del método, hay que superar algunas comprobaciones. El programa saldrá del método si:

  • el modo de selección de la fila está desactivado;
  • la barra de desplazamiento se encuentra en estado activo;
  • el clic no ha sido hecho en la tabla. 

Si las comprobaciones han sido superadas, es necesario obtener el desplazamiento actual desde el punto extremo del lienzo y la coordenada Y del cursor para calcular la coordenada de la pulsación. Después de eso, determinamos en el ciclo la fila de la pulsación. Luego, una vez encontrada esta fila, hay que comprobar si está seleccionada en este momento, y si es así, quitar la selección. Si la fila se selecciona, hay que guardar su índice y el texto de la primera columna. Después de terminar la búsqueda de la fila en el ciclo, la tabla se redibuja. Se envía el mensaje que incluye lo siguiente:

  • identificador del evento ON_CLICK_LIST_ITEM
  • identificador del control
  • índice de la fila seleccionada
  • texto de la fila seleccionada. 
class CCanvasTable : public CElement
  {
private:
   //--- Procesamiento del clic en el control
   bool              OnClickTable(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Procesamiento del clic en el control                             |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickTable(const string clicked_object)
  {
//--- Salir si el modo de selección de la fila está desactivado
   if(!m_selectable_row)
      return(false);
//--- Salimos si la barra de desplazamiento se encuentra en modo activo
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Salimos si el nombre del objeto no coincide
   if(m_table.Name()!=clicked_object)
      return(false);
//--- Obtenemos el desplazamiento por los ejes X y Y
   int xoffset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int yoffset=(int)m_table.GetInteger(OBJPROP_YOFFSET);

//--- Determinamos las coordenadas para el campo de edición debajo del cursor del ratón
   int y=m_mouse.Y()-m_table.Y()+yoffset;
//--- Determinamos la fila que ha sido pulsada
   for(int r=0; r<m_rows_total; r++)
     {
      //--- Establecemos las coordenadas iniciales para comprobar la condición
      int y_offset=(r*m_cell_y_size)-r;
      //--- Comprobación de la condición en el eje Y
      bool y_pos_check=(y>=y_offset && y<y_offset+m_cell_y_size);

      //--- Si la pulsación ha sido hecha en esta línea, vamos a la siguiente
      if(!y_pos_check)
         continue;
      //--- Si hemos pulsado en una fila ya seleccionada, quitar la selección
      if(r==m_selected_item)
        {
         m_selected_item      =WRONG_VALUE;
         m_selected_item_text ="";
         break;
        }
      //--- Guardamos el índice de la fila
      m_selected_item      =r;
      m_selected_item_text =m_vcolumns[0].m_vrows[r];
      break;
     }
//--- Dibujar la tabla
   DrawTable();
//--- Enviamos el mensaje sobre ello
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElementBase::Id(),m_selected_item,m_selected_item_text);
   return(true);
  }

La tabla dibujada con una fila seleccionada es así:

Fig. 3. Demostración de la selección y deselección de una fila en la tabla dibujada.

Fig. 3. Demostración de la selección y deselección de una fila en la tabla dibujada. 

 

Encabezados para las columnas

Cualquier tabla sin encabezados de las columnas parece a una pieza bruta. En este tipo de las tablas también vamos a dibujar los encabezados, pero lo haremos en un lienzo separado. Para eso, en la clase CCanvasTable se incluye otra instancia de la clase CRectCanvas y se crea un método separado para la creación del lienzo. No vamos a mostrar aquí el código de este método: es casi el mismo que el método para la creación de la tabla. Su diferencia consiste en los tamaños que establecemos y en la ubicación del objeto.

class CCanvasTable : public CElement
  {
private:
   //--- Objetos para crear la tabla
   CRectCanvas       m_headers;
   //---
private:
   bool              CreateHeaders(void);
  };

Ahora, veremos las propiedades referentes a los encabezados de las columnas. Se puede configurarlas antes de crear la tabla.

  • Modo de visualización de los encabezados de la tabla.
  • Tamaño (alto) de los encabezados.
  • Color del fondo de los encabezados en diferentes estados.
  • Color del texto de encabezados.

Campos y métodos referentes a estas propiedades: 

class CCanvasTable : public CElement
  {
private:
   //--- Modo de visualización de los encabezados de la tabla
   bool              m_show_headers;
   //--- Tamaño (alto) de los encabezados
   int               m_header_y_size;
   //--- Color de los encabezados (fondo) en diferentes estados
   color             m_headers_color;
   color             m_headers_color_hover;
   color             m_headers_color_pressed;
   //--- Color del texto de encabezados
   color             m_headers_text_color;
   //---
public:
   //--- (1) Modo de visualización de los encabezados, alto de los (2) encabezados
   void              ShowHeaders(const bool flag)         { m_show_headers=flag;             }
   void              HeaderYSize(const int y_size)        { m_header_y_size=y_size;          }
   //--- Colores del (1) fondo y del (2) texto de los encabezados
   void              HeadersColor(const color clr)        { m_headers_color=clr;             }
   void              HeadersColorHover(const color clr)   { m_headers_color_hover=clr;       }
   void              HeadersColorPressed(const color clr) { m_headers_color_pressed=clr;     }
   void              HeadersTextColor(const color clr)    { m_headers_text_color=clr;        }
  };

Necesitamos un método que  va a establecer los nombres de los encabezados. Aparte de eso, necesitaremos un array en el que van a almacenarse estos valores. El tamaño de este array será igual al número de las columnas y va a establecerse en el mismo método CCanvasTable::TableSize(), durante el establecimiento de los tamaños de la tabla. 

class CCanvasTable : public CElement
  {
private:
   //--- Texto de los encabezados
   string            m_header_text[];
   //---
public:
  //--- Establecimiento del texto en el encabezado indicado
   void              SetHeaderText(const int column_index,const string value);
  };
//+------------------------------------------------------------------+
//| Rellena el array de encabezados según el índice especificado     |
//+------------------------------------------------------------------+
void CCanvasTable::SetHeaderText(const uint column_index,const string value)
  {
//--- Comprobar la superación del rango de las columnas
   uint csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index>=csize)
      return;
//--- Colocar el valor en el array
   m_header_text[column_index]=value;
  }

La alineación del texto en las celdas y encabezados se hará a través del método común CCanvasTable::TextAlign(). El modo de alineación en las celdas de la tabla y en los encabezados coincide por el eje X, mientras que, en caso del eje Y, este modo se establece con un valor pasado. En esta versión, el texto en los encabezados va a centrarse por el eje YTA_VCENTER, y en las celdas habrá un margen ajustable desde el borde superior de la celda— TA_TOP

class CCanvasTable : public CElement
  {
private:
   //--- Devuelve el modo de alineación del texto en la columna especificada
   uint              TextAlign(const int column_index,const uint anchor);
  };
//+---------------------------------------------------------------------+
//| Devuelve el modo de alineación del texto en la columna especificada |
//+---------------------------------------------------------------------+
uint CCanvasTable::TextAlign(const int column_index,const uint anchor)
  {
   uint text_align=0;
//--- Alineación del texto para la columna actual
   switch(m_vcolumns[column_index].m_text_align)
     {
      case ALIGN_CENTER :
         text_align=TA_CENTER|anchor;
         break;
      case ALIGN_RIGHT :
         text_align=TA_RIGHT|anchor;
         break;
      case ALIGN_LEFT :
         text_align=TA_LEFT|anchor;
         break;
     }
//--- Devolver el modo de alineación
   return(text_align);
  }

En muchas tablas del entorno operativo, la imagen del puntero cambia si el cursor se sitúa sobre la línea entre dos encabezados. En la captura de pantalla de abajo, esta situación se muestra a través del ejemplo de la tabla en la ventana «Caja de herramientas» de la plataforma comercial MetaTrader 5. Si pinchamos en este puntero, se activará el modo de modificación del ancho de la columna. El color del fondo del encabezado de esta columna cambiará.

Fig. 4. Puntero del cursor al ser situado sobre la línea entre los encabezados.

Fig. 4. Puntero del cursor al ser situado sobre la línea entre los encabezados.

 

Prepararemos la misma imagen para nuestra librería. Usted puede descargar el archivo adjunto al artículo que contiene la carpeta con todas las imágenes de los controles de la librería. Añadimos nuevos identificadores en la enumeración de los punteros ENUM_MOUSE_POINTER en el archivo Enums.mqh para el cambio de los tamaños en los ejes X y Y

//+------------------------------------------------------------------+
//| Enumeración de tipos de punteros                                 |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM            =0,
   MP_X_RESIZE          =1,
   MP_Y_RESIZE          =2,
   MP_XY1_RESIZE        =3,
   MP_XY2_RESIZE        =4,
   MP_X_RESIZE_RELATIVE =5,
   MP_Y_RESIZE_RELATIVE =6,

   MP_X_SCROLL          =7,
   MP_Y_SCROLL          =8,
   MP_TEXT_SELECT       =9
  };

Hay que introducir adiciones correspondientes en la clase CPointer, para que este tipo del puntero esté disponible para el uso en las clases de los controles. 

//+------------------------------------------------------------------+
//|                                                      Pointer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Recursos
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp"

//+------------------------------------------------------------------+
//| Clase para crear el puntero del cursor del ratón                 |
//+------------------------------------------------------------------+
class CPointer : public CElement
  {
private:
   //--- Establecer las imágenes para el puntero del cursor
   void              SetPointerBmp(void);
  };
//+------------------------------------------------------------------+
//| Establecer las imágenes para el puntero según el tipo del puntero|
//+------------------------------------------------------------------+
void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      ...
      case MP_X_RESIZE_RELATIVE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp";
         break;
      case MP_Y_RESIZE_RELATIVE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp";
         break;

      ...
     }
//--- Si ha sido especificado el tipo personalizado (MP_CUSTOM)
   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > Para el puntero del cursor hay que establecer ambas imágenes"
);   }

Además, aquí vamos a necesitar campos adicionales:

  • Para determinar el momento de la captación del borde del encabezado.
  • Para determinar el momento cuando el cursor pase del área de un encabezado al área del otro. Es necesario para ahorrar los recursos, para que los encabezados se redibujen exclusivamente en el momento de la intersección de los límites de áreas adyacentes. 

class CCanvasTable : public CElement
  {
private:
   //--- Para determinar el momento cuando el cursor pasa de un encabezado a otro
   int               m_prev_header_index_focus;
   //--- Estado de la captura del borde del encabezado para cambiar el ancho de la columna
   int               m_column_resize_control;
  };

El método CCanvasTable::HeaderColorCurrent() permite obtener el color actual para el encabezado, dependiendo del modo actual, posición del cursor y el estado del botón izquierdo del ratón. El foco sobre el encabezado va a determinarse en el método CCanvasTable::DrawHeaders() destinado para dibujar el fondo de los encabezados, y va a pasarse acá como resultado de la comprobación.

class CCanvasTable : public CElement
  {
private:
   //--- Devuelve el color actual del fondo del encabezado
   uint              HeaderColorCurrent(const bool is_header_focus);
  };
//+------------------------------------------------------------------+
//| Devuelve el color actual del fondo del encabezado                |
//+------------------------------------------------------------------+
uint CCanvasTable::HeaderColorCurrent(const bool is_header_focus)
  {
   uint clr=clrNONE;
//--- Si no hay foco
   if(!is_header_focus || !m_headers.MouseFocus())
      clr=m_headers_color;
   else
     {
      //--- Si el botón izquierdo del ratón se mantiene pulsado y no estamos en el proceso del cambio del ancho de la columna
      bool condition=(m_mouse.LeftButtonState() && m_column_resize_control==WRONG_VALUE);
      clr=(condition)? m_headers_color_pressed : m_headers_color_hover;
     }
//--- Devolver el color del encabezado
   return(::ColorToARGB(clr));
  }

El código del método CCanvasTable::DrawHeaders() se muestra más abajo. Aquí, si el cursor del ratón no se encuentra en el área de encabezados, el lienzo entero se colorea con el color especificado. Si el foco está sobre los encabezados, es necesario determinar sobre cuál de ellos se encuentra exactamente. Para eso hay que determinar las coordenadas relativas del cursor, y luego, calculando en el ciclo las coordenadas de cada encabezado, determinamos si se encuentra en el foco o no. Aparte de eso, aquí se toma en consideración el modo del cambio del ancho de las columnas. Para este modo, en los cálculos se utiliza el margen adicional. Si el foco ha sido encontrado, hay que recordar el índice de la columna

class CCanvasTable : public CElement
  {
private:
   //--- Margen desde los límites de las líneas separadoras para mostrar el puntero del ratón en el modo del cambio del ancho de columnas
   int               m_sep_x_offset;
   //---
private:
   //--- Dibuja los encabezados
   void              DrawHeaders(void);
  };
//+------------------------------------------------------------------+
//| Dibuja el fondo de encabezados                                   |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeaders(void)
  {
//--- Si no se encuentra en el foco, hay que resetear el color de encabezados
   if(!m_headers.MouseFocus())
     {
      m_headers.Erase(::ColorToARGB(m_headers_color));
      return;
     }
//--- Para comprobar el foco sobre los encabezados
   bool is_header_focus=false;
//--- Coordenadas del cursor del ratón
   int x=0;
//--- Coordenadas
   int x1=0,x2=0,y1=0,y2=m_header_y_size;
//--- Obtenemos las coordenadas relativas del cursor del ratón
   if(::CheckPointer(m_mouse)!=POINTER_INVALID)
     {
      //--- Obtenemos el desplazamiento por el eje X
      int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
      //--- Determinamos las coordenadas del cursor del ratón
      x=m_mouse.X()-m_headers.X()+xoffset;
     }
//--- Limpiar el fondo de encabezados
   m_headers.Erase(::ColorToARGB(clrNONE,0));
//--- Margen considerando el modo del cambio del ancho de las columnas
   int sep_x_offset=(m_column_resize_mode)? m_sep_x_offset : 0;
//--- Dibujamos el fondo de encabezados
   for(int i=0; i<m_columns_total; i++)
     {
      //--- Calcular las coordenadas
      x2+=m_vcolumns[i].m_width;
      //--- Comprobamos el foco
      if(is_header_focus=x>x1+((i!=0)? sep_x_offset : 0) && x<=x2+sep_x_offset)
         m_prev_header_index_focus=i;

      //--- Dibujar el fondo del encabezado
      m_headers.FillRectangle(x1,y1,x2,y2,HeaderColorCurrent(is_header_focus));
      //--- Calcular el margen para el siguiente encabezado
      x1+=m_vcolumns[i].m_width;
     }
  }

Después de dibujar el fondo de encabezados, hay que dibujar la cuadrícula (bordes de los encabezados). Para eso se utiliza el método CCanvasTable::DrawHeadersGrid(). Primero, se dibuja el borde común, luego en el ciclo, se trazan las líneas separadoras.

class CCanvasTable : public CElement
  {
private:
   //--- Dibuja la cuadrícula de los encabezados de la tabla
   void              DrawHeadersGrid(void);
  };
//+------------------------------------------------------------------+
//| Dibuja la cuadrícula de los encabezados de la tabla              |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeadersGrid(void)
  {
//--- Color de la cuadrícula
   uint clr=::ColorToARGB(m_grid_color);
//--- Coordenadas
   int x1=0,x2=0,y1=0,y2=0;
   x2=m_table_x_size-1;
   y2=m_header_y_size-1;
//--- Dibujar el borde
   m_headers.Rectangle(x1,y1,x2,y2,clr);
//--- Líneas separadoras
   x2=x1=m_vcolumns[0].m_width;
   for(int i=1; i<m_columns_total; i++)
     {
      m_headers.Line(x1,y1,x2,y2,clr);
      x2=x1+=m_vcolumns[i].m_width;
     }
  }

En último lugar, dibujamos el texto de los encabezados. De esta tarea se encarga el método CCanvasTable::DrawHeadersText(). Aquí, hay que recorrer en el ciclo todos los encabezados, determinando la coordenada para el texto y el modo de alineación en cada iteración. La última operación del ciclo introduce el nombre del encabezado. Aquí mismo se utiliza la corrección del texto respecto al ancho de la columna. Para eso se utiliza el método CCanvasTable::CorrectingText(). Lo consideraremos con más detalles en la siguiente sección de este artículo. 

class CCanvasTable : public CElement
  {
private:
  //--- Dibuja el texto de encabezados de la tabla
   void              DrawHeadersText(void);
  };
//+------------------------------------------------------------------+
//| Dibuja el texto de encabezados de la tabla                       |
//+------------------------------------------------------------------+
void CCanvasTable::DrawHeadersText(void)
  {
//--- Para calcular las coordenadas y los márgenes
   int x=0,y=m_header_y_size/2;
   int column_offset =0;
   uint text_align   =0;
//--- Color del texto
   uint clr=::ColorToARGB(m_headers_text_color);
//--- Propiedades de la fuente
   m_headers.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);
//--- Dibujar el texto
   for(int c=0; c<m_columns_total; c++)
     {
       //--- Obtenemos la coordenada X para el texto
      x=TextX(c,column_offset);
       //--- Obtenemos el modo de alineación del texto
      text_align=TextAlign(c,TA_VCENTER);
      //--- Dibujar el nombre de la columna
      m_headers.TextOut(x,y,CorrectingText(c,0,true),clr,text_align);
     }
  }

Todos los métodos mencionados para el dibujo de encabezados se invocan en el método común CCanvasTable::DrawTableHeaders(). Si el modo de visualización de encabezados está desactivado, el acceso a este método queda bloqueado

class CCanvasTable : public CElement
  {
private:
  //--- Dibuja los encabezados de la tabla
   void              DrawTableHeaders(void);
  };
//+------------------------------------------------------------------+
//| Dibuja los encabezados de la tabla                               |
//+------------------------------------------------------------------+
void CCanvasTable::DrawTableHeaders(void)
  {
//--- Salir si los encabezados están desactivados
   if(!m_show_headers)
      return;

//--- Dibuja los encabezados
   DrawHeaders();
//--- Dibujar la cuadrícula
   DrawHeadersGrid();
//--- Dibujar el texto de los encabezados
   DrawHeadersText();
  }

El foco sobre el encabezado se determina con el método CCanvasTable::CheckHeaderFocus(). El programa sale del método en dos ocasiones:

  • si el modo de visualización de encabezados está desactivado
  • o si el proceso del cambio del ancho de las columnas ha comenzado.

Luego, obtenemos las coordenadas relativas del cursor en el lienzo. Buscamos el foco sobre algún encabezado en el ciclo y comprobamos si ha cambiado desde la última llamada a este método. Si registramos un foco nuevo (momento de la intersección de los límites de encabezados), hay que resetear el índice del encabezado guardado anteriormente y detener el ciclo.

class CCanvasTable : public CElement
  {
private:
  //--- Comprobación del foco en el encabezado
   void              CheckHeaderFocus(void);
  };
//+------------------------------------------------------------------+
//| Comprobación del foco en el encabezado                           |
//+------------------------------------------------------------------+
void CCanvasTable::CheckHeaderFocus(void)
  {
//--- Salir si (1) los encabezados están desactivados o (2) el proceso del cambio del ancho de las columnas ha comenzado
   if(!m_show_headers || m_column_resize_control!=WRONG_VALUE)
      return;
//--- Coordenadas de los encabezados
   int x1=0,x2=0;
//--- Obtenemos el desplazamiento por el eje X
   int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
//--- Obtenemos las coordenadas relativas del cursor del ratón
   int x=m_mouse.X()-m_headers.X()+xoffset;
//--- Margen considerando el modo del cambio del ancho de las columnas
   int sep_x_offset=(m_column_resize_mode)? m_sep_x_offset : 0;
//--- Buscamos el foco
   for(int i=0; i<m_columns_total; i++)
     {
      //--- Calcular la coordenada a la derecha
      x2+=m_vcolumns[i].m_width;
      //--- Si el foco del encabezado ha cambiado
      if((x>x1+sep_x_offset && x<=x2+sep_x_offset) && m_prev_header_index_focus!=i)
        {
         m_prev_header_index_focus=WRONG_VALUE;
         break;
        }
      //--- Calcular la coordenada a la izquierda
      x1+=m_vcolumns[i].m_width;
     }
  }

A su vez, los encabezados se redibujan sólo cuando sus límites se cruzan. Eso ahorra los recursos de la CPU. Para esa tarea se utiliza el método CCanvasTable::ChangeHeadersColor(). Aquí el programa sale del método si el modo de visualización de los encabezados está desactivado o el proceso del cambio de su ancho está en marcha. Si las comprobaciones han sido superadas al principio del método, se comprueba el foco sobre los encabezados, y ellos se redibujan.

class CCanvasTable : public CElement
  {
private:
   //--- Cambia el color de encabezados
   void              ChangeHeadersColor(void);
  };
//+------------------------------------------------------------------+
//| Cambio del color de encabezados                                  |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeHeadersColor(void)
  {
//--- Salir si los encabezados están desactivados
   if(!m_show_headers)
      return;
//--- Si el puntero del cursor está activado
   if(m_column_resize.IsVisible() && m_mouse.LeftButtonState())
     {
       //--- Guardamos el índice de la columna capturada
      if(m_column_resize_control==WRONG_VALUE)
         m_column_resize_control=m_prev_header_index_focus;
      //---
      return;
     }
//--- Si no se encuentra en el foco
   if(!m_headers.MouseFocus())
     {
      //--- Si todavía no está indicado que no se encuentra en el foco
      if(m_prev_header_index_focus!=WRONG_VALUE)
        {
         //--- Resetear el foco
         m_prev_header_index_focus=WRONG_VALUE;
         //--- Cambiar el color
         DrawTableHeaders();
         m_headers.Update();
        }
     }
//--- Si se encuentra en el foco
   else
     {
     }   //--- Comprobar el foco sobre los encabezados
      CheckHeaderFocus();
      //--- Si no hay foco
      if(m_prev_header_index_focus==WRONG_VALUE)
        {
         //--- Cambiar el color
         DrawTableHeaders();
         m_headers.Update();
        }
     }
  }

Abajo se muestra el código del método CCanvasTable::CheckColumnResizeFocus(). Es necesario para determinar el foco en las líneas entre los encabezados y se encarga de la muestra/ocultación del cursor para el cambio del ancho de las columnas. Al principio del método hay dos comprobaciones. El programa sale del método si el modo del cambio del ancho de las columnas está desactivado. Si el modo está activado y el proceso del cambio del ancho de la columna está en marcha, hay que actualizar las coordenadas del puntero del cursor del ratón desde el método.

Si el proceso del cambio del ancho de la columna no ha comenzado todavía, entonces si el cursor se encuentra en el área de encabezados, tratamos de determinar en el ciclo el foco sobre el límite de uno de ellos. Si el foco ha sido encontrado, actualizamos las coordenadas del puntero del cursor, hacemos que esté visible y salimos del método. Si el foco no ha sido encontrado, es necesario ocultar el puntero

class CCanvasTable : public CElement
  {
private:
   //--- Comprobación del foco en los límites de encabezados para el cambio de su ancho
   void              CheckColumnResizeFocus(void);
  };
//+--------------------------------------------------------------------------------+
//| Comprobación del foco en los límites de encabezados para el cambio de su ancho |
//+--------------------------------------------------------------------------------+
void CCanvasTable::CheckColumnResizeFocus(void)
  {
//--- Salir si el modo del cambio del ancho de las columnas está desactivado
   if(!m_column_resize_mode)
      return;
//--- Salir si el proceso del cambio del ancho de las columnas ha comenzado
   if(m_column_resize_control!=WRONG_VALUE)
     {
      //--- Actualizar las coordenadas del puntero y hacerlo visible
      m_column_resize.Moving(m_mouse.X(),m_mouse.Y());
      return;
     }
//--- Para comprobar el foco sobre los límites de los encabezados
   bool is_focus=false;
//--- Si el cursor está en el área de encabezados
   if(m_headers.MouseFocus())
     {
      //--- Coordenadas de los encabezados
      int x1=0,x2=0;
      //--- Obtenemos el desplazamiento por el eje X
      int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
      //--- Obtenemos las coordenadas relativas del cursor del ratón
      int x=m_mouse.X()-m_headers.X()+xoffset;
       //--- Buscamos el foco
      for(int i=0; i<m_columns_total; i++)
        {
        //--- Cálculo de coordenadas
         x1=x2+=m_vcolumns[i].m_width;
        //--- Comprobación del foco
         if(is_focus=x>x1-m_sep_x_offset && x<=x2+m_sep_x_offset)
            break;
        }
      //--- Si hay foco
      if(is_focus)
        {
        //--- Actualizar las coordenadas del puntero y hacerlo visible
         m_column_resize.Moving(m_mouse.X(),m_mouse.Y());
        //--- Mostrar el puntero
         m_column_resize.Show();

         return;
        }
     }
//--- Ocultar el puntero si no hay foco
   if(!m_headers.MouseFocus() || !is_focus)
      m_column_resize.Hide();
  }

El resultado es el siguiente:

 Fig. 5. Encabezados para las columnas.

Fig. 5. Encabezados para las columnas.

 

 


Corrección del largo de la línea respecto al ancho de la columna

Anteriormente, para que el texto no se solapase con las celdas contiguas, teníamos que ajustar el ancho de las columnas manualmente y volver a compilar el archivo para ver el resultado. Desde luego, no es muy cómodo.

Hagamos que el largo de la línea se corrija automáticamente si ella no cabe en la celda de la tabla. Las líneas corregidas anteriormente no van a corregirse de nuevo cuando la tabla se redibuja. Para almacenar estas líneas, agregamos un array más en la estructura de las propiedades de la tabla.

class CCanvasTable : public CElement
  {
private:
   //--- Array de valores y propiedades de la tabla
   struct CTOptions
     {
      string            m_vrows[];
      string            m_text[];
      int               m_width;
      ENUM_ALIGN_MODE   m_text_align;
     };
   CTOptions         m_vcolumns[];
  };

Como resultado, en el array m_vrows[] va a almacenarse el texto completo, y el array m_text[] va a contener su versión corregida.

El método CCanvasTable::CorrectingText() se encargará de corregir el largo de la línea, tanto en los encabezados como en las celdas de la tabla. Después de determinar el tipo del texto con el que trabajamos, obtenemos su ancho. Luego, comprobamos si el texto completo de la línea cabe en la celda, tomando en cuenta los márgenes especificados desde los bordes. Si cabe, lo guardamos en el array m_text[] y salimos del método. En esta versión, el texto corregido se guarda sólo para las celdas, y no para los encabezados.

Si el texto no cabe, hay que recortar los caracteres sobrantes y añadir los puntos suspensivos '…' que indicarán que el texto a visualizar está recortado. No es muy complicado implementar eso:

1) Obtenemos el largo de la línea.

Luego, empezando del final de la línea, recorremos en el ciclo todos los caracteres, eliminando el último carácter y guardando el texto ya recortado en la variable temporal.

3) Si ya no quedan caracteres, devolvemos la línea vacía.

4) Hasta que haya caracteres, obtenemos el ancho de la línea resultante tomando en consideración los puntos suspensivos.

5) Comprobamos si la línea en esta forma cabe en la celda de la tabla, tomando en cuenta los márgenes especificados desde los bordes de la celda.

6) Si la línea cabe, la guardamos en la variable global del método y detenemos el ciclo.

7) Después de eso, guardamos la línea corregida en el array m_text[] y la devolvemos del método. 

class CCanvasTable : public CElement
  {
private:
   //--- Devuelve el texto corregido según el ancho de la columna
   string            CorrectingText(const int column_index,const int row_index,const bool headers=false);
  };
//+------------------------------------------------------------------+
//| Devuelve el texto corregido según el ancho de la columna         |
//+------------------------------------------------------------------+
string CCanvasTable::CorrectingText(const int column_index,const int row_index,const bool headers=false)
  {
//--- Obtenemos el texto actual
   string corrected_text=(headers)? m_header_text[column_index]: m_vcolumns[column_index].m_vrows[row_index];
//--- Márgenes desde los bordes de la celda por el eje X
   int x_offset=m_text_x_offset*2;
//--- Obtenemos el puntero al objeto del lienzo
   CRectCanvas *obj=(headers)? ::GetPointer(m_headers) : ::GetPointer(m_table);
//--- Obtenemos el ancho del texto
   int full_text_width=obj.TextWidth(corrected_text);
//--- Si cabemos en la celda, guardamos el texto corregido en un array separado y lo devolvemos
   if(full_text_width<=m_vcolumns[column_index].m_width-x_offset)
     {
      //--- Si no son encabezados, guardamos el texto corregido
      if(!headers)
         m_vcolumns[column_index].m_text[row_index]=corrected_text;
      //---
      return(corrected_text);
     }
//--- Si el texto no se ajusta a la celda, hay que corregirlo (recortar los caracteres sobrantes y añadir puntos suspensivos)
   else
     {
       //--- Para trabajar con la línea
      string temp_text="";
      //--- Obtenemos la longitud de la línea
      int total=::StringLen(corrected_text);
      //--- Vamos a eliminar los caracteres de la línea uno por uno, hasta que consigamos el ancho necesario del texto
      for(int i=total-1; i>=0; i--)
        {
         //--- Eliminamos un carácter
         temp_text=::StringSubstr(corrected_text,0,i);
         //--- Si no queda nada, dejamos la línea en blanco
         if(temp_text=="")
           {
            corrected_text="";
            break;
           }
         //--- Agregamos los puntos suspensivos antes de la comprobación
         int text_width=obj.TextWidth(temp_text+"...");
         //--- Si cabemos en la celda
         if(text_width<m_vcolumns[column_index].m_width-x_offset)
           {
            //--- Guardamos el texto y detenemos el ciclo
            corrected_text=temp_text+"...";
            break;
           }
        }

     }
//--- Si no son encabezados, guardamos el texto corregido
   if(!headers)
      m_vcolumns[column_index].m_text[row_index]=corrected_text;
//--- Devolvemos el texto corregido
   return(corrected_text);
  }

El uso de las líneas corregidas durante el redibujo de la tabla es muy conveniente cuando el proceso del cambio del ancho de la columna está en marcha. En vez de corregir el texto dentro de todas las celdas de la tabla una y otra vez, será suficiente hacerlo solamente dentro de las celdas de la columna que cambia su ancho. Eso ahorra los recursos de la CPU.

El método CCanvasTable::Text() va a determinar si hace falta corregir el texto para la columna especificada o bastará con enviar la versión corregida anteriormente. Su código es el siguiente: 
class CCanvasTable : public CElement
  {
private:
   //--- Devuelve el texto
   string            Text(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Devuelve el texto                                                |
//+------------------------------------------------------------------+
string CCanvasTable::Text(const int column_index,const int row_index)
  {
   string text="";
//--- Corregimos el texto si no es el modo del cambio del ancho de la columna
   if(m_column_resize_control==WRONG_VALUE)
      text=CorrectingText(column_index,row_index);
//--- Si es el modo del cambio del ancho de la columna, entonces...
   else
     {
      //--- ...corregimos el texto sólo para la columna cuyo ancho cambiamos
      if(column_index==m_column_resize_control)
         text=CorrectingText(column_index,row_index);
      //--- Para las demás, usamos el texto corregido anteriormente
      else
         text=m_vcolumns[column_index].m_text[row_index];
     }
//--- Devolvemos el texto
   return(text);
  }

El código del método CCanvasTable::ChangeColumnWidth() destinado para el cambio del ancho de la columna se muestra más abajo.

Establecemos el ancho mínimo de la columna a 30 píxeles. El programa saldrá del método si la visualización de los encabezados está desactivada. Si la comprobación ha sido superada, a continuación se comprueba el foco en los bordes de los encabezados. Si después de esta comprobación resulta que el proceso no ha sido iniciado/terminado, las variables auxiliares se resetean y el programa sale del método. Si el proceso ha sido iniciado, obtenemos la coordenada relativa X del cursor. Si el proceso acaba de empezar, hay que guardar los valores actuales de la coordenada X del cursor (variable x_fixed) y del ancho de la columna capturada (variable prev_width). Las variables locales destinadas para eso son estáticas. Por eso, durante cada entrada en este método, sus valores van a guardarse hasta que no vayan a ser reseteados al final del proceso. 

Ahora calcularemos el nuevo ancho para la columna. Si resulta que el ancho mínimo de la columna ha sido alcanzado, el programa sale del método. De lo contrario, el nuevo ancho se guarda en la estructura de las propiedades de la tabla según la columna especificada. Luego, los tamaños de la tabla se recalculan y se aplican de nuevo, y al final del método ella se redibuja. 

class CCanvasTable : public CElement
  {
private:
   //--- Ancho mínimo para las columnas
   int               m_min_column_width;
   //---
private:
   //--- Cambia el ancho de la columna capturada
   void              ChangeColumnWidth(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) : m_min_column_width(30)
  {
   ...
  }
//+------------------------------------------------------------------+
//| Cambia el ancho de la columna capturada                          |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeColumnWidth(void)
  {
//--- Salir si los encabezados están desactivados
   if(!m_show_headers)
      return;
//--- Comprobación del foco en los límites de encabezados
   CheckColumnResizeFocus();
//--- Variables auxiliares
   static int x_fixed    =0;
   static int prev_width =0;
//--- Si hemos terminado, reseteamos los valores
   if(m_column_resize_control==WRONG_VALUE)
     {
      x_fixed    =0;
      prev_width =0;
      return;
     }
//--- Obtenemos el desplazamiento por el eje X
   int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
//--- Obtenemos las coordenadas relativas del cursor del ratón
   int x=m_mouse.X()-m_headers.X()+xoffset;
//--- Si acabamos de empezar el cambio del ancho de la columna
   if(x_fixed<1)
     {
      //--- Guardamos la coordenada X actual y el ancho de la columna
      x_fixed    =x;
      prev_width =m_vcolumns[m_column_resize_control].m_width;
     }
//--- Calcularemos nuevo ancho para la columna
   int new_width=prev_width+(x-x_fixed);
//--- Dejar sin alterar, si es menos de la restricción establecida
   if(new_width<m_min_column_width)
      return;
//--- Guardamos nuevo ancho de la columna
   m_vcolumns[m_column_resize_control].m_width=new_width;
//--- Calcular los tamaños de la tabla
   CalculateTableSize();
//--- Establecer nuevo tamaño de la tabla
   ChangeTableSize();
//--- Dibujar la tabla
   DrawTable();
  }

Es lo que tenemos al final:

 Fig. 5. Corrección del largo de la línea respecto al ancho de la columna.

Fig. 5. Corrección del largo de la línea respecto al ancho de la columna. 

 


Procesamiento de eventos

La gestión del color de los objetos de la tabla y el cambio del ancho de sus columnas se realiza por el manejador del control cuando ocurre el evento del desplazamiento del cursor (CHARTEVENT_MOUSE_MOVE). 

//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CCanvasTable::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(!CElementBase::IsVisible())
         return;
      //--- Salir si los números de las subventanas no coinciden
      if(!CElementBase::CheckSubwindowNumber())
         return;
      //--- Comprobación del foco sobre los controles
      CElementBase::CheckMouseFocus();
      m_headers.MouseFocus(m_mouse.X()>m_headers.X() && m_mouse.X()<m_headers.X2() &&
                           m_mouse.Y()>m_headers.Y() && m_mouse.Y()<m_headers.Y2());
      //--- Si la barra de desplazamiento se encuentra en acción
      if(m_scrollv.ScrollBarControl() || m_scrollh.ScrollBarControl())
        {
         ShiftTable();
         return;
        }
       //--- Cambio del color de objetos
      ChangeObjectsColor();
       //--- Cambiar el ancho de la columna capturada
      ChangeColumnWidth();
      return;
     }
   ...
  }

Además, vamos a necesitar un nuevo identificador del evento para determinar el momento del cambio del estado del botón izquierdo del ratón. Es necesario para deshacerse de las comprobaciones y procesamientos repetidos en varios bloques del código del manejador. Vamos a añadir nuevo identificador ON_CHANGE_MOUSE_LEFT_BUTTON al archivo Defines.mqh

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CHANGE_MOUSE_LEFT_BUTTON (33) // Cambio del estado del botón izquierdo del ratón

Aparte de eso, a la clase para la obtención de los parámetros actuales del ratón (CMouse) ha sido añadido el método CMouse::CheckChangeLeftButtonState() para determinar el momento del cambio del estado del botón izquierdo del ratón. Este método se invoca en el manejador de la clase. Si el estado del botón izquierdo ha cambiado, desde el método se envía un mensaje con el identificador ON_CHANGE_MOUSE_LEFT_BUTTON. Luego se puede recibir este mensaje y procesarlo en cualquier control.

//+------------------------------------------------------------------+
//| Clase para obtener los parámetros del ratón                      |
//+------------------------------------------------------------------+
class CMouse
  {
private:
   //--- Comprobación del cambio del estado del botón izquierdo del ratón
   bool              CheckChangeLeftButtonState(const string mouse_state);
  };
//+------------------------------------------------------------------+
//| Procesamiento de eventos del desplazamiento del cursor del ratón |
//+------------------------------------------------------------------+
void CMouse::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)
     {
       //--- Coordenadas y el estado del botón izquierdo del ratón
      m_x                 =(int)lparam;
      m_y                 =(int)dparam;
      m_left_button_state =CheckChangeLeftButtonState(sparam);
      ...
     }
  }
//+------------------------------------------------------------------+
//| Comprobación del cambio del estado del botón izquierdo del ratón |
//+------------------------------------------------------------------+
bool CMouse::CheckChangeLeftButtonState(const string mouse_state)
  {
   bool left_button_state=(bool)int(mouse_state);
//--- Enviamos el mensaje sobre el cambio del estado del botón izquierdo del ratón
   if(m_left_button_state!=left_button_state)
      ::EventChartCustom(m_chart.ChartId(),ON_CHANGE_MOUSE_LEFT_BUTTON,0,0.0,"");
//---
   return(left_button_state);
  }

En la clase CCanvasTable, el procesamiento del evento con el identificador ON_CHANGE_MOUSE_LEFT_BUTTON es necesario para lo siguiente:

  •  para anular algunos campos de la clase;
  •  para corregir las barras de desplazamiento
  •  para redibujar la tabla 
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Cambio del estado del botón izquierdo del ratón
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON)
     {
      //--- Salir si los encabezados están desactivados
      if(!m_show_headers)
         return;
      //--- Si el botón izquierdo del ratón está suelto
      if(!m_mouse.LeftButtonState())
        {
         //--- Resetar el modo del cambio del ancho
         m_column_resize_control=WRONG_VALUE;
        //--- Ocultar el puntero
         m_column_resize.Hide();
        //--- Corregimos la barra de desplazamiento de acuerdo con los últimos cambios
         HorizontalScrolling(m_scrollh.CurrentPos());
        }
      //--- Resetear el índice del último foco del encabezado
      m_prev_header_index_focus=WRONG_VALUE;
       //--- Cambio del color de objetos
      ChangeObjectsColor();
     }
  }

El resultado del trabajo de la aplicación MQL ha sido demostrado en las capturas animadas de este artículo. Usted puede descargar esta aplicación a su ordenador para analizarla más detalladamente.

 

Conclusión

En esta actualización de la librería, hemos mejorado la tabla dibujada tipo CCanvasTable. No es la versión definitiva de la tabla, continuaremos el trabajo con ella y añadiremos otras posibilidades nuevas.

A continuación, se muestra el esquema de la librería para la creación de interfaces gráficas.

 Fig. 6. Estructura de la librería en la fase actual del desarrollo.

Fig. 6. Estructura de la librería en la fase actual del desarrollo.

 

Abajo puede descargar la última versión de la librería y los archivos para las pruebas.

Si le surgen algunas preguntas sobre el uso del material de estos archivos, puede dirigirse a la descripción detallada del proceso de desarrollo de la librería en uno de los artículos de esta serie, o bien hacer su pregunta en los comentarios para el artículo. 

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/3030

Archivos adjuntos |
Modelado 3D en MQL5 Modelado 3D en MQL5
Una serie temporal es un sistema dinámico en el que los valores de una cierta magnitud aleatoria llegan de forma consecutiva: ininterrumpidamente o tras un cierto intervalo temporal. El paso del análisis plano del mercado al análisis con volumen permitirá mirar de una forma nueva a los complejos procesos y manifestaciones que interesan al investigador. En el artículo se describen las funciones de visualización de la representación 3-D de datos bidimensionales.
Canal universal con interfaz gráfica Canal universal con interfaz gráfica
Todos los indicadores de canales están constituidos por tres líneas: una central, una superior y otra inferior. Según su principio de construcción la línea central es idéntica a una media móvil, y en la mayoría de los casos para construir el canal se usa precisamente una media móvil. Las líneas superior e inferior se ubican a la misma distancia de la central. Esta distancia se puede definir simplemente en puntos, en tanto por ciento del precio (indicador Envelopes), se pueden usar valores de desviación estándar (franjas de Bollinger) o los valores del indicador ATR (canal de Keltner).
Interfaces gráficas X: Actualizaciones para la tabla dibujada y optimización del código (build 10) Interfaces gráficas X: Actualizaciones para la tabla dibujada y optimización del código (build 10)
Continuamos completar la tabla dibujada (CCanvasTable) con nuevas funcionalidades. Ahora la tabla va a contener las siguientes funciones: resalto de las filas al situar el cursor encima; posibilidad de agregar el array de imágenes para cada celda y el método para su conmutación; posibilidad de establecer y editar el texto de las cceldas durante la ejecución del programa, y muchas cosas más.
Interfaces gráficas X: Control "Campo de edición del texto multilínea" (build 8) Interfaces gráficas X: Control "Campo de edición del texto multilínea" (build 8)
Se considera el control «Campo de edición multilínea». A diferencia del objeto gráfico OBJ_EDIT, en esta versión no habrá limitación alguna para el número de los caracteres a introducir. Aparte de eso, se hace disponible el modo cuando el campo de edición se convierte en un sencillo editor de texto donde se puede mover el cursor usando el ratón o el teclado.