Interfaces gráficas X: Control "Gráfico estándar" (build 4)
Índice
- Introducción
- Desarrollo de la clase para la creación del control «Gráfico estándar»
- Aplicación para la prueba del control
- Optimización del temporizador y del manejador de eventos del motor de la librería
- Optimización del control «Lista jerárquica» y «Explorador de archivos»
- Nuevos iconos para las carpetas y archivos en el explorador de archivos
- 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 cada artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede 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.
Vamos a analizar otro control de la la interfaz del que no hemos podido pasar a la hora de desarrollar nuestra librería. Cuando el usuario abre el terminal de trading, aparecen los gráficos de precios, y estaría bien obtener una herramienta que permitiría manejarlos con más comodidad. Antes escribí el artículo Guía práctica de MQL5: supervisar múltiples períodos de tiempo en una sola ventana en el que fue demostrada una de posibles opciones de esta herramienta. En este caso escribiremos la clase que podríamos usar fácilmente en las interfaces gráficas de nuestras aplicaciones MQL. A diferencia de la versión anterior (ver el enlace de arriba), en esta implementación Usted podrá desplazar el contenido de los objetos-gráficos por la horizontal, tal como ya está acostumbrado de hacerlo en la ventana principal del gráfico.
Aparte de eso, continuaremos optimizando el código de la librería para reducir el consumo de los recursos de CPU. Para más información sobre todo eso, lea a continuación.
Desarrollo de la clase para la creación del control «Gráfico estándar»
Antes de empezar con el desarrollo de la clase CStandardChart para la creación del control «Gráfico estándar», hay que incluir la clase base CSubChart con propiedades adicionales (véase el código de abajo) en el archivo Object.mqh, como ya ha sido hecho antes para todos los tipo de objetos gráficos que se utilizan durante la creación de los controles de la librería. La clase CChartObjectSubChart de la librería estándar servirá de base para la clase CSubChart.
//+------------------------------------------------------------------+ //| Objects.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ ... //--- Lista de las clases en el archivo para una rápida navegación (Alt+G) ... class CSubChart; //+------------------------------------------------------------------+ //| Clase con propiedades adicionales para el objeto Sub Chart | //+------------------------------------------------------------------+ class CSubChart : public CChartObjectSubChart { protected: int m_x; int m_y; int m_x2; int m_y2; int m_x_gap; int m_y_gap; int m_x_size; int m_y_size; bool m_mouse_focus; //--- public: CSubChart(void); ~CSubChart(void); //--- Coordenadas int X(void) { return(m_x); } void X(const int x) { m_x=x; } int Y(void) { return(m_y); } void Y(const int y) { m_y=y; } int X2(void) { return(m_x+m_x_size); } int Y2(void) { return(m_y+m_y_size); } //--- Márgenes desde el punto extremo (xy) int XGap(void) { return(m_x_gap); } void XGap(const int x_gap) { m_x_gap=x_gap; } int YGap(void) { return(m_y_gap); } void YGap(const int y_gap) { m_y_gap=y_gap; } //--- Tamaños int XSize(void) { return(m_x_size); } void XSize(const int x_size) { m_x_size=x_size; } int YSize(void) { return(m_y_size); } void YSize(const int y_size) { m_y_size=y_size; } //--- Foco bool MouseFocus(void) { return(m_mouse_focus); } void MouseFocus(const bool focus) { m_mouse_focus=focus; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSubChart::CSubChart(void) : m_x(0), m_y(0), m_x2(0), m_y2(0), m_x_gap(0), m_y_gap(0), m_x_size(0), m_y_size(0), m_mouse_focus(false) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CSubChart::~CSubChart(void) { }
La clase CChartObjectSubChart incluye el método para la creación del objeto-gráfico, así como los métodos para el cambio de las propiedades del gráfico que se utilizan con más frecuencia. Esta lista contiene los métodos para establecer y obtener las siguientes propiedades:
- coordenadas y tamaños;
- símbolo, período de tiempo y escala;
- visualización de la escala de precio y de tiempo.
//+------------------------------------------------------------------+ //| ChartObjectSubChart.mqh | //| Copyright 2009-2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "ChartObject.mqh" //+------------------------------------------------------------------+ //| Class CChartObjectSubChart. | //| Purpose: Class of the "SubChart" object of chart. | //| Derives from class CChartObject. | //+------------------------------------------------------------------+ class CChartObjectSubChart : public CChartObject { public: CChartObjectSubChart(void); ~CChartObjectSubChart(void); //--- methods of access to properties of the object int X_Distance(void) const; bool X_Distance(const int X) const; int Y_Distance(void) const; bool Y_Distance(const int Y) const; ENUM_BASE_CORNER Corner(void) const; bool Corner(const ENUM_BASE_CORNER corner) const; int X_Size(void) const; bool X_Size(const int size) const; int Y_Size(void) const; bool Y_Size(const int size) const; string Symbol(void) const; bool Symbol(const string symbol) const; int Period(void) const; bool Period(const int period) const; int Scale(void) const; bool Scale(const int scale) const; bool DateScale(void) const; bool DateScale(const bool scale) const; bool PriceScale(void) const; bool PriceScale(const bool scale) const; //--- change of time/price coordinates is blocked bool Time(const datetime time) const { return(false); } bool Price(const double price) const { return(false); } //--- method of creating object bool Create(long chart_id,const string name,const int window, const int X,const int Y,const int sizeX,const int sizeY); //--- method of identifying the object virtual int Type(void) const { return(OBJ_CHART); } //--- methods for working with files virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); };
Ahora puede crear el archivo StandardChart.mqh con la clase CStandardChart en cuyo contenido base se puede determinar ya mismo los métodos de gestión estándar para todos los controles de la librería (véase el código de abajo). Puesto que aquí va a implementarse el modo de desplazamiento horizontal, vamos a necesitar un icono para el puntero del ratón que va a indicar al usuario que el modo está activado y los datos del objeto-gráfico van a desplazarse al ser movido el puntero del ratón por la horizontal. Para cambiar el icono, incluimos el archivo Pointer.mqh con la clase CPointer que ha sido considerada antes en el artículo Interfaces gráficas VIII: Control “Lista jerárquica” (Capítulo 2). Como imagen para el icono del cursor vamos a usar la copia que se activa durante el desplazamiento horizontal del gráfico principal (flecha negra doble con el contorno blanco). Puede descargar dos versiones de esta imagen (flecha negra y azul) al final del artículo.
En consecuencia, la enumeración de los punteros para el cursor del ratón (ENUM_MOUSE_POINTER) se completa con el identificador (MP_X_SCROLL):
//+------------------------------------------------------------------+ //| Enumeración de tipos de punteros | //+------------------------------------------------------------------+ enum ENUM_MOUSE_POINTER { MP_CUSTOM =0, MP_X_RESIZE =1, MP_Y_RESIZE =2, MP_XY1_RESIZE =3, MP_XY2_RESIZE =4, MP_X_SCROLL =5 };
Aparte de eso, hay que incluir los recursos con las imágenes para este tipo de puntero en el archivo Pointer.mqh, y la construcción switch en el método CPointer::SetPointerBmp() hay que ampliarla con otro bloque case:
//+------------------------------------------------------------------+ //| Pointer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" //--- Recursos ... #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp" //+------------------------------------------------------------------+ //| Establecer las imágenes para el puntero según el tipo del puntero | //+------------------------------------------------------------------+ void CPointer::SetPointerBmp(void) { switch(m_type) { case MP_X_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs.bmp"; break; case MP_Y_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs.bmp"; break; case MP_XY1_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_rs.bmp"; break; case MP_XY2_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_rs.bmp"; break; case MP_X_SCROLL : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_scroll.bmp"; break; } //--- Si ha sido especificado el tipo personalizado (MP_CUSTOM) if(m_file_on=="" || m_file_off=="") ::Print(__FUNCTION__," > Para el puntero del cursor hay que establecer ambas imágenes"); }
Aquí hay que mencionar también que el método Moving() ahora puede utilizarse en dos modos que puede ser establecido con el tercer argumento del método. Por defecto, el valor de este argumento es false, lo que significa la posibilidad de mover el control sólo en el caso si el formulario al que está adjuntado se encuentra en el modo de desplazamiento en este momento.
//+------------------------------------------------------------------+ //| StandardChart.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "Pointer.mqh" //+------------------------------------------------------------------+ //| Clase para crear el gráfico estándar | //+------------------------------------------------------------------+ class CStandardChart : public CElement { private: //--- Puntero al formulario al que está adjuntado el control CWindow *m_wnd; //--- public: //--- Guarda el puntero del formulario void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- Manejador del evento del gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Desplazamiento del control virtual void Moving(const int x,const int y,const bool moving_mode=false); //--- (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); //--- private: //--- Cambiar el ancho por el lado derecho de la ventana virtual void ChangeWidthByRightWindowSide(void); //--- Cambiar el alto por el borde inferior de la ventana virtual void ChangeHeightByBottomWindowSide(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CStandardChart::CStandardChart(void) { //--- Guardamos el nombre de la clase del control en la clase base CElement::ClassName(CLASS_NAME); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CStandardChart::~CStandardChart(void) { }
Si el valor del tercer argumento del método Moving() es true, entonces cuando se llama al método, el control será movido de forma forzada, independientemente de que si el formulario se encuentre en el modo de desplazamiento en este momento. En algunas ocasiones, eso reduce considerablemente el consumo de recursos del procesador.
Para saber si se encuentra el formulario en modo de desplazamiento, en la clase CWindow ha sido incluido el método ClampingAreaMouse() que devuelve el área en la que ha sido apretado el botón izquierdo del ratón:
//+------------------------------------------------------------------+ //| Clase de creación del formulario para los controles | //+------------------------------------------------------------------+ class CWindow : public CElement { public: //--- Devuelve el área en la que ha sido apretado el botón izquierdo del ratón ENUM_MOUSE_STATE ClampingAreaMouse(void) const { return(m_clamping_area_mouse); } };
El método CWindow::ClampingAreaMouse() está disponible a través del puntero del formulario en cada control adjunto a él. Para que todo funcione tal como ha sido descrito más arriba, en el método Moving() de cada control se inserta el bloque del código como se muestra a continuación (fragmento marcado en amarillo). Como ejemplo, este método se muestra desde la clase CSimpleButton (versión reducida).
//+------------------------------------------------------------------+ //| Desplazamiento de controles | //+------------------------------------------------------------------+ void CSimpleButton::Moving(const int x,const int y,const bool moving_mode=false) { //--- Salir si el control está ocultado if(!CElement::IsVisible()) return; //--- Si la gestión ha sido pasada a la ventana, determinamos su posición if(!moving_mode) if(m_wnd.ClampingAreaMouse()!=PRESSED_INSIDE_HEADER) return; //--- Si el anclaje está a la derecha //--- Si el anclaje está a la iizquierda //--- Si el anclaje está abajo //--- Si el anclaje está arriba //--- Actualizando las coordenadas de objetos gráficos m_button.X_Distance(m_button.X()); m_button.Y_Distance(m_button.Y()); }
Usted puede ver el ejemplo del uso del método Moving() más abajo donde se muestra el código del método CSimpleButton::Show(). En este caso, la actualización de las coordenadas del control tiene que realizarse de forma forzada, por eso en el tercer argumento tenemos el valor true. Los cambios correspondientes han trancendido a todas las clases de la librería donde se utiliza el método Moving().
//+------------------------------------------------------------------+ //| Muestra el botón | //+------------------------------------------------------------------+ void CSimpleButton::Show(void) { //--- Salir si el control ya está visible if(CElement::IsVisible()) return; //--- Hacer que todos los objetos sean visibles m_button.Timeframes(OBJ_ALL_PERIODS); //--- Estado de visibilidad CElement::IsVisible(true); //--- Actualizar la posición de objetos Moving(m_wnd.X(),m_wnd.Y(),true); }
Más tarde volveremos a la optimización del código de la librería en este artículo, mientras tanto seguiremos analizando el código del control «Gráfico estándar».
Hagamos que haya posibilidad de crear el array de objetos-gráficos colocados en una fila. Por eso es necesario declarar los arrays dinámicos para los objetos que representan los gráfico, así como para algunas propiedades como: (1) identificador del gráfico, (2) símbolo y (3) período de tiempo. Antes de crear el control «Gráfico estándar», hay que usar el método CStandardChart::AddSubChart() en el que tenemos que pasar el símbolo y el período del gráfico. En el principio de este método, antes de añadir el control a los arrays e inicializarlos con valores enviados, se realiza la comprobación del símbolo con el método CStandardChart::CheckSymbol().
class CStandardChart : public CElement { private: //--- Objetos para crear el control CSubChart m_sub_chart[]; //--- Propiedades de gráficos: long m_sub_chart_id[]; string m_sub_chart_symbol[]; ENUM_TIMEFRAMES m_sub_chart_tf[]; //--- public: //--- Añade el gráfico con propiedades especificadas antes de la creación void AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf); //--- private: //--- Comprobación del símbolo bool CheckSymbol(const string symbol); };
Primero, en el método CStandardChart::CheckSymbol() se comprueba si figura este símbolo en la ventana «Observación del mercado». Si el símbolo no ha sido encontrado, se realiza el intento de encontrarlo en la lista general de los símbolos. Si el símbolo ha sido encontrado, es obligatorio añadirlo a la ventana «Observación del mercado», de lo contrario no se podrá crear el objeto-gráfico con este símbolo (se creará el objeto-gráfico con el símbolo de la ventana principal del gráfico).
En caso del éxito, el método CStandardChart::CheckSymbol() devuelve true. Si el símbolo especificado no ha sido encontrado, el método devolverá false y el objeto-gráfico no será añadido (los arrays tendrán el mismo tamaño), en el registro aparecerá un mensaje sobre ello.
//+------------------------------------------------------------------+ //| Añade el gráfico | //+------------------------------------------------------------------+ void CStandardChart::AddSubChart(const string symbol,const ENUM_TIMEFRAMES tf) { //--- Comprobamos si hay este símbolo en el servidor if(!CheckSymbol(symbol)) { ::Print(__FUNCTION__," > Символа "+symbol+" нет на сервере!"); return; } //--- Aumentamos el tamaño del array a un elemento int array_size=::ArraySize(m_sub_chart); int new_size=array_size+1; ::ArrayResize(m_sub_chart,new_size); ::ArrayResize(m_sub_chart_id,new_size); ::ArrayResize(m_sub_chart_symbol,new_size); ::ArrayResize(m_sub_chart_tf,new_size); //--- Guardamos los valores de los parámetros enviados m_sub_chart_symbol[array_size] =symbol; m_sub_chart_tf[array_size] =tf; } //+------------------------------------------------------------------+ //| Comprobación de la `presencia del símbolo | //+------------------------------------------------------------------+ bool CStandardChart::CheckSymbol(const string symbol) { bool flag=false; //--- Comprobamos el símbolo en la ventana "Observación del Mercado" int symbols_total=::SymbolsTotal(true); for(int i=0; i<symbols_total; i++) { //--- Si este símbolo está presente, detenemos el cíclo if(::SymbolName(i,true)==symbol) { flag=true; break; } } //--- Si el símbolo no ha sido encontrado en la ventana «Observación del Mercado», ... if(!flag) { //--- ... intentaremos encontrarlo en la lista general symbols_total=::SymbolsTotal(false); for(int i=0; i<symbols_total; i++) { //--- Si este símbolo está presente, ... if(::SymbolName(i,false)==symbol) { //--- ... lo colocamos en la ventana «Observación del mercado» y detenemos el ciclo» ::SymbolSelect(symbol,true); flag=true; break; } } } //--- Devolver el resultados de búsqueda return(flag); }
Para la creación del control «Gráfico estándar» hacen falta tres métodos: un método público principal (public) y dos privados (private) uno de los cuales se refiere al icono para el cursor del ratón en el modo de desplazamiento horizontal. Como método adicional, a la clase se añade el método público CStandardChart::SubChartsTotal() para obtener el número de objetos-gráficos.
class CStandardChart : public CElement { private: //--- Objetos para crear el control CSubChart m_sub_chart[]; CPointer m_x_scroll; //--- public: //--- Métodos para la creación del gráfico estándar bool CreateStandardChart(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateSubChart(void); bool CreateXScrollPointer(void); //--- public: //--- Devuelve el tamaño del array de gráficos int SubChartsTotal(void) const { return(::ArraySize(m_sub_chart)); } };
Veremos el método CStandardChart::CreateSubCharts() para la creación de objetos-gráficos. Aquí al principio se comprueba el número de gráficos añadidos a los arrays antes de la creación del control. Si no ha sido añadido ninguno, el programa saldrá del método mostrando un mensaje con la descripción en el registro.
Si los gráficos han sido añadidos, se realiza el cálculo del ancho para cada objeto. El usuario debe indicar por sí mismo el ancho del control en la clase personalizada de la aplicación MQL. Si hace falta crear más de un gráfico, entonces para saber el ancho para cada objeto, será suficiente dividir el ancho del control por el número de los gráficos.
Luego, se crean los objetos en el ciclo. Además, se toma en cuenta el posicionamiento del control (puntos de anclaje a uno de los lados del formulario). Este tema ha sido analizado al detalle en el artículo anterior, por eso no vamos a tratarlo aquí.
Después de la creación del gráfico, obtenemos y guardamos en el array el identificador del gráfico creado, establecemos y guardamos sus propiedades.
//+------------------------------------------------------------------+ //| Crea los gráficos | //+------------------------------------------------------------------+ bool CStandardChart::CreateSubCharts(void) { //--- Obtenemos el número de gráficos int sub_charts_total=SubChartsTotal(); //--- Si el grupo no tiene ningún gráfico, avisar sobre ello if(sub_charts_total<1) { ::Print(__FUNCTION__," > ¡La llamada a este método debe realizarse " "cuando en el grupo hay por lo menos un gráfico! Utilice el método CStandardChart::AddSubChart()"); return(false); } //--- Calculamos las coordenadas y el tamaño int x=m_x; int x_size=(sub_charts_total>1)? m_x_size/sub_charts_total : m_x_size; //--- Creamos el número especificado de gráficos for(int i=0; i<sub_charts_total; i++) { //--- Formación del nombre del objeto string name=CElement::ProgramName()+"_sub_chart_"+(string)i+"__"+(string)CElement::Id(); //--- Cálculo de la coordenada X x=(i>0)?(m_anchor_right_window_side)? x-x_size+1 : x+x_size-1 : x; //--- Corrección del ancho del último gráfico if(i+1>=sub_charts_total) x_size=m_x_size-(x_size*(sub_charts_total-1)-(sub_charts_total-1)); //--- Establecemos el botón if(!m_sub_chart[i].Create(m_chart_id,name,m_subwin,x,m_y,x_size,m_y_size)) return(false); //--- Obtenemos y guardamos el identificador del gráfico creado m_sub_chart_id[i]=m_sub_chart[i].GetInteger(OBJPROP_CHART_ID); //--- Establecemos las propiedades m_sub_chart[i].Symbol(m_sub_chart_symbol[i]); m_sub_chart[i].Period(m_sub_chart_tf[i]); m_sub_chart[i].Z_Order(m_zorder); m_sub_chart[i].Tooltip("\n"); //--- Guardamos los tamaños m_sub_chart[i].XSize(x_size); m_sub_chart[i].YSize(m_y_size); //--- Márgenes desde el punto extremo m_sub_chart[i].XGap((m_anchor_right_window_side)? x : x-m_wnd.X()); m_sub_chart[i].YGap((m_anchor_bottom_window_side)? m_y : m_y-m_wnd.Y()); //--- Guardamos el puntero del objeto CElement::AddToArray(m_sub_chart[i]); } //--- return(true); }
Ya después de la creación del control «Gráfico estándar», en cualquier momento se puede modificar cualquiera de las propiedades de sus objetos-gráficos usando el puntero que se puede obtener a través del método CStandardChart::GetSubChartPointer(). Si por casualidad se envía un índice incorrecto, será corregido para evitar la salida fuera del rango del array.
class CStandardChart : public CElement { public: //--- Devuelve el puntero al objeto-gráfico según el índice especificado CSubChart *GetSubChartPointer(const uint index); }; //+------------------------------------------------------------------+ //| Devuelve el puntero al objeto-gráfico según el índice especificado | //+------------------------------------------------------------------+ CSubChart *CStandardChart::GetSubChartPointer(const uint index) { uint array_size=::ArraySize(m_sub_chart); //--- Si no hay ningún gráfico, avisar sobre ello if(array_size<1) { ::Print(__FUNCTION__," > ¡La llamada a este método debe realizarse " "cuando en el grupo hay por lo menos un gráfico!"); } //--- Corrección en caso de salir fuera del diapasón uint i=(index>=array_size)? array_size-1 : index; //--- Devolver el puntero return(::GetPointer(m_sub_chart[i])); }
El icono para el cursor se crea sólo si los objetos gráficos tienen activado el modo de desplazamiento horizontal de datos. Hay que activarlo antes de crear el control usando el método CStandardChart::XScrollMode().
class CStandardChart : public CElement { private: //--- Modo de desplazamiento horizontal bool m_x_scroll_mode; //--- public: //--- Modo de desplazamiento horizontal void XScrollMode(const bool mode) { m_x_scroll_mode=mode; } }; //+------------------------------------------------------------------+ //| Crea el puntero del cursor del desplazamiento horizontal | //+------------------------------------------------------------------+ bool CStandardChart::CreateXScrollPointer(void) { //--- Salir si el desplazamiento horizontal no es necesario if(!m_x_scroll_mode) return(true); //--- Establecemos las propiedades m_x_scroll.XGap(0); m_x_scroll.YGap(-20); m_x_scroll.Id(CElement::Id()); m_x_scroll.Type(MP_X_SCROLL); //--- Creación del control if(!m_x_scroll.CreatePointer(m_chart_id,m_subwin)) return(false); //--- return(true); }
Vamos a resumir lo arriba mencionado. Si el modo de desplazamiento horizontal está activado, para su trabajo se utiliza el método CStandardChart::HorizontalScroll() que va a llamarse en el manejador de eventos cuando llega el evento CHARTEVENT_MOUSE_MOVE. Si el botón izquierdo está pulsado, entonces dependiendo de que haya sido pulsado ahora mismo o se trate de las llamadas repetidas al método en el proceso del desplazamiento horizontal, se realiza el cálculo de la distancia pasada del cursor del ratón en píxeles desde el punto del pulsado. Aquí mismo:
- Se bloquea el formulario.
- Se calculan las coordenadas para la imagen del cursor del ratón.
- Se realiza la visualización de la imagen.
El valor calculado para el desplazamiento dentro de los objetos-gráficos sólo puede ser negativo, ya que el desplazamiento va a realizarse respecto a la última barra: el método ::ChartNavigate() con el valor CHART_END (en el segundo argumento) a partir de la enumeración ENUM_CHART_POSITION. Si el valor del desplazamiento resulta ser positivo, el programa saldrá del método. Si la comprobación ha sido superada, guardamos el valor actual del desplazamiento hasta la siguiente iteración. Luego deshabilitamos el modo «Desplazamiento automático» (CHART_AUTOSCROLL) y «Desplazamiento desde el borde derecho del gráfico» (CHART_SHIFT) para todos los objetos-gráficos y realizamos el desplazamiento de acuerdo con el valor calculado.
Si el botón izquierdo está suelto, el formulario será desbloqueado y la imagen del cursor del ratón que simboliza el proceso del desplazamiento horizontal será ocultada. Después de eso el programa sale del método.
class CStandardChart : public CElement { private: //--- Variables relacionadas con el desplazamiento horizontal del gráfico int m_prev_x; int m_new_x_point; int m_prev_new_x_point; //--- private: //--- Desplazamiento horizontal void HorizontalScroll(void); }; //+------------------------------------------------------------------+ //| Desplazamiento horizontal del gráfico | //+------------------------------------------------------------------+ void CStandardChart::HorizontalScroll(void) { //--- Salir si el modo de desplazamiento horizontal de gráficos está desactivado if(!m_x_scroll_mode) return; //--- Si el botón del ratón está pulsado if(m_mouse.LeftButtonState()) { //--- Recordamos las coordenadas actuales X del cursor if(m_prev_x==0) { m_prev_x =m_mouse.X()+m_prev_new_x_point; m_new_x_point =m_prev_new_x_point; } else m_new_x_point=m_prev_x-m_mouse.X(); //--- Bloquear el formulario if(!m_wnd.IsLocked()) { m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); } //--- Actualizar las coordenadas del puntero y hacerlo visible int l_x=m_mouse.X()-m_x_scroll.XGap(); int l_y=m_mouse.Y()-m_x_scroll.YGap(); m_x_scroll.Moving(l_x,l_y); //--- Mostrar el puntero m_x_scroll.Show(); //--- Establecer la bandera de visibilidad m_x_scroll.IsVisible(true); } else { m_prev_x=0; //--- Desbloquear el formulario if(m_wnd.IdActivatedElement()==CElement::Id()) { m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); } //--- Ocultar el puntero m_x_scroll.Hide(); //--- Establecer la bandera de visibilidad m_x_scroll.IsVisible(false); return; } //--- Salir si el valor es positivo if(m_new_x_point>0) return; //--- Recordar la posición actual m_prev_new_x_point=m_new_x_point; //--- Aplicar a todos los gráficos int symbols_total=SubChartsTotal(); //--- Desactivamos el desplazamiento automático y desplazamiento desde el borde derecho for(int i=0; i<symbols_total; i++) { if(::ChartGetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL)) ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false); if(::ChartGetInteger(m_sub_chart_id[i],CHART_SHIFT)) ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false); } //--- Resetear el último error ::ResetLastError(); //--- Desplazamos los gráficos for(int i=0; i<symbols_total; i++) if(!::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point)) ::Print(__FUNCTION__," > error: ",::GetLastError()); }
Para el reseteo de las variables auxiliares del modo de desplazamiento horizontal de los datos dentro de los objetos-gráficos, se utilizará el método CStandardChart::ZeroHorizontalScrollVariables(). Además, puede que nos sea necesario ir a la última barra mediante programación. Para eso vamos a usar el método público CStandardChart::ResetCharts().
class CStandardChart : public CElement { public: //--- Resetear gráficos void ResetCharts(void); //--- private: //--- Puesta a cero de las variables del desplazamiento horizontal void ZeroHorizontalScrollVariables(void); }; //+------------------------------------------------------------------+ //| Resetear gráficos | //+------------------------------------------------------------------+ void CStandardChart::ResetCharts(void) { int sub_charts_total=SubChartsTotal(); for(int i=0; i<sub_charts_total; i++) ::ChartNavigate(m_sub_chart_id[i],CHART_END); //--- Poner a cero las variables auxiliares para el desplazamiento horizontal de gráficos ZeroHorizontalScrollVariables(); } //+------------------------------------------------------------------+ //| Puesta a cero de las variables del desplazamiento horizontal | //+------------------------------------------------------------------+ void CStandardChart::ZeroHorizontalScrollVariables(void) { m_prev_x =0; m_new_x_point =0; m_prev_new_x_point =0; }
Es muy probable que pueda surgir la necesidad de supervisar el evento de hacer clic izquierdo en un objeto-gráfico del control «Gráfico estándar». Por eso añadimos nuevo identificador en el archivo Defines.mqh:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ ... //--- Identificadores de eventos ... #define ON_CLICK_SUB_CHART (28) // Clic en el objeto-gráfico
Para determinar el clic en el objeto-gráfico, implementamos el método CStandardChart::OnClickSubChart(). Si el nombre e identificador han sido comprobados con éxito (ver el código a continuación), se genera el mensaje con (1) el identificador ON_CLICK_SUB_CHART del evento, (2) identificador del control, (3) índice del objeto-gráfico y (4) el nombre del símbolo.
class CStandardChart : public CElement { private: //--- Procesamiento del clic en el gráfico bool OnClickSubChart(const string clicked_object); }; //+------------------------------------------------------------------+ //| Procesamiento del clic en el botón | //+------------------------------------------------------------------+ bool CStandardChart::OnClickSubChart(const string clicked_object) { //--- Salimos si el clic ha sido hecho fuera del elemento del menú if(::StringFind(clicked_object,CElement::ProgramName()+"_sub_chart_",0)<0) return(false); //--- Obtenemos el identificador e índice desde el nombre del objeto int id=CElement::IdFromObjectName(clicked_object); //--- Salir si el identificador no coincide if(id!=CElement::Id()) return(false); //--- Obtenemos el índice int group_index=CElement::IndexFromObjectName(clicked_object); //--- Enviar la señal sobre ello ::EventChartCustom(m_chart_id,ON_CLICK_SUB_CHART,CElement::Id(),group_index,m_sub_chart_symbol[group_index]); return(true); }
Supongamos que necesita otro modo de navegación por los objetos-gráficos, tal como ha sido implementado para el gráfico principal a través de los medios del terminal. Si pulsa la tecla «Space» o «Enter» en el terminal de trading MetaTrader, en la esquina inferior izquierda del gráfico aparecerá el campo de edición (véase la captura de pantalla a continuación). Es una especie de la línea de comandos en la que se puede introducir una fecha para ir rápidamente a ella en el gráfico. Además, se puede usar esta línea de comandos para cambiar el símbolo y el período de tiempo del gráfico.
Fig. 1. Línea de comandos del gráfico en la esquina inferior izquierda.
Por cierto, en la última actualización de ahora del terminal de trading (build 1455) los desarrolladores han añadido nueva posibilidad de manejar la línea de comandos:
…
6. MQL5: Añadida la propiedad CHART_QUICK_NAVIGATION para activar/desactivar las barras de navegación rápida en el gráfico. Para cambiar y obtener el estado de una propiedad, use las funciones ChartSetInteger y ChartGetInteger.
La barra se llama pulsando las teclas Enter o Space. Con la ayuda de ésta, es posible desplazarse rápidamente hasta la fecha establecida en el gráfico, alternar el símbolo y el marco temporal. Si su programa MQL5 procesa la pulsación de las teclas Enter o Space, desactive la propiedad CHART_QUICK_NAVIGATION, para que el terminal no intercepte estos eventos. En este sentido, seguirá existiendo la posibilidad de llamar la barra de navegación con un doble clic del ratón.
…
Dentro de los márgenes de la la interfaz gráfica, se puede hacer todo eso aún más fácil y cómodo. En la librería Easy And Fast ya existe la clase «Calendario» (clase CCalendar). Se puede implementar la navegación por el gráfico principal y objetos-gráficos seleccionando simplemente una fecha en el calendario. Vamos a simplificar todos eso hasta la llamada a un solo método con un argumento. El valor de este argumento será la fecha hasta la cual hay que desplazar el gráfico. Llamaremos este método CStandardChart::SubChartNavigate(). El código de su versión actual se muestra a continuación.
Al principio del método, desactivamos el modo «Desplazamiento automático del gráfico» y «Desplazamiento desde el borde derecho del gráfico» para el gráfico principal. Luego, si la fecha enviada al método es mayor que la fecha actual, simplemente vamos a la última barra y salimos del método. Si la fecha es menor, hay que calcular a qué número de barras se realiza el desplazamiento a la izquierda. Primero el cálculo se hace para el gráfico actual:
- Obtenemos el número total de barras disponibles para el símbolo y período de tiempo actuales a partir del inicio del día actual hasta la fecha especificada.
- Obtenemos el número de barras visibles en el gráfico.
- Obtenemos el número de barras a partir del día actual + dos barras como margen adicional.
- Calculamos el número de barras para el desplazamiento desde la última barra.
Después de eso se realiza el desplazamiento del gráfico y todo se repite para los objetos-gráficos.
class CStandardChart : public CElement { public: //--- Ir a la fecha especificada void SubChartNavigate(const datetime date); }; //+------------------------------------------------------------------+ //| Ir a la fecha especificada | //+------------------------------------------------------------------+ void CStandardChart::SubChartNavigate(const datetime date) { //--- (1) Fecha actual en el gráfico y (2) la fecha recién seleccionada en el calendario datetime current_date =::StringToTime(::TimeToString(::TimeCurrent(),TIME_DATE)); datetime selected_date =date; //--- Desactivamos el desplazamiento automático y desplazamiento desde el borde derecho ::ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,false); ::ChartSetInteger(m_chart_id,CHART_SHIFT,false); //--- Si la fecha seleccionada en el calendario es mayor que la fecha actual if(selected_date>=current_date) { //--- Ir a la fecha actual en todos los gráficos ::ChartNavigate(m_chart_id,CHART_END); ResetCharts(); return; } //--- Obtenemos el númemro de barras a partir de la fecha especificada int bars_total =::Bars(::Symbol(),::Period(),selected_date,current_date); int visible_bars =(int)::ChartGetInteger(m_chart_id,CHART_VISIBLE_BARS); long seconds_today =::TimeCurrent()-current_date; int bars_today =int(seconds_today/::PeriodSeconds())+2; //--- Establecemos el margen desde el borde derecho de todos los gráficos m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today); ::ChartNavigate(m_chart_id,CHART_END,m_new_x_point); //--- int sub_charts_total=SubChartsTotal(); for(int i=0; i<sub_charts_total; i++) { //--- Desactivamos el desplazamiento automático y desplazamiento desde el borde derecho ::ChartSetInteger(m_sub_chart_id[i],CHART_AUTOSCROLL,false); ::ChartSetInteger(m_sub_chart_id[i],CHART_SHIFT,false); //--- Obtenemos el número de barras a partir de la fecha especificada bars_total =::Bars(m_sub_chart[i].Symbol(),(ENUM_TIMEFRAMES)m_sub_chart[i].Period(),selected_date,current_date); visible_bars =(int)::ChartGetInteger(m_sub_chart_id[i],CHART_VISIBLE_BARS); bars_today =int(seconds_today/::PeriodSeconds((ENUM_TIMEFRAMES)m_sub_chart[i].Period()))+2; //--- Margen desde el borde derecho del gráfico m_prev_new_x_point=m_new_x_point=-((bars_total-visible_bars)+bars_today); ::ChartNavigate(m_sub_chart_id[i],CHART_END,m_new_x_point); } }
Hemos concluido el desarrollo de la clase CStandardChart para la creación del control «Gráfico estándar». Ahora escribiremos la aplicación para ver cómo funciona eso.
Aplicación para la prueba del control
Se puede coger el EA del artículo anterior para la prueba. Eliminemos de ahí todos los controles, salvo el menú principal, la barra de estado y las pestañas. Hagamos que cada pestaña tenga su grupo de objetos-gráficos. Cada grupo va a contener una determinada divisa, por eso cada pestaña tendrá su descripción:
- La primera pestaña – EUR (Euro — euro).
- La segunda pestaña – GBP (Great Britain Pound – libra esterlina).
- La tercera pestaña – AUD (Australian Dollar – dólar australiano).
- La cuarta pestaña – CAD (Canadian Dollar – dólar canadiense).
- La quinta pestaña – JPY (Japanese Yen – yen japonés).
Los objetos-gráficos van a ubicarse estrictamente en el área de trabajo de las pestañas, y cambiar automáticamente su tamaño al cambiar el tamaño del formulario. El lado derecho del área de trabajo de las pestañas siempre va a tener el margen de 173 píxeles desde el borde derecho del formulario. Vamos a llenar este área con los controles para establecer las siguientes propiedades:
- Mostrar la escala de tiempo (Date time).
- Mostrar la escala de precio (Price scale).
- Cambio del marco temporal del gráfico (Timeframes).
- Navegación por los datos del gráfico mediante el calendario.
como ejemplo será suficiente mostrar el código de creación sólo de un control «Gráfico estándar» (CStandardChart). Recordamos que por defecto el modo de desplazamiento horizontal está desactivado, por eso si el desplazamiento es necesario, se puede usar el método CStandardChart::XScrollMode(). Para añadir los gráficos al grupo, se usa el método CStandardChart::AddSubChart().
//+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { protected: //--- Gráfico estándar CStandardChart m_sub_chart1; //--- protected: //--- Gráfico estándar bool CreateSubChart1(const int x_gap,const int y_gap); }; //+------------------------------------------------------------------+ //| Crea el gráfico estándar 1 | //+------------------------------------------------------------------+ bool CProgram::CreateSubChart1(const int x_gap,const int y_gap) { //--- Guardamos el puntero a la ventana m_sub_chart1.WindowPointer(m_window); //--- Adjuntar a la primera pestaña m_tabs.AddToElementsArray(0,m_sub_chart1); //--- Coordenadas int x=m_window.X()+x_gap; int y=m_window.Y()+y_gap; //--- Establecemos las propiedades antes de la creación m_sub_chart1.XSize(600); m_sub_chart1.YSize(200); m_sub_chart1.AutoXResizeMode(true); m_sub_chart1.AutoYResizeMode(true); m_sub_chart1.AutoXResizeRightOffset(175); m_sub_chart1.AutoYResizeBottomOffset(25); m_sub_chart1.XScrollMode(true); //--- Añadimos los gráficos m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1); m_sub_chart1.AddSubChart("EURGBP",PERIOD_D1); m_sub_chart1.AddSubChart("EURAUD",PERIOD_D1); //--- Creamos el control if(!m_sub_chart1.CreateStandardChart(m_chart_id,m_subwin,x,y)) return(false); //--- Añadimos el objeto al array común de los grupos de objetos CWndContainer::AddToElementsArray(0,m_sub_chart1); return(true); }
En la captura de pantalla de abajo se muestra el resultado obtenido. En este ejemplo, Usted puede desplazar los datos en los objetos-gráficos por la horizontal igual que eso se hace en el gráfico principal. Aparte de eso, la navegación por los objetos-gráficos funciona a través del calendario, inclusive durante el avance/retroceso rápido de fechas
Fig. 2. Prueba del control «Gráfico estándar».
Puede descargar esta aplicación de prueba al final del artículo para estudiarla más detalladamente.
Optimización del temporizador y del manejador de eventos del motor de la librería
Con este propósito, las pruebas de la librería Easy And Fast fueron realizadas por mí sólo en el sistema operativo Windows 7 x64. Después de pasar a Windows 10 x64, he notado que el consumo de recursos se aumenta considerablemente. Incluso en el estado de reposo, cuando no se efectúa la interacción con la la interfaz gráfica, los procesos de la librería consumían hasta 10% de los recursos de CPU. En las siguientes capturas de pantalla se muestra el consumo de recursos del procesador hasta y después del inicio de la aplicación MQL en el gráfico.
Fig. 3. Consumo de recursos de CPU hasta el inicio de la aplicación MQL en el gráfico.
Fig. 4. Consumo de recursos de CPU después del inicio de la aplicación MQL en el gráfico.
Resulta que el problema se encuentra en el motor de la librería donde la actualización del gráfico se realiza cada 16 ms (véase el código de abajo):
//+------------------------------------------------------------------+ //| Temporizador | //+------------------------------------------------------------------+ void CWndEvents::OnTimerEvent(void) { //--- Si el array está vacío, mostramos if(CWndContainer::WindowsTotal()<1) return; //--- Comprobación de los eventos de todos los controles por el temporizador CheckElementsEventsTimer(); //--- Redibujamos el gráfico m_chart.Redraw(); }
Si a eso le añadimos el desplazamiento del cursor del ratón en el área del gráfico y la interacción activa con la interfaz gráfica de la aplicación MQL, el consumo crecerá aun más. El objetivo consiste en limitar el trabajo del temporizador del motor de la librería y evitar el redibujo del gráfico cuando llega el evento del desplazamiento del cursor del ratón. ¿Cómo se puede hacerlo?
Eliminemos la línea que se encarga del redibujo del gráfico (marcado en rojo) en el método CWndEvents::ChartEventMouseMove():
//+------------------------------------------------------------------+ //| Evento CHARTEVENT MOUSE MOVE | //+------------------------------------------------------------------+ void CWndEvents::ChartEventMouseMove(void) { //--- Salir si no es un evento de desplazamiento del cursor if(m_id!=CHARTEVENT_MOUSE_MOVE) return; //--- Desplazamiento de la ventana MovingWindow(); //--- Establecer el estado del gráfico SetChartState(); //--- Redibujamos el gráfico m_chart.Redraw(); }
En cuanto al temporizador del motor de la librería, en el momento actual su función se limita a cambiar el color de controles al situar el cursor sobre ellos y realizar el avance/retroceso rápido en diferentes controles (listas, tablas, calendario, etc.). Por eso, no es necesario que funcione permanentemente. Para ahorrar los recursos, hagamos que el temporizador se active cuando el cursor del ratón empiece a mover, y en cuanto el movimiento se detenga, su trabajo va a bloquearse dentro de una pequeña pausa.
Para implementar lo planteado, hay que introducir algunas adiciones en la clase CMouse. Vamos a añadir el contador de llamadas del temporizador de sistema y el método CMouse::GapBetweenCalls() que va a devolver la diferencia entre las llamadas al evento del desplazamiento del cursor del ratón.
class CMouse { private: //--- Contador de llamadas ulong m_call_counter; //--- public: //--- Devuelve (1) el valor del contador guardado durante la última llamada y (2) la diferencia entre las llamadas al manejador del evento del desplazamiento del cursor del ratón ulong CallCounter(void) const { return(m_call_counter); } ulong GapBetweenCalls(void) const { return(::GetTickCount()-m_call_counter); } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CMouse::CMouse(void) : m_call_counter(::GetTickCount()) { }
Aquí la lógica es muy simple. En cuanto el cursor del ratón empiece a mover, guardamos el valor actual del temporizador de sistema en el manejador de eventos de la clase CMouse:
//+------------------------------------------------------------------+ //| Procesamiento de eventos del desplazamiento del cursor del ratón | //+------------------------------------------------------------------+ void CMouse::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) { //--- Coordenadas y el estado del botón izquierdo del ratón m_x =(int)lparam; m_y =(int)dparam; m_left_button_state =(bool)int(sparam); //--- Guardamos el valor del contador de llamadas m_call_counter=::GetTickCount(); //--- Obtenemos la posición del cursor if(!::ChartXYToTimePrice(0,m_x,m_y,m_subwin,m_time,m_level)) return; //--- Obtenemos la coordenada relativa Y if(m_subwin>0) m_y=m_y-m_chart.SubwindowY(m_subwin); } }
Hay que poner una condición en el temporizador del motor de la librería (clase CWndEvents): si el cursor del ratón se encuentra en estado de reposo más de 500 ms, hay que redibujar el gráfico. El botón izquierdo debe estar suelto para no enfrentarse a que el avance/retroceso rápido de los controles trabaje sólo durante 500 ms.
//+------------------------------------------------------------------+ //| Temporizador | //+------------------------------------------------------------------+ void CWndEvents::OnTimerEvent(void) { //--- Salir si el cursor está en estado de reposo (la diferencia entre las llamadas es >500 ms) y el botón izquierdo está suelto if(m_mouse.GapBetweenCalls()>500 && !m_mouse.LeftButtonState()) return; //--- Si el array está vacío, mostramos if(CWndContainer::WindowsTotal()<1) return; //--- Comprobación de los eventos de todos los controles por el temporizador CheckElementsEventsTimer(); //--- Redibujamos el gráfico m_chart.Redraw(); }
El problema está resuelto. La exclusión del redibujo según el evento del desplazamiento del cursor no ha afectado la calidad del desplazamiento del formulario con los controles, ya que el intervalo de 16 ms para el redibujo en el temporizador es más que suficiente para ello. Hemos solucionado el problema de una manera simple pero no única. Ya volveremos a la optimización del código de la librería en los siguientes artículos, porque existen otros métodos y opciones que ayudarán a reducir el consumo de los recursos de CPU de una manera aun más eficaz.
Optimización del control «Lista jerárquica» y «Explorador de archivos»
Se descubrió que cuando había una gran cantidad de elementos en la lista jerárquica (CTreeView), así como en el explorador de archivos (CFileNavigator) que utiliza este tipo de listas, la inicialización era muy prolongada. Para resolver este problema, al añadir el control a los arrays, en la función ::ArrayResize() se indica el tamaño de reserva para el array como el tercer parámetro.
La cita de la Ayuda para la función ::ArrayResize():
…
En caso de la distribución frecuente de la memoria se recomienda utilizar el tercer parámetro que establece una reserva para disminuir la cantidad de distribución física de la memoria. Las siguientes llamadas a la función ArrayResize no llevan a la redistribución física de la memoria, simplemente se cambia el tamaño de la primera dimensión del array dentro de los límites de la memoria reservada. Hay que recordar que el tercer parámetro va a utilizarse sólo cuando va a tener lugar la distribución física de la memoria...
…
A efectos de comparación, abajo se muestran los resultados de las pruebas con diferentes valores del tamaño reservado de los arrays en la lista jerárquica. El número de archivos para la prueba es más de 15 000.
Fig. 5. Resultados de las pruebas de formación de los arrays con el valor de reserva del tamaño.
Vamos a establecer el tamaño para los arrays de la lista jerárquica igual a 10 000. Los cambios correspondientes han sido introducidos en las clases CTreeView y CFileNavigator.
Nuevos iconos para las carpetas y archivos en el explorador de archivos
Han sido añadidos nuevos iconos para las carpetas y archivos del explorador de archivos (clase CFileNavigator), similares a los que están establecidos en el explorador de archivos del sistema operativo Windows 10. Su diseño conciso conviene mejor para las interfaces gráficas de nuestra librería, pero si es necesario, Usted tiene la posibilidad de usar sus versiones.
Fig. 6. Nuevos iconos para las carpetas y archivos en el explorador de archivos.
Estas imágenes están disponibles para la descarga al final del artículo.
Conclusión
En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema general tiene el siguiente aspecto.
Fig. 6. Estructura de la librería en la fase actual del desarrollo.
Seguiremos desarrollando la librería Easy And Fast en los siguientes artículos de la serie sobre las interfaces gráficas. La librería será ampliada con los controles adicionales que pueden ser necesarios en las aplicaciones MQL desarrolladas. Los controles existentes van a desarrollarse y completarse con nuevas posibilidades.
Puede descargar la cuarta (build 4) versión de la librería Easy And Fast al final del artículo.Si está interesado, puede contribuir al desarrollo más rápido de este proyecto si va a proponer su versiones de la solución de algunas tareas, dejando los comentarios para los artículos o enviando mensajes privados.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2763
- 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