Descargar MetaTrader 5

Aplicación de los contenedores para componer la interfaz gráfica: clase CBox

9 octubre 2015, 09:12
Enrico Lambino
0
451

Índice


1. Introducción

El posicionamiento absoluto de los elementos de control dentro de la ventana de diálogo es la manera más sencilla de crear la interfaz gráfica para una aplicación. No obstante, a veces este enfoque de diseñar la interfaz gráfica de usuario puede ser inconveniente e incluso prácticamente contraproducente. En este artículo se describe el método alternativo de creación de la interfaz gráfica a base de los esquemas de composición y contenedores usando el gestor de composición, a saber, la clase CBox.

La clase del gestor de composición que ha sido implementada y que se utiliza en este artículo parece en términos generales a las clases utilizadas en algunos lenguajes de programación convencionales, como por ejemplo BoxLayout (Java) y Pack geometry manager (Python/Tkinter).


2. Objetivos

Si nos fijamos en los ejemplos SimplePanel y Controls disponibles en MetaTrader 5, veremos que los elementos de control de estos paneles se ubican píxel por píxel (posicionamiento absoluto). Cada elemento de control tiene una determinada posición en el área de cliente, y el posicionamiento de cada elemento depende de la posición del elemento creado anteriormente, tomando en cuenta las sangrías adicionales. Aunque este enfoque es el más natural y lógico, en la mayoría de los casos este nivel de precisión no es necesario, y el uso de este método de posicionamiento tiene sus desventajas en muchos aspectos.

Cualquier programador con suficiente experiencia podrá diseñar una interfaz gráfica con elementos de control posicionados en los píxeles definidos con precisión. Sin embargo, aquí nos encontramos con las siguientes desventajas:

  • Por lo común, el cambio de la posición o tamaño de uno de los componentes no puede no afectar a los demás componentes.
  • La mayor parte del código no conviene para el uso reiterativo, lo que quiere decir que unas pequeñas modificaciones de la interfaz a veces requieren unos importantes cambios del código.
  • Este método puede ocupar mucho tiempo, especialmente al crear las interfaces más complicadas.

Eso nos a llevado a la creación de un sistema de composición que dispone de las siguientes características:

  • El código tiene que ser reusable.
  • La alteración de una parte de la interfaz debe tener el mínimo impacto sobre otros componentes.
  • La distribución de los componentes dentro de la interfaz debe calcularse automáticamente.

En este artículo se muestra la implementación de este sistema usando el contenedor, es decir, la clase CBox.


3. Clase CBox

La clase CBox actúa como contenedor o bloque: en este bloque se añaden los elementos de control, y CBox calcula automáticamente su ubicación dentro de los límites del espacio asignado. Normalmente, cuando se usa la clase CBox, se aplica la siguiente composición:

Composición de CBox

Fig. 1. Composición de CBox

El bloque externo representa el tamaño del contenedor entero, mientras que el bloque punteado dentro de él determina los límites de la sangría. El tamaño de la sangría está representado con el color azul. El área blanca restante es el espacio destinado para la distribución de los elementos de control dentro del contenedor.

Dependiendo de la complejidad del panel, se puede utilizar la clase CBox de varias maneras. Por ejemplo, un contenedor (CBox) puede contener otro contenedor en el que van a ubicarse los elementos de control. O bien, el contenedor puede incluir los elementos de control y otro contenedor. Sea como sea, recomiendo insistentemente que los contenedores se utilicen como elementos del mismo nivel en un contenedor padre.

CBox se crea extendiendo la clase CWndClient (sin barras de desplazamiento), tal como se muestra en el siguiente fragmento del código:

#include <Controls\WndClient.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CBox : public CWndClient
  {
public:
                     CBox();
                    ~CBox();   
   virtual bool      Create(const long chart,const string name,const int subwin,
                           const int x1,const int y1,const int x2,const int y2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBox::CBox() 
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBox::~CBox()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CBox::Create(const long chart,const string name,const int subwin,
                  const int x1,const int y1,const int x2,const int y2)
  {
   if(!CWndContainer::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
   if(!CreateBack())
      return(false);
   if(!ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG))
      return(false);
   if(!ColorBorder(clrNONE))
      return(false);
   return(true);
  }
//+------------------------------------------------------------------+

La clase CBox también puede heredar directamente la clase CWndContainer. No obstante, eso puede privar la clase de algunas propiedades útiles, como por ejemplo, la visualización del fondo y borde. Se puede crear otra versión más simple mediante la extensión directa de la clase CWndObj, pero para eso será necesario añadir la instancia CArrayObj como uno de sus miembros privados o protegidos y recrear los métodos de la clase, inclusive los objetos que deben guardarse dentro.


3.1. Estilos de composición

CBox ofrece dos estilos de composición: horizontal y vertical.

La composición base del estilo horizontal es la siguiente:

Estilo horizontal de la clase Clase CBox

Fig. 2. Estilo horizontal (centrado)

La composición base del estilo vertical es la siguiente:

Estilo vertical de la clase Clase CBox

Fig. 3. Estilo vertical (centrado)

Por defecto, CBox utiliza el estilo horizontal.

La combinación de estos tipos de composición (es posible el uso de varios contenedores) permite recrear prácticamente cualquier tipo de la interfaz gráfica. Es más, la distribución de los elementos dentro de los contenedores hace posible el diseño segmentado. En otras palabras, Usted puede cambiar el tamaño y posición de los elementos de control en un determinado contenedor sin afectar estos parámetros de los elementos en otros contenedores.

Para la implementación del estilo horizontal y vertical en CBox, hay que declarar la enumeración que luego va a guardarse como uno de los miembros de la clase especificada:

enum LAYOUT_STYLE
  {
   LAYOUT_STYLE_VERTICAL,
   LAYOUT_STYLE_HORIZONTAL
  };

3.2. Cálculo del espacio entre los elementos de control

CBox incrementará al máximo el espacio interno y lo usará para la distribución uniforme de los elementos de control existentes, tal como se muestra en las imágenes anteriores.

Si nos fijamos en estos imágenes, podemos deducir una fórmula para calcular el espacio entre los elementos de control en un determinado contenedor CBox, usando el pseudocódigo que va a continuación:

composición horizontal:
espacio x = ((espacio disponible x)-(tamaño total x de todos los elementos de control))/(número total de los elementos de control + 1)
espacio y = ((espacio disponible y)-(tamaño y del elemento de control))/2

composición vertical:
espacio x = ((espacio disponible x)-(tamaño x del elemento de control))/2
espacio y = ((espacio disponible y)-(tamaño total y de todos los elementos de control))/(número total de los elementos de control + 1)

3.3. Alineación

El cálculo del espacio entre los elementos de control mostrado en la sección anterior se realiza sólo en el caso de la alineación por el centro. Probablemente, vamos a necesitar más tipos de alineación en la clase CBox. Por eso es necesario modificar un poco el método del cálculo.

En caso del estilo horizontal, a parte de la alineación centrada, también existe la alineación por la izquierda, por la derecha y por el centro sin espacios a los lados, como se puede ve más abajo:

Estilo horizontal (alineación por la izquierda)

Fig. 4. Estilo horizontal (alineación por la izquierda)

Estilo horizontal (alineación por la derecha)

Fig. 5. Estilo horizontal (alineación por la derecha)

Estilo horizontal (alineación por el centro, sin espacios a los lados)

Fig. 6. Estilo horizontal (por el centro, sin espacios a los lados)


En caso del estilo vertical, a parte de la alineación centrada, también existe la alineación por la parte superior, por la parte inferior y por el centro sin espacios arriba y abajo, como se puede ve más abajo:

Estilo vertical (alineación por la parte superior) Estilo vertical (alineación por el centro, sin espacios arriba y abajo) Estilo vertical (alineación por la parte inferior)

Fig. 7. Estilo vertical: (a la izquierda) alineación por la parte superior, (en el medio) por el centro, sin espacios arriba y abajo, (a la derecha) alineación por la parte inferior

Obsérvese que la clase CBox debe calcular automáticamente el espacio por X y Y entre los elementos de control basándose en el tipo de alineación. Por eso en vez usar el divisor

(número total de los elementos de control + 1)

utilizado para crear el espacio entre los controles, usamos el número total de los elementos de control para la alineación asimétrica (por el lado derecho, izquierdo, inferior y superior), y (el número total de los elementos de control - 1) para el contenedor centrado sin espacios a los lados.

Igual que con los estilos de composición, la implementación de los recursos de alineación en la clase CBox requiere las enumeraciones. Vamos a declarar una enumeración para cada uno de los estilos de alineación:

enum VERTICAL_ALIGN
  {
   VERTICAL_ALIGN_CENTER,
   VERTICAL_ALIGN_CENTER_NOSIDES,
   VERTICAL_ALIGN_TOP,
   VERTICAL_ALIGN_BOTTOM
  };
enum HORIZONTAL_ALIGN
  {
   HORIZONTAL_ALIGN_CENTER,
   HORIZONTAL_ALIGN_CENTER_NOSIDES,
   HORIZONTAL_ALIGN_LEFT,
   HORIZONTAL_ALIGN_RIGHT
  };

3.4. Renderización de componentes

Normalmente, creamos los elementos de control indicando los parámetros x1, y1, x2 y y2, como en el siguiente fragmento del código para crear un botón:

CButton m_button;
int x1 = currentX;
int y1 = currentY;
int x2 = currentX+BUTTON_WIDTH; 
int y2 = currentY+BUTTON_HEIGHT
if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,x1,y1,x2,y2))
      return(false);

donde x2 menos x1 y y2 menos y1 son iguales al ancho y alto del elemento de control, respectivamente. Pero en vez de eso, podemos crear el mismo botón en la clase CBox usando el método más sencillo, como se muestra en el siguiente fragmento del código:

if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,0,0,BUTTON_WIDTH,BUTTON_HEIGHT))
      return(false);

La clase CBox moverá el componente automáticamente al crear la ventana del panel más tarde. Para mover lo elementos de control y los contenedores, hay que llamar al método Pack() que llama al método Render():

bool CBox::Pack(void)
  {
   GetTotalControlsSize();
   return(Render());
  }

El método Pack() simplemente obtiene el tamaño total de los contenedores y luego llama al método Render() donde ocurre la acción principal. El fragmento del código de abajo muestra la renderización real de los elementos dentro del contenedor mediante el método Render():

bool CBox::Render(void)
  {
   int x_space=0,y_space=0;
   if(!GetSpace(x_space,y_space))
      return(false);
   int x=Left()+m_padding_left+
      ((m_horizontal_align==HORIZONTAL_ALIGN_LEFT||m_horizontal_align==HORIZONTAL_ALIGN_CENTER_NOSIDES)?0:x_space);
   int y=Top()+m_padding_top+
      ((m_vertical_align==VERTICAL_ALIGN_TOP||m_vertical_align==VERTICAL_ALIGN_CENTER_NOSIDES)?0:y_space);
   for(int j=0;j<ControlsTotal();j++)
     {
      CWnd *control=Control(j);
      if(control==NULL) 
         continue;
      if(control==GetPointer(m_background)) 
         continue;
      control.Move(x,y);     
      if (j<ControlsTotal()-1)
         Shift(GetPointer(control),x,y,x_space,y_space);      
     }
   return(true);
  }

3.5. Modificación del tamaño de componentes

Si el elemento de control supera el espacio dentro del contenedor, es necesario cambiar su tamaño de tal manera que corresponda a este espacio. De lo contrario, el elemento de control va a salir fuera de los límites del contenedor lo que va a afectar el aspecto visual del panel entero. Este enfoque conviene también si Usted quiere que un determinado elemento de control se aumente y ocupe todo el ancho y alto del área de cliente o de su contenedor. Si el ancho y alto de algún elemento supera el ancho y alto del contenedor excluyendo las sangrías (por todos lados), el tamaño de este elemento se cambiará hasta las dimensiones máximas.

Por favor, nótese que CBox no cambia el tamaño del contenedor si el tamaño total de todos sus elementos supera el espacio disponible. En este caso el tamaño de la ventana principal (CDialog o CAppDialog) o de sus determinados elementos de control habrá que ajustar manualmente.


3.6. Renderización recursiva

Para el uso simple de CBox, la única llamada del método Pack() será suficiente. No obstante, para los contenedores compuestos es necesaria la llamada del mismo método para que todos los contenedores puedan distribuir sus elementos personales u otros contenedores. Podemos evitar eso añadiendo el método a la función a causa de que el mismo método se implemente en sus propios elementos de control, pero sólo si el elemento gráfico en cuestión es la instancia de la clase CBox o de otra clase de composición. Para hacerlo, primero definimos la macro y le asignamos el valor único:

#define CLASS_LAYOUT 999

Luego redifinimos el método Type() de la clase CObject para que devuelva el valor de la macro que acabamos de preparar:

virtual int       Type() const {return CLASS_LAYOUT;}

Finalmente, dentro del método Pack() de la clase CBox, aplicamos el método de renderización a los contenedores hijos que son las instancias de la clase de composición:

for(int j=0;j<ControlsTotal();j++)
     {
      CWnd *control=Control(j);
      if(control==NULL) 
         continue;
      if(control==GetPointer(m_background)) 
         continue;
      control.Move(x,y);

      //llamada al método Pack(), si es la clase de composición
      if(control.Type()==CLASS_LAYOUT)
        {
         CBox *container=control;
         container.Pack();
        }     
   
      if (j<ControlsTotal()-1)
         Shift(GetPointer(control),x,y,x_space,y_space);      
     }

El método de renderización empieza con el cálculo del espacio disponible para la distribución de los elementos de control dentro del contenedor. Los valores se almacenan en m_total_x y m_total_y, respectivamente. La siguiente tarea consiste en calcular el espacio entre los elementos de control basándose en el estilo de composición y alineación. El último paso es la implementación de la redistribución real de los elementos de control dentro del contenedor.

CBox lleva la cuenta de los elementos distribuidos puesto que dentro del contenedor hay objetos que no requieren la redistribución, por ejemplo el objeto del área del fondo CWndClient, u otros elementos de control que requieren la extensión de CBox.

CBox también mantiene el tamaño mínimo del elemento de control dentro del contenedor (a excepción del fondo) que se define por m_min_size (struct CSize). Es necesario para el posicionamiento uniforme horizontal o vertical de los elementos dentro de los límites del contenedor. La definición es bastante contra intuitiva porque, en realidad, es el tamaño del elemento más grande. Pero aquí lo definimos como el tamaño mínimo ya que CBox lo va a asumir así y va a calcular a su base el tamaño disponible.

Nótese que la acción estándar del método Shift() parece a la implementación del posicionamiento de los elementos de control (posicionamiento absoluto). Los métodos de renderización guardan la información sobre las coordenadas X y Y, que se memorizan y se actualizan cuando CBox redistribuye cada elemento de control. Pero con CBox eso se hace automáticamente, y al desarrollador del panel le queda sólo indicar el tamaño real de cada elemento de control.


4. Implementación de la ventana de diálogo

Al utilizar CBox, prácticamente reemplazamos la funcionalidad del área nativa de cliente de CDialog o CAppDialog, m_client_area, que es la instancia de la clase CWndClient. En este caso tenemos tres opciones para actuar:

  1. Extender/reescribir CAppDialog o CDialog para permitir a CBox reemplazar el área de cliente.
  2. Usar los contenedores y añadirlos en el área de cliente.
  3. Usar el contenedor principal CBox para ubicar los contenedores pequeños dentro de él.

La primera opción requiere muchos esfuerzos ya que tendremos que reescribir los objetos de la ventana de diálogo para asegurar el uso del nuevo objeto del área de cliente. Como alternativa, los objetos de la ventana pueden ser extendidos para el uso de la clase personalizada del contenedor, pero la instancia de la clase CWndClient (m_client_area) no se utiliza y ocupa en vano la memoria.

La segunda opción también es viable. Simplemente colocamos los elementos de control dentro de los contenedores de CBox, luego usamos el posicionamiento píxel por píxel para añadirlos en el área de cleinte. Pero esta opción no usa de todo las posibilidades de la clase CBox que es capaz de diseñar los paneles sin preocuparse de la distribución de los controles y contenedores individuales.

Recomendamos la tercera opción. Es decir, creamos el contenedor principal CBox para ubicar dentro de él los contenedores y elementos de control más pequeños. El contenedor principal ocupará el ancho del área de cliente nativa entera y será añadido a ella como su heredero único. Eso hará que el área nativa de cliente sea un poco más redundante, pero por lo menos seguirá siendo utilizada. Es más, está opción nos permite evitar una tarea de gran envergadura como codificación/recodificación.


5. Ejemplos


5.1. Ejemplo №1: calculador simple del tamaño del pip

Ahora podemos usar la clase CBox para implementar un panel simple: calculador del tamaño del pip. La ventana de diálogo del calculador va a contener tres campos del tipo CEdit, a saber:

  • nombre del símbolo o instrumento;
  • tamaño de 1 pip para el símbolo o instrumento especificado;
  • valor de 1 pip para el símbolo o instrumento.

Entonces, eso nos da en total 7 diferentes elementos de control, incluyendo las etiquetas (CLabel) para cada campo y el botón (CButton) para ejecutar el cálculo. A continuación, se muestra el pantallazo del calculador:

Calculador del tamaño del pip- captura de pantalla

Fig. 8. Calculador del tamaño del pip

Como podemos ver en el panel del calculador, se va a utilizar 5 diferentes contenedores CBox. En el panel tiene que haber 3 contenedores horizontales para cada uno de los campos y un contenedor horizontal más, con la alineación por el lado derecho, para el botón. Cada uno de estos contenedores se ubica dentro del contenedor principal con el estilo vertical. Finalmente, el contenedor principal tiene que estar adjuntado al área de cliente de la instancia de la clase CAppDialog. En la imagen de abajo se muestra la composición de los contenedores. Los bloques del color violeta representan las filas horizontales. Los bloques blancos son los principales elementos de control, el bloque grande del color gris, en el que se encuentran todos los bloques pequeños, es la ventana principal.

Calculador del tamaño del pip: composición de la ventana de diálogo

Fig. 9. Composición del calculador del tamaño del pip

Nótese que cuando usamos los contenedores CBox no declaramos las macros para las lagunas y sangrías. En vez de eso, declaramos las macros para los tamaños de los elementos de control, ajustamos cada instancia CBox y permitimos organizar los elementos de manera correspondiente.

Vamos a empezar a diseñar nuestro panel con la creación del archivo de cabecera "PipValueCalculator.mqh" que debe ubicarse en la misma carpeta que el archivo de origen principal de cuya preparación nos ocuparemos más tarde (PipValueCalculator.mq5). En este archivo incluiremos el archivo de cabecera de la clase CBox, así como otras inclusiones necesarias para este panel. Además, vamos a necesitar la clase CSymbolInfo que va a utilizarse para el cálculo real del tamaño del pip de cualquier símbolo seleccionado:

#include <Trade\SymbolInfo.mqh>
#include <Layouts\Box.mqh>
#include <Controls\Dialog.mqh>
#include <Controls\Label.mqh>
#include <Controls\Button.mqh>

Luego indicamos el ancho y el alto de los elementos de control. Se puede poner un tamaño determinado para cada elemento, pero en caso de nuestro panel usamos el tamaño base. En otras palabras, todos los elementos principales tendrán el mismo ancho y alto:

#define CONTROL_WIDTH   (100)
#define CONTROL_HEIGHT  (20)

Ahora pasamos a la creación del objeto real de la clase del panel. Eso se hace de manera habitual, usando la clase nueva heredada de la clase CAppDialog:

class CPipValueCalculatorDialog : public CAppDialog

La estructura inicial de la clase parece a lo siguiente:

class CPipValueCalculatorDialog : public CAppDialog
  {
protected:
//miembros protegidos de la clase
public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();

protected:
//métodos protegidos de clase
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPipValueCalculatorDialog::CPipValueCalculatorDialog(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPipValueCalculatorDialog::~CPipValueCalculatorDialog(void)
  {
  }

El fragmento del código de arriba es la plantilla inicial para la clase del panel del calculador del tamaño del pip (en realidad, se puede usar este fragmento para crear los paneles semejantes). Ahora nos ponemos a crear el miembro de la clase del contenedor principal que va a servir de padre para todos los contenedores CBox ubicados en el panel:

class CPipValueCalculatorDialog : public CAppDialog
  {
protected:
   CBox              m_main;
//más código aquí...

Hemos dado la definición al contenedor principal CBox para el panel, pero no a la función real para su creación. Para eso añadimos otro método de la clase a la clase del panel, como se muestra abajo:

// inicio de la definición de la clase
// ...
public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();
protected:
   virtual bool      CreateMain(const long chart,const string name,const int subwin);
// el resto de la definición
// ...

Ahora definimos fuera de la clase el cuerpo real del método de la clase (de manera semejante a la definición del cuerpo del constructor y destructor de la clase):

bool CPipValueCalculatorDialog::CreateMain(const long chart,const string name,const int subwin)
  {   
   //creación del contenedor principal de CBox
   if(!m_main.Create(chart,name+"main",subwin,0,0,CDialog::m_client_area.Width(),CDialog::m_client_area.Height()))
      return(false);   

   //aplicar la composición vertical
   m_main.LayoutStyle(LAYOUT_STYLE_VERTICAL);
   
   //establecer la sangría de 10 píxeles de todos lados
   m_main.Padding(10);
   return(true);
  }

Usamos CDialog::m_client_area.Width() and CDialog::m_client_area.Height() para especificar el ancho y el alto del contenedor. Es decir, se ocupa el espacio entero del área de cliente del panel. Además, vamos a modificar un poco el contenedor: aplicamos el estilo vertical y establecemos la sangría de 10 píxeles de todos lados. Estas funciones se facilitan por la clase CBox.

Después de definir el miembro de la clase del contenedor principal y el tipo de su creación, vamos a crear los miembros para las filas, como se muestra en la imagen 9. Para la fila superior, que se usa para el símbolo, declaramos mediante la creación del contenedor y luego los elementos principales de control que este contenedor contiene. Todo eso se hace debajo de la declaración de la clase del contenedor principal que se muestra en el fragmento anterior:

CBox              m_main;
CBox              m_symbol_row;   //contenedor de la fila
CLabel            m_symbol_label; //contenedor de la etiqueta
CEdit             m_symbol_edit;  //elemento de control Edit

De manera similar al contenedor principal, definimos la función para crear el contenedor real de la fila:

bool CPipValueCalculatorDialog::CreateSymbolRow(const long chart,const string name,const int subwin)
  {
   //creación del contenedor CBox para esta fila (fila del símbolo)
   if(!m_symbol_row.Create(chart,name+"symbol_row",subwin,0,0,CDialog::m_client_area.Width(),CONTROL_HEIGHT*1.5))
      return(false);

   //--- crear elemento de control con etiqueta
   if(!m_symbol_label.Create(chart,name+"symbol_label",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))
      return(false);
   m_symbol_label.Text("Symbol");
   
 //--- crear elemento de control edit
   if(!m_symbol_edit.Create(chart,name+"symbol_edit",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))
      return(false);
   m_symbol_edit.Text(m_symbol.Name());

   //añadir los elementos principales en el contenedor padre (fila)
   if(!m_symbol_row.Add(m_symbol_label))
      return(false);
   if(!m_symbol_row.Add(m_symbol_edit))
      return(false);
   return(true);
  }

En esta función, primero, vamos a crear el contenedor de la fila del símbolo. Obsérvese que usamos el ancho entero del área de cliente como el ancho del contenedor, aumentando al mismo tiempo su alto a 50% en comparación con el alto del elemento de control definido anteriormente.

Después de crear la fila, pasamos a la creación de los elementos de control individuales. Esta vez ellos utilizan las macros de ancho y alto de los elementos de control definidas por nosotros antes. Fíjense también cómo creamos estos elementos de control:

Create(chart,name+"symbol_edit",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))

Los valores de color rojo son las coordenadas x1 y y1. Eso quiere decir que, en el momento de la creación, todos los elementos de control se ubican en la esquina superior izquierda del gráfico. Serán reorganizados después de la llamada al método Pack() de la clase CBox.

Bien, hemos creado el contenedor de la fila. También hemos creado los elementos principales de control dentro de este contenedor. El siguiente paso consiste en añadir los elementos recién creados en el contenedor de la fila representado en las últimas líneas de la función:

if(!m_symbol_row.Add(m_symbol_label))
   return(false);
if(!m_symbol_row.Add(m_symbol_edit))
   return(false);

Para otras filas (tamaño del pip, valor del pip y filas de botones) vamos a utiliza el método casi parecido como en el caso de la fila del símbolo.

Al utilizar la clase CBox, necesitamos crear el contenedor principal y otras filas hijas. Ahora vamos a pasar al tema más conocido, es decir, a la creación del objeto del panel. Se crea (independientemente si se usa CBox o no) mediante la redefinición del método virtual Create() de la clase CAppDialog. Los dos métodos que hemos creado antes obtienen el sentido precisamente en este método, puesto que los invocamos precisamente aquí:

bool CPipValueCalculatorDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   //creación del panel CAppDialog
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
   
   //creación del contenedor principal CBox usando la función definida anteriormente  
   if(!CreateMain(chart,name,subwin))
      return(false);  

   //creación del contenedor principal CBox para la fila del símbolo usando la función definida anteriormente  
   if(!CreateSymbolRow(chart,name,subwin))
      return(false);

   //adición del contenedor CBox para la fila del símbolo como heredero del contenedor principal CBox
   if(!m_main.Add(m_symbol_row))
      return(false);

   //renderización del contenedor principal CBox y de todos sus sus contenedores hijos (recursivamente)
   if (!m_main.Pack())
      return(false);
   
   //adición del contenedor principal CBox como heredero único del área de cliente del panel
   if (!Add(m_main))
      return(false);
   return(true);
  }

No olvide declarar el método redefinido Create() en la clase CPipValueCalculatorDialog de la siguiente manera:

public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);

Como se deduce del código arriba mencionado, tiene que ser el método público porque se invoca desde fuera de la clase. Para ser más exacto, esto será necesario en el principal archivo original: PipValueCalculator.mq5:

#include "PipValueCalculator.mqh"
CPipValueCalculatorDialog ExtDialog;
//+------------------------------------------------------------------+
//| Función de inicialización del Asesor Experto                     |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
//--- crear la ventana de la aplicación
   if(!ExtDialog.Create(0,"Pip Value Calculator",0,50,50,279,250))
      return(INIT_FAILED);
//--- iniciar la aplicación
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Función de deinicialización del Asesor Experto                   |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| Función de tick del Asesor Experto                               |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  }
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
    //comentado, la discusión será más adelante en la sección  
    //ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Este código parece mucho a lo que solemos ver en el principal archivo original del panel, a excepción de tres puntos:

  1. Como archivo de cabecera añadimos "PipValueCalculator.mqh", en vez del archivo de inclusión para CAppDalog. "PipValueCalculator.mqh" ya incluye el archivo de cabecera, así que no necesitamos incluirlo en el principal archivo original. "PipValueCalculator.mqh" responde también por la inclusión del archivo de cabecera de la clase CBox.
  2. Declaramos ExtDialog como la instancia de la clase que hemos definido en "PipValueCalculator.mqh" (clase PipValueCalculator).
  3. Indicamos el tamaño personalizado más conveniente para el panel, tal como está definido en ExtDialog.Create().

Si hacemos la compilación sólo con la fila del símbolo, el panel será como se muestra en la imagen de abajo:

Calculador del tamaño del pip con una fila

Fig. 10. Calculador del tamaño del pip con una fila

El contenedor principal tiene la composición vertical y la alineación por el centro, mientras que para la fila del símbolo ha sido elegida la composición horizontal (también con el centrado horizontal y vertical). Para que este panel parezca al que se muestra en la imagen 8, hay que añadir tres filas más, usando el mismo método que hemos utilizado para crear la fila del símbolo. La excepción es la fila del botón que contiene el único elemento de control (botón) y tiene que alinearse por la derecha:

m_button_row.HorizontalAlign(HORIZONTAL_ALIGN_RIGHT);

Los objetivos del presente artículo no suponen la descripción del procesamiento de eventos. Sin embargo, para completar nuestro ejemplo, vamos a discutir esta cuestión aquí. Empecemos con la declaración del nuevo miembro de la clase PipValueCalculator, m_symbol. También incluiremos dos miembros adicionales: m_digits_adjust y m_points_adjust, los que vamos a utilizar más tarde para convertir el tamaño de los puntos en pips.

CSymbolInfo      *m_symbol;
int               m_digits_adjust;
double            m_points_adjust;

Inicializamos m_symbol en el constructor de la clase o en el método Create() usando el siguiente código:

if (m_symbol==NULL)
      m_symbol=new CSymbolInfo();
if(m_symbol!=NULL)
{
   if (!m_symbol.Name(_Symbol))
      return(false);
}   

Si el puntero del símbolo es NULL, creamos la nueva instancia de la clase CSymbolInfo. Si no es NULL, asignamos el nombre del símbolo igual al nombre del símbolo del gráfico.

En la siguiente fase definimos el manejador del evento del click para el botón. Para eso usamos el método de la clase OnClickButton(). Definimos su cuerpo de la siguiente manera:

void CPipValueCalculatorDialog::OnClickButton()
  {
   string symbol=m_symbol_edit.Text();
   StringToUpper(symbol);
   if(m_symbol.Name(symbol))
     {
      m_symbol.RefreshRates();
      m_digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5)?10:1;
      m_points_adjust=m_symbol.Point()*m_digits_adjust;
      m_pip_size_edit.Text((string)m_points_adjust);      
      m_pip_value_edit.Text(DoubleToString(m_symbol.TickValue()*(StringToDouble(m_pip_size_edit.Text()))/m_symbol.TickSize(),2));
     }
   else Print("invalid input");
  }

El método de la clase calcula el tamaño del pip obteniendo el valor desde el elemento de control m_symbol_edit. Luego pasa el nombre del símbolo a la instancia de la clase CSymbolInfo. Esta clase recibe el valor del tick del símbolo seleccionado, que luego se ajusta por un determinado multiplicador con el fin de calcular el valor de 1 pip.

El último paso de la activación del procesamiento de eventos de la clase es la definición del manejador de eventos (también dentro de la clase PipValueCalculator). Inserta esta cadena del código en el bloque de declaración de los métodos públicos de la clase:

virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

Luego definimos el cuerpo del método de la clase fuera de la clase usando el siguiente fragmento del código:

EVENT_MAP_BEGIN(CPipValueCalculatorDialog)
   ON_EVENT(ON_CLICK,m_button,OnClickButton)
EVENT_MAP_END(CAppDialog)


5.2. Ejemplo №2: reconstrucción del ejemplo Controls

El ejemplo del panel de los elementos de control se instala automáticamente durante la instalación del MetaTrader. En la ventana del navegador, se puede encontrarlo a través de Asesores Expertos\Examples\Controls. Abajo tiene la captura de pantalla de este panel:

Elementos de control - ventana de diálogo

Fig. 11. Ventana de diálogo con los elementos de control (original)

En la siguiente imagen se muestra con detalles la composición de la ventana de diálogo de arriba. Para la reconstrucción del panel usando las instancias de la clase CBox, tenemos 4 filas horizontales (color violeta) para el siguiente conjunto de elementos de control:

  1. elemento de control Edit;
  2. tres elementos de control Button;
  3. SpinEdit y DatePicker;
  4. ComboBox, RadioGroup y CheckGroup (columna 1), así como ListView (columna 2).

El último contenedor horizontal representa un caso especial ya que se trata de un contenedor horizontal compuesto que contiene dos contenedores más (columnas 1 y 2 de color verde). Estos contenedores deben tener la composición vertical.

Fig. 12. Composición de los elementos de control en la ventana de diálogo

Fig. 12. Composición de los elementos de control en la ventana de diálogo

Cuando se reconstruye el ejemplo Controls, hay que eliminar todas las líneas del código que llaman al método Add(), a excepción de estas líneas para el contenedor principal que va a actuar como heredero único del área de cliente de la ventana de diálogo. Entretanto, hay que añadir otros elementos de control y contenedores en los contenedores padres correspondientes desde los niveles más profundos hasta el contenedor principal, y luego en el área de cliente nativa.

Empiece la compilación y ejecución después de la instalación. Todo tiene que funcionar perfectamente, salvo el objeto de selección de la fecha cuyos botones de incremento, decremento y de la lista se negarán a funcionar. Eso está relacionado con el hecho de que la lista desplegable de la clase CDatePicker se encuentra al fondo de los demás contenedores. Para solucionar este problema, diríjase al archivo de la clase CDatePicker ubicado en %Carpeta de datos%\MQL5\Include\Controls\DatePicker.mqh. Encuentre el método ListShow() e inserte la siguiente cadena del código al principio de la función:

BringToTop();

Vuelva a compilar y haga la prueba. Eso moverá la lista desplegable del objeto de selección de fecha al primer plano, así como dará la prioridad a los eventos del click del botón en cualquier momento. Abajo se muestra el fragmento del código de la función entera:

bool CDatePicker::ListShow(void)
  {
   BringToTop();
//--- establecer valor   
   m_list.Value(m_value);
//--- mostramos la lista
   return(m_list.Show());
  }

El pantallazo de la ventana de diálogo reconstruida Controls se muestra a continuación:

Elementos de control - ventana de diálogo reconstruida

Fig. 13. Ventana de diálogo con los elementos de control (usando CBox)

Si echamos un vistazo a la imagen grande, podemos pensar que es prácticamente idéntica al original. A pesar de todo, hay una sorprendente diferencia que ha aparecido por casualidad: la columna 1 está alineada perfectamente con la columna 2. En la versión original vemos que CheckGroup está alineado con ListView por la parte de abajo. Pero en la parte superior, ComboBox no está alineado con ListView por la parte de arriba. Claro que se puede reposicionar las coordinadas en el panel original, pero para eso no sólo habrá que configurar las coordenadas de los píxeles para ComboBox, sino también las coordenadas de RadioGroup y los espacios entre estos elementos de control. Por otro lado, el uso del contenedor CBox requiere el valor cero de las sangrías arriba y abajo y la aplicación de una alineación correcta.

Pero eso no significa que el uso del CBox o composición es ideal en cuanto a la precisión. Y aunque este modo se reconoce menos preciso que la codificación de las coordenadas exactas para los elementos de control, el uso de los contenedores y de los esquemas de composición proporciona un nivel de precisión suficiente, facilitando un poco al mismo tiempo la construcción de la interfaz gráfica de usuario.


6. Los pro y los contra

Los pro:

  • Se puede utilizar el código una y otra vez: Usted puede utilizar CBox o cualquier clase de composición en diferentes aplicaciones y ventanas de diálogo.
  • Posibilidad de escalamiento: aunque en las aplicaciones pequeñas el código fuente puede ser más grande, las ventajas de este modo son bastante evidentes en los paneles y ventanas de diálogo de más complejidad.
  • La segmentación de los conjuntos de elementos de control permite modificar el conjunto de los elementos sin afectar mucho la posición de otros elementos de control.
  • Posicionamiento automático: no hay que codificar las sangrías, lagunas y espacios manualmente. La clase de composición se encarga de ello automáticamente.

Los contra:

  • Hay que crear los elementos de control adicionales para los contenedores, así como es necesario crear las funciones adicionales para poder usar estos contenedores.
  • Menos preciso: la distribución está limitada con la composición y opciones de alineación disponibles.
  • Pueden surgir problemas o dificultades si el contenedor contiene los elementos de control de tamaños diferentes: en este caso habrá que reducir la diferencia de tamaños al mínimo, o bien usar los contenedores compuestos.


7. Conclusión

En este artículo hemos considerado la posibilidad de utilizar la composición o contenedores en el diseño de los paneles gráficos. Nuestro enfoque ha permitido automatizar el proceso de posicionamiento de diferentes elementos de control mediante la composición y estilos de alineación. Facilita el diseño de paneles gráficos, y a veces reduce el tiempo de la escritura del código.

La clase Cbox representa un medio auxiliar de control que actúa como contenedor de los elementos principales de control del panel con la interfaz gráfica. En el artículo hemos demostrado su funcionamiento y cómo puede ser utilizado en aplicaciones reales. A pesar de que este método sea menos preciso que el posicionamiento absoluto, igual así garantiza el nivel de precisión que conviene para muchas aplicaciones.

Traducción del inglés realizada por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/en/articles/1867

Archivos adjuntos |
Box.mqh (11.88 KB)
Controls2.mq5 (2.13 KB)
ControlsDialog2.mqh (20.18 KB)
Dibujo de indicadores de aguja usando la clase CCanvas Dibujo de indicadores de aguja usando la clase CCanvas

Los instrumentos indicadores de esfera nos rodean por todas partes: en los coches y aviones, en la industria y en nuestra vida cotidiana. Se aplican cuando se requiere una rápida reacción al valor controlado por parte del operador. En este artículo conoceremos la biblioteca de instrumentos indicadores para MetaTrader 5.

Programamos los modos de funcionamiento del Asesor Experto usando la programación orientada a objetos Programamos los modos de funcionamiento del Asesor Experto usando la programación orientada a objetos

En este artículo se considera la idea de la programación multi-modo de los robots comerciales usando el lenguaje MQL5. Se utiliza el enfoque orientado a objetos para la implementación de cada uno de los modos. Se muestra el ejemplo de la jerarquía de las clases de régimen y el ejemplo de las clases para el testeo (prueba). Se supone que la programación multi-modo de los robots comerciales toma en consideración las particularidades de cada modo de trabajo del Asesor Experto MQL5. Para la identificación de los modos se crean las funciones y enumeraciones.

Tercera generación de neuroredes: "Neuroredes profundas" Tercera generación de neuroredes: "Neuroredes profundas"

El artículo está dedicado a una corriente nueva con muy buenas perspectivas en el aprendizaje automático, al así llamado "aprendizaje profundo" y más concretamente a las "neuroredes profundas". Se ha efectuado una breve panorámica de las neuroredes de 2 generación, sus arquitecturas de conexiones y tipos, métodos y normas de aprendizaje principales, así como de sus defectos más destacables. A continuacón se estudia la historia de la aparición y el desarrollo de las neuroredes de tercera generación, sus tipos principales, sus particularidades y métodos de aprendizaje. Se han realizado experimentos prácticos sobre la construcción y aprendizaje con datos reales de neurored profunda, iniciada con los pesos del auto-codificador acumulado. Se han estudiado todas las etapas, desde los datos de origen hasta la obtención de la métrica. En la última parte del artículo, se adjunta la implementación programática de una neurored profunda en forma de indicador-experto en MQL4/R.

Trabajamos con archivos ZIP con los medios de MQL5, sin usar bibliotecas ajenas Trabajamos con archivos ZIP con los medios de MQL5, sin usar bibliotecas ajenas

El lenguaje MQL5 prosigue su desarrollo, continuamente se le añaden nuevas funciones para trabajar con datos. Desde hace cierto tiempo, gracias a las innovaciones, resulta posible trabajar con archivos ZIP con las herramientas estándar MQL5, sin tener que implicar bibliotecas DLL ajenas. Este artículo describe de forma detallada cómo hacerlo, usando como ejemplo la descripción de la clase CZip, un instruemnto universal de lectura, creación y modificación de archivos ZIP.