English Русский 中文 Deutsch 日本語 Português
preview
DoEasy. Elementos de control (Parte 31): Desplazamiento por el contenido del control "ScrollBar"

DoEasy. Elementos de control (Parte 31): Desplazamiento por el contenido del control "ScrollBar"

MetaTrader 5Ejemplos | 7 abril 2023, 10:13
285 0
Artyom Trishkin
Artyom Trishkin

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.

Volver al contenido

*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

Archivos adjuntos |
MQL5.zip (4551.42 KB)
Características del Wizard MQL5 que debe conocer (Parte 5): Cadenas de Markov Características del Wizard MQL5 que debe conocer (Parte 5): Cadenas de Markov
Las cadenas de Markov son una poderosa herramienta matemática que se puede usar para modelar y predecir los datos de las series temporales en varios campos, incluido el financiero. En el modelado y la previsión de series temporales financieras, las cadenas de Markov se usan a menudo para modelar la evolución de los activos financieros a lo largo del tiempo, como los precios de las acciones o los tipos de cambio. Una de las principales ventajas de los modelos de cadenas de Markov es su simplicidad y sencillez de uso.
Algoritmos de optimización de la población: Algoritmo de luciérnagas (Firefly Algorithm - FA) Algoritmos de optimización de la población: Algoritmo de luciérnagas (Firefly Algorithm - FA)
Hoy analizaremos el método de optimización «Búsqueda con ayuda del algoritmo de luciérnagas» 'Firefly Algorithm Search' (FA). Tras modificar el algoritmo, este ha pasado de ocupar un lugar marginal a convertirse en un verdadero líder en la tabla de calificación.
Aprendiendo a diseñar un sistema comercial con Gator Oscillator Aprendiendo a diseñar un sistema comercial con Gator Oscillator
Bienvenidos a un nuevo artículo de la serie dedicada a la creación de sistemas comerciales basados en indicadores técnicos populares. En esta ocasión, hablaremos sobre el indicador Gator Oscillator y crearemos un sistema comercial utilizando estrategias simples.
Aprendizaje automático y Data Science (Parte 10): Regresión de cresta Aprendizaje automático y Data Science (Parte 10): Regresión de cresta
La regresión de cresta (Ridge Regression) es una técnica simple para reducir la complejidad del modelo y combatir el ajuste que puede derivar de una regresión lineal simple.