Interfaces gráficas XI: Campos de edición y combobox en las celdas de la tabla (build 15)

Anatoli Kazharski | 7 septiembre, 2017


Contenido

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). A cada artículo se le adjunta el archivo comprimido con la versión completa de la librería en la fase actual del desarrollo. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.

En la presente actualización, vamos a ocuparnos del control «Tabla» (clase CTable). Antes, apareció la posibilidad de añadir las casillas de verificación (checkbox) y los botones a las celdas de la tabla. Vamos a ampliar la gama de estos controles con los campos de edición y las listas combinadas (combobox). Además, añadiremos la posibilidad de controlar las dimensiones de la ventana durante la ejecución de la aplicación.

Controlando las dimensiones de la ventana

Para usar cómodamente las listas, tablas o los campos de edición multilínea, a menudo es necesario reducir el tamaño de la ventana o maximizarla hasta el tamaño del gráfico. Hay varias formas para manejar el tamaño de la ventana.

  • El modo se conmuta rápidamente del habitual al de pantalla completa y viceversa con un clic en un botón especial.
  • El doble clic en el encabezado de la ventana también extiende la ventana a la pantalla completa. El doble clic repetido devuelve la ventana a su estado anterior.
  • Podemos cambiar el tamaño de la ventana si agarramos sus bordes con el botón izquierdo del ratón y los arrastramos.

Veremos cómo está implementado todo eso en la librería. 

Para crear el botón de pantalla completa, ha sido declarada la instancia de la clase CButton. Para obtener el puntero al botón, se usa el método público CButton::GetFullscreenButtonPointer(). Por defecto, este botón se encuentra desactivado en el formulario. Para activarlo, utilice el método CButton::FullscreenButtonIsUsed(). 

//+------------------------------------------------------------------+
//| Clase del formulario para los controles                          |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   //--- Objetos para crear el formulario
   CButton           m_button_fullscreen;
   //--- Presencia del botón para ir al modo de pantalla completa
   bool              m_fullscreen_button;
   //---
public:
   //--- Devuelve los punteros a los botones del formulario
   CButton          *GetFullscreenButtonPointer(void)                { return(::GetPointer(m_button_fullscreen)); }
   //--- Usar el botón de pantalla completa
   void              FullscreenButtonIsUsed(const bool state)        { m_fullscreen_button=state;                 }
   bool              FullscreenButtonIsUsed(void)              const { return(m_fullscreen_button);               }
  };

El botón del modo de pantalla completa se crea en el método común CWindow::CreateButtons() (véase el código de abajo), donde se crean todos los botones incluidos del formulario. Igual como los botones para mostrar las descripciones emergentes y minimizar el formulario, el botón del modo de pantalla completa puede ser usado sólo en la ventana principal. 

//+------------------------------------------------------------------+
//| Crea los botones en el formulario                                |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"
//---
bool CWindow::CreateButtons(void)
  {
//--- Salimos si el tipo del programa es «Script»
   if(CElementBase::ProgramType()==PROGRAM_SCRIPT)
      return(true);
//--- Contador, tamaño, cantidad
   int i=0,x_size=20;
   int buttons_total=4;
//--- Ruta hacia el archivo
   string icon_file="";
//--- Excepción en el área de la captura
   m_right_limit=0;
//---
   CButton *button_obj=NULL;
//---
   for(int b=0; b<buttons_total; b++)
     {
      ...
      else if(b==1)
        {
         m_button_fullscreen.MainPointer(this);
         //--- Salimos si (1) el botón no está activado o (2) es la ventana de diálogo
         if(!m_fullscreen_button || m_window_type==W_DIALOG)
            continue;
         //---
         button_obj=::GetPointer(m_button_fullscreen);
         icon_file="Images\\EasyAndFastGUI\\Controls\\full_screen.bmp";
        }
      ...
     }
//---
   return(true);
  }

Los tamaños mínimos de la ventana se establecen automáticamente, siendo iguales a los tamaños que han sido especificados durante la creación del control. Pero podemos redefinir estos valores. En la versión actual, no se puede establecer los tamaños de la ventana menos de 200x200 píxeles. Eso se controla en el método de la inicialización de las propiedades del control, CWindow::InitializeProperties(). 

class CWindow : public CElement
  {
private:
   //--- Tamaños mínimos de la ventana
   int               m_minimum_x_size;
   int               m_minimum_y_size;
   //---
public:
   //--- Establecimiento de los tamaños mínimos de la ventana
   void              MinimumXSize(const int x_size)                  { m_minimum_x_size=x_size;                   }
   void              MinimumYSize(const int y_size)                  { m_minimum_y_size=y_size;                   }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_minimum_x_size(0),
                         m_minimum_y_size(0)
  {
...
  }
//+------------------------------------------------------------------+
//| Inicialización de las propiedades                                |
//+------------------------------------------------------------------+
void CWindow::InitializeProperties(const long chart_id,const int subwin,const string caption_text,const int x_gap,const int y_gap)
  {
...
   m_x_size         =(m_x_size<1)? 200 : m_x_size;
   m_y_size         =(m_y_size<1)? 200 : m_y_size;
...
   m_minimum_x_size =(m_minimum_x_size<200)? m_x_size : m_minimum_x_size;
   m_minimum_y_size =(m_minimum_y_size<200)? m_y_size : m_minimum_y_size;
...
  }

Antes de cambiar los tamaños durante la expansión de la ventana a la pantalla completa, hay que guardar las dimensiones actuales, coordenadas y los modos del cambio automático de los tamaños, si éstos han sido establecidos. Estos valores se almacenan en los campos privados especiales de la clase:

class CWindow : public CElement
  {
private:
   //--- Últimas coordenadas y tamaños de la ventana antes de pasar hacia las dimensiones de la pantalla completa
   int               m_last_x;
   int               m_last_y;
   int               m_last_x_size;
   int               m_last_y_size;
   bool              m_last_auto_xresize;
   bool              m_last_auto_yresize;
  };

Para el procesamiento del clic en el botón del modo de pantalla completa, se usa el método CWindow::OnClickFullScreenButton(). Ahí, primero, se verifica el identificador y el índice del control, y luego, el código se divide e dos bloques:

  • Si la ventana no está expandida en este momento, la pasamos al modo de pantalla completa. Luego, obtenemos los tamaños actuales del gráfico, guardamos los tamaños actuales, las coordenadas de la ventana y los modos del cambio automático de los tamaños en los campos especiales de la clase. Puesto que en el modo de pantalla completa es necesario que el formulario cambie sus tamaños automáticamente cuando se cambian los tamaños del gráfico principal, hay que activar los modos del cambio automático de los tamaños. Después de eso, se establecen los tamaños de la ventana. La ubicación de la ventana se establece en la esquina superior izquierda con el fin de llenar el espacio completo del gráfico. El icono en el botón se cambia por otro.
  • Si la ventana está expandida en este momento, la pasamos al tamaño anterior de la ventana. Aquí, los modos del cambio automático de los tamaños de la ventana se pasan al estado anterior. Luego, dependiendo del modo que está deshabilitado, se establece el tamaño anterior de la ventana. También se establece la posición anterior y la imagen correspondiente para el botón.

Al final del método CWindow::OnClickFullScreenButton(), el formulario se redibuja:

class CWindow : public CElement
  {
private:
   //--- Estatus de la ventana en modo de pantalla completa
   bool              m_is_fullscreen;
   //---
public:
   //--- Al tamaño de pantalla completa o al tamaño anterior de la ventana
   bool              OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE);
  };
//+--------------------------------------------------------------------------------+
//| Al tamaño de pantalla completa o al tamaño anterior del formulario             |
//+--------------------------------------------------------------------------------+
bool CWindow::OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE)
  {
//--- Comprobar el identificador y el índice del control si ha tenido lugar la llamada externa
   int check_id    =(id!=WRONG_VALUE)? id : CElementBase::Id();
   int check_index =(index!=WRONG_VALUE)? index : CElementBase::Index();
//--- Salir, si los índices no coinciden
   if(check_id!=m_button_fullscreen.Id() || check_index!=m_button_fullscreen.Index())
      return(false);
//--- Si la ventana no se encuentra en el tamaño de la pantalla completa
   if(!m_is_fullscreen)
     {
      //--- Poner en el tamaño de pantalla completa
      m_is_fullscreen=true;
      //--- Obtenemos los tamaños de la ventana del gráfico
      SetWindowProperties();
      //--- Guardamos las coordenadas y los tamaños actuales del formulario
      m_last_x            =m_x;
      m_last_y            =m_y;
      m_last_x_size       =m_x_size;
      m_last_y_size       =m_full_height;
      m_last_auto_xresize =m_auto_xresize_mode;
      m_last_auto_yresize =m_auto_yresize_mode;
      //--- Activar el cambio automático de los tamaños del formulario
      m_auto_xresize_mode=true;
      m_auto_yresize_mode=true;
      //--- Maximizar el formulario ajustando al tamaño del gráfico
      ChangeWindowWidth(m_chart.WidthInPixels()-2);
      ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3);
      //--- Actualizar la ubicación
      m_x=m_y=1;
      Moving(m_x,m_y);
      //--- Cambiar el icono en el botón
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
     }
//--- Si la ventana se encuentra en el tamaño de la pantalla completa
   else
     {
       //--- Pasar al tamaño anterior de la ventana
      m_is_fullscreen=false;
      //--- Desactivar el cambio automático de los tamaños
      m_auto_xresize_mode=m_last_auto_xresize;
      m_auto_yresize_mode=m_last_auto_yresize;
      //--- Si  el modo está desactivado, establecer el tamaño anterior
      if(!m_auto_xresize_mode)
         ChangeWindowWidth(m_last_x_size);
      if(!m_auto_yresize_mode)
         ChangeWindowHeight(m_last_y_size);
      //--- Actualizar la ubicación
      m_x=m_last_x;
      m_y=m_last_y;
      Moving(m_x,m_y);
      //--- Cambiar el icono en el botón
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
     }
//--- Quitar el foco del botón
   m_button_fullscreen.MouseFocus(false);
   m_button_fullscreen.Update(true);
   return(true);
  }

El método CWindow::OnClickFullScreenButton() se invoca en el manejador (handle) del control, una vez obtenido el evento personalizado ON_CLICK_BUTTON

//+------------------------------------------------------------------+
//| Manejador de eventos del gráfico                                 |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del clic en los botones del formulario
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ...
      //--- Comprobación del modo de pantalla completa
      if(OnClickFullScreenButton((uint)lparam,(uint)dparam))
         return;
      ...
      //---
      return;
     }
  }

El resultado será el siguiente:

 Fig. 1. Demostración del cambio al modo de pantalla completa y al revés.

Fig. 1. Demostración del cambio al modo de pantalla completa y al revés.

Para pasar a la vista de pantalla completa y viceversa usando doble clic en el encabezado de la ventana, ahora será suficiente recibir en el manejador del control este evento del doble clic (ON_DOUBLE_CLICK) en el encabezado de la ventana.

void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Procesamiento del evento del doble clic en el objeto
   if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK)
     {
      //--- Si el evento se generaba en el encabezado de la ventana
      if(CursorInsideCaption(m_mouse.X(),m_mouse.Y()))
         OnClickFullScreenButton(m_button_fullscreen.Id(),m_button_fullscreen.Index());
      //---
      return;
     }
  }

Funciona así:

 Fig. 2. Demostración del cambio al modo de pantalla completa usando el doble clic en el encabezado.

Fig. 2. Demostración del cambio al modo de pantalla completa usando el doble clic en el encabezado.

Ahora hablaremos del cambio de las dimensiones de la ventana a través del arrastre de sus bordes. Para activarlo, hay que usar el método CWindow::ResizeMode().

class CWindow : public CElement
  {
private:
   //--- Modo del cambio del tamaño de la ventana
  bool               compare_sl_tp_levels()
   //---
public:
   //--- Posibilidad del cambio del tamaño de la ventana
   bool              ResizeMode(void)                          const { return(m_xy_resize_mode);                  }
   void              ResizeMode(const bool state)                    { m_xy_resize_mode=state;                    }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_xy_resize_mode(false)
  {
...
  }

Para captar el clic izquierdo en los bordes de la ventana, necesitamos un indicador más, (PRESSED_INSIDE_BORDER), en la enumeración ENUM_MOUSE_STATE que se encuentra en el archivo Enums.mqh.

//+------------------------------------------------------------------+
//|                                                        Enums.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
//+----------------------------------------------------------------------------------------+
//| Enumeración de las áreas de la pulsación del botón izquierdo del ratón                 |
//+----------------------------------------------------------------------------------------+
enum ENUM_MOUSE_STATE
  {
   NOT_PRESSED           =0,
   PRESSED_INSIDE        =1,
   PRESSED_OUTSIDE       =2,
   PRESSED_INSIDE_HEADER =3,
   PRESSED_INSIDE_BORDER =4
  };

 Si el modo de los cambios de los tamaños está activado, se crea un objeto gráfico para el puntero del cursor del ratón con el nuevo identificador MP_WINDOW_RESIZE desde la enumeración ENUM_MOUSE_POINTER.

//+------------------------------------------------------------------+
//| 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_WINDOW_RESIZE     =5,
   MP_X_RESIZE_RELATIVE =6,
   MP_Y_RESIZE_RELATIVE =7,
   MP_X_SCROLL          =8,
   MP_Y_SCROLL          =9,
   MP_TEXT_SELECT       =10
  };

Para crear el objeto gráfico del puntero del cursor del ratón, ha sido añadido el método CreateResizePointer() a la clase CWindow:

class CWindow : public CElement
  {
private:
   bool              CreateResizePointer(void);
  };
//+------------------------------------------------------------------+
//| Crea el puntero del cursor del cambio de los tamaños             |
//+------------------------------------------------------------------+
bool CWindow::CreateResizePointer(void)
  {
//--- Salir si el modo del cambio de los tamaños está desactivado
   if(!m_xy_resize_mode)
      return(true);
//--- Propiedades
   m_xy_resize.XGap(13);
   m_xy_resize.YGap(11);
   m_xy_resize.XSize(23);
   m_xy_resize.YSize(23);
   m_xy_resize.Id(CElementBase::Id());
   m_xy_resize.Type(MP_WINDOW_RESIZE);
//--- Creación del control
   if(!m_xy_resize.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

Para cambiar las dimensiones de la ventana, hubo que implementar varios métodos. Vamos a analizarlos uno por uno.

Es necesario monitorear la posición del cursor del ratón en el momento de su aparición dentro del área de la ventana. En esta versión, se podrá cambiar los tamaños de la ventana, arrastrando su borde izquierdo, derecho o inferior. El método CWindow::ResizeModeIndex() sigue el foco en uno de los bordes mencionados y guarda el índice del borde para su procesamiento posterior en otros métodos. A este método se le traspasan las coordenadas del cursor del ratón respecto a la ventana para los cálculos.

class CWindow : public CElement
  {
private:
   //--- Índice del borde para el cambio de los tamaños de la ventana
   int               m_resize_mode_index;
   //---
private:
   //--- Devuelve el índice del modo para el cambio de los tamaños de la ventana
   int               ResizeModeIndex(const int x,const int y);
  };
//+-------------------------------------------------------------------------------------+
//| Devuelve el índice del modo para el cambio de los tamaños de la ventana             |
//+-------------------------------------------------------------------------------------+
int CWindow::ResizeModeIndex(const int x,const int y)
  {
//--- Devolver el índice del borde, si ya hay captura
   if(m_resize_mode_index!=WRONG_VALUE && m_mouse.LeftButtonState())
      return(m_resize_mode_index);
//--- Grosor, margen e índice del borde
   int width  =5;
   int offset =15;
   int index  =WRONG_VALUE;
//--- Comprobación del foco en el borde izquierdo
   if(x>0 && x<width && y>m_caption_height+offset && y<m_y_size-offset)
      index=0;
//--- Comprobación del foco en el borde derecho
   else if(x>m_x_size-width && x<m_x_size && y>m_caption_height+offset && y<m_y_size-offset)
      index=1;
//--- Comprobación del foco en el borde inferior
   else if(y>m_y_size-width && y<m_y_size && x>offset && x<m_x_size-offset)
      index=2;
//--- Si el índice ha sido obtenido, marcamos el área de pulsación
   if(index!=WRONG_VALUE)
      m_clamping_area_mouse=PRESSED_INSIDE_BORDER;
//--- Devolver el índice del área
   return(index);
  }

Vamos a necesitar los campos auxiliares de la clase para determinar los puntos de la captura, guardar los tamaños iniciales y cálculos posteriores. Cuando el proceso del cambio de los tamaños ya ha empezado, habrá que generar el mensaje para formar la lista de los controles disponibles. Por eso, también necesitaremos el método para generar el mensaje para recuperar los controles y resetear los campos de servicio: CWindow::ZeroResizeVariables().

class CWindow : public CElement
  {
private:
   //--- Variables relacionadas con el cambio de los tamaños de la ventana
   int               m_x_fixed;
   int               m_size_fixed;
   int               m_point_fixed;
   //---
private:
   //--- Puesta a cero de las variables
   void              ZeroResizeVariables(void);
  };
//+-----------------------------------------------------------------------------------------------+
//| Puesta a cero de las variables relacionadas con el cambio de los tamaños de la ventana        |
//+-----------------------------------------------------------------------------------------------+
void CWindow::ZeroResizeVariables(void)
  {
//--- Salir si la puesta a cero ya ha sido hecha
   if(m_point_fixed<1)
      return;
//--- Poner a cero
   m_x_fixed     =0;
   m_size_fixed  =0;
   m_point_fixed =0;
//--- Enviamos el mensaje para recuperar los controles disponibles
   ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,"");
//--- Enviamos el mensaje sobre el cambio en la interfaz gráfica
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
  }

Para determinar que el modo, la muestra o la ocultación del puntero del cursor del ratón están listos para cambiar los tamaños de la ventana, ha sido implementado el método CWindow::CheckResizePointer(). Aquí, primero, determinamos el índice del borde usando el método CWindow::ResizeModeIndex()

Si el puntero del cursor todavía no está mostrado, entonces cuando el índice del borde está determinado, es necesario establecer la imagen correspondiente, corregir la posición y mostrar el puntero.

Si después de la definición del índice del borde ha resultado que el puntero del cursor ya se muestra, él va a desplazarse siguiendo el cursor del ratón, siempre y cuando haya foco sobre uno de los bordes de la ventana. Si no hay foco y el botón izquierdo del ratón está suelto, el puntero se oculta, y las variables se ponen a cero.

El método CWindow::CheckResizePointer() devuelve true si el borde de la ventana para modificar los tamaños está definido, en caso contrario devuelve false.

class CWindow : public CElement
  {
private:
   //--- Comprobando la preparación para cambiar los tamaños de la ventana
   bool              CheckResizePointer(const int x,const int y);
  };
//+------------------------------------------------------------------------------------+
//| Comprobando la preparación para cambiar los tamaños de la ventana                  |
//+------------------------------------------------------------------------------------+
bool CWindow::CheckResizePointer(const int x,const int y)
  {
//--- Determinamos el índice actual del borde
   m_resize_mode_index=ResizeModeIndex(x,y);
//--- Si el cursor está ocultado
   if(!m_xy_resize.IsVisible())
     {
      //--- Si el borde está determinado
      if(m_resize_mode_index!=WRONG_VALUE)
        {
         //--- Para determinar el índice de la imagen a mostrar para el puntero del cursor del ratón
         int index=WRONG_VALUE;
         //--- Si en los bordes verticales
         if(m_resize_mode_index==0 || m_resize_mode_index==1)
            index=0;
         //--- Si en los bordes horizontales
         else if(m_resize_mode_index==2)
            index=1;
         //--- Cambiar la imagen
         m_xy_resize.ChangeImage(0,index);
         //--- Mover, redibujar y mostrar
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
         m_xy_resize.Update(true);
         m_xy_resize.Reset();
         return(true);
        }
     }
   else
     {
      //--- Mover el puntero
      if(m_resize_mode_index!=WRONG_VALUE)
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
      //--- Ocultar el puntero
      else if(!m_mouse.LeftButtonState())
        {
         //--- Ocultar el puntero y poner a cero las variables
         m_xy_resize.Hide();
         ZeroResizeVariables();
        }
      //--- Actualizar el gráfico
      m_chart.Redraw();
      return(true);
     }
//---
   return(false);
  }

Para comprobar el comienzo del desplazamiento del borde de la ventana, se usa el método CWindow::CheckDragWindowBorder(). En el momento de la captura del borde, hay que guardar el tamaño actual y la coordenada del punto inicial de la captura en los campos de la clase. En este mismo momento, se envía el mensaje para determinar los controles disponibles

Si durante las siguientes llamadas a este método resulta que la captura del borde ya ha sido realizada, es necesario calcular la distancia recorrida en este estado y devolver el valor obtenido.

class CWindow : public CElement
  {
private:
   //--- Verificación del arrastre del borde de la ventana
   int               CheckDragWindowBorder(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Verificación del arrastre del borde de la ventana                |
//+------------------------------------------------------------------+
int CWindow::CheckDragWindowBorder(const int x,const int y)
  {
//--- Para determinar la distancia del desplazamiento
   int distance=0;
//--- Si la captura del borde todavía no ha sido realizada
   if(m_point_fixed<1)
     {
      //--- Si el tamaño se cambia por el eje X
      if(m_resize_mode_index==0 || m_resize_mode_index==1)
        {
         m_x_fixed     =m_x;
         m_size_fixed  =m_x_size;
         m_point_fixed =x;
        }
      //--- Si el tamaño se cambia por el eje Y
      else if(m_resize_mode_index==2)
        {
         m_size_fixed  =m_y_size;
         m_point_fixed =y;
        }
      //--- Enviamos el mensaje para determinar los controles disponibles
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,"");
      //--- Enviamos el mensaje sobre el cambio en la interfaz gráfica
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
      return(0);
     }
//--- Si es el borde izquierdo
   if(m_resize_mode_index==0)
      distance=m_mouse.X()-m_x_fixed;
//--- Si es el borde derecho
   else if(m_resize_mode_index==1)
      distance=x-m_point_fixed;
//--- Si es el borde inferior
   else if(m_resize_mode_index==2)
      distance=y-m_point_fixed;
//--- Devolver la distancia del desplazamiento
   return(distance);
  }

El resultado obtenido que ha sido devuelto por el método CWindow::CheckDragWindowBorder() se envía al método CWindow::CalculateAndResizeWindow(), donde se calculan las coordenadas y los tamaños de la ventana respecto a su borde

class CWindow : public CElement
  {
private:
   //--- Calculo y el cambio de los tamaños de la ventana
   void              CalculateAndResizeWindow(const int distance);
  };
//+------------------------------------------------------------------+
//| Calculo y el cambio de los tamaños de la ventana                 |
//+------------------------------------------------------------------+
void CWindow::CalculateAndResizeWindow(const int distance)
  {
//--- El borde izquierdo
   if(m_resize_mode_index==0)
     {
      int new_x      =m_x_fixed+distance-m_point_fixed;
      int new_x_size =m_size_fixed-distance+m_point_fixed;
      //--- Salir si superamos las limitaciones
      if(new_x<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- Coordenadas
      CElementBase::X(new_x);
      m_canvas.X_Distance(new_x);
      //--- Determinar y guardar el tamaño
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- Borde derecho
   else if(m_resize_mode_index==1)
     {
      int gap_x2     =m_chart_width-m_mouse.X()-(m_size_fixed-m_point_fixed);
      int new_x_size =m_size_fixed+distance;
      //--- Salir si superamos las limitaciones
      if(gap_x2<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- Determinar y guardar el tamaño
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- Borde inferior
   else if(m_resize_mode_index==2)
     {
      int gap_y2=m_chart_height-m_mouse.Y()-(m_size_fixed-m_point_fixed);
      int new_y_size=m_size_fixed+distance;
      //--- Salir si superamos las limitaciones
      if(gap_y2<2 || new_y_size<=m_minimum_y_size)
         return;
      //--- Determinar y guardar el tamaño
      m_full_height=new_y_size;
      CElementBase::YSize(new_y_size);
      m_canvas.YSize(new_y_size);
      m_canvas.Resize(m_canvas.XSize(),new_y_size);
     }
  }

Los métodos CWindow::CheckDragWindowBorder() y CWindow::CheckDragWindowBorder() se invocan detro del método CWindow::UpdateSize(). Aquí, al principio del método se comprueba si el botón izquierdo se mantiene pulsado en este momento. Si el botón está suelto, todos los valores de las variables relacionadas con el cambio de los tamaños de la ventana se resetean, y el programa sale del método.

Si el botón izquierdo se mantiene pulsado, entonces (1) determinamos la distancia recorrida en el estado de la captura, (2) calculamos y cambiamos los tamaños de la ventana, (3) redibujamos la ventana y (4) corregimos la posición de sus controles.

Al final del método, dependiendo del eje por el que ha sido cambiado el tamaño de la ventana, se genera el evento según el cual luego van a modificarse los tamaños de todos los controles adjuntos a la ventana y los que tienen activado el modo correspondiente.

class CWindow : public CElement
  {
private:
   //--- Actualización de los tamaños de la ventana
   void              UpdateSize(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Actualización de los tamaños de la ventana                       |
//+------------------------------------------------------------------+
void CWindow::UpdateSize(const int x,const int y)
  {
//--- Si hemos terminado y el botón izquierdo del ratón está suelto, reseteamos los valores
   if(!m_mouse.LeftButtonState())
     {
      ZeroResizeVariables();
      return;
     }
//--- Salir si la captura y desplazamiento del borde todavía no se ha empezado
   int distance=0;
   if((distance=CheckDragWindowBorder(x,y))==0)
      return;
//--- Calculo y modificación de los tamaños de la ventana
   CalculateAndResizeWindow(distance);
//--- Redibujar la ventana
   Update(true);
//--- Actualizar la posición de los objetos
   Moving(m_x,m_y);
//--- Mensaje sobre el cambio de los tamaños de la ventana
   if(m_resize_mode_index==2)
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElementBase::Id(),0,"");
   else
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_XSIZE,(long)CElementBase::Id(),0,"");
  }

Todos los métodos mencionados para el cambio de los tamaños de la ventana se invocan en el método principal CWindow::ResizeWindow(). Ahí primero se comprueba la disponibilidad de la ventana. Luego, si el botón izquierdo del ratón ha sido pulsado fuera de cualquier borde de la ventana, el programa sale del método. Luego, se realizan otras tres comprobaciones: se comprueba (1) si el modo del cambio de los tamaños está activado, (2) si la ventana está extendida en el modo de pantalla completa en este momento y (3) si no está minimizada.

Si todas las comprobaciones han sido superadas, obtenemos las coordenadas del cursor del ratón, y si el borde de la ventana ha sido capturada, cambiamos los tamaños del control.

class CWindow : public CElement
  {
private:
   //--- Controla las dimensiones de la ventana
   void              ResizeWindow(void);
  };
//+------------------------------------------------------------------+
//| Controla las dimensiones de la ventana                           |
//+------------------------------------------------------------------+
void CWindow::ResizeWindow(void)
  {
//--- Salir si la ventana no está disponible
   if(!IsAvailable())
      return;
//--- Salir si el botón del ratón ha sido pulsado fuera del borde del formulario
   if(m_clamping_area_mouse!=PRESSED_INSIDE_BORDER && m_clamping_area_mouse!=NOT_PRESSED)
      return;
//--- Salir si (1) el modo del cambio de los tamaños de la ventana está desactivado o
    //--- (2) la ventana se encuentra en el tamaño de la pantalla completa o (3) la ventana está minimizada
   if(!m_xy_resize_mode || m_is_fullscreen || m_is_minimized)
      return;
//--- Coordenadas
   int x =m_mouse.RelativeX(m_canvas);
   int y =m_mouse.RelativeY(m_canvas);
//--- Comprobación de la preparación para el cambio del ancho de las listas
   if(!CheckResizePointer(x,y))
      return;
//--- Actualización de los tamaños de la ventana
   UpdateSize(x,y);
  }

El método CWindow::ResizeWindow() se invoca en el manejador de eventos cuando llega el evento del desplazamiento del cursor del ratón (CHARTEVENT_MOUSE_MOVE). 

Funciona así:

 Fig. 3. Cambiando los tamaños de la ventana usando el método del desplazamiento de los bordes.

Fig. 3. Cambiando los tamaños de la ventana usando el método del desplazamiento de los bordes.

Campos de edición y combobox en las celdas de la tabla

Si en las celdas de la tabla hay varios controles, ella se convierte en una herramienta muy flexible para manejar los datos que contiene. El ejemplo más próximo se puede ver directamente en los terminales comerciales MetaTrader en la pestaña «Parámetros de entrada» de las ventanas de los ajustes de las aplicaciones MQL, o en la pestaña «Parámetros» de la ventana «Probador de Estrategias». Las interfaces gráficas con estas posibilidades subirán las aplicaciones MQL a un nuevo nivel.

 Fig. 4. Ventana de ajustes en un programa MQL.

Fig. 4. Ventana de ajustes en un programa MQL.

 Fig. 5. Ajustes de una aplicación MQL en el Probador de estrategias.

Fig. 5. Fig. 5. Ajustes de una aplicación MQL en el Probador de estrategias.

En uno de los artículos anteriores, hemos insertado las casillas de verificación (checkbox) y los botones en las celdas de la tabla. Ahora, vamos a considerar cómo se puede implementar el uso de los campos de edición y los combobox. 

En primer lugar, han sido añadidos dos nuevos identificadores a la enumeración ENUM_TYPE_CELL del archivo Enums.mqh para denotar los tipos de la celda de la tabla:

  • CELL_COMBOBOX – celda del tipo del combobox.
  • CELL_EDIT – celda del tipo del campo de edición.
//+------------------------------------------------------------------+
//| Enumeración de los tipos de las celdas                           |
//+------------------------------------------------------------------+
enum ENUM_TYPE_CELL
  {
   CELL_SIMPLE   =0,
   CELL_BUTTON   =1,
   CELL_CHECKBOX =2,
   CELL_COMBOBOX =3,
   CELL_EDIT     =4
  };

Para realizar lo pensado, bastará con crear sólo un control del campo de edición (CTextEdit) y/o un control del combobox (CComboBox) en la tabla, como partes integrantes del control CTable. Van a aparecer al hacer doble clic en la tabla, cuando hay que editar el valor dentro de ella. 

Al establecer el tipo de la celda a través del método CTable::CellType(), hay que colocar una vez la bandera en los campos especiales de la clase, si se especifica el tipo CELL_EDIT o CELL_COMBOBOX.

//+------------------------------------------------------------+
//| Clase par crear la tabla dibujada                          |
//+------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- Presencia de las celdas con los campos de edición y los combobox
   bool              m_edit_state;
   bool              m_combobox_state;
   //---
public:
   //--- Establecimiento/obtención del tipo de la celda
   void              CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type);
  };
//+------------------------------------------------------------------+
//| Establece el tipo de la celda                                    |
//+------------------------------------------------------------------+
void CTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type)
  {
//--- Comprobar la superación del rango
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Establecer el tipo de la celda
   m_columns[column_index].m_rows[row_index].m_type=type;
//--- Indicio de la presencia del campos de edición
   if(type==CELL_EDIT && !m_edit_state)
      m_edit_state=true;
//--- Indicio de la presencia del combobox
   else if(type==CELL_COMBOBOX && !m_combobox_state)
      m_combobox_state=true;
  }

Si resulta que no ha sido establecida ninguna celda tipo CELL_EDIT o CELL_COMBOBOX, los controles de los tipos correspondientes no van a crearse durante la creación de la tabla. Si es necesario, se puede obtener los punteros aestos controles.

class CTable : public CElement
  {
private:
   //--- Objetos para crear la tabla
   CTextEdit         m_edit;
   CComboBox         m_combobox;
   //---
private:
   bool              CreateEdit(void);
   bool              CreateCombobox(void);
   //---
public:
   //--- Devuelve los punteros a los controles
   CTextEdit        *GetTextEditPointer(void)                { return(::GetPointer(m_edit));     }
   CComboBox        *GetComboboxPointer(void)                { return(::GetPointer(m_combobox)); }
  };

Durante el procesamiento del clic izquierdo del ratón en la tabla, se llama al método CTable::CheckCellElement(). Ahí se introducen las adiciones correspondientes para las celdas tipo CELL_EDIT y CELL_COMBOBOX. Abajo se muestra la versión reducida de este método. Los métodos para procesar diferentes tipos de las celdas van a considerarse en detalle más abajo.

//+------------------------------------------------------------------+
//--- Comprueba si ha sido empleado el control en la celda al hacer clic
//+------------------------------------------------------------------+
bool CTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Salir si en la celda no hay control
   if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE)
      return(false);
//---
   switch(m_columns[column_index].m_rows[row_index].m_type)
     {
      ...
      //--- Si es una celda con campo de edición
      case CELL_EDIT :
        {
         if(!CheckPressedEdit(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
      //--- Si es una celda con combobox
      case CELL_COMBOBOX :
        {
         if(!CheckPressedCombobox(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
     }
//---
   return(true);
  }

Antes de pasar a los métodos para el procesamiento del clic en las celdas de la tabla con los controles, hablaremos un poco de las adiciones en los controles tipo CTextBox. Cuando se activa el campo de edición, a veces es necesario que el texto dentro de él se seleccione automáticamente y el cursor se mueva al final de la línea. Eso es bastante cómodo para introducir nuevo texto reemplazando el texto anterior. 

En la versión actual, la selección automática funciona solamente para el campo de edición de una línea. Puede activar este modo usando el método CTextBox::AutoSelectionMode(). 

//+------------------------------------------------------------------+
//| Clase para crear el campo de edición multilínea                  |
//+------------------------------------------------------------------+
class CTextBox : public CElement
  {
private:
   //--- Modo de la selección automática del texto
   bool              m_auto_selection_mode;
   //---
public:
   //--- Modo para la selección automática del texto
   void              AutoSelectionMode(const bool state)       { m_auto_selection_mode=state;     }
  };

Para seleccionar todo el texto dentro del campo de edición, ha sido implementado el método privado CTextBox::SelectAllText(). Aquí, primero, obtenemos el número de los símbolos de la primera línea y establecemos los índices para la selección del texto. Luego, hay que desplazar el área visible del texto hacia el final a la derecha. Por último, hay que desplazar el cursor del texto al final de la línea.

class CTextBox : public CElement
  {
private:
   //--- Seleccionar el texto entero
   void              SelectAllText(void);
  };
//+------------------------------------------------------------------+
//| Seleccionar el texto entero                                      |
//+------------------------------------------------------------------+
void CTextBox::SelectAllText(void)
  {
//--- Obtenemos el tamaño del array de caracteres
   int symbols_total=::ArraySize(m_lines[0].m_symbol);
//--- Establecer los índices para la selección del texto
   m_selected_line_from   =0;
   m_selected_line_to     =0;
   m_selected_symbol_from =0;
   m_selected_symbol_to   =symbols_total;
//--- Desplazar el deslizador de la barra de desplazamiento horizontal a la última posición
   HorizontalScrolling();
//--- Mover el cursor al final de la línea
   SetTextCursor(symbols_total,0);
  }

Después de hacer el doble clic en la celda de la tabla, el campo de edición va a aparecer, pero con el fin de evitar otra pulsación para activar el campo de edición, necesitaremos un método público adicional, CTextBox::ActivateTextBox(). Su llamada imita la pulsación en el campo de edición. Para eso sólo hace falta llamar al método CTextBox::OnClickTextBox(), pasándole el nombre del objeto gráfico del control. La selección del texto va a realizarse en este método.

class CTextBox : public CElement
  {
public:
  //--- Activación del campo de edición
   void              ActivateTextBox(void);
  };
//+------------------------------------------------------------------+
//| Activación del campo de edición                                  |
//+------------------------------------------------------------------+
void CTextBox::ActivateTextBox(void)
  {
   OnClickTextBox(m_textbox.Name());
  }

Como en nuestra tabla vamos a usar sólo un campo de edición, hay que tener la posibilidad de cambiar sus tamaños, es que las celdas pueden tener varios anchos. Por eso, ha sido añadido el método público adicional CTextBox::ChangeSize(), donde son llamados los métodos analizados e implementados en otros artículos.

class CTextBox : public CElement
  {
public:
   //--- Cambio de tamaños
   void              ChangeSize(const uint x_size,const uint y_size);
  };
//+------------------------------------------------------------------+
//| Cambio de tamaños                                                |
//+------------------------------------------------------------------+
void CTextBox::ChangeSize(const uint x_size,const uint y_size)
  {
//--- Establecer nuevo tamaño
   ChangeMainSize(x_size,y_size);
//--- Calcular los tamaños del campo de edición
   CalculateTextBoxSize();
//--- Establecer nuevo tamaño para el campo de edición
   ChangeTextBoxSize();
  }

El doble clic en la celda con el campo de edición llama al método CTable::CheckPressedEdit(). Para luego poder procesar el evento del fin de la introducción del valor (ON_END_EDIT), también vamos a necesitar aquí los campos de la clase para almacenar los índices de la última celda editada

En la versión actual, los campos de edición van a llamarse sólo con el doble clic en la celda, por eso aquí esta comprobación se encuentra al principio del método. Luego, se guardan los índices traspasados de la columna y de la fila. Para colocar correctamente los campos de edición sobre la celda de la tabla, es necesario calcular las coordenadas tomando en cuenta el desplazamiento de la tabla por dos ejes. Aparte de eso, en los cálculos se toma en consideración la presencia de los encabezados. Después de eso, hay que calcular y establecer los tamaños para el campo de edición, así como colocar dentro la fila actual que se muestra en la celda. Luego, el campo de edición se activa y se hace visible, y el gráfico se redibuja para mostrar los últimos cambios realizados.

class CTable : public CElement
  {
private:
  //--- Los índices de la columna y la fila de la última celda editada
   int               m_last_edit_row_index;
   int               m_last_edit_column_index;
   //---
private:
   //--- Comprobamos si la celda con el campo de edición ha sido pulsada
   bool              CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false);
  };
//+--------------------------------------------------------------------------------------+
//| Comprueba si el clic ha sido hecho en el campo de edición en la celda                |
//+--------------------------------------------------------------------------------------+
bool CTable::CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Salir si no es el doble clic
   if(!double_click)
      return(false);
//--- Guardar los índices
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- Desplazamiento por dos ejes
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Establecemos nuevas coordenadas
   m_edit.XGap(m_columns[column_index].m_x-x_offset);
   m_edit.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset);
//--- Tamaños
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
//--- Establecer tamaño
   m_edit.GetTextBoxPointer().ChangeSize(x_size,y_size);
//--- Establecer el valor desde la celda de la tabla
   m_edit.SetValue(m_columns[column_index].m_rows[row_index].m_full_text);
//--- Activar el campo de edición
   m_edit.GetTextBoxPointer().ActivateTextBox();
//--- Establecer el foco
   m_edit.GetTextBoxPointer().MouseFocus(true);
//--- Mostrar campo de edición
   m_edit.Reset();
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Una vez introducidos los valores en la celda, se genera el evento con el identificador ON_END_EDIT, que es necesario recibir en el manejador de los eventos de la tabla. Para el procesamiento de este evento se utiliza el método CTable::OnEndEditCell(). Si las celdas con el campo de edición existen y los identificadores han coincidido, luego se coloca el nuevo valor en la celda. Después de eso, hay que desactivar y ocultar el campo de edición.

class CTable : public CElement
  {
private:
   //--- Procesamiento del fin de la introducción del valor en la celda
   bool              OnEndEditCell(const int id);
  };
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
Procesamiento de eventos del fin de la introducción
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      if(OnEndEditCell((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| Procesamiento del fin de la introducción del valor en la celda   |
//+------------------------------------------------------------------+
bool CTable::OnEndEditCell(const int id)
  {
//--- Salir si (1) los identificadores no coinciden o (2) no hay celdas con el campo de edición
   if(id!=CElementBase::Id() || !m_edit_state)
      return(false);
//--- Establecer el valor nuevo en la celda de la tabla
   SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),0,true);
   Update();
//--- Desactivar y ocultar el campo de edición
   m_edit.GetTextBoxPointer().DeactivateTextBox();
   m_edit.Hide();
   m_chart.Redraw();
   return(true);
  }

Cuando el clic se hace fuera del campo de edición activado, este campo debe ocultarse. Para eso vamos a necesitar el método CTable::OnEndEditCell(). Además, hay que desactivar el campo de edición para que se muestre correctamente en las siguientes llamadas. El método CTable::OnEndEditCell() se invoca en el manejador de la tabla como resultado del evento del cambio del estado del botón izquierdo del ratón (ON_CHANGE_MOUSE_LEFT_BUTTON). El método CTable::CheckAndHideCombobox() para comprobar el combobox en las celdas funciona siguiendo el mismo principio. No vamos a mostrar aquí el código de este método, ya que es prácticamente idéntico al código analizado.

class CTable : public CElement
  {
private:
   //--- Comprobación de los controles en las celdas en cuanto a su ocultación
   void              CheckAndHideEdit(void);
  };
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CTable::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)
     {
      ...
       //--- Comprobación del campo de edición   en las celdas en cuanto a su ocultación
      CheckAndHideEdit();
       //--- Comprobación del combobox en las celdas en cuanto a su ocultación
      CheckAndHideCombobox();
      return;
     }
   ...
  }
//+------------------------------------------------------------------------------+
//| Comprobación del campo de edición en las celdas en cuanto a su ocultación    |
//+------------------------------------------------------------------------------+
void CTable::CheckAndHideEdit(void)
  {
//--- Salir si (1) no hay campo de edición o (2) se encuentra ocultado
   if(!m_edit_state || !m_edit.IsVisible())
      return;
//--- Comprobamos el foco
   m_edit.GetTextBoxPointer().CheckMouseFocus();
//--- Desactivar y ocultar el campo de edición si (1) se encuentra fuera del foco y (2) el botón del ratón está pulsado
   if(!m_edit.GetTextBoxPointer().MouseFocus() && m_mouse.LeftButtonState())
     {
      m_edit.GetTextBoxPointer().DeactivateTextBox();
      m_edit.Hide();
      m_chart.Redraw();
     }
  }

Ahora veremos cómo funcionan los métodos de la llamada al combobox desde la celda de la tabla. Para las celdas tipo CELL_COMBOBOX necesitaremos un array en el que van a almacenarse los valores para la lista del combobox, así como un campo adicional donde va a guardarse el índice del elemento seleccionado. El array y el campo han sido añadidos a la estructura CTCell.

class CTable : public CElement
  {
private:
   //--- Propiedades de las celdas de la tabla
   struct CTCell
     {
      ...
      string            m_value_list[];   // Array de valores (para las celdas con los combobox)
      int               m_selected_item;  // Elemento seleccionado en la lista del combobox
      ...
     };
  };

Cuando, antes de la creación de la tabla, en la clase personalizada se indica el tipo del combobox (CELL_COMBOBOX) para la celda, es necesario pasar también la lista de valores que van a traspasarse en la lista del combobox. 

Para eso se utiliza el método CTable::AddValueList(). Además, este método recibe los índices de las celdas y el índice del elemento que hay que seleccionar en la lista del combobox. Por defecto, tenemos seleccionado el primer elemento (índice 0).

Al principio del método, se comprueba la superación del rango. Luego, para el array de la estructura CTCell se le establece el mismo tamaño que tiene el array traspasado, y se hace una copia de los valores. El índice del elemento seleccionado se ajusta en caso de salir fuera del rango y también se guarda en la estructura CTCell. En la celda, se coloca el texto del elemento seleccionado.

class CTable : public CElement
  {
public:
   //--- Añadir la lista de valores al combobox
   void              AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0);
  };
//+------------------------------------------------------------------+
//| Añadir la lista de valores al combobox                           |
//+------------------------------------------------------------------+
void CTable::AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0)
  {
//--- Comprobar la superación del rango
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Establecemos el tamaño para la lista de la celda especificada
   uint total=::ArraySize(array);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_value_list,total);
//--- Guardamos los valores traspasados
   ::ArrayCopy(m_columns[column_index].m_rows[row_index].m_value_list,array); 
//--- Comprobación del índice del elemento seleccionado en la lista
   uint check_item_index=(selected_item>=total)? total-1 : selected_item;
//--- Guardar el elemento seleccionado en la lista
   m_columns[column_index].m_rows[row_index].m_selected_item=(int)check_item_index;
//--- Guardar el texto del elemento seleccionado en la celda
   m_columns[column_index].m_rows[row_index].m_full_text=array[check_item_index];
  }

El doble clic en la celda con combobox se procesa en el método CTable::CheckPressedCombobox(). Aquí, primero, se guardan los índices de la celda para el procesamiento posterior, en caso cuando el elemento se selecciona en la lista. Luego, se establecen las coordenadas para el combobox por la esquina superior izquierda de la celda. Después de eso, para sus controles, se establecen los mismos tamaños que tiene la celda. Para poder cambiar los tamaños del botón (CButton) y de la lista (CListView) durante la ejecución del programa, ha sido añadido el método ChangeSize() a sus clases (igual como para el campo de edición). Puesto que el tamaño de la lista en cada celda puede variar, cada vez hay que volver a construir y rellenar la lista. Luego, los controles del combobox se redibujan, y él se hace visible. Al final del método, se genera el evento sobre el cambio en la interfaz gráfica

class CTable : public CElement
  {
private:
   //--- Comprobamos si la celda con el combobox ha sido pulsada
   bool              CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Comprueba si el clic ha sido hecho en el combobox de la celda    |
//+------------------------------------------------------------------+
bool CTable::CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Salir si no es el doble clic
   if(!double_click)
      return(false);
//--- Guardar los índices
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- Desplazamiento por dos ejes
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- Establecemos nuevas coordenadas
   m_combobox.XGap(m_columns[column_index].m_x-x_offset);
   m_combobox.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset);
//--- Establecer el tamaño del botón
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
   m_combobox.GetButtonPointer().ChangeSize(x_size,y_size);
//--- Establecer tamaño para la lista
   y_size=m_combobox.GetListViewPointer().YSize();
   m_combobox.GetListViewPointer().ChangeSize(x_size,y_size);
//--- Establecer tamaño para la lista de la celda
   int total=::ArraySize(m_columns[column_index].m_rows[row_index].m_value_list);
   m_combobox.GetListViewPointer().Rebuilding(total);
//--- Establecer la lista desde la celda
   for(int i=0; i<total; i++)
      m_combobox.GetListViewPointer().SetValue(i,m_columns[column_index].m_rows[row_index].m_value_list[i]);
//--- Establecer el elemento desde la lista
   int index=m_columns[column_index].m_rows[row_index].m_selected_item;
   m_combobox.SelectItem(index);
//--- Actualizar el control
   m_combobox.GetButtonPointer().MouseFocus(true);
   m_combobox.GetButtonPointer().Update(true);
   m_combobox.GetListViewPointer().Update(true);
//--- Mostrar campo de edición
   m_combobox.Reset();
//--- Redibujar el gráfico
   m_chart.Redraw();
//--- Enviamos el mensaje sobre el cambio en la interfaz gráfica
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
   return(true);
  }

La selección del elemento en la lista del combobox (ON_CLICK_COMBOBOX_ITEM) se procesa por el método CTable::OnClickComboboxItem(). Primero, se comprueba la correspondencia de los identificadores y la presencia del combobox en la tabla Si estas comprobaciones han sido superadas, se establece el índice del elemento seleccionado y el valor del elemento en la celda según los índices guardados antes.

class CTable : public CElement
  {
private:
   //--- Procesamiento de la selección del elemento en la lista desplegable de la celda
   bool              OnClickComboboxItem(const int id);
  };
//+------------------------------------------------------------------+
//| Manejador de eventos                                             |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- Procesamiento del evento de la selección del elemento en le lista
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      if(OnClickComboboxItem((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+-------------------------------------------------------------------------------------------+
//| Procesamiento de la selección del elemento en el combobox de la celda                     |
//+-------------------------------------------------------------------------------------------+
bool CTable::OnClickComboboxItem(const int id)
  {
//--- Salir si (1) los identificadores no coinciden o (2) no hay celdas con el combobox
   if(id!=CElementBase::Id() || !m_combobox_state)
      return(false);
//--- Índices de la última celda editada
   int c=m_last_edit_column_index;
   int r=m_last_edit_row_index;
//--- Guardamos el índice del elemento seleccionado en la celda
   m_columns[c].m_rows[r].m_selected_item=m_combobox.GetListViewPointer().SelectedItemIndex();
//--- Establecer el valor nuevo en la celda de la tabla
   SetValue(c,r,m_combobox.GetValue(),0,true);
   Update();
   return(true);
  }

Al final, todo eso va a funcionar de la siguiente manera:

Fig. 6. Demostración del trabajo con los campos de edición y los combobox en las celdas de la tabla. 

Fig. 6. Demostración del trabajo con los campos de edición y los combobox en las celdas de la tabla.

Aplicación para la prueba

Para realizar las pruebas, ha sido creada una aplicación MQL con la interfaz gráfica que incluye los controles: la tabla (CTable) y el campo de edición multilínea (CTextBox). Todas las celdas de la primera columna de la tabla contienen el control (CELL_CHECKBOX). Las celdas de la segunda columna tienen el tipo «campo de edición» (CELL_EDIT). En las celdas de la tercera columna los tipos «Combobox» (CELL_COMBOBOX) y «Campo de edición»  (CELL_EDIT) van turnando. En las celdas de la quinta columna, tenemos el tipo «Botón» (CELL_BUTTON). Los eventos de la tabla van a procesarse en el manejador de eventos de la clase personalizada de la aplicación MQL, y van a mostrarse en el campo de edición multilínea. 

Eso funciona así:

 Fig. 7. Aplicación MQL para las pruebas del trabajo realizado.

Fig. 7. Aplicación MQL para las pruebas del trabajo realizado.

Puede descargar el archivo adjunto de esta aplicación para estudiarla más detalladamente.

Conclusión

Hemos añadido la posibilidad de crear las celdas tipo «Campo de edición» y «Combobox» en la tabla. Podemos maximizar el formulario para los controles a pantalla completa, o especificar sus tamaños manualmente arrastrando los bordes.

En esta fase del desarrollo, el esquema general de la librería tiene el siguiente aspecto:

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

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

Este código de la librería se ofrece de forma gratuita. Usted puede utilizarlo en sus proyectos, inclusive de negocio, escribir los artículos y realizar los trabajos de encargo.

Si tiene algunas preguntas respecto al uso del material del artículo, puede hacerlas en los comentarios para el artículo.