
DoEasy. Elementos de control (Parte 5): Objeto básico WinForms, control "Panel", parámetro AutoSize
Contenido
- Concepto
- Mejorando las clases de la biblioteca
- Clase de objeto básico WinForms
- Simulación
- ¿Qué es lo próximo?
Concepto
Antes de comenzar a implementar las propiedades AutoSize y AutoSizeMode del objeto del panel, vamos a crear una clase básica para todos los objetos de la biblioteca de WinForms. Como muchas de las propiedades de dichos objetos se heredan entre sí, las propiedades inherentes al panel, en cuyo objeto estamos trabajando ahora, se pueden usar para otros objetos WinForms. Entonces, para no tener que escribir las mismas propiedades para cada uno de estos objetos, crearemos un objeto WinForms básico, del cual heredaremos todos los demás objetos de este tipo. El objeto básico en sí se heredará de la clase del objeto de formulario donde se implementa la interacción con el ratón.
Si un objeto colocado dentro de un panel tiene activada la propiedad Dock analizada en el artículo anterior, dicho objeto se "pegará" a los límites de su contenedor. El borde del contenedor se indica en la propiedad DockMode. En este caso, si cada objeto subsiguiente colocado dentro del panel tiene la misma vinculación al lado de su contenedor (panel) que el objeto anterior colocado dentro del panel, entonces se vinculará al lado más cercano del objeto anterior, y no al lado especificado del contenedor. Así, todos los objetos colocados dentro del panel con vinculación, por ejemplo, al borde izquierdo del contenedor, se ordenarán en una fila de izquierda a derecha. Al mismo tiempo, si el modo AutoSize está activado para el panel, el contenedor aumentará automáticamente de anchura para que todos los objetos colocados dentro de él, alineados en una fila, no rebasen su contenedor. El contenedor debe comportarse de la misma forma si se coloca dentro de él un objeto que sobresalga más allá de los bordes del panel; si el panel tiene habilitado el modo AutoSize, entonces deberá ajustar el tamaño de sus lados para que el objeto no sobrepase sus límites.
Al mismo tiempo, existen diferencias si el objeto está adjunto a uno de los lados de su contenedor y el contenedor en sí tiene la propiedad AutoSize activa o no.
Todos los objetos colocados dentro de un contenedor con tamaño automático activo serán colocados en su orden de prioridad, marcado por el número ordinal del objeto en la lista de objetos adjuntos al contenedor. Esto nos ofrecerá la oportunidad de predeterminar la ubicación de los objetos dentro del contenedor, cuyo tamaño se ajustará automáticamente al tamaño total de todos los elementos adjuntos al mismo. Hablaremos de ello en artículos posteriores. Hoy finalizaremos las clases de la biblioteca según la tarea establecida: crearemos un nuevo objeto básico WinForms e implementaremos la propiedad Autosize al crear un elemento adjunto directamente desde el panel.
Además de las tareas indicadas, realizaremos una pequeña optimización de la construcción de los elementos gráficos dentro del panel. Como primero deberemos organizar todos los elementos adjuntos al panel según sus números ordinales y su valor de anclaje (Dock), y luego deberemos cambiar el tamaño del panel en caso de que tenga la propiedad de tamaño automático configurada para adaptarse a su contenido interno, si es realmente necesario, entonces, primero tendremos que colocar "virtualmente" todos los elementos dentro del panel, y luego ver cuánto necesitamos cambiar el tamaño del panel, cambiarlo si es necesario, y solo después de ello, redibujar todos los elementos ubicados dentro del panel. De no hacer esto, y los elementos se dibujarán inmediatamente, y su dibujado con el cambio de tamaño y el punto de anclaje se volverá visible en tiempo real, lo cual redundará en la aparición de varios artefactos de imagen alrededor del panel, cosa fea y nada correcta. Por consiguiente, primero colocaremos los elementos en el orden correcto, cambiaremos su tamaño si es necesario (esto dependerá del método de vinculación del elemento y su número en la lista de elementos vinculados), y luego calcularemos cuánto se debe cambiar el tamaño del panel; solo después de hacer todo lo anterior, redibujaremos el panel y los elementos nuevamente ubicados dentro de él en un nuevo orden.
Mejorando las clases de la biblioteca
Como hoy estamos creando un nuevo objeto de biblioteca, deberemos añadir su tipo a la lista de tipos de objetos de biblioteca. Además, necesitaremos saber el tipo de objeto al acceder al objeto de sustrato del panel.
En el archivo \MQL5\Include\DoEasy\Defines.mqh, en la lista de tipos de objeto de la biblioteca, añadimos el nuevo tipo, el objeto básico WinForms:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_BASE, // WinForms Base object type (base abstract WinForms object) OBJECT_DE_TYPE_GWF_PANEL, // WinForms Panel object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type //--- ... //--- ... }
En la lista de tipos de elementos gráficos, añadimos dos nuevos tipos: el elemento de sustrato gráfico y el objeto básico WinForms, y la macrosustitución GRAPH_ELEMENT_TYPE_PANEL la renombramos como GRAPH_ELEMENT_TYPE_WF_PANEL:
//+------------------------------------------------------------------+ //| 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 GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
Partiendo de estos tipos, ahora sabremos qué objeto está seleccionado actualmente y haremos algo con él si este tipo es el que estamos buscando.
En el archivo \MQL5\Include\DoEasy\Data.mqh escribimos los índices de los nuevos mensajes de la biblioteca:
MSG_GRAPH_ELEMENT_TYPE_WINDOW, // Window MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Underlay of the Panel WinForms control object MSG_GRAPH_ELEMENT_TYPE_WF_BASE, // WinForms base control MSG_GRAPH_ELEMENT_TYPE_WF_PANEL, // Panel control
y los textos de los mensajes que se corresponden con los índices nuevamente añadidos:
{"Окно","Window"}, {"Подложка объекта-элемента управления WinForms \"Панель\"","Underlay object-control WinForms \"Panel\""}, {"Базовый элемент управления WinForms","Base WinForms control"}, {"Элемент управления \"Panel\"","Control element \"Panel\""},
Todos los objetos gráficos de la biblioteca se heredan de la clase del objeto gráfico básico de la biblioteca CGBaseObj. En esta clase se encuentran todos los métodos básicos para trabajar con cualquier objeto gráfico de la biblioteca. En el archivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, en el método que retorna la descripción del tipo del elemento gráfico, añadimos la muestra de la descripción de los dos nuevos tipos de elementos gráficos de la biblioteca:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(void) { return ( this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : "Unknown" ); } //+------------------------------------------------------------------+
Vamos a virtualizar algunos métodos en la clase de objeto de elemento gráfico en el lienzo, de la cual se heredan el resto de objetos gráficos Canvas de la librería. La clase se encuentra en el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
El asunto es que no para cada objeto heredero de esta clase puede adecuarse la implementación de algunos métodos creados en ella. Tras hacerlos virtuales aquí, ofrecemos la oportunidad de cambiar estos métodos en aquellas clases herederas donde la implementación de los métodos debería diferir de la creada en esta clase.
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge, virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); void SetRightEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); } void SetBottomEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); }
...
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); }
Ahora bien, en aquellas clases herederas donde se necesite una implementación diferente de estos métodos, simplemente escribiremos con exactitud los mismos métodos virtuales, pero con su propia implementación, que resulta diferente a la aquí escrita: precisamente esos métodos virtuales de las clases herederas serán llamados al acceder a la clase principal del método virtual.
En algunos métodos de las clases que finalizaremos hoy, necesitamos añadir una bandera que indique la necesidad de redibujar el objeto gráfico. Esto es necesario para optimizar el procesamiento de las listas de objetos, para no redibujar constantemente cada objeto subsiguiente de la lista, y primero procesar todos los objetos de la misma (por ejemplo, cambiando el tamaño de cada uno y desplazándolos a una nueva ubicación), y ya después de procesar la lista completa, simplemente redibujar cada objeto en sus nuevas coordenadas, o con su nuevo tamaño. Esto resultará visualmente más rápido que redibujar cada objeto de la lista justo después de cambiar sus dimensiones y coordenadas, y pasar al siguiente objeto de la lista y cambiar el tamaño, las coordenadas y redibujar de la misma forma.
En el archivo de la clase del objeto de sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh, cambiamos los nombres de las variables y métodos, quitando para ello el texto "shadow". Simplemente nos parece que encontrar esta subcadena en los nombres de las variables y los métodos del objeto de sombra resulta superfluo.
Como al cambiar el tamaño del objeto que proyecta la sombra, necesitaremos redibujar completamente la sombra con las nuevas dimensiones, pero con los mismos parámetros de sombra que había antes de cambiar el tamaño, añadiremos una variable más que almacenará el radio de desenfoque de la sombra; para ello, agregaremos dos métodos para obtener acceso a la nueva variable, y en el método de redibujado de la sombra, añadiremos una bandera que indica la necesidad de redibujar el objeto de sombra al completo:
//+------------------------------------------------------------------+ //| Shadows object class | //+------------------------------------------------------------------+ class CShadowObj : public CGCnvElement { private: color m_color; // Shadow color uchar m_opacity; // Shadow opacity uchar m_blur; // Blur //--- Gaussian blur bool GaussianBlur(const uint radius); //--- Return the array of weight ratios bool GetQuadratureWeights(const double mu0,const int n,double &weights[]); //--- Draw the object shadow form void DrawShadowFigureRect(const int w,const int h); public: //--- Constructor CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); //--- Supported object properties (1) integer and (2) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Draw an object shadow void Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw); //+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) return the shadow color void SetColor(const color colour) { this.m_color=colour; } color Color(void) const { return this.m_color; } //--- (1) Set and (2) return the shadow opacity void SetOpacity(const uchar opacity) { this.m_opacity=opacity; } uchar Opacity(void) const { return this.m_opacity; } //--- (1) Set and (2) return the shadow blur void SetBlur(const uchar blur) { this.m_blur=blur; } uchar Blur(void) const { return this.m_blur; } }; //+------------------------------------------------------------------+
En el constructor de clases, introducimos el valor de opacidad de la sombra por defecto, especificado por la macrosustitución CLR_DEF_SHADOW_OPACITY en el archivo Defines.mqh de la biblioteca, y especificamos el valor de desenfoque de la sombra por defecto, también indicado por la macrosustitución DEF_SHADOW_BLUR del mismo archivo:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CShadowObj::CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetColorBackground(clrNONE); CGCnvElement::SetOpacity(0); CGCnvElement::SetActive(false); this.m_opacity=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.m_visible=true; CGCnvElement::Erase(); } //+------------------------------------------------------------------+
En la implementación del método que dibuja la sombra de un objeto, ahora indicaremos explícitamente la bandera que indica la necesidad de redibujar la sombra, y en lugar de la variable local radius, usaremos la nueva variable m_blur. Esto nos dará la oportunidad de recordar el valor de desenfoque de la sombra para el posterior redibujado del objeto de sombra con los parámetros con los que se ha dibujado originalmente:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw) { //--- Set the shadow shift values to the variables by X and Y axes this.SetCoordXRelative(shift_x); this.SetCoordYRelative(shift_y); //--- Calculate the height and width of the drawn rectangle int w=this.Width()-OUTER_AREA_SIZE*2; int h=this.Height()-OUTER_AREA_SIZE*2; //--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); //--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value); //--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(this.m_blur)) return; //--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw); CGCnvElement::Update(redraw); } //+------------------------------------------------------------------+
Como implementaremos un poco más el objeto básico de todos los objetos de la biblioteca WinForms, algunas de las variables y métodos ya existentes en las clases escritas de los objetos WinForms y sus clases principales deberán transferirse a la nueva clase básica o a su clase principal, la clase CForm. Necesitemos que sea así para que todas estas variables y métodos estén disponibles en los niveles de aquellas clases de la jerarquía de herencia donde sean necesarios.
Por ejemplo, las coordenadas y dimensiones de un objeto durante su creación se almacenarán en variables en la clase CPanel. Sin embargo, necesitaremos los mismos datos en otros objetos WinForms. Por ello, los transferiremos a la clase padre de los objetos WinForms en el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Parecería lógico transferir estas variables y métodos a la clase básica de los objetos WinForms, pero, como es posible que necesitemos estos datos para los objetos de formulario cuya clase será padre de la clase de objeto básico de todos los objetos WinForms, transferiremos estas variables a ella:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor int m_init_x; // Newly created form X coordinate int m_init_y; // Newly created form Y coordinate int m_init_w; // Newly created form width int m_init_h; // Newly created form height //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Create a new graphical object
Para optimizar el código, el método CreateNewElement() se dividirá en dos: crearemos otro método CreateAndAddNewElement() donde se creará un nuevo elemento y se añadirá a la lista. Vamos a declarar este método en la sección protegida de la clasey transferir los métodos de la clase CPanel a la sección pública para trabajar con las variables que almacenan las coordenadas y tamaños iniciales del objeto:
protected: CArrayObj m_list_tmp; // List for storing the pointers int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom //--- Initialize the variables void Initialize(void); void Deinitialize(void); //--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Update coordinates of bound objects virtual bool MoveDependentObj(const int x,const int y,const bool redraw=false); //--- Create a new bound element and add it to the list of bound objects CGCnvElement *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity); public: //--- Return the initial (1) X and (2) Y coordinate, (3) form width and (4) height int GetCoordXInit(void) const { return this.m_init_x; } int GetCoordYInit(void) const { return this.m_init_y; } int GetWidthInit(void) const { return this.m_init_w; } int GetHeightInit(void) const { return this.m_init_h; } //--- Set the initial (1) X and (2) Y coordinate, (3) form width and (4) height void SetCoordXInit(const int value) { this.m_init_x=value; } void SetCoordYInit(const int value) { this.m_init_y=value; } void SetWidthInit(const int value) { this.m_init_w=value; } void SetHeightInit(const int value) { this.m_init_h=value; } //--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam); int MouseCursorX(void) const { return this.m_mouse.CoordX(); } int MouseCursorY(void) const { return this.m_mouse.CoordY(); }
Haremos que el método CreateNewElement() sea virtual y añadiremos una bandera que indique la necesidad de dibujar el objeto recién creado:
//--- Create a new attached element virtual bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y);
Vamos a hacer el método virtual para que en cada clase heredada podamos hacer exactamente el mismo método, pero con su propia implementación. La bandera que indica la necesidad de redibujar el objeto es necesaria para que, al crear muchos objetos al mismo tiempo, no se muestren uno por uno inmediatamente después de su creación, sino que primero se creen todos los objetos y luego se ajusten las dimensiones del panel en el que se han creado, y solo entonces se dibuje todo al mismo tiempo. Visualmente será más rápido y no habrá artefactos visuales con la representación constante de un panel redimensionable al crear cada objeto posterior partiendo de todos los objetos adjuntos creados simultáneamente en un ciclo.
En el bloque de los métodos para el acceso simplificado a las propiedades de los objetos, declararemos dos métodos para trabajar con la propiedad "Desenfoque de la sombra":
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) get the form frame color void SetColorFrame(const color colour) { this.m_color_frame=colour; } color ColorFrame(void) const { return this.m_color_frame; } //--- (1) Set and (2) return the form shadow color void SetColorShadow(const color colour); color ColorShadow(void) const; //--- (1) Set and (2) return the form shadow opacity void SetOpacityShadow(const uchar opacity); uchar OpacityShadow(void) const; //--- (1) Set and (2) return the form shadow blur void SetBlurShadow(const uchar blur); uchar BlurShadow(void) const; }; //+------------------------------------------------------------------+
En todos los constructores paramétricos, después de llamar al método de inicialización de propiedades, escribiremos las llamadas a los métodos de inicialización de las propiedades que almacenan las dimensiones y coordenadas iniciales del objeto:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CForm::CForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CForm::CForm(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CForm::CForm(const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
En el método de inicialización en sí, introduciremos ceros en estos valores:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CForm::Initialize(void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE; this.m_gradient_v=true; this.m_gradient_c=false; this.m_mouse_state_flags=0; this.m_offset_x=0; this.m_offset_y=0; this.m_init_x=0; this.m_init_y=0; this.m_init_w=0; this.m_init_h=0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(m_animations); } //+------------------------------------------------------------------+
Método que crea un nuevo elemento adjunto y lo añade a la lista de objetos adjuntos:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, 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; } //--- Specify the element index in the list int num=this.m_list_elements.Total(); //--- Create a graphical element name string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num); string name="Elm"+ns; //--- Get the screen coordinates of the object relative to the coordinate system of the base object int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return NULL; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return NULL; } //--- Set the minimum properties for a bound graphical element obj.SetColorBackground(colour); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(main); obj.SetBase(this.GetObject()); obj.SetID(this.ID()); obj.SetNumber(num); obj.SetCoordXRelative(x); obj.SetCoordYRelative(y); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(x); obj.SetCoordYRelativeInit(y); return obj; } //+------------------------------------------------------------------+
En esencia, la lógica del método repite la lógica del método CreateNewElement(), pero no hay representación incondicional del elemento creado. El método simplemente retorna el puntero a un elemento gráfico creado con éxito, o NULL en caso de error al crearlo o añadirlo a la lista. Además, aquí hemos solucionado un pequeño error: en el objeto se establece su número en la lista de objetos adjuntos, lo cual no era el caso anteriormente, y el número del objeto en la lista, respectivamente, siempre era cero.
El método que crea un nuevo elemento adjuntoahora se ve distinto: el código completo para crear un nuevo objeto y añadirlo a la lista ha sido desplazado al método anterior, que se llama aquí. Si el objeto no se crea o añade a la lista, el método retornará false, de lo contrario, el objeto se dibujará con la bandera que indica la necesidad de su dibujado físico y se retornará true.
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- Create a new graphical element CGCnvElement *obj=this.CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); //--- If the object has been created, draw the added object and return 'true' if(obj==NULL) return false; obj.Erase(colour,opacity,redraw); return true; } //+------------------------------------------------------------------+
En el método que dibuja la sombra, ahora necesitamos transmitir la bandera para dibujar la sombra en el método Draw() de la clase de objeto de sombra:
//+------------------------------------------------------------------+ //| Draw the shadow | //+------------------------------------------------------------------+ void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR) { //--- If the shadow flag is disabled, exit if(!this.m_shadow) return; //--- If there is no shadow object, create it if(this.m_shadow_obj==NULL) this.CreateShadowObj(colour,opacity); //--- If the shadow object exists, draw the shadow on it, //--- set the shadow object visibility flag and //--- move the form object to the foreground if(this.m_shadow_obj!=NULL) { this.m_shadow_obj.Draw(shift_x,shift_y,blur,true); this.m_shadow_obj.SetVisible(true,false); this.BringToTop(); } } //+------------------------------------------------------------------+
Aquí, siempre transmitiremos true para dibujar la sombra. Si no es necesario dibujar la sombra, un operador condicional comprobará esto en los métodos de llamada. Esto resulta más simple que controlar el dibujado de un rectángulo de sombra sin desenfoque, monitorear si está dibujado o no antes de usar el desenfoque de este rectángulo y otras trampas relacionadas. Es más fácil comprobar si es necesario dibujar la sombra y llamar a este método.
Método que establece el desenfoque de la sombra del formulario:
//+------------------------------------------------------------------+ //| Set the form shadow blur | //+------------------------------------------------------------------+ void CForm::SetBlurShadow(const uchar blur) { if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT)); return; } this.m_shadow_obj.SetBlur(blur); } //+------------------------------------------------------------------+
Si el objeto de sombra no existe, mostraremos un mensaje que indique que primero se debe crear el objeto de sombra; de lo contrario, se llamará al método para establecer el valor de desenfoque del objeto de sombra.
Método que retorna el desenfoque de la sombra del formulario:
//+------------------------------------------------------------------+ //| Return the form shadow blur | //+------------------------------------------------------------------+ uchar CForm::BlurShadow(void) const { if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT)); return 0; } return this.m_shadow_obj.Blur(); } //+------------------------------------------------------------------+
Si el objeto de sombra no existe, mostraremos un mensaje que indique que el objeto de sombra debe crearse primero; de lo contrario, retornaremos el valor de desenfoque del objeto de sombra.
Además de los cambios descritos anteriormente, hemos realizado mejoras menores en la clase que no afectan a su lógica, por lo que no las describiremos aquí. El lector podrá encontrar todos los cambios en los archivos adjuntos al artículo.
Clase del objeto básico WinForms
En la carpeta de la biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, creamos un nuevo archivo WinFormBase.mqh de la clase CWinFormBase. La clase se deberá heredar de la clase del objeto de formulario CForm. Para que la clase básica sea visible en este archivo, le añadiremos el archivo de clase del objeto de formulario:
//+------------------------------------------------------------------+ //| WinFormBase.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Form.mqh" //+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { }
Vamos a transferir a esta clase las variables y métodos para trabajar con ellos desde el archivo de clase del objeto WinForms de "Panel" desde el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh. En principio, aquí se encontrarán todas las variables y métodos que hemos escrito para el objeto de panel. Y, claro está, añadiremos otros nuevos.
Vamos a colocar las siguientes variables en la sección protegida de la clase:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { protected: color m_fore_color; // Default text color for all control objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Control frame style bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding control borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls private:
En la sección privada, colocamos el método que retorna las banderas de fuente:
private: //--- Return the font flags uint GetFontFlags(void); public:
y en la sección pública, declararemos los métodos virtuales para borrar el elemento, su redibujado y el cambio de tamaño, así como los constructores de clase, y trasladaremos los siguientes métodos del archivo de clase de CPanel:
public: //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Redraw the object virtual void Redraw(bool redraw); //--- Set the new size for the (1) current object and (2) the object specified by index virtual bool Resize(const int w,const int h,const bool redraw); virtual bool Resize(const int index,const int w,const int h,const bool redraw); //--- Constructors CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { this.m_type=OBJECT_DE_TYPE_GWF_BASE; } //--- (1) Set and (2) return the default text color of all panel objects void SetForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the Bold font flag void SetBold(const bool flag); bool Bold(void); //--- (1) Set and (2) return the Italic font flag void SetItalic(const bool flag); bool Italic(void); //--- (1) Set and (2) return the Strikeout font flag void SetStrikeout(const bool flag); bool Strikeout(void); //--- (1) Set and (2) return the Underline font flag void SetUnderline(const bool flag); bool Underline(void); //--- (1) Set and (2) return the font style void SetFontDrawStyle(ENUM_FONT_STYLE style); ENUM_FONT_STYLE FontDrawStyle(void); //--- (1) Set and (2) return the font width type void SetFontBoldType(ENUM_FW_TYPE type); ENUM_FW_TYPE FontBoldType(void) const { return this.m_bold_type; } //--- (1) Set and (2) return the frame style void SetBorderStyle(const ENUM_FRAME_STYLE style) { this.m_border_style=style; } ENUM_FRAME_STYLE BorderStyle(void) const { return this.m_border_style; } //--- (1) Set and (2) return the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { this.m_autosize=flag; } bool AutoSize(void) { return this.m_autosize; } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { this.m_dock_mode=mode; } ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void) const { return this.m_dock_mode; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control void SetMarginLeft(const int value) { this.m_margin[0]=value; } void SetMarginTop(const int value) { this.m_margin[1]=value; } void SetMarginRight(const int value) { this.m_margin[2]=value; } void SetMarginBottom(const int value) { this.m_margin[3]=value; } void SetMarginAll(const int value) { this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control int MarginLeft(void) const { return this.m_margin[0]; } int MarginTop(void) const { return this.m_margin[1]; } int MarginRight(void) const { return this.m_margin[2]; } int MarginBottom(void) const { return this.m_margin[3]; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control virtual void SetPaddingLeft(const uint value) { this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value); } virtual void SetPaddingTop(const uint value) { this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value); } virtual void SetPaddingRight(const uint value) { this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value); } virtual void SetPaddingBottom(const uint value) { this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value); } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } //--- Set the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value;} virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- Return the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom int FrameWidthLeft(void) const { return this.m_frame_width_left; } int FrameWidthTop(void) const { return this.m_frame_width_top; } int FrameWidthRight(void) const { return this.m_frame_width_right; } int FrameWidthBottom(void) const { return this.m_frame_width_bottom; } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } }; //+------------------------------------------------------------------+
Haremos que la mayoría de los métodos trasladados sean virtuales, para que podamos anularlos en las clases heredadas, si fuera necesario. Todos los métodos para establecer las propiedades ahora tienen el prefijo "Set" en sus nombres. Esto indica inequívocamente la propiedad del método.
En el constructor paramétrico, establecemos el tipo de elemento gráfico y el objeto de la biblioteca, además de los valores de propiedad por defecto, o bien los valores que se transmiten en los parámetros:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.m_fore_color=CLR_DEF_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.SetMarginAll(0); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_frame_width_right=0; this.m_frame_width_left=0; this.m_frame_width_top=0; this.m_frame_width_bottom=0; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
Métodos trasladados desde la clase CPanel:
//+------------------------------------------------------------------+ //| Return the font flags | //+------------------------------------------------------------------+ uint CWinFormBase::GetFontFlags(void) { string name; int size; uint flags; uint angle; CGCnvElement::GetFont(name,size,flags,angle); return flags; } //+------------------------------------------------------------------+ //| Set the Bold font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetBold(const bool flag) { uint flags=this.GetFontFlags(); if(flag) { this.m_bold_type=FW_TYPE_BOLD; CGCnvElement::SetFontFlags(flags | FW_BOLD); } else this.m_bold_type=FW_TYPE_NORMAL; } //+------------------------------------------------------------------+ //| Return the Bold font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Bold(void) { uint flags=this.GetFontFlags(); return(flags &FW_BOLD)==FW_BOLD; } //+------------------------------------------------------------------+ //| Set the Italic font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetItalic(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_ITALIC); } //+------------------------------------------------------------------+ //| Return the Italic font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Italic(void) { uint flags=this.GetFontFlags(); return(flags &FONT_ITALIC)==FONT_ITALIC; } //+------------------------------------------------------------------+ //| Set the Strikeout font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetStrikeout(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT); } //+------------------------------------------------------------------+ //| Return the Strikeout font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Strikeout(void) { uint flags=this.GetFontFlags(); return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT; } //+------------------------------------------------------------------+ //| Set the Underline font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetUnderline(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE); } //+------------------------------------------------------------------+ //| Return the Underline font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Underline(void) { uint flags=this.GetFontFlags(); return(flags &FONT_UNDERLINE)==FONT_UNDERLINE; } //+------------------------------------------------------------------+ //| Set the font style | //+------------------------------------------------------------------+ void CWinFormBase::SetFontDrawStyle(ENUM_FONT_STYLE style) { switch(style) { case FONT_STYLE_ITALIC : this.SetItalic(true); break; case FONT_STYLE_UNDERLINE : this.SetUnderline(true); break; case FONT_STYLE_STRIKEOUT : this.SetStrikeout(true); break; default: break; } } //+------------------------------------------------------------------+ //| Return the font style | //+------------------------------------------------------------------+ ENUM_FONT_STYLE CWinFormBase::FontDrawStyle(void) { return ( this.Italic() ? FONT_STYLE_ITALIC : this.Underline() ? FONT_STYLE_UNDERLINE : this.Strikeout() ? FONT_STYLE_UNDERLINE : FONT_STYLE_NORMAL ); } //+------------------------------------------------------------------+ //| Set the font width type | //+------------------------------------------------------------------+ void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type) { this.m_bold_type=type; uint flags=this.GetFontFlags(); switch(type) { case FW_TYPE_DONTCARE : CGCnvElement::SetFontFlags(flags | FW_DONTCARE); break; case FW_TYPE_THIN : CGCnvElement::SetFontFlags(flags | FW_THIN); break; case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT); break; case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT); break; case FW_TYPE_LIGHT : CGCnvElement::SetFontFlags(flags | FW_LIGHT); break; case FW_TYPE_REGULAR : CGCnvElement::SetFontFlags(flags | FW_REGULAR); break; case FW_TYPE_MEDIUM : CGCnvElement::SetFontFlags(flags | FW_MEDIUM); break; case FW_TYPE_SEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD); break; case FW_TYPE_DEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD); break; case FW_TYPE_BOLD : CGCnvElement::SetFontFlags(flags | FW_BOLD); break; case FW_TYPE_EXTRABOLD : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD); break; case FW_TYPE_ULTRABOLD : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD); break; case FW_TYPE_HEAVY : CGCnvElement::SetFontFlags(flags | FW_HEAVY); break; case FW_TYPE_BLACK : CGCnvElement::SetFontFlags(flags | FW_BLACK); break; default : CGCnvElement::SetFontFlags(flags | FW_NORMAL); break; } } //+------------------------------------------------------------------+
Ya hemos analizado todos estos métodos en artículos anteriores acerca de la creación del objeto de panel.
Métodos virtuales para limpiar (colorear) un elemento:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const bool redraw=false) { //--- Fully clear the element with the redrawing flag CGCnvElement::Erase(redraw); } //+------------------------------------------------------------------+
Estos métodos virtuales anulan los mismos métodos de la clase principal. Aquí, primero, llamamos al método de la clase principal para rellenar el elemento con color, y luego verificamos la bandera que indica la presencia de un marco en el objeto y, si el marco debe estar presente y se ha establecido la bandera para redibujar el objeto, se dibujará el marco. A continuación, actualizamos el objeto.
Método que establece las nuevas dimensiones del objeto actual:
//+------------------------------------------------------------------+ //| Set the new size for the current object | //+------------------------------------------------------------------+ bool CWinFormBase::Resize(const int w,const int h,const bool redraw) { //--- If the object width and height are equal to the passed ones, return 'true' if(this.Width()==w && this.Height()==h) return true; //--- Declare the variable with the property change result bool res=true; //--- Save the panel initial size int prev_w=this.Width(); int prev_h=this.Height(); //--- Set the property change result to the 'res' variable //--- (if the property value is not equal to the passed value) if(this.Width()!=w) res &=this.SetWidth(w); if(this.Height()!=h) res &=this.SetHeight(h); if(!res) return false; //--- Calculate the value, by which the size should be changed int excess_w=this.Width()-prev_w; int excess_h=this.Height()-prev_h; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object has been received, if(this.IsShadow() && shadow!=NULL) { //--- save shadow shifts by X and Y, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- set the shadow new width and height res &=shadow.SetWidth(shadow.Width()+excess_w); res &=shadow.SetHeight(shadow.Height()+excess_h); if(!res) return false; //--- If there is no need to redraw, remove the shadow if(!redraw) shadow.Erase(); //--- Save the previously set shadow shift values relative to the panel shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- Redraw the element with new size if(redraw) this.Redraw(true); //--- All is successful - return 'true' return true; } //+------------------------------------------------------------------+
La lógica del método se explica con detalle en los comentarios al código. En resumen: si las dimensiones existentes del objeto son transmitidas al método, no será necesario cambiar nada; retornaremos directamente true. Si las dimensiones transmitidas al método no coinciden con las que tiene actualmente el objeto, entonces las cambiaremos. Si hay una sombra, también modificaremos su tamaño y luego redibujaremos el objeto completo si el indicador de redibujado ha sido establecido. Es decir, si el indicador de redibujado no ha sido establecido, el tamaño del objeto cambiará, pero no se redibujará. Esto será necesario para acelerar el redibujado de muchos objetos adjuntos a otros. Solo después de cambiar el tamaño de todos los objetos vinculados, estos deberán redibujarse.
Método que establece las nuevas dimensiones para el objeto especificado según el índice:
//+------------------------------------------------------------------+ //| Set the new size for the object specified by object index | //+------------------------------------------------------------------+ bool CWinFormBase::Resize(const int index,const int w,const int h,const bool redraw) { CWinFormBase *obj=this.GetElement(index); return(obj!=NULL ? obj.Resize(w,h,redraw) : false); } //+------------------------------------------------------------------+
Al método se le transmite el índice del objeto en la lista de objetos vinculados cuyas dimensiones se deben cambiar, que también se pasan al método.
Obtenemos el objeto según índice en la lista y retornamos el resultado de la llamada a su método Resize(), analizado anteriormente.
Método que redibuja un objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CWinFormBase::Redraw(bool redraw) { //--- If the object type is less than the "Base WinForms object", exit if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE) return; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object exists, redraw it if(this.IsShadow() && shadow!=NULL) { //--- remove the previously drawn shadow, shadow.Erase(); //--- save the relative shadow coordinates, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- redraw the shadow, if(redraw) shadow.Draw(0,0,shadow.Blur(),redraw); //--- restore relative shadow coordinates shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- If the redraw flag is set, if(redraw) { //--- completely redraw the object and save its new initial look this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw); this.Done(); } //--- otherwise, remove the object else this.Erase(); //--- Redraw all bound objects with the redraw flag for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; element.Redraw(redraw); } //--- If the redraw flag is set and if this is the main object the rest are bound to, //--- redraw the chart to display changes immediately if(redraw && this.GetMain()==NULL) ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
La lógica del método es similar a la lógica del método que cambia el tamaño de un objeto. Primero, redibujamos la sombra del objeto, ya que debe encontrarse más abajo que el resto; luego, con el indicador de redibujado establecido, redibujamos completamente el objeto y establecemos su apariencia actual como "referencia". Si el indicador de redibujado no está configurado, borraremos el objeto de sombra dibujado y el objeto en sí. A continuación, recorreremos la lista de objetos adjuntos y redibujaremos cada uno de ellos según el indicador de redibujado: borraremos todo o dibujaremos todo.
Al final, actualizaremos el gráfico para mostrar directamente los cambios realizados si establecemos el indicador de redibujado y si este objeto es el objeto principal en toda la jerarquía de objetos vinculados (no tiene un objeto principal y su método GetMain() retornará NULL).
La clase básica de todos los objetos de la biblioteca WinForms está lista.
Vamos a mejorar la clase del objeto de panel en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.
Al establecer la propiedad Dock para los objetos vinculados a un panel, estos objetos deberán colocarse automáticamente en el orden de sus números en la lista de objetos vinculados y según las reglas establecidas para el tipo de vinculación. Es decir, si el Dock está a la izquierda, el objeto se estirará a lo alto hasta alcanzar la altura total del panel al que está vinculado, y su coordenada izquierda deberá ser el lado izquierdo del sustrato del panel (si el objeto se encuentra el primero en la lista), o el lado derecho del objeto anterior de la lista de objetos vinculados. Y así para cada objeto de la lista. Al mismo tiempo, el indicador de cambio de tamaño automático del panel en sí y el modo de cambio de tamaño (solo aumentar o aumentar y disminuir) también serán importantes.
Hoy implementaremos la configuración de la propiedad Dock para objetos vinculados solo con el modo de tamaño automático del panel deshabilitado. Para determinar al borde de qué objeto se adjuntará el objeto actual de la lista de objetos adjuntos, crearemos cuatro objetos (según el número de bordes): superior, inferior, izquierda y derecha. Cada objeto en el que se establezca la propiedad Dock encajará en uno de estos objetos, de forma que el resto de los objetos de la lista puedan saber a las coordenadas de qué objeto fijarse (porque están unidos a los bordes del objeto anterior en la lista, si la propiedad Dock también está configurada para él).
Desde ahora, todos los objetos WimForms se heredarán del objeto básico WinForms, en lugar del archivo de objeto de formulario incluido:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Form.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm
conectamos el archivo del objeto básico WinForms:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\WForms\WinFormBase.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+
En la sección privada de la clase, declararemos los punteros a los cuatro objetos en los que almacenaremos los punteros a los objetos a cuyas coordenadas necesitaremos adjuntar los objetos Dock:
class CPanel : public CWinFormBase { private: CGCnvElement *m_obj_top; // Pointer to the object whose coordinates the current upper object is bound to CGCnvElement *m_obj_bottom; // Pointer to the object whose coordinates the current bottom object is bound to CGCnvElement *m_obj_left; // Pointer to the object whose coordinates the current left object is bound to CGCnvElement *m_obj_right; // Pointer to the object whose coordinates the current right object is bound to CGCnvElement *m_underlay; // Underlay for placing elements bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content //--- 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); //--- Return the initial coordinates of a bound object virtual void GetCoords(int &x,int &y); //--- Create the underlay object bool CreateUnderlayObj(void); //--- Set the underlay as a coordinate system zero void SetUnderlayAsBase(void); protected:
En lugar del método SetDockingToContainer(), declararemos el método SetUnderlayAsBase(), que asignará el objeto de sustrato a los cuatro punteros declarados anteriormente. Cuando creamos el objeto por primera vez, el origen de las coordenadas de vinculación del primer objeto vinculado será el sustrato del objeto creado. A medida que se ubiquen los objetos Dock, asignaremos a las variables los punteros a los objetos correspondientes ya adjuntos a los bordes correspondientes. Pero al principio, el objeto vinculado será el sustrato.
En la sección protegida de la clase, declararemos dos métodos que retornan el número máximo en el que todos los objetos Dock sobrepasan el objetoal que están adjuntos; asimismo, declararemos un método que cambiará automáticamente el tamaño del contenedor para que se ajuste a los objetos ubicados en él, y escribiremos cuatro métodos que retornan los punteros a los objetos a cuyos bordes deberá adjuntarse el siguiente objeto Dock de la lista de objetos adjuntos al contenedor:
protected: //--- Return the maximum value of Dock object borders going beyond the container by width and (2) height int GetExcessMaxX(void); int GetExcessMaxY(void); //--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters bool SetCoordXUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false); } bool SetCoordYUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false); } bool SetWidthUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value) : false); } bool SetHeightUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false); } bool SetUnderlayParams(void); //--- Return the underlay (1) X, (2) Y coordinate, (3) width and (4) height int GetCoordXUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordX() : 0); } int GetCoordYUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordY() : 0); } int GetWidthUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.Width() : 0); } int GetHeightUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.Height() : 0); } //--- Return the underlay (1) X and (2) Y coordinate relative to the panel int GetCoordXUnderlayRelative(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordXRelative() : 0); } int GetCoordYUnderlayRelative(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordYRelative() : 0); } //--- Return the object whose coordinates the current (1) top, (2) bottom, (3) left or (4) right object is bound to CGCnvElement *GetTopObj(void) { return this.m_obj_top; } CGCnvElement *GetBottomObj(void) { return this.m_obj_bottom; } CGCnvElement *GetLeftObj(void) { return this.m_obj_left; } CGCnvElement *GetRightObj(void) { return this.m_obj_right; } //--- Adjust the element size to fit its content bool AutoSizeProcess(const bool redraw); public:
Todas las demás variables y métodos de aquí ya se han transferido a la clase básica del objeto WinForms: podrá encontrar más detalles sobre dichos cambios en los archivos adjuntos al artículo.
La sección pública de la clase, teniendo en cuenta todos los métodos transferidos, las correcciones realizadas y los nuevos métodos añadidos, mostrará el aspecto siguiente:
public: //--- Return the (1) underlay and (2) the object the current object is bound to CGCnvElement *GetUnderlay(void) { return this.m_underlay; } //--- Update the coordinates (shift the canvas) virtual bool Move(const int x,const int y,const bool redraw=false); //--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); //--- Create a new attached element virtual bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Redraw the object virtual void Redraw(bool redraw); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); } //--- Reset the size of all bound objects to the initial ones bool ResetSizeAllToInit(void); //--- Place bound objects in the order of their Dock binding bool ArrangeObjects(const bool redraw); //--- (1) Set and (2) return the auto scrollbar flag void SetAutoScroll(const bool flag) { this.m_autoscroll=flag; } bool AutoScroll(void) { return this.m_autoscroll; } //--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling void SetAutoScrollMarginWidth(const int value) { this.m_autoscroll_margin[0]=value; } void SetAutoScrollMarginHeight(const int value) { this.m_autoscroll_margin[1]=value; } void SetAutoScrollMarginAll(const int value) { this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value); } //--- Return the (1) field width and (2) height around the control during auto scrolling int AutoScrollMarginWidth(void) const { return this.m_autoscroll_margin[0]; } int AutoScrollMarginHeight(void) const { return this.m_autoscroll_margin[1]; } //--- (1) Set the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } //--- (1) Set and (2) return the mode of the element auto resizing depending on the content void SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw) { ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode(); if(prev==mode) return; this.m_autosize_mode=mode; if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return this.m_autosize_mode; } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { if(this.DockMode()==mode) return; CWinFormBase::SetDockMode(mode,redraw); CPanel *base=this.GetBase(); if(base!=NULL) base.ArrangeObjects(redraw); } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control virtual void SetPaddingLeft(const uint value) { CWinFormBase::SetPaddingLeft(value); if(this.m_underlay!=NULL) { //--- Set the underlay shift along the X axis this.m_underlay.SetCoordXRelative(this.PaddingLeft()); //--- Set the X coordinate and the underlay width this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } virtual void SetPaddingTop(const uint value) { CWinFormBase::SetPaddingTop(value); if(this.m_underlay!=NULL) { //--- Set the underlay shift along the Y axis this.m_underlay.SetCoordYRelative(this.PaddingTop()); //--- Set the Y coordinate and underlay height this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } virtual void SetPaddingRight(const uint value) { CWinFormBase::SetPaddingRight(value); if(this.m_underlay!=NULL) { //--- Set the underlay width this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } virtual void SetPaddingBottom(const uint value) { CWinFormBase::SetPaddingBottom(value); if(this.m_underlay!=NULL) { //--- Set the underlay height this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } //--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; if(this.PaddingLeft()<this.FrameWidthLeft()) this.SetPaddingLeft(this.FrameWidthLeft()); } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; if(this.PaddingTop()<this.FrameWidthTop()) this.SetPaddingTop(this.FrameWidthTop()); } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; if(this.PaddingRight()<this.FrameWidthRight()) this.SetPaddingRight(this.FrameWidthRight()); } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value; if(this.PaddingBottom()<this.FrameWidthBottom()) this.SetPaddingBottom(this.FrameWidthBottom()); } virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_BEVEL); this.SetAutoScroll(false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+
Todos los métodos Set ahora tienen el prefijo "Set" en sus nombres.
Los métodos para establecer el tamaño automático, el modo de tamaño automático y los métodos de vinculación de los objetos Dock al contenedor, primero llaman al método de clase básica para establecer la propiedady luego llaman al método de cambio de tamaño del contenedor:
virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); }
En este caso, el cambio de tamaño del contenedor deberá efectuarse solo si al menos un objeto está adjunto al contenedor.
Los otros métodos similares se han implementado con una lógica similar, y el lector podrá analizarlos por su cuenta.
Cualquier duda al respecto podrá formularse en la descripción del artículo.
En el método que establece todos los parámetros del sustrato, indicamos el tipo de objeto "sustrato":
//+------------------------------------------------------------------+ //| Set all underlay parameters | //+------------------------------------------------------------------+ bool CPanel::SetUnderlayParams(void) { //--- Set the object type this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY); //--- Set the underlay shift values to the variables by X and Y axes this.m_underlay.SetCoordXRelative(this.PaddingLeft()); this.m_underlay.SetCoordYRelative(this.PaddingTop()); //--- Set the underlay coordinates and size bool res=true; res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); return res; } //+------------------------------------------------------------------+
Debemos tener conocimiento de que el objeto seleccionado es del tipo "sustrato" al colocar los objetos Dock dentro del contenedor. Si el objeto a cuyos bordes deseamos vincular el objeto actual de la lista de objetos vinculados es un sustrato, entonces la coordenada X del sustrato deberá considerarse como el borde para la vinculación, por ejemplo, a la izquierda, mientras que si no es un sustrato, sino otro objeto Dock, el borde de vinculación será el lado derecho de ese otro objeto Dock.
En los métodos que establecen las coordenadas y dimensiones del panel, primero llamamos al método parta establecer las propiedades del objeto básicoy luego cambiamos las coordenadas y dimensiones delobjeto de sustrato:
//+------------------------------------------------------------------+ //| Set the panel X coordinate | //+------------------------------------------------------------------+ bool CPanel::SetCoordX(const int coord_x) { if(!CGCnvElement::SetCoordX(coord_x)) return false; return(this.m_underlay!=NULL ? this.SetCoordXUnderlay(coord_x+this.PaddingLeft()) : true); } //+------------------------------------------------------------------+ //| Set the panel Y coordinate | //+------------------------------------------------------------------+ bool CPanel::SetCoordY(const int coord_y) { if(!CGCnvElement::SetCoordY(coord_y)) return false; return(this.m_underlay!=NULL ? this.SetCoordYUnderlay(coord_y+this.PaddingTop()) : true); } //+------------------------------------------------------------------+ //| Set the panel width | //+------------------------------------------------------------------+ bool CPanel::SetWidth(const int width) { if(!CGCnvElement::SetWidth(width)) return false; return(this.m_underlay!=NULL ? this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()) : true); } //+------------------------------------------------------------------+ //| Set the panel height | //+------------------------------------------------------------------+ bool CPanel::SetHeight(const int height) { if(!CGCnvElement::SetHeight(height)) return false; return(this.m_underlay!=NULL ? this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()) : true); } //+------------------------------------------------------------------+
Método que restablece el tamaño de todos los objetos adjuntos a sus tamaños originales:
//+------------------------------------------------------------------+ //| Reset the size of all bound objects to the initial ones | //+------------------------------------------------------------------+ bool CPanel::ResetSizeAllToInit(void) { bool res=true; for(int i=0;i<this.ElementsTotal();i++) { CPanel *obj=this.GetElement(i); if(obj==NULL) { res &=false; continue; } res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit()); } return res; } //+------------------------------------------------------------------+
En un ciclo por todos los objetos vinculados al contenedor, obtenemos el siguiente objeto y escribimos en la variable res el resultado del cambio de tamaño del objeto a su tamaño original. Al final del ciclo, la variable res guardará el resultado general del cambio de tamaño. Si hemos cambiado el tamaño de al menos un objeto con un error, la variable res se establecerá en false, de lo contrario, true.
Método que crea un nuevo elemento vinculado:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CPanel::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, 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 failed to create a new graphical element, return 'false' CGCnvElement *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- If the object type is a base WinForms object and higher, if(obj.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { //--- declare the pointer with the base WinForms object type and assign the pointer to the newly created object to it, //--- set the frame color equal to the background color CWinFormBase *wf=obj; wf.SetColorFrame(wf.ColorBackground()); } //--- 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; } //+------------------------------------------------------------------+
La lógica del método se describe en los comentarios al código, y resulta bastante simple.
Método que retorna el valor máximo de la anchura de los bordes de los objetos Dock que se extienden más allá del contenedor:
//+------------------------------------------------------------------+ //| Return the maximum value of Dock object borders | //| going beyond the container by width | //+------------------------------------------------------------------+ int CPanel::GetExcessMaxX(void) { int value=0; for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(element.RightEdge()>value) value=element.RightEdge(); } return value-this.m_underlay.RightEdge(); } //+------------------------------------------------------------------+
Método que retorna el valor máximo de la altura de los bordes del objeto Dock que se extienden más allá del contenedor:
//+------------------------------------------------------------------+ //| Return the maximum value of Dock object borders | //| going beyond the container by height | //+------------------------------------------------------------------+ int CPanel::GetExcessMaxY(void) { int value=0; for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(element.BottomEdge()>value) value=element.BottomEdge(); } return value-this.m_underlay.BottomEdge(); } //+------------------------------------------------------------------+
Ambos métodos tienen una lógica idéntica: en un ciclo por todos los objetos vinculados al contenedor, obtenemos el siguiente objeto y, si su borde derecho (inferior) es mayor que el valor escrito en la variable value, a esta variable se le asignará el valor del borde. Al final del ciclo, el valor máximo del borde derecho (inferior) de todos los objetos se escribirá en la variable value y se retornará la diferencia entre el valor encontrado y el borde derecho (inferior) del objeto de sustrato. Así, obtendremos un valor positivo o negativo en píxeles, por lo que deberemos modificar el tamaño del contenedor.
Método que organiza los objetos vinculados en el orden de su vinculación Dock:
//+------------------------------------------------------------------+ //| Place bound objects in the order of their Dock binding | //+------------------------------------------------------------------+ bool CPanel::ArrangeObjects(const bool redraw) { CWinFormBase *prev=NULL, *obj=NULL; //--- If auto resizing mode is enabled if(this.AutoSize()) { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- Get the previous element from the list prev=this.GetElement(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- Get the current element obj=GetElement(i); //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL) continue; //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { } } this.Resize(this.GetWidthInit(),this.GetHeightInit(),false); } //--- If auto resizing mode disabled else { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- Get the current and previous elements from the list obj=this.GetElement(i); prev=this.GetElement(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the top whose edges are used to bind the current one CGCnvElement *coord_base=this.GetTopObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the top one whose edges will be used to bind the next one this.m_obj_top=obj; } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the bottom whose edges are used to bind the current one CGCnvElement *coord_base=this.GetBottomObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the bottom one whose edges will be used to bind the next one this.m_obj_bottom=obj; } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the left whose edges are used to bind the current one CGCnvElement *coord_base=this.GetLeftObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the left one whose edges will be used to bind the next one this.m_obj_left=obj; } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the right whose edges are used to bind the current one CGCnvElement *coord_base=this.GetRightObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the right one whose edges will be used to bind the next one this.m_obj_right=obj; } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { //--- If failed to change the object size (for the entire underlay width and height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false)) continue; //--- Set the underlay as a binding object this.SetUnderlayAsBase(); //--- Get the object binding coordinates x=this.GetLeftObj().CoordX(); y=this.GetTopObj().CoordY(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { //--- Reset the object size obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false); //--- Get the initial object location coordinates x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit(); y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Calculate and set the relative object coordinates obj.SetCoordXRelative(x-this.m_underlay.CoordX()); obj.SetCoordYRelative(y-this.m_underlay.CoordY()); } } //--- Redraw the object with the redraw flag and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
La lógica del método se explica con detalle en los comentarios al código. Para los paneles con el indicador de cambio de tamaño automático establecido, aún no hemos organizado los objetos dentro del contenedor (aquí solo hay stubs donde escribiremos los manejadores más adelante), ya que habrá una lógica diferente a la implementada para el panel sin cambio de tamaño automático.
Método que establece el sustrato en el origen de las coordenadas de los objetos Dock vinculados:
//+------------------------------------------------------------------+ //| Set the underlay as a coordinate system zero | //+------------------------------------------------------------------+ void CPanel::SetUnderlayAsBase(void) { this.m_obj_left=this.m_underlay; this.m_obj_right=this.m_underlay; this.m_obj_top=this.m_underlay; this.m_obj_bottom=this.m_underlay; } //+------------------------------------------------------------------+
Aquí, simplemente asignaremos a los cuatro objetos vinculados el puntero al sustrato.
Método que cambia el tamaño de un elemento para que se ajuste a su contenido interno:
//+------------------------------------------------------------------+ //| Adjust the element size to fit its content | //+------------------------------------------------------------------+ bool CPanel::AutoSizeProcess(const bool redraw) { //--- Get values along X and Y axes, by which the panel size is to be changed int excess_w=this.GetExcessMaxX(); int excess_h=this.GetExcessMaxY(); //--- If failed to change the size, return 'true' if(excess_w==0 && excess_h==0) return true; //--- If failed to change the panel size, return the result of its adjustment return ( //--- if only a size increase this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? this.Resize(this.Width()+(excess_w>0 ? excess_w : 0),this.Height()+(excess_h>0 ? excess_h : 0),redraw) : //--- if both increase and decrease this.Resize(this.Width()+(excess_w!=0 ? excess_w : 0),this.Height()+(excess_h!=0 ? excess_h : 0),redraw) ); } //+------------------------------------------------------------------+
La lógica del método se explica en los comentarios al código. Resumiendo: obtenemos los valores máximos de X e Y en los que los objetos vinculados sobresalen del sustrato. Un valor positivo indicará que los objetos se extienden más allá del sustrato; un valor negativo indicará que el sustrato es demasiado grande y se puede reducir. Bueno, y también retornamos el resultado del cambio de tamaño del panel, teniendo en cuenta el modo del cambio de tamaño automático de sus dimensiones (ya sea solo aumentar o aumentar y disminuir)
En el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh de la clase de colección de elementos gráficos, debemos corregir los nombres de los métodos de ajuste de propiedades BorderStyle() y FrameWidthAll() en los métodos de creación de paneles con otros nuevos: SetBorderStyle() y SetFrameWidthAll(). En los archivos adjuntos al artículo, los métodos ya han sido renombrados a otros nuevos.
En el archivo \MQL5\Include\DoEasy\Engine.mqh, en los métodos que retornan el objeto WForm Panel, deberemos cambiar el nombre antiguo de la macrosustitución GRAPH_ELEMENT_TYPE_PANEL por el nuevo: GRAPH_ELEMENT_TYPE_WF_PANEL:
//--- Return the WForm Element object by object ID CForm *GetWFForm(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by object name on the current chart CPanel *GetWFPanel(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by chart ID and object name CPanel *GetWFPanel(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by object ID CPanel *GetWFPanel(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Create the WinForm Element object
Por hoy, estos serán todos los cambios y mejoras necesarios.
Simulación
Para la simulación, tomaremos el asesor del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part105\ con el nuevo nombre TestDoEasyPart105.mq5.
¿Cómo realizaremos la prueba? Vamos a añadir una bandera para cambiar automáticamente el tamaño del panel de forma que se ajuste a su contenido interno, y también el modo de cambio de tamaño automático en la configuración del asesor experto.
Vamos a añadir una nueva tecla al teclado, a la que responderá el asesor experto para colocar objetos dentro del contenedor. Al presionar la tecla Q, todos los objetos creados dentro del panel se colocarán según su modo de vinculación. Tendremos 6 de estos objetos, según el número de modos de vinculación:
//+------------------------------------------------------------------+ //| Control borders bound to the container | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_DOCK_MODE { CANV_ELEMENT_DOCK_MODE_NONE, // Attached to the specified coordinates, size does not change CANV_ELEMENT_DOCK_MODE_TOP, // Attaching to the top and stretching along the container width CANV_ELEMENT_DOCK_MODE_BOTTOM, // Attaching to the bottom and stretching along the container width CANV_ELEMENT_DOCK_MODE_LEFT, // Attaching to the left and stretching along the container height CANV_ELEMENT_DOCK_MODE_RIGHT, // Attaching to the right and stretching along the container height CANV_ELEMENT_DOCK_MODE_FILL, // Stretching along the entire container width and height }; //+------------------------------------------------------------------+
En consecuencia, cada uno de estos objetos recibirá el modo de vinculación correspondiente a su número en la lista de objetos del panel. El primer objeto de la lista no tendrá vinculación (CANV_ELEMENT_DOCK_MODE_NONE), el segundo tendrá CANV_ELEMENT_DOCK_MODE_TOP, el tercero MODE_BOTTOM, y así sucesivamente.
En el área global, añadimos una macrosustitución para la tecla Q y enumeraciones para los parámetros de entrada en inglés y el idioma del país del usuario, e introducimos los nuevos parámetros de entrada:
//+------------------------------------------------------------------+ //| TestDoEasyPart105.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- defines #define FORMS_TOTAL (3) // Number of created forms #define START_X (4) // Initial X coordinate of the shape #define START_Y (4) // Initial Y coordinate of the shape #define KEY_LEFT (65) // (A) Left #define KEY_RIGHT (68) // (D) Right #define KEY_UP (87) // (W) Up #define KEY_DOWN (88) // (X) Down #define KEY_FILL (83) // (S) Filling #define KEY_ORIGIN (90) // (Z) Default #define KEY_INDEX (81) // (Q) By index //--- enumerations by compilation language #ifdef COMPILE_EN enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Grow AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Grow and Shrink }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Increase and decrease }; #endif //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Autosize Mode //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
Asimismo, eliminamos del manejador OnInit() el código para crear los formularios y los elementos. Dejaremos solo la creación de un panel y en el ciclo del propio panel, crearemos seis objetos de panel adjuntos con la bandera de redibujado igual a false. En este caso, la altura del panel será ligeramente inferior a la altura total de todos los objetos construidos en su interior, y la anchura, por el contrario, será mayor. Así, podremos ver cómo el panel se ajusta a los tamaños de los objetos construidos en su interior:
//+------------------------------------------------------------------+ //| 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,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true); 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 6 bound panel objects for(int i=0;i<6;i++) { //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 30 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20); int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16)); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false); } pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Después de crear todos los elementos dentro del panel, redibujaremos completamente todo el panel junto con los elementos creados en él (bandera de redibujado igual a true)
En el manejador de eventos OnChartEvent(), en el bloque de procesamiento de la pulsación de teclas, escribiremos un manejador para presionar la tecla Q en el teclado:
//--- If a key is pressed if(id==CHARTEVENT_KEYDOWN) { CPanel *panel=engine.GetWFPanel(0); if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX)) { for(int i=0;i<panel.ElementsTotal();i++) { CPanel *obj=panel.GetElement(i); if(obj!=NULL) { if(lparam==KEY_UP) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false); else if(lparam==KEY_DOWN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false); else if(lparam==KEY_LEFT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false); else if(lparam==KEY_RIGHT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false); else if(lparam==KEY_FILL) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false); else if(lparam==KEY_ORIGIN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); else if(lparam==KEY_INDEX) { obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true); Sleep(i>0 ? 500 : 0); } } } panel.Redraw(true); } }
Al mismo tiempo, para ver claramente cómo los objetos se desplazan a sus lugares según el modo de ajuste, implementaremos un retraso de medio segundo después de cada próximo movimiento del objeto a las nuevas coordenadas.
Vamos a compilar el asesor y a ejecutarlo en el gráfico:
Como podemos ver, la fijación de objetos a cada lado del panel funciona correctamente: al presionar la tecla Q, cada objeto se adjunta al lado deseado del panel, y al cambiar los modos de cambio de tamaño automático del panel, se adapta a su contenido interno según el modo de tamaño automático.
¿Qué es lo próximo?
En el próximo artículo, continuaremos el desarrollo de los objetos WinForms.
*Artículos de esta serie:
DoEasy. Elementos de control (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
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/10794





- 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