
Interfaces gráficas IX: Control "Paleta para seleccionar el color" (Capítulo 1)
Índice
- Introducción
- Control «Paleta para seleccionar el color»
- Botón para abrir la paleta de colores
- Prueba de controles
- 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.
En la novena parte hablaremos de los siguientes controles y elementos de la interfaz:
1. Primer capítulo:
- Control «Paleta para seleccionar el color» (clase CColorPicker).
- Control «Botón para abrir la paleta de colores» (clase CColorButton).
2. Segundo capítulo:
- Elemento «Indicador de progreso» (clase CProgressBar).
- Elemento «Gráfico lineal» (clase CLineGraph).
Para cada uno de estos elementos vamos a mostrar detallados ejemplos de cómo puede usarlos en sus aplicaciones.
Control «Paleta para seleccionar el color»
La paleta de colores está presente en una gran variedad de aplicaciones que ofrecen la posibilidad de especificar el color para algún objeto. Usted puede necesitar la paleta de colores en los terminales MetaTrader para cambiar rápidamente el color de los elementos en su aplicación MQL. Supongamos que al diseñar el estudio visual para la creación de las interfaces gráficas, cuando es necesario configurar la solución de colores para cada elemento, sería muy inconveniente hacerlo sin la paleta de colores.
La paleta de colores es un complejo control compuesto que aparte de la misma paleta que muestra el modelo de colores seleccionado, contiene otros objetos y grupos de controles. Vamos a nombrar todas las partes integrantes de este control.
- Fondo
- Paleta de colores que muestra el modelo de colores especificado.
- Marcador del color establecido
- Marcador del color seleccionado
- Marcador del color al situar el cursor encima
- Grupo de botones de opción (radio buttons) con los campos de edición para el ajuste manual de los componentes del modelo de colores
- Botón para cancelar el color seleccionado
- Botón para establecer (fijar) el color especificado en el segundo marcador
Fig. 1. Partes integrantes del control «Paleta para seleccionar el color».
En la captura de pantalla de arriba se ve que el grupo de botones de opción (radio buttons) está dividido en tres subgrupos con tres botones en cada uno. Cada uno de estos subgrupos determina un modelo de colores, y cada botón de opción determina el componente de este modelo (coordenada del espacio de colores). La lista de abajo contiene el descifre de las abreviaturas de todos los modelos de colores que van a utilizarse durante la creación de la paleta de colores para nuestra librería.
1. Modelo de colores HSL:
- H (Hue) – tono del color. El rango de valores es de 0 a 360.
- S (Saturation) – saturación. El rango de valores es de 0 a 100.
- L (Lightness) – luminosidad. El rango de valores es de 0 a 100.
2. Modelo de colores RGB:
- R (Red) – rojo. El rango de valores es de 0 a 255.
- G (Green) – verde. El rango de valores es de 0 a 255.
- B (Blue) – azul. El rango de valores es de 0 a 255.
3. Modelo de colores Lab:
- L (Luminance) – luminancia. El rango de valores es de 0 a 100.
- a – la primera coordenada cromática que define la matiz del color del verde al coccíneo. El rango de valores es de -128 a 127.
- b – la segunda coordenada cromática que define la matiz del color del azul al amarillo. El rango de valores es de -128 a 127.
A continuación, vamos a ver cómo está organizada la clase de CColorPicker para la creación de l apaleta de colores.
Desarrollo de la clase CColorPicker
Creamos el archivo ColorPicker.mqh en el mismo directorio que ahora contiene los archivos de los demás controles (<carpeta de datos>\MQLX\Include\EasyAndFastGUI\Controls). En este archivo se debe crear la clase CColorPicker con los miembros estándar, tal como se muestra a continuación:
//+------------------------------------------------------------------+ //| ColorPicker.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| Clase para crear la paleta para seleccionar el color | //+------------------------------------------------------------------+ class CColorPicker : public CElement { private: //--- Puntero al formulario al que está adjuntado el control CWindow *m_wnd; //--- public: CColorPicker(void); ~CColorPicker(void); //--- public: //--- Guarda el puntero del formulario void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Manejador de eventos del gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Temporizador virtual void OnEventTimer(void); //--- Desplazamiento del control virtual void Moving(const int x,const int y); //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- (1) Establecer, (2) resetear las prioridades para el clic izquierdo del ratón virtual void SetZorders(void); virtual void ResetZorders(void); //--- Restablecer el color virtual void ResetColors(void) {} };
Para la configuración de los objetos gráficos estarán disponibles las siguientes propiedades:
- Color del fondo del control
- Color del borde del fondo del control
- Color del borde de la paleta y los marcadores del color
Usted puede cambiar las propiedades de los objetos gráficos de otros controles al obtener su puntero.
class CColorPicker : public CElement { private: //--- Color del (1) fondo y (2) borde del fondo color m_area_color; color m_area_border_color; //--- Color del borde de la paleta color m_palette_border_color; //--- public: //--- Establecer el color del (1) fondo y (2) del borde del fondo, (3) borde de la paleta void AreaBackColor(const color clr) { m_area_color=clr; } void AreaBorderColor(const color clr) { m_area_border_color=clr; } void PaletteBorderColor(const color clr) { m_palette_border_color=clr; } };
Puesto que vamos a utilizar otros controles como partes integrantes de la paleta de colores, hay que incluir los archivos con las clases de estos controles en el archivo ColorPicker.mqh. Para crear la paleta de colores, vamos a necesitar diecisiete métodos privados (private) y un método público (public).
//+------------------------------------------------------------------+ //| ColorPicker.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "SpinEdit.mqh" #include "SimpleButton.mqh" #include "RadioButtons.mqh" //+------------------------------------------------------------------+ //| Clase para crear la paleta de colores | //+------------------------------------------------------------------+ class CColorPicker : public CElement { private: //--- Objetos para crear el control CRectLabel m_area; CRectCanvas m_canvas; CRectLabel m_current; CRectLabel m_picked; CRectLabel m_hover; //--- CRadioButtons m_radio_buttons; CSpinEdit m_hsl_h_edit; CSpinEdit m_hsl_s_edit; CSpinEdit m_hsl_l_edit; //--- CSpinEdit m_rgb_r_edit; CSpinEdit m_rgb_g_edit; CSpinEdit m_rgb_b_edit; //--- CSpinEdit m_lab_l_edit; CSpinEdit m_lab_a_edit; CSpinEdit m_lab_b_edit; //--- CSimpleButton m_button_ok; CSimpleButton m_button_cancel; //--- public: //--- Métodos para crear el control bool CreateColorPicker(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateArea(void); bool CreatePalette(void); bool CreateCurrentSample(void); bool CreatePickedSample(void); bool CreateHoverSample(void); bool CreateRadioButtons(void); bool CreateHslHEdit(void); bool CreateHslSEdit(void); bool CreateHslLEdit(void); bool CreateRgbREdit(void); bool CreateRgbGEdit(void); bool CreateRgbBEdit(void); bool CreateLabLEdit(void); bool CreateLabAEdit(void); bool CreateLabBEdit(void); bool CreateButtonOK(const string text); bool CreateButtonCancel(const string text); };
Al conmutar el botón de opción, al lado de la paleta van a mostrarse los cortes bidimensionales de los espacios de colores de acuerdo con el valor especificado del componente seleccionado. En otras palabras, para el dibujo de cada corte, primero es necesario realizar el cálculo respecto al valor actual de su componente. Por eso para cada modelo de colores escribiremos tres métodos separados. Todos estos métodos van a recibir el índice del botón de opción (índice del componente seleccionado).
Para la conversión del color vamos a usar los métodos de la instancia de la clase CColors que está declarada en la clase base del control (CElement). Para la conversión desde el formato RGB al formato Lab, en la clase CColors no hay ningún método conveniente. Por eso cuando necesitamos la conversión RGB->Lab, va a aplicarse la conversión doble a través del modelo XYZ, es decir: RGB->XYZ->Lab. Para los cálculos y almacenamiento de los valores de los componentes de todos los modelos de colores, hay que declarar los campos correspondientes en la clase CColorPicker.
class CColorPicker : public CElement { private: //--- Valores de los componentes en diferentes modelos de colores: // HSL double m_hsl_h; double m_hsl_s; double m_hsl_l; //--- RGB double m_rgb_r; double m_rgb_g; double m_rgb_b; //--- Lab double m_lab_l; double m_lab_a; double m_lab_b; //--- XYZ double m_xyz_x; double m_xyz_y; double m_xyz_z; //--- private: //--- Dibuja la paleta según el modelo de colores HSL (0: H, 1: S, 2: L) void DrawHSL(const int index); //--- Dibuja la paleta según el modelo de colores RGB (3: R, 4: G, 5: B) void DrawRGB(const int index); //--- Dibuja la paleta según el modelo de colores LAB (6: L, 7: a, 8: b) void DrawLab(const int index); };
Como ejemplo, vamos a mostrar aquí el código sólo de uno de estos métodos, CColorPicker::DrawHSL(). Porque la única diferencia que existe entre ellos es el cálculo previo de los valores de los componentes antes de la conversión. Puede estudiar el código de los demás métodos en los archivos adjuntos al artículo.
El cálculo y el dibujo se realiza para cada píxel de la imagen. Preste atención en que los cálculos se realizan respecto a los tamaños de la paleta gráfica. Es decir, al usar este código, se puede crear un control semejante dentro del cual la paleta de colores va a tener otros tamaños, y no necesariamente tener la forma del cuadrado.
Fig. 2. Ejemplo de la paleta de colores con el tamaño 500x255 píxeles.
//+------------------------------------------------------------------+ //| Dibuja la paleta de colores HSL | //+------------------------------------------------------------------+ void CColorPicker::DrawHSL(const int index) { switch(index) { //--- Hue (H) - tono de colores en el rango de valores de 0 a 360 case 0 : { //--- Calculamos el componente H m_hsl_h=m_hsl_h_edit.GetValue()/360.0; //--- for(int ly=0; ly<m_canvas.YSize(); ly++) { //--- Calculamos el componente L m_hsl_l=ly/(double)m_canvas.YSize(); //--- for(int lx=0; lx<m_canvas.XSize(); lx++) { //--- Calculamos el componente S m_hsl_s=lx/(double)m_canvas.XSize(); //--- Conversión de los componentes HSL en los componentes RGB m_clr.HSLtoRGB(m_hsl_h,m_hsl_s,m_hsl_l,m_rgb_r,m_rgb_g,m_rgb_b); //--- Unimos los canales uint rgb_color=XRGB(m_rgb_r,m_rgb_g,m_rgb_b); m_canvas.PixelSet(lx,m_canvas.YSize()-ly,rgb_color); } } break; } //--- Saturation (S) - saturación en el rango de valores de 0 a 100 case 1 : { //--- Calculamos el componente S m_hsl_s=m_hsl_s_edit.GetValue()/100.0; //--- for(int ly=0; ly<m_canvas.YSize(); ly++) { //--- Calculamos el componente L m_hsl_l=ly/(double)m_canvas.YSize(); //--- for(int lx=0; lx<m_canvas.XSize(); lx++) { //--- Calculamos el componente H m_hsl_h=lx/(double)m_canvas.XSize(); //--- Conversión de los componentes HSL en los componentes RGB m_clr.HSLtoRGB(m_hsl_h,m_hsl_s,m_hsl_l,m_rgb_r,m_rgb_g,m_rgb_b); //--- Unimos los canales uint rgb_color=XRGB(m_rgb_r,m_rgb_g,m_rgb_b); m_canvas.PixelSet(lx,m_canvas.YSize()-ly,rgb_color); } } break; } //--- Lightness (L) - luminancia en el rango de valores de 0 a 100 case 2 : { //--- Calculamos el componente L m_hsl_l=m_hsl_l_edit.GetValue()/100.0; //--- for(int ly=0; ly<m_canvas.YSize(); ly++) { //--- Calculamos el componente S m_hsl_s=ly/(double)m_canvas.YSize(); //--- for(int lx=0; lx<m_canvas.XSize(); lx++) { //--- Calculamos el componente H m_hsl_h=lx/(double)m_canvas.XSize(); //--- Conversión de los componentes HSL en los componentes RGB m_clr.HSLtoRGB(m_hsl_h,m_hsl_s,m_hsl_l,m_rgb_r,m_rgb_g,m_rgb_b); //--- Unimos los canales uint rgb_color=XRGB(m_rgb_r,m_rgb_g,m_rgb_b); m_canvas.PixelSet(lx,m_canvas.YSize()-ly,rgb_color); } } break; } } }
Para dibujar el borde sobre el lienzo de la paleta de colores, escribiremos el método CColorPicker::DrawPaletteBorder():
class CColorPicker : public CElement { private: //--- Dibuja borde de la paleta void DrawPaletteBorder(void); }; //+------------------------------------------------------------------+ //| Dibuja el borde de la paleta | //+------------------------------------------------------------------+ void CColorPicker::DrawPaletteBorder(void) { //--- Tamaño de la paleta int x_size=m_canvas.XSize()-1; int y_size=m_canvas.YSize()-1; //--- Dibujar el borde m_canvas.Line(0,0,x_size,0,m_palette_border_color); m_canvas.Line(0,y_size,x_size,y_size,m_palette_border_color); m_canvas.Line(0,0,0,y_size,m_palette_border_color); m_canvas.Line(x_size,0,x_size,y_size,m_palette_border_color); }
Todos los métodos de dibujo mencionados van a llamarse en el método para el dibujo de la paleta de colores CColorPicker::DrawPalette():
class CColorPicker : public CElement { private: //--- Dibuja la paleta void DrawPalette(const int index); }; //+------------------------------------------------------------------+ //| Dibuja la paleta | //+------------------------------------------------------------------+ void CColorPicker::DrawPalette(const int index) { switch(index) { //--- HSL (0: H, 1: S, 2: L) case 0 : case 1 : case 2 : { DrawHSL(index); break; } //--- RGB (3: R, 4: G, 5: B) case 3 : case 4 : case 5 : { DrawRGB(index); break; } //--- LAB (6: L, 7: a, 8: b) case 6 : case 7 : case 8 : { DrawLab(index); break; } } //--- Dibujamos el borde de la paleta DrawPaletteBorder(); //--- Actualizamos la paleta m_canvas.Update(); }
Cuando se selecciona un color en la paleta o durante la configuración de uno u otro componente de cualquier modelo de colores presente en el control, los valores en todos los campos de edición van a recalcularse automáticamente. Hacen falta los métodos mediante los cueles van a calcularse los componentes de todos los modelos de colores del control respecto al modelo cuyo corte (el botón de opción seleccionado) se muestre en la paleta en este momento.
En primer lugar, vamos a necesitar los métodos de corrección de componentes de los modelos RGB y HSL, los que van a llamarse en muchos otros métodos de la clase:
class CColorPicker : public CElement { private: //--- Corrección de los componentes RGB void AdjustmentComponentRGB(void); //--- Corrección de los componentes HSL void AdjustmentComponentHSL(void); }; //+------------------------------------------------------------------+ //| Corrección de los componentes RGB | //+------------------------------------------------------------------+ void CColorPicker::AdjustmentComponentRGB(void) { m_rgb_r=::fmin(::fmax(m_rgb_r,0),255); m_rgb_g=::fmin(::fmax(m_rgb_g,0),255); m_rgb_b=::fmin(::fmax(m_rgb_b,0),255); } //+------------------------------------------------------------------+ //| Corrección de los componentes HSL | //+------------------------------------------------------------------+ void CColorPicker::AdjustmentComponentHSL(void) { m_hsl_h*=360; m_hsl_s*=100; m_hsl_l*=100; }
Después de calcular todos los componentes, es necesario establecer nuevos valores en los campos de edición del control. En algunos casos habrá que (1) establecer los valores de todos los componentes, y a veces (2), para todos, a excepción del componente seleccionado en el momento actual. Para estas situaciones, escribiremos el método CColorPicker::SetControls(), que puede trabajar en dos modos.
class CColorPicker : public CElement { private: //--- Establecer los parámetros actuales en los campos de edición void SetControls(const int index,const bool fix_selected); }; //+------------------------------------------------------------------+ //| Establecer los parámetros actuales en los campos de edición | //+------------------------------------------------------------------+ void CColorPicker::SetControls(const int index,const bool fix_selected) { //--- Si es necesario fijar el valor en el campo de edición del botón de opción seleccionado if(fix_selected) { //--- Componentes HSL if(index!=0) m_hsl_h_edit.ChangeValue(m_hsl_h); if(index!=1) m_hsl_s_edit.ChangeValue(m_hsl_s); if(index!=2) m_hsl_l_edit.ChangeValue(m_hsl_l); //--- Componentes RGB if(index!=3) m_rgb_r_edit.ChangeValue(m_rgb_r); if(index!=4) m_rgb_g_edit.ChangeValue(m_rgb_g); if(index!=5) m_rgb_b_edit.ChangeValue(m_rgb_b); //--- Componentes Lab if(index!=6) m_lab_l_edit.ChangeValue(m_lab_l); if(index!=7) m_lab_a_edit.ChangeValue(m_lab_a); if(index!=8) m_lab_b_edit.ChangeValue(m_lab_b); return; } //--- Si es necesario corregir los valores en los campos de edición de todos los modelos de colores m_hsl_h_edit.ChangeValue(m_hsl_h); m_hsl_s_edit.ChangeValue(m_hsl_s); m_hsl_l_edit.ChangeValue(m_hsl_l); //--- m_rgb_r_edit.ChangeValue(m_rgb_r); m_rgb_g_edit.ChangeValue(m_rgb_g); m_rgb_b_edit.ChangeValue(m_rgb_b); //--- m_lab_l_edit.ChangeValue(m_lab_l); m_lab_a_edit.ChangeValue(m_lab_a); m_lab_b_edit.ChangeValue(m_lab_b); }
Para calcular los componentes de todos los modelos del control respecto al modelo cuyo corte (el botón de opción seleccionado) se muestre en la paleta en este momento, escribiremos tres métodos separados: CColorPicker::SetHSL(), CColorPicker::SetRGB() и CColorPicker::SetLab(). El contenido de estos métodos es muy parecido. Aquí mostraremos sólo uno de ellos: CColorPicker::SetRGB(). Al principio del método, obtenemos los valores de los campos de edición del modelo RGB en los campos de la clase. Convertimos loa valores obtenidos en el formato HSL y Lab. Luego, al final del todo, llamamos al método CColorPicker::SetControls() en el modo de establecimiento de valores para todos los modelos de colores del control (false).
class CColorPicker : public CElement { private: //--- Establecer los parámetros de los modelos de colores respecto a (1) HSL, (2) RGB, (3) Lab void SetHSL(void); void SetRGB(void); void SetLab(void); }; //+------------------------------------------------------------------+ //| Establecer los parámetros de los modelos de colores respecto a RGB | //+------------------------------------------------------------------+ void CColorPicker::SetRGB(void) { //--- Obtenemos el valor actual del componente RGB m_rgb_r=m_rgb_r_edit.GetValue(); m_rgb_g=m_rgb_g_edit.GetValue(); m_rgb_b=m_rgb_b_edit.GetValue(); //--- Conversión de los componentes RGB en los componentes HSL m_clr.RGBtoHSL(m_rgb_r,m_rgb_g,m_rgb_b,m_hsl_h,m_hsl_s,m_hsl_l); //--- Corrección del componente HSL AdjustmentComponentHSL(); //--- Conversión de los componentes RGB en los componentes Lab m_clr.RGBtoXYZ(m_rgb_r,m_rgb_g,m_rgb_b,m_xyz_x,m_xyz_y,m_xyz_z); m_clr.XYZtoCIELab(m_xyz_x,m_xyz_y,m_xyz_z,m_lab_l,m_lab_a,m_lab_b); //--- Establecer los parámetros actuales en los campos de edición SetControls(0,false); }
Y por último, necesitamos el método principal en el que van a llamarse todos los métodos mencionado para los cálculos, el dibujo y establecimiento de los valores de los componentes en los campos de edición del control. Aquí este método es CColorPicker::SetComponents(). También trabaja en dos modos. Si el argumento fix_selected es true, los componentes van a calcularse respecto al color seleccionado, y el establecimiento de valores en los campos de edición será respecto al componente seleccionado con el botón de opción. Si el argumento fix_selected es false, el cálculo se realiza respecto al modelo de colores especificado. Una vez realizados todos los cálculos, la paleta de colores se redibuja.
class CColorPicker : public CElement { private: //--- Calcular y establecer los componentes del color void SetComponents(const int index,const bool fix_selected); }; //+------------------------------------------------------------------+ //| Calcular y establecer los componentes del color | //+------------------------------------------------------------------+ void CColorPicker::SetComponents(const int index=0,const bool fix_selected=true) { //--- Si hace falta corregir los colores respecto al componente seleccionado con el botón de opción if(fix_selected) { //--- Descomponemos el color seleccionado en los componentes RGB m_rgb_r=m_clr.GetR(m_picked_color); m_rgb_g=m_clr.GetG(m_picked_color); m_rgb_b=m_clr.GetB(m_picked_color); //--- Convertimos los componentes RGB en los componentes HSL m_clr.RGBtoHSL(m_rgb_r,m_rgb_g,m_rgb_b,m_hsl_h,m_hsl_s,m_hsl_l); //--- Corrección del componente HSL AdjustmentComponentHSL(); //--- Convertimos los componentes RGB en los componentes LAB m_clr.RGBtoXYZ(m_rgb_r,m_rgb_g,m_rgb_b,m_xyz_x,m_xyz_y,m_xyz_z); m_clr.XYZtoCIELab(m_xyz_x,m_xyz_y,m_xyz_z,m_lab_l,m_lab_a,m_lab_b); //--- Establecemos los colores en los campos de edición SetControls(m_radio_buttons.SelectedButtonIndex(),true); return; } //--- Establecer los parámetros de los modelos de colores switch(index) { case 0 : case 1 : case 2 : SetHSL(); break; case 3 : case 4 : case 5 : SetRGB(); break; case 6 : case 7 : case 8 : SetLab(); break; } //--- Dibujar la paleta del botón de opción seleccionado DrawPalette(m_radio_buttons.SelectedButtonIndex()); }
Escribiremos el método CColorPicker::CurrentColor() para establecer el color actual de la paleta que va a asignarse a todos los objetos marcadores. Luego mostraremos dónde va a utilizarse.
class CColorPicker : public CElement { public: //--- Establecer el color seleccionado por el usuario en la paleta void CurrentColor(const color clr); }; //+------------------------------------------------------------------+ //| Establecer el color actual | //+------------------------------------------------------------------+ void CColorPicker::CurrentColor(const color clr) { m_hover_color=clr; m_hover.Color(clr); m_hover.BackColor(clr); m_hover.Tooltip(::ColorToString(clr)); //--- m_picked_color=clr; m_picked.Color(clr); m_picked.BackColor(clr); m_picked.Tooltip(::ColorToString(clr)); //--- m_current_color=clr; m_current.BackColor(clr); m_current.Tooltip(::ColorToString(clr)); }
Tenemos preparados todos los métodos para los cálculos. Vamos a los métodos para procesar los eventos del control.
Métodos para procesar los eventos del control
Vamos a necesitar los siguientes métodos para procesar los eventos y controlar la paleta de colores:
- El método CColorPicker::OnHoverColor() se utiliza para obtener el color (sobre la paleta) debajo del cursor del ratón. El programa sale del método si el cursor se encuentra fuera del área de la paleta de colores. Si el cursor se encuentra dentro de esta área, determinamos sus coordenadas sobre ella y obtenemos el color debajo del cursor. Inmediatamente después de eso, el nuevo color se establece para el marcador correspondiente, y a través del método ColorToString() se establece la descripción emergente en forma de la línea del color en el formato RGB para los objetos gráficos del marcador y la paleta de colores.
//+------------------------------------------------------------------+ //| Obtener el color debajo del cursor del ratón | //+------------------------------------------------------------------+ bool CColorPicker::OnHoverColor(const int x,const int y) { //--- Salir si el foco no está sobre la paleta if(!m_canvas.MouseFocus()) return(false); //--- Determinamos el color sobre la paleta debajo del cursor int lx =x-m_canvas.X(); int ly =y-m_canvas.Y(); m_hover_color=(color)::ColorToARGB(m_canvas.PixelGet(lx,ly),0); //--- Establecemos el color y la descripción emergente en el marcador correspondiente m_hover.Color(m_hover_color); m_hover.BackColor(m_hover_color); m_hover.Tooltip(::ColorToString(m_hover_color)); //--- Establecemos la descripción emergente para la paleta de colores m_canvas.Tooltip(::ColorToString(m_hover_color)); return(true); }
- El método CColorPicker::OnClickPalette() sirve para el procesamiento del clic en la paleta de colores. Al principio del método se comprueba el nombre del objeto. Si el clic ha sido hecho en la paleta, se guarda y se establece el color que encuentra debajo del cursor del ratón, así como la descripción emergente para el marcador correspondiente. Al final se invoca el método CColorPicker::SetComponents() para calcular y establecer los componentes del modelo de colores respecto al componente seleccionado con el botón de opción.
//+------------------------------------------------------------------+ //| Procesamiento del clic en la paleta de colores | //+------------------------------------------------------------------+ bool CColorPicker::OnClickPalette(const string clicked_object) { //--- Salir si el nombre del objeto no coincide if(clicked_object!=m_canvas.Name()) return(false); //--- Establecemos el color y la descripción emergente en el marcador correspondiente m_picked_color=m_hover_color; m_picked.Color(m_picked_color); m_picked.BackColor(m_picked_color); m_picked.Tooltip(::ColorToString(m_picked_color)); //--- Calculamos y establecemos los componentes del color respecto al botón de opción seleccionado SetComponents(); return(true); }
- El método CColorPicker::OnClickRadioButton() sirve para el procesamiento del clic en el botón de opción. Aquí primero hay que realizar dos pruebas: (1) del identificador del control y (2) del texto que muestra el botón de opción. Si las pruebas han sido superadas, la la paleta de colores se redibuja respecto al componente del modelo seleccionado al que pertenece.
//+------------------------------------------------------------------+ //| Procesamiento del clic en el botón de opción | //+------------------------------------------------------------------+ bool CColorPicker::OnClickRadioButton(const long id,const int button_index,const string button_text) { //--- Salir si los identificadores no coinciden if(id!=CElement::Id()) return(false); //--- Salir si el texto del botón de opción no coincide if(button_text!=m_radio_buttons.SelectedButtonText()) return(false); //--- Actualizar la paleta de colores de acuerdo con últimos cambios DrawPalette(button_index); return(true); }
- El método CColorPicker::OnEndEdit() sirve para procesar la introducción del valor nuevo en el campo de edición. Aquí bastará comprobar sólo el identificador del control. Después de superar la prueba, se realiza el cálculo de los componentes de todos los modelos de colores respecto al modelo cuyo botón de opción del componente está seleccionado en este momento.
//+------------------------------------------------------------------+ //| Procesamiento de la introducción del valor nuevo en el campo de edición | //+------------------------------------------------------------------+ bool CColorPicker::OnEndEdit(const long id,const int button_index) { //--- Salir si los identificadores no coinciden if(id!=CElement::Id()) return(false); //--- Calculamos y establecemos los componentes del color para todos los modelos de colores SetComponents(button_index,false); return(true); }
- El método CColorPicker::OnClickButtonOK() sirve para el procesamiento del clic en el botón OK. No es la versión definitiva del método. Luego en el artículo será completado un poco. Lo único que necesitamos saber ahora es que al hacer clic en este botón, el color seleccionado se guarda como el color actual.
//+------------------------------------------------------------------+ //| Procesamiento del clic en el botón «OK» | //+------------------------------------------------------------------+ bool CColorPicker::OnClickButtonOK(const string clicked_object) { //--- Salir si el nombre del objeto no coincide if(clicked_object!=m_button_ok.Text()) return(false); //--- Guardar el color seleccionado m_current_color=m_picked_color; m_current.BackColor(m_current_color); m_current.Tooltip(::ColorToString(m_current_color)); return(true); }
- El método CColorPicker::OnClickButtonCancel() sirve para el procesamiento del clic en el botón «Cancel». En este método se comprueba sólo el nombre del objeto. Luego, si el formulario al que está adjuntado el control tiene el tipo «cuadro de diálogo», entonces se cierra.
//+------------------------------------------------------------------+ //| Procesamiento del clic en el botón «Cancel» | //+------------------------------------------------------------------+ bool CColorPicker::OnClickButtonCancel(const string clicked_object) { //--- Salir si el nombre del objeto no coincide if(clicked_object!=m_button_cancel.Text()) return(false); //--- Cerrar la ventana si es de diálogo if(m_wnd.WindowType()==W_DIALOG) m_wnd.CloseDialogBox(); //--- return(true); }
El manejador de eventos de la paleta de colores CColorPicker::OnEvent() va a tener seis bloques en total. Cada método de la lista de arriba a ser llamado cuando llegue el identificador del evento correspondiente. Abajo se muestra el código completo del manejador de eventos del control:
//+------------------------------------------------------------------+ //| Manejador del evento del gráfico | //+------------------------------------------------------------------+ void CColorPicker::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Procesamiento del evento del desplazamiento del cursor if(id==CHARTEVENT_MOUSE_MOVE) { //--- Salir si el control está ocultado if(!CElement::IsVisible()) return; //--- Coordenadas y el estado del botón izquierdo del ratón int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); m_canvas.MouseFocus(x>m_canvas.X() && x<m_canvas.X2()-1 && y>m_canvas.Y() && y<m_canvas.Y2()-1); //--- Obtener el color debajo del cursor del ratón if(OnHoverColor(x,y)) return; //--- return; } //--- Procesamiento del evento del clic izquierdo en el objeto if(id==CHARTEVENT_OBJECT_CLICK) { //--- Si el clic ha sido hecho en la paleta de colores if(OnClickPalette(sparam)) return; //--- return; } //--- Procesamiento de la entrada del valor en el campo de edición if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { //--- Comprobación de la introducción del nuevo valor if(OnEndEdit(lparam,(int)dparam)) return; //--- return; } //--- Procesamiento del clic en el control if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL) { //--- Si el clic ha sido hecho en el botón de opción if(OnClickRadioButton(lparam,(int)dparam,sparam)) return; //--- return; } //--- Procesamiento del clic en los conmutadores de los campos de edición if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC || id==CHARTEVENT_CUSTOM+ON_CLICK_DEC) { //--- Comprobación de la introducción del nuevo valor if(OnEndEdit(lparam,(int)dparam)) return; //--- return; } //--- Procesamiento del clic en el botón del control if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Salir si los identificadores no coinciden if(lparam!=CElement::Id()) return; //--- Si el clic ha sido hecho en el botón «OK» if(OnClickButtonOK(sparam)) return; //--- Si el clic ha sido hecho en el botón «Cancel» if(OnClickButtonCancel(sparam)) return; //--- return; } }
En muchos controles de nuestra librería hay método FastSwitching(). Suele utilizarse para el avance/retroceso rápido de los valores en los campos de edición, desplazamiento de las listas o tablas. No obstante, aquí es necesario para rediibujar la paleta de colores cuando está activado el avance/retroceso rápido de algún contador del campo de edición del componente. Puede estudiar el código del método CColorPicker::FastSwitching() en los archivos adjuntos al artículo.
Botón para abrir la paleta de colores
La clase para la creación de la paleta de colores ya está hecha, pero nos falta otro control más para poder usarla con productividad. Hace falta el botón que permita abrir la ventana con la paleta de colores. El botón debe estar organizado de tal manera que se vea el color que está establecido en este momento. Vamos a escribir la clase para la creación del botón de este tipo. Abajo se listan sus partes integrantes.
- Fondo del control
- Etiqueta de texto con la descripción
- Indicador del color seleccionado
- Fondo del botón
- Descripción del color seleccionado en el formato RGB
Fig. 3. Partes integrantes del botón «Botón para abrir la paleta de colores».
Ya hemos descrito los controles semejantes en los artículos anteriores, por eso no vamos a dar aquí la descripción de esta clase del código. En vez de eso, pasaremos directamente a la interacción entre el botón y la paleta de colores. En archivo adjunto al articulo se puede encontrar el fichero ColorButton.mqh con la clase CColorButton y estudiarlo más detalladamente.
Necesitamos vincular la paleta de colores con el botón de su apertura. Hagámoslo usando el puntero del botón que va a guardarse en la clase CColorPicker. Para eso incluimos el archivo ColorButton.mqh en el archivo ColorPicker.mqh y declaramos la instancia de la clase CColorButton en la que va a almacenarse el puntero al botón de apertura de la paleta de colores.
//+------------------------------------------------------------------+ //| ColorPicker.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "SpinEdit.mqh" #include "SimpleButton.mqh" #include "RadioButtons.mqh" #include "ColorButton.mqh" //+------------------------------------------------------------------+ //| Clase para crear la paleta para seleccionar el color | //+------------------------------------------------------------------+ class CColorPicker : public CElement { private: //--- Puntero al botón que llama al control para seleccionar el color CColorButton *m_color_button; };
Además vamos a necesitar el método público para guardar el puntero al botón. Para facilitar al máximo la interacción entre el botón y la paleta, al pasar el objeto del botón para guardar su puntero, aquí mismo todos los marcadores de la paleta van a recibir el color actual del botón. Después de eso, se abrirá la ventana que contiene la paleta de colores (véase el código de abajo).
class CColorPicker : public CElement { public: //--- Guarda el puntero al botón que llama a la paleta de colores void ColorButtonPointer(CColorButton &object); }; //+------------------------------------------------------------------+ //| Guarda el puntero al botón que llama a la paleta de colores y | //| abre la ventana que contiene la paleta | //+------------------------------------------------------------------+ void CColorPicker::ColorButtonPointer(CColorButton &object) { //--- Guardamos el puntero al botón m_color_button=::GetPointer(object); //--- Establecemos el color del botón pasado para todos los marcadores de la paleta CurrentColor(object.CurrentColor()); //--- Abrimos la ventana a la que está adjuntada la paleta m_wnd.Show(); }
Además, vamos a necesitar los métodos para determinar y obtener el color del botón. El color determinado va a mostrarse en el marcador (indicador) del botón. Como información adicional, en el contenido del texto del botón se mostrará la representación string del color en el formato RGB.
class CColorButton : public CElement { public: //--- Devuelve/establece el color actual del parámetro color CurrentColor(void) const { return(m_current_color); } void CurrentColor(const color clr); }; //+------------------------------------------------------------------+ //| Cambia el color actual del parámetro | //+------------------------------------------------------------------+ void CColorButton::CurrentColor(const color clr) { m_current_color=clr; m_button_icon.BackColor(clr); m_button_label.Description(::ColorToString(clr)); }
Y otra pequeña adición se introduce en el método CColorPicker::OnClickButtonOK(). S el puntero al botón está establecido:
- para el botón se establece el color seleccionado en la paleta de colores;
- la ventana a la que está adjuntada la paleta se cierra;
- se genera el mensaje indicando que en la paleta ha sido seleccionado nuevo color. Aquí se necesitará nuevo identificador del evento ON_CHANGE_COLOR que se encuentra en el archivo Defines.mqh. Además, este mensaje va a contener (1) el identificador del control, (1) identificador del elemento, (2) índice del control y (3) el texto del botón que ha abierto la paleta de colores. Así, en el manejador de la clase personalizada se podrá entender con qué botón está relacionado este mensaje, lo que permitirá procesar el evento correctamente;
- el puntero al botón se anula.
Si no hay puntero al botón, el desarrollador de la aplicación MQL recibirá un mensaje con la ayuda en el registro del terminal.
//+------------------------------------------------------------------+ //| Procesamiento del clic en el botón «OK» | //+------------------------------------------------------------------+ bool CColorPicker::OnClickButtonOK(const string clicked_object) { //--- Salir si el nombre del objeto no coincide if(clicked_object!=m_button_ok.Text()) return(false); //--- Guardar el color seleccionado m_current_color=m_picked_color; m_current.BackColor(m_current_color); m_current.Tooltip(::ColorToString(m_current_color)); //--- Si hay puntero del botón de apertura de la ventana para la selección del color if(::CheckPointer(m_color_button)!=POINTER_INVALID) { //--- Establecer el color seleccionado para el botón m_color_button.CurrentColor(m_current_color); //--- Cerramos la ventana m_wnd.CloseDialogBox(); //--- Enviamos el mensaje sobre ello ::EventChartCustom(m_chart_id,ON_CHANGE_COLOR,CElement::Id(),CElement::Index(),m_color_button.LabelText()); //--- Anulamos el puntero m_color_button=NULL; } else { //--- Si el puntero no existe y la ventana es de diálogo, // mostrar el mensaje que no hay puntero al botón para la apertura del control if(m_wnd.WindowType()==W_DIALOG) ::Print(__FUNCTION__," > Puntero inválido del control de apertura (CColorButton)."); } //--- return(true); }
Ahora tenemos todo preparado para probar la paleta de colores.
Prueba de controles
Se puede utilizar cualquier EA del artículo anterior para realizar el testeo. Haremos la copia y dejaremos dentro el menú principal y la barra de estado. En la ventana principal (W_MAIN) de la interfaz gráfica, crearemos cinco botones para abrir el cuadro de diálogo (W_DIALOG) con la paleta de colores. En otras palabras, será suficiente crear una paleta de colores para toda la aplicación MQL. Cada vez que se pinche en el botón de apertura del cuadro de diálogo con la paleta de colores, se abrirá la misma ventana. Pero en el momento de la llamada habrá que pasar el puntero del botón a la clase CColorPicker personalmente. A continuación, será mostrado cómo eso debe realizarse en la clase personalizada de la aplicación.
En la clase personalizada CProgram, hay que declarar las instancias de las clases para la creación de una ventana más (CWindow), cinco botones para llamar a la paleta de colores (CColorButton) y la paleta de colores (CColorPicker), así como los métodos para su creación con los márgenes desde el punto extremo del formulario.
class CProgram : public CWndEvents { private: //--- Formulario 2 - ventana con la paleta de colores para seleccionar el color CWindow m_window2; //--- Botones para abrir la ventana con la paleta de colores CColorButton m_color_button1; CColorButton m_color_button2; CColorButton m_color_button3; CColorButton m_color_button4; CColorButton m_color_button5; //--- Paleta de colores CColorPicker m_color_picker; //--- private: //--- Formulario 2 bool CreateWindow2(const string text); //--- Botones para abrir la paleta de colores #define COLORBUTTON1_GAP_X (7) #define COLORBUTTON1_GAP_Y (50) bool CreateColorButton1(const string text); #define COLORBUTTON2_GAP_X (7) #define COLORBUTTON2_GAP_Y (75) bool CreateColorButton2(const string text); #define COLORBUTTON3_GAP_X (7) #define COLORBUTTON3_GAP_Y (100) bool CreateColorButton3(const string text); #define COLORBUTTON4_GAP_X (7) #define COLORBUTTON4_GAP_Y (125) bool CreateColorButton4(const string text); #define COLORBUTTON5_GAP_X (7) #define COLORBUTTON5_GAP_Y (150) bool CreateColorButton5(const string text); //--- Paleta de colores #define COLORPICKER_GAP_X (1) #define COLORPICKER_GAP_Y (20) bool CreateColorPicker(void); };
Los códigos de los métodos para la creación de los botones de apertura de la paleta de colores son prácticamente idénticos, por eso vamos a mostrar aquí sólo un ejemplo de ellos. La diferencia puede haber sólo en la determinación del color inicial que va a mostrarse en el indicador del botón inmediatamente después de la determinación, así como del texto en la descripción.
//+------------------------------------------------------------------+ //| Crea el botón para abrir la paleta de colores 1 | //+------------------------------------------------------------------+ bool CProgram::CreateColorButton1(const string text) { //--- Guardamos el puntero a la ventana m_color_button1.WindowPointer(m_window1); //--- Coordenadas int x=m_window1.X()+COLORBUTTON1_GAP_X; int y=m_window1.Y()+COLORBUTTON1_GAP_Y; //--- Establecemos las propiedades antes de la creación m_color_button1.XSize(195); m_color_button1.YSize(18); m_color_button1.ButtonXSize(100); m_color_button1.ButtonYSize(18); m_color_button1.AreaColor(clrWhiteSmoke); m_color_button1.LabelColor(clrBlack); m_color_button1.BackColor(C'220,220,220'); m_color_button1.BorderColor(clrSilver); m_color_button1.CurrentColor(clrRed); //--- Crear el control if(!m_color_button1.CreateColorButton(m_chart_id,m_subwin,text,x,y)) return(false); //--- Añadimos el puntero al control a la base CWndContainer::AddToElementsArray(0,m_color_button1); return(true); }
La única diferencia del código del método para la creación del formulario del cuadro de diálogo y la ventana principal consiste solamente en el hecho de que es necesario especificar el tipo de la ventana (W_DIALOG). Aparte de eso, estableceremos una imagen única para esta ventana que indique en su propósito. Todas las imágenes van adjuntas al final del artículo. .
//+------------------------------------------------------------------+ //| Crea el formulario 2 para la paleta de colores | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\color_picker.bmp" //--- bool CProgram::CreateWindow2(const string caption_text) { //--- Guardamos el puntero a la ventana CWndContainer::AddWindow(m_window2); //--- Coordenadas int x=(m_window2.X()>0) ? m_window2.X() : 30; int y=(m_window2.Y()>0) ? m_window2.Y() : 30; //--- Propiedades m_window2.Movable(true); m_window2.XSize(350); m_window2.YSize(286); m_window2.WindowType(W_DIALOG); m_window2.WindowBgColor(clrWhiteSmoke); m_window2.WindowBorderColor(clrLightSteelBlue); m_window2.CaptionBgColor(clrLightSteelBlue); m_window2.CaptionBgColorHover(clrLightSteelBlue); m_window2.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\color_picker.bmp"); //--- Creación del formulario if(!m_window2.CreateWindow(m_chart_id,m_subwin,caption_text,x,y)) return(false); //--- return(true); }
Abajo se muestra el código del método CProgram::CreateColorPicker() para la creación de la paleta de colores. Es obligatorio guardar el puntero al cuadro de diálogo al que va adjuntarse el control. Al añadir el puntero del control a la base de controles, es necesario pasar el índice de la ventana al que está adjuntado el control. En este caso el índice de la ventana de diálogo es [1].
//+------------------------------------------------------------------+ //| Crea la paleta de colores para seleccionar el color | //+------------------------------------------------------------------+ bool CProgram::CreateColorPicker(void) { //--- Guardamos el puntero a la ventana m_color_picker.WindowPointer(m_window2); //--- Coordenadas int x=m_window2.X()+COLORPICKER_GAP_X; int y=m_window2.Y()+COLORPICKER_GAP_Y; //--- Creación del control if(!m_color_picker.CreateColorPicker(m_chart_id,m_subwin,x,y)) return(false); //--- Añadimos el puntero al control a la base CWndContainer::AddToElementsArray(1,m_color_picker); return(true); }
Al hacer clic en el botón, es necesario cuidar de que su puntero se pase a la paleta de colores. Se puede hacerlo en el manejador de eventos de la clase personalizada CProgram::OnEvent(). Al hacer clic en el botón, se genera el mensaje con el identificador del evento ON_CLICK_BUTTON. Además, el mensaje contiene el texto de la descripción del botón según el cual vamos a determinar qué objeto del botón tipo CColorButton hay que pasar a la paleta de colores. Una vez pasado el objeto del botón, se abrirá la ventana con la paleta de colorees, y en todos sus marcadores va a mostrarse el color del botón cuyo objeto acaba de ser traspasado. El código de abajo lo demuestra:
//+------------------------------------------------------------------+ //| Manejador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento del clic en el botón if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Si ha sido pulsado el primer botón if(sparam==m_color_button1.LabelText()) { m_color_picker.ColorButtonPointer(m_color_button1); return; } //--- Si ha sido pulsado el segundo botón if(sparam==m_color_button2.LabelText()) { m_color_picker.ColorButtonPointer(m_color_button2); return; } //--- Si ha sido pulsado el tercer botón if(sparam==m_color_button3.LabelText()) { m_color_picker.ColorButtonPointer(m_color_button3); return; } //--- Si ha sido pulsado el cuarto botón if(sparam==m_color_button4.LabelText()) { m_color_picker.ColorButtonPointer(m_color_button4); return; } //--- Si ha sido pulsado el quinto botón if(sparam==m_color_button5.LabelText()) { m_color_picker.ColorButtonPointer(m_color_button5); return; } } }
Una vez confirmada la selección del color con el clic en el botón «OK» en la paleta de colores, el mensaje con el identificador ON_CHANGE_COLOR será procesado tal como se muestra en el siguiente listado del código.
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento del cambio del color a través de la paleta de colores if(id==CHARTEVENT_CUSTOM+ON_CHANGE_COLOR) { //--- Si los identificadores de los controles coinciden if(lparam==m_color_picker.Id()) { //--- Si es la respuesta del primer botón if(sparam==m_color_button1.LabelText()) { //--- Cambiar el color del objeto que se refiere al primer botón... return; } //--- Si es la respuesta del segundo botón if(sparam==m_color_button2.LabelText()) { //--- Cambiar el color del objeto que se refiere al segundo botón... return; } //--- Si es la respuesta del tercer botón if(sparam==m_color_button3.LabelText()) { //--- Cambiar el color del objeto que se refiere al tercer botón... return; } //--- Si es la respuesta del cuarto botón if(sparam==m_color_button4.LabelText()) { //--- Cambiar el color del objeto que se refiere al cuarto botón... return; } //--- Si es la respuesta del quinto botón if(sparam==m_color_button5.LabelText()) { //--- Cambiar el color del objeto que se refiere al quinto botón... return; } } return; } }
Compile el programa y cárguelo en el gráfico del terminal. El resultado se muestra en la captura de pantalla de abajo:
Fig. 4. Prueba de los botones para abrir la paleta de colores.
Al pinchar en el botón de apertura de la paleta de colores (uno de cinco en la ventana principal), se abrirá la ventana para seleccionar el color (Color picker), tal como se muestra en la captura de pantalla de abajo:
Fig. 5. Prueba del control «Paleta para seleccionar el color».
¡Ahora todo funciona tal como lo hemos planeado!
Conclusión
En este artículo (el primer capítulo de la novena parte de la serie), hemos analizado un control bastante complejo de la interfaz gráfica, “Paleta para la selección del color”. Como control adicional, hemos creado el botón para abrir la paleta de colores. Ahora los usuarios de nuestra librería tienen posibilidad de controlar los colores de uno u otro objeto usando la interfaz gráfica de su aplicación MQL.
En el siguiente artículo será presentada la descripción de las clases del código de los siguientes elementos: «Indicador de progreso» y «Gráfico lineal».
Más abajo puede descargar el material de la novena parte de la serie para poder probar cómo funciona todo eso. 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 listados más abajo, o bien hacer su pregunta en los comentarios para el artículo.
Lista de artículos (capítulos) de la novena parte:
- Interfaces gráficas IX: Control «Paleta para seleccionar el color» (Capítulo 1)
- Interfaces gráficas IX: Elementos «Indicador de progreso» y «Gráfico lineal» (Capítulo 2)
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2579





- 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