Experto comercial con interfaz gráfica: Creación del panel (Parte I)

Anatoli Kazharski | 13 septiembre, 2018

Contenido

Introducción

A pesar del activo desarrollo del trading algorítmico, muchos tráders hasta ahora prefieren el comercio manual. Pero en nuestro caso, difícilmente podremos evitar la automatización de operaciones rutinarias.

En el artículo se muestra la creación de un experto de señal multisímbolo para el comercio manual. Como ejemplo, analizaremos las señales del indicador Stochastic del paquete estándar de la biblioteca. Este código podemos utilizarlo para crear nuestros propios expertos con interfaz gráfica: podemos conectarle cualquier otro indicador o usar los resultados de ciertos cálculos para tomar decisiones.

Para aquellos que ejecutan trabajos por encargo, este artículo puede ser útil como ejemplo de tarea técnica para mostrar a los clientes. Es posible que el ejemplo les ayude a ahorrar tiempo al preparar una tarea técnica comprensible para el programador a la hora de desarrollar un programa de interfaz gráfica.

Vamos a enumerar las cuestiones a analizar con detalle en este trabajo.

El artículo se publicará en dos partes. En el presente artículo se analizará la creación de un panel, mientras que en la segunda parte se describirá la funcionalidad que lo llenará.

Elementos de la interfaz gráfica

Comenzaremos el desarrollo del experto por la construcción de la interfaz gráfica, con cuya ayuda se implementará la interacción con el usuario y la visualización de los datos. Podemos crear la interfaz gráfica usando las posibilidades de la bilioteca estándar, pero en nuestro ejemplo, se implementará sobre la base de EasyAndFast. Sus amplias posibilidades permiten concentrarse en la funcionalidad del propio programa sin distraerse en la mejora de su parte gráfica.

Primero dibujaremos el esquema general de la interfaz gráfica. En el esquema de abajo mostramos que en la ventana de la interfaz gráfica hay un grupo de dos pestañas. En las listas se muestran las funciones que debemos ubicar en ellas. Se trata de un ejemplo simplificado: el cliente y el ejecutor podrán trabajarlo con más detalle durante la discusión.

Fig. 1 - Esquema general de la interfaz gráfica con aclaraciones.

Fig. 1. Esquema general de la interfaz gráfica con aclaraciones.

Puede haber muchos elementos de control para la interfaz gráfica. Por eso, primero vamos a enumerarlos de forma jerárquica:

En la clase principal del programa (CProgram) declaramos los métodos y ejemplares de las clases de los elementos enumerados más arriba. El código de los métodos para la creación de elementos se muestra en un archivo aparte y se incluye en el archivo con la clase del programa MQL:

//+------------------------------------------------------------------+
//| Clase para crear la aplicación                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Ventana
   CWindow           m_window1;
   //--- Barra de estado
   CStatusBar        m_status_bar;
   //--- Pestañas
   CTabs             m_tabs1;
   //--- Campo de edición
   CTextEdit         m_symb_filter;
   CTextEdit         m_lot;
   CTextEdit         m_up_level;
   CTextEdit         m_down_level;
   CTextEdit         m_chart_scale;
   //--- Botones
   CButton           m_request;
   CButton           m_chart_shift;
   CButton           m_buy;
   CButton           m_sell;
   CButton           m_close_all;
   //--- Cuadros combinados
   CComboBox         m_timeframes;
   //--- Casilla de verificación
   CCheckBox         m_date_scale;
   CCheckBox         m_price_scale;
   CCheckBox         m_show_indicator;
   //--- Recuadros
   CTable            m_table_positions;
   CTable            m_table_symb;
   //--- Gráfico estándar
   CStandardChart    m_sub_chart1;
   //--- Barra de progreso
   CProgressBar      m_progress_bar;
   //---
public:
   //---Crea una interfaz gráfica
   bool              CreateGUI(void);
   //---
private:
   //--- Formulario
   bool              CreateWindow(const string text);
   //--- Barra de estado
   bool              CreateStatusBar(const int x_gap,const int y_gap);
   //--- Pestañas
   bool              CreateTabs1(const int x_gap,const int y_gap);
   //--- Campo de edición
   bool              CreateSymbFilter(const int x_gap,const int y_gap,const string text);
   bool              CreateLot(const int x_gap,const int y_gap,const string text);
   bool              CreateUpLevel(const int x_gap,const int y_gap,const string text);
   bool              CreateDownLevel(const int x_gap,const int y_gap,const string text);
   bool              CreateChartScale(const int x_gap,const int y_gap,const string text);
   //--- Botones
   bool              CreateRequest(const int x_gap,const int y_gap,const string text);
   bool              CreateChartShift(const int x_gap,const int y_gap,const string text);
   bool              CreateBuy(const int x_gap,const int y_gap,const string text);
   bool              CreateSell(const int x_gap,const int y_gap,const string text);
   bool              CreateCloseAll(const int x_gap,const int y_gap,const string text);
   //--- Cuadro combinado
   bool              CreateComboBoxTF(const int x_gap,const int y_gap,const string text);
   //--- Casilla de verificación
   bool              CreateDateScale(const int x_gap,const int y_gap,const string text);
   bool              CreatePriceScale(const int x_gap,const int y_gap,const string text);
   bool              CreateShowIndicator(const int x_gap,const int y_gap,const string text);
   //--- Recuadros
   bool              CreatePositionsTable(const int x_gap,const int y_gap);
   bool              CreateSymbolsTable(const int x_gap,const int y_gap);
   //--- Gráfico estándar
   bool              CreateSubChart1(const int x_gap,const int y_gap);
   //--- Barra de progreso
   bool              CreateProgressBar(const int x_gap,const int y_gap,const string text);
  };
//+------------------------------------------------------------------+
//| Métodos para crear los elementos de control                      |
//+------------------------------------------------------------------+
#include "CreateGUI.mqh"
//+------------------------------------------------------------------+

A continuación, vamos a analizar el montaje de la interfaz gráfica, los métodos para crear sus elementos y sus propiedades.

Montaje de la interfaz gráfica

Bien, en la interfaz gráfica de la aplicación desarrollada se usarán elementos de diez tipos diferentes.

Vamos a necesitar más de un elemento en algunas categorías de esta lista, por eso analizaremos uno solo de cada grupo. En el mismo orden vamos a estudiar los métodos para crearlos. 

Formulario para los elementos de control

Más abajo mostramos el código del método para crear el formulario en el que se ubicarán los demás elementos. Al principio hay que añadir el formulario a la lista de elementos de la interfaz gráfica del programa. Para ello, debemos llamar el método CWndContainer::AddWindow(), transmitiendo al mismo el objeto del elemento del tipo CWindow. A continuación, antes de crear un formulario, establecemos sus propiedades. Indicamos las siguientes propiedades (en el mismo orden que en la lista de abajo):

Después de que las propiedades hayan sido establecidas, hay que llamar el método de creación del formulario CWindow::CreateWindow() y transmitir al mismo:

//+------------------------------------------------------------------+
//| Crea el formulario para los elementos de control                 |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Añadimos el puntero de la ventana a la matriz de ventanas
   CWndContainer::AddWindow(m_window1);
//--- Propiedades
   m_window1.XSize(750);
   m_window1.YSize(450);
   m_window1.FontSize(9);
   m_window1.IsMovable(true);
   m_window1.ResizeMode(true);
   m_window1.CloseButtonIsUsed(true);
   m_window1.CollapseButtonIsUsed(true);
   m_window1.TooltipsButtonIsUsed(true);
   m_window1.FullscreenButtonIsUsed(true);
//--- Establecemos las pistas emergentes
   m_window1.GetCloseButtonPointer().Tooltip("Close");
   m_window1.GetTooltipButtonPointer().Tooltip("Tooltips");
   m_window1.GetFullscreenButtonPointer().Tooltip("Fullscreen");
   m_window1.GetCollapseButtonPointer().Tooltip("Collapse/Expand");
//--- Creando el formulario
   if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,1,1))
      return(false);
//---
   return(true);
  }

Cada vez que se añada un nuevo elemento de la interfaz gráfica, sería deseable compilar el programa y ver qué resultado obtenemos: 

Fig. 2 - Formulario para los elementos de control.

Fig. 2. Formulario para los elementos de control.

Más abajo se mostrarán las capturas de pantalla de todos los resultados intermedios.

Barra de estado

El código del método para la creación de la barra comienza por la indicación del elemento principal. A partir de él, se calcularán el posicionamiento y la alineación según el tamaño de los elementos asociados al mismo. Esto ahorrará tiempo a la hora de desarrollar la aplicación: será posible desplazar un grupo completo de elementos relacionados con solo modificar las coordenadas del principal. Para anclar un elemento, su puntero se transmite al método CElement::MainPointer(). En este ejemplo, estamos anclando la barra de estado al formulario, por eso transmitimos el objeto del formulario al método.

A continuación, establecemos las propiedades de la barra de estado. En esta, crearemos tres puntos en los que se mostrará la información para el ususario. 

Después de que hayamos indicado las propiedades, creamos el elemento. Ahora que ya está preparado para el trabajo, podemos cambiar el texto en sus puntos mientras se ejecuta el programa. En nuestro ejemplo, definimos en el primer punto el texto «For Help, press F1»

Al final del método, tenemos que guardar necesariamente el puntero del elemento creado en la lista general de elementos de la interfaz gráfica. Para ello, llamamos el método CWndContainer::AddToElementsArray() y le transmitimos el índice del formulario y el objeto de elemento. Puesto que solo tenemos un formulario, su índice será 0.

//+------------------------------------------------------------------+
//| Crea la barra de estado                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateStatusBar(const int x_gap,const int y_gap)
  {
#define STATUS_LABELS_TOTAL 3
//--- Guardamos el puntero a la ventana
   m_status_bar.MainPointer(m_window1);
//--- Propiedades
   m_status_bar.AutoXResizeMode(true);
   m_status_bar.AutoXResizeRightOffset(1);
   m_status_bar.AnchorBottomWindowSide(true);
//--- Indicamos cuántas partes debe haber y definimos sus propiedades
   int width[STATUS_LABELS_TOTAL]={0,200,110};
   for(int i=0; i<STATUS_LABELS_TOTAL; i++)
      m_status_bar.AddItem(width[i]);
//--- Creamos el elemento de control
   if(!m_status_bar.CreateStatusBar(x_gap,y_gap))
      return(false);
//--- Definimos el texto en los puntos de la barra de estado
   m_status_bar.SetValue(0,"For Help, press F1");
//--- Añadimos el objeto a la matriz general de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_status_bar);
   return(true);
  }

Los demás elementos de la biblioteca EasyAndFast se crean según el mismo principio. Por eso, a continuación, analizaremos solo las propiedades establecidas que se usarán en nuestro experto.

Fig. 3 - Adición de la barra de estado.

Fig. 3.  Adición de la barra de estado.

Grupo de pestañas

En el método para crear el grupo de pestañas, establecemos las siguientes propiedades para el elemento:

Aquí tenemos el código del método:

//+------------------------------------------------------------------+
//| Crea el grupo con pestañas 1                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs1(const int x_gap,const int y_gap)
  {
#define TABS1_TOTAL 2
//--- Guardamos el puntero al elemento principal
   m_tabs1.MainPointer(m_window1);
//--- Propiedades
   m_tabs1.IsCenterText(true);
   m_tabs1.PositionMode(TABS_TOP);
   m_tabs1.AutoXResizeMode(true);
   m_tabs1.AutoYResizeMode(true);
   m_tabs1.AutoXResizeRightOffset(3);
   m_tabs1.AutoYResizeBottomOffset(25);
//--- Añadimos las pestañas con las propiedades indicadas
   string tabs_names[TABS1_TOTAL]={"Trade","Positions"};
   for(int i=0; i<TABS1_TOTAL; i++)
      m_tabs1.AddTab(tabs_names[i],100);
//--- Creamos el elemento de control
   if(!m_tabs1.CreateTabs(x_gap,y_gap))
      return(false);
//--- Añadimos el objeto a la matriz general de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_tabs1);
   return(true);
  }

Fig. 4 - Adición del grupo de pestañas.

Fig. 4. Adición del grupo de pestañas.

Campo de edición

Como ejemplo, analizaremos el campo de edición en el que el usuario puede indicar las divisas y/o parejas de divisas para formar la lista de símbolos en el recuadro. El elemento principal para el mismo será un grupo de pestañas. Aquí tenemos que indicar en qué pestaña debemos representar este campo de edición. Para ello, llamaremos el método CTabs::AddToElementsArray(), transmitiremos al mismo el índice de la pestaña y el objeto del elemento unido.

A continuación, analizaremos las propiedades indicadas para este campo de edición.

El elemento del tipo CTextEdit consta de varios elementos. Por eso, si es necesario cambiar sus propiedades, existe la posibilidad de obtener los punteros al mismo. Necesitaremos cambiar ciertaspropiedades del campo de edición de texto (CTextBox). Vamos a analizarlas en el mismo orden en que se realiza en la lista de código más abajo.

Por defecto, la casilla de verificación del campo de edición se encuentra activada. Para hacer esto, debemos activarla justo después de crear el elemento.

//+--------------------------------------------------------------------------+
//| Crea la casilla de verificación con el campo de edición "Symbols filter" |
//+--------------------------------------------------------------------------+
bool CProgram::CreateSymbolsFilter(const int x_gap,const int y_gap,const string text)
  {
//--- Guardamos el puntero al elemento principal
   m_symb_filter.MainPointer(m_tabs1);
//--- Fijar para la pestaña
   m_tabs1.AddToElementsArray(0,m_symb_filter);
//--- Propiedades
   m_symb_filter.SetValue("USD"); // "EUR,USD" "EURUSD,GBPUSD" "EURUSD,GBPUSD,AUDUSD,NZDUSD,USDCHF"
   m_symb_filter.CheckBoxMode(true);
   m_symb_filter.AutoXResizeMode(true);
   m_symb_filter.AutoXResizeRightOffset(90);
   m_symb_filter.GetTextBoxPointer().XGap(100);
   m_symb_filter.GetTextBoxPointer().AutoXResizeMode(true);
   m_symb_filter.GetTextBoxPointer().AutoSelectionMode(true);
   m_symb_filter.GetTextBoxPointer().DefaultText("Example: EURUSD,GBP,NOK");
//--- Creamos el elemento de control
   if(!m_symb_filter.CreateTextEdit(text,x_gap,y_gap))
      return(false);
//--- Activamos la casilla de verificación
   m_symb_filter.IsPressed(true);
//--- Añadimos el objeto a la matriz general de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_symb_filter);
   return(true);
  }

Aparte del campo de edición de texto, en la interfaz gráfica también tendremos campos de edición numéricos. Por ejemplo, el campo Lot (volumen para la apertura de posición). Para los campos de edición de este tipo hay que indicar otras propiedades.

Aquí tenemos el código de este método:

//+------------------------------------------------------------------+
//| Crea el campo de edición "Lot"                                   |
//+------------------------------------------------------------------+
bool CProgram::CreateLot(const int x_gap,const int y_gap,const string text)
  {
//--- Guardamos el puntero al elemento principal
   m_lot.MainPointer(m_tabs1);
//--- Fijar para la pestaña
   m_tabs1.AddToElementsArray(0,m_lot);
//--- Propiedades
   m_lot.XSize(80);
   m_lot.MaxValue(1000);
   m_lot.MinValue(0.01);
   m_lot.StepValue(0.01);
   m_lot.SetDigits(2);
   m_lot.SpinEditMode(true);
   m_lot.SetValue((string)0.1);
   m_lot.GetTextBoxPointer().XSize(50);
   m_lot.GetTextBoxPointer().AutoSelectionMode(true);
   m_lot.GetTextBoxPointer().AnchorRightWindowSide(true);
//--- Creamos el elemento de control
   if(!m_lot.CreateTextEdit(text,x_gap,y_gap))
      return(false);
//--- Añadimos el objeto a la matriz general de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_lot);
   return(true);
  }

Fig. 5 - Adición de los campos de edición.

Fig. 5.  Adición de los campos de edición.

La imagen tiene un aspecto bastante lógico, pero al añadir los demás elementos, todo resultará en su sitio.

Botón

Añadimos a la interfaz gráfica de nuestro experto varios botones. Analizaremos aquel de ellos en el se han implicado más propiedades: el botón para la apertura de posiciones SELL.

Las mismas propiedades se cambian también en el botón BUY, con la única diferencia de los matices establecidos para el fondo.

//+------------------------------------------------------------------+
//| Crea el botón 'Sell'                                             |
//+------------------------------------------------------------------+
bool CProgram::CreateSell(const int x_gap,const int y_gap,const string text)
  {
//--- Guardar el puntero al elemento principal
   m_sell.MainPointer(m_tabs1);
//--- Fijar para la pestaña
   m_tabs1.AddToElementsArray(0,m_sell);
//--- Propiedades
   m_sell.XSize(80);
   m_sell.IsCenterText(true);
   m_sell.BackColor(C'255,51,51');
   m_sell.BackColorHover(C'255,100,100');
   m_sell.BackColorPressed(C'195,0,0');
   m_sell.LabelColor(clrWhite);
   m_sell.LabelColorHover(clrWhite);
   m_sell.LabelColorPressed(clrWhite);
   m_sell.BorderColor(clrBlack);
   m_sell.BorderColorHover(clrBlack);
   m_sell.BorderColorPressed(clrBlack);
//--- Creamos el elemento de control
   if(!m_sell.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al elemento en la base
   CWndContainer::AddToElementsArray(0,m_sell);
   return(true);
  }

Fig. 6 - Adición de botones.

Fig. 6. Adición de botones.

Cuadro combinado con lista desplegable

Para cambiar el marco temporal, haremos un cuadro combinado con lista desplegable. Enumeramos las propiedades para su ajustes.

A continuación, a cada punto en la lista se le asignan valores, y después, se le asignan ciertas propiedades a la lista con la ayuda del puntero.

Aquí tenemos el código del método para crear este cuadro combinado:

//+------------------------------------------------------------------+
//| Creamos el cuadro combinado para elegir marcos temporales        |
//+------------------------------------------------------------------+
bool CProgram::CreateComboBoxTF(const int x_gap,const int y_gap,const string text)
  {
//--- Número total de puntos en la lista
#define ITEMS_TOTAL2 21
//--- Transmitir el objeto del panel
   m_timeframes.MainPointer(m_tabs1);
//--- Fijar para la pestaña
   m_tabs1.AddToElementsArray(0,m_timeframes);
//--- Propiedades
   m_timeframes.XSize(115);
   m_timeframes.ItemsTotal(ITEMS_TOTAL2);
   m_timeframes.AnchorRightWindowSide(true);
   m_timeframes.GetButtonPointer().XSize(50);
   m_timeframes.GetButtonPointer().AnchorRightWindowSide(true);
//--- Guardamos los valores de los puntos en la lista del cuadro combinado
   string items_text[ITEMS_TOTAL2]={"M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30","H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"};
   for(int i=0; i<ITEMS_TOTAL2; i++)
      m_timeframes.SetValue(i,items_text[i]);
//--- Obtenemos el puntero de la lista
   CListView *lv=m_timeframes.GetListViewPointer();
//--- Establecemos las propiedades de la lista
   lv.LightsHover(true);
   lv.SelectItem(18);
//--- Creamos el elemento de control
   if(!m_timeframes.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al elemento en la base
   CWndContainer::AddToElementsArray(0,m_timeframes);
   return(true);
  }

Fig. 7 - Añadir cuadro combinado.

Fig. 7. Añadir cuadro combinado.

Casilla de verificación

La casilla de verificación es el elemento más sencillo. Para este, basta con indicar dos propiedades.

Después de crear el elemento, podemos activar la casilla de verificación de forma programática.

//+------------------------------------------------------------------+
//| Crea la casilla de verificación "Date scale"                     |
//+------------------------------------------------------------------+
bool CProgram::CreateDateScale(const int x_gap,const int y_gap,const string text)
  {
//--- Guardamos el puntero a la ventana
   m_date_scale.MainPointer(m_tabs1);
//--- Fijar para la pestaña
   m_tabs1.AddToElementsArray(0,m_date_scale);
//--- Propiedades
   m_date_scale.XSize(70);
   m_date_scale.AnchorRightWindowSide(true);
//--- Creamos el elemento de control
   if(!m_date_scale.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- Activar la casilla de verificación
   m_date_scale.IsPressed(true);
//--- Añadimos el objeto a la matriz general de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_date_scale);
   return(true);
  }

Fig. 8 - Añadir casillas de verificación.

Fig. 8. Añadir casillas de verificación.

Recuadro

En la interfaz gráfica tendremos dos recuadros. Analizaremos aquel que hace visible la lista formada de símbolos y señales para la apertura de posiciones. Se ubicará en la primera pestaña. Primero declaramos e inicializamos las matrices para establecer las propiedades del recuadro. Ajustamos las siguientes propiedades.

El texto para los encabezados se puede indicar después de crear el recuadro:

//+------------------------------------------------------------------+
//| Crea el recuadro de símbolos                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsTable(const int x_gap,const int y_gap)
  {
#define COLUMNS1_TOTAL 2
#define ROWS1_TOTAL    1
//--- Guardamos el puntero al elemento principal
   m_table_symb.MainPointer(m_tabs1);
//--- Fijar para la pestaña
   m_tabs1.AddToElementsArray(0,m_table_symb);
//--- Matriz de anchura de las columnas
   int width[COLUMNS1_TOTAL]={95,58};
//--- Matriz de alineamiento del texto en columnas
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]={ALIGN_LEFT,ALIGN_RIGHT};
//--- Matriz de sangría del texto en las columnas según el eje X
   int text_x_offset[COLUMNS1_TOTAL]={5,5};
//--- Propiedades
   m_table_symb.XSize(168);
   m_table_symb.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table_symb.ColumnsWidth(width);
   m_table_symb.TextAlign(align);
   m_table_symb.TextXOffset(text_x_offset);
   m_table_symb.ShowHeaders(true);
   m_table_symb.SelectableRow(true);
   m_table_symb.ColumnResizeMode(true);
   m_table_symb.IsZebraFormatRows(clrWhiteSmoke);
   m_table_symb.AutoYResizeMode(true);
   m_table_symb.AutoYResizeBottomOffset(2);
//--- Creamos el elemento de control
   if(!m_table_symb.CreateTable(x_gap,y_gap))
      return(false);
//--- Establecemos los nombres de los encabezados
   m_table_symb.SetHeaderText(0,"Symbol");
   m_table_symb.SetHeaderText(1,"Values");
//--- Añadimos el objeto a la matriz general de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_table_symb);
   return(true);
  }

El segundo recuadro representa algunas propiedades de las posiciones abiertas. Sus diez columnas representarán los siguientes datos.

En el segundo recuadro hay que configurar de forma adicional las siguientes propiedades.

Las imágenes en las celdas de la primera columna simbolizarán los botones: al pulsar estos, podremos cerrar una posición o todas las posiciones - si se trata de una cuenta con cobertura - en el símbolo indicado. 

//+------------------------------------------------------------------+
//| Crea un recuadro de posiciones                                   |
//+------------------------------------------------------------------+
bool CProgram::CreatePositionsTable(const int x_gap,const int y_gap)
  {
...
//--- Propiedades
   m_table_positions.TableSize(COLUMNS2_TOTAL,ROWS2_TOTAL);
   m_table_positions.ColumnsWidth(width);
   m_table_positions.TextAlign(align);
   m_table_positions.TextXOffset(text_x_offset);
   m_table_positions.ImageXOffset(image_x_offset);
   m_table_positions.ImageYOffset(image_y_offset);
   m_table_positions.ShowHeaders(true);
   m_table_positions.IsSortMode(true);
   m_table_positions.SelectableRow(true);
   m_table_positions.ColumnResizeMode(true);
   m_table_positions.IsZebraFormatRows(clrWhiteSmoke);
   m_table_positions.AutoXResizeMode(true);
   m_table_positions.AutoYResizeMode(true);
   m_table_positions.AutoXResizeRightOffset(2);
   m_table_positions.AutoYResizeBottomOffset(2);
...
   return(true);
  }

Fig. 9 - Añadir un recuadro en la primera pestaña.

Fig. 9. Añadir un recuadro en la primera pestaña.

Fig. 10 - Añadir un recuadro en la segunda pestaña.

Fig. 10.  Añadir un recuadro en la segunda pestaña.

Hablaremos sobre cómo trabajar con recuadros en el archivo principal del programa (CProgram) en uno de los siguientes apartados del artículo.

Gráfico estándar

Para visualizar los datos de los símbolos se ha pensado un elemento del tipo CStandardChart. Por defecto, se mostrará el gráfico EURUSD en el marco temporal diario. Tiene las siguientes propiedades.

En caso necesario, podemos crear una matriz de gráficos construidos en la fila horizontal. Para ello, use el método CStandardChart::AddSubChart(), transmitiendo en el mismo el símbolo y el marco temporal como argumentos. Pero en este caso, necesitaremos solo un gráfico; los símbolos y marcos temporales los alternaremos con la ayuda de otros elementos de control.

//+------------------------------------------------------------------+
//| Crea el gráfico estándar 1                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateSubChart1(const int x_gap,const int y_gap)
  {
//--- Guardamos el puntero a la ventana
   m_sub_chart1.MainPointer(m_tabs1);
//--- Fijar para la 1-era pestaña
   m_tabs1.AddToElementsArray(0,m_sub_chart1);
//--- Propiedades
   m_sub_chart1.XScrollMode(true);
   m_sub_chart1.AutoXResizeMode(true);
   m_sub_chart1.AutoYResizeMode(true);
   m_sub_chart1.AutoXResizeRightOffset(125);
   m_sub_chart1.AutoYResizeBottomOffset(2);
//--- Añadimos los gráficos
   m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1);
//--- Creamos el elemento de control
   if(!m_sub_chart1.CreateStandardChart(x_gap,y_gap))
      return(false);
//--- Añadimos el objeto a la matriz general de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_sub_chart1);
   return(true);
  }

Fig. 11 - Adición del gráfico.

Fig. 11.  Adición del gráfico.

Barra de progreso

Para que el usuario entienda qué está haciendo ahora el programa, añadiremos a la interfaz gráfica el indicador de progreso. Aquí tenemos las propiedades de nuestro ejemplo (en el mismo orden que en el código).

Más abajo, mostramos ejemplos del uso del indicador de progreso.

//+------------------------------------------------------------------+
//| Crea una barra de progreso                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateProgressBar(const int x_gap,const int y_gap,const string text)
  {
//--- Guardamos el puntero al elemento principal
   m_progress_bar.MainPointer(m_status_bar);
//--- Propiedades
   m_progress_bar.YSize(17);
   m_progress_bar.BarYSize(14);
   m_progress_bar.BarXGap(0);
   m_progress_bar.BarYGap(1);
   m_progress_bar.LabelXGap(5);
   m_progress_bar.LabelYGap(2);
   m_progress_bar.PercentXGap(5);
   m_progress_bar.PercentYGap(2);
   m_progress_bar.IsDropdown(true);
   m_progress_bar.Font("Consolas");
   m_progress_bar.BorderColor(clrSilver);
   m_progress_bar.IndicatorBackColor(clrWhiteSmoke);
   m_progress_bar.IndicatorColor(clrLightGreen);
   m_progress_bar.AutoXResizeMode(true);
   m_progress_bar.AutoXResizeRightOffset(2);
//--- Creación del elemento
   if(!m_progress_bar.CreateProgressBar(text,x_gap,y_gap))
      return(false);
//--- Añadimos el puntero al elemento en la base
   CWndContainer::AddToElementsArray(0,m_progress_bar);
   return(true);
  }

Hemos analizado todos los elementos de control que se usarán en la interfaz gráfica de nuestro experto. En este momento, se trata simplemente de un envoltorio gráfico. Pero, más tarde, escribiremos los métodos necesarios para que todo funcione de acuerdo con la idea incial.

Actualizaciones de la biblioteca EasyAndFast

En la biblioteca EasyAndFasten la clase CTable, se ha desarrollado por completo el método público CTable::SortData(). Ahora, como segundo argumento podemos indicar la dirección de clasificación del recuadro (parámetro no obligatorio). Antes, una nueva llamada del método CTable::SortData() comenzaba la clasificación en la dirección opuesta a la actual. Asimismo, se han añadido los métodos para obtener la dirección de clasificación actual y el índice de la columna clasificada. Ahora, si el recuadro ha sido clasificado por el usuario manualmente (con un clic en el encabezado), y a continuación los datos del recuadro no han sido actualizados en el mismo orden, entonces, tras aclarar la dirección de clasificación actual, podemos restablecerlo. 

//+------------------------------------------------------------------+
//| Clase para crear el recuadro dibujado                            |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
public:
...
   //--- Clasificar los datos de la columna indicada
   void              SortData(const uint column_index=0,const int direction=WRONG_VALUE);
   //--- (1) Dirección de clasificación actual, (2) índice de la matriz clasificada
   int               IsSortDirection(void)             const { return(m_last_sort_direction);    }
   int               IsSortedColumnIndex(void)         const { return(m_is_sorted_column_index); }
...
  };
//+------------------------------------------------------------------+
//| Clasificar los datos de la columna indicada                      |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0,const int direction=WRONG_VALUE)
  {
//--- Salir, si salimos de los límites del recuadro
   if(column_index>=m_columns_total)
      return;
//--- Índice a partir del cual hay que comenzar la clasificación
   uint first_index=0;
//--- Último índice
   uint last_index=m_rows_total-1;
//--- Sin control de dirección por parte del usuario
   if(direction==WRONG_VALUE)
     {
      //--- La primera vez se clasificará en orden creciente, y a continuación, todas las veces en dirección opuesta
      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;
     }
   else
     {
      m_last_sort_direction=(ENUM_SORT_MODE)direction;
     }
//--- Recordamos el índice de la última columna de datos clasificada
   m_is_sorted_column_index=(int)column_index;
//--- Clasificando
   QuickSort(first_index,last_index,column_index,m_last_sort_direction);
  }

Se ha hecho otra pequeña adición a la clase CKeys en el método CKeys::KeySymbol(). Anteriormente, el teclado numérico (un bloque de teclas aparte en la zona derecha del teclado) no se procesaba. Ahora podemos introducir cifras desde esta parte del teclado, y también símbolos especiales.

//+------------------------------------------------------------------+
//| Retorna el símbolo de la tecla pulsada                           |
//+------------------------------------------------------------------+
string CKeys::KeySymbol(const long key_code)
  {
   string key_symbol="";
//--- Si hay que introducir un espacio (la tecla "Space")
   if(key_code==KEY_SPACE)
     {
      key_symbol=" ";
     }
//--- Si hay que introducir (1) un símbolo alfabético o (2) un símbolo del teclado numérico o (3) un símbolo especial
   else if((key_code>=KEY_A && key_code<=KEY_Z) ||
           (key_code>=KEY_0 && key_code<=KEY_9) ||
           (key_code>=KEY_NUMLOCK_0 && key_code<=KEY_NUMLOCK_SLASH) ||
           (key_code>=KEY_SEMICOLON && key_code<=KEY_SINGLE_QUOTE))
     {
      key_symbol=::ShortToString(::TranslateKey((int)key_code));
     }
//--- Retornar símbolo
   return(key_symbol);
  }

Las nuevas versiones de las clases CTable y CKeys podemos descargarlas al final del artículo.

Conclusión

Usted ya ha leído la primera parte del artículo. En esta hemos analizado cómo podemos crear sin grandes esfuerzos interfaces gráficas de cualquier nivel de complejidad para nuestros programas. Usted puede seguir desarrollado este programa y usarlo para sus propios propósitos. En la segunda parte del artículo mostraremos cómo trabajar con la interfaz gráfica y, lo más importante, cómo llenarla con una funcionalidad. 

Más abajo podrá descargar a su computadora los archivos para realizar las pruebas y analizar con más detalle el código adjunto al artículo.

Nombre del archivo Comentarios
MQL5\Experts\TradePanel\TradePanel.mq5 Experto comercial para el comercio manual con la interfaz gráfica
MQL5\Experts\TradePanel\Program.mqh Archivo con la clase del programa
MQL5\Experts\TradePanel\CreateGUI.mqh Archivo con la implementación de los métodos para crear una interfaz gráfica a partir de la clase de programa en el archivo Program.mqh
MQL5\Include\EasyAndFastGUI\Controls\Table.mqh Clase actualizada CTable
MQL5\Include\EasyAndFastGUI\Keys.mqh Clase actualizada CKeys