![DoEasy. Elementos de control (Parte 1): Primeros pasos](https://c.mql5.com/2/49/doeasy_001_600x314.jpg)
DoEasy. Elementos de control (Parte 1): Primeros pasos
Contenido
- Concepto
- Mejorando las clases de la biblioteca
- Clase de objeto de WinForms Panel
- Simulación
- ¿Qué es lo próximo?
Concepto
Este artículo es el comienzo de una nueva serie sobre la creación de controles al estilo de Windows Forms. Obviamente, no podemos reproducir todos los elementos de la lista de controles de MS Visual Studio. Sin embargo, implementaremos los más populares para construir la GUI para programas MQL5.
La razón por la que pasamos a un nuevo tema sin terminar los anteriores es la necesidad de usar dichos controles para seguir desarrollando los objetos de la biblioteca gráfica a los que dedicamos los temas anteriores. Ya se está haciendo difícil manejar las cosas sin controles. Así que crearemos todos los controles posibles al estilo Windows Forms y luego regresaremos a los temas anteriores, pero con las herramientas necesarias para continuar su desarrollo.
Si abrimos el panel de elementos en MS Visual Studio, veremos una lista de grupos de controles:
- Todos los formularios de Windows Forms: aquí encontraremos todos los formularios disponibles para crear en nuestro proyecto
- Controles estándar
- Contenedores
- Menú y paneles de herramientas
- Datos
- Componentes
- Impresión
- Ventanas de diálogo
Estos no son todos los grupos disponibles en la lista del panel de elementos de MS Visual Studio. Cada uno de estos grupos contiene un gran conjunto de elementos. No vamos a implementar cada una de ellos para la biblioteca. Eso sí, los más esenciales, obviamente, los crearemos nosotros.
Comenzaremos a crear los controles por el elemento Panel, porque este elemento se usará para construir los elementos de ventana. El panel es un contenedor para colocar otros controles dentro de él, y el propio panel con los elementos colocados en él puede colocarse a su vez en el panel padre. Ese panel puede ser un objeto dentro de otro panel, y así sucesivamente.
Ya hemos creado la clase de objeto gráfico canvas, la clase padre para todos los demás objetos gráficos, basada en la clase CCanvas. El objeto de clase de formulario se construye usando como base un elemento gráfico. El objeto formulario ya dispone de un conjunto de funciones para controlar y mover este. El objeto panel se creará a partir del objeto formulario, al que se le añadiremos nuevas propiedades para implementar su funcionalidad.
El panel tendrá la capacidad de almacenar cualquier control que vaya a crear dentro de la sección actual de la descripción de desarrollo de la biblioteca. A su vez, podremos usar dicho panel para implementar las ventanas principales y de diálogo de un programa que se ejecute en el terminal.
Pero antes de empezar a crear la clase de panel, mejoraremos las clases de objeto de la biblioteca ya creadas (pues seguimos trabajando en los temas anteriores) y, paso a paso, perfeccionaremos los objetos de la biblioteca existentes y arreglaremos los errores que hayamos detectado al trabajar con ellos. Ya a lo largo del desarrollo de este tema, iremos corrigiendo los errores en las clases que ya hemos creado y perfeccionando estas, e incluso trabajando con los objetos de control que crearemos en esta nueva sección.
Mejorando las clases de la biblioteca
En la última actualización de la versión beta del terminal, (3245) se añadieron nuevas propiedades para el símbolo y la cuenta:
- MQL5: Añadido el valor SYMBOL_SUBSCRIPTION_DELAY a la enumeración ENUM_SYMBOL_INFO_INTEGER, que indica la magnitud del retraso en las cotizaciones transmitidas según el símbolo.
Se usa solo para las herramientas de suscripción, normalmente al emitir los datos en modo de prueba gratuito.
La propiedad solo puede solicitarse para los símbolos seleccionados en la Observación del Mercado. De lo contrario, obtendremos el error ERR_MARKET_NOT_SELECTED (4302).
- MQL5: Añadida la propiedad ACCOUNT_HEDGE_ALLOWED a la enumeración ENUM_ACCOUNT_INFO_INTEGER, que indica el permiso para abrir posiciones opuestas y órdenes pendientes. Se usa solo para las cuentas de cobertura, permitiendo implementar los exigencias de algunos reguladores cuando en la cuenta está prohibido tener posiciones opuestas, pero se permite tener múltiples posiciones del mismo símbolo en la misma dirección.
Si esta opción está desactivada, no se permitirá que las cuentas tengan posiciones y órdenes dirigidas de forma opuesta para el mismo instrumento y al mismo tiempo. Por ejemplo, si hay una posición de compra en la cuenta, el usuario no podrá abrir una posición de venta o colocar una orden de venta pendiente. El usuario obtendrá el error TRADE_RETCODE_HEDGE_PROHIBITED si intenta hacerlo.
Vamos a introducir también estas propiedades en los objetos del símbolo y la cuenta de la biblioteca.
En el archivo \MQL5\Include\DoEasy\Data.mqh escribimos los índices de los nuevos mensajes:
//+------------------------------------------------------------------+ //| List of the library's text message indices | //+------------------------------------------------------------------+ enum ENUM_MESSAGES_LIB { MSG_LIB_PARAMS_LIST_BEG=ERR_USER_ERROR_FIRST, // Beginning of the parameter list MSG_LIB_PARAMS_LIST_END, // End of the parameter list MSG_LIB_PROP_NOT_SUPPORTED, // Property not supported MSG_LIB_PROP_NOT_SUPPORTED_MQL4, // Property not supported in MQL4 MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_2155, // Property not supported in MetaTrader 5 versions lower than 2155 MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245, // Property not supported in MetaTrader 5 versions lower than 3245 MSG_LIB_PROP_NOT_SUPPORTED_POSITION, // Property not supported for position
...
MSG_SYM_PROP_BACKGROUND_COLOR, // Background color of the symbol in Market Watch MSG_SYM_PROP_SUBSCRIPTION_DELAY, // Delay for quotes passed by symbol for instruments working on subscription basis //---
...
MSG_ACC_PROP_FIFO_CLOSE, // Flag of a position closure by FIFO rule only MSG_ACC_PROP_HEDGE_ALLOWED, // Permission to open opposite positions and set pending orders //--- MSG_ACC_PROP_BALANCE, // Account balance
...
MSG_GRAPH_ELEMENT_TYPE_FORM, // Form MSG_GRAPH_ELEMENT_TYPE_WINDOW, // Window MSG_GRAPH_ELEMENT_TYPE_PANEL, // Panel control MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Graphical object does not belong to a program //---
y los textos de los mensajes que se corresponden con los índices nuevamente añadidos:
{"Свойство не поддерживается в MetaTrader5 версии ниже 2155","The property is not supported in MetaTrader5, build lower than 2155"}, {"Свойство не поддерживается в MetaTrader5 версии ниже 3245","The property is not supported in MetaTrader5, build lower than 3245"}, {"Свойство не поддерживается у позиции","Property not supported for position"},
...
{"Цвет фона символа в Market Watch","Background color of the symbol in Market Watch"}, {"Размер задержки у котировок, передаваемых по символу, для инструментов, работающих по подписке","Delay size for quotes transmitted per symbol for instruments working by subscription"}, {"Максимальный Bid за день","Maximum Bid of the day"},
...
{"Тип торгового сервера","Type of trading server"}, {"Признак закрытия позиций только по правилу FIFO","Sign of closing positions only according to the FIFO rule"}, {"Разрешение на открытие встречных позиций и отложенных ордеров","Permission to open opposite positions and pending orders"}, //--- {"Баланс счета","Account balance"},
...
{"Форма","Form"}, {"Окно","Window"}, {"Элемент управления \"Panel\"","Control element \"Panel\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
Cualquier objeto de panel que creemos hoy tendrá una configuración por defecto para los mensajes de texto que se muestren en él. Estas opciones se usarán para cualquier mostrado en el panel o en sus objetos herederos, o que se adjunte a ellos, si el panel es un contenedor de estos objetos. Así, deberemos establecer los valores por defecto para el nombre de la fuente, el tamaño y el color.
Abrimos el archivo \MQL5\Include\DoEasy\Defines.mqh y escribimos las nuevas macrosustituciones para estas propiedades de texto en el panel:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel #define CLR_FORE_COLOR (C'0x2D,0x43,0x48') // Default color for texts of objects on canvas #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace //--- Graphical object parameters
En la lista de tipos de objetos de la biblioteca, añadimos el nuevo tipo:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_PANEL, // WinForms Panel object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type OBJECT_DE_TYPE_GFRAME_TEXT, // "Single text animation frame" object type OBJECT_DE_TYPE_GFRAME_QUAD, // "Single rectangular animation frame" object type OBJECT_DE_TYPE_GFRAME_GEOMETRY, // "Single geometric animation frame" object type OBJECT_DE_TYPE_GANIMATIONS, // "Animations" object type //--- Managing graphical objects
En esta sección (WinForms), iremos añadiendo más tipos de objetos a medida que los vayamos creando.
En la enumeración de las propiedades de tipo entero de la cuenta, añadimos la nueva propiedad y aumentamos el número de propiedades enteras de 11 a 12:
//+------------------------------------------------------------------+ //| Account integer properties | //+------------------------------------------------------------------+ enum ENUM_ACCOUNT_PROP_INTEGER { ... ACCOUNT_PROP_FIFO_CLOSE, // Flag of a position closure by FIFO rule only ACCOUNT_PROP_HEDGE_ALLOWED // Permission to open opposite positions and set pending orders }; #define ACCOUNT_PROP_INTEGER_TOTAL (12) // Total number of integer properties #define ACCOUNT_PROP_INTEGER_SKIP (0) // Number of integer account properties not used in sorting //+------------------------------------------------------------------+
En la lista de posibles criterios para clasificar las cuentas, añadimos esta nueva propiedad:
//+------------------------------------------------------------------+ //| Possible account sorting criteria | //+------------------------------------------------------------------+ #define FIRST_ACC_DBL_PROP (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP) #define FIRST_ACC_STR_PROP (ACCOUNT_PROP_INTEGER_TOTAL-ACCOUNT_PROP_INTEGER_SKIP+ACCOUNT_PROP_DOUBLE_TOTAL-ACCOUNT_PROP_DOUBLE_SKIP) enum ENUM_SORT_ACCOUNT_MODE { ... SORT_BY_ACCOUNT_FIFO_CLOSE, // Sort by the flag of a position closure by FIFO rule only SORT_BY_ACCOUNT_HEDGE_ALLOWED, // Sort by permission to open opposite positions and set pending orders //--- Sort by real properties SORT_BY_ACCOUNT_BALANCE = FIRST_ACC_DBL_PROP, // Sort by an account balance in the deposit currency SORT_BY_ACCOUNT_CREDIT, // Sort by credit in a deposit currency ... SORT_BY_ACCOUNT_COMPANY // Sort by a name of a company serving an account }; //+------------------------------------------------------------------+
En la enumeración de las propiedades de tipo entero, añadimos la nueva propiedad y aumentamos el número de propiedades enteras de 40 a 41:
//+------------------------------------------------------------------+ //| Symbol integer properties | //+------------------------------------------------------------------+ enum ENUM_SYMBOL_PROP_INTEGER { //--- ... SYMBOL_PROP_OPTION_MODE, // Option type (from the ENUM_SYMBOL_OPTION_MODE enumeration) SYMBOL_PROP_OPTION_RIGHT, // Option right (Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) SYMBOL_PROP_SUBSCRIPTION_DELAY, // Delay for quotes passed by symbol for instruments working on subscription basis //--- skipped property SYMBOL_PROP_BACKGROUND_COLOR // The color of the background used for the symbol in Market Watch }; #define SYMBOL_PROP_INTEGER_TOTAL (41) // Total number of integer properties #define SYMBOL_PROP_INTEGER_SKIP (1) // Number of symbol integer properties not used in sorting //+------------------------------------------------------------------+
A la lista de posibles criterios de clasificación de los símbolos, añadimos la clasificación según la nueva propiedad:
//+------------------------------------------------------------------+ //| Possible symbol sorting criteria | //+------------------------------------------------------------------+ #define FIRST_SYM_DBL_PROP (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP) #define FIRST_SYM_STR_PROP (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP+SYMBOL_PROP_DOUBLE_TOTAL-SYMBOL_PROP_DOUBLE_SKIP) enum ENUM_SORT_SYMBOLS_MODE { ... SORT_BY_SYMBOL_OPTION_MODE, // Sort by option type (from the ENUM_SYMBOL_OPTION_MODE enumeration) SORT_BY_SYMBOL_OPTION_RIGHT, // Sort by option right (Call/Put) (from the ENUM_SYMBOL_OPTION_RIGHT enumeration) SORT_BY_SYMBOL_SUBSCRIPTION_DELAY, // Sort by delay for quotes passed by symbol for instruments working on subscription basis
Asimismo, añadimos a la lista de tipos de elementos gráficos el nuevo tipo de elemento:
//+------------------------------------------------------------------+ //| 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_ELEMENT, // Element GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
Al crear cada control posterior, su tipo se añadirá a esta subsección de la enumeración (WinForms).
Si añadimos otro objeto (más grande que el panel contenedor) al objeto de panel y permitimos el cambio automático de su tamaño, tendremos dos opciones de cambio del tamaño:
- solo el aumento del tamaño del panel
- el aumento y la disminución del tamaño del panel
En el primer caso, los lados del panel que no incluyan el objeto colocado en su interior se ampliarán para que el objeto quepa en él por completo. En el segundo caso, además de aumentar el tamaño del panel como se indica en el paso 1, también se disminuirán los lados que sean más grandes que el objeto que hay dentro.
Inmediatamente después de enumerar los posibles criterios de clasificación de los objetos gráficos, escribimos una nueva enumeración en la que especificaremos los modos de redimensionamiento automático del elemento de la interfaz:
//+------------------------------------------------------------------+ //| Mode of automatic interface element resizing | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_AUTO_SIZE_MODE { CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK, // Increase and decrease }; //+------------------------------------------------------------------+
Al colocar un objeto dentro de un panel, este objeto podrá fijarse a cualquier lado de su contenedor: superior, inferior, derecho e izquierdo. Al darse esta fijación, el lado más próximo del objeto se "pegará" al lado correspondiente del objeto contenedor, y las dimensiones del objeto fijado se estirarán a los lados del contenedor perpendicularmente al lado al que está fijado el objeto. Por ejemplo, si un objeto está unido al borde superior de su contenedor, su borde superior será atraído por el borde superior del contenedor, y los lados izquierdo y derecho del objeto se estirarán hacia los lados correspondientes del contenedor. La altura del objeto no cambiará. La fijación a los otros lados del contenedor funcionará de la misma forma.
Inmediatamente después de enumerar los modos de redimensionamiento automático, escribiremos una nueva enumeración:
//+------------------------------------------------------------------+ //| Control borders bound to the container | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_DOCK_MODE { CANV_ELEMENT_DOCK_MODE_TOP, // Attaching to the top and stretching along the container width CANV_ELEMENT_DOCK_MODE_BOTTOM, // Attaching to the bottom and stretching along the container width CANV_ELEMENT_DOCK_MODE_LEFT, // Attaching to the left and stretching along the container height CANV_ELEMENT_DOCK_MODE_RIGHT, // Attaching to the right and stretching along the container height CANV_ELEMENT_DOCK_MODE_FILL, // Stretching along the entire container width and height CANV_ELEMENT_DOCK_MODE_NONE, // Attached to the specified coordinates, size does not change }; //+------------------------------------------------------------------+
En este caso, aparte de los cuatro modos de fijación de un objeto a un contenedor comentados anteriormente, habrá dos más: el rellenado, en el que las dimensiones del objeto se ajustarán a las del contenedor, y la ausencia de fijación, en la que el objeto se adjuntará solo a unas coordenadas específicas dentro de su contenedor sin cambiar sus dimensiones.
Si un control tiene funcionalidad para interactuar con el usuario, bajo ciertas condiciones, el objeto podrá no considerarse disponible para la interacción (el botón no está activo, por ejemplo). Para indicar la posibilidad de interactuar con un elemento, introducimos una nueva propiedad de elemento gráfico.
En la enumeración de propiedades de tipo entero del elemento gráfico, añadimos la nueva propiedad y aumentamos el número de propiedades enteras de 24 a 25:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { ... CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart CANV_ELEMENT_PROP_ENABLED, // Element availability flag }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (25) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
En la lista de posibles criterios de clasificación de los elementos gráficos en el lienzo, , añadimos la clasificación según la nueva propiedad:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { ... SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart SORT_BY_CANV_ELEMENT_ENABLED, // Sort by the element availability flag ... }; //+------------------------------------------------------------------+
Como ahora tenemos nuevas propiedades para el símbolo y la cuenta, necesitaremos mejorar las clases de estos objetos.
Vamos a abrir el archivo \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh e introducir las mejoras en la clase de objeto de cuenta.
Asimismo, añadiremos a la estructura de las propiedades del objeto la nueva propiedad entera:
//+------------------------------------------------------------------+ //| Account class | //+------------------------------------------------------------------+ class CAccount : public CBaseObjExt { private: struct SData { //--- Account integer properties ... bool fifo_close; // ACCOUNT_FIFO_CLOSE (The flag indicating that positions can be closed only by the FIFO rule) bool hedge_allowed; // ACCOUNT_HEDGE_ALLOWED (Permission to open opposite positions and set pending orders) ... }; SData m_struct_obj; // Account object structure uchar m_uchar_array[]; // uchar array of the account object structure //--- Object properties
En el bloque de métodos para el acceso simplificado a las propiedades de un objeto de cuenta , escribiremos un nuevo método:
//+------------------------------------------------------------------+ //| Methods of a simplified access to the account object properties | //+------------------------------------------------------------------+ //--- Return the account's integer properties ... bool FIFOClose(void) const { return (bool)this.GetProperty(ACCOUNT_PROP_FIFO_CLOSE); } bool IsHedge(void) const { return this.MarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING; } bool HedgeAllowed(void) const { return (bool)this.GetProperty(ACCOUNT_PROP_HEDGE_ALLOWED); } ...
El método simplemente retornará el valor escrito en la matriz de propiedades del objeto.
En el constructor de la clase, escribiremos este valor en la matriz de propiedades del objeto:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccount::CAccount(void) { this.m_type=OBJECT_DE_TYPE_ACCOUNT; //--- Initialize control data this.SetControlDataArraySizeLong(ACCOUNT_PROP_INTEGER_TOTAL); this.SetControlDataArraySizeDouble(ACCOUNT_PROP_DOUBLE_TOTAL); this.ResetChangesParams(); this.ResetControlsParams(); ... this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE] = (::TerminalInfoString(TERMINAL_NAME)=="MetaTrader 5" ? 5 : 4); this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif ); this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif ); ... //--- Update the base object data and search for changes CBaseObjExt::Refresh(); } //+-------------------------------------------------------------------+
Aquí, solo para MQL5: si la versión del terminal es inferior a 3245, no existirá tal propiedad, por lo que escribiremos el valor false. Si la versión del terminal es mayor o igual a 3245, obtendremos este valor de la propiedad de la nueva cuenta y lo escribiremos en la matriz de propiedades enteras del objeto. En el caso de MQL4, escribiremos siempre false, no existen estas propiedades y muchas otras.
En el método que actualiza todos los datos de la cuenta, escribimos exactamente de la misma manera el valor en la propiedad del nuevo objeto:
//+------------------------------------------------------------------+ //| Update all account data | //+------------------------------------------------------------------+ void CAccount::Refresh(void) { //--- Initialize event data this.m_is_event=false; this.m_hash_sum=0; ... this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif ); this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif ); ... CBaseObjExt::Refresh(); this.CheckEvents(); } //+------------------------------------------------------------------+
En el método que crea la estructura del objeto de cuenta, implementamos la escritura de datos en los dos campos de la estructura:
//+------------------------------------------------------------------+ //| Create the account object structure | //+------------------------------------------------------------------+ bool CAccount::ObjectToStruct(void) { //--- Save integer properties ... this.m_struct_obj.server_type=(int)this.ServerType(); this.m_struct_obj.fifo_close=this.FIFOClose(); this.m_struct_obj.hedge_allowed=this.HedgeAllowed(); ... //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
Aquí, escribimos en los campos enteros de la estructura del objeto la nueva propiedad y la propiedad de la cuenta FIFOClose, añadida en la versión 2155. De alguna forma, ese punto se nos pasó aquí...
En el método que crea un objeto de cuenta a partir de una estructura, escribimos el valor del campo de la estructura en la propiedad del objeto para la nueva propiedad:
//+------------------------------------------------------------------+ //| Create the account object from the structure | //+------------------------------------------------------------------+ void CAccount::StructToObject(void) { //--- Save integer properties ... this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = this.m_struct_obj.fifo_close; this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = this.m_struct_obj.hedge_allowed; ... } //+------------------------------------------------------------------+
En el método que retorna la descripción de la propiedad entera de la cuenta, , escribimos un bloque de código para mostrar la descripción de la nueva propiedad:
//+------------------------------------------------------------------+ //| Return the description of the account integer property | //+------------------------------------------------------------------+ string CAccount::GetPropertyDescription(ENUM_ACCOUNT_PROP_INTEGER property) { return ( ... property==ACCOUNT_PROP_FIFO_CLOSE ? CMessage::Text(MSG_ACC_PROP_FIFO_CLOSE)+": "+ (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) : property==ACCOUNT_PROP_HEDGE_ALLOWED ? CMessage::Text(MSG_ACC_PROP_HEDGE_ALLOWED)+": "+ (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) : "" ); } //+------------------------------------------------------------------+
Vamos a añadir mejoras similares al archivo del objeto de símbolo en \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh.
En la sección protegida de la clase, declaramos un método que retorna el valor de la nueva propiedad del símbolo:
protected: //--- Protected parametric constructor CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index); //--- Get and return integer properties of a selected symbol from its parameters ... long SymbolCalcMode(void) const; long SymbolSwapMode(void) const; long SymbolSubscriptionDelay(void) const; long SymbolDigitsLot(void); int SymbolDigitsBySwap(void); ... //--- Search for a symbol and return the flag indicating its presence on the server bool Exist(void) const; public:
En la sección pública, en el bloque con los métodos de acceso simplificado a las propiedades de un objeto de símbolo, escribimos un método que retorna el valor de la nueva propiedad:
//+------------------------------------------------------------------+ //| Methods for a simplified access to symbol object properties | //+------------------------------------------------------------------+ //--- Integer properties ... ENUM_SYMBOL_OPTION_MODE OptionMode(void) const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE); } ENUM_SYMBOL_OPTION_RIGHT OptionRight(void) const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT); } long SubscriptionDelay(void) const { return this.GetProperty(SYMBOL_PROP_SUBSCRIPTION_DELAY); } //--- Real properties
Aquí, simplemente retornamos con el método GetProperty() el valor escrito en la matriz de propiedades enteras del objeto de símbolo.
En el constructor paramétrico cerrado, escribimos la nueva propiedad en la matriz de propiedades enteras del objeto:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index) { //--- Save integer properties ... this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE] = this.m_book_subscribed; this.m_long_prop[SYMBOL_PROP_SUBSCRIPTION_DELAY] = this.SymbolSubscriptionDelay(); ... //--- Initializing default values of a trading object this.m_trade.Init(this.Name(),0,this.LotsMin(),5,0,0,false,this.GetCorrectTypeFilling(),this.GetCorrectTypeExpiration(),LOG_LEVEL_ERROR_MSG); } //+------------------------------------------------------------------+
Método que retorna la magnitud del retraso en las cotizaciones transmitidas según el símbolo para los instrumentos de suscripción:
//+------------------------------------------------------------------+ //| Return the delay size for quotes passed by symbol | //| in case of subscription-based symbols | //+------------------------------------------------------------------+ long CSymbol::SymbolSubscriptionDelay(void) const { return ( #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)>=3245 ? ::SymbolInfoInteger(this.m_name,SYMBOL_SUBSCRIPTION_DELAY) : 0) #else 0 #endif ); } //+------------------------------------------------------------------+
Aquí, para MQL5, si la versión del terminal es igual o superior a 3245, retornaremos el valor de la nueva propiedad del símbolo, de lo contrario, retornaremos cero.
Para MQL4, siempre retornaremos cero: no hay tal propiedad ahí.
En el método que retorna la descripción de una propiedad entera, escribimos un bloque de código que retornará la descripción de la nueva propiedad:
//+------------------------------------------------------------------+ //| Return the description of the symbol integer property | //+------------------------------------------------------------------+ string CSymbol::GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER property) { return ( ... property==SYMBOL_PROP_BACKGROUND_COLOR ? CMessage::Text(MSG_SYM_PROP_BACKGROUND_COLOR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (this.GetProperty(property)==CLR_MW_DEFAULT || this.GetProperty(property)==CLR_NONE ? ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+::ColorToString((color)this.GetProperty(property),true)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : property==SYMBOL_PROP_SUBSCRIPTION_DELAY ? CMessage::Text(MSG_SYM_PROP_SUBSCRIPTION_DELAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : #ifdef __MQL5__ (::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? ": ("+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MT5_LESS_3245)+")" : ": "+(string)this.GetProperty(property)) #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif ) : "" ); } //+------------------------------------------------------------------+
Para los esquemas de color de los elementos de GUI, en el archivo \MQL5\Include\DoEasy\GraphINI.mqh, añadimos el valor del color del texto, aumentamos el número de parámetros en el esquema de color de 4 a 5 y añadimos a las matrices con los valores de los esquemas de color el valor del color del texto:
//+------------------------------------------------------------------+ //| List of indices of color scheme parameters | //+------------------------------------------------------------------+ enum ENUM_COLOR_THEME_COLORS { COLOR_THEME_COLOR_FORM_BG, // Form background color COLOR_THEME_COLOR_FORM_FRAME, // Form frame color COLOR_THEME_COLOR_FORM_RECT_OUTER, // Form outline rectangle color COLOR_THEME_COLOR_FORM_SHADOW, // Form shadow color COLOR_THEME_COLOR_FORM_TEXT, // Form text color }; #define TOTAL_COLOR_THEME_COLORS (5) // Number of parameters in the color theme //+------------------------------------------------------------------+ //| The array containing color schemes | //+------------------------------------------------------------------+ color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]= { //--- Parameters of the "Blue steel" color scheme { C'134,160,181', // Form background color C'134,160,181', // Form frame color clrDimGray, // Form outline rectangle color clrGray, // Form shadow color C'0x3E,0x3E,0x3E', // Form text color }, //--- Parameters of the "Light cyan gray" color scheme { C'181,196,196', // Form background color C'181,196,196', // Form frame color clrGray, // Form outline rectangle color clrGray, // Form shadow color C'0x3E,0x3E,0x3E', // Form text color }, }; //+------------------------------------------------------------------+
En la lista de estilos de marcos, añadimos un campo que indique que no hay marco:
//+------------------------------------------------------------------+ //| Frame styles | //+------------------------------------------------------------------+ enum ENUM_FRAME_STYLE { FRAME_STYLE_NONE, // No frame FRAME_STYLE_SIMPLE, // Simple frame FRAME_STYLE_FLAT, // Flat frame FRAME_STYLE_BEVEL, // Embossed (convex) FRAME_STYLE_STAMP, // Embossed (concave) }; //+------------------------------------------------------------------+
Vamos a mejorar la clase del objeto básico de todos los objetos gráficos de la biblioteca en el archivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh.
Para poder especificar 0 o NULL en nuestros programas al especificar el identificador del gráfico actual en lugar de establecer el valor numérico del identificador o transmitir la función ChartID(), añadiremos la comprobación del valor transmitido al método SetChartID():
public: //--- Return the prefix name string NamePrefix(void) const { return this.m_name_prefix; } //--- Set the values of the class variables void SetObjectID(const long value) { this.m_object_id=value; } void SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong; } void SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj; } void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type; } void SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species; } void SetGroup(const int group) { this.m_group=group; } void SetName(const string name) { this.m_name=name; } void SetDigits(const int value) { this.m_digits=value; } void SetChartID(const long chart_id) { this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); } //--- Set the "Background object" flag
Aquí, comprobaremos qué valor se transmite al método, y si es 0 o NULL, asignaremos a la variable el valor del identificador del gráfico actual, en caso contrario, asignaremos el valor transmitido al método.
En el método que retorna la descripción del tipo de elemento gráfico, implementamos el retorno de la descripción del objeto Panel:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(void) { return ( this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_PANEL) : "Unknown" ); } //+------------------------------------------------------------------+
En la clase de objeto del elemento gráfico, en el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, añadimos a la estructura del objeto el nuevo campo para la propiedad de disponibilidad del elemento:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: 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 //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: struct SData { //--- Object integer properties ... int coord_act_bottom; // Bottom border of the element active area long zorder; // Priority of a graphical object for receiving the event of clicking on a chart bool enabled; // Element availability flag //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name }; SData m_struct_obj; // Object structure uchar m_uchar_array[]; // uchar array of the object structure
En el bloque con los métodos de acceso simplificado a las propiedades del objeto, añadimos los nuevos métodos para establecer y retornar el valor de disponibilidad del elemento:
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ ... //--- Set (1) object movability, (2) activity, (3) interaction, //--- (4) element ID, (5) element index in the list, (6) availability and (7) shadow flag void SetMovable(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag); } void SetActive(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag); } void SetInteraction(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag); } void SetID(const int id) { this.SetProperty(CANV_ELEMENT_PROP_ID,id); } void SetNumber(const int number) { this.SetProperty(CANV_ELEMENT_PROP_NUM,number); } void SetEnabled(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag); } void SetShadow(const bool flag) { this.m_shadow=flag; } ... //--- Return the (1) element movability, (2) activity, (3) interaction and (4) availability flag bool Movable(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); } bool Active(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); } bool Interaction(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); } bool Enabled(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); } //--- Return (1) the object name, (2) the graphical resource name, (3) the chart ID and (4) the chart subwindow index
En uno de los tres métodos para limpiar el lienzo, eliminamos los valores por defecto de las banderas:
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely void Erase(const bool redraw=false); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); }
el método antes era así:
void Erase(color &colors[],const uchar opacity,const bool vgradient=true,const bool cycle=false,const bool redraw=false);
y no se podía usar porque el compilador no era capaz de seleccionar el método sobrecargado correcto.
En el constructor paramétrico , vamos a escribir la comprobación del valor del ID del gráfico transmitido y a establecer la bandera de disponibilidad del elemento y los valores por defecto de la fuente:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=colour; this.m_opacity=opacity; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) { ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border ... } //+------------------------------------------------------------------+
En el constructor protegido, hacemos lo mismo:
//+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=CLR_CANV_NULL; this.m_opacity=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false)) { ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,false); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,false); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,true); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border ... } //+------------------------------------------------------------------+
En el método que crea la estructura del objeto, añadimos el rellenado del nuevo campo de la estructura con la nueva bandera de disponibilidad del elemento:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties ... this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); // Flag of interaction with the outside environment this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); // Element availability flag this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area ... return true; } //+------------------------------------------------------------------+
En el método que crea un objeto a partir de una estructura, añadimos a la propiedad de disponibilidad del objeto la escritura del valor desde el campo de estructura apropiado:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties ... this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled); // Element availability flag this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity this.m_zorder=this.m_struct_obj.zorder; // Priority of a graphical object for receiving the on-chart mouse click event ... //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
En el método que crea un objeto de elemento gráfico, añadimos también la verificación del valor de ID del gráfico que se transmite:
//+------------------------------------------------------------------+ //| Create the graphical element object | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Chart ID const int wnd_num, // Chart subwindow const string name, // Element name const int x, // X coordinate const int y, // Y coordinate const int w, // Width const int h, // Height const color colour, // Background color const uchar opacity, // Opacity const bool redraw=false) // Flag indicating the need to redraw { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num); return true; } CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //+------------------------------------------------------------------+
Vamos a mejorar la clase del objeto formulario en el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Trasladaremos el método privado para inicializar las variables
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor //--- Initialize the variables void Initialize(void); //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void);
a la sección protegida de la clase, ya que este método será necesario en los objetos herederos, y declaramos un nuevo método para desinicializar el objeto de la clase:
//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Create a new graphical object CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, 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 a shadow object void CreateShadowObj(const color colour,const uchar opacity); protected: //--- Initialize the variables void Initialize(void); void Deinitialize(void); public: //--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor
A continuación, renombramos el método GetList() (encargado de retornar la lista de objetos adjuntos) como GetListElements(), que conviene más por su sentido y cometido:
//--- Return (1) the list of attached objects and (2) the shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetListElements(void) { return &this.m_list_elements; } CGCnvElement *GetShadowObj(void) { return this.m_shadow_obj; } //--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames
En la sección pública de la clase, declaramos el método que añade un nuevo elemento adjunto a la lista de elementos de formulario adjuntos:
//--- Create a new attached element bool CreateNewElement(const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y); //--- Draw an object shadow void DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4);
Desde el destructor de la clase, trasladamos el bloque de código que borra todos los objetos de clase dinámica utilizados
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CForm::~CForm() { if(this.m_shadow_obj!=NULL) delete this.m_shadow_obj; if(this.m_animations!=NULL) delete this.m_animations; } //+------------------------------------------------------------------+
al método de desinicialización:
//+------------------------------------------------------------------+ //| Deinitialize the variables | //+------------------------------------------------------------------+ void CForm::Deinitialize(void) { if(this.m_shadow_obj!=NULL) delete this.m_shadow_obj; if(this.m_animations!=NULL) delete this.m_animations; } //+------------------------------------------------------------------+
Llamaremos a este método en el destructor:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CForm::~CForm() { this.Deinitialize(); } //+------------------------------------------------------------------+
Esto se hará así para que podamos eliminar los objetos dinámicos innecesarios de la clase padre de las clases heredadas.
Método que añade un nuevo elemento adjunto a la lista de elementos adjuntos de un objeto:
//+------------------------------------------------------------------+ //| Add a new attached element | //+------------------------------------------------------------------+ bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y) { if(obj==NULL) return false; this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ); int index=this.m_list_elements.Search(obj); if(index>WRONG_VALUE) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj()); return false; } if(!this.m_list_elements.Add(obj)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj()); return false; } return true; } //+------------------------------------------------------------------+
Transmitimos al método el puntero al objeto que se va a añadir a la lista de objetos adjuntos.
Clasificamos la lista de elementos según el nombre del objeto especificado y buscamos ese objeto en la lista.
Si en la lista ya existe un objeto con este nombre, informamos sobre ello y retornamos false.
Si el objeto no se ha podido colocar en la lista de objetos adjuntos, informamos sobre ello y retornamos false.
Finalmente, retornamos true.
El método que crea el nuevo elemento adjunto llamará ahora al método que añade a la lista el objeto creado:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const int element_num, const string element_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,element_num,element_name,x,y,w,h,colour,opacity,movable,activity); if(obj==NULL) return false; if(!this.AddNewElement(obj,x,y)) { delete obj; return false; } return true; } //+------------------------------------------------------------------+
Antes, en este método, añadíamos el objeto recién creado a la lista, lo cual no era racional, porque también podemos añadir elementos gráficos a la lista de objetos adjuntos desde otros lugares del programa, no solo al crear un objeto.
En el método que crea un objeto de sombra, establecemos la bandera de sombra móvil en true. Esto hacía que el objeto de la sombra se pudiera mover. A nuestro juicio, esto es correcto: lo mejor será heredar el valor de esta propiedad del objeto para el que se construye el objeto sombra. Vamos a corregir esto:
//+------------------------------------------------------------------+ //| Create the shadow object | //+------------------------------------------------------------------+ void CForm::CreateShadowObj(const color colour,const uchar opacity) { //--- If the shadow flag is disabled or the shadow object already exists, exit if(!this.m_shadow || this.m_shadow_obj!=NULL) return; //--- Calculate the shadow object coordinates according to the offset from the top and left int x=this.CoordX()-OUTER_AREA_SIZE; int y=this.CoordY()-OUTER_AREA_SIZE; //--- Calculate the width and height in accordance with the top, bottom, left and right offsets int w=this.Width()+OUTER_AREA_SIZE*2; int h=this.Height()+OUTER_AREA_SIZE*2; //--- Create a new shadow object and set the pointer to it in the variable this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); return; } //--- Set the properties for the created shadow object this.m_shadow_obj.SetID(this.ID()); this.m_shadow_obj.SetNumber(-1); this.m_shadow_obj.SetOpacityShadow(opacity); this.m_shadow_obj.SetColorShadow(colour); this.m_shadow_obj.SetMovable(this.Movable()); this.m_shadow_obj.SetActive(false); this.m_shadow_obj.SetVisible(false,false); //--- Move the form object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
Ya hemos completado todas las etapas preparatorias.
Clase de objeto WinForms Panel
El objeto de panel será heredero de la clase de objeto de formulario. Es decir, tendrá toda la funcionalidad y las propiedades del formulario, y, en este caso, al implementarlo, le añadiremos tanto nuevas propiedades como nuevas funcionalidades. El panel será capaz de admitir otros objetos dentro de sí, podrá cambiar de tamaño para ajustarse al contenido interno y habilitar el desplazamiento automático si el contenido interno se extiende más allá del panel.
Hoy crearemos un objeto panel en blanco, definiremos todas sus propiedades y crearemos los métodos necesarios para establecerlas y retornarlas. En artículos posteriores, añadiremos gradualmente la funcionalidad completa del objeto de panel. No obstante, hoy solo podremos crear un objeto de panel usando su constructor.
Para todos los controles de WinForms, definiremos un nuevo directorio en la biblioteca.
Vamos a crear una nueva carpeta \MQL5\Include\DoEasy\Objects\Graph\WForms\ y a crear en ella las subcarpetas según los nombres de los grupos de controles de MS Visual Studio en el número que hemos definido al principio del artículo:
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Components\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Data\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Dialogs\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Menu & Toolbars\
- \MQL5\Include\DoEasy\Objects\Graph\WForms\Printing
Como el panel es un contenedor para otros objetos, el archivo de clase de este objeto se ubicará en la carpeta correspondiente \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\.
En la carpeta especificada, creamos un nuevo archivo Panel.mqh de la clase CPanel heredada de la clase CForm, cuyo archivo vamos a conectar:
//+------------------------------------------------------------------+ //| Panel.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 "..\..\Form.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { }
En la sección privada de la clase, declaramos todas las variables y matrices necesarias:
//+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { private: color m_fore_color; // Default text color for all panel objects ENUM_FRAME_STYLE m_border_style; // Panel frame style bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding element borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls public:
Para explicar lo que son Margin, Padding y AutoSize, mostraremos un ejemplo de la guía ayuda de MS Windows Forms .NET Framework 4.X:
... las tres propiedades más importantes son: Margin, Padding y AutoSize, disponibles en todos los controles de Windows Forms.
La propiedad Margin define el campo alrededor del control, garantizando una cierta distancia entre los límites de este elemento y los otros elementos.
La propiedad Padding define un campo dentro del control que ofrece una distancia definida entre el contenido del control (por ejemplo, el valor de la propiedad Text) y sus límites.
La propiedad AutoSize indica al control que debe cambiar de tamaño automáticamente según su contenido. El tamaño no será inferior al valor de la propiedad Size original, y tendrá en cuenta el valor de su propiedad Padding.
En la sección pública de la clase, escribimos los métodos para establecer y retornar los valores de todas las variables de clase declaradas:
public: //--- (1) Set and (2) return the default text color of all panel objects void ForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the frame style void BorderStyle(const ENUM_FRAME_STYLE style) { this.m_border_style=style; } ENUM_FRAME_STYLE BorderStyle(void) const { return this.m_border_style; } //--- (1) Set and (2) return the auto scrollbar flag void AutoScroll(const bool flag) { this.m_autoscroll=flag; } bool AutoScroll(void) { return this.m_autoscroll; } //--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling void AutoScrollMarginWidth(const int value) { this.m_autoscroll_margin[0]=value; } void AutoScrollMarginHeight(const int value) { this.m_autoscroll_margin[1]=value; } void AutoScrollMarginAll(const int value) { this.AutoScrollMarginWidth(value); this.AutoScrollMarginHeight(value); } //--- Return the (1) field width and (2) height around the control during auto scrolling int AutoScrollMarginWidth(void) const { return this.m_autoscroll_margin[0]; } int AutoScrollMarginHeight(void) const { return this.m_autoscroll_margin[1]; } //--- (1) Set and (2) return the flag of the element auto resizing depending on the content void AutoSize(const bool flag) { this.m_autosize=flag; } bool AutoSize(void) { return this.m_autosize; } //--- (1) Set and (2) return the mode of the element auto resizing depending on the content void AutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode) { this.m_autosize_mode=mode; } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return this.m_autosize_mode; } //--- (1) Set and (2) return the mode of binding element borders to the container void DockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode){ this.m_dock_mode=mode; } ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void) const { return this.m_dock_mode; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control void MarginLeft(const int value) { this.m_margin[0]=value; } void MarginTop(const int value) { this.m_margin[1]=value; } void MarginRight(const int value) { this.m_margin[2]=value; } void MarginBottom(const int value) { this.m_margin[3]=value; } void MarginAll(const int value) { this.MarginLeft(value); this.MarginTop(value); this.MarginRight(value); this.MarginBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control int MarginLeft(void) const { return this.m_margin[0]; } int MarginTop(void) const { return this.m_margin[1]; } int MarginRight(void) const { return this.m_margin[2]; } int MarginBottom(void) const { return this.m_margin[3]; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control void PaddingLeft(const int value) { this.m_padding[0]=value; } void PaddingTop(const int value) { this.m_padding[1]=value; } void PaddingRight(const int value) { this.m_padding[2]=value; } void PaddingBottom(const int value) { this.m_padding[3]=value; } void PaddingAll(const int value) { this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } //--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+
Para algunos de ellos, es posible establecer simultáneamente cada propiedad correspondiente a cada lado del objeto.
Por ejemplo, para el valor Margin en MS Visual Studio, es posible establecer cada propiedad aparte o las cuatro propiedades simultáneamente:
Tenemos cuatro constructores de clase: especificando (1) el ID del gráfico, la subventana del gráfico, el nombre del objeto y las coordenadas con el tamaño; (2) la subventana del gráfico actual, el nombre del objeto y las coordenadas con el tamaño; (3) el nombre del objeto y las coordenadas con el tamaño, y (4) el nombre del objeto con coordenadas y tamaño cero:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CPanel::CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CPanel::CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CPanel::CPanel(const string name, const int x, const int y, const int w, const int h) : CForm(::ChartID(),0,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_FORE_COLOR; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //+------------------------------------------------------------------+
En cada constructor, transmitimos en la línea de inicialización los parámetros necesarios al constructor de la clase padre.
Luego, en el cuerpo del constructor, establecemos el tipo de elemento gráfico, el tipo de objeto de la biblioteca, el color por defecto de los textos para el panel, y también establecemos un Margin para todos los lados igual a 3, un Pading igual a 0, e inicializamos las variables de la clase padre.
Esto será suficiente para crear simplemente un objeto de panel en el gráfico del terminal. Implementaremos lo demás para el objeto "Panel" en artículos posteriores.
En el destructor de la clase, llamamos al método de desinicialización de la clase padre:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CPanel::~CPanel() { CForm::Deinitialize(); } //+------------------------------------------------------------------+
Vamos a mejorar la clase de colección de objetos gráficos en \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.
En lugar del archivo de objetos de formulario, conectamos un archivo de objetos de panel:
//+------------------------------------------------------------------+ //| GraphElementsCollection.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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Graph\WForms\Containers\Panel.mqh" #include "..\Objects\Graph\Standard\GStdVLineObj.mqh"
Como el objeto de panel es heredero del objeto de formulario, todos los objetos de su jerarquía padre serán visibles en la clase de colección.
En la sección pública de la clase, escribimos dos métodos que retornan una lista de elementos gráficos según el ID del gráfico y el ID del objeto, y según el ID del gráfico y el nombre del objeto:
//--- Return the list of graphical objects by chart ID and group CArrayObj *GetListStdGraphObjByGroup(const long chart_id,const int group) { CArrayObj *list=GetList(GRAPH_OBJ_PROP_CHART_ID,0,chart_id,EQUAL); return CSelect::ByGraphicStdObjectProperty(list,GRAPH_OBJ_PROP_GROUP,0,group,EQUAL); } //--- Return the list of graphical elements by chart and object IDs CArrayObj *GetListCanvElementByID(const long chart_id,const int element_id) { CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);; } //--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,name,EQUAL);; } //--- Constructor
Ya hemos analizado muchas veces la lógica de estos métodos. Aquí simplemente filtramos la lista según los parámetros requeridos y retornamos la lista resultante, que deberá contener un puntero al objeto encontrado en la lista de colección.
Si no se encuentra ningún objeto, los métodos retornarán NULL.
Al final del cuerpo de la clase, escribimos los métodos para crearlos objetos de elementos gráficos, los formularios y los paneles:
//--- Create a graphical element object on canvas on a specified chart and subwindow int CreateElement(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling int CreateElementVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,true,false,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the horizontal gradient filling int CreateElementHGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,false,false,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the cyclic vertical gradient filling int CreateElementVGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,true,true,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling int CreateElementHGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr[0],opacity,movable,activity,redraw); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.Erase(clr,opacity,false,true,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow int CreateForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling int CreateFormVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,true,false,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the horizontal gradient filling int CreateFormHGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,false,false,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the cyclic vertical gradient filling int CreateFormVGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,true,true,redraw); return obj.ID(); } //--- Create a graphical object form object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling int CreateFormHGradientCicle(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,false,true,redraw); return obj.ID(); } //--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow int CreatePanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h); if(!this.AddCanvElmToCollection(obj)) { delete obj; return WRONG_VALUE; } obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+
Los métodos para crear los elementos y formularios son casi idénticos. La única diferencia es la forma de rellenar el fondo a color. O bien es un color constante o un rellenado de gradiente. Se usan varios tipos de rellenado de gradiente: vertical, horizontal y cíclico vertical y horizontal. En cuanto creamos un objeto, añadimos este a la lista de colección de elementos gráficos y establecemos para él las propiedades mínimas necesarias que se transmitirán al método al ser llamado.
En el método que resetea para todos los formularios las banderas de interacción salvo la especificada, cambiamos el tipo de objeto a elemento, ya que los elementos gráficos son el objeto mínimo para construir los elementos de GUI:
//+---------------------------------------------------------------------+ //| Reset all interaction flags for all forms except the specified one | //+---------------------------------------------------------------------+ void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept) { //--- In the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i); //--- if failed to receive the pointer, or it is not a form, or it is not a form whose pointer has been passed to the method, move on if(obj==NULL || obj.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID())) continue; //--- Reset the interaction flag for the current form in the loop obj.SetInteraction(false); } } //+------------------------------------------------------------------+
Como ahora tenemos métodos para crear elementos gráficos, formularios y paneles, el método que añade un elemento gráfico a la lista de la colección en el archivo \MQL5\Include\DoEasy\Engine.mqh del objeto principal de la biblioteca CEngine ya no será necesario, así que vamos a eliminarlo:
//--- Return the list of graphical elements on canvas CArrayObj *GetListCanvElement(void) { return this.m_graph_objects.GetListCanvElm(); } //--- Add the graphical element on canvas to the collection bool GraphAddCanvElmToCollection(CGCnvElement *element) { return this.m_graph_objects.AddCanvElmToCollection(element); } //--- Fill in the array with IDs of the charts opened in the terminal void GraphGetArrayChartsID(long &array_charts_id[])
En su lugar, escribiremos dos métodos que retornarán la lista de elementos gráficos según el ID del gráfico y el ID del objeto y la lista de elementos gráficos según el ID de gráfico y el nombre del objeto:
//--- Return the list of graphical elements on canvas CArrayObj *GetListCanvElement(void) { return this.m_graph_objects.GetListCanvElm(); } //--- Return the list of graphical elements by chart and object IDs CArrayObj *GetListCanvElementByID(const long chart_id,const int element_id) { return this.m_graph_objects.GetListCanvElementByID(chart_id,element_id); } //--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { return this.m_graph_objects.GetListCanvElementByName(chart_id,name); } //--- Fill in the array with IDs of the charts opened in the terminal void GraphGetArrayChartsID(long &array_charts_id[])
Estos dos métodos simplemente retornarán el resultado de la solicitud de los métodos homónimos de la clase de colección de elementos gráficos que hemos comentado anteriormente.
Ya estamos preparados para realizar las pruebas.
Simulación
Para la simulación, tomaremos el asesor del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part101\ con el nuevo nombre TestDoEasyPart101.mq5.
¿Qué estamos poniendo a prueba? Vamos a dejar la creación de los tres objetos de formulario del artículo anterior, y a añadir tres objetos de elemento y un objeto de panel.
En cada uno de los objetos, mostraremos un texto con el nombre de ese tipo de objeto y su identificador en la colección de elementos gráficos.
En el manejador OnInit(), implementaremos la creación de todos los objetos utilizando los métodos añadidos hoy a la clase de colección:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create form objects int obj_id=WRONG_VALUE; CArrayObj *list=NULL; CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { obj_id=engine.GetGraphicObjCollection().CreateFormVGradient(ChartID(),0,"Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true,true); list=engine.GetListCanvElementByID(ChartID(),obj_id); form=list.At(0); if(form==NULL) continue; //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,"Form: ID "+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- Create four graphical elements CGCnvElement *elm=NULL; array_clr[0]=C'0x65,0xA4,0xA9'; array_clr[1]=C'0x48,0x75,0xA2'; //--- Vertical gradient obj_id=engine.GetGraphicObjCollection().CreateElementVGradient(NULL,0,"CElmVG",form.RightEdge()+50,20,200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Vertical cyclic gradient obj_id=engine.GetGraphicObjCollection().CreateElementVGradientCicle(NULL,0,"CElmVGC",form.RightEdge()+50,80, 200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal gradient obj_id=engine.GetGraphicObjCollection().CreateElementHGradient(NULL,0,"CElmHG",form.RightEdge()+50,140,200,50,array_clr,127,true,true,true); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal cyclic gradient obj_id=engine.GetGraphicObjCollection().CreateElementHGradientCicle(NULL,0,"CElmHGC",form.RightEdge()+50,200,200,50,array_clr,127,true,true,false); list=engine.GetGraphicObjCollection().GetListCanvElementByID(ChartID(),obj_id); elm=list.At(0); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,"Element: ID "+(string)elm.ID(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Create WinForms Panel object CPanel *pnl=NULL; obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+50,50,150,150,array_clr[0],200,true,true,false,true); list=engine.GetListCanvElementByID(ChartID(),obj_id); pnl=list.At(0); if(pnl!=NULL) { pnl.SetFontSize(10); pnl.TextOnBG(0,"WinForm Panel: ID "+(string)pnl.ID(),4,2,FRAME_ANCHOR_LEFT_TOP,pnl.ForeColor(),pnl.Opacity()); pnl.Update(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Los objetos de formulario se rellenarán con un gradiente vertical, mientras que los objetos de elemento se rellenarán cada uno con un tipo de gradiente distinto. El objeto de panel se rellenará a color.
Vamos a compilar el asesor y a ejecutarlo en el gráfico:
Las formularios responden al movimiento del ratón y siempre se encuentran por encima de los objetos gráficos añadidos al gráfico. El rellenado de gradiente de los objetos de elemento se dibuja correctamente, y el color del objeto de panel es único. Pero aquí, ni los elementos ni el panel responden al ratón y se encuentran en el fondo, por debajo de todos los objetos gráficos. Esto se debe a que solo hemos creado el procesamiento de los eventos del ratón para los objetos de formulario. E incluso el hecho de que un panel sea también en esencia un formulario resulta irrelevante, ya que en el manejador solo estamos procesando explícitamente la clase CForm. Todo esto se corregirá más adelante.
¿Qué es lo próximo?
En el próximo artículo, continuaremos desarrollando la clase de objeto WinForms Panel.
*Último artículo de la serie anterior:
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/10663
![DoEasy. Elementos de control (Parte 2): Continuamos trabajando con la clase CPanel](https://c.mql5.com/2/46/MQL5-avatar-doeasy-library-2__1.png)
![Gráficos en la biblioteca DoEasy (Parte 100): Solucionamos las deficiencias al trabajar con los objetos gráficos estándar extendidos](https://c.mql5.com/2/45/MQL5-avatar-doeasy-library3-2__4.png)
![DoEasy. Elementos de control (Parte 3): Creando controles vinculados](https://c.mql5.com/2/46/MQL5-avatar-doeasy-library-2__2.png)
![Aprendizaje automático y Data Science (Parte 01): Regresión lineal](https://c.mql5.com/2/48/linear_regression__1.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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