DoEasy. Elementos de control (Parte 27): Seguimos trabajando en el objeto WinForms "ProgressBar"
Contenido
Concepto
Vamos a continuar desarrollando el control ProgressBar (barra de progreso), que comenzamos a tratar en el último artículo. En nuestro caso, este elemento constará de dos componentes: el sustrato y la barra de progreso. En el futuro, además de la barra de progreso, podremos colocar otros elementos de diseño en el sustrato: textos, imágenes, etc. Por el momento, este control será un objeto WinForms estático. Hoy desarrollaremos una funcionalidad con la que podremos controlar programáticamente la barra de progreso de este objeto. Así, haremos posible usarlo en nuestros programas para indicar el progreso de aquellas operaciones que requieran un número significativo de acciones idénticas.
Si prestamos atención a las barras de progreso similares en el entorno de Windows, notaremos que mientras se rellena la barra de progreso, esta se ve recorrida por destellos u otros efectos visuales. Aparte de "dar vida" a la interfaz del programa, haciéndola más atractiva, los efectos visuales en la barra de progreso también ejercen como indicación adicional de la finalización del proceso. Si el número de operaciones que muestra la barra de progreso resulta lo suficientemente grande, la barra de progreso no se rellenará en cada operación completada, sino después de un cierto número de ellas. Por consiguiente, incluso si la barra de progreso está llena, las operaciones aún podrían proseguir. En tal situación, si los efectos visuales continúan mostrándose en la barra de progreso, esto indicará un proceso que aún no se ha completado. Una vez se complete el proceso, la barra de progreso ya no mostrará efectos visuales.
Por lo general, al finalizar el proceso, el control ProgressBar también se ocultará, por lo que la indicación descrita anteriormente no resultará visible, pero si creamos la barra de progreso en una ventana aparte, que, una vez completadas todas las operaciones, deba cerrarse manualmente, la indicación de finalización/no finalización del proceso se podrá observar mediante los efectos visuales en la barra de progreso. O, si hay varias barras de progreso al mismo tiempo que muestran un proceso, pero dividido en subprocesos. Luego, mostraremos las barras de progreso de los procesos ya completados hasta que se complete el proceso general, y en los controles ProgressBar que muestran los subprocesos, también podremos observar esta doble indicación: si la barra de progreso está completamente llena, pero los efectos visuales continúan mostrándose en ella, entonces este subproceso aún no se habrá completado al 100%.
La implementación de la funcionalidad de efectos visuales para el control ProgressBar nos permitirá usar su concepto en otros controles de la biblioteca. Cada objeto que deba tener efectos visuales se colocará en la lista de elementos activos. La biblioteca verá esta lista y llamará a los controladores de eventos del temporizador de cada uno de los objetos ubicados en esta lista a su vez en el temporizador. Será precisamente en los propios objetos, en su manejador del temporizador, donde se implementará la funcionalidad necesaria.
Además, hoy mejoraremos ligeramente el dibujado de los iconos predefinidos. En el último artículo, hablamos del extraño comportamiento de las líneas suavizadas al dibujarse estas con transparencia; de hecho, todas las primitivas gráficas dibujadas con los métodos de suavizado de la biblioteca estándar se dibujan de manera anormal si se configuran como transparentes. Es decir, no podemos implementar una aparición o desaparición suave de tales líneas, pero podemos recurrir a un truco: si establecemos la opacidad del color en un cierto valor para la línea, por ejemplo, de 128 a 255, entonces se deberá dibujar con el método de suavizado. Si la opacidad se establece de 127 a 0, dibujaremos dicha línea usando el método habitual sin suavizado. Este enfoque dotará al efecto visual de una sensación de aparición/desaparición suave de las primitivas gráficas dibujadas, mientras que la semitransparencia ocultará la "aspereza" de las líneas dibujadas sin suavizado.
Mejorando las clases de la biblioteca
Al crear un objeto resaltado en el último artículo, partimos de la suposición de que se creará usando como base un objeto de sombra; es decir, se derivará de él para utilizar los métodos de desenfoque disponibles en esta clase. Sin embargo, como ha demostrado la práctica, este enfoque no se ha justificado: además del hecho de que complica la adición de efectos visuales a cualquier objeto, el uso de un destello como heredero de un objeto de sombra limitará su uso en el marco de un concepto ya creado.
Por lo tanto, haremos lo siguiente: el objeto resaltado se heredará de la clase de objeto básico de todos los objetos WinForms de la biblioteca CWinFormBase y estará en la lista de objetos auxiliares. Luego, podremos añadirlo fácilmente con solo una línea de código en la clase donde debe usarse, y ya habremos escrito los manejadores para los objetos activos. Este enfoque nos ahorrará la necesidad de añadir a diferentes clases de la biblioteca funcionalidad idéntica a la funcionalidad de procesamiento de objetos ocultos. Simplemente utilizaremos los métodos preparados para añadir objetos vinculados y procesarlos.
En el archivo \MQL5\Include\DoEasy\Defines.mqh, desplazaremos la línea de identificación del objeto resaltado de la sección de objetos gráficos a la sección de controles auxiliares de la biblioteca:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base //--- 'Container' object types are to be set below GRAPH_ELEMENT_TYPE_WF_CONTAINER, // Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, // Windows Forms GroupBox GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // Windows Forms TabControl GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER, // Windows Forms SplitContainer //--- 'Standard control' object types are to be set below GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, // Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, // Base list object of Windows Forms elements GRAPH_ELEMENT_TYPE_WF_LIST_BOX, // Windows Forms ListBox GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // Windows Forms ButtonListBox GRAPH_ELEMENT_TYPE_WF_TOOLTIP, // Windows Forms ToolTip GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR, // Windows Forms ProgressBar //--- Auxiliary elements of WinForms objects GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, // Windows Forms ListBoxItem GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Windows Forms TabHeader GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, // Windows Forms TabField GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL, // Windows Forms SplitContainerPanel GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON, // Windows Forms ArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP, // Windows Forms UpArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN, // Windows Forms DownArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT, // Windows Forms LeftArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT, // Windows Forms RightArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX, // Windows Forms UpDownArrowButtonsBox GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX, // Windows Forms LeftRightArrowButtonsBox GRAPH_ELEMENT_TYPE_WF_SPLITTER, // Windows Forms Splitter GRAPH_ELEMENT_TYPE_WF_HINT_BASE, // Windows Forms HintBase GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT, // Windows Forms HintMoveLeft GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT, // Windows Forms HintMoveRight GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP, // Windows Forms HintMoveUp GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN, // Windows Forms HintMoveDown GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR, // Windows Forms BarProgressBar GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ, // Glare object }; //+------------------------------------------------------------------+
Para dibujar efectos visuales (en este caso, los destellos), deberemos establecer una lista de estilos de efectos visuales:
//+------------------------------------------------------------------+ //| List of visual effect styles | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_VISUAL_EFF_STYLE { CANV_ELEMENT_VISUAL_EFF_STYLE_RECTANGLE, // Rectangle CANV_ELEMENT_VISUAL_EFF_STYLE_PARALLELOGRAMM, // Parallelogram }; //+------------------------------------------------------------------+
Por ahora, implementaremos solo dos tipos de destellos: un rectángulo regular y un paralelogramo. A continuación, podremos dibujar cualquier número de formas de destellos predefinidas y usarlas para diseñar efectos visuales. No tendrán por qué ser obligatoriamente primitivas gráficas: pueden ser imágenes superpuestas a objetos como efectos, especialmente porque estas imágenes pueden estar animadas, lo cual implementaremos más adelante como ejemplo.
En el archivo \MQL5\Include\DoEasy\Data.mqh, añadiremos los índices de los nuevos mensajes de la biblioteca :
//--- CForm MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// No shadow object. Create it using the CreateShadowObj() method MSG_FORM_OBJECT_TEXT_NO_GLARE_OBJ_FIRST_CREATE_IT, // No glare object. We must first create it using the CreateGlareObj() method MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ, // Failed to create new shadow object MSG_FORM_OBJECT_ERR_FAILED_CREATE_GLARE_OBJ, // Failed to create new glare object MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ, // Failed to create new pixel copier object MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST, // Pixel copier object with ID already present in the list
y los mensajes de texto correspondientes a los nuevos índices añadidos:
//--- CForm {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj() method"}, {"Отсутствует объект блика. Необходимо сначала его создать при помощи метода CreateGlareObj()","There is no glare object. You must first create it using the CreateGlareObj() method"}, {"Не удалось создать новый объект для тени","Failed to create new object for shadow"}, {"Не удалось создать новый объект для блика","Failed to create new object for glare"}, {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"}, {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "},
Después desplazaremos el índice del mensaje "Objeto de destello" al grupo de objetos estándar de WinForms:
MSG_GRAPH_ELEMENT_TYPE_ELEMENT, // Element MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object MSG_GRAPH_ELEMENT_TYPE_FORM, // Form MSG_GRAPH_ELEMENT_TYPE_WINDOW, // Window MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR, // BarProgressBar control MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR, // ProgressBar control MSG_GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ, // Glare object //--- MSG_GRAPH_ELEMENT_TYPE_WF_WRONG_TYPE_PASSED, // Incorrect control type
Y de la misma forma trasladaremos el texto de este mensaje:
{"Элемент","Element"}, {"Объект тени","Shadow object"}, {"Форма","Form"}, {"Окно","Window"}, {"Элемент управления BarProgressBar","Control element \"BarProgressBar\""}, {"Элемент управления ProgressBar","Control element \"ProgressBar\""}, {"Объект блика","Glare object"}, {"Передан не правильный тип элемента управления","Wrong control type passed"},
En el archivo \MQL5\Include\DoEasy\GraphINI.mqh, donde se indican los parámetros de varios esquemas de color, añadiremos una propiedad para especificar el valor de opacidad para el destello. Luego aumentaremos el número total de propiedades a 12:
//+------------------------------------------------------------------+ //| List of form style parameter indices | //+------------------------------------------------------------------+ enum ENUM_FORM_STYLE_PARAMS { //--- CForm FORM_STYLE_FRAME_GLARE_OPACITY, // Glare opacity FORM_STYLE_FRAME_SHADOW_OPACITY, // Shadow opacity FORM_STYLE_FRAME_SHADOW_BLUR, // Shadow blur FORM_STYLE_DARKENING_COLOR_FOR_SHADOW, // Form shadow color darkening FORM_STYLE_FRAME_SHADOW_X_SHIFT, // Shadow X axis shift FORM_STYLE_FRAME_SHADOW_Y_SHIFT, // Shadow Y axis shift FORM_STYLE_GRADIENT_V, // Vertical gradient filling flag FORM_STYLE_GRADIENT_C, // Cyclic gradient filling flag //--- CPanel FORM_STYLE_FRAME_WIDTH_LEFT, // Panel frame width to the left FORM_STYLE_FRAME_WIDTH_RIGHT, // Panel frame width to the right FORM_STYLE_FRAME_WIDTH_TOP, // Panel frame width on top FORM_STYLE_FRAME_WIDTH_BOTTOM, // Panel frame width below }; #define TOTAL_FORM_STYLE_PARAMS (12) // Number of form style parameters //+------------------------------------------------------------------+
Y escribiremos los valores predeterminados para la opacidad de los destellos en el array de propiedades para los dos esquemas de color actualmente disponibles en la biblioteca:
//+------------------------------------------------------------------+ //| Array containing form style parameters | //+------------------------------------------------------------------+ int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]= { //--- "Flat form" style parameters { //--- CForm 40, // Glare opacity 80, // Shadow opacity 4, // Shadow blur 80, // Form shadow color darkening 2, // Shadow X axis shift 2, // Shadow Y axis shift false, // Vertical gradient filling flag false, // Cyclic gradient filling flag //--- CPanel 3, // Panel frame width to the left 3, // Panel frame width to the right 3, // Panel frame width on top 3, // Panel frame width below }, //--- "Embossed form" style parameters { //--- CForm 40, // Glare opacity 80, // Shadow opacity 4, // Shadow blur 80, // Form shadow color darkening 2, // Shadow X axis shift 2, // Shadow Y axis shift true, // Vertical gradient filling flag false, // Cyclic gradient filling flag //--- CPanel 3, // Panel frame width to the left 3, // Panel frame width to the right 3, // Panel frame width on top 3, // Panel frame width below }, }; //+------------------------------------------------------------------+
En el futuro, modificaremos radicalmente y complementaremos la composición de estos valores de propiedad para diferentes esquemas de color cuando comencemos a implementarlos.
Como hemos cambiado hoy la categoría del elemento gráfico de destello, en el archivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh de la clase de objeto básico de todos los objetos gráficos de la biblioteca, en el método que retorna la descripción del tipo del elemento gráfico, trasladaremos el procesamiento del tipo "objeto de destello" transmitido al método desde su ubicación anterior al lugar correspondiente al tipo:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER) : //--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_TOOLTIP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP) : type==GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR) : //--- Auxiliary control objects type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_SPLITTER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER) : type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR) : type==GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ) : "Unknown" ); } //+------------------------------------------------------------------+
Técnicamente, no existe diferencia en el orden de verificación, pero por el bien del orden, lo desplazaremos al lugar "correcto". Por cierto, como es obvio, existe una diferencia desde el punto de vista técnico en la velocidad de acceso, ya que la verificación if-else se organiza aquí y, si algún tipo se verifica con más frecuencia que otros, pero se encuentra más cerca del final de la lista de comprobaciones, el tiempo de acceso aumentará. Pero esto se da solo en teoría en el marco de los ciclos del procesador. No obstante, para que el acceso a cualquier elemento sea el mismo, deberemos realizar todas estas comprobaciones en los métodos de la biblioteca usando el operador switch, cosa que realizaremos posteriormente al final de la creación de la biblioteca.
Antes planeábamos (y ya lo hicimos en el último artículo) que el objeto de destello se heredara del objeto de sombra. La razón radicaba en la presencia de métodos de desenfoque gaussiano en los métodos del objeto de sombra. Ahora hemos abandonado esta idea, porque, en primer lugar, no necesitamos usar el desenfoque para implementar destellos, y en segundo lugar, porque usar un objeto de sombra como principal para un objeto de destello complicará enormemente su adición a otros elementos gráficos como parte de los objetos adjuntos a ellos. Por lo tanto, el objeto de destello será un objeto WinForms auxiliar independiente. No obstante, para que podamos usar el desenfoque en aquellos objetos en los que sea necesario (y no solo en el objeto de sombra), transferiremos los métodos de desenfoque del objeto de sombra al objeto de elemento gráfico; desde allí, estos métodos estarán disponibles para todos los elementos gráficos de la biblioteca.
Ahora, quitaremos del archivo \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh la inclusión de la biblioteca AlgLib, eliminaremos la declaración de los métodos para el desenfoque y borraremos los códigos de implementación para estos dos métodos:
//+------------------------------------------------------------------+ //| ShadowObj.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "GCnvElement.mqh" #include <Math\Alglib\alglib.mqh> //+------------------------------------------------------------------+ //| Shadows object class | //+------------------------------------------------------------------+ class CShadowObj : public CGCnvElement { private: color m_color; // Shadow color uchar m_opacity; // Shadow opacity uchar m_blur; // Blur //--- Draw the object shadow form void DrawShadowFigureRect(const int w,const int h); protected: //--- Gaussian blur bool GaussianBlur(const uint radius); //--- Return the array of weight ratios bool GetQuadratureWeights(const double mu0,const int n,double &weights[]); //--- Protected constructor with object type, chart ID and subwindow CShadowObj(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public:
En el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, escribiremos la conexión de la biblioteca AlgLib y, en la sección protegida, declararemos los métodos para implementar el desenfoque:
//+------------------------------------------------------------------+ //| GCnvElement.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "GBaseObj.mqh" #include <Math\Alglib\alglib.mqh> //+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CGCnvElement *m_element_main; // Pointer to the initial parent element within all the groups of bound objects CGCnvElement *m_element_base; // Pointer to the parent element within related objects of the current group CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_duplicate_res[]; // Array for storing resource data copy color m_array_colors_bg[]; // Array of element background colors color m_array_colors_bg_dwn[]; // Array of control background colors when clicking on the control color m_array_colors_bg_ovr[]; // Array of control background colors when hovering the mouse over the control bool m_gradient_v; // Vertical gradient filling flag bool m_gradient_c; // Cyclic gradient filling flag int m_init_relative_x; // Initial relative X coordinate int m_init_relative_y; // Initial relative Y coordinate color m_array_colors_bg_init[]; // Array of element background colors (initial color) //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); //--- Copy the color array to the specified background color array void CopyArraysColors(color &array_dst[],const color &array_src[],const string source); //--- Return the number of graphical elements (1) by type, (2) by name and type int GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const; int GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const; //--- Create and return the graphical element name by its type string CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type); //--- Gaussian blur bool GaussianBlur(const uint radius); //--- Return the array of weight ratios bool GetQuadratureWeights(const double mu0,const int n,double &weights[]); private:
Fuera del cuerpo de la clase, añadiremos la implementación de los métodos de desenfoque eliminados de la clase CShadowObj:
//+------------------------------------------------------------------+ //| Gaussian blur | //| https://www.mql5.com/en/articles/1612#chapter4 | //+------------------------------------------------------------------+ bool CGCnvElement::GaussianBlur(const uint radius) { //--- int n_nodes=(int)radius*2+1; //--- Read graphical resource data. If failed, return false if(!CGCnvElement::ResourceStamp(DFUN)) return false; //--- Check the blur amount. If the blur radius exceeds half of the width or height, return 'false' if((int)radius>=this.Width()/2 || (int)radius>=this.Height()/2) { ::Print(DFUN,CMessage::Text(MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE)); return false; } //--- Decompose image data from the resource into a, r, g, b color components int size=::ArraySize(this.m_duplicate_res); //--- arrays for storing A, R, G and B color components //--- for horizontal and vertical blur uchar a_h_data[],r_h_data[],g_h_data[],b_h_data[]; uchar a_v_data[],r_v_data[],g_v_data[],b_v_data[]; //--- Change the size of component arrays according to the array size of the graphical resource data if(::ArrayResize(a_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_h_data\""); return false; } if(::ArrayResize(r_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_h_data\""); return false; } if(::ArrayResize(g_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_h_data\""); return false; } if(ArrayResize(b_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_h_data\""); return false; } if(::ArrayResize(a_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_v_data\""); return false; } if(::ArrayResize(r_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_v_data\""); return false; } if(::ArrayResize(g_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_v_data\""); return false; } if(::ArrayResize(b_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_v_data\""); return false; } //--- Declare the array for storing blur weight ratios and, //--- if failed to get the array of weight ratios, return 'false' double weights[]; if(!this.GetQuadratureWeights(1,n_nodes,weights)) return false; //--- Set components of each image pixel to the color component arrays for(int i=0;i<size;i++) { a_h_data[i]=GETRGBA(this.m_duplicate_res[i]); r_h_data[i]=GETRGBR(this.m_duplicate_res[i]); g_h_data[i]=GETRGBG(this.m_duplicate_res[i]); b_h_data[i]=GETRGBB(this.m_duplicate_res[i]); } //--- Blur the image horizontally (along the X axis) uint XY; // Pixel coordinate in the array double a_temp=0.0,r_temp=0.0,g_temp=0.0,b_temp=0.0; int coef=0; int j=(int)radius; //--- Loop by the image width for(int Y=0;Y<this.Height();Y++) { //--- Loop by the image height for(uint X=radius;X<this.Width()-radius;X++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { a_temp+=a_h_data[XY+i]*weights[coef]; r_temp+=r_h_data[XY+i]*weights[coef]; g_temp+=g_h_data[XY+i]*weights[coef]; b_temp+=b_h_data[XY+i]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_h_data[XY]=(uchar)::round(a_temp); r_h_data[XY]=(uchar)::round(r_temp); g_h_data[XY]=(uchar)::round(g_temp); b_h_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts to the left by copying adjacent pixels for(uint x=0;x<radius;x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[Y*this.Width()+radius]; r_h_data[XY]=r_h_data[Y*this.Width()+radius]; g_h_data[XY]=g_h_data[Y*this.Width()+radius]; b_h_data[XY]=b_h_data[Y*this.Width()+radius]; } //--- Remove blur artifacts to the right by copying adjacent pixels for(int x=int(this.Width()-radius);x<this.Width();x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[(Y+1)*this.Width()-radius-1]; r_h_data[XY]=r_h_data[(Y+1)*this.Width()-radius-1]; g_h_data[XY]=g_h_data[(Y+1)*this.Width()-radius-1]; b_h_data[XY]=b_h_data[(Y+1)*this.Width()-radius-1]; } } //--- Blur vertically (along the Y axis) the image already blurred horizontally int dxdy=0; //--- Loop by the image height for(int X=0;X<this.Width();X++) { //--- Loop by the image width for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { dxdy=i*(int)this.Width(); a_temp+=a_h_data[XY+dxdy]*weights[coef]; r_temp+=r_h_data[XY+dxdy]*weights[coef]; g_temp+=g_h_data[XY+dxdy]*weights[coef]; b_temp+=b_h_data[XY+dxdy]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_v_data[XY]=(uchar)::round(a_temp); r_v_data[XY]=(uchar)::round(r_temp); g_v_data[XY]=(uchar)::round(g_temp); b_v_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts at the top by copying adjacent pixels for(uint y=0;y<radius;y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+radius*this.Width()]; r_v_data[XY]=r_v_data[X+radius*this.Width()]; g_v_data[XY]=g_v_data[X+radius*this.Width()]; b_v_data[XY]=b_v_data[X+radius*this.Width()]; } //--- Remove blur artifacts at the bottom by copying adjacent pixels for(int y=int(this.Height()-radius);y<this.Height();y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+(this.Height()-1-radius)*this.Width()]; r_v_data[XY]=r_v_data[X+(this.Height()-1-radius)*this.Width()]; g_v_data[XY]=g_v_data[X+(this.Height()-1-radius)*this.Width()]; b_v_data[XY]=b_v_data[X+(this.Height()-1-radius)*this.Width()]; } } //--- Set the twice blurred (horizontally and vertically) image pixels to the graphical resource data array for(int i=0;i<size;i++) this.m_duplicate_res[i]=ARGB(a_v_data[i],r_v_data[i],g_v_data[i],b_v_data[i]); //--- Display the image pixels on the canvas in a loop by the image height and width from the graphical resource data array for(int X=0;X<this.Width();X++) { for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; this.m_canvas.PixelSet(X,Y,this.m_duplicate_res[XY]); } } //--- Done return true; } //+------------------------------------------------------------------+ //| Return the array of weight ratios | //| https://www.mql5.com/en/articles/1612#chapter3_2 | //+------------------------------------------------------------------+ bool CGCnvElement::GetQuadratureWeights(const double mu0,const int n,double &weights[]) { CAlglib alglib; double alp[]; double bet[]; ::ArrayResize(alp,n); ::ArrayResize(bet,n); ::ArrayInitialize(alp,1.0); ::ArrayInitialize(bet,1.0); //--- double out_x[]; int info=0; alglib.GQGenerateRec(alp,bet,mu0,n,info,out_x,weights); if(info!=1) { string txt=(info==-3 ? "internal eigenproblem solver hasn't converged" : info==-2 ? "Beta[i]<=0" : "incorrect N was passed"); ::Print("Call error in CGaussQ::GQGenerateRec: ",txt); return false; } return true; } //+------------------------------------------------------------------+
Escribimos estos métodos hace mucho tiempo, y ya los hemos analizado en artículos anteriores: simplemente los transferiremos de una clase a otra.
Ya hemos dicho antes que debemos mejorar los métodos para dibujar las primitivas gráficas y las imágenes predefinidas. Como los métodos para dibujar primitivas con suavizado no funcionan muy bien al dibujar líneas semitransparentes, deberemos reemplazar el dibujado de primitivas suavizadas por otras sin suavizar si la transparencia de la línea supera el umbral establecido, a saber, la mitad del valor total de opacidad.
Echemos un vistazo a los métodos actualizados. Si el valor de opacidad de la primitiva gráfica dibujada supera el 127, la primitiva se dibujará con el método de suavizado. De lo contrario, dibujaremos usando el método sin suavizado:
//+------------------------------------------------------------------+ //| Draw the Info icon | //+------------------------------------------------------------------+ void CGCnvElement::DrawIconInfo(const int coord_x,const int coord_y,const uchar opacity) { int x=coord_x+8; int y=coord_y+8; this.DrawCircleFill(x,y,7,C'0x00,0x77,0xD7',opacity); if(opacity>127) this.DrawCircleWu(x,y,7.5,C'0x00,0x3D,0x8C',opacity); else this.DrawCircle(x,y,8,C'0x00,0x3D,0x8C',opacity); this.DrawRectangle(x,y-5,x+1,y-4, C'0xFF,0xFF,0xFF',opacity); this.DrawRectangle(x,y-2,x+1,y+4,C'0xFF,0xFF,0xFF',opacity); } //+------------------------------------------------------------------+ //| Draw the Warning icon | //+------------------------------------------------------------------+ void CGCnvElement::DrawIconWarning(const int coord_x,const int coord_y,const uchar opacity) { int x=coord_x+8; int y=coord_y+1; this.DrawTriangleFill(x,y,x+8,y+14,x-8,y+14,C'0xFC,0xE1,0x00',opacity); if(opacity>127) this.DrawTriangleWu(x,y,x+8,y+14,x-7,y+14,C'0xFF,0xB9,0x00',opacity); else this.DrawTriangle(x,y,x+8,y+14,x-7,y+14,C'0xFF,0xB9,0x00',opacity); this.DrawRectangle(x,y+5,x+1,y+9, C'0x00,0x00,0x00',opacity); this.DrawRectangle(x,y+11,x+1,y+12,C'0x00,0x00,0x00',opacity); } //+------------------------------------------------------------------+ //| Draw the Error icon | //+------------------------------------------------------------------+ void CGCnvElement::DrawIconError(const int coord_x,const int coord_y,const uchar opacity) { int x=coord_x+8; int y=coord_y+8; this.DrawCircleFill(x,y,7,C'0xF0,0x39,0x16',opacity); if(opacity>127) { this.DrawCircleWu(x,y,7.5,C'0xA5,0x25,0x12',opacity); this.DrawLineWu(x-3,y-3,x+3,y+3,C'0xFF,0xFF,0xFF',opacity); this.DrawLineWu(x+3,y-3,x-3,y+3,C'0xFF,0xFF,0xFF',opacity); } else { this.DrawCircle(x,y,8,C'0xA5,0x25,0x12',opacity); this.DrawLine(x-3,y-3,x+3,y+3,C'0xFF,0xFF,0xFF',opacity); this.DrawLine(x+3,y-3,x-3,y+3,C'0xFF,0xFF,0xFF',opacity); } } //+------------------------------------------------------------------+
Obviamente, esto solo ocultará el problema, aunque el uso aquí está justificado: cuando la opacidad disminuye de 255 a 128, la línea suavizada parece más o menos normal, pero luego comienza a romperse y desmoronarse en píxeles en lugar de volverse más transparente. Si remplazamos el dibujado de una línea por el dibujado sin suavizado, esto resultará completamente invisible aquí: la línea ya es semitransparente y en este estado no se notará su transición, que resultará claramente visible si dibujamos una línea sin suavizar completamente opaca.
El resto de los métodos de dibujado de formas predefinidos de esta clase también utilizarán métodos de dibujado suavizado, pero aún no hemos probado cómo quedará todo al cambiar la transparencia. Si también hay artefactos visuales en esos métodos, los mejoraremos exactamente de la misma forma.
Como ahora el objeto resaltado no es un tipo aislado separado (como un objeto de sombra), sino que pertenece a la lista de objetos WinForms auxiliares, la inclusión del archivo de clase de este objeto ahora deberá realizarse en el archivo de la clase del objeto de panel. Este objeto será un objeto contenedor al que estarán conectados los archivos de todos los objetos WinForms de la biblioteca.
Desde el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh, eliminaremos la línea de conexión de la clase del objeto de destello:
//+------------------------------------------------------------------+ //| WinFormBase.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "GlareObj.mqh" #include "..\Form.mqh" #include "..\..\..\Services\Select.mqh" //+------------------------------------------------------------------+
y escribiremos la inclusión del archivo de la clase de objeto de destello en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh de la clase de objeto de panel:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "..\Helpers\TabField.mqh" #include "..\Helpers\ArrowUpButton.mqh" #include "..\Helpers\ArrowDownButton.mqh" #include "..\Helpers\ArrowLeftButton.mqh" #include "..\Helpers\ArrowRightButton.mqh" #include "..\Helpers\ArrowUpDownBox.mqh" #include "..\Helpers\ArrowLeftRightBox.mqh" #include "..\Helpers\HintMoveLeft.mqh" #include "..\Helpers\HintMoveRight.mqh" #include "..\Helpers\HintMoveUp.mqh" #include "..\Helpers\HintMoveDown.mqh" #include "GroupBox.mqh" #include "TabControl.mqh" #include "SplitContainer.mqh" #include "..\..\WForms\Common Controls\ListBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" #include "..\..\WForms\Common Controls\ButtonListBox.mqh" #include "..\..\WForms\Common Controls\ToolTip.mqh" #include "..\..\WForms\Common Controls\ProgressBar.mqh" #include "..\..\WForms\GlareObj.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CContainer
Ahora el objeto destacado estará disponible en todos los objetos contenedores, a partir de los cuales podremos crearlo y conectarlo a las listas de objetos adjuntos en cualquier otro elemento gráfico de la biblioteca. Y como cualquier ventana de interfaz independiente es a priori un contenedor, podremos añadir un objeto de destello (y posteriormente un objeto de efecto visual) a cualquier control adjunto a esta ventana.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh de la clase del objeto contenedor básico, en su método para establecer los parámetros del objeto adjunto, añadiremos un bloque de código al final para establecer los parámetros del objeto de destello recién creado:
//+------------------------------------------------------------------+ //| Set parameters for the attached object | //+------------------------------------------------------------------+ void CContainer::SetObjParams(CWinFormBase *obj,const color colour) { //--- Set the text color of the object to be the same as that of the base container obj.SetForeColor(this.ForeColor(),true); //--- If the created object is not a container, set the same group for it as the one for its base object if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER) obj.SetGroup(this.Group()); //--- Depending on the object type switch(obj.TypeGraphElement()) { //--- For the Container, Panel and GroupBox WinForms objects case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : obj.SetBorderColor(obj.BackgroundColor(),true); break; //--- For "Label", "CheckBox" and "RadioButton" WinForms objects //---... //---... //--- For ProgressBar WinForms object case GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR : obj.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BACK_COLOR,true); obj.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For GlareObj WinForms object case GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ : obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetBorderColor(CLR_CANV_NULL,true); obj.SetForeColor(CLR_CANV_NULL,true); obj.SetBorderStyle(FRAME_STYLE_NONE); break; default: break; } obj.Crop(); } //+------------------------------------------------------------------+
Después de crear cualquiera de los controles gráficos adjuntos al contenedor, llamaremos a este método, que se encargará de establecer las propiedades mínimas para que el objeto funcione correctamente.
Vamos a mejorar la clase de objeto de destello en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\GlareObj.mqh.
La clase heredará ahora de la clase de objeto WinForms básico. Ahora incluiremos su archivo en el archivo de la clase de objeto de destello y cambiaremos la clase padre:
//+------------------------------------------------------------------+ //| GlareObj.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "WinFormBase.mqh" //+------------------------------------------------------------------+ //| Glare object class | //+------------------------------------------------------------------+ class CGlareObj : public CWinFormBase { //---...
En la sección privada, declararemos una variable para almacenar el estilo de destello y cambiaremos los métodos de dibujado del destello, declarando otro más:
class CGlareObj : public CWinFormBase { private: ENUM_CANV_ELEMENT_VISUAL_EFF_STYLE m_style; // Glare style color m_color; // Glare color uchar m_opacity; // Glare opacity uchar m_blur; // Blur //--- Draw the object glare form void DrawGlareFigure(void); void DrawFigureRectangle(void); void DrawFigureParallelogram(void); protected:
El estilo de destello determinará qué método llamar para dibujarlo. En este caso, el método DrawGlareFigure() llamará a los métodos de dibujado correspondientes al estilo establecido. No habrá parámetros formales en los métodos de dibujado del destello: por el momento, no hay nada que especificar para la correcta representación de una imagen en un elemento gráfico con un tamaño predefinido. El color y la opacidad del destello a dibujar se establecerán de antemano usando los métodos apropiados. El desenfoque aún no se utilizará.
En la sección pública de la clase, declararemos los métodos para dibujar un destello, redibujarlo, configurar la opacidad e inicializar las propiedades, y escribiremos los métodos para configurar y obtener algunas propiedades del objeto:
public: //--- Constructor indicating the main and base objects, chart ID and subwindow CGlareObj(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- Supported object properties (1) integer, (2) real and (3) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Draw the object glare void Draw(void); //--- Redraw the object virtual void Redraw(bool redraw); //--- Set the element opacity virtual void SetOpacity(const uchar value,const bool redraw=false); //--- Initialize the properties void Initialize(void); //+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) return the glare color void SetColor(const color colour) { this.m_color=colour; } color Color(void) const { return this.m_color; } //--- (1) Set and (2) return the opacity of the drawn glare void SetOpacityDraw(const uchar opacity) { this.m_opacity=opacity; } uchar OpacityDraw(void) const { return this.m_opacity; } //--- (1) Set and (2) return the glare blur void SetBlur(const uchar blur) { this.m_blur=blur; } uchar Blur(void) const { return this.m_blur; } //--- (1) Set and (2) return the glare visual effect void SetVisualEffectStyle(const ENUM_CANV_ELEMENT_VISUAL_EFF_STYLE style) { this.m_style=style; } ENUM_CANV_ELEMENT_VISUAL_EFF_STYLE VisualEffectStyle(void) const { return this.m_style; } //--- Set the glare visual effect style as (1) rectangle and (2) parallelogram void SetStyleRectangle(void) { this.SetVisualEffectStyle(CANV_ELEMENT_VISUAL_EFF_STYLE_RECTANGLE); } void SetStyleParallelogramm(void) { this.SetVisualEffectStyle(CANV_ELEMENT_VISUAL_EFF_STYLE_PARALLELOGRAMM); } }; //+------------------------------------------------------------------+
En el bloque de métodos para el acceso simplificado a las propiedades de los objetos, escribiremos los métodos en los que los valores trasmitidos al método simplemente se establecerán en variables privadas; los valores registrados se retornarán desde estas variables.
En los constructores de clases, reemplazaremos la inicialización de la clase principal CShadowObj con CWinFormBase y añadiremos la llamada al método de inicialización de variables:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CGlareObj::CGlareObj(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CWinFormBase(type,main_obj,base_obj,chart_id,subwindow,descript,x-OUTER_AREA_SIZE,y-OUTER_AREA_SIZE,w+OUTER_AREA_SIZE*2,h+OUTER_AREA_SIZE*2) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GGLARE; this.Initialize(); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CGlareObj::CGlareObj(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GGLARE; this.Initialize(); } //+------------------------------------------------------------------+
Ahora, en lugar de escribir en cada constructor las líneas de inicialización para cada variable (lo mismo para ambos constructores), simplemente llamaremos al método de inicialización de la propiedad del objeto:
//+------------------------------------------------------------------+ //| Initialize the properties | //+------------------------------------------------------------------+ void CGlareObj::Initialize(void) { CGCnvElement::SetBackgroundColor(CLR_CANV_NULL,true); CGCnvElement::SetActive(false); this.SetBlur(4); this.SetColor(clrWhite); this.SetOpacity(127); this.m_shadow=false; this.SetVisibleFlag(false,false); this.SetVisualEffectStyle(CANV_ELEMENT_VISUAL_EFF_STYLE_RECTANGLE); this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_NORMAL); this.SetDisplayed(false); } //+------------------------------------------------------------------+
El método establecerá los valores predeterminados para las propiedades del objeto. Estableceremos el desenfoque en 4, y el color de destello en blanco. La opacidad será de 127: un objeto semitransparente, sin sombra (lo cual es natural). Asimismo, estableceremos la bandera de invisibilidad del objeto, y el tipo de efecto visual será un destello rectangular. El estado de la visualización será el normal (el objeto estará oculto), mientras que la bandera de visualización estará reseteada: el objeto no deberá mostrarse en el objeto principal visualizado.
Método que establece la opacidad de un elemento:
//+------------------------------------------------------------------+ //| Set the element opacity | //+------------------------------------------------------------------+ void CGlareObj::SetOpacity(const uchar value,const bool redraw=false) { CGCnvElement::SetOpacity(0,false); this.SetOpacityDraw(value>(uchar)CLR_DEF_SHADOW_OPACITY ? (uchar)CLR_DEF_SHADOW_OPACITY : value); this.m_canvas.Update(redraw); } //+------------------------------------------------------------------+
El método será idéntico al método del objeto de sombra del mismo nombre. Aquí, el sustrato sobre el que se dibuja el destello se establecerá en transparencia total, y el color utilizado para dibujar la imagen resaltada será el valor transmitido al método. En este caso, si el valor de opacidad transmitido al método es mayor que el valor de opacidad de la sombra por defecto, entonces se usará el valor predeterminado. De lo contrario, se transmitirá al método. Esto se hace para que el objeto nunca quede completamente opaco. El valor predeterminado para la sombra lo usaremos simplemente porque resultará bastante adecuado para estos fines.
Método para dibujar destellos:
//+------------------------------------------------------------------+ //| Draw the object glare | //+------------------------------------------------------------------+ void CGlareObj::Draw(void) { if(!this.IsVisible()) return; //--- Draw the glare this.DrawGlareFigure(); } //+------------------------------------------------------------------+
Si el objeto está oculto, no necesitaremos dibujar nada; saldremos del método.
Luego llamaremos al método en el que se selecciona el método de dibujado:
//+------------------------------------------------------------------+ //| Draw the object glare form | //+------------------------------------------------------------------+ void CGlareObj::DrawGlareFigure() { switch(this.VisualEffectStyle()) { case CANV_ELEMENT_VISUAL_EFF_STYLE_RECTANGLE : this.DrawFigureRectangle(); break; case CANV_ELEMENT_VISUAL_EFF_STYLE_PARALLELOGRAMM : this.DrawFigureParallelogram(); break; default: break; } } //+------------------------------------------------------------------+
Según el tipo de destello dibujado, llamaremos al método correspondiente que ya dibujará en realidad la forma del destello.
Método que dibuja la forma rectangular del destello de un objeto:
//+------------------------------------------------------------------+ //| Draw the rectangle object glare form | //+------------------------------------------------------------------+ void CGlareObj::DrawFigureRectangle(void) { CGCnvElement::DrawRectangleFill(0,0,this.Width()-1,this.Height()-1,this.m_color,this.OpacityDraw()); CGCnvElement::Update(); } //+------------------------------------------------------------------+
Simplemente dibujaremos un rectángulo relleno sobre todo el tamaño del objeto con el color del objeto y la opacidad de dibujado establecida.
Método que dibuja la forma del destello del objeto como un paralelogramo:
//+------------------------------------------------------------------+ //| Draw the shape of the object glare as a parallelogram | //+------------------------------------------------------------------+ void CGlareObj::DrawFigureParallelogram(void) { int array_x[]={6,this.Width()-1,this.Width()-1-6,0}; int array_y[]={0,0,this.Height()-1,this.Height()-1}; CGCnvElement::DrawPolygonFill(array_x,array_y,this.m_color,this.OpacityDraw()); CGCnvElement::Update(); } //+------------------------------------------------------------------+
Aquí se dibujará un rectángulo "biselado". Primero, declararemos los arrays de coordenadas X e Y de los vértices del polígono, y luego, utilizando los vértices indicados en los conjuntos, dibujaremos un polígono relleno en forma de rectángulo biselado: un paralelogramo con el color y la opacidad de dibujado establecidos para el objeto.
Si simplemente dibujamos un objeto una vez y luego lo desplazamos, notaremos que cuando el objeto movido va más allá de los límites de la barra de progreso (en este caso, el objeto se desplazará precisamente a lo largo de él), el objeto de destello permanecerá visible. Visualmente, parecerá que el destello "salta" fuera de la barra de progreso y se dibuja en el panel al que está vinculado el control ProgressBar. Para evitar esto, deberemos redibujar el objeto recortando aquellas partes que vayan más allá de su contenedor. Para lograr esto, necesitaremos un método para redibujar el objeto.
Método virtual que redibuja un objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CGlareObj::Redraw(bool redraw) { CGCnvElement::Erase(false); this.Draw(); this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Aquí, primero borraremos completamente el objeto con su relleno de color y opacidad. Como hemos establecido el color de fondo del objeto en transparente y el valor de opacidad es cero, el objeto se volverá completamente transparente. A continuación, llamaremos al método anteriormente analizado para dibujar la forma de destello establecida, cortaremos las áreas del objeto que sobresalgan del contenedor y actualizaremos el lienzo.
Ahora bastará con llamar a este método al mover el objeto a lo largo de la barra de progreso y el destello no irá más allá de su contenedor.
Vamos a desarrollar la funcionalidad del objeto ProgressBar para controlar su barra de progreso.
Para mostrar el progreso de un determinado proceso, necesitaremos aumentar la anchura del objeto BarProgressBar en una cantidad igual al paso de aumento de la barra de progreso. En general, todos los valores de anchura de la barra de progreso serán relativos, como porcentaje de la anchura del objeto ProgressBar y como porcentaje del valor máximo del parámetro Value de la barra de progreso. La barra de progreso podrá cambiar sus valores dentro de los límites establecidos en los parámetros Minimum y Maximum. Pero estos valores no deberán establecerse en píxeles, sino como porcentaje de la anchura del objeto. Entonces, un Value de 100 tendría 100 píxeles de anchura cuando ProgressBar tuviera la misma anchura, pero sería 200 cuando ProgressBar tuviera 200 píxeles de anchura. En este caso, si el valor de Value es 50, se corresponderá con los valores de 50 y 100 píxeles para los anteriores valores de anchura de objeto.
Para que podamos controlar simplemente los valores de la barra de progreso, haremos que antes de usar el objeto, antes de iniciar el ciclo cuyo proceso deberá mostrarse, establezcamos los valores mínimo y máximo de los límites y establezcamos el paso en el que se deberá aumentar el valor de la anchura de la barra de progreso. Y luego, tras completar la siguiente acción en el ciclo rastreado, solo necesitaremos llamar al método PerformStep() del objeto ProgressBar, y la anchura de la barra de progreso aumentará según el paso de aumento especificado. Por consiguiente, siempre deberemos actuar así: conociendo el número de iteraciones del ciclo monitoreado, asignaremos los valores necesarios al objeto ProgressBar y, dentro del ciclo, al completarse la siguiente iteración, llamaremos al método PerformStep(), lo cual hará que la barra de progreso cambie en un paso.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ProgressBar.mqh, en su sección privada, declararemos las variables para almacenar los valores de la barra de progreso, así como el método para calcular la anchura de la barra de progreso:
//+------------------------------------------------------------------+ //| ArrowLeftRightBox object class of WForms controls | //+------------------------------------------------------------------+ class CProgressBar : public CContainer { private: int m_progress_bar_max; // Maximum progress bar width int m_value_by_max; // Value relative to Maximum int m_steps_skipped; // Number of skipped steps of increasing the width of the progress bar //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Create the progress bar object void CreateProgressBar(void); //--- Calculate the progress bar width int CalculateProgressBarWidth(void); //--- Initialize the element properties void Initialize(void); protected:
La implementación del método público SetValue() se sacará del cuerpo de la clase, dejando en el cuerpo solo su declaración:
//--- (1) Set and (2) return the progress bar increment to redraw it void SetStep(const int value) { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,value); CBarProgressBar *bar=this.GetProgressBar(); if(bar!=NULL) bar.SetStep(value); } int Step(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STEP); } //--- (1) Set and (2) return the current value of the progress bar in the range from Min to Max void SetValue(const int value); int Value(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE); } //--- (1) Set and (2) return the upper bound of the ProgressBar operating range void SetMaximum(const int value) { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM,value); } int Maximum(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM); }
A continuación, declararemos algunos métodos más:
//--- Return the pointer to the progress bar object CBarProgressBar *GetProgressBar(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,0); } //--- Resets the progress bar values to the set minimum void ResetProgressBar(void) { this.SetValue(this.Minimum()); } //--- Set the element width virtual bool SetWidth(const int width); //--- Initialize values for handling in PerformStep void SetValuesForProcessing(const int minimum,const int maximum,const int step,const int steps_skipped); //--- Increase the current position of the progress bar by the step value void PerformStep(); //--- Supported object properties (1) integer, (2) real and (3) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; }
El método ResetProgressBar() se implementará directamente en el cuerpo de la clase y establecerá el valor Value de la barra de progreso en el valor escrito en la propiedad Minimum. Esto permitirá devolver la barra de progreso a su estado original.
En el método de inicialización del valor predeterminado , estableceremos el valor de la barra de progreso en el 50% de la anchura del objeto, calcularemos la anchura máxima en píxeles para el objeto BarProgressBar, que será la barra de progreso, y calcularemos el valor como un porcentaje de la anchura del objeto.:
//+------------------------------------------------------------------+ //| Initialize the element properties | //+------------------------------------------------------------------+ void CProgressBar::Initialize(void) { this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BACK_COLOR,true); this.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true); this.SetMarqueeAnimationSpeed(10); this.SetMaximum(100); this.SetMinimum(0); this.SetValue(50); this.SetStep(10); this.SetStyle(CANV_ELEMENT_PROGRESS_BAR_STYLE_CONTINUOUS); this.m_progress_bar_max=this.Width()-this.BorderSizeLeft()-this.BorderSizeRight(); this.m_value_by_max=this.Value()*100/this.Maximum(); this.m_steps_skipped=0; } //+------------------------------------------------------------------+
La anchura máxima del objeto de la barra de progreso no deberá superar la anchura del sustrato menos el tamaño de su marco a la izquierda y a la derecha, para que la barra de progreso pueda caber dentro del marco dibujado en el fondo sin salirse de sus límites.
En el método que crea el objeto de la barra de progreso, estableceremos la anchura del objeto creado a partir del valorcalculado por el método CalculateProgressBarWidth()y añadiremos la creación de un objeto de destello:
//+------------------------------------------------------------------+ //| Create the progress bar object | //+------------------------------------------------------------------+ void CProgressBar::CreateProgressBar(void) { //--- Set the length of the progress bar equal to the object Value() //--- The height of the progress bar is equal to the height of the object minus the top and bottom frame sizes int w=this.CalculateProgressBarWidth(); int h=this.Height()-this.BorderSizeTop()-this.BorderSizeBottom(); //--- Create the progress bar object this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,0,0,w,h,clrNONE,255,false,false); //--- Create the glare object this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ,0,0,w,h,CLR_CANV_NULL,0,true,false); //--- Add the current CProgressBar object to the list of active elements of the collection this.AddObjToListActiveElements(); } //+------------------------------------------------------------------+
La lógica del método se explica con detalle en los comentarios al código. Aquí todo es simple: calcularemos la anchura de la barra de progreso y crearemos un objeto con la anchura calculada. El valor predeterminado supondrá el 50 % de la anchura del objeto ProgressBar. Luego crearemos un objeto de destello y añadiremos el objeto actual a la lista de objetos activos. Todos los objetos que se encuentran en la lista de elementos activos se procesarán en el temporizador de la biblioteca y podrán realizar algunas acciones independientes que se implementan en el controlador del temporizador de estos objetos. Para este control, el temporizador procesará la aparición de un destello en la barra de progreso.
Método que inicializa los valores para su procesamiento en PerformStep:
//+------------------------------------------------------------------+ //| Initialize values for handling in PerformStep | //+------------------------------------------------------------------+ void CProgressBar::SetValuesForProcessing(const int minimum,const int maximum,const int step,const int steps_skipped) { this.SetMinimum(minimum<0 ? 0 : minimum); this.SetMaximum(maximum<this.Minimum() ? this.Minimum() : maximum); this.SetStep(step<0 ? 0 : step); this.m_steps_skipped=steps_skipped; } //+------------------------------------------------------------------+
Aquí, los valores de los límites mínimo y máximo se transmitirán al método, dentro del cual podremos cambiar el valor de Value, el paso de cambio y la cantidad de pasos omitidos. A continuación, verificaremos la corrección de los valores mínimo, máximo y de paso, y escribiremos en la variable el valor de los pasos omitidos. Como el valor de la barra de progreso se calcula como un porcentaje de la anchura del objeto ProgressBar, lo más probable es que no usemos el valor de los pasos omitidos; esto quedará claro a partir de una serie de pruebas de objetos una vez que estén listos. En esta implementación, dicho valor no se usará en ninguna parte.
Método que establece el valor actual de la barra de progreso:
//+------------------------------------------------------------------+ //| Set the current value of the progress bar | //+------------------------------------------------------------------+ void CProgressBar::SetValue(const int value) { //--- Correct the value passed to the method and set it to the object property int v=(value<this.Minimum() ? this.Minimum() : value>this.Maximum() ? this.Maximum() : value); this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,v); //--- Get the progress bar object CBarProgressBar *bar=this.GetProgressBar(); if(bar!=NULL) { //--- Set 'value' for the progress bar bar.SetValue(v); //--- Calculate the width of the progress bar object int w=this.CalculateProgressBarWidth(); //--- If the calculated width is greater than the maximum possible value, set the maximum width if(w>this.m_progress_bar_max) w=this.m_progress_bar_max; //--- If the width is less than 1, then if(w<1) { //--- hide the progress bar and redraw the chart to display changes immediately bar.Hide(); ::ChartRedraw(bar.ChartID()); } //--- If the width value is not less than 1 else { //--- If the progress bar is hidden, display it and if(!bar.IsVisible()) bar.Show(); //--- change its size according to the received width bar.Resize(w,bar.Height(),true); } } } //+------------------------------------------------------------------+
La lógica del método se detalla en los comentarios al código. Dicho de forma más clara: si al calcular la anchura de un objeto, su anchura resulta menor a un píxel, ese valor no se podrá establecer para el objeto. Por lo tanto, le asignaremos una anchura de un píxel y ocultaremos el objeto, simulando así una barra de progreso de anchura cero.
Método que aumenta la posición actual de la barra de progreso en el valor del paso:
//+------------------------------------------------------------------------+ //| Increase the current position of the progress bar by the step value | //+------------------------------------------------------------------------+ void CProgressBar::PerformStep(void) { this.SetValue(this.Value()+this.Step()); } //+------------------------------------------------------------------+
Aquí simplemente estableceremos la propiedad Value del objeto en su valor pasado, más el valor de incremento del paso de este valor especificado anteriormente. Por lo tanto, con cada llamada subsiguiente a este método, el valor de Value aumentará en el incremento registrado en Step.
Método que calcula la anchura de la barra de progreso:
//+------------------------------------------------------------------+ //| Calculate the width of the progress bar | //+------------------------------------------------------------------+ int CProgressBar::CalculateProgressBarWidth(void) { this.m_value_by_max=this.Value()*100/this.Maximum(); return this.m_progress_bar_max*this.m_value_by_max/100; } //+------------------------------------------------------------------+
Aquí, primero calcularemos el valor de Value como un porcentaje del máximo posible y luego retornaremos la anchura relativa de la anchura máxima de la barra de progreso como un porcentaje del valor calculado anteriormente.
Método que establece la nueva anchura del objeto:
//+------------------------------------------------------------------+ //| Set a new width | //+------------------------------------------------------------------+ bool CProgressBar::SetWidth(const int width) { if(!CGCnvElement::SetWidth(width)) return false; this.m_progress_bar_max=this.Width()-this.BorderSizeLeft()-this.BorderSizeRight(); CBarProgressBar *bar=this.GetProgressBar(); if(bar==NULL) return false; int w=this.CalculateProgressBarWidth(); bar.SetWidth(w); return true; } //+------------------------------------------------------------------+
Aquí, primero configuraremos la nueva anchura de relleno, luego calcularemos la anchura máxima de la barra de progreso en píxeles.
A continuación, obtendremos la anchura relativa en tanto por ciento para la barra de progreso y la configuraremos en el objeto CBarProgressBar, que es la barra de progreso.
Todos los efectos visuales que aparecerán en la barra de progreso deberán procesarse en el temporizador del objeto BarProgressBar. El resaltado deberá aparecer a lo largo de la barra de progreso después de una pausa. Y así constantemente. Pausa - destello - pausa - destello, etc. Para implementar este comportamiento, necesitaremos establecer algunos valores para las propiedades que ya tienen los objetos. Estas propiedades las establecimos para implementar la aparición/desaparición suave de pistas en artículos anteriores. Para un objeto de destello, estas propiedades también resultarán adecuadas.
Ahora, incluiremos en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\BarProgressBar.mqh de la clase de objeto de barra de progreso el archivo de objeto de destello, y en su sección privada, escribiremos los métodos para establecer/retornar el retraso y declararemos el método de inicialización de las propiedades de los objetos:
//+------------------------------------------------------------------+ //| BarProgressBar.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\WinFormBase.mqh" #include "..\GlareObj.mqh" //+------------------------------------------------------------------+ //| BarProgressBar object class of the ProgressBar control | //+------------------------------------------------------------------+ class CBarProgressBar : public CWinFormBase { private: //--- (1) Set and (2) return a pause before displaying the effect void SetShowDelay(const long delay) { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,delay); } ulong ShowDelay(void) { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY); } //--- Initialize the properties void Initialize(void); protected:
En ambos constructores de las clases, en lugar de las propias líneas para establecer las propiedades, escribiremos una llamada al método para establecerlas:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CBarProgressBar::CBarProgressBar(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CWinFormBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.Initialize(); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CBarProgressBar::CBarProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.Initialize(); } //+------------------------------------------------------------------+
Luego transmitiremos las líneas de inicialización de las propiedades eliminadas de los constructores al método de inicialización:
//+------------------------------------------------------------------+ //| Initialize the properties | //+------------------------------------------------------------------+ void CBarProgressBar::Initialize(void) { this.SetPaddingAll(0); this.SetMarginAll(0); this.SetBorderSizeAll(0); this.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true); this.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true); this.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true); this.SetShowDelay(2000); } //+------------------------------------------------------------------+
Además de las líneas trasladadas, aquí hemos añadido la posibilidad de ajustar un retraso de dos segundos entre la aparición de los destellos en la barra de progreso.
Antes, en el temporizador de los objetos, simplemente enviábamos el valor de la función GetTickCount() al gráfico en un comentario.
Ahora escribiremos un manejador completo.
Manejador de eventos del temporizador:
//+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CBarProgressBar::OnTimer(void) { CWinFormBase *base=this.GetBase(); if(base==NULL) return; CWinFormBase *glare=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ,0); if(glare==NULL) return; //--- If the object is in the normal state (hidden) if(glare.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_NORMAL) { //--- set the state of waiting for fading in to the object (in our case, waiting for a shift along the progress bar), //--- set the waiting duration and set the countdown time glare.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN); this.m_pause.SetWaitingMSC(this.ShowDelay()); this.m_pause.SetTimeBegin(); //--- If the right edge of the glare object is to the right of the left edge of the progress bar object if(glare.RightEdge()>=this.CoordX()) { //--- Hide the glare object and move it beyond the right edge of the progress bar glare.Hide(); if(glare.Move(this.CoordX()-glare.Width(),this.CoordY())) { //--- Set the relative coordinates of the glare object glare.SetCoordXRelative(glare.CoordX()-this.CoordX()); glare.SetCoordYRelative(glare.CoordY()-this.CoordY()); //--- and its visibility scope equal to the entire object glare.SetVisibleArea(0,0,glare.Width(),glare.Height()); } } return; } //--- If the object is in the state of waiting for fading in (in our case, waiting for a shift along the progress bar) if(glare.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN) { //--- If the waiting time has not yet passed, leave if(this.m_pause.Passed()<this.ShowDelay()) return; //--- Set the state of the object being in the process of shifting along the progress bar and //--- the process start countdown time glare.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN); this.m_pause.SetTimeBegin(); return; } //--- If the object is in the state of a shift along the progress bar if(glare.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN) { //--- If the glare object X coordinate still has not gone beyond the right edge of the progress bar if(glare.CoordX()<=this.RightEdge()) { //--- set the display flag and show the object if(!glare.Displayed()) glare.SetDisplayed(true); if(!glare.IsVisible()) glare.Show(); //--- bring the highlight object to the foreground glare.BringToTop(); //--- Shift the highlight by 16 pixels to the right if(glare.Move(glare.CoordX()+16,this.CoordY())) { //--- Set the relative coordinates of the highlight object and redraw it glare.SetCoordXRelative(glare.CoordX()-this.CoordX()); glare.SetCoordYRelative(glare.CoordY()-this.CoordY()); glare.Redraw(true); } return; } //--- Set the completion state of the shift along the progress bar glare.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN); } //--- If the object is in the state of completion of shifting along the progress bar if(glare.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN) { //--- set the object to its normal state (invisible), //--- hide the object and set its invisibility flag glare.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_NORMAL); glare.Hide(); glare.SetDisplayed(false); return; } } //+------------------------------------------------------------------+
La lógica del método se describe por entero en los comentarios del código y es idéntica al manejador de eventos del temporizador que escribimos para los objetos de pista en el artículo anterior. Esta es solo una versión más simplificada. En los objetos de pista, esperaremos a que el objeto comience a aparecer (aquí también), luego el objeto aparecerá (aquí recorrerá la barra de progreso), después esperará la desaparición y se desvanecerá (aquí no). Esa es la única diferencia. Pero para que el destello aparezca y se extienda a lo largo de la barra de progreso, usaremos los mismos valores de la enumeración ENUM_CANV_ELEMENT_DISPLAY_STATE que usamos en los objetos de pista:
//+------------------------------------------------------------------+ //| List of control display states | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_DISPLAY_STATE { CANV_ELEMENT_DISPLAY_STATE_NORMAL, // Normal CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN, // Wait for fading in CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN, // Fading in CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN, // Fading in end CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT, // Wait for fading out CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT, // Fading out CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT, // Fading out end CANV_ELEMENT_DISPLAY_STATE_COMPLETED, // Complete processing }; //+------------------------------------------------------------------+
Es decir, las constantes de esta enumeración serán lo suficientemente universales como para procesar diferentes eventos en objetos distintos. Si fuera necesario, podremos añadir constantes a esta lista para describir algunos otros eventos que puedan darse posteriormente.
Ya estamos preparados para las pruebas.
Simulación
Para la prueba, tomaremos el asesor experto del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part127\ con el nuevo nombre TestDoEasy127.mq5.
Previamente hemos creado un objeto ProgressBar estático en el segundo panel del control SplitContainer ubicado en la primera pestaña de TabControl. Ahora necesitaremos obtener el puntero al objeto ProgressBar; a continuación, en un ciclo, aumentaremos su tamaño para ver cómo cambia esto el tamaño relativo de la barra de progreso, establecido en el 50% de la anchura de ProgressBar. Luego, nuevamente, en un ciclo, aumentaremos el valor de la barra de progreso usando el método PerformStep. Para que el método funcione, estableceremos los parámetros necesarios de antemano: mínimo = 0, máximo = 350, paso = 1. Una vez completados ambos ciclos, obtendremos el puntero al objeto de destello y estableceremos los parámetros de visualización para él.
Para implementar todo esto, al final del controlador OnInit(), escribiremos el siguiente bloque de código:
//--- Display and redraw all created panels for(int i=0;i<FORMS_TOTAL;i++) { //--- Get the panel object pnl=engine.GetWFPanel("WinForms Panel"+(string)i); if(pnl!=NULL) { //--- display and redraw the panel pnl.Show(); pnl.Redraw(true); //--- Get the TabControl object from the panel CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); //--- Get the SplitContainer object from the first tab of the TabControl object CSplitContainer *sc=tc.GetTabElementByType(0,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,0); //--- Get the second panel from the SplitContainer object CSplitContainerPanel *scp=sc.GetPanel(1); //--- Get the ProgressBar object from the received panel CProgressBar *pb=scp.GetElementByType(GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,0); //--- Wait for 1/10 of a second Sleep(100); //--- Get the width of the ProgressBar object int w=pb.Width(); //--- In the loop, increase the width of the ProgressBar by 180 pixels with a delay of 1/50 for(int n=0;n<180;n++) { Sleep(20); pb.Resize(w+n,pb.Height(),true); } //--- Set the values for PerformStep of the ProgressBar object pb.SetValuesForProcessing(0,350,1,0); //--- Reset ProgressBar to minimum pb.ResetProgressBar(); //--- Wait for 1/5 second Sleep(200); //--- In the loop from the minimum to the maximum value of ProgressBar for(int n=0;n<=pb.Maximum();n++) { //--- call the method for increasing the progress bar by a given step with a wait of 1/5 second pb.PerformStep(); Sleep(20); } //--- Get the glare object CGlareObj *gl=pb.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ,0); if(gl!=NULL) { //--- Set the glare type - rectangle, opacity 40, color - white gl.SetStyleRectangle(); gl.SetOpacity(40); gl.SetColor(clrWhite); } } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Luego compilaremos y ejecutaremos el asesor experto en el gráfico:
En el primer ciclo, veremos cómo aumenta la anchura del objeto ProgressBar, y después, la anchura de la barra de progreso también aumentará proporcionalmente. Al mismo tiempo, el valor de Value se establecerá inicialmente en 50 y permanecerá así, ya que la anchura de la barra de progreso se establecerá en valores relativos.
En el segundo ciclo, llamaremos cada vez al método PerformStep, que en cada paso incrementará el valor Value (relativo) en el valor del paso de incremento. Aunque hay 350 incrementos, podemos ver que la barra de progreso crecerá más de 1 píxel a la vez. Esto se debe a que todos los valores son relativos y se calcularán como un porcentaje de la anchura de ProgressBar. Y esto será simplemente normal y correcto: resulta imposible incrementar un píxel en cada paso, si la anchura de ProgressBar es de 100 píxeles y el número de pasos es 1000, entonces obtendremos 10 pasos de incremento por píxel, y se omitirán al calcular valores relativos.
Cuando se complete el ciclo de incremento de la barra de progreso, veremos un destello cada dos segundos. Para la demostración, esto resultará suficiente, pero para el aspecto normal, no. En primer lugar, el destello debería recorrer la barra de progreso en movimiento, y no la barra completada; en segundo lugar, el "efecto" ha resultado demasiado simple. Pero todo esto será corregido y mejorado gradualmente.
Los íconos de los objetos de pista ahora desaparecen con normalidad, sin resultar visibles todo el tiempo hasta el final del proceso de desvanecimiento, y desaparecer junto con el objeto solo en el último paso con la opacidad 0.
¿Qué es lo próximo?
En el próximo artículo, continuaremos trabajando en los objetos de la biblioteca WinForms y el objeto ProgressBar.
*Artículos de esta serie:
DoEasy. Elementos de control (Parte 20): El objeto WinForms SplitContainer
DoEasy. Elementos de control (Parte 21): Elemento de control SplitContainer. Separador de paneles
DoEasy. Elementos de control (Parte 22): SplitContainer. Cambiando las propiedades del objeto creado
DoEasy. Elementos de control (Parte 23): mejorando los objetos WinForms TabControl y SplitContainer
DoEasy. Elementos de control (Parte 24): El objeto auxiliar WinForms "Pista"
DoEasy. Elementos de control (Parte 25): El objeto WinForms «Tooltip»
DoEasy. Elementos de control (Parte 26): Mejoramos el objeto WinForms "ToolTip" y comenzamos a desarrollar "ProgressBar"
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/11764
- 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