English Русский 中文 Deutsch 日本語 Português
Interfaces gráficas X: Control "Hora", control "Lista de las casillas de verificación" y ordenamiento (sort) de la tabla (build 6)

Interfaces gráficas X: Control "Hora", control "Lista de las casillas de verificación" y ordenamiento (sort) de la tabla (build 6)

MetaTrader 5Ejemplos | 10 enero 2017, 14:10
1 242 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 los artículos de cada parte se puede encontrar la lista de los capítulos con los enlaces, así como 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.

El desarrollo de la librería continúa. Hablaremos de los controles como «Hora» y «Lista de las casillas de verificación». Aparte de eso, a la clase de la tabla tipo CTable se le ha sido añadida la posibilidad de organizar los datos en orden ascendiente y descendiente. Hablaremos de eso y de otras actualizaciones en este artículo.

 

Control «Hora»

Durante la creación de la la interfaz gráfica para un indicador o Asesor Experto, a veces podemos necesitar indicar los intervalos temporales. A veces, esta necesidad surge durante el trading de intradía. En uno de los artículos anteriores, ya fueron mostrados Calendario y Calendario desplegable, a través de los cuales se podía especificar la fecha pero no la hora (horas y minutos).

Vamos a nombrar todos los componentes que van a formar parte del control «Hora»:

  • Fondo
  • Icono
  • Descripción
  • Dos campos de edición


Fig. 1. Partes integrantes del control “Hora”.

 

Vamos a ver con más detalles cómo está organizada la clase de este control.

 

Clase para la creación del control «Hora»

Creamos el archivo TimeEdit.mqh con la clase CTimeEdit con los métodos estándar para todos los controles, y lo incluimos en el motor de la librería (archivo WndContainer.mqh). Abajo se listan las propiedades que estarán disponibles para la configuración personalizada del control.

  • Color del fondo del control
  • Iconos del control para el estado activo y bloqueado
  • Márgenes del icono por dos ejes (x, y)
  • Texto de la descripción del control
  • Márgenes de la etiqueta de texto por dos ejes (x, y)
  • Color del texto en diferentes estados del control
  • Ancho de los campos de edición
  • Márgenes de los campos de edición por dos ejes (x, y)
  • Estado del control (disponible/bloqueado)
  • Modo para resetear los valores en los campos de edición
//+------------------------------------------------------------------+
//| Clase para crear el control «Hora»                              |
//+------------------------------------------------------------------+
class CTimeEdit : public CElement
  {
private:
   //--- Color del fondo del control
   color             m_area_color;
   //--- Iconos del control en el estado activo y bloqueado
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- Márgenes del icono
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- Texto de la descripción del control
   string            m_label_text;
   //--- Márgenes de la etiqueta de texto
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- Colores del texto en estados diferentes
   color             m_label_color;
   color             m_label_color_hover;
   color             m_label_color_locked;
   color             m_label_color_array[];
   //--- Tamaños del campo de edición
   int               m_edit_x_size;
  //--- Márgenes de los campos de edición
   int               m_edit_x_gap;
   int               m_edit_y_gap;
   //--- Estado del control (disponible/bloqueado)
   bool              m_time_edit_state;
   //--- Modo de reseteo del valor
   bool              m_reset_mode;
   //---
public:
   //--- (1) Color del fondo, (2) márgenes del icono
   void              AreaColor(const color clr)                     { m_area_color=clr;                  }
   void              IconXGap(const int x_gap)                      { m_icon_x_gap=x_gap;                }
   void              IconYGap(const int y_gap)                      { m_icon_y_gap=y_gap;                }
   //--- (1) Texto de la descripción del control, (2) márgenes de la etiqueta de texto
   string            LabelText(void)                          const { return(m_label.Description());     }
   void              LabelText(const string text)                   { m_label.Description(text);         }
   void              LabelXGap(const int x_gap)                     { m_label_x_gap=x_gap;               }
   void              LabelYGap(const int y_gap)                     { m_label_y_gap=y_gap;               }
   //--- Colores de la etiqueta de texto en diferentes estados
   void              LabelColor(const color clr)                    { m_label_color=clr;                 }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;           }
   void              LabelColorLocked(const color clr)              { m_label_color_locked=clr;          }
   //--- (1) Tamaños del campo de edición, (2) márgenes del campo de edición
   void              EditXSize(const int x_size)                    { m_edit_x_size=x_size;              }
   void              EditXGap(const int x_gap)                      { m_edit_x_gap=x_gap;                }
   void              EditYGap(const int y_gap)                      { m_edit_y_gap=y_gap;                }
   //--- (1) Modo del reseteo al hacer clic en la etiqueta de texto, (2) modo de selección del texto
   bool              ResetMode(void)                                { return(m_reset_mode);              }
   void              ResetMode(const bool mode)                     { m_reset_mode=mode;                 }
  };

Para crear el control «Hora», vamos a necesitar cinco métodos privados (private) y un método público (public). Este control pertenece al tipo compuesto, y como campos de edición van a usarse los controles ya hechos del tipo CSpinEdit

class CTimeEdit : public CElement
  {
private:
   //--- Objetos para crear el control
   CRectLabel        m_area;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CSpinEdit         m_hours;
   CSpinEdit         m_minutes;

   //---
public:
   //--- Métodos para crear el control
   bool              CreateTimeEdit(const long chart_id,const int subwin,const string label_text,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateArea(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateHoursEdit(void);
   bool              CreateMinutesEdit(void);
   //---
public:
   //--- Devuelve los punteros de los campos de edición
   CSpinEdit        *GetHoursEditPointer(void)                      { return(::GetPointer(m_hours));     }
   CSpinEdit        *GetMinutesEditPointer(void)                    { return(::GetPointer(m_minutes));   }

  };

Para obtener mediante programación y establecer los valores actuales en los campos de edición (horas y minutos), utilice los métodos que se muestran a continuación:

class CTimeEdit : public CElement
  {
public:
   //--- Devolver y establecer los valores de los campos de edición
   int               GetHours(void)                           const { return((int)m_hours.GetValue());   }
   int               GetMinutes(void)                         const { return((int)m_minutes.GetValue()); }
   void              SetHours(const uint value)                     { m_hours.ChangeValue(value);        }
   void              SetMinutes(const uint value)                   { m_minutes.ChangeValue(value);      }
  };

Luego en el artículo se mostrará el ejemplo de la visualización de este control en el gráfico del terminal. 


Control “Lista con casillas de verificación”

En uno de los artículos anteriores hemos considerado detalladamente el control «Lista» (clase CListView) mediante el cual se puede seleccionar un elemento de la lista disponible. Pero a veces surge la necesidad de seleccionar varios elementos a la vez. Por ejemplo, Usted puede necesitar crear una lista de símbolos o períodos de tiempo en la que el usuario de la aplicación MQL puede seleccionar sólo los que necesita para el trading.

Es la lista de los componentes de los que va a construirse el control «Lista de las casillas de verificación»:

  1. Fondo general del control
  2. Barra de desplazamiento vertical
  3. Grupo de los checkbox:
    • Fondo
    • Icono
    • Etiqueta de texto


Fig. 2. Partes integrantes del control «Lista de los checkbox»

 

A continuación vamos a ver en qué se diferencia este tipo de la lista (CCheckBoxList) de la lista común (CListView), la que ya hemos considerado anteriormente.

 

Clase para crear la lista con casillas de verificación

Creamos el archivo CheckBoxList.mqh con la clase CCheckBoxList con los métodos estándar para todos los controles de la librería. La primera diferencia de la lista tipo CListView consiste en que los elementos de la lista se construyen de tres objetos gráficos (véase el código de abajo). Para cada tipo del objeto se crea un método privado separado.

//+------------------------------------------------------------------+
//| Clase para crear la lista con checkbox                          |
//+------------------------------------------------------------------+
class CCheckBoxList : public CElement
  {
private:
   //--- Objetos para crear la lista
   CRectLabel        m_area;
   CEdit             m_items[];
   CBmpLabel         m_checks[];
   CLabel            m_labels[];

   CScrollV          m_scrollv;
   //---
public:
   //--- Métodos para crear el control
   bool              CreateCheckBoxList(const long chart_id,const int subwin,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateArea(void);
   bool              CreateList(void);
   bool              CreateItems(void);
   bool              CreateChecks(void);
   bool              CreateLabels(void);

   bool              CreateScrollV(void);
  };

Aparte del array de los elementos (descripción de los elementos), vamos a necesitar el array de los estados de los checkbox (activado/desactivado). Además, harán falta los métodos correspondientes para establecer y obtener los valores según el índice especificado en la lista: 

class CCheckBoxList : public CElement
  {
private:
   //--- Array de los valores y estados de los checkbox de la lista
   string            m_item_value[];
   bool              m_item_state[];
   //---
public:
   //--- Devuelve/guarda (1) el estado y (2) texto del elemento seleccionado en la lista según el índice especificado
   void              SetItemState(const uint item_index,const bool state);
   void              SetItemValue(const uint item_index,const string value);
   bool              GetItemState(const uint item_index);
   string            GetItemValue(const uint item_index);
  };
//+------------------------------------------------------------------+
//| Establecer el estado                                               |
//+------------------------------------------------------------------+
void CCheckBoxList::SetItemState(const uint item_index,const bool state)
  {
   uint array_size=::ArraySize(m_item_state);
//--- Si la lista no tiene elementos, avisar sobre ello
   if(array_size<1)
      ::Print(__FUNCTION__," > ¡La llamada a este método debe realizarse, cuando la lista tiene por lo menos un elemento!");
//--- Corrección en caso de salir fuera del diapasón
   uint check_index=(item_index>=array_size)? array_size-1 : item_index;
//--- Guardar valor
   m_item_state[check_index]=state;
//--- Mueve la lista respecto a la barra de desplazamiento
   ShiftList();
  }
//+------------------------------------------------------------------+
//| Obtener el estado del checkbox de la lista                              |
//+------------------------------------------------------------------+
bool CCheckBoxList::GetItemState(const uint item_index)
  {
   uint array_size=::ArraySize(m_item_state);
//--- Si la lista no tiene elementos, avisar sobre ello
   if(array_size<1)
      ::Print(__FUNCTION__," > ¡La llamada a este método debe realizarse, cuando la lista tiene por lo menos un elemento!");
//--- Corrección en caso de salir fuera del diapasón
   uint check_index=(item_index>=array_size)? array_size-1 : item_index;
//--- Guardar valor
   return(m_item_state[check_index]);
  }

Los cambios correspondientes han sido introducidos en los métodos relacionados con la gestión de la lista. Usted puede conocerlos por sí mismo. 


Ordenamiento de la tabla

Si en la interfaz gráfica de la aplicación se utilizan las tablas con datos, a veces puede surgir la necesidad de ordenarlas por la columna especificada por el usuario. En muchas implementaciones de las interfaces gráficas eso está implementado de tal manera que los datos se ordenan con el clic en el encabezado de la columna. El primer clic en el encabezado ordena los datos en orden ascendiente, es decir del valor mínimo al máximo. El segundo clic ordena los datos en orden descendiente, es decir del valor máximo al mínimo.

En la captura de abajo se muestra la ventana «Caja de herramientas» del editor del código MetaEditor con la tabla de tres columnas. El ordenamiento (descendiente) está implementado en la tercera columna que contiene las fechas.


Fig. 3. Ejemplo de la tabla con datos ordenados.

 

Como indicio de datos ordenados, el encabezado de la columna normalmente contiene una flecha. Si indica hacia abajo, como en la captura de arriba, eso significa que los datos están ordenados en orden descendiente, y viceversa.

Pues, en este artículo vamos a hacer el ordenamiento para la tabla del tipo CTable. Ya contiene la posibilidad de incluir los encabezados para las columnas, y ahora hay que hacerlos interactivos. En primer lugar, hay que hacer que los encabezados cambien su color al situar el cursor sobre ellos, así como al hacer clic en ellos. Para eso añadimos los campos y los métodos en la clase CTable para establecer el color de los encabezados de las columnas en diferentes estados CTable (véase el código de abajo).

class CTable : public CElement
  {
private:
   //--- Color del fondo de encabezados
   color             m_headers_color;
   color             m_headers_color_hover;
   color             m_headers_color_pressed;

   //---
public:
   //--- Color del fondo de 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;             }

  };

El usuario decide por sí mismo si la posibilidad de ordenamiento es necesaria. Por defecto, este modo será deshabilitado. Para activarlo, utilice el método CTable::IsSortMode(): 

class CTable : public CElement
  {
private:
   //--- Modo de ordenamiento de datos por las columnas
   bool              m_is_sort_mode;
   //---
public:
   //--- Modo de ordenamiento de datos
   void              IsSortMode(const bool flag)                                { m_is_sort_mode=flag;                     }
  };

Para cambiar el color del encabezado al situar el cursor sobre él, va a usarse el método privado CTable::HeaderColorByHover(). Su llamada va a realizarse en el manejador de eventos del control. 

class CTable : public CElement
  {
private:
   //--- Cambio del color del encabezado de la tabla al situar el cursor
   void              HeaderColorByHover(void);
  };
//+------------------------------------------------------------------+
//| Cambio del color del encabezado de la tabla al situar el cursor     |
//+------------------------------------------------------------------+
void CTable::HeaderColorByHover(void)
  {
//--- Salir si el modo de las columnas ordenadas está desactivado
   if(!m_is_sort_mode || !m_fix_first_row)
      return;
//---
   for(uint c=0; c<m_visible_columns_total; c++)
     {
      //--- Comprobamos el foco en el encabezado actual
      bool condition=m_mouse.X()>m_columns[c].m_rows[0].X() && m_mouse.X()<m_columns[c].m_rows[0].X2() &&
                     m_mouse.Y()>m_columns[c].m_rows[0].Y() && m_mouse.Y()<m_columns[c].m_rows[0].Y2();
      //---
      if(!condition)
         m_columns[c].m_rows[0].BackColor(m_headers_color);
      else
        {
         if(!m_mouse.LeftButtonState())
            m_columns[c].m_rows[0].BackColor(m_headers_color_hover);
         else
            m_columns[c].m_rows[0].BackColor(m_headers_color_pressed);
        }
     }
  }

Para crear el icono de indicio de datos ordenados, hay que añadir el método privado CTable::CreateSignSortedData(). Si el modo de ordenamiento no ha sido activado antes de la creación de la tabla, la imagen no va a crearse. Pero si el modo está activado, la imagen será ocultada inmediatamente después de la creación, puesto que desde el principio los datos de la tabla no están ordenados. 

class CTable : public CElement
  {
private:
   //--- Objetos para crear la tabla
   CBmpLabel         m_sort_arrow;
   //---
private:
   //--- Métodos para crear la tabla
   bool              CreateSignSortedData(void);
  };
//+------------------------------------------------------------------+
//| Crea el icono de la flecha como indicio de datos ordenados        |
//+------------------------------------------------------------------+
bool CTable::CreateSignSortedData(void)
  {
//--- Salir si el modo de ordenamiento está desactivado
   if(!m_is_sort_mode)
      return(true);

//--- Formación del nombre del objeto
   string name=CElement::ProgramName()+"_table_sort_array_"+(string)CElement::Id();
//--- Coordenadas
   int x =(m_anchor_right_window_side)? m_columns[0].m_rows[0].X()-m_columns[0].m_rows[0].XSize()+m_sort_arrow_x_gap : m_columns[0].m_rows[0].X2()-m_sort_arrow_x_gap;
   int y =(m_anchor_bottom_window_side)? CElement::Y()-m_sort_arrow_y_gap : CElement::Y()+m_sort_arrow_y_gap;
//--- Si no se ha indicado la imagen para la flecha, entonces la colocación es por defecto
   if(m_sort_arrow_file_on=="")
      m_sort_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
   if(m_sort_arrow_file_off=="")
      m_sort_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//--- Establecemos el objeto
   if(!m_sort_arrow.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Establecemos las propiedades
   m_sort_arrow.BmpFileOn("::"+m_sort_arrow_file_on);
   m_sort_arrow.BmpFileOff("::"+m_sort_arrow_file_off);
   m_sort_arrow.Corner(m_corner);
   m_sort_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_sort_arrow.Selectable(false);
   m_sort_arrow.Z_Order(m_zorder);
   m_sort_arrow.Tooltip("\n");
//--- Guardamos las coordenadas
   m_sort_arrow.X(x);
   m_sort_arrow.Y(y);
//--- Guardamos los tamaños (en el objeto)
   m_sort_arrow.XSize(m_sort_arrow.X_Size());
   m_sort_arrow.YSize(m_sort_arrow.Y_Size());
//--- Márgenes desde el punto extremo
   m_sort_arrow.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
   m_sort_arrow.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Ocultar el objeto
   m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
//--- Guardamos el puntero del objeto
   CElement::AddToArray(m_sort_arrow);
   return(true);
  }

En la estructura de los valores y propiedades de las celdas de la tabla hay que incluir el array en el que va a guardarse la cantidad de dígitos decimales tras la coma para cada celda si son números reales, así como el campo con el tipo de datos para cada columna de la tabla.

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

Por defecto, cuando los valores se introducen en la celda de la tabla, el número de dígitos tras la coma se hace igual a cero para ella: 

class CTable : public CElement
  {
public:
   //--- Establece el valor en la celda especificada de la tabla
   void              SetValue(const uint column_index,const uint row_index,const string value="",const uint digits=0);
  };

Para establecer el tipo de datos en una u otra columna de la tabla, así como para obtener el tipo, es necesario usar los métodos CTable::DataType():

class CTable : public CElement
  {
public:
   //--- Obtención/establecimiento del tipo de datos
   ENUM_DATATYPE     DataType(const uint column_index)                          { return(m_vcolumns[column_index].m_type); }
   void              DataType(const uint column_index,const ENUM_DATATYPE type) { m_vcolumns[column_index].m_type=type;    }
  };

Antes de crear la tabla, especificamos el número total de columnas y filas. El campo m_type y el array m_digits se inicializan con valores predefinidos. Desde el principio, el tipo en todas las columnas es string (TYPE_STRING), y el número de dígitos tras la coma en todas las celdas es igual a cero. 

//+------------------------------------------------------------------+
//| Establece el tamaño de la tabla                                    |
//+------------------------------------------------------------------+
void CTable::TableSize(const uint columns_total,const uint rows_total)
  {
//--- Tiene que haber no menos de una columna
   m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- Tiene que haber no menos de dos filas
   m_rows_total=(rows_total<2) ? 2 : rows_total;
//--- Establecer el tamaño para el array de columnas
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- Establecer el tamaño para el array de filas
   for(uint i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_digits,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_text_align,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_text_color,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_cell_color,m_rows_total);
      //--- Inicialización del array del color del fondo de las celdas con los valores predefinidos
      m_vcolumns[i].m_type=TYPE_STRING;
      ::ArrayInitialize(m_vcolumns[i].m_digits,0);
      ::ArrayInitialize(m_vcolumns[i].m_text_align,m_align_mode);
      ::ArrayInitialize(m_vcolumns[i].m_cell_color,m_cell_color);
      ::ArrayInitialize(m_vcolumns[i].m_text_color,m_cell_text_color);
     }
  }

Para el ordenamiento de los arrays de la tabla necesitaremos varios métodos privados donde van a ejecutarse las siguientes operaciones:

  • Algoritmo de ordenamiento.
  • Comparación de los valores según la condición especificada.
  • Intercambio de los valores de los elementos del array.

El método CTable::Swap() se encarga del intercambio de los valores de los elementos del array de la tabla. Aquí el intercambio se realiza directamente con las filas de la tabla. A veces se cambian no sólo los valores en la celdas, sino también el color del texto. 

class CTable : public CElement
  {
private:
   //--- Alternamos los valores en las celdas
   void              Swap(uint c,uint r1,uint r2);
  };
//+------------------------------------------------------------------+
//| Alternar los elementos                                        |
//+------------------------------------------------------------------+
void CTable::Swap(uint c,uint r1,uint r2)
  {
//--- Recorremos en el ciclo todas las columnas
   for(uint i=0; i<m_columns_total; i++)
     {
      //--- Alternamos el texto
      string temp_text          =m_vcolumns[i].m_vrows[r1];
      m_vcolumns[i].m_vrows[r1] =m_vcolumns[i].m_vrows[r2];
      m_vcolumns[i].m_vrows[r2] =temp_text;
      //--- Alternamos el color del texto
      color temp_text_color          =m_vcolumns[i].m_text_color[r1];
      m_vcolumns[i].m_text_color[r1] =m_vcolumns[i].m_text_color[r2];
      m_vcolumns[i].m_text_color[r2] =temp_text_color;
     }
  }

Para un ordenamiento correcto, la comparación de los valores se debe realizar con la conversión al tipo especificado en el campo m_type. Para eso ha sido escrito el método especial CTable::CheckSortCondition().

class CTable : public CElement
  {
private:
   //--- Comprobación de las condiciones del ordenamiento
   bool              CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
  };
//+------------------------------------------------------------------+
//| Comparación de los valores según la condición especificada del ordenamiento              |
//+------------------------------------------------------------------+
//| direction: true (>), false (<)                                   |
//+------------------------------------------------------------------+
bool CTable::CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction)
  {
   bool condition=false;
//---
   switch(m_vcolumns[column_index].m_type)
     {
      case TYPE_STRING :
        {
         string v1=m_vcolumns[column_index].m_vrows[row_index];
         string v2=check_value;
         condition=(direction)? v1>v2 : v1<v2;
         break;
        }
      //---
      case TYPE_DOUBLE :
        {
         double v1=double(m_vcolumns[column_index].m_vrows[row_index]);
         double v2=double(check_value);
         condition=(direction)? v1>v2 : v1<v2;
         break;
        }
      //---
      case TYPE_DATETIME :
        {
         datetime v1=::StringToTime(m_vcolumns[column_index].m_vrows[row_index]);
         datetime v2=::StringToTime(check_value);
         condition=(direction)? v1>v2 : v1<v2;
         break;
        }
      //---
      default :
        {
         long v1=(long)m_vcolumns[column_index].m_vrows[row_index];
         long v2=(long)check_value;
         condition=(direction)? v1>v2 : v1<v2;
         break;
        }
     }
//---
   return(condition);
  }

Los métodos CTable::Swap() y CTable::CheckSortCondition() van a utilizarse en el método con el algoritmo del ordenamiento. Vamos a detallar un poco qué algoritmo ha sido escogido para ordenar los datos. Han sido probados diez algoritmos, inclusive el ordenamiento MQL estándar a través de la función ArraySort():

  • Ordenamiento MQL
  • Ordenamiento con acceso
  • Ordenamiento de burbuja
  • Ordenamiento de burbuja bidereccional
  • Ordenamiento por inserción
  • Ordenamiento Shell
  • Ordenamiento con árbol binario
  • Ordenamiento rápido
  • Ordenamiento piramidal
  • Ordenamiento por mezcla

La prueba de estos métodos fue realizada con el array cuyo tamaño es de cien mil elementos. El array se inicializaba con números aleatorios. Los resultados se muestran en el histograma de abajo:


Fig. 4. Gráfico de resultados de la prueba de diferentes métodos del ordenamiento. Tamaño del array 100 000 elementos.


El método del ordenamiento rápido ha resultado el más rápido. El código de este método ha sido cogido de la librería estándar, el método QuickSort(). Durante esta prueba, incluso ha mostrado el resultado mejor que la función ArraySort(). Para la mejor precisión, la medición de la duración del funcionamiento del algoritmo se realizaba a través de la función GetMicrosecondCount(). Vamos a dejar sólo los algoritmos que han mostrado el mejor resultado (menos de un segundo). 


Fig. 5. Gráfico de los mejores resultados de la prueba de los métodos del ordenamiento. Tamaño del array 100 000 elementos.


Aumentamos el tamaño del array 10 veces más, es decir ahora vamos a ordenar el array de 1 000 000 elementos (un millón).


Fig. 6. Gráfico de los resultados de la prueba de los métodos del ordenamiento. Tamaño del array de 1 000 000 de elementos.


El algoritmo de plantilla ha sido el mejor durante esta prueba, se trata de la función ArraySort(). El método del ordenamiento rápido no ha sido mucho peor, por eso vamos a elegir precisamente este método. Es que la función ArraySort() no nos conviene para nuestra tarea por siguiente: (1) se precisa la posibilidad de realizar el ordenamiento en dos direcciones, (2) en la clase CTable se utiliza el array de estructuras y (3) es necesario controlar no sólo la posición de los valores en las celdas, sino también otras sus propiedades. 

Hay que añadir la enumeración ENUM_SORT_MODE al archivo Enums.mqh para dos direcciones del ordenamiento:

  • SORT_ASCEND – en orden ascendiente.
  • SORT_DESCEND – en orden descendiente.
//+------------------------------------------------------------------+
//| Enumeración de los modos del ordenamiento                                  |
//+------------------------------------------------------------------+
enum ENUM_SORT_MODE
  {
   SORT_ASCEND  =0,
   SORT_DESCEND =1
  };

En el código de abajo se muestra la versión actual del algoritmo del ordenamiento rápido en el método CTable::QuickSort(). Los métodos CTable::Swap() y CTable::CheckSortCondition(), que han sido mostrados antes en el artículo están marcados en amarillo. 

class CTable : public CElement
  {
private:
   //--- Método del ordenamiento rápido
   void              QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
  };
//+------------------------------------------------------------------+
//| Algoritmo del ordenamiento rápido                                      |
//+------------------------------------------------------------------+
void CTable::QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND)
  {
   uint   r1         =beg;
   uint   r2         =end;
   uint   c          =column;
   string temp       =NULL;
   string value      =NULL;
   uint   data_total =m_rows_total-1;
//--- Ejecutar el algoritmo hasta que el índice izquierdo sea menos que el índice más extremo derecho
   while(r1<end)
     {
      //--- Obtenemos el valor desde la mitad de la serie
      value=m_vcolumns[c].m_vrows[(beg+end)>>1];
      //--- Ejecutar el algoritmo hasta que el índice izquierdo sea menos que el índice derecho encontrado
      while(r1<r2)
        {
         //--- Desplazar el índice a la derecha mientras buscamos el valor según la condición especificada
         while(CheckSortCondition(c,r1,value,(mode==SORT_ASCEND)? false : true))
           {
            //--- Control de la salida fuera del límite del array
            if(r1==data_total)
               break;
            r1++;
           }
         //--- Desplazar el índice a la izquierda mientras buscamos el valor según la condición especificada
         while(CheckSortCondition(c,r2,value,(mode==SORT_ASCEND)? true : false))
           {
            //--- Control de la salida fuera del límite del array
            if(r2==0)
               break;
            r2--;
           }
         //--- Si el índice izquierdo todavía no supera el índice derecho
         if(r1<=r2)
           {
            //--- Alternar los valores
            Swap(c,r1,r2);
            //--- Si hemos llegado hasta el límite de la izquierda
            if(r2==0)
              {
               r1++;
               break;
              }
            //---
            r1++;
            r2--;
           }
        }
      //--- Continuación recursiva del algoritmo hasta no llagar al inicio del diapasón
      if(beg<r2)
         QuickSort(beg,r2,c,mode);
      //--- Reducción del diapasón para la siguiente alteración
      beg=r1;
      r2=end;
     }
  }

Todos estos métodos son privados. Ahora veremos el método público CTable::SortData() que sirve para la llamada (1) al pulsar en los encabezados de las columnas de los datos de la tabla o (2) mediante programación en cualquier otro momento cuando eso puede ser necesario según la idea del autor de la aplicación MQL. Hay que enviar el índice de la columna al método CTable::SortData(), por defecto, se ordena la primera columna Si el ordenamiento de la columna especificada se realiza por primera vez o el último ordenamiento de la misma columna ha sido en orden descendiente, los valores serán ordenados en orden ascendiente. Después del ordenamiento de datos, la tabla se actualiza. En la última línea del método CTable::SortData() se establece el icono correspondiente para la flecha indicadora de la tabla ordenada. 

class CTable : public CElement
  {
private:
   //--- Indice de la columna ordenada (WRONG_VALUE – tabla no ordenada)
   int               m_is_sorted_column_index;
   //--- Ultima dirección del ordenamiento
   ENUM_SORT_MODE    m_last_sort_direction;
   //---
public:
   //--- Ordenar los datos para la columna especificada
   void              SortData(const uint column_index=0);
  };
//+------------------------------------------------------------------+
//| Ordenar los datos para la columna especificada                         |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0)
  {
//--- Indice (tomado en cuenta la presencia de los encabezados) con el que se empieza el ordenamiento
   uint first_index=(m_fix_first_row) ? 1 : 0;
//--- Ultimo índice del array
   uint last_index=m_rows_total-1;
//--- Por primera vez será ordenado en orden ascendiente, y luego cada vez, en dirección contraria
   if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND)
      m_last_sort_direction=SORT_ASCEND;
   else
      m_last_sort_direction=SORT_DESCEND;
//--- Guardamos el índice de la última columna ordenada
   m_is_sorted_column_index=(int)column_index;
//--- Ordenamiento
   QuickSort(first_index,last_index,column_index,m_last_sort_direction);
//--- Actualizar la tabla
   UpdateTable();
//--- Colocar el icono de acuerdo con la dirección del ordenamiento
   m_sort_arrow.State((m_last_sort_direction==SORT_ASCEND)? true : false);
  }

Vamos a necesitar un método para el procesamiento del clic en los encabezados de las columnas CTable::OnClickTableHeaders() cuando el modo de ordenamiento de datos está activado. Después de pasar todas las comprobaciones de pertenencia del objeto a este control, en el ciclo se determina el índice del encabezado (columna), y luego la tabla se ordena. Inmediatamente después del ordenamiento de la tabla, se genera el evento con el nuevo indicador ON_SORT_DATA. Además de este identificador del evento, el mensaje contiene (1) el identificador del control, (2) el índice de la columna ordenada y (3) el tipo de datos de esta columna.

class CTable : public CElement
  {
private:
  //--- Procesamiento del clic en los encabezados de la tabla
   bool              OnClickTableHeaders(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Procesamiento del clic en el encabezado de la tabla                           |
//+------------------------------------------------------------------+
bool CTable::OnClickTableHeaders(const string clicked_object)
  
//--- Salir si el modo de ordenamiento está desactivado
   if(!m_is_sort_mode)
      return(false);

//--- Salimos si el clic no ha sido hecho en la celda de la tabla
   if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- Obtenemos el identificador del nombre del objeto
   int id=CElement::IdFromObjectName(clicked_object);
//--- Salir si el identificador no coincide
   if(id!=CElement::Id())
      return(false);
//--- Salir si no es el encabezado de la tabla
   if(RowIndexFromObjectName(clicked_object)>0)
      return(false);
//--- Para la determinación del índice de la columna
   uint column_index=0;
//--- Desplazamiento a un índice si el modo de encabezados fijos está activado
   int l=(m_fix_first_column) ? 1 : 0;
//--- Obtenemos la posición actual del deslizador de la barra de desplazamiento horizontal
   int h=m_scrollh.CurrentPos()+l;
//--- Columnas
   for(uint c=l; c<m_visible_columns_total; c++)
     {
      //--- Si el clic ha sido hecho en esta celda
      if(m_columns[c].m_rows[0].Name()==clicked_object)
        {
        //--- Obtenemos el índice de la columna
         column_index=(m_fix_first_column && c==0) ? 0 : h;
         break;
        }
      //---
      h++;
     }
//--- Ordenamiento de datos para la columna especificada
   SortData(column_index);
//--- Enviamos el mensaje sobre ello
   ::EventChartCustom(m_chart_id,ON_SORT_DATA,CElement::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
   return(true);
  }

Si el número total de las columnas supera el número visible de las columnas, al desplazar la barra de desplazamiento horizontal, hay que corregir la posición de la flecha de indicio de la tabla ordenada. Para eso se utiliza el método privado CTable::ShiftSortArrow().

class CTable : public CElement
  {
private:
  //--- Desplazamiento de la flecha del ordenamiento
   void              ShiftSortArrow(const uint column);
  };
//+------------------------------------------------------------------+
//| Desplazamiento de la flecha a una columna ordenada de la tabla              |
//+------------------------------------------------------------------+
void CTable::ShiftSortArrow(const uint column)
  {
//--- Mostrar el objeto si el control no está ocultado
   if(CElement::IsVisible())
      m_sort_arrow.Timeframes(OBJ_ALL_PERIODS);
//--- Calcular y establecer la coordenada
   int x=m_columns[column].m_rows[0].X2()-m_sort_arrow_x_gap;
   m_sort_arrow.X(x);
   m_sort_arrow.X_Distance(x);
//--- Margen desde el punto extremo
   m_sort_arrow.XGap((m_anchor_right_window_side)? m_wnd.X2()-x : x-m_wnd.X());
  }

La llamada a este método va a ubicarse en el método CTable::UpdateTable(), en el bloque del código donde se realiza el desplazamiento de los encabezados. Abajo se muestra la versión reducida del método CTable::UpdateTable() con los fragmentos añadidos. Aquí, si en el primer ciclo ha sido encontrada la columna ordenada, se coloca la bandera y se hace el desplazamiento de la flecha de indicio. Después del fin del ciclo, si resulta que la columna ordenada ya existe pero no ha sido encontrada en el ciclo anterior, eso puede significar que ha salido fuera de la zona de visibilidad y hay que ocultarla. Si es la primera columna (índice cero) y al mismo tiempo está fijada contra el desplazamiento, la flecha de indicio se coloca en ella.

//+------------------------------------------------------------------+
//| Actualización de datos de la tabla de acuerdo con últimos cambios           |
//+------------------------------------------------------------------+
void CTable::UpdateTable(void)
  {
//...

//--- Mover los encabezados en la fila superior
   if(m_fix_first_row)
     {
      //--- Para determinar el desplazamiento del icono del ordenamiento
      bool is_shift_sort_arrow=false;
      //--- Columnas
      for(uint c=l; c<m_visible_columns_total; c++)
        {
        //--- Si no salimos fuera del rango
         if(h>=l && h<m_columns_total)
           {
            //--- Si hemos encontrado la columna ordenada
            if(!is_shift_sort_arrow && m_is_sort_mode && h==m_is_sorted_column_index)
              {
               is_shift_sort_arrow=true;
               //--- Corregimos el icono del ordenamiento
               uint column=h-(h-c);
               if(column>=l && column<m_visible_columns_total)
                  ShiftSortArrow(column);
              }

            //--- Corrección de (1) valores, (2) color del fondo, (3) color del texto y (4) alineación del texto en las celdas
            SetCellParameters(c,0,m_vcolumns[h].m_vrows[0],m_headers_color,m_headers_text_color,m_vcolumns[h].m_text_align[0]);
           }
         //---
         h++;
        }
      //--- Si la columna ordenada existe, pero no ha sido encontrada
      if(!is_shift_sort_arrow && m_is_sort_mode && m_is_sorted_column_index!=WRONG_VALUE)
        {
         //--- Ocultar si el índice es superior a cero
         if(m_is_sorted_column_index>0 || !m_fix_first_column)
            m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
         //--- Determinar el encabezado de la primera columna
         else
            ShiftSortArrow(0);
        }

     }
//...

  }

Al final del artículo, Usted puede descargar los archivos con el EA de prueba en el que se puede probar por sí mismo cómo funciona eso.

 

Otras actualizaciones de la librería

En el bild han sido incluidas las siguientes actualizaciones de la librería que suponen posibilidades adicionales:

1. Ahora se puede establecer la fuente y su tamaño para cada control. Para eso los campos y métodos correspondientes han sido incluidos en la clase base de los controles CElement. Por defecto, ha sido establecida la fuente «Calibri», y el tamaño de la fuente es de 8 puntos

class CElement
  {
protected:
   //--- Fuente
   string            m_font;
   int               m_font_size;
   //---
public:
   //--- (1) Fuente y (2) tamaño de la fuente
   void              Font(const string font)                         { m_font=font;                          }
   string            Font(void)                                const { return(m_font);                       }
   void              FontSize(const int font_size)                   { m_font_size=font_size;                }
   int               FontSize(void)                            const { return(m_font_size);                  }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CElement::CElement(void) : m_font("Calibri"),
                           m_font_size(8)
  {
  }

Por tanto, en todos los métodos de creación de los controles donde es necesario indicar la fuente, los valores se cogen de la clase base. Abajo se muestra el ejemplo para la etiqueta de texto de la clase CCheckBox. Lo mismo ha sido hecho en todas las clases de la librería. 

//+------------------------------------------------------------------+
//| Crea la etiqueta de texto del checkbox                                 |
//+------------------------------------------------------------------+
bool CCheckBox::CreateLabel(void)
  {
//--- Formación del nombre del objeto
   string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id();
//--- Coordenadas
   int x =(m_anchor_right_window_side)? m_x-m_label_x_gap : m_x+m_label_x_gap;
   int y =(m_anchor_bottom_window_side)? m_y-m_label_y_gap : m_y+m_label_y_gap;
//--- Color del texto dependiendo del estado
   color label_color=(m_check_button_state) ? m_label_color : m_label_color_off;
//--- Establecemos el objeto
   if(!m_label.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- Establecemos las propiedades
   m_label.Description(m_label_text);
   m_label.Font(CElement::Font());
   m_label.FontSize(CElement::FontSize());

   m_label.Color(label_color);
   m_label.Corner(m_corner);
   m_label.Anchor(m_anchor);
   m_label.Selectable(false);
   m_label.Z_Order(m_zorder);
   m_label.Tooltip("\n");
//--- Márgenes desde el punto extremo
   m_label.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
   m_label.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Inicialización del array del gradiente
   CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array);
//--- Guardamos el puntero del objeto
   CElement::AddToArray(m_label);
   return(true);
  }

 

2. Ahora los márgenes desde el punto extremo del formulario hay que enviar directamente al método de la creación del control para cada control de la interfaz gráfica. El cálculo será automático. Abajo se mustra el ejemplo del método de creación del calendario desplegable desde la clase personalizada CProgram

//+------------------------------------------------------------------+
//| Crea el calendario desplegable                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar(const int x_gap,const int y_gap,const string text)
  {
//--- Pasar el objeto del panel
   m_drop_calendar.WindowPointer(m_window);
//--- Adjuntar a la segunda pestaña
   m_tabs.AddToElementsArray(1,m_drop_calendar);
//--- Establecemos las propiedades antes de la creación
   m_drop_calendar.XSize(140);
   m_drop_calendar.YSize(20);
   m_drop_calendar.AreaBackColor(clrWhite);
//--- Creamos el control
   if(!m_drop_calendar.CreateDropCalendar(m_chart_id,m_subwin,text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_drop_calendar);
   return(true);
  }

 


Aplicación para la prueba del control

Ahora vamos a escribir una aplicación MQL de prueba en la que Ud. podrá probar todos los controles nuevos. Vamos a crear una interfaz gráfica en la que el menú principal (CMenuBar) va a tener los menús contextuales desplegables, barra de estado y dos pestañas. La primera pestaña tendrá una tabla tipo CTable con el modo de ordenamiento. 

En esta tabla, las tres primeras columnas tendrán los siguientes tipos de datso:

Por defecto, las demás columnas se quedan con el tipo TYPE_STRING. En la captura de pantalla de abajo se muestra el aspecto de la interfaz gráfica con la tabla en la primera pestaña. 

Fig. 7. Ejemplo de la tabla ordenada (ascendiente) por la primera columna.

Fig. 7. Ejemplo de la tabla ordenada (ascendiente) por la primera columna.


Crearemos cuatro controles en la segunda pestaña: 

  • Calendario desplegable (clase Clase CDropCalendar).
  • Control «Hora» (clase CTimeEdit).
  • Lista de los checkbox (clase CCheckBoxList).
  • Lista simple (clase CListView).

Abajo se muestra cómo se ve eso en la interfaz gráfica de una aplicación MQL:

Fig. 8. Controles de la segunda pestaña.

Fig. 8. Controles de la segunda pestaña.


El código fuente de esta aplicación de prueba se adjunta al final del artículo. 

 

Conclusión

En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema tiene el siguiente aspecto:

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

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


No es el último artículo en la serie sobre las interfaces gráficas. Su desarrollo continuará con adición de nuevas posibilidades. 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/2897

Archivos adjuntos |
Oscilador universal con interfaz gráfica Oscilador universal con interfaz gráfica
En el artículo se describe la creación de un indicador universal basado en todos los osciladores del terminal con una interfaz gráfica propia. Esto permitirá cambiar de forma rápida y cómoda los parámetros de cada oscilador por separado, directamente desde la ventana del gráfico (y sin abrir la ventana de propiedades), comparar sus índices y elegir el óptimo para usted para una tarea concreta.
Ejemplo de desarrollo de una estrategia con spread para futuros en la bolsa de Moscú Ejemplo de desarrollo de una estrategia con spread para futuros en la bolsa de Moscú
MetaTrader 5 permite desarrollar y simular robots que comercien simultáneamente en varios instrumentos. El simulador de estrategias incorporado en la plataforma descarga de forma automática del servidor comercial del bróker la historia de ticks y tiene en cuenta las especificaciones de los contratos: el desarrollador no tiene que hacer nada con sus propias manos. Esto permite reproducir todas las condiciones del entorno comercial de forma fácil y extraordinariamente fiable. MetaTrader 5 permite desarrollar y poner a prueba robots, incluso simulando intervalos de milisegundos entre la llegada de ticks de diferentes símbolos. En este artículo mostraremos cómo realizar el desarrollo y la simulación de una estretegia de spread con dos futuros de la bolsa de Moscú.
Distribuciones estadísticas en forma de histogramas sin búferes de indicador y matrices Distribuciones estadísticas en forma de histogramas sin búferes de indicador y matrices
En el artículo se estudia la posibilidad de crear los histogramas de las distribuciones estadísticas de las características del mercado usando memoria gráfica, es decir, sin usar búferes de indicador y matrices. Se adjuntan ejemplos detallados de la construcción de este tipoo de histogramas y se muestra la llamada funcionalidad "oculta" de los objetos gráficos del lenguaje MQL5.
LifeHack para tráders: Informe comparativo de varias simulaciones LifeHack para tráders: Informe comparativo de varias simulaciones
En el artículo se analiza el inicio simultáneo de la simulación del asesor en cuatro símbolos diferentes. La comparación final de los cuatro informes de la simulación se realizará en un recuadro, como sucede al elegir los productos en una tienda online. Como bonus adicional, se muestran los gráficos de distribución creados de forma automática para cada símbolo.