English Русский 中文 Deutsch 日本語 Português
preview
DoEasy. Elementos de control (Parte 33): "ScrollBar" vertical

DoEasy. Elementos de control (Parte 33): "ScrollBar" vertical

MetaTrader 5Ejemplos | 2 agosto 2024, 15:13
221 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

En el último artículo sobre elementos gráficos de la biblioteca, creamos una barra de desplazamiento horizontal que aparece en un objeto cuando un objeto adjunto a un formulario se extiende más allá de los límites de su formulario padre por la izquierda, la derecha o ambos bordes. Hoy, basándonos en el objeto barra de desplazamiento horizontal, crearemos una barra de desplazamiento vertical. Aparecerá en el formulario si el objeto unido a él se extiende más allá de sus límites en la parte superior, inferior o en ambas.

El artículo será pequeño, será más bien incluso una visión general, ya que no resultará muy difícil hacer una copia de un objeto de barra de desplazamiento horizontal y convertirla en una vertical. Eso sí, necesitaremos estas barras al desarrollar posteriores controles de estilo Windows Form. De hecho, desarrollamos la barra de desplazamiento vertical hace tiempo, pero la publicación del artículo se retrasó debido a un pequeño error, mejor dicho, ni siquiera un error, sino una omisión que provocaba artefactos muy desagradables al interactuar con elementos gráficos; dichos artefactos se manifestaban en un "parpadeo" constante de las partes invisibles de los objetos. Esto se debía a una actualización prematura e incontrolada de los objetos, que posteriormente se recortaban al tamaño de su progenitor. Así es como apareció este "parpadeo": primero se dibujaba el objeto por completo, se mostraba en el gráfico y luego se recortaba al tamaño del objeto de formulario padre. La solución, como suele ocurrir en estas situaciones, resultó ser la más sencilla: eliminar la actualización prematura con redibujado. Pero tardamos mucho tiempo en encontrar dónde se estaba produciendo el redibujado. Ahora este error ha sido encontrado y solucionado, y podemos seguir desarrollando la biblioteca con total seguridad.


Mejorando las clases de la biblioteca

En primer lugar, añadiremos funciones y métodos útiles que nos servirán en las modificaciones posteriores de la biblioteca.

A veces debemos encontrar la hora de apertura de la barra en la que se ha producido un evento. Si el evento se ha producido en el momento de la apertura de la vela, no habrá problemas para encontrar la hora de apertura de la misma. Pero si el evento ha sucedido entre la hora de apertura y la hora de cierre de esta vela, podremos utilizar la hora de este evento para calcular la hora de apertura de la vela en un periodo gráfico determinado. Claro que podemos utilizar las funciones estándar, convertir la hora del evento en número de barra, y luego, según el número de barra, obtener la hora de apertura de la vela requerida en el periodo del gráfico necesario... Sin embargo, todo esto requiere tiempo de CPU. No obstante, cuando la velocidad de ejecución es importante, es mejor utilizar el cálculo, siempre que el evento haya tenido lugar dentro de una vela real existente.

Aquí, en el foro, hay un hilo útil donde los usuarios de recursos comparten este tipo de códigos interesantes. Vamos a utilizar el algoritmo propuesto y a escribir una función para la biblioteca.

En el archivo de la biblioteca \MQL5\Include\DoEasy\Services\DELib.mqh, al final de la misma, escribiremos una función:

//+------------------------------------------------------------------------------------+
//| Get the opening time of the virtual bar based on input time and                    |
//| timeframe, regardless of the existence of a real bar.                              |
//| It counts correctly only till 28.02.2100                                           |
//| It is not a replacement for iBarShift!!! It does not depend on the bar history.    |
//| https://www.mql5.com/ru/forum/170952/page234#comment_50523898                      |
//+------------------------------------------------------------------------------------+
datetime GetStartTimeOfBarFast(const ENUM_TIMEFRAMES timeframe, const datetime time)
  {
   ENUM_TIMEFRAMES tf=(timeframe==PERIOD_CURRENT ? _Period : timeframe);

   int ts=0;
   if(tf<PERIOD_MN1)
     {
      ushort i_tf=ushort(tf);
      uchar _i=uchar(i_tf>>14);
      int n=i_tf & 0x0FFF;
      ts=(_i==0 ? n*60 : _i==1 ? n*60*60 : 60*60*24*7);
     }
   if(tf<PERIOD_W1)
      return time-time % ts;
   if(tf==PERIOD_W1)
      return time-(time+4*24*60*60) % ts;
   else // Period MN1
     {
      static int dm[12] = {0,31,61,92,122,153,184, 214, 245, 275, 306, 337};
      static int last_days = 0;
      static datetime last_result = 0;
      int days = int(time/(24*60*60));
      if(last_days!=days)
        {
         last_days = days;
         int d1 = (days+306+365)%1461;
         int y = d1/365;
         datetime t1 = time - time % (24*60*60) - d1*24*60*60;
         int m = 0;
         if(d1==1460)
           {
            m=11;
            y--;
           };
         int d = d1-y*365+1;
         if(d!=31)
            if(d==276)
               m = 9;
            else
               m = int(d/30.68);
         if(m<0 || m>11)
            return WRONG_VALUE;
         last_result = t1+y*365*24*60*60+dm[m]*24*60*60;
        }
      return last_result;
     }
  }
//+------------------------------------------------------------------+

El desglose del algoritmo se encuentra en el foro en el enlace anterior. Si está interesado puede leerlo. En el futuro, usando esta función, siempre podremos encontrar la hora de apertura de la barra dentro de la cual se ha producido un evento. No tendremos que recurrir a funciones insuficientemente rápidas cuando la velocidad de cálculo sea importante.

Al trabajar con objetos gráficos, a veces es necesario cambiar el color de un objeto gráfico en función de la situación. Obviamente, podemos utilizar colores de la lista de colores estándar, pero a menudo no resulta suficiente. Por ejemplo, tenemos un objeto, digamos de color gris neutro. Puede cambiar ligeramente el tono según la situación. En un caso puede cambiar a un color ligeramente rojizo, en otro caso puede cambiar a un color ligeramente verdoso. Es decir, en este caso, solo deberemos añadir un poco de saturación a un componente de color concreto, en lugar de establecer los colores a partir del conjunto estándar.

Para ello, en el archivo del objeto de elemento gráfico \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, en la sección pública, declararemos un método:

//--- Change the lightness of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorLightness(const uint clr,const double change_value);
   color             ChangeColorLightness(const color colour,const double change_value);
//--- Change the saturation of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorSaturation(const uint clr,const double change_value);
   color             ChangeColorSaturation(const color colour,const double change_value);
//--- Changes the color component of RGB-Color
   color             ChangeRGBComponents(color clr,const uchar R,const uchar G,const uchar B);
   

Escribiremos su implementación fuera del cuerpo de la misma:

//+------------------------------------------------------------------+
//| Change the color component of RGB-Color                          |
//+------------------------------------------------------------------+
color CGCnvElement::ChangeRGBComponents(color clr,const uchar R,const uchar G,const uchar B)
  {
   double r=CColors::GetR(clr)+R;
   if(r>255)
      r=255;
   double g=CColors::GetG(clr)+G;
   if(g>255)
      g=255;
   double b=CColors::GetB(clr)+B;
   if(b>255)
      b=255;
   return CColors::RGBToColor(r,g,b);
  }
//+------------------------------------------------------------------+
//| Save the image to the array                                      |
//+------------------------------------------------------------------+

Aquí todo resulta simple: obtendremos cada uno de los componentes del color transmitido al método que deberá ser cambiado, y luego añadiremos a los valores de los componentes recibidos los valores correspondientes pasados al método. Si alguno de los valores llega a ser superior a 255, lo corregiremos al valor 255. Como resultado, retornaremos el color ensamblado a partir de los nuevos constituyentes calculados utilizando el método RGBToColor de la clase CColor de la biblioteca.

Aquí, en el mismo archivo, tenemos un método que establecerá las coordenadas y dimensiones del área visible del elemento gráfico:

//--- Set relative coordinates and size of the visible area
   void              SetVisibleArea(const int x,const int y,const int w,const int h)
                       {
                        this.SetVisibleAreaX(x,false);
                        this.SetVisibleAreaY(y,false);
                        this.SetVisibleAreaWidth(w,false);
                        this.SetVisibleAreaHeight(h,false);
                       }

Le añadiremos la posibilidad de especificar cómo se establece el ámbito: solo en las propiedades del objeto elemento gráfico, o en las propiedades y en el objeto físico. Para ello, simplemente añadiremos otra variable de entrada y, en consecuencia, arreglaremos la llamada al método que establecerá el ámbito al tamaño completo del objeto:

//--- Set relative coordinates and size of the visible area
   void              SetVisibleArea(const int x,const int y,const int w,const int h,const bool only_prop)
                       {
                        this.SetVisibleAreaX(x,only_prop);
                        this.SetVisibleAreaY(y,only_prop);
                        this.SetVisibleAreaWidth(w,only_prop);
                        this.SetVisibleAreaHeight(h,only_prop);
                       }
//--- Sets the size of the visible area equal to the entire object
   void              ResetVisibleArea(void)                    { this.SetVisibleArea(0,0,this.Width(),this.Height(),false);            }


En la implementación del método que limpiará el elemento con relleno de color y opacidad sin recorte y con actualización del gráfico según la bandera, necesitaremos cambiar ligeramente la lógica de actualización del objeto. Antes, el objeto siempre se actualizaba aquí independientemente de si se había activado la bandera de redibujado del gráfico:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//| without cropping and with the chart update by flag               |
//+------------------------------------------------------------------+
void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false)
  {
   color arr[1];
   arr[0]=colour;
   this.SaveColorsBG(arr);
   this.m_canvas.Erase(::ColorToARGB(colour,opacity));
   this.Update(redraw);
  }

El método Update() con la bandera de redibujado del gráfico aquí siempre actualiza el objeto después de que haya sido completamente dibujado con el color especificado en los parámetros del método EraseNoCrop(). En consecuencia, independientemente de la bandera de redibujado, el objeto siempre se actualizaba, es decir, se mostraban los cambios realizados en este. De la bandera de redibujado solo dependía el momento de la visualización de los cambios: o bien inmediatamente (si la bandera se había establecido en true), o bien al inicio de la actualización del gráfico (si la bandera se había establecido en false). Y como este método vuelve a colorear completamente todo el objeto, este puede mostrarse en su tamaño completo en el gráfico en cualquier momento. Si este objeto debe ser recortado al tamaño de su objeto padre al que está unido, este redibujado causaba un desagradable "parpadeo" de la parte invisible del objeto, porque el recorte de la parte invisible siempre se efectúa después de llamar a este método.
Esto ya se ha solucionado y el parpadeo de la parte invisible del objeto ha desaparecido:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//| without cropping and with the chart update by flag               |
//+------------------------------------------------------------------+
void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false)
  {
   color arr[1];
   arr[0]=colour;
   this.SaveColorsBG(arr);
   this.m_canvas.Erase(::ColorToARGB(colour,opacity));
   if(redraw)
      this.Update(redraw);
  }

En este caso, solo se llamará a la actualización del objeto cuando se active la bandera de redibujado. En consecuencia, ahora podremos controlar la visualización del objeto de forma programática: si estamos seguros de que el objeto no debe recortarse, llamaremos al método con la bandera activada, y su nuevo aspecto se mostrará inmediatamente en el gráfico. Si el objeto debe ser recortado, este método se llamará primero con la bandera reseteada, y luego se llamará al método Crop(), recortando las áreas ocultas y actualizando la apariencia del objeto con el redibujado del gráfico según la bandera. Era este error lógico el que impedía seguir desarrollando los elementos gráficos de la biblioteca. Por fortuna, ya lo hemos corregido

Ahora, en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\BarProgressBar.mqh, en el manejador del temporizador, corregiremos la llamada del método SetVisibleArea() especificando la bandera requerida:

//--- ...
//--- ...

//--- If the object is in the normal state (hidden)
   if(glare.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_NORMAL)
     {
      //--- set the state of waiting for fading in to the object (in our case, waiting for a shift along the progress bar),
      //--- set the waiting duration and set the countdown time
      glare.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN);
      this.m_pause.SetWaitingMSC(this.ShowDelay());
      this.m_pause.SetTimeBegin();
      //--- If the right edge of the glare object is to the right of the left edge of the progress bar object
      if(glare.RightEdge()>=this.CoordX())
        {
         //--- Hide the glare object and move it beyond the right edge of the progress bar
         glare.Hide();
         if(glare.Move(this.CoordX()-glare.Width(),this.CoordY()))
           {
            //--- Set the relative coordinates of the glare object
            glare.SetCoordXRelative(glare.CoordX()-this.CoordX());
            glare.SetCoordYRelative(glare.CoordY()-this.CoordY());
            //--- and its visibility scope equal to the entire object
            glare.SetVisibleArea(0,0,glare.Width(),glare.Height(),false);
           }
        }
      return;
     }

//--- ...
//--- ...


Casi todos los objetos de la biblioteca tienen un objeto de gestión del gráfico que permite crear dinámicamente objetos de formulario gráficos. Vamos a añadir los métodos que permiten crear algunos objetos gráficos estándar. En el archivo de clase del objeto de gestión de objetos gráficos \MQL5\Include\DoEasy\Objects\Graph\GraphElmControl.mqh, en la sección pública, declararemos los nuevos métodos para dibujar líneas de tendencia y flechas:

public:
//--- Return itself
   CGraphElmControl *GetObject(void)                  { return &this;               }
//--- Set a type of the object the graphics is constructed for
   void              SetTypeNode(const int type_node) { this.m_type_node=type_node; }
   
//--- Create a form object
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h);

//--- Creates the trend line standard graphical object
   bool              CreateTrendLine(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID);
   bool              CreateTrendLine(const string name,const int subwindow,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID);
   bool              CreateTrendLine(const string name,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID);

//--- Create the arrow standard graphical object
   bool              CreateArrow(const long chart_id,const string name,const int subwindow,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1);
   bool              CreateArrow(const string name,const int subwindow,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1);
   bool              CreateArrow(const string name,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1);

//--- Constructors
                     CGraphElmControl(){ this.m_type=OBJECT_DE_TYPE_GELEMENT_CONTROL; }
                     CGraphElmControl(int type_node);
  };


En la sección privada, declararemos un método para establecer los parámetros comunes para los objetos gráficos estándar:

//+------------------------------------------------------------------+
//| Class for managing graphical elements                            |
//+------------------------------------------------------------------+
class CGraphElmControl : public CObject
  {
private:
   int               m_type;                          // Object type
   int               m_type_node;                     // Type of the object the graphics is constructed for
//--- Set general parameters for standard graphical objects
   void              SetCommonParamsStdGraphObj(const long chart_id,const string name);
public:
//--- Return itself
   CGraphElmControl *GetObject(void)                  { return &this;               }

A cada objeto recién creado se le deberán asignar algunas propiedades por defecto, cuyo valor será el mismo para todos los objetos gráficos creados sin excepción: el objeto deberá estar oculto en la lista de todos los objetos gráficos, no deberá estar seleccionado y no podrá seleccionarse con el ratón, y deberá mostrarse en todos los marcos temporales. Precisamente estas propiedades las establecerá el método SetCommonParamsStdGraphObj, que se implementa fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//|Set general parameters for standard graphical objects             |
//+------------------------------------------------------------------+
void CGraphElmControl::SetCommonParamsStdGraphObj(const long chart_id,const string name)
  {
   ::ObjectSetInteger(chart_id,name,OBJPROP_HIDDEN,true);
   ::ObjectSetInteger(chart_id,name,OBJPROP_SELECTED,false);
   ::ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);
   ::ObjectSetInteger(chart_id,name,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS);
  }


También fuera del cuerpo de la clase escribiremos la implementación de los métodos que crean los objetos gráficos:

//+------------------------------------------------------------------+
//| Create the trend line standard graphical object                  |
//| on a specified chart in a specified subwindow                    |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateTrendLine(const long chart_id,const string name,const int subwindow,
                                       const datetime time1,const double price1,
                                       const datetime time2,const double price2,
                                       color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
  {
   if(!CreateNewStdGraphObject(chart_id,name,OBJ_TREND,subwindow,time1,price1,time2,price2))
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),": ",StdGraphObjectTypeDescription(OBJ_TREND));
      return false;
     }
   this.SetCommonParamsStdGraphObj(chart_id,name);
   ::ObjectSetInteger(chart_id,name,OBJPROP_COLOR,clr);
   ::ObjectSetInteger(chart_id,name,OBJPROP_WIDTH,width);
   ::ObjectSetInteger(chart_id,name,OBJPROP_STYLE,style);
   return true;
  }
//+------------------------------------------------------------------+
//| Create the trend line standard graphical object                  |
//| on the current chart in a specified subwindow                    |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateTrendLine(const string name,const int subwindow,
                                       const datetime time1,const double price1,
                                       const datetime time2,const double price2,
                                       color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
  {
   return this.CreateTrendLine(::ChartID(),name,subwindow,time1,price1,time2,price2,clr,width,style);
  }
//+------------------------------------------------------------------+
//| Create the trend line standard graphical object                  |
//| on the current chart in the main window                          |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateTrendLine(const string name,
                                       const datetime time1,const double price1,
                                       const datetime time2,const double price2,
                                       color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
  {
   return this.CreateTrendLine(::ChartID(),name,0,time1,price1,time2,price2,clr,width,style);
  }
//+------------------------------------------------------------------+
//| Create the arrow standard graphical object                       |
//| on a specified chart in a specified subwindow                    |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateArrow(const long chart_id,const string name,const int subwindow,
                                   const datetime time1,const double price1,
                                   color clr,uchar arrow_code,int width=1)
  {
   if(!CreateNewStdGraphObject(chart_id,name,OBJ_ARROW,subwindow,time1,price1))
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),": ",StdGraphObjectTypeDescription(OBJ_ARROW));
      return false;
     }
   this.SetCommonParamsStdGraphObj(chart_id,name);
   ::ObjectSetInteger(chart_id,name,OBJPROP_COLOR,clr);
   ::ObjectSetInteger(chart_id,name,OBJPROP_WIDTH,width);
   ::ObjectSetInteger(chart_id,name,OBJPROP_ARROWCODE,arrow_code);
   return true;
  }
//+------------------------------------------------------------------+
//| Create the arrow standard graphical object                       |
//| on the current chart in a specified subwindow                    |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateArrow(const string name,const int subwindow,
                                   const datetime time1,const double price1,
                                   color clr,uchar arrow_code,int width=1)
  {
   return this.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width);
  }
//+------------------------------------------------------------------+
//| Create the arrow standard graphical object                       |
//| on the current chart in the main window                          |
//+------------------------------------------------------------------+
bool CGraphElmControl::CreateArrow(const string name,
                                   const datetime time1,const double price1,
                                   color clr,uchar arrow_code,int width=1)
  {
   return this.CreateArrow(::ChartID(),name,0,time1,price1,clr,arrow_code,width);
  }


Existe un ejemplar de un objeto de la clase de gestión de objetos gráficos como parte de cada objeto de biblioteca heredado de la clase de objeto básico de la biblioteca CBaseObj. El objeto de gestión de objetos gráficos dispone de métodos para crear dichos objetos, pero para poder crear objetos gráficos a partir de la clase de cualquier objeto, necesitaremos escribir métodos para crear objetos gráficos en la clase del objeto básico. Esto simplificará el desarrollo de los gráficos en las aplicaciones. En esencia, primero podemos obtener un puntero al objeto requerido, luego obtener el puntero a su objeto de gestión de objetos gráficos y, a continuación, haciendo referencia a sus métodos, crear los objetos gráficos. Pero falta mucho aún. Resulta más fácil, cómodo y rápido simplemente obtener un puntero a un objeto y utilizar sus métodos para crear objetos gráficos dentro de los cuales se ejecutará toda la cadena descrita anteriormente.

En el archivo de objeto básico de la biblioteca \MQL5\Include\DoEasy\Objects\BaseObj.mqh, en su sección pública, en el apartado de trabajo con objetos gráficos, declararemos nuevos métodos para crear líneas de tendencia y objetos de flecha:

//+------------------------------------------------------------------+
//| Methods for handling graphical elements                          |
//+------------------------------------------------------------------+
//--- Create a form object on a specified chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,chart_id,wnd,name,x,y,w,h);                }
//--- Create a form object on the current chart in a specified subwindow
   CForm            *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,wnd,name,x,y,w,h);                         }
//--- Create the form object on the current chart in the main window
   CForm            *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h)
                       { return this.m_graph_elm.CreateForm(form_id,name,x,y,w,h);                             }
   
//--- Create a standard graphical trend line object in the specified subwindow of the specified chart
   bool              CreateTrendLine(const long chart_id,const string name,const int subwindow,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
                       { return this.m_graph_elm.CreateTrendLine(chart_id,name,subwindow,time1,price1,time2,price2,clr,width,style);   }
//--- Create a standard graphical trend line object in the specified subwindow of the current chart
   bool              CreateTrendLine(const string name,const int subwindow,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
                       { return this.m_graph_elm.CreateTrendLine(::ChartID(),name,subwindow,time1,price1,time2,price2,clr,width,style);}
//--- Create a standard graphical trend line object in the main window of the current chart
   bool              CreateTrendLine(const string name,
                                     const datetime time1,const double price1,
                                     const datetime time2,const double price2,
                                     color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID)
                       { return this.m_graph_elm.CreateTrendLine(::ChartID(),name,0,time1,price1,time2,price2,clr,width,style);        }
   
//--- Create a standard arrow graphical object in the specified subwindow of the specified chart
   bool              CreateArrow(const long chart_id,const string name,const int subwindow,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1)
                       { return this.m_graph_elm.CreateArrow(chart_id,name,subwindow,time1,price1,clr,arrow_code,width);               }
//--- Create a standard arrow graphical object in the specified subwindow of the current chart
   bool              CreateArrow(const string name,const int subwindow,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1)
                       { return this.m_graph_elm.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width);            }
//---  Create a standard arrow graphical object in the main window of the current chart
   bool              CreateArrow(const string name,
                                 const datetime time1,const double price1,
                                 color clr,uchar arrow_code,int width=1)
                       { return this.m_graph_elm.CreateArrow(::ChartID(),name,0,time1,price1,clr,arrow_code,width);                    }
   
//--- Constructor

Los métodos simplemente llamarán a los métodos correspondientes del objeto de gestión del gráfico. Más adelante añadiremos los métodos necesarios para crear otros objetos gráficos estándar. Por ahora, tenemos suficientes objetos gráficos de estos para utilizarlos en futuros artículos.

Empezaremos creando el objeto de barra de desplazamiento vertical.

El área de captura de la barra de desplazamiento será un control deslizante que se podrá agarrar con el ratón y desplazarse dentro de la barra de desplazamiento, moviendo así el área que controla. Al desplazar la rueda del ratón en una u otra dirección, cuando el cursor se encuentra dentro de la barra de desplazamiento, se generará un evento de clic en el botón de control de desplazamiento correspondiente (botones de flecha situados en los bordes de la barra de desplazamiento). Ya tenemos eventos generados al desplazar la rueda del ratón para la barra de desplazamiento horizontal: los eventos de clic de botón izquierdo y derecho. Ahora deberemos añadir la generación de eventos en los botones de flecha arriba y abajo para el control deslizante de la barra de desplazamiento vertical.

En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh, en el manejador de eventos "Cursor dentro del área activa, la rueda del ratón se desplaza", haremos los siguientes cambios:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
void CScrollBarThumb::MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
   base.BringToTop();
   ENUM_WF_CONTROL_EVENT evn=WF_CONTROL_EVENT_NO_EVENT;
   switch(base.TypeGraphElement())
     {
      case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL: evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : WF_CONTROL_EVENT_NO_EVENT); break;
      case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL  : evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP   : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN  : WF_CONTROL_EVENT_NO_EVENT); break;
      default                                         : break;
     }
   base.OnChartEvent(evn,lparam,dparam,sparam);
   ::ChartRedraw(base.ChartID());
  }

Dependiendo de si la barra de desplazamiento horizontal o vertical es el objeto básico para el área de captura, llamaremos al manejador de eventos del objeto básico y le transmitiremos los códigos de evento correspondientes: el clic del ratón en el botón de flecha izquierda o derecha, o el clic del ratón en el botón de flecha arriba o abajo.

Para crear un objeto scrollbar vertical, tomaremos archivo de clase del objeto scrollbar horizontal \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh y lo guardaremos con el nombre \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarVertical.mqh. Como la nueva clase se crea a partir de una clase idéntica, solo tendremos que sustituir algunos cálculos: en lugar de "izquierda/derecha" en los cálculos, utilizaremos "arriba/abajo", y otras cosas por el estilo. No tiene sentido describir todos los cambios efectuados. Podrá leer más sobre la creación de un objeto de este tipo en el artículo correspondiente. Aquí solo veremos el archivo de clase completo con las modificaciones ya realizadas:

//+------------------------------------------------------------------+
//|                                            ScrollBarVertical.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ScrollBarThumb.mqh"
#include "ArrowDownButton.mqh"
#include "ArrowUpButton.mqh"
#include "ScrollBar.mqh"
//+------------------------------------------------------------------+
//| CScrollBarVertical object class of WForms controls               |
//+------------------------------------------------------------------+
class CScrollBarVertical : 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
                     CScrollBarVertical(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);
                                        
//--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler
   virtual void      MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
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 button with the (1) up, (2) down arrow
   CArrowUpButton   *GetArrowButtonUp(void)     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0);      }
   CArrowDownButton *GetArrowButtonDown(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,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
                     CScrollBarVertical(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);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CScrollBarVertical::CScrollBarVertical(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) : CScrollBar(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.CreateThumbArea();
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CScrollBarVertical::CScrollBarVertical(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) : CScrollBar(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL);
   this.CreateThumbArea();
  }
//+------------------------------------------------------------------+
//| Create the ArrowButton objects                                   |
//+------------------------------------------------------------------+
void CScrollBarVertical::CreateArrowButtons(const int width,const int height)
  {
//--- Set the size of the buttons equal to the width of the scrollbar without the size of its frame
   int size=this.Thickness()-this.BorderSizeLeft()-this.BorderSizeRight();
//--- Create the buttons with up and down arrows and the area capture object. The arrow size is set to 2
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,  0,0,size,size,this.BackgroundColor(),255,true,false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0,this.Height()-height,size,size,this.BackgroundColor(),255,true,false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB,0,this.Height()/2-height,size,30,CLR_DEF_CONTROL_SCROLL_BAR_THUMB_COLOR,255,true,false);
   this.SetArrowSize(2);
//--- Get the pointer to the up arrow button and set the colors of its various states for it
   CArrowUpButton *bu=this.GetArrowButtonUp();
   if(bu!=NULL)
     {
      bu.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_COLOR,true);
      bu.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_DOWN);
      bu.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_OVER);
      bu.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_COLOR,true);
      bu.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_DOWN);
      bu.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_OVER);
     }
//--- Get the pointer to the down arrow button and set the colors of its various states for it
   CArrowDownButton *bd=this.GetArrowButtonDown();
   if(bd!=NULL)
     {
      bd.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_COLOR,true);
      bd.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_DOWN);
      bd.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_OVER);
      bd.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_COLOR,true);
      bd.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_DOWN);
      bd.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_OVER);
     }
//--- Get the pointer to the capture area object and set the colors of its various states for it
   CScrollBarThumb *th=this.GetThumb();
   if(th!=NULL)
     {
      th.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_COLOR,true);
      th.SetBorderColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_BORDER_COLOR,true);
      th.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_MOUSE_DOWN);
      th.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_MOUSE_OVER);
      th.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_COLOR,true);
      th.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_MOUSE_DOWN);
      th.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_MOUSE_OVER);
     }
  }
//+------------------------------------------------------------------+
//| Set the new size                                                 |
//+------------------------------------------------------------------+
bool CScrollBarVertical::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 down arrow
   CArrowDownButton *bd=this.GetArrowButtonDown();
//--- If the button is not received, return 'false'
   if(bd==NULL)
      return false;
//--- Move the button to the bottom edge of the scrollbar
   if(bd.Move(bd.CoordX(),this.BottomEdge()-this.BorderSizeBottom()-bd.Height()))
     {
      //--- Set new relative coordinates for the button
      bd.SetCoordXRelative(bd.CoordX()-this.CoordX());
      bd.SetCoordYRelative(bd.CoordY()-this.CoordY());
     }
//--- Set the slider parameters
   this.SetThumbParams();
//--- Successful
   return true;
  }
//+------------------------------------------------------------------+
//| Calculate and set the parameters of the capture area (slider)    |
//+------------------------------------------------------------------+
int CScrollBarVertical::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 height size of the visible part inside the container
   int base_h=base.HeightWorkspace();
//--- Calculate the total height of all attached objects
   int objs_h=base_h+base.OversizeTop()+base.OversizeBottom();
//--- Calculate the relative size of the visible part window
   double px=(double)base_h/double(objs_h!=0 ? objs_h : 1);
//--- Calculate and adjust the size of the slider relative to the height of its workspace (not less than the minimum size)
   int thumb_size=(int)::floor(this.BarWorkAreaSize()*px);
   if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN)
      thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
   if(thumb_size>this.BarWorkAreaSize())
      thumb_size=this.BarWorkAreaSize();
//--- Calculate the coordinate of the slider and change its size to match the previously calculated one
   int thumb_y=this.CalculateThumbAreaDistance(thumb_size);
   if(!thumb.Resize(thumb.Width(),thumb_size,true))
      return 0;
//--- Shift the slider by the calculated Y coordinate
   if(thumb.Move(thumb.CoordX(),this.BarWorkAreaCoord()+thumb_y))
     {
      thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX());
      thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY());
     }
//--- Return the calculated slider size
   return thumb_size;
  }
//+------------------------------------------------------------------+
//| Calculate the distance of the capture area (slider)              |
//+------------------------------------------------------------------+
int CScrollBarVertical::CalculateThumbAreaDistance(const int thumb_size)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return 0;
   double x=(double)thumb_size/(double)base.HeightWorkspace();
   return (int)::ceil((double)base.OversizeTop()*x);
  }
//+------------------------------------------------------------------+
//| Return the size of the slider working area                       |
//+------------------------------------------------------------------+
int CScrollBarVertical::BarWorkAreaSize(void)
  {
   CArrowUpButton  *bu=this.GetArrowButtonUp();
   CArrowDownButton *bd=this.GetArrowButtonDown();
   int y1=(bu!=NULL ? bu.BottomEdge() : this.CoordY()+this.BorderSizeTop());
   int y2=(bd!=NULL ? bd.CoordY() : this.BottomEdge()-this.BorderSizeBottom());
   return(y2-y1);
  }
//+------------------------------------------------------------------+
//| Return the coordinate of the beginning of the slider working area|
//+------------------------------------------------------------------+
int CScrollBarVertical::BarWorkAreaCoord(void)
  {
   CArrowUpButton  *bu=this.GetArrowButtonUp();
   return(bu!=NULL ? bu.BottomEdge() : this.CoordY()+this.BorderSizeTop());
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CScrollBarVertical::OnTimer(void)
  {

  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CScrollBarVertical::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
   CArrowUpButton  *buttu=this.GetArrowButtonUp();
   CArrowDownButton *buttd=this.GetArrowButtonDown();
   CScrollBarThumb   *thumb=this.GetThumb();
   if(buttu==NULL || buttd==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 X coordinate equal to the X coordinate of the control element
      x=this.CoordX()+this.BorderSizeLeft();
      //--- Adjust the Y coordinate so that the capture area does not go beyond the control, taking into account the arrow buttons
      if(y<buttu.BottomEdge())
        y=buttu.BottomEdge();
      if(y>buttd.CoordY()-thumb.Height())
        y=buttd.CoordY()-thumb.Height();
      //--- 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());
        }
      //--- Get the pointer to the base object
      CWinFormBase *base=this.GetBase();
      if(base!=NULL)
        {
         //--- Check if the content goes beyond the container
         base.CheckForOversize();

         //--- Calculate the distance the slider is from the upper border of the scrollbar (from the bottom side of the upper arrow button)
         int distance=thumb.CoordY()-buttu.BottomEdge();
         
         //--- Declare a variable that stores the distance value before the slider shift
         static int distance_last=distance;
         //--- Declare a variable that stores the value in screen pixels the slider was shifted by
         int shift_value=0;
         
         //--- If the values of the past and current distances are not equal (the slider is shifted),
         if(distance!=distance_last)
           {
            //--- calculate the value the slider is shifted by
            shift_value=distance_last-distance;
            //--- and enter the new distance into the value of the previous distance for the next calculation
            distance_last=distance;
           }
         
         //--- Get the largest and smallest coordinates of the lower and upper sides of the base object content
         int cntt_d=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_BOTTOM);
         int cntt_u=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_Y);

         //--- Get the coordinate offset of the upper side of the base object content
         //--- relative to the initial coordinate of the base object working area
         int extu=base.CoordYWorkspace()-cntt_u;
         
         //--- Calculate the relative value of the desired coordinate,
         //--- where the contents of the base object, shifted by the slider, should be located
         double y=(double)this.HeightWorkspace()*(double)distance/double(thumb.Height()!=0 ? thumb.Height() : DBL_MIN);
         
         //--- Calculate the required shift value of the base object content along the above calculated coordinate 
         int shift_need=extu-(int)::round(y);
         
         //--- If the slider is shifted upwards (positive shift value)
         if(shift_value>0)
           {
            if(cntt_u+shift_need<=base.CoordYWorkspace())
               base.ShiftDependentObj(0,shift_need);
           }
         //--- If the slider is shifted downwards (negative shift value)
         if(shift_value<0)
           {
            if(cntt_d-shift_need>=base.BottomEdgeWorkspace())
               base.ShiftDependentObj(0,shift_need);
           }
         ::ChartRedraw(this.ChartID());
        }
     }
//--- If any scroll button is clicked
   if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP || id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN)
     {
      //--- 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 lower and upper sides of the base object content
      int cntt_d=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_BOTTOM);
      int cntt_u=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_Y);
      //--- Set the number of pixels, by which the content of the base object should be shifted
      int shift=(sparam!="" ? DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK : DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL);
      //--- If the up button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP)
        {
         if(cntt_u+shift<=base.CoordYWorkspace())
            base.ShiftDependentObj(0,shift);
        }
      //--- If the down button is clicked
      if(id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN)
        {
         if(cntt_d-shift>=base.BottomEdgeWorkspace())
            base.ShiftDependentObj(0,-shift);
        }
      //--- Calculate the width and coordinates of the slider
      this.SetThumbParams();
     }
  }
//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
void CScrollBarVertical::MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   ENUM_WF_CONTROL_EVENT evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT);
   this.OnChartEvent(evn,lparam,dparam,sparam);
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Cuando ya hayamos realizado los cambios en este archivo, la barra de desplazamiento vertical aparecerá en los objetos a los que se han adjuntado los objetos hijos, que se extenderán más allá del objeto padre en la parte superior, inferior o en ambos lados a la vez.

Para que las barras de desplazamiento aparezcan en el objeto padre que sea contenedor de hijos, deberemos comprobar si el objeto adjunto está fuera de su contenedor al crearlo. Esta comprobación ya la hemos realizado para mostrar la barra de desplazamiento horizontal. Ahora necesitaremos mejorar la clase del objeto de contenedor para que ambas barras de desplazamiento aparezcan si el objeto unido al contenedor se sale del contenedor por cualquiera de sus lados.

Ahora abriremos el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh y en el método de creación de objetos adjuntos escribiremos las comprobaciones necesarias y la visualización de las barras de desplazamiento:

//+------------------------------------------------------------------+
//| 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
        {
         if(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();
                 }
              }
            //--- If the attached objects go beyond the visibility window from above or below
            if(this.OversizeTop()>0 || this.OversizeBottom()>0)
              {
               CScrollBarVertical *sbv=this.GetScrollBarVertical();
               if(sbv!=NULL)
                 {
                  sbv.SetThumbParams();
                  sbv.SetDisplayed(true);
                  sbv.Show();
                 }
              }
           }
        }
     }
//--- Crop the created object along the edges of the visible part of the container
   obj.Crop();
//--- return 'true'
   return true;
  }


Ahora necesitaremos ajustar algunas clases que contienen la llamada al método para redibujar sin recortar el objeto EraseNoCrop(). Tendremos que especificar la bandera false para que no se produzca ninguna actualización del objeto dentro de este método.

Asimismo, deberemos realizar cambios en los métodos Redraw() de tres objetos en tres archivos de la biblioteca \MQL5\Include\DoEasy\Objects\Graph/WForms\WForms\Common Controls\Button.mqh, \MQL\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh y \MQL\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh.

Todos los cambios consistirán simplemente en especificar la bandera false:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CButton::Redraw(bool redraw)
  {
//--- Fill the object with the background color
   this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),false);
//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment
   int x=0,y=0;
   CLabel::SetTextParamsByAlign(x,y);
//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Crop();
   this.Update(redraw);
  }

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CCheckBox::Redraw(bool redraw)
  {
//--- Fill the object with the background color having full transparency
   this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),false);
//--- Set corrected text coordinates relative to the checkbox
   this.SetCorrectTextCoords();
//--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object 
   this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.ShowControlFlag(this.CheckState());
   this.Crop();
   this.Update(redraw);
  }

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CLabel::Redraw(bool redraw)
  {
//--- Fill the object with the background color having full transparency
   this.EraseNoCrop(this.BackgroundColor(),0,false);
//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment
   int x=0,y=0;
   this.SetTextParamsByAlign(x,y);
//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Crop();
   this.Update(redraw);
  }

La lógica es la siguiente: en primer lugar, el objeto completo se rellenará de color, con la bandera de actualización a cero, lo cual significa que los cambios no se mostrarán en el gráfico. A continuación, el texto se dibujará con los parámetros establecidos. Después, el objeto se recortará a lo largo de los bordes del área visible (de ser necesario) y, cuando termine, se llamará al método de actualización del objeto, que mostrará los cambios realizados en la representación del objeto en su objeto padre en particular, y en el gráfico en su conjunto.


Simulación

Para la prueba, tomaremos el asesor del artículo anterior y lo guardaremos en la nueva carpeta \MT5\MQL5\Expertos\TestDoEasy\Part133\ con el nuevo nombre TestDoEasy133.mq5.

La única corrección que introduciremos en él será el cambio de sus dimensiones al crear un objeto de botón unido al panel para que sea verticalmente más grande que su padre, es decir, que supere los límites en la parte superior e inferior, mientras que la anchura será la contraria, cabrá completamente dentro del panel padre:

//--- 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,10,-40,pnl.WidthWorkspace()-30,pnl.HeightWorkspace()+50,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");

Eso es todo, no se requerirá ningún otro cambio.

Vamos a compilar el asesor y a ejecutarlo en el gráfico, habiendo especificado previamente el valor No en los ajustes para el valor Panel Autosize:


Podemos ver que la barra de desplazamiento vertical funciona exactamente igual que la barra de desplazamiento horizontal implementada en el último artículo.


¿Qué es lo próximo?

En el próximo artículo sobre la creación de elementos de control gráfico en la biblioteca DoEasy, enlazaremos las dos barras de desplazamiento en un objeto de contenedor y continuaremos creando otros controles.

Todos los archivos se adjuntan al artículo y podrá examinarlos y probarlos usted mismo.


Volver al contenido


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/14278

Archivos adjuntos |
MQL5.zip (5257.78 KB)
Redes neuronales: así de sencillo (Parte 76): Exploración de diversos patrones de interacción con Multi-future Transformer Redes neuronales: así de sencillo (Parte 76): Exploración de diversos patrones de interacción con Multi-future Transformer
Este artículo continúa con el tema de la predicción del próximo movimiento de los precios. Le invito a conocer la arquitectura del Transformador Multifuturo. Su idea principal es descomponer la distribución multimodal del futuro en varias distribuciones unimodales, lo que permite simular eficazmente varios modelos de interacción entre agentes en la escena.
Desarrollo y prueba de sistemas comerciales basados en el canal de Keltner Desarrollo y prueba de sistemas comerciales basados en el canal de Keltner
En este artículo examinaremos los sistemas comerciales que utilizan un concepto muy importante de los mercados financieros: la volatilidad. Asimismo, estudiaremos un sistema comercial basado en el Canal de Keltner, incluyendo su implementación en código y sus pruebas con varios activos.
Redes neuronales: así de sencillo (Parte 77): Transformador de covarianza cruzada (XCiT) Redes neuronales: así de sencillo (Parte 77): Transformador de covarianza cruzada (XCiT)
En nuestros modelos, a menudo utilizamos varios algoritmos de atención. Y, probablemente, lo más frecuente es utilizar transformadores. Su principal desventaja es la necesidad de recursos. En este artículo, estudiaremos un nuevo algoritmo que puede ayudar a reducir los costes informáticos sin perder calidad.
Aplicamos el coeficiente generalizado de Hurst y la prueba del coeficiente de varianza en MQL5 Aplicamos el coeficiente generalizado de Hurst y la prueba del coeficiente de varianza en MQL5
En este artículo, discutiremos cómo utilizar el coeficiente generalizado de Hurst y la prueba del coeficiente de varianza para analizar el comportamiento de las series de precios en MQL5.