DoEasy. Elementos de control (Parte 11): Objetos WinForms: grupos, el objeto WinForms CheckedListBox
Contenido
- Concepto
- Mejorando las clases de la biblioteca
- El objeto WinForms CheckedListBox
- Simulación
- ¿Qué es lo próximo?
Concepto
Los objetos WinForms adjuntos a un contenedor se convierten en un conjunto de objetos combinados en un mismo grupo. No importa si están adjuntos a un objeto GroupBox, o simplemente a cualquier panel: este contenedor se convertirá en una entidad para los objetos, uniéndolos a todos en un solo grupo, y los objetos comenzarán a comportarse según las reglas de este grupo. Por ejemplo, el objeto RadioButton es prácticamente inútil en una única ejecución. Si se ha creado sin seleccionar, después de clicar en él, su casilla de verificación se seleccionará y no será posible desmarcarla nuevamente. Para borrar esta casilla de verificación, deberemos seleccionar otro objeto del mismo tipo. Y aquí hay una diferencia: si seleccionamos otro objeto RadioButton como parte del mismo contenedor, el primero no estará marcado y el segundo, en el que hemos clicado, estará marcado. Pero si intentamos seleccionar un RadioButton adjunto a otro contenedor, el primer objeto seleccionado en el primer grupo no se desmarcará. Y esto es natural, por eso son diferentes grupos de objetos en diferentes contenedores.
Pero, ¿qué sucederá si queremos tener dos conjuntos de objetos RadioButton en el mismo contenedor que funcionen de forma independiente? Después de todo, su contenedor los combinará en un conjunto de objetos (del cual deberán heredar el número de su grupo) y funcionarán según el número de grupo común recibido de su contenedor. Y para hacer varios conjuntos de tales objetos que funcionen independientemente en un contenedor, introduciremos el concepto de grupo de objetos.
Así, si creamos un conjunto de seis objetos RadioButton en un contenedor con el número de grupo 1, a todos se les asignará el número de grupo 1, y todos estarán vinculados entre sí por el número de grupo de su contenedor y funcionarán de la forma correspondiente; un clic sobre cualquiera de los seis objetos RadioButton anulará la selección de los cinco restantes.
Pero si a cuatro objetos del grupo 1 se les asigna el grupo 2 y a los dos objetos restantes se les asigna el grupo 3, dentro del contenedor con el grupo 1 se crearán dos subgrupos de objetos, con el número de grupo 2 y el número de grupo 3. Y, en consecuencia, cada uno de estos nuevos grupos trabajará solo de forma conjunta con los objetos de su grupo.
Esto nos permitirá crear varios subgrupos en un contenedor unidos en su propio grupo bajo su propio número, y no habrá necesidad de crear nuevos contenedores para grupos dentro del principal.
Por lo tanto, combinando los dos botones de interruptor en un grupo, haremos que estos dos botones constituyan un interruptor de dos botones: al presionar el primer botón, se liberará el segundo y viceversa. Así, podremos hacer que varios botones Toggle combinados en un grupo puedan estar en un estado o que todos los botones no estén presionados, o que solo uno de ellos esté fijo en el estado presionado, mientras que el resto esté en el estado liberado .
Bueno, vamos a modificar ligeramente la clase del objeto de cuenta, ya que en algunos servidores el nombre del terminal no se retorna como estándar. Por lo general, al solicitar el nombre del terminal, el servidor retorna la cadena "MetaTrader 5", pero puede suceder que el bróker añada algo propio allí y el nombre de la terminal se vuelve diferente. Por ello, para determinar el tipo de servidor, no comprobaremos el nombre del terminal, sino la subcadena "MetaTrader 5" en su nombre.
Mejorando las clases de la biblioteca
En el archivo \MQL5\Include\DoEasy\Defines.mqh, en la enumeración de los tipos de elementos gráficos, introducimos un nuevo tipo de objeto WinForms:
//+------------------------------------------------------------------+ //| 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 //--- '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_CHECKED_LIST_BOX, // Windows Forms CheckedListBox }; //+------------------------------------------------------------------+
En la lista de propiedades de tipo entero del elemento gráfico en el lienzo, escribimos las nuevas propiedades del elemento y cambiamos el número total de propiedades enteras de 81 a 83:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type //---... //---... CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_GROUP, // Group the graphical element belongs to CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... CANV_ELEMENT_PROP_BUTTON_TOGGLE, // Toggle flag of the control featuring a button CANV_ELEMENT_PROP_BUTTON_GROUP, // Button group flag CANV_ELEMENT_PROP_BUTTON_STATE, // Status of the Toggle control featuring a button //---... //---... CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN, // Color of control checkbox when clicking on the control CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER, // Color of control checkbox when hovering the mouse over the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (83) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
El grupo al que pertenece el gráfico será el número de grupo, mientras que la bandera de grupo de botones será un indicador que mostrará que el botón funciona como parte de varios botones de interruptor. Si tres botones forman un objeto RadioButton, entonces cada uno de ellos deberá tener la bandera de botón de grupo establecida y deberá tener el mismo grupo.
Vamos a añadir estas nuevas propiedades a la enumeración de posibles criterios de clasificación de los objetos gráficos en el lienzo:
//+------------------------------------------------------------------+ //| 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 integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_GROUP, // Sort by a group the graphical element belongs to 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_BUTTON_TOGGLE, // Sort by the Toggle flag of the control featuring a button SORT_BY_CANV_ELEMENT_BUTTON_GROUP, // Sort by button group flag SORT_BY_CANV_ELEMENT_BUTTON_STATE, // Sort by the status of the Toggle control featuring a button SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR, // Sort by color of control checkbox background SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_OPACITY, // Sort by opacity of control checkbox background color SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,// Sort by color of control checkbox background when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_MOUSE_OVER,// Sort by color of control checkbox background when hovering the mouse over the control SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR, // Sort by color of control checkbox frame SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_OPACITY, // Sort by opacity of control checkbox frame color SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_MOUSE_DOWN, // Sort by color of control checkbox frame when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_MOUSE_OVER, // Sort by color of control checkbox frame when hovering the mouse over the control SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR, // Sort by color of control checkbox SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_OPACITY, // Sort by opacity of control checkbox color SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_DOWN, // Sort by color of control checkbox when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_OVER, // Sort by color of control checkbox when hovering the mouse over the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text }; //+------------------------------------------------------------------+
Ahora podremos filtrar, clasificar y seleccionar todos los elementos gráficos según las nuevas propiedades.
En el archivo \MQL5\Include\DoEasy\Data.mqh, escribiremos los índices de los nuevos mensajes:
//--- CPanel MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ, // Failed to create the underlay object MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE, // Error. The created object should be of WinForms Base type or be derived from it //--- CCheckedListBox MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ, // Failed to create the CheckBox object MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ, // Failed to get the CheckBox object from the object list //--- Integer properties of graphical elements
...
MSG_CANV_ELEMENT_PROP_AUTOCHECK, // Auto change flag status when it is selected MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE, // Toggle flag of the control featuring a button MSG_CANV_ELEMENT_PROP_BUTTON_GROUP, // Button group flag MSG_CANV_ELEMENT_PROP_BUTTON_STATE, // Status of the Toggle control featuring a button MSG_CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR, // Color of control checkbox background
y los mensajes de texto correspondientes a los nuevos índices añadidos:
//--- CPanel {"Не удалось создать объект-подложку","Failed to create underlay object"}, {"Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником","Error. The object being created must be of type WinForms Base or be derived from it"}, //--- CCheckedListBox {"Не удалось создать объект CheckBox","Failed to create CheckBox object"}, {"Не удалось получить объект CheckBox из списка объектов","Failed to get CheckBox object from list of objects"}, //--- Integer properties of graphical elements
...
{"Автоматическое изменение состояния флажка при его выборе","Automatically change the state of the checkbox when it is selected"}, {"Флаг \"Переключатель\" элемента управления, имеющего кнопку","\"Button-toggle\" flag of a control"}, {"Флаг группы кнопки","Button group flag"}, {"Состояние элемента управления \"Переключатель\", имеющего кнопку","The \"Toggle-button\" control state"}, {"Цвет фона флажка проверки элемента управления","The background color of the control's validation checkbox"},
En el archivo de clase del objeto de cuenta \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh, en el constructor de clase, cambiamos ligeramente la definición del tipo de servidor. Como ya hemos mencionado con anterioridad, no obtendremos la cadena con el nombre del terminal, sino que buscaremos en la cadena con el nombre del terminal la subcadena "MetaTrader 5":
//+------------------------------------------------------------------+ //| 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(); //--- Save integer properties this.m_long_prop[ACCOUNT_PROP_LOGIN] = ::AccountInfoInteger(ACCOUNT_LOGIN); this.m_long_prop[ACCOUNT_PROP_TRADE_MODE] = ::AccountInfoInteger(ACCOUNT_TRADE_MODE); this.m_long_prop[ACCOUNT_PROP_LEVERAGE] = ::AccountInfoInteger(ACCOUNT_LEVERAGE); this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS] = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE] = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED] = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT] = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT); this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ; this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #else 2 #endif ; this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE] = (::StringFind(::TerminalInfoString(TERMINAL_NAME),"MetaTrader 5")>WRONG_VALUE ? 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 ); //--- Save real properties this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)] = ::AccountInfoDouble(ACCOUNT_BALANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)] = ::AccountInfoDouble(ACCOUNT_CREDIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)] = ::AccountInfoDouble(ACCOUNT_PROFIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)] = ::AccountInfoDouble(ACCOUNT_EQUITY); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)] = ::AccountInfoDouble(ACCOUNT_MARGIN); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)] = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)] = ::AccountInfoDouble(ACCOUNT_ASSETS); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)] = ::AccountInfoDouble(ACCOUNT_LIABILITIES); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED); //--- Save string properties this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)] = ::AccountInfoString(ACCOUNT_NAME); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)] = ::AccountInfoString(ACCOUNT_SERVER); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)] = ::AccountInfoString(ACCOUNT_CURRENCY); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)] = ::AccountInfoString(ACCOUNT_COMPANY); //--- Account object name, object and account type (MetaTrader 5 or 4) this.m_name=CMessage::Text(MSG_LIB_PROP_ACCOUNT)+" "+(string)this.Login()+": "+this.Name()+" ("+this.Company()+")"; this.m_type=COLLECTION_ACCOUNT_ID; this.m_type_server=(uchar)this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]; //--- Filling in the current account data for(int i=0;i<ACCOUNT_PROP_INTEGER_TOTAL;i++) this.m_long_prop_event[i][3]=this.m_long_prop[i]; for(int i=0;i<ACCOUNT_PROP_DOUBLE_TOTAL;i++) this.m_double_prop_event[i][3]=this.m_double_prop[i]; //--- Update the base object data and search for changes CBaseObjExt::Refresh(); } //+-------------------------------------------------------------------+
En la variable m_type_server , introduciremos el valor obtenido anteriormente (5 o 4). Antes, escribíamos en él el resultado de la verificación del nombre del terminal en el valor "MetaTrader 5", lo que a veces sucede de forma errónea. Ahora escribiremos en él el valor que ya hemos escrito en la propiedad de la cuenta.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh de la clase del objeto gráfico básico de la biblioteca, haremos virtuales los métodos para establecer y obtener el grupo del objeto :
//--- 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; } virtual void SetGroup(const int value) { this.m_group=value; } void SetName(const string name) { this.m_name=name; } void SetDigits(const int value) { this.m_digits=value; }
...
virtual long Zorder(void) const { return this.m_zorder; } int SubWindow(void) const { return this.m_subwindow; } int ShiftY(void) const { return this.m_shift_y; } int VisibleOnTimeframes(void) const { return this.m_timeframes_visible; } int Digits(void) const { return this.m_digits; } virtual int Group(void) const { return this.m_group; } bool IsBack(void) const { return this.m_back; } bool IsSelected(void) const { return this.m_selected; } bool IsSelectable(void) const { return this.m_selectable; } bool IsHidden(void) const { return this.m_hidden; } bool IsVisible(void) const { return this.m_visible; }
Tendremos que redefinirlos en las clases heredadas.
Los elementos gráficos en la GUI están interconectados por una jerarquía común de su ubicación unos respecto a otros. El objeto básico de una cadena de objetos relacionados será el objeto al que se adjuntarán los subordinados. A su vez, los objetos subordinados podrán tener sus propias cadenas de objetos vinculados, y el básico, a su vez, podrá ser un eslabón en una cadena de objetos vinculados a otro. En este caso, se considerará que el objeto principal es el que tiene objetos subordinados en su composición, pero no está vinculado a ninguno más: sería, digamos, el «antepasado» de toda la jerarquía de conexiones de todos los objetos subordinados. Por lo general, dicho objeto es una ventana en Windows, un formulario en C#, y aquí también será una "Ventana", ya que la definición de "Formulario" ya está tomada por el elemento gráfico que implementa la funcionalidad para trabajar con el ratón, y este objeto es el padre del objeto WinForms básico.
Para poder obtener los identificadores de los objetos básico y principal, en el archivo MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh de la clase de elemento gráfico, escribiremos los métodos que los retornan:
//--- Return the flag indicating that the object is (1) main, (2) base bool IsMain(void) { return this.GetMain()==NULL; } bool IsBase(void) { return this.GetBase()==NULL; } //--- Get the (1) main and (2) base object ID int GetMainID(void) { if(this.IsMain()) return this.ID(); CGCnvElement *main=this.GetMain(); return(main!=NULL ? main.ID() : WRONG_VALUE); } int GetBaseID(void) { if(this.IsBase()) return this.ID(); CGCnvElement *base=this.GetBase(); return(base!=NULL ? base.ID() : WRONG_VALUE); } //--- Return the pointer to a canvas object
Usando como ejemplo la obtención del identificador de un objeto básico, si el objeto ya es un objeto básico (tiene objetos subordinados en su composición), retornaremos su identificador. De lo contrario, obtendremos el puntero al objeto básico (estos punteros se escriben en cada objeto subordinado), y si el puntero ha sido obtenido, retornaremos el identificador del objeto básico, en caso contrario, retornaremos -1 (error: el objeto no pudo ser obtenido).
Para obtener el identificador del objeto principal, la lógica del método es idéntica.
Escribimos los métodos virtuales para obtener y configurar el grupo de un elemento gráfico:
//--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value); return true; } //--- Graphical object group virtual int Group(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP); } virtual void SetGroup(const int value) { CGBaseObj::SetGroup(value); this.SetProperty(CANV_ELEMENT_PROP_GROUP,value); } //+------------------------------------------------------------------+
El método Group() retorna el valor escrito en la propiedad "group" del objeto.
En el método para establecer la propiedad "grupo", primero se establece el valor transmitido al método en el objeto principal y luego se escribe en la propiedad del elemento gráfico.
Cada elemento gráfico de la colección deberá tener su propio identificador único. Esto nos permitirá referirnos directamente al objeto según su identificador (si está almacenado en el programa), y no tener que buscarlo en ciclos entre todos los objetos. Actualmente, los identificadores únicos se asignan solo a aquellos elementos gráficos creados directamente desde el programa. Si creamos (también programáticamente) nuevos objetos vinculados a partir de los ya creados (que están vinculados al objeto desde el que se crea el nuevo), al objeto subordinado recién creado se le asignará el identificador del objeto desde el que se creó. En esta situación, podremos obtener este objeto según el identificador del objeto básico, indicando el número del objeto en la lista de objetos subordinados.
Obviamente, este enfoque también funcionará, pero para simplificar el trabajo con programas creados sobre la base de la biblioteca, buscaremos un identificador único y lo asignaremos al elemento gráfico subordinado recién creado. Por lo tanto, a cada elemento gráfico se le asignará un identificador único que usaremos para acceder a él, y en segundo lugar, seguiremos teniendo el método implementado en estos momentos, es decir: consultamos el objeto básico y obtenemos el elemento requerido de la lista según número de elemento. Tener dos formas de obtener los punteros a los objetos es definitivamente mejor que una, sobre todo porque ahora habrá una forma más rápida de acceder a un elemento según su identificador único.
En el archivo de clase del objeto de formulario \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, en la sección pública, declararemos cuatro métodos para encontrar el valor máximo de una propiedad de objeto y el identificador:
//--- Return (1) itself, the list of (2) attached objects, (3) the list of interaction objects and (4) shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetListElements(void) { return &this.m_list_elements; } CArrayObj *GetListInteractObj(void) { return &this.m_list_interact; } CShadowObj *GetShadowObj(void) { return this.m_shadow_obj; } //--- Return the maximum value (1) of the specified integer property and (2) ID from all objects bound to the base one long GetMaxLongPropForm(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); int GetMaxIDForm(void); //--- Return the maximum value (1) of the specified integer property and (2) ID from the entire hierarchy of related objects long GetMaxLongPropAll(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); int GetMaxIDAll(void); //--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames
Los métodos con parámetros retornarán el valor máximo encontrado de la propiedad indicada, mientras que los métodos sin parámetros devolverán el valor máximo encontrado del identificador del elemento gráfico.
En el método encargado de crear un nuevo elemento vinculado y añadirlo a la lista de objetos vinculados CreateAndAddNewElement(), cambiaremos el establecimiento del identificador del objeto recién creado.
Antes, aquí se establecía el identificador del objeto a partir del cual se creaba uno nuevo:
obj.SetID(this.ID());
ahora escribiremos el valor del identificador como el máximo valor de identificador encontrado en toda la jerarquía de objetos subordinados, partiendo desde el principal, y sumaremos 1 al valor resultante. Es decir, el nuevo objeto tendrá el mayor valor de todos los identificadores de todos los objetos de la colección:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- ... //--- ... //--- ... //--- Set the minimum properties for a bound graphical element obj.SetBackgroundColor(colour,true); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); obj.SetID(this.GetMaxIDAll()+1); obj.SetNumber(num); obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(obj.CoordXRelative()); obj.SetCoordYRelativeInit(obj.CoordYRelative()); return obj; } //+------------------------------------------------------------------+
En cuanto el cursor se aleja del área del elemento gráfico, el controlador de este evento se activará para restaurar los colores del fondo, el texto y el marco del objeto a sus valores predeterminados, porque estos colores cambian cuando el cursor se desplaza sobre el área del elemento para mostrar visualmente la actividad del elemento gráfico. Al realizar varias pruebas, noté que los colores no siempre se restauran correctamente. En ocasiones, resulta necesario volver a colocar el cursor en el formulario donde se encuentran estos objetos para que su color se restablezca al original. Gradualmente, a medida que desarrollemos el componente visual de los objetos de la biblioteca, solucionaremos estos detalles. Bien, ahora añadiremos una condición más al manejador del último evento del ratón, que procesará la retirada del cursor del elemento gráfico:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(true); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Ahora procesaremos no solo la situación en la que el cursor se encontraba en el área activa del objeto, sino también la situación cuando el cursor estaba en el área del objeto, ya que el área activa no siempre tiene las dimensiones del el área completa del elemento, y el cursor, antes de ir más allá del formulario desde la zona activa, entra en la inactiva, pero dentro del elemento gráfico. Ahora también se tendrá en cuenta esta situación.
Método que retorna el valor máximo de la propiedad entera especificada de todos los objetos subordinados al objeto básico:
//+------------------------------------------------------------------+ //| Return the maximum value of the specified integer | //| property from all objects subordinate to the base one | //+------------------------------------------------------------------+ long CForm::GetMaxLongPropForm(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Get the pointer to the base object CForm *base=this.GetBase(); //--- If the base object is received, then set the property value of the specified property, otherwise the value is equal to -1 long property=(base!=NULL ? base.GetProperty(prop) : WRONG_VALUE); //--- If the received value is greater than -1 if(property>WRONG_VALUE) { //--- In the loop through the list of bound objects for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object CForm *elm=this.GetElement(i); if(elm==NULL) continue; //--- If the property value of the received object is greater than the value set in the property, //--- set the property value of the current object to 'property' if(elm.GetProperty(prop)>property) property=elm.GetProperty(prop); //--- Get the maximum property value from objects bound to the current one long prop_form=elm.GetMaxLongPropForm(prop); //--- If the received value is greater than the 'property' value //--- set the received value to 'property' if(prop_form>property) property=prop_form; } } //--- Return the found maximum property value return property; } //+------------------------------------------------------------------+
La lógica del método se aclara en los comentarios del código. Este método busca el valor máximo de la propiedad entera indicada que tenga valores a partir de cero y mayores (no negativos) de todos los objetos vinculados al formulario. En este caso, se tendrá en cuenta el valor de la propiedad del objeto básico y la búsqueda comenzará a partir de él. Como todos los objetos subordinados se crean uno tras otro partiendo del básico, no podrá darse la situación en la que omitamos el valor máximo de la propiedad de aquellos objetos que están directamente adjuntos al básico y comencemos la búsqueda desde un objeto ubicado lejos en la jerarquía respecto al básico.
No obstante, si alguna vez necesitamos buscar precisamente a través de toda la jerarquía del objeto básico, podremos crear fácilmente dicho método.
Método que retorna el valor máximo del identificador del elemento gráfico de todos los objetos vinculados al básico:
//+------------------------------------------------------------------+ //| Returns the maximum value of an ID | //| from all bound objects | //+------------------------------------------------------------------+ int CForm::GetMaxIDForm(void) { return (int)this.GetMaxLongPropForm(CANV_ELEMENT_PROP_ID); } //+------------------------------------------------------------------+
El método simplemente retorna el resultado del funcionamiento del método anterior, al que se le transmite la propiedad "identificador del objeto" para la búsqueda.
Método que retorna el valor máximo de la propiedad entera indicada en toda la jerarquía de objetos vinculados:
//+------------------------------------------------------------------+ //| Return the maximum value of the specified integer | //| property from the entire hierarchy of related objects | //+------------------------------------------------------------------+ long CForm::GetMaxLongPropAll(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Get the pointer to the main object CForm *main=(this.IsMain() ? this.GetObject() : this.GetMain()); //--- If the main object is obtained, then set the value of the specified property to 'property', otherwise the value is equal to -1 long property=(main!=NULL ? main.GetProperty(prop) : WRONG_VALUE); //--- If the received value is greater than -1 if(property>WRONG_VALUE) { //--- In the loop through the list of objects bound to the main object for(int i=0;i<main.ElementsTotal();i++) { //--- get the next object CForm *elm=main.GetElement(i); if(elm==NULL) continue; //--- Get the maximum value of the property from the entire hierarchy of objects subordinate to the current one in the loop long prop_form=elm.GetMaxLongPropForm(prop); //--- If the received value is greater than the 'property' value //--- set the received value to 'property' if(prop_form>property) property=prop_form; } } //--- Return the found maximum property value return property; } //+------------------------------------------------------------------+
La lógica del método se describe con detalle en los comentarios al código, y es similar a la del método encargado de encontrar la propiedad máxima de los objetos vinculados al básico. A diferencia del primero, en este método comenzaremos la búsqueda desde el objeto principal, el «antepasado» en la jerarquía de los objetos vinculados, e iteramos por las listas de todos los objetos de la cadena jerárquica completa de objetos vinculados. Como resultado, tendremos el mayor valor de propiedad en toda la jerarquía de objetos subordinados.
Método que retorna el valor máximo de un identificador en toda la jerarquía de objetos vinculados:
//+------------------------------------------------------------------+ //| Returns the maximum value of an ID | //| from the entire hierarchy of related objects | //+------------------------------------------------------------------+ int CForm::GetMaxIDAll(void) { return (int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_ID); } //+------------------------------------------------------------------+
El método retorna el resultado de la llamada al método anterior, en cuyos parámetros se transmite la propiedad "identificador" para buscar su valor máximo.
Las descripciones de las propiedades de los elementos gráficos se implementan en la clase del objeto WinForms básico, en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh.
En el método GetPropertyDescription(), escribimos los bloques de código necesarios para retornar la descripción de las dos nuevas propiedades del elemento gráfico:
property==CANV_ELEMENT_PROP_ACT_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_GROUP ? CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ZORDER ? CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) :
...
property==CANV_ELEMENT_PROP_BUTTON_TOGGLE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_BUTTON_GROUP ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_BUTTON_STATE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_STATE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) :
Ahora el método retornará correctamente las descripciones de todas las propiedades del elemento gráfico.
El objeto WinForms RadioButton solo puede funcionar correctamente junto con otros objetos de este tipo. Además, estos objetos deberán estar vinculados al mismo contenedor o tener el mismo valor de grupo, es decir, ser parte del mismo grupo de objetos. Si clicamos en uno de estos objetos con el ratón, se seleccionará su casilla de verificación (si no ha sido seleccionada antes), y todos los demás objetos de este grupo tendrán sus casillas de verificación sin marcar. Cuando cliquemos nuevamente en un objeto ya seleccionado, su indicador no se borrará.
Vamos a introducir algunas mejoras en la clase de objeto RadioButton, en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh.
En la sección privada de la clase, declararemos el método que establece el estado "no seleccionado" para todos los objetos del mismo grupo, y en la sección pública escribiremos el método que establece el estado indicado del objeto y su casilla de verificación :
//+------------------------------------------------------------------+ //| CheckBox object class of the WForms controls | //+------------------------------------------------------------------+ class CRadioButton : public CCheckBox { private: //--- Set the state of the checkbox as "not selected" for all RadioButtons of the same group in the container void UncheckOtherAll(void); protected: //--- Displays the checkbox for the specified state virtual void ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state); //--- 'The cursor is inside the active area, the left mouse button is clicked' event handler virtual void MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); public: //--- Set the checkbox status virtual void SetChecked(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag); this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag); if(this.Checked()) this.UncheckOtherAll(); } //--- Constructor
En el método encargado de establecer el estado de la casilla de verificación, escribiremos el valor transmitido al método en su propiedad, luego estableceremos el estado de la selección (seleccionado o no). Además, si el estado del objeto es "seleccionado", llamaremos al método en el que todos los demás objetos similares de este grupo se establecen en el estado "no seleccionado" y eliminaremos la casilla de verificación.
En el controlador del evento "El cursor está dentro del área activa, el botón del ratón (izquierdo) está sin pulsar", añadiremos un bloque de código en el que se verificará el estado del objeto, si se encuentra en el estado no seleccionado, entonces llamaremos al método para poner el objeto en el estado "seleccionado":
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CRadioButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.BackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); if(!this.Checked()) this.SetChecked(true); Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID(),", Group=",this.Group()); } this.Redraw(false); } //+------------------------------------------------------------------+
Aquí, se mostrará una entrada de depuración en el registro, indicando el evento, el estado del elemento (seleccionado/no seleccionado), su ID y el número de grupo. Luego eliminaremos esta entrada y la reemplazaremos con el envío de un mensaje a la biblioteca y al programa de control.
Método que establece el estado de la casilla de verificación como "no seleccionado" para todos los objetos RadioButton del mismo grupo en el contenedor:
//+------------------------------------------------------------------+ //| Sets the state of the checkbox to "not selected" | //| for all RadioButton objects of the same group in the container | //+------------------------------------------------------------------+ void CRadioButton::UncheckOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- From the base object, get a list of all objects of the RadioButton type CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CRadioButton *obj=list.At(i); if(obj==NULL) continue; //--- set its state to "not selected" obj.SetChecked(false); //--- Redraw the object to display an unselected checkbox obj.Redraw(false); } } } //+------------------------------------------------------------------+
Cada línea de código tiene los comentarios correspondientes, por lo que esperamos que la lógica del método no genere dudas. En resumen, necesitaremos obtener una lista con todos los objetos RadioButton adjuntos al contenedor. Todos deberán tener el mismo grupo, y la lista no deberá contener el objeto desde el cual se ha llamado a este método (después de todo, este es el objeto en el que se clicó con el ratón y que fue seleccionado, lo cual significa que no necesitaremos eliminar la bandera de selección del mismo). Ahora, iteraremos por la lista resultante y estableceremos para cada uno de los objetos el estado «no seleccionado» y desmarcaremos la casilla de verificación. Los objetos se redibujarán para mostrar los cambios.
En la clase de objeto de botón, en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, realizaremos modificaciones similares que nos permitirán no solo hacer los botones "clicables", sino también conseguir que dichos botones puedan tener un estado de activado/desactivado. En consecuencia, podremos definirlos en grupos en los que los botones vinculados por un mismo grupo funcionarán juntos.
En la sección privada de la clase, declararemos el método que establece para todos los botones de un grupo el estado "no presionado":
//+------------------------------------------------------------------+ //| Label object class of WForms controls | //+------------------------------------------------------------------+ class CButton : public CLabel { private: int m_text_x; // Text X coordinate int m_text_y; // Text Y coordinate color m_array_colors_bg_tgl[]; // Array of element background colors for the 'enabled' state color m_array_colors_bg_tgl_dwn[]; // Array of control background colors for the 'enabled' state when clicking on the control color m_array_colors_bg_tgl_ovr[]; // Array of control background colors for the 'enabled' state when hovering the mouse over the control color m_array_colors_bg_tgl_init[]; // Array of initial element background colors for the 'enabled' state //--- Set the button state as "released" for all Buttons of the same group in the container void UnpressOtherAll(void); protected:
En la sección pública de la clase, cambiaremos el método que establece el estado del botón y escribiremos los métodos necesarios para establecer y retornar la bandera del botón que funciona en un grupo con otros objetos de botón:
//--- (1) Set and (2) return the Toggle control status void SetState(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag); if(this.State()) { this.UnpressOtherAll(); } } bool State(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); } //--- (1) Set and (2) return the group flag void SetGroupButtonFlag(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,flag); } bool GroupButton(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP); }
En el método que establece el estado del botón, primero se escribirá el estado en la propiedad del objeto, y luego, si el estado es "pulsado", llamaremos al método que establece el estado para el resto de los botones de un grupo como "no pulsado".
En el controlador del último evento del ratón, añadiremos la verificación de una condición más, similar al método de la clase de objeto de formulario del mismo nombre, que analizamos anteriormente:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CButton::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleON() : this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Método que establece el estado del botón como "desactivado" para todos los objetos Button del mismo grupo en el contenedor:
//+------------------------------------------------------------------+ //| Sets the state of the button to "released" | //| for all Buttons of the same group in the container | //+------------------------------------------------------------------+ void CButton::UnpressOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all objects of the Button type from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_BUTTON); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CButton *obj=list.At(i); if(obj==NULL) continue; //--- set the button status to "released", obj.SetState(false); //--- set the background color to the original one (the cursor is on another button outside this one) obj.SetBackgroundColor(obj.BackgroundColorInit(),false); //--- Redraw the object to display the changes obj.Redraw(false); } } } //+------------------------------------------------------------------+
La lógica del método es idéntica al método de la clase de objeto RadioButton que se describe al completo en los comentarios al código, y espero que no necesite explicaciones.
El objeto WinForms CheckBox en su estado normal, al desplazar sobre él el cursor del ratón, cambia el color de fondo, la casilla de verificación y el marco del área de la casilla de verificación. El fondo del objeto en sí permanece sin cambios (transparente). Pero si dichos objetos se combinan en un grupo (como se realizará en el objeto que haremos a continuación), al pasar el cursor del ratón sobre el objeto, su color de fondo también cambiará. Para utilizar el objeto CheckBox para crear una lista de objetos CheckBox, realizaremos los cambios necesarios en este objeto, en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh.
Haremos que el método para configurar el estado de la casilla de verificación sea virtual, como en su objeto heredero RadioButton:
//--- (1) Set and (2) return the checkbox status virtual void SetChecked(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag); if((bool)this.CheckState()!=flag) this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag); } bool Checked(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_CHECKED); }
En el constructor de clases, estableceremos explícitamente el color de fondo del objeto en opacidad total y estableceremos asimismo el desplazamiento de la zona activa en un píxel en cada lado:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CLabel(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKBOX); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetCheckWidth(DEF_CHECK_SIZE); this.SetCheckHeight(DEF_CHECK_SIZE); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetOpacity(0); this.SetForeColorMouseDown(CLR_DEF_FORE_COLOR_MOUSE_DOWN); this.SetForeColorMouseOver(CLR_DEF_FORE_COLOR_MOUSE_OVER); this.SetCheckBackgroundColor(CLR_DEF_CHECK_BACK_COLOR,true); this.SetCheckBackgroundColorMouseDown(CLR_DEF_CHECK_BACK_MOUSE_DOWN); this.SetCheckBackgroundColorMouseOver(CLR_DEF_CHECK_BACK_MOUSE_OVER); this.SetCheckBorderColor(CLR_DEF_CHECK_BORDER_COLOR,true); this.SetCheckBorderColorMouseDown(CLR_DEF_CHECK_BORDER_MOUSE_DOWN); this.SetCheckBorderColorMouseOver(CLR_DEF_CHECK_BORDER_MOUSE_OVER); this.SetCheckFlagColor(CLR_DEF_CHECK_FLAG_COLOR,true); this.SetCheckFlagColorMouseDown(CLR_DEF_CHECK_FLAG_MOUSE_DOWN); this.SetCheckFlagColorMouseOver(CLR_DEF_CHECK_FLAG_MOUSE_OVER); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetTextAlign(ANCHOR_LEFT); this.SetActiveAreaShift(1,1,1,1); this.m_text_x=0; this.m_text_y=0; this.m_check_x=0; this.m_check_y=0; this.Redraw(false); } //+------------------------------------------------------------------+
Aquí estableceremos el fondo en opacidad total, ya que necesitaremos crear más objetos con la opacidad del objeto básico, y para no configurar constantemente la opacidad de este objeto en su estado normal al ser creado, la estableceremos explícitamente aquí en el constructor. El desplazamiento del área activa en un píxel por cada lado, por su parte, es un intento de aumentar la separación entre los objetos CheckBox adyacentes para que cuando el cursor se aleje de un objeto, tenga tiempo de estar fuera del objeto sin posarse inmediatamente sobre el siguiente, para que así el cursor pase a través del objeto básico y no entre inmediatamente en el cercano. Todo esto ha surgido como resultado de la búsqueda de una solución al problema en el que los objetos cercanos no siempre recuperan su color de fondo predeterminado después de alejar el cursor de ellos. Sin embargo, esta solución no siempre sirve de ayuda, y aún deberemos encontrar tiempo para comprender a fondo este problema y resolverlo.
En el método que redibuja el objeto, en lugar de especificar la opacidad completa (valor 0), ahora especificaremos el valor de la magnitud de la opacidad registrado en las propiedades del objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CCheckBox::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.Erase(this.BackgroundColor(),this.Opacity(),true); //--- Set corrected text coordinates relative to the checkbox this.SetCorrectTextCoords(); //--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.ShowControlFlag(this.CheckState()); this.Update(redraw); } //+------------------------------------------------------------------+
Esto será necesario para que en diferentes implementaciones de estos objetos podamos usar el color de fondo para que este cambie al pasar el cursor sobre el objeto. Obviamente, con un fondo opaco, como era el caso antes, no podíamos mostrar los cambios del color de fondo.
En todos los controladores de los eventos del ratón que requieran cambiar el color de fondo del objeto, introduciremos las mejoras correspondientes:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorMouseOver(),false); this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseDown(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseDown(),false); this.SetCheckFlagColor(this.CheckFlagColorMouseDown(),false); this.SetBackgroundColor(this.BackgroundColorMouseDown(),false); this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorInit(),false); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetChecked(!this.Checked()); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID()); } this.Redraw(false); } //+------------------------------------------------------------------+
En el controlador del último evento del ratón, añadiremos la verificación del estado que ya conocemos:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CCheckBox::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Ahora podemos comenzar a crear un nuevo objeto.
El objeto WinForms CheckedListBox
Este objeto WinForms será un panel que contendrá una lista de objetos CheckBox. Cuando movamos el cursor del ratón sobre los objetos de la lista, su color de fondo cambiará junto con el color de la casilla de verificación, su fondo y el borde del área de la casilla de verificación. Los objetos de la lista se podrán ubicar tanto verticalmente uno encima del otro como en columnas de varias unidades. Hoy solo implementaremos la disposición vertical de los objetos.
En el directorio de la biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, crearemos el nuevo archivo CheckedListBox.mqh de la clase CCheckedListBox.
La clase deberá heredarse del objeto contenedor básico, y para que "vea" tanto la clase CContainer como la clase CCheckBox, le conectaremos el archivo de clase del objeto de panel en el que todos los archivos requeridos de las clases necesarias están ya visibles:
//+------------------------------------------------------------------+ //| CheckedListBox.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 "..\Containers\Panel.mqh" //+------------------------------------------------------------------+ //| CCheckedListBox object Class of the WForms controls | //+------------------------------------------------------------------+ class CCheckedListBox : public CContainer { }
En la sección privada, declararemos el método virtual para crear un nuevo objeto gráfico, y en la sección pública, declararemos el método necesario para crear la lista del número especificado de objetos CheckBox y el constructor paramétrico:
//+------------------------------------------------------------------+ //| CCheckedListBox object Class of the WForms controls | //+------------------------------------------------------------------+ class CCheckedListBox : public CContainer { private: //--- Create a new graphical object virtual 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); public: //--- Create the specified number of CheckBox objects void CreateCheckBox(const int count); //--- Constructor CCheckedListBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Constructor paramétrico:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CCheckedListBox::CCheckedListBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); } //+------------------------------------------------------------------+
Después, estableceremos para el objeto su tipo de objeto WinForms y el tipo del objeto gráfico de la biblioteca. A continuación, estableceremos para el tamaño del marco del objeto un píxel; el tipo de marco es simple, así que estableceremos el marco predeterminado y el color del texto para los objetos gráficos en la biblioteca.
Método que crea el número especificado de objetos CheckBox en el panel principal:
//+------------------------------------------------------------------+ //| Create the specified number of CheckBox objects | //+------------------------------------------------------------------+ void CCheckedListBox::CreateCheckBox(const int count) { //--- Create a pointer to the CheckBox object CCheckBox *cbox=NULL; //--- In the loop through the specified number of objects for(int i=0;i<count;i++) { //--- Set the coordinates and dimensions of the created object int x=this.BorderSizeLeft()+1; int y=(cbox==NULL ? this.BorderSizeTop()+1 : cbox.BottomEdgeRelative()+4); int w=this.Width()-this.BorderSizeLeft()-this.BorderSizeRight(); int h=DEF_CHECK_SIZE+1; //--- If the object could not be created, display a message in the log if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,x,y,w,h,clrNONE,255,true,false)) CMessage::ToLog(DFUN,MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ); //--- Get the created object from the list by the loop index cbox=this.GetElement(i); //--- If the object could not be obtained, display a message in the log if(cbox==NULL) CMessage::ToLog(DFUN,MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ); //--- Set the frame size to zero cbox.SetBorderSizeAll(0); //--- Set the left center alignment of the checkbox and the text cbox.SetCheckAlign(ANCHOR_LEFT); cbox.SetTextAlign(ANCHOR_LEFT); //--- Set the object text cbox.SetText("CheckBox"+string(i+1)); //--- Set the opacity of the base object and the default background color cbox.SetOpacity(this.Opacity()); cbox.SetBackgroundColor(this.BackgroundColor(),true); cbox.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER); cbox.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN); } //--- For the base object, set the auto resizing mode as "increase and decrease" //--- and set the auto resize flag this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); this.SetAutoSize(true,false); } //+------------------------------------------------------------------+
La lógica del método se comenta con todo detalle en el código. En resumen: transmitiremos al método el número requerido de objetos CheckBox que deben crearse en el panel. En un ciclo por el número especificado de objetos, crearemos estos y estableceremos las propiedades necesarias para ellos. Una vez completado el ciclo de creación de objetos CheckBox, configuraremos el modo de cambio de tamaño automático del panel para que pueda ajustarse al tamaño total de todos los objetos creados en él y configuraremos el indicador de cambio de tamaño automático para el panel, que a su vez hará que el panel se redimensione para ajustarse a los objetos creados en él.
Método virtual para crear un nuevo objeto gráfico:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CCheckedListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_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) { string name=this.CreateNameDependentObject(obj_name); //--- create the CheckBox object CGCnvElement *element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); //--- set the object relocation flag and relative coordinates element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
Como uno de los padres de este objeto es la clase del objeto de formulario donde se crea la funcionalidad para trabajar con el ratón, y la clase de este objeto no está visible en ella, deberemos redefinir el método virtual de la clase CForm para crear un nuevo objeto gráfico aquí. En este método, no necesitaremos verificar el tipo de objeto transmitido al mismo, ya que aquí sabemos exactamente qué tipo de objeto necesitaremos crear, y este tipo es el tipo CheckBox, creado aquí y para el que estableceremos los valores mínimos: la bandera de reubicación y las coordenadas relativas.
Todos los demás métodos necesarios para que la clase funcione ya están en sus clases principales.
Naturalmente, mejoraremos aún más la clase de este objeto para implementar su funcionalidad adicional, pero hoy, para realizar las verificaciones, nos detendremos aquí.
Ahora necesitaremos añadir el procesamiento de este tipo de objetos en todas las clases de contenedor para que podamos crear tales objetos en ellas.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh de la clase de objeto de panel, incluiremos el archivo de la clase recién creada:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+
Ahora esta nueva clase será visible en todas las clases de contenedor de la biblioteca.
En el método que crea un nuevo objeto gráfico, añadiremos el procesamiento del tipo del nuevo objeto de biblioteca:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_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) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Aquí simplemente crearemos un nuevo objeto de la clase CCheckedListBox.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh de la clase del objeto de contenedor básico, en el método que crea un nuevo elemento vinculado, asignaremos al objeto creado un grupo igual al del objeto básico, pero solo si el objeto creado no es un objeto de contenedor. Para los objetos WinForms "Text label", "CheckBox", "RadioButton", añadiremos una configuración de color de fondo transparente, así como su transparencia total y añadiremos el procesamiento del objeto WinForms "CheckedListBox":
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set the text color of the created 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_GROUPBOX) obj.SetGroup(this.Group()); //--- Depending on the created 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 : //--- set the frame color equal to the background color obj.SetBorderColor(obj.BackgroundColor(),true); break; //--- For the Text Label, CheckBox and RadioButton WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- set the object text color depending on the one passed to the method: //--- either the container text color, or the one passed to the method. //--- The frame color is set equal to the text color //--- Set the background color to transparent obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- For the Button WinForms object case GRAPH_ELEMENT_TYPE_WF_BUTTON : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For the CheckedListBox WinForms object case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break; default: break; } //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Redraw the panel and all added objects, and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh de la clase de objeto GroupBox, en el método para crear un nuevo objeto gráfico , añadiremos el procesamiento del nuevo tipo de objeto CheckedListBox:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_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) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Al final del método de inicialización de variables Initialize(), añadiremos la configuración del valor de grupo predeterminado:
this.SetEnabled(true); this.SetVisible(true,false); //--- Set the default group value this.SetGroup((int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_GROUP)+1); } //+------------------------------------------------------------------+
Aquí llamaremos al método analizado anteriormente, que retorna el valor máximo de la propiedad indicada de todos los objetos en la colección de elementos gráficos de la biblioteca. A continuación, especificaremos la propiedad "grupo" como el parámetro deseado y añadiremos 1 al valor resultante, lo que hará que el valor del nuevo grupo sea máximo. Es importante que los objetos de contenedor tengan un grupo único que los distinga de otros objetos de contenedor.
En el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh de la clase de colección de elementos gráficos, añadiremos el archivo de clase del objeto CheckedListBox a la lista de archivos incluidos:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Graph\WForms\Containers\GroupBox.mqh" #include "..\Objects\Graph\WForms\Containers\Panel.mqh" #include "..\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh" #include "..\Objects\Graph\Standard\GStdVLineObj.mqh" #include "..\Objects\Graph\Standard\GStdHLineObj.mqh"
En la sección privada de la clase, declararemos el método que retorna el identificador máximo de todos los elementos gráficos de la colección:
//--- Return the screen coordinates of each pivot point of the graphical object bool GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]); //--- Return the maximum ID from all graphical elements of the collection int GetMaxID(void); public:
En todos los métodos de creación de elementos gráficos, cambiaremos la línea donde se establece como identificador el número total de elementos gráficos en la colección,
int id=this.m_list_all_canv_elm_obj.Total();
por la línea donde se asigna al identificador el ID máximo de todos los elementos gráficos de la colección más 1:
//--- 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.GetMaxID()+1; CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; if(res==ADD_OBJ_RET_CODE_EXIST) obj.SetID(id); 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
Dichos cambios se han realizado en todos los métodos que crean elementos gráficos, y no los presentaremos aquí: los encontrará en los archivos de la biblioteca adjuntos al artículo.
En el método para procesar el anterior formulario activo debajo del cursor, añadiremos una llamada al manejador de eventos del ratón para el objeto de ciclo actual, para evitar omitir su procesamiento en caso de que el objeto aún no se encuentre en la lista de objetos inactivos cuando el cursor se aleje de él, pero, de hecho, ya esté inactivo:
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(void) { //--- Get all the elements of the CForm type and above CArrayObj *list=GetList(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_FORM,EQUAL_OR_MORE); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CForm *elm=obj.GetInteractForm(j); if(elm==NULL) continue; //--- and call its method of handling mouse events elm.OnMouseEventPostProcessing(); } } } //+------------------------------------------------------------------+
Para crear en el futuro el procesamiento de la interacción del botón derecho del ratón con los objetos de la interfaz gráfica del programa, en el controlador de eventos OnChartEvent(), añadiremos una verificación sobre la pulsación y la retención del botón derecho del ratón:
//--- Handling mouse events of graphical objects on canvas //--- If the event is not a chart change else { //--- Check whether the mouse button is clicked ENUM_MOUSE_BUTT_KEY_STATE butt_state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam); bool pressed=(butt_state==MOUSE_BUTT_KEY_STATE_LEFT || butt_state==MOUSE_BUTT_KEY_STATE_RIGHT ? true : false); ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare static variables for the active form and status flags
al mismo tiempo, escribiremos el estado de los botones del ratón en una variable, de modo que luego, si necesitamos este valor, no tengamos que llamar nuevamente el método que lee el estado de los botones.
Método que retorna el identificador máximo de todos los elementos gráficos de la colección:
//+------------------------------------------------------------------+ //| Return the maximum ID | //| of all graphical elements of the collection | //+------------------------------------------------------------------+ int CGraphElementsCollection::GetMaxID(void) { int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID); CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index); return(obj!=NULL ? obj.ID() : WRONG_VALUE); } //+------------------------------------------------------------------+
Aquí, obtenemos el índice del objeto en la lista de colección con el mayor valor de identificador. A continuación, obtendremos el puntero al objeto en el índice recibido y retornaremos el identificador del objeto si hemos recibido el puntero al objeto. De lo contrario, retornaremos -1: o bien la colección de elementos gráficos está vacía o bien ha sucedido un error al obtener el puntero.
Simulación
Para la prueba, tomaremos el asesor experto del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part111\ con el nuevo nombre TstDE111.mq5.
Haremos que el panel principal sea más grande y colocaremos en su primer objeto de contenedor GroupBox el objeto CheckBox, así como cuatro objetos RadioButton con un valor de grupo de 2, y tres objetos de botón, dos de los cuales tendrán el grupo 2, mientras que el tercero tendrá un grupo predeterminado heredado del grupo de su objeto de contenedor, el grupo 1. Debajo de los botones, colocaremos dos objetos RadioButton más con un valor de grupo de 3. Por lo tanto, en el contenedor, tendremos cuatro objetos RadioButton con el grupo 2, dos objetos RadioButton con el grupo 3 y tres botones: dos con el grupo 2 y uno con grupo 1.
A la derecha del primer contenedor GroupBox, colocaremos otro del mismo tipo y crearemos dentro de él un nuevo objeto CheckedListBox, en el que crearemos cuatro objetos CheckBox. Todos los objetos colocados en diferentes grupos del mismo contenedor deberían funcionar como conjuntos de objetos separados. Asimismo, todos los componentes visuales para la interacción de los objetos con el ratón deberían funcionar satisfactoriamente.
En el manejador OnInit() del asesor, colocaremos este bloque para crear todos los elementos de la GUI:
//+------------------------------------------------------------------+ //| 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 WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,400,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 2 bound panel objects CPanel *obj=NULL; for(int i=0;i<2;i++) { //--- create the panel object with calculated coordinates, width of 90 and height of 40 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(prev==NULL ? xb : xb+prev.Width()+20); int y=0; if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false)) { obj=pnl.GetElement(i); if(obj==NULL) continue; obj.SetBorderSizeAll(3); obj.SetBorderStyle(FRAME_STYLE_BEVEL); obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),4*i),true); obj.SetForeColor(clrRed,true); //--- Calculate the width and height of the future text label object int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight(); int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom(); //--- Create a text label object obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,w,h,clrNONE,255,false,false); //--- Get the pointer to a newly created object CLabel *lbl=obj.GetElement(0); if(lbl!=NULL) { //--- If the object has an even or zero index in the list, set the default text color for it if(i % 2==0) lbl.SetForeColor(CLR_DEF_FORE_COLOR,true); //--- If the object index in the list is odd, set the object opacity to 127 else lbl.SetForeColorOpacity(127); //--- Set the font Black width type and //--- specify the text alignment from the EA settings lbl.SetFontBoldType(FW_TYPE_BLACK); lbl.SetTextAlign(InpTextAlign); lbl.SetAutoSize((bool)InpTextAutoSize,false); //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK)); //--- Set the frame width, type and color for a text label and update the modified object lbl.SetBorderSizeAll(1); lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle); lbl.SetBorderColor(CLR_DEF_BORDER_COLOR,true); lbl.Update(true); } } } //--- Create the WinForms GroupBox1 object CGroupBox *gbox1=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox1 int w=pnl.GetUnderlay().Width(); int y=obj.BottomEdgeRelative()+6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0,y,200,150,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox1=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0); if(gbox1!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox1.SetBorderStyle(FRAME_STYLE_STAMP); gbox1.SetBorderColor(pnl.BackgroundColor(),true); gbox1.SetForeColor(gbox1.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox1.SetText("GroupBox1"); //--- Create the CheckBox object gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false); //--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects CCheckBox *cbox=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,0); //--- If CheckBox is created and the pointer to it is received if(cbox!=NULL) { //--- Set the CheckBox parameters from the EA inputs cbox.SetAutoSize((bool)InpCheckAutoSize,false); cbox.SetCheckAlign(InpCheckAlign); cbox.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status cbox.SetText("CheckBox"); cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); cbox.SetBorderColor(CLR_DEF_BORDER_COLOR,true); cbox.SetChecked(true); cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState); } //--- Create 4 RadioButton WinForms objects CRadioButton *rbtn=NULL; for(int i=0;i<4;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? cbox.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,yrb+4,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+1)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(2); } } //--- Create 3 Button WinForms objects CButton *butt=NULL; for(int i=0;i<3;i++) { //--- Create the Button object int ybtn=(butt==NULL ? 12 : butt.BottomEdgeRelative()+4); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+20,ybtn,78,18,clrNONE,255,true,false); //--- get the pointer to the Button object by its index in the list of bound Button type objects butt=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i); //--- If Button is created and the pointer to it is received if(butt!=NULL) { //--- Set the Button parameters from the EA inputs butt.SetAutoSize((bool)InpButtonAutoSize,false); butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false); butt.SetTextAlign(InpButtonTextAlign); //--- Set the text color, as well as frame style and color butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,2),true); butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle); butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-10),true); //--- Set the 'toggle' mode depending on the settings butt.SetToggleFlag(InpButtonToggle); //--- Set the displayed text on the button depending on the 'toggle' flag string txt="Button"+string(i+1); if(butt.Toggle()) butt.SetText("Toggle-"+txt); else butt.SetText(txt); if(i<2) { butt.SetGroup(2); if(butt.Toggle()) { butt.SetBackgroundColorMouseOver(butt.ChangeColorLightness(butt.BackgroundColor(),-5)); butt.SetBackgroundColorMouseDown(butt.ChangeColorLightness(butt.BackgroundColor(),-10)); butt.SetBackgroundColorToggleON(C'0xE2,0xC5,0xB1',true); butt.SetBackgroundColorToggleONMouseOver(butt.ChangeColorLightness(butt.BackgroundColorToggleON(),-5)); butt.SetBackgroundColorToggleONMouseDown(butt.ChangeColorLightness(butt.BackgroundColorToggleON(),-10)); } } } } //--- Create 2 RadioButton WinForms objects rbtn=NULL; for(int i=0;i<2;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? butt.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,butt.CoordXRelative()-4,yrb+3,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i+4); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+5)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(3); } } } } //--- Create the GroupBox2 WinForms object CGroupBox *gbox2=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2 w=gbox1.Width()-1; int x=gbox1.RightEdgeRelative()+1; int h=gbox1.BottomEdgeRelative()-6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1); if(gbox2!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox2.SetBorderStyle(FRAME_STYLE_STAMP); gbox2.SetBorderColor(pnl.BackgroundColor(),true); gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox2.SetText("GroupBox2"); //--- Create the CheckedListBox object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,80,80,clrNONE,255,true,false); //--- get the pointer to the CheckedListBox object by its index in the list of bound objects of the CheckBox type CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0); //--- If CheckedListBox is created and the pointer to it is received if(clbox!=NULL) { clbox.CreateCheckBox(4); } } } //--- Redraw all objects according to their hierarchy pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Los comentarios al código describen la lógica con todo detalle. Lo dejaremos para el estudio independiente. Podrá plantear cualquier pregunta en los comentarios al artículo.
Vamos a compilar el asesor y ejecutarlo en el gráfico:
Bien, ya hemos visto que la funcionalidad declarada funciona correctamente: los mismos objetos del mismo contenedor, pero colocados en diferentes grupos, funcionan como es debido; los objetos de cada grupo forman una unidad independiente y no afectan al funcionamiento de los objetos del mismo tipo de otro grupo. Los dos botones de interruptor combinados en un grupo funcionan correctamente y no afectan al tercer botón, que funciona solo. El objeto CheckedListBox en su estado actual también funciona como debe. Todo el componente visual se comporta correctamente (lo cual no excluye la aparición de fallas en el futuro al cambiar los colores durante el procesamiento del estado anterior del ratón y sus botones). No obstante, estoy seguro de que encontraremos y corregiremos todos los posibles errores durante el posterior desarrollo de los elementos gráficos.
¿Qué es lo próximo?
En el próximo artículo continuaremos trabajando en los elementos gráficos de la GUI de los programas creados a partir de la biblioteca.
*Artículos de esta serie:
DoEasy. Controles (Parte 1): Primeros pasos
DoEasy. Elementos de control (Parte 2): Continuamos trabajando con la clase CPanel
DoEasy. Elementos de control (Parte 3): Creando controles vinculados
DoEasy. Elementos de control (Parte 4): Elemento de control "Panel", parámetros Padding y Dock
DoEasy. Elementos de control (Parte 5): Objeto básico WinForms, control «Panel», parámetro AutoSize
DoEasy. Elementos de control (Parte 6): Control «Panel», cambio automático del tamaño del contenedor según el contenido interno
DoEasy. Elementos de control (Parte 7): Elemento de control «etiqueta de texto»
DoEasy. Elementos de control (Parte 8): Objetos básicos WinForms por categorías, controles "GroupBox" y "CheckBox
DoEasy. Elementos de control (Parte 9): Reorganizando los métodos de los objetos WinForms, los controles "RadioButton" y "Button"
DoEasy. Elementos de control (Parte 10): Objetos WinForms: dando vida a la interfaz
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/11194
- 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