![DoEasy. Elementos de control (Parte 31): Desplazamiento por el contenido del control "ScrollBar"](https://c.mql5.com/2/51/doeasy_031_600x314.jpg)
DoEasy. Elementos de control (Parte 31): Desplazamiento por el contenido del control "ScrollBar"
Contenido
Concepto
Continuamos desarrollando la funcionalidad del control ScrollBar. La barra de desplazamiento que estamos desarrollando puede responder a la pulsación de los botones y el desplazamiento del control deslizante, pero después no suceden más acciones, y necesitamos que al clicar en los botones de desplazamiento, el contenido del contenedor se desplace dentro de él, mostrando así las áreas previamente ocultas y ocultando las que se mostraban antes desde el lado opuesto del contenedor. Hoy implementaremos la posibilidad de desplazar el contenido del contenedor al clicar en los botones de la barra de desplazamiento horizontal. En este caso, el control deslizante del elemento de control ajustará automáticamente su tamaño y posición.
El control deslizante de la barra de desplazamiento no será solo una parte del elemento que, al moverse, permita controlar la posición del contenido del formulario desplazándolo dentro del contenedor: también servirá como representación esquemática de la posición relativa del contenedor y su contenido. La barra de desplazamiento en sí supone la anchura de todo el contenido del contenedor, mientras que el control deslizante en la barra de desplazamiento es la anchura del contenedor donde se ubica el contenido, y, cuanto más contenido salga del contenedor, más pequeño será el tamaño del control deslizante, y es natural. Después de todo, el control deslizante muestra con su tamaño la ventana dentro de la cual podemos ver el contenido, mientras que la barra de desplazamiento muestra todo el contenido del contenedor. Al mover el control deslizante a lo largo de la barra de desplazamiento, le estamos diciendo al programa dónde queremos ver el contenido ahora.
Exactamente de la misma forma, podemos controlar la posición del contenido del contenedor usando los botones con flechas ubicados en los bordes de la barra de desplazamiento. Al mismo tiempo, tanto el contenido del contenedor como el control deslizante en la barra de desplazamiento se mueven, mostrándonos qué parte del contenido total se muestra actualmente.
Hoy crearemos la capacidad de cambiar el contenido del contenedor usando los botones con flechas de la barra de desplazamiento horizontal. El control deslizante se moverá y tendrá el tamaño relativo correcto y las coordenadas de ubicación en la barra de desplazamiento. En general, primero desarrollaremos la funcionalidad de la barra de desplazamiento horizontal y luego la transferiremos ya lista a la barra vertical; después las haremos funcionar juntas.
Mejorando las clases de la biblioteca
Como el control deslizante de la barra de desplazamiento ajusta automáticamente su tamaño dependiendo de cuánto se extienda el contenido del contenedor más allá de sus límites, si el tamaño se reduce considerablemente, el control deslizante podría volverse demasiado pequeño. Para evitar esto, deberemos establecer un tamaño mínimo para el control deslizante, que no podrá ser inferior a este, y al desplazar el contenido del contenedor, deberemos establecer el tamaño de paso en píxeles en el cual el contenido del contenedor se podrá mover en un solo paso. En el MetaEditor, por ejemplo, este paso será de seis píxeles. Nosotros lo haremos más pequeño: dos píxeles.
En el archivo \MQL5\Include\DoEasy\Defines.mqh, crearemos dos nuevas macrosustituciones para especificar los parámetros anteriores:
#define DEF_CONTROL_SCROLL_BAR_WIDTH (11) // Default ScrollBar control width #define DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN (8) // Minimum size of the capture area (slider) #define DEF_CONTROL_SCROLL_BAR_SCROLL_STEP (2) // Shift step in pixels of the container content when scrolling #define DEF_CONTROL_CORNER_AREA (4) // Number of pixels defining the corner area to resize #define DEF_CONTROL_LIST_MARGIN_X (1) // Gap between columns in ListBox controls #define DEF_CONTROL_LIST_MARGIN_Y (0) // Gap between rows in ListBox controls
Cuando queramos obtener los datos de las propiedades del objeto "bordes superior, inferior, izquierdo y derecho de un elemento gráfico", los obtendremos de las propiedades del objeto. Los valores se escribirán en estos parámetros cuando el objeto se cree correctamente, y luego, si cambiamos el tamaño del objeto, estos datos ya no se guardarán en las propiedades del elemento gráfico. Sí, obtendremos los valores solicitando estos con la ayuda de uno de los métodos que retornan esta propiedad, como BottomEdge(), pero este método simplemente retornará el valor calculado. En las propiedades del objeto, estos valores no cambian, y esto hay que corregirlo. En el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh del elemento gráfico básico de la biblioteca, en todos los métodos que de alguna manera cambian los límites del objeto, deberemos escribir los nuevos valores en las propiedades del objeto:
//+------------------------------------------------------------------+ //| Set the new X coordinate | //+------------------------------------------------------------------+ bool CGCnvElement::SetCoordX(const int coord_x) { int x=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE); if(coord_x==x) { if(coord_x==this.GetProperty(CANV_ELEMENT_PROP_COORD_X)) return true; this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x); this.SetRightEdge(); return true; } if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,coord_x)) { this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x); this.SetRightEdge(); return true; } return false; } //+------------------------------------------------------------------+ //| Set the new Y coordinate | //+------------------------------------------------------------------+ bool CGCnvElement::SetCoordY(const int coord_y) { int y=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE); if(coord_y==y) { if(coord_y==this.GetProperty(CANV_ELEMENT_PROP_COORD_Y)) return true; this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y); this.SetBottomEdge(); return true; } if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,coord_y)) { this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y); this.SetBottomEdge(); return true; } return false; } //+------------------------------------------------------------------+ //| Set the new width | //+------------------------------------------------------------------+ bool CGCnvElement::SetWidth(const int width) { if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width) return true; if(!this.m_canvas.Resize(width,this.m_canvas.Height())) { CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH); return false; } this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width); this.SetVisibleAreaX(0,true); this.SetVisibleAreaWidth(width,true); this.SetRightEdge(); return true; } //+------------------------------------------------------------------+ //| Set the new height | //+------------------------------------------------------------------+ bool CGCnvElement::SetHeight(const int height) { if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height) return true; if(!this.m_canvas.Resize(this.m_canvas.Width(),height)) { CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT); return false; } this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height); this.SetVisibleAreaY(0,true); this.SetVisibleAreaHeight(height,true); this.SetBottomEdge(); return true; } //+------------------------------------------------------------------+
Ahora, con cualquier cambio en el tamaño o las coordenadas de un objeto, las coordenadas de sus lados se escribirán en las propiedades del elemento gráfico, lo cual nos permitirá encontrar correctamente los objetos necesarios según los valores establecidos para sus lados, usando la clasificación según las propiedades del objeto.
Algunos de los métodos anteriormente creados en la clase de objeto contenedor solo serán necesarios para los objetos contenedor. Por ejemplo, los métodos que retornan los límites del espacio de trabajo del contenedor dentro de los que se pueden ubicar los objetos adjuntos. Pero aquí ha resultado problemático que en otros objetos de la biblioteca, salvo los objetos contenedores, estos métodos no están disponibles, y a menudo son necesarios; después de todo, estamos accediendo a un objeto contenedor desde una clase en la que no hay acceso a dicho objeto (simplemente no lo conoce). En consecuencia, deberemos acceder a las propiedades de dicho objeto a través de las propiedades de su objeto padre, el objeto básico de todos los objetos de la biblioteca de WinForms, y esta clase no ve los métodos de su heredero: la clase del objeto contenedor. Así, nos vemos ante un círculo vicioso. Pero existe una salida: renunciando un poco a la estructura de los objetos, podemos transmitir todos los métodos necesarios a la clase CWinFormBase, que es la clase padre de todos los objetos de la biblioteca WinForms. Así, al acceder a distintas propiedades de objetos con distintas finalidades entre sí, nos resultará más sencillo obtener los datos necesarios, incluso si dichos datos pertenecen a un tipo diferente de objeto y no se usan en este objeto, todos los objetos podrán acceder a métodos de otros objetos que no se usan en el actual, pero que se utilizan en aquellos que están siendo llamados .
Resulta un poco confuso, pero se ve mucho más fácil en el código: simplemente transferiremos algunos métodos de la clase de objeto contenedor a la clase de objeto básico de todos los objetos de la biblioteca de WinForms. Así, haremos que estos métodos resulten visibles desde cualquier clase.
Del archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh, cortamos estos métodos públicos:
public: //--- Return the size and coordinates of the working area int WidthWorkspace(void) const { return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int HeightWorkspace(void) const { return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } int CoordXWorkspace(void) const { return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft()); } int CoordYWorkspace(void) const { return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop()); } int RightEdgeWorkspace(void) const { return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int BottomEdgeWorkspace(void) const { return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } //--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)
y los insertamos en la sección pública de la clase, en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:
//--- Destructor ~CWinFormBase(void) { if(this.m_list_active_elements!=NULL) { this.m_list_active_elements.Clear(); delete this.m_list_active_elements; } } //--- Return the size and coordinates of the working area int WidthWorkspace(void) const { return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int HeightWorkspace(void) const { return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());} int CoordXWorkspace(void) const { return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft()); } int CoordYWorkspace(void) const { return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop()); } int RightEdgeWorkspace(void) const { return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int BottomEdgeWorkspace(void) const { return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } //--- (1) Set and (2) return the default text color of all panel objects void SetForeColor(const color clr,const bool set_init_color) { if(this.ForeColor()==clr) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr); if(set_init_color) this.SetForeColorInit(clr); }
Ahora estos métodos resultarán visibles desde todos los objetos WinForms de la biblioteca,
y por la misma razón, debido a la visibilidad de los métodos en todas las clases de los objetos de WinForms, escribiremos los métodos que, en esencia, deberían pertenecer a la clase del objeto contenedor, pero cuya solicitud proviene de objetos que no son contenedores.
Ahora, necesitaremos saber cuántos píxeles de los objetos adjuntos al objeto contenedor se salen del contenedor. Para ello, verificaremos dichos valores en un método y los escribiremos en una estructura que contenga los campos con los valores del número de píxeles en la parte superior, inferior, izquierda y derecha. Si los objetos vinculados van más allá de cualquier borde del contenedor, o de varios a la vez, la estructura almacenará los valores con el número de píxeles a cada lado del objeto donde el contenido se sale del contenedor. Partiendo de estos datos, podremos calcular el tamaño de la barra de desplazamiento en el control ScrollBar.
En la sección privada de la clase, declararemos dicha estructura y una variable con el tipo de esta estructura en la que escribiremos los datos necesarios:
protected: CArrayObj *m_list_active_elements; // Pointer to the list of active elements color m_fore_color_init; // Initial color of the control text color m_fore_state_on_color_init; // Initial color of the control text when the control is "ON" private: struct SOversizes // Structure of values for bound objects leaving the container { int top; // top int bottom; // bottom int left; // left int right; // right }; SOversizes m_oversize; // Structure of values for leaving the container //--- Return the font flags uint GetFontFlags(void); public:
En la sección pública, declararemos un método que verificará si los objetos vinculados se salen del contenedor y rellenará la estructura declarada anteriormente, así como un método que desplazará todos los objetos vinculados al contenedor, además de declarar los métodos que retornarán los valores mínimo y máximo de la propiedad especificada de todos los objetos vinculados al actual:
//--- 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); //--- Return the flag of the container content leaving the container borders bool CheckForOversize(void); //--- Shift all bound objects bool ShiftDependentObj(const int shift_x,const int shift_y); //--- Return the (1) maximum and (2) minimum values of the specified integer property from all attached objects to the current one long GetMaxLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); long GetMinLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); protected: //--- Protected constructor with object type, chart ID and subwindow CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor
Todos estos métodos son necesarios para trabajar con objetos contenedores, pero se llamarán más allá de los objetos que no son contenedores, por lo que se ubicarán en la clase principal común de todos los objetos WinForms.
Como la estructura y la variable con el tipo de estructura son privadas, necesitaremos métodos públicos para retornar los valores escritos en los campos de esta estructura. Los escribiremos en la sección pública al final del cuerpo de la clase:
//--- Return the number of pixels, by which attached objects go beyond the container at the (1) top, (2) bottom, (3) left and (4) right int OversizeTop(void) const { return this.m_oversize.top; } int OversizeBottom(void) const { return this.m_oversize.bottom; } int OversizeLeft(void) const { return this.m_oversize.left; } int OversizeRight(void) const { return this.m_oversize.right; } }; //+------------------------------------------------------------------+
En ambos constructores de clase, inicializaremos todos los campos de la estructura en cero:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CForm(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(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_gradient_v=true; this.m_gradient_c=false; this.m_list_active_elements=new CArrayObj(); ::ZeroMemory(this.m_oversize); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CForm(GRAPH_ELEMENT_TYPE_WF_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(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_gradient_v=true; this.m_gradient_c=false; this.m_list_active_elements=new CArrayObj(); ::ZeroMemory(this.m_oversize); } //+------------------------------------------------------------------+
Método que retorna la bandera que indica que el contenido del contenedor se sale de sus límites:
//+------------------------------------------------------------------+ //| Return the flag of the container content leaving its borders | //+------------------------------------------------------------------+ bool CWinFormBase::CheckForOversize(void) { //--- Update the structure of values for bound objects leaving the container ::ZeroMemory(this.m_oversize); //--- In the loop by the number of attached objects for(int i=0;i<this.ElementsTotal();i++) { //--- Get the next object and skip scrollbars CWinFormBase *obj=this.GetElement(i); if(obj==NULL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL) continue; //--- Get the value in pixels of the object leaving the form at the right //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure int r=obj.RightEdge()-this.RightEdgeWorkspace(); if(r>0 && r>this.m_oversize.right) this.m_oversize.right=r; //--- Get the value in pixels of the object leaving the form at the left //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure int l=this.CoordXWorkspace()-obj.CoordX(); if(l>0 && l>this.m_oversize.left) this.m_oversize.left=l; //--- Get the value in pixels of the object leaving the form at the top //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure int t=this.CoordYWorkspace()-obj.CoordY(); if(t>0 && t>this.m_oversize.top) this.m_oversize.top=t; //--- Get the value in pixels of the object leaving the form at the bottom //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure int b=obj.BottomEdge()-this.BottomEdgeWorkspace(); if(b>0 && b>this.m_oversize.bottom) this.m_oversize.bottom=b; } //--- Return the flag indicating that at least one side of the attached object goes beyond the form borders return(m_oversize.top>0 || m_oversize.bottom>0 || m_oversize.left>0 || m_oversize.right>0); } //+------------------------------------------------------------------+
La lógica del método se detalla en los comentarios al código. Resumiendo: necesitamos saber si alguno (o varios, o incluso todos) de los objetos vinculados al contenedor se sale de sus límites. Y para conocer los valores máximos según los cuales los objetos se encuentran fuera de su contenedor, buscaremos el valor máximo de cada lado en un ciclo a través de todos los objetos adjuntos. Al final del ciclo, en la estructura tendremos todos los valores en los que los objetos adjuntos a cada lado del contenedor van más allá de sus límites. Según estos valores, podremos calcular más adelante el tamaño del control deslizante de la barra de progreso. Después de todo, el tamaño del control deslizante dependerá de cuánto sobrepasen los objetos el contenedor (cuanto mayor sea la magnitud en que se sobrepasa el contenedor, menor será el tamaño del control deslizante).
Método que desplaza todos los objetos vinculados:
//+------------------------------------------------------------------+ //| Shift all bound objects | //+------------------------------------------------------------------+ bool CWinFormBase::ShiftDependentObj(const int shift_x,const int shift_y) { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object and skip scrollbars CWinFormBase *obj=this.GetElement(i); if(obj==NULL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ) continue; //--- Set the offset coordinates int x=obj.CoordX()+shift_x; int y=obj.CoordY()+shift_y; if(!obj.Move(x,y,false)) return false; //--- After a successful offset, set relative coordinates and redraw the object obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.Redraw(false); } return true; } //+------------------------------------------------------------------+
La lógica del método se comenta con todo detalle en el código. Necesitaremos mover los objetos vinculados al contenedor al clicar en los botones de desplazamiento o al mover el control deslizante. Este método compensará todos los objetos en la lista de objetos vinculados a él. El desplazamiento se realizará usando el método Move(); en este método ya hemos implementado todo para que los demás elementos adjuntos al objeto que se está moviendo también se desplacen. En general, aquí cambiaremos todos los objetos adjuntos al contenedor (salvo las barras de desplazamiento) en la magnitud especificada, ya que serán controles del objeto contenedor y no objetos adjuntos a este (aunque se encuentran en la lista general).
Método que retorna el valor máximo de la propiedad entera especificada de todos los objetos básicos subordinados:
//+------------------------------------------------------------------+ //| Return the maximum value of the specified integer | //| property from all objects subordinate to the base one | //+------------------------------------------------------------------+ long CWinFormBase::GetMaxLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Initialize 'property' with -1 long property=-LONG_MAX; //--- In the loop through the list of bound objects for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object and skip scrollbars CWinFormBase *obj=this.GetElement(i); if(obj==NULL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ) continue; //--- If the property value of the received object is greater than the value set in the property, //--- set the current object property value to 'property' if(obj.GetProperty(prop)>property) property=obj.GetProperty(prop); //--- Get the maximum property value from objects bound to the current one long prop_form=obj.GetMaxLongPropFromDependent(prop); //--- If the received value is greater than the 'property' value //--- set the received value to 'property' if(prop_form>property) property=prop_form; } //--- Return the found maximum property value return property; } //+------------------------------------------------------------------+
La lógica completa aquí también está escrita en los comentarios al código, y todo es simple: en un ciclo, buscaremos entre todos los objetos (excepto las barras de desplazamiento) el objeto con el valor máximo de la propiedad especificada. Luego retornaremos el valor máximo encontrado.
Método que retorna el valor mínimo de la propiedad entera especificada de todos los objetos básicos subordinados:
//+------------------------------------------------------------------+ //| Return the minimum value of the specified integer | //| property from all objects subordinate to the base one | //+------------------------------------------------------------------+ long CWinFormBase::GetMinLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Initialize 'property' using the LONG_MAX value long property=LONG_MAX; //--- In the loop through the list of bound objects for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object and skip scrollbars CWinFormBase *obj=this.GetElement(i); if(obj==NULL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ) continue; //--- If the value of the obtained object property is less than the value set in 'property', //--- set the current object property value to 'property' if(obj.GetProperty(prop)<property) property=obj.GetProperty(prop); //--- Get the minimum property value from bound objects to the current one long prop_form=obj.GetMinLongPropFromDependent(prop); //--- If the obtained value is less than the property value //--- set the received value to 'property' if(prop_form<property) property=prop_form; } //--- Return the found minimum property value return property; } //+------------------------------------------------------------------+
El método es similar al anterior: en un ciclo, buscaremos entre todos los objetos (excepto las barras de desplazamiento) el objeto con el valor mínimo de la propiedad especificada. Luego retornaremos el valor mínimo encontrado.
Al pasar el cursor del ratón sobre el control deslizante de la barra de desplazamiento, el objeto ScrollBar al completo deberá moverse al primer plano, y no solo al pasar el cursor sobre el control deslizante, sino que, en general, siempre que pasemos el ratón sobre el área de este objeto, este deberá situarse por encima todos los objetos contenedores. Esto será necesario para que el cursor pueda interactuar con el objeto superior, y no con los que puedan estar encima, ya que han sido creados posteriormente. Por el momento, la clase de área de captura de objetos se heredará de la clase del objeto de botón y utilizará la funcionalidad de la clase principal para interactuar con el ratón. Como los métodos para procesar diferentes eventos al interactuar con el ratón son virtuales, deberemos redefinirlos en la clase de objeto de área de captura.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh, en la sección protegida, declararemos los manejadores virtuales de eventos del ratón, así como el último manejador de eventos del ratón en la sección pública:
//+------------------------------------------------------------------+ //| Label object class of WForms controls | //+------------------------------------------------------------------+ class CScrollBarThumb : public CButton { private: protected: //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler virtual void MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler virtual void MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the active area, the left mouse button is clicked' event handler virtual void MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Protected constructor with object type, chart ID and subwindow CScrollBarThumb(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void); //--- Constructor CScrollBarThumb(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Vamos a escribir la implementación de los manejadores virtuales declarados.
Manejador del evento «Cursor dentro del área activa, botones del ratón no presionados»:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CScrollBarThumb::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { CWinFormBase *base=this.GetBase(); if(base!=NULL) { base.BringToTop(); } CButton::MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Aquí, obtendremos el puntero al objeto básico: este será un objeto de barra de desplazamiento que trasladaremos al primer plano. A continuación, llamaremos al manejador del evento de ratón del objeto principal correspondiente al método.
Manejador del evento Cursor dentro del área activa, botón del ratón presionado (cualquiera de ellos):
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CScrollBarThumb::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { CWinFormBase *base=this.GetBase(); if(base!=NULL) { base.BringToTop(); } CButton::MouseActiveAreaPressedHandler(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Todo resultará exactamente igual: primero trasladaremos el objeto básico al primer plano (será una barra de desplazamiento con todos los controles), luego llamaremos al manejador de eventos del ratón del objeto principal.
Manejador del evento «Cursor dentro del área activa, botón (izquierdo) del ratón no presionado»:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CScrollBarThumb::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { CWinFormBase *base=this.GetBase(); if(base!=NULL) { base.BringToTop(); } CButton::MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
Es idéntico a los dos manejadores anteriores.
El manejador del último evento de ratón simplemente se ha trasladado al completo desde el objeto principal para posiblemente mejorarlo aún más:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CScrollBarThumb::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled()) return; ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false); this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled //--- The cursor is within the window resizing area, the mouse buttons are not clicked //--- The cursor is within the window resizing area, the mouse button (any) is clicked //--- The cursor is within the window resizing area, the mouse wheel is being scrolled //--- The cursor is within the window resizing area, the mouse buttons are not clicked //--- The cursor is within the window resizing area, the mouse button (any) is clicked //--- The cursor is within the window separator area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : //--- Within the active area case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : //--- Within the scrolling area at the bottom case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_WHEEL : //--- Within the scrolling area to the right case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_WHEEL : //--- Within the window resizing area at the top case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_WHEEL : //--- Within the window resizing area at the bottom case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_WHEEL : //--- Within the window resizing area to the left case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_WHEEL : //--- Within the window resizing area to the right case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_WHEEL : //--- Within the window resizing area to the top-left case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_WHEEL : //--- Within the window resizing area to the top-right case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_WHEEL : //--- Within the window resizing area at the bottom left case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_WHEEL : //--- Within the window resizing area at the bottom-right case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_WHEEL : //--- Within the control area case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL : break; //---MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Por el momento, el manejador resultará idéntico al manejador de la clase principal.
En la clase de barra de desplazamiento de objetos abstractos, en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBar.mqh, en el método que crea el área de captura de los objetos (control de deslizamiento) y los botones de desplazamiento, indicaremos el tamaño correcto de los botones registrados en la macrosustitución, diseñado específicamente para almacenar los tamaños de los botones de la barra de desplazamiento:
//+------------------------------------------------------------------+ //| Create the capture area object | //+------------------------------------------------------------------+ void CScrollBar::CreateThumbArea(void) { this.CreateArrowButtons(DEF_CONTROL_SCROLL_BAR_WIDTH,DEF_CONTROL_SCROLL_BAR_WIDTH); } //+------------------------------------------------------------------+
En el método que calcula el tamaño del área de captura, retornaremos del método no cero, sino el valor escrito en la macrosustitución que almacena el tamaño mínimo predeterminado del control deslizante:
//+------------------------------------------------------------------+ //| Calculate the capture area size | //+------------------------------------------------------------------+ int CScrollBar::CalculateThumbAreaSize(void) { return DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN; } //+------------------------------------------------------------------+
La clase de objeto de la barra de desplazamiento deberá calcular el tamaño y la posición del control deslizante en función de cuántos píxeles se extiende más allá del contenido del contenedor. Al cambiar el tamaño del contenedor o su contenido, se recalculará el tamaño y la posición del control deslizante. Al presionar los botones de desplazamiento, el contenido del contenedor (si va más allá de este) deberá moverse en la dirección opuesta a la dirección de la flecha del botón presionado. El control deslizante deberá moverse en la dirección de la flecha.
Hoy crearemos esta funcionalidad para la barra de desplazamiento horizontal. Además, tras completar su desarrollo, transferiremos la funcionalidad terminada a la clase de objeto de barra de desplazamiento vertical, y luego implementaremos su trabajo conjunto.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh, cambiaremos el nombre del método privado CalculateThumbAreaSize por el método CalculateThumbAreaDistance(). En la sección pública, declararemos los métodos para calcular el tamaño y las coordenadas del control deslizante y el método principal para recalcular sus parámetros:
//+------------------------------------------------------------------+ //| CScrollBarHorisontal object class of WForms controls | //+------------------------------------------------------------------+ class CScrollBarHorisontal : public CScrollBar { private: //--- Create the ArrowButton objects virtual void CreateArrowButtons(const int width,const int height); //--- Calculate the distance of the capture area (slider) int CalculateThumbAreaDistance(const int thumb_size); protected: //--- Protected constructor with object type, chart ID and subwindow CScrollBarHorisontal(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Supported object properties (1) integer, (2) real and (3) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return the (1) left and (2) right arrow button CArrowLeftButton *GetArrowButtonLeft(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0); } CArrowRightButton*GetArrowButtonRight(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,0); } //--- Return the size of the slider working area int BarWorkAreaSize(void); //--- Return the coordinate of the beginning of the slider working area int BarWorkAreaCoord(void); //--- Set the new size virtual bool Resize(const int w,const int h,const bool redraw); //--- Calculate and set the parameters of the capture area (slider) int SetThumbParams(void); //--- Constructor CScrollBarHorisontal(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- Timer virtual void OnTimer(void); //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); }; //+------------------------------------------------------------------+
Vamos a analizar los métodos declarados.
Método que establece las nuevas dimensiones del objeto:
//+------------------------------------------------------------------+ //| Set the new size | //+------------------------------------------------------------------+ bool CScrollBarHorisontal::Resize(const int w,const int h,const bool redraw) { //--- If failed to change the object size, return 'false' if(!CWinFormBase::Resize(w,h,redraw)) return false; //--- Get the button object with the right arrow CArrowRightButton *br=this.GetArrowButtonRight(); //--- If the button is not received, return 'false' if(br==NULL) return false; //--- Move the button to the right edge of the scrollbar if(br.Move(this.RightEdge()-this.BorderSizeRight()-br.Width(),br.CoordY())) { //--- Set new relative coordinates for the button br.SetCoordXRelative(br.CoordX()-this.CoordX()); br.SetCoordYRelative(br.CoordY()-this.CoordY()); } //--- Set the slider parameters this.SetThumbParams(); //--- Successful return true; } //+------------------------------------------------------------------+
Primero, estableceremos las nuevas dimensiones de la barra de desplazamiento usando el método de la clase principal. Luego, tras cambiar el tamaño con éxito, deberemos desplazar el botón ubicado en el lado derecho del objeto para que se ubique en el borde del objeto redimensionado (junto con el cambio de tamaño, la coordenada de su borde derecho también cambiará). Después de realizar todos los cambios y movimientos, recalcularemos el tamaño y la posición del control deslizante utilizando el método SetThumbParams(), que analizaremos a continuación.
Método que calcula y establece los parámetros del área de captura (deslizador):
//+------------------------------------------------------------------+ //| Calculate and set the parameters of the capture area (slider) | //+------------------------------------------------------------------+ int CScrollBarHorisontal::SetThumbParams(void) { //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return 0; //--- Get the capture area object (slider) CScrollBarThumb *thumb=this.GetThumb(); if(thumb==NULL) return 0; //--- Get the width size of the visible part inside the container int base_w=base.WidthWorkspace(); //--- Calculate the total width of all attached objects and the window size of the visible part in % int objs_w=base_w+base.OversizeLeft()+base.OversizeRight(); double px=base_w*100.0/objs_w; //--- Calculate and adjust the size of the slider in % relative to the width of its workspace (not less than the minimum size) int thumb_size=(int)::ceil(this.BarWorkAreaSize()/100.0*px); if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN) thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN; //--- Calculate the coordinate of the slider and change its size to match the previously calculated one int thumb_x=this.CalculateThumbAreaDistance(thumb_size); if(!thumb.Resize(thumb_size,thumb.Height(),true)) return 0; //--- Shift the slider by the calculated X coordinate if(thumb.Move(this.BarWorkAreaCoord()+thumb_x,thumb.CoordY())) { thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX()); thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY()); } //--- Return the calculated slider size return thumb_size; } //+------------------------------------------------------------------+
Cada línea del método se comenta con detalle. La esencia del método consiste en calcular el tamaño del control deslizante en función del tamaño del contenido del contenedor que sobrepase sus bordes. Cuanto mayor sea el valor en píxeles que sobrepase el contenido del contenedor, más pequeño será el control deslizante. La coordenada de la posición del control deslizante se calculará desde el borde izquierdo del contenedor, también en tamaños relativos, para que se corresponda con la posición de la parte visible del contenido del contenedor en relación con su parte invisible, que se extiende más allá del borde izquierdo. Cuanto más en píxeles se extienda el contenido del contenedor más allá del borde izquierdo, más a la derecha se colocará el control deslizante. Como resultado, resultará que la barra de desplazamiento, junto con el control deslizante, será una copia reducida (o ejemplar) del contenedor y su contenido. En la barra de desplazamiento, la barra en sí mostrará el contenido completo del contenedor, mientras que el control deslizante mostrará el contenedor, esa parte en la que el contenido resulta visible.
Método que calcula la distancia del área de captura (control deslizante):
//+------------------------------------------------------------------+ //| Calculate the distance of the capture area (slider) | //+------------------------------------------------------------------+ int CScrollBarHorisontal::CalculateThumbAreaDistance(const int thumb_size) { CWinFormBase *base=this.GetBase(); if(base==NULL) return 0; double x=(double)thumb_size/(double)base.WidthWorkspace(); return (int)::ceil((double)base.OversizeLeft()*x); } //+------------------------------------------------------------------+
El tamaño del control deslizante es transmitido al método. A continuación, calcularemos cuánto menor es el tamaño del control deslizante respecto al tamaño del espacio de trabajo del contenedor (en el que se ve el contenido). Esta relación se usará luego para calcular la distancia del control deslizante que precisamente se retornará. En otras palabras, tomaremos la medida en que el deslizador es más pequeño que el contenedor, para indicar hasta qué punto la distancia del deslizador respecto al origen de sus coordenadas será inferior al número de píxeles en que el contenido del contenedor sobrepasará su borde izquierdo.
Método que retorna el tamaño del área de trabajo del control deslizante:
//+------------------------------------------------------------------+ //| Return the size of the slider working area | //+------------------------------------------------------------------+ int CScrollBarHorisontal::BarWorkAreaSize(void) { CArrowLeftButton *bl=this.GetArrowButtonLeft(); CArrowRightButton *br=this.GetArrowButtonRight(); int x1=(bl!=NULL ? bl.RightEdge() : this.CoordX()+this.BorderSizeLeft()); int x2=(br!=NULL ? br.CoordX() : this.RightEdge()-this.BorderSizeRight()); return(x2-x1); } //+------------------------------------------------------------------+
El área de trabajo del control deslizante es el área dentro de la cual se mueve en la barra de desplazamiento. En otras palabras, esta será el área entre los dos botones con flechas de la barra de desplazamiento. Por lo tanto, para calcular esta distancia, deberemos apuntar a estos botones y calcular la distancia entre el borde izquierdo del botón derecho y el borde derecho del izquierdo.
Método que retorna la coordenada del inicio del área de trabajo del control deslizante:
//+------------------------------------------------------------------+ //| Return the coordinate of the beginning of the slider working area| //+------------------------------------------------------------------+ int CScrollBarHorisontal::BarWorkAreaCoord(void) { CArrowLeftButton *bl=this.GetArrowButtonLeft(); return(bl!=NULL ? bl.RightEdge() : this.CoordX()+this.BorderSizeLeft()); } //+------------------------------------------------------------------+
El método devuelve la coordenada del borde derecho del botón izquierdo de la barra de desplazamiento. Esta coordenada será la inicial para la ubicación del control deslizante, y será a partir de esta coordenada que contaremos su posición al calcular sus dimensiones y coordenadas.
El manejador de eventos del ratón ahora estará rediseñado y se verá así:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CScrollBarHorisontal::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); //--- Get the pointers to control objects of the scrollbar CArrowLeftButton *buttl=this.GetArrowButtonLeft(); CArrowRightButton *buttr=this.GetArrowButtonRight(); CScrollBarThumb *thumb=this.GetThumb(); if(buttl==NULL || buttr==NULL || thumb==NULL) return; //--- If the event ID is an object movement if(id==WF_CONTROL_EVENT_MOVING) { //--- Move the scrollbar to the foreground this.BringToTop(); //--- Declare the variables for the coordinates of the capture area int x=(int)lparam; int y=(int)dparam; //--- Set the Y coordinate equal to the Y coordinate of the control element y=this.CoordY()+this.BorderSizeTop(); //--- Adjust the X coordinate so that the capture area does not go beyond the control, taking into account the arrow buttons if(x<buttl.RightEdge()) x=buttl.RightEdge(); if(x>buttr.CoordX()-thumb.Width()) x=buttr.CoordX()-thumb.Width(); //--- If the capture area object is shifted by the calculated coordinates if(thumb.Move(x,y,true)) { //--- set the object relative coordinates thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX()); thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY()); ::ChartRedraw(this.ChartID()); } //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base!=NULL) { //--- Check if the content goes beyond the container and recalculate the slider parameters base.CheckForOversize(); this.SetThumbParams(); } } //--- If any scroll button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT) { //--- Move the scrollbar to the foreground this.BringToTop(); //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Calculate how much each side of the content of the base object goes beyond its borders base.CheckForOversize(); //--- Get the largest and smallest coordinates of the right and left sides of the base object content int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT); int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X); //--- Set the number of pixels, by which the content of the base object should be shifted int shift=DEF_CONTROL_SCROLL_BAR_SCROLL_STEP; //--- If the left button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT) { if(cntt_l+shift<=base.CoordXWorkspace()) base.ShiftDependentObj(shift,0); } //--- If the right button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT) { if(cntt_r-shift>=base.RightEdgeWorkspace()) base.ShiftDependentObj(-shift,0); } //--- Calculate the width and coordinates of the slider this.SetThumbParams(); } } //+------------------------------------------------------------------+
Aquí añadiremos el procesamiento de la pulsación en los botones de la barra de desplazamiento. Al clicar en un botón, calcularemos las coordenadas de desplazamiento y moveremos todo el contenido del contenedor en la dirección opuesta al botón presionado con la flecha. Después del desplazamiento, reconstruiremos las dimensiones y coordenadas del control deslizante.
Al mejorar la clase de objeto de barra de desplazamiento horizontal, hemos realizado simultáneamente cambios y mejoras similares en la clase de objeto de barra de desplazamiento vertical. No obstante, no hemos realizado todos los cambios, y no los hemos probado todos. Simplemente porque primero hemos creado la funcionalidad de la barra de desplazamiento horizontal y luego la hemos trasladado a la barra vertical. Por consiguiente, no analizaremos aquí algunos cambios realizados en el código de la clase de barra de desplazamiento vertical; analizaremos el código tras crearlo nuevamente usando como base el código de la barra de desplazamiento horizontal en funcionamiento y ya depurado.
Vamos a regresar a la clase de objeto contenedor en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh.
En la sección protegida, declararemos un método para traer las barras de desplazamiento al primer plano. En la sección pública, escribiremos los métodos que devuelven los punteros a ambas barras de desplazamiento, declararemos un método virtual para desplazar el objeto y haremos que el método virtual para redibujarlo sea una declaración con una implementación fuera del cuerpo de la clase:
//+------------------------------------------------------------------+ //| Class of the base container object of WForms controls | //+------------------------------------------------------------------+ class CContainer : public CWinFormBase { private: //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Calculate Dock objects' binding coordinates void CalculateCoords(CArrayObj *list); //--- Create the (1) vertical and (2) horizontal ScrollBar CWinFormBase *CreateScrollBarVertical(const int width); CWinFormBase *CreateScrollBarHorisontal(const int width); protected: //--- Adjust the element size to fit its content bool AutoSizeProcess(const bool redraw); //--- Set parameters for the attached object void SetObjParams(CWinFormBase *obj,const color colour); //--- Create vertical and horizontal ScrollBar objects void CreateScrollBars(const int width); //--- Move the scrollbars to the foreground void BringToTopScrollBars(void); public: //--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher) CArrayObj *GetListWinFormsObj(void); CArrayObj *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type); //--- Return the pointer to the specified WinForms object with the specified type by index CWinFormBase *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index); //--- Return the pointer to the (1) vertical and (2) horizontal scrollbar CScrollBarVertical *GetScrollBarVertical(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL,0); } CScrollBarHorisontal *GetScrollBarHorisontal(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL,0);} //--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height virtual bool SetCoordX(const int coord_x) { return CGCnvElement::SetCoordX(coord_x); } virtual bool SetCoordY(const int coord_y) { return CGCnvElement::SetCoordY(coord_y); } virtual bool SetWidth(const int width) { return CGCnvElement::SetWidth(width); } virtual bool SetHeight(const int height) { return CGCnvElement::SetHeight(height); } //--- 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); //--- Update the coordinates virtual bool Move(const int x,const int y,const bool redraw=false); //--- Create a new attached element virtual bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Redraw the object virtual void Redraw(bool 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 virtual bool ArrangeObjects(const bool redraw);
En el método que establece el modo de cambio de tamaño automático del elemento para que se ajuste al contenido, añadiremos una verificación que comprobará si la bandera de cambio de tamaño automático según el contenido está o no establecida:
//--- (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.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode); if(!this.AutoSize()) return; if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); }
Al establecer el modo de tamaño automático, se llamará al proceso de cambio el tamaño del contenedor según el tamaño de su contenido, si el modo establecido no coincide con el actual. Al mismo tiempo, si el indicador de cambio de tamaño automático no está habilitado, no será necesario clasificar nada, que es lo que hace el código añadido: retorna el método cuando se borra la bandera de cambio de tamaño automático.
Después de crear el objeto contenedor, si la bandera de cambio automático del tamaño del contenedor según el contenido no está configurada para el objeto, y si el contenido del contenedor sobrepasa sus límites, deberemos mostrar la barra de desplazamiento correspondiente:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set parameters for the created object this.SetObjParams(obj,colour); //--- If there are bound objects if(this.ElementsTotal()>0) { //--- If the panel has auto resize enabled, call the auto resize method if(this.AutoSize()) this.AutoSizeProcess(redraw); //--- If auto resize is disabled, determine whether scrollbars should be displayed else { this.CheckForOversize(); //--- If the attached objects go beyond the visibility window to the left or right if(this.OversizeLeft()>0 || this.OversizeRight()>0) { CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal(); if(sbh!=NULL) { sbh.SetThumbParams(); sbh.SetDisplayed(true); sbh.Show(); } } } } //--- Crop the created object along the edges of the visible part of the container obj.Crop(); //--- return 'true' return true; } //+------------------------------------------------------------------+
Aquí, primero verificaremos los objetos adjuntos al contenedor para ver si están fuera de los límites, y si realmente los sobrepasan por la izquierda o la derecha, mostraremos una barra de desplazamiento horizontal. Sin embargo, primero calcularemos y estableceremos los parámetros del control deslizante de la barra de desplazamiento, y luego mostraremos el objeto de barra de desplazamiento en sí.
Puesto que ahora el objeto de barra de desplazamiento procesa el cambio de tamaño, el método que cambia el tamaño del objeto actual ha cambiado:
//+------------------------------------------------------------------+ //| Set the new size for the current object | //+------------------------------------------------------------------+ bool CContainer::Resize(const int w,const int h,const bool redraw) { //--- If it was not possible to change the size of the container, return 'false' if(!CWinFormBase::Resize(w,h,redraw)) return false; //--- Get the vertical scrollbar and CScrollBarVertical *scroll_v=this.GetScrollBarVertical(); if(scroll_v!=NULL) { //--- If the vertical size of the scrollbar is changed to fit the size of the container workspace if(scroll_v.Resize(scroll_v.Width(),this.HeightWorkspace(),false)) { //--- Move the vertical scrollbar to new coordinates if(scroll_v.Move(this.RightEdgeWorkspace()-scroll_v.Width(),this.CoordYWorkspace())) { scroll_v.SetCoordXRelative(scroll_v.CoordX()-this.CoordX()); scroll_v.SetCoordYRelative(scroll_v.CoordY()-this.CoordY()); } } scroll_v.BringToTop(); } //--- Get the horizontal scrollbar and CScrollBarHorisontal *scroll_h=this.GetScrollBarHorisontal();//this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL,0); if(scroll_h!=NULL) { //--- If the horizontal size of the scrollbar is changed to fit the size of the container workspace if(scroll_h.Resize(this.WidthWorkspace(),scroll_h.Height(),false)) { //--- Move the horizontal scrollbar to new coordinates if(scroll_h.Move(this.CoordXWorkspace(),this.BottomEdgeWorkspace()-scroll_h.Height())) { scroll_h.SetCoordXRelative(scroll_h.CoordX()-this.CoordX()); scroll_h.SetCoordYRelative(scroll_h.CoordY()-this.CoordY()); } } scroll_h.BringToTop(); } return true; } //+------------------------------------------------------------------+
Como ahora no debemos procesar el cambio de tamaño de las barras de desplazamiento aquí para desplazar sus botones a nuevos lugares, el método se ha vuelto más breve y claro. Cada barra de desplazamiento se trasladará al primer planotras cambiar de tamaño.
Método virtual que redibuja un objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CContainer::Redraw(bool redraw) { CWinFormBase::Redraw(redraw); this.BringToTopScrollBars(); } //+------------------------------------------------------------------+
Primero, redibujaremos el objeto usando el método de la clase principal y luego llamaremos al método para traer las barras de desplazamiento al primer plano.
Método que traslada las barras de desplazamiento al primer plano:
//+------------------------------------------------------------------+ //| Bring scrollbars to the foreground | //+------------------------------------------------------------------+ void CContainer::BringToTopScrollBars(void) { CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal(); CScrollBarVertical *sbv=this.GetScrollBarVertical(); if(sbh!=NULL) sbh.BringToTop(); if(sbv!=NULL) sbv.BringToTop(); } //+------------------------------------------------------------------+
Luego obtendremos los punteros a los objetos de la barra de desplazamiento, tanto horizontal como vertical, y, tras obtener correctamente el puntero, desplazaremos cada objeto al primer plano.
Método que actualiza las coordenadas:
//+------------------------------------------------------------------+ //| Update the coordinates | //+------------------------------------------------------------------+ bool CContainer::Move(const int x,const int y,const bool redraw=false) { if(!CForm::Move(x,y,redraw)) return false; this.BringToTopScrollBars(); return true; } //+------------------------------------------------------------------+
Primero, llamaremos al método de la clase principal para mover el objeto, luego, tras desplazarlo con éxito, traeremos las barras de desplazamiento al primer plano.
Ahora necesitaremos incluir los eventos de objetos de barra de desplazamiento en el procesamiento en la clase de colección de elementos gráficos.
En el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, en su manejador de eventos, en el bloque de procesamiento de la pulsación sobre los objetos, escribiremos el siguiente bloque de código para llamar al manejador de eventos del ratón de los objetos de barra de desplazamiento:
//+------------------------------------------------------------------+ //| Clicking the control | //+------------------------------------------------------------------+ if(idx==WF_CONTROL_EVENT_CLICK) { //--- If TabControl type is set in dparam if((ENUM_GRAPH_ELEMENT_TYPE)dparam==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { //--- Set the event type depending on the element type that generated the event int event_id= (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); //--- If the base control is received, call its event handler if(base_elm!=NULL) base_elm.OnChartEvent(event_id,lparam,dparam,sparam); } //--- If the base object is a horizontal or vertical scrollbar if(base!=NULL && (base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL)) { //--- Set the event type depending on the element type that generated the event int event_id= (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); //--- Call the event handler of the base element base.OnChartEvent(event_id,lparam,dparam,sparam); } } //+------------------------------------------------------------------+ //| Selecting the TabControl tab | //+------------------------------------------------------------------+ if(idx==WF_CONTROL_EVENT_TAB_SELECT) { if(base!=NULL) base.OnChartEvent(idx,lparam,dparam,sparam); }
Ya estamos preparados para las pruebas.
Simulación
Para la prueba, tomaremos el asesor experto del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part131\ con el nuevo nombre TestDoEasy131.mq5.
La última vez creamos en el panel un objeto de botón grande. Ahora le añadiremos texto para poder ver el desplazamiento horizontal del objeto al clicar en los botones de desplazamiento, y colocaremos un objeto de etiqueta de texto en el botón para poder ver que todos los objetos adjuntos se desplazan correctamente:
//--- Create the required number of WinForms Panel objects CPanel *pnl=NULL; for(int i=0;i<FORMS_TOTAL;i++) { pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { pnl.Hide(); //--- Set Padding to 4 pnl.SetPaddingAll(3); //--- 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); //--- pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,-40,10,pnl.WidthWorkspace()+80,pnl.HeightWorkspace()-30,clrNONE,255,true,false); CButton *btn=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,0); btn.SetText("123456789012345678901234567890"); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,40,20,60,20,clrDodgerBlue,255,false,false); CLabel *lbl=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LABEL,0); lbl.SetText("LABEL"); /* //--- Create TabControl pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false); CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tc!=NULL) {
Vamos a compilar el asesor y a ejecutarlo en el gráfico:
Como podemos ver, el desplazamiento controlado por los botones con flechas funciona correctamente. Al intentar mover el control deslizante con el ratón, este "se resiste", lo cual es natural: aún no hemos implementado el procesamiento del desplazamiento del control deslizante, pero ya hemos creado el recálculo de sus dimensiones y coordenadas. Como consecuencia, al intentar mover el control deslizante con el ratón, el método de configuración de sus coordenadas lo retorna a la posición que se corresponde con la posición del contenido del contenedor en su área visible. En artículos posteriores, mejoraremos todo lo necesario para que funcione correctamente.
¿Qué es lo próximo?
En el próximo artículo, continuaremos desarrollando el control ScrollBar.
*Artículos de esta serie:
DoEasy. Elementos de control (Parte 26): Mejoramos el objeto WinForms "ToolTip" y comenzamos a desarrollar "ProgressBar"
DoEasy. Elementos de control (Parte 27): Seguimos trabajando en el objeto WinForms "ProgressBar"
DoEasy. Elementos de control (Parte 28): Estilos de barra en el control «ProgressBar»
DoEasy. Elementos de control (Parte 29): Control auxiliar "ScrollBar"
DoEasy. Elementos de control (Parte 30): Animando el elemento de control "ScrollBar"
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/11926
![Características del Wizard MQL5 que debe conocer (Parte 5): Cadenas de Markov](https://c.mql5.com/2/51/markov_chains_avatar.png)
![Algoritmos de optimización de la población: Algoritmo de luciérnagas (Firefly Algorithm - FA)](https://c.mql5.com/2/51/firefly_algorithm_avatar.png)
![Aprendiendo a diseñar un sistema comercial con Gator Oscillator](https://c.mql5.com/2/51/trading-system-by-Alligator-002q1g.png)
![Aprendizaje automático y Data Science (Parte 10): Regresión de cresta](https://c.mql5.com/2/51/ridge_regression_avatar.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso