Interfaces gráficas X: Control "Hora", control "Lista de las casillas de verificación" y ordenamiento (sort) de la tabla (build 6)
Índice
- Introducción
- Control «Hora»
- Clase para la creación del control «Hora»
- Control “Lista con casillas de verificación”
- Clase para crear la lista con casillas de verificación
- Ordenamiento de la tabla
- Otras actualizaciones de la librería
- Aplicación para la prueba del control
- Conclusión
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.
{
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:
{
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»:
- Fondo general del control
- Barra de desplazamiento vertical
- 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:
{
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).
{
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():
{
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.
{
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.
{
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.
{
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:
{
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():
{
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.
{
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().
{
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.
{
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.
{
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.
{
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().
{
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.
{
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:
- La primera columna – TYPE_DATETIME.
- La segunda columna – TYPE_DOUBLE.
- La tercera columna – TYPE_LONG.
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.
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.
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.
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
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso