English Русский 中文 Español Deutsch 日本語
preview
DoEasy. Controles (Parte 31): Rolando o conteúdo do controle "ScrollBar"

DoEasy. Controles (Parte 31): Rolando o conteúdo do controle "ScrollBar"

MetaTrader 5Exemplos | 10 abril 2023, 10:46
213 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Ideia

Continuamos a desenvolver a funcionalidade do controle ScrollBar. A barra de rolagem que estamos desenvolvendo pode responder a pressionamentos de botão e movimentos do controle de deslizante. Contudo, não ocorrem mais ações após isso. Necessitamos que, ao clicar nos botões de rolagem, o conteúdo do contêiner se desloque no interior do mesmo, revelando áreas previamente ocultas e ocultando as que antes estavam visíveis no lado oposto do mesmo. Hoje, vamos criar a possibilidade de deslocar o conteúdo do contêiner ao clicar nos botões da barra de rolagem horizontal. Nesse processo, o cursor do controle ajustará automaticamente seu tamanho e posição.

O controle deslizante da barra de rolagem não é apenas uma parte do elemento que, ao ser movido, permite controlar a posição do conteúdo da forma, deslocando-o dentro do contêiner. Ele também serve como uma representação esquemática da relação entre o contêiner e o seu conteúdo. A própria barra de rolagem representa a largura total do conteúdo do contêiner, e o cursor na barra de rolagem simboliza a largura do contêiner onde o conteúdo está disposto. Quanto mais o conteúdo ultrapassa os limites do contêiner, menor se torna o cursor. E isso faz sentido, pois o cursor mostra, por meio de seu tamanho, a janela na qual o conteúdo é visível, enquanto a barra de rolagem exibe todo o conteúdo do contêiner. Ao deslocar o cursor pela barra de rolagem, indicamos ao programa qual parte do conteúdo do contêiner queremos visualizar no momento.

Da mesma forma, podemos controlar a posição do conteúdo do contêiner utilizando os botões de seta situados nas extremidades da barra de rolagem. Nesse processo, tanto o conteúdo do próprio contêiner quanto o controle deslizante na barra de rolagem se deslocam, mostrando qual parte do conteúdo total do contêiner está sendo exibida no momento.

Hoje, criaremos a possibilidade de ajustar o conteúdo do contêiner usando os botões de seta da barra de rolagem horizontal. O controle deslizante se deslocará e terá o tamanho relativo correto, bem como as coordenadas de posicionamento na barra de rolagem. Inicialmente, desenvolveremos a funcionalidade da barra de rolagem horizontal e, em seguida, a aplicaremos de forma pronta à barra de rolagem vertical, fazendo com que ambas trabalhem em conjunto.


Modificando as classes da biblioteca

Uma vez que o controle deslizante da barra de rolagem ajusta automaticamente seu tamanho com base em quanto o conteúdo do contêiner ultrapassa seus limites, se o tamanho diminuir significativamente, o controle deslizante pode ficar muito pequeno. Para evitar essa situação, é necessário definir um tamanho mínimo para o mesmo, abaixo do qual ele não pode ficar. Além disso, ao deslocar o conteúdo do contêiner, deve-se definir o tamanho do passo em pixels, que corresponde a quanto o conteúdo do contêiner pode se mover a cada passo. No MetaEditor, por exemplo, esse passo é de seis pixels. Faremos com que seja menor em dois pixels.

No arquivo \MQL5\Include\DoEasy\Defines.mqh, vamos criar duas novas macro-substituições para especificar os parâmetros acima:

#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


Quando desejamos obter dados das propriedades do objeto referentes às "bordas superior, inferior, esquerda e direita de um elemento gráfico", nós os extraímos das próprias propriedades do objeto. Tais parâmetros são preenchidos com valores no momento da criação bem-sucedida do objeto. No entanto, se modificarmos o tamanho do objeto posteriormente, esses dados não serão mais armazenados nas propriedades do elemento gráfico. De fato, conseguimos obter valores ao requisitá-los por meio de um dos métodos que retornam essa propriedade, como BottomEdge(), mas esse método apenas retorna o valor calculado. Já nas propriedades do objeto, esses valores permanecem inalterados. E isso precisa ser corrigido. No arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh do elemento gráfico base da biblioteca, em todos os métodos que, de alguma forma, alteram os limites do objeto, é necessário registrar os novos valores nas propriedades do 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;
  }
//+------------------------------------------------------------------+

A partir de agora, sempre que houver alteração no tamanho ou nas coordenadas de um objeto, as coordenadas de suas bordas serão atualizadas e armazenadas nas propriedades do elemento gráfico. Isso nos possibilitará encontrar adequadamente os objetos desejados com base nos valores fornecidos de suas bordas, empregando a ordenação pelas propriedades do objeto.


Alguns dos métodos que criamos anteriormente na classe do objeto-contêiner são necessários apenas para objetos do tipo contêiner. Por exemplo, métodos que retornam os limites da área de trabalho do contêiner, onde os objetos anexados podem ser posicionados. No entanto, o problema é que, em outros objetos da biblioteca que não são objetos-contêiner, esses métodos não estão disponíveis, mas são frequentemente necessários. Afinal, estamos acessando um objeto-contêiner a partir de uma classe que não tem acesso a tal objeto (ela simplesmente não tem conhecimento dele). Portanto, somos obrigados a acessar as propriedades de tal objeto por meio das propriedades de seu objeto-pai, que é o objeto base de todos os objetos da biblioteca WinForms. Esta classe, no entanto, não enxerga os métodos de sua classe derivada - a classe do objeto-contêiner - gerando um ciclo vicioso. Contudo, há uma solução: transferindo todos os métodos necessários para a classe CWinFormBase, que é a classe-mãe de todos os objetos WinForms da biblioteca, perdemos um pouco na estruturação dos objetos, mas facilitamos a obtenção dos dados necessários ao acessar diferentes propriedades de objetos com diferentes finalidades. Mesmo que tais dados pertençam a um tipo diferente de objeto e não sejam utilizados no objeto atual, todos os objetos poderão acessar métodos de outros objetos não utilizados no objeto atual, mas que são usados naqueles aos quais nos referimos.

Embora possa parecer um pouco confuso, é muito mais simples no código-fonte. Basta transferir alguns métodos da classe do objeto-contêiner para a classe de objeto base de todos os objetos WinForms da biblioteca, tornando esses métodos visíveis a partir de qualquer classe.

Vamos extrair esses métodos públicos do arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh :

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)

e os inserimos na seção pública da classe no arquivo \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);
                       }

Agora esses métodos estarão visíveis a partir de todos os objetos WinForms da biblioteca.

Pelo mesmo motivo - devido à visibilidade dos métodos em todas as classes de objetos WinForms - escreveremos métodos que, por natureza, deveriam pertencer à classe do objeto-contêiner, mas as solicitações a eles vêm de objetos que não são contêineres.

Precisamos saber quantos pixels os objetos vinculados ao objeto-contêiner ultrapassam os limites do contêiner. Verificaremos esses valores em um único método e os armazenaremos em uma estrutura que contém campos com os valores do número de pixels na parte superior, inferior, esquerda e direita. Caso os objetos vinculados ultrapassem alguma borda do contêiner ou várias delas simultaneamente, a estrutura registrará os valores do número de pixels de cada lado do objeto onde o conteúdo excede os limites do contêiner. Com base nesses dados, seremos capazes de calcular o tamanho da barra de rolagem no controle deslizante (ScrollBar).

Na seção privada da classe, declararemos tal estrutura e uma variável com o tipo desta mesma, na qual escreveremos os dados que precisamos:

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:


Na seção pública, vamos declarar um método que verifica se os objetos vinculados saem do contêiner e preenchem a estrutura acima declarada, um método que desloca todos os objetos vinculados ao contêiner, e métodos que retornam os valores mínimo e máximo de a propriedade especificada de todos os objetos vinculados ao atual:

//--- 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 esses métodos são necessários para trabalhar com objetos contêiner, mas serão chamados mais longe de objetos não contêiner, então eles estão localizados na classe pai comum de todos os objetos WinForms.

Como a estrutura e a variável com o tipo da estrutura são privadas, precisamos de métodos públicos para retornar os valores escritos nos campos dessa estrutura. Vamos escrevê-los na seção pública no final do corpo da classe:

//--- 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;  }
  };
//+------------------------------------------------------------------+


Em ambos os construtores de classe, inicializamos todos os campos da estrutura com zero:

//+------------------------------------------------------------------+
//| 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 o sinalizador que indica que o conteúdo do contêiner ultrapassa seus limites:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

A lógica do método está totalmente descrita nos comentários ao código. Em resumo: precisamos saber se algum (ou vários, ou mesmo todos) dos objetos vinculados ao contêiner ultrapassam seus limites. E para conhecer os valores máximos pelos quais os objetos estão fora de seu contêiner, procuramos o valor máximo em cada lado, em um loop que percorre todos os objetos vinculados. Ao final do ciclo, teremos na estrutura todos os valores pelos quais os objetos vinculados ultrapassam os limites do contêiner em cada lado. Com base nesses valores, posteriormente, seremos capazes de calcular o tamanho do controle deslizante da barra de progresso. Afinal, o tamanho do controle deslizante depende de o quanto os objetos ultrapassam os limites do contêiner (quanto maior for a quantidade que ultrapassa os limites do contêiner, menor será o tamanho do controle deslizante).

Método que desloca todos os 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;
  }
//+------------------------------------------------------------------+

A lógica do método é explicada detalhadamente nos comentários do código. Precisamos mover os objetos vinculados ao contêiner quando os botões de rolagem são clicados ou quando o controle deslizante é movido. Este método desloca todos os objetos na lista de objetos vinculados a ele. O deslocamento é realizado pelo método Move(), e neste método já foi feito tudo para que os demais elementos vinculados ao objeto que está sendo movido também se desloquem. Em resumo, aqui deslocamos todos os objetos vinculados ao contêiner, exceto as barras de rolagem, pelo valor especificado, pois são elementos de controle do objeto-contêiner, e não objetos vinculados a ele (embora estejam na lista geral).


Método que retorna o valor máximo da propriedade inteira especificada a partir de todos os objetos base 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;
  }
//+------------------------------------------------------------------+

Aqui também toda a lógica está escrita nos comentários do código, e aqui tudo é simples: no loop, procuramos entre todos os objetos (exceto barras de rolagem) um objeto com o valor máximo da propriedade especificada. O valor máximo encontrado é retornado


Método que retorna o valor mínimo da propriedade inteira especificada a partir de todos os objetos base 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;
  }
//+------------------------------------------------------------------+

O método é semelhante ao anterior: em um loop, procuramos entre todos os objetos (exceto barras de rolagem) um objeto com o valor mínimo da propriedade especificada. Retornamos o valor mínimo encontrado.


Ao passar o cursor do mouse sobre o controle deslizante da barra de rolagem, todo o objeto ScrollBar precisa ser movido para a frente. E não apenas quando se passa o mouse sobre o controle deslizante, mas em geral, sempre que se passa o mouse sobre a área desse objeto, é necessário movê-lo acima de todos os objetos do contêiner. Isso é necessário para que o cursor possa interagir com o objeto mais acima, e não com aqueles que possam estar acima dele, pois foram criados posteriormente. No momento, a classe do objeto área de captura é herdada da classe do objeto botão e utiliza a funcionalidade da classe pai para interagir com o mouse. Como os métodos para tratar diferentes eventos ao interagir com o mouse são virtuais, precisamos sobrescrevê-los na classe do objeto área de captura.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh, declaramos os manipuladores virtuais de eventos do mouse na seção protegida e o último manipulador de eventos do mouse na seção 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 escrever a implementação dos manipuladores virtuais declarados.

Manipulador do evento "Cursor dentro da área ativa, nenhum botão do mouse pressionado":

//+------------------------------------------------------------------+
//| '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);
  }
//+------------------------------------------------------------------+

Aqui: pegamos um ponteiro para o objeto base, que será um objeto de barra de rolagem e o trazemos para a frente. Em seguida, chamamos o manipulador de evento do mouse do objeto pai, manipulador esse que corresponde ao método.


Manipulador de evento "Cursor dentro da área ativa, botão do mouse pressionado (qualquer)":

//+------------------------------------------------------------------+
//| '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);
  }
//+------------------------------------------------------------------+

Tudo é exatamente o mesmo, primeiro trazemos o objeto base para o primeiro plano (será uma barra de rolagem com todos os controles), e chamamos o manipulador de evento do mouse do objeto pai.


Manipulador de evento "Cursor dentro da área ativa, botão do mouse liberado (esquerdo)":

//+------------------------------------------------------------------+
//| '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);
  }
//+------------------------------------------------------------------+

De forma idêntica aos dois manipuladores acima.


Basta transferir todo o manipulador do último evento do mouse desde o objeto pai para possivelmente melhorá-lo ainda mais:

//+------------------------------------------------------------------+
//| 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;
     }
  }
//+------------------------------------------------------------------+

No momento, o manipulador é idêntico ao manipulador da classe pai.


Na classe da barra de rolagem do objeto abstrato barra de rolagem, no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBar.mqh, no método que cria o objeto área de captura (controle deslizante) e botões de rolagem, especificamos os tamanhos dos botões registrados na macro substituição voltada especificamente para armazenar os tamanhos dos botões da barra de rolagem:

//+------------------------------------------------------------------+
//| Create the capture area object                                   |
//+------------------------------------------------------------------+
void CScrollBar::CreateThumbArea(void)
  {
   this.CreateArrowButtons(DEF_CONTROL_SCROLL_BAR_WIDTH,DEF_CONTROL_SCROLL_BAR_WIDTH);
  }
//+------------------------------------------------------------------+


No método que calcula o tamanho da área de captura, retornaremos do método não zero, mas o valor escrito na macro substituição que armazena o tamanho mínimo padrão do controle deslizante:

//+------------------------------------------------------------------+
//| Calculate the capture area size                                  |
//+------------------------------------------------------------------+
int CScrollBar::CalculateThumbAreaSize(void)
  {
   return DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN;
  }
//+------------------------------------------------------------------+


A classe do objeto da barra de rolagem deve calcular o tamanho e a posição do controle deslizante com base em quantos pixels o conteúdo do contêiner ultrapassa seus limites. Ao redimensionar o contêiner ou seu conteúdo, o tamanho e a posição do controle deslizante devem ser recalculados. Quando os botões de rolagem são pressionados, o conteúdo do contêiner (caso ultrapasse os limites do contêiner) deve se deslocar na direção oposta à da seta do botão pressionado. O controle deslizante, por sua vez, deve se mover na direção da seta.

Hoje, vamos desenvolver essa funcionalidade para a barra de rolagem horizontal. Depois de concluir seu desenvolvimento, transferiremos a funcionalidade pronta para a classe do objeto da barra de rolagem vertical. E, em seguida, implementaremos a operação conjunta entre elas.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh , renomeamos o método privado CalculateThumbAreaSize para CalculateThumbAreaDistance(). Na seção pública, declaramos os métodos para calcular o tamanho e as coordenadas do controle deslizante e o método principal para recalcular seus 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);
  };
//+------------------------------------------------------------------+


Consideremos os métodos declarados.

Método que define as novas dimensões do 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;
  }
//+------------------------------------------------------------------+

Primeiro, definimos as novas dimensões da barra de rolagem utilizando o método da classe pai. Em seguida, caso o redimensionamento seja bem-sucedido, precisamos reposicionar o botão localizado na parte direita do objeto, de modo que ele fique alinhado à borda do objeto que teve seu tamanho alterado (com a mudança no tamanho, a coordenada da borda direita também se altera). Após todas as modificações e deslocamentos, recalculamos o tamanho e a posição do controle deslizante utilizando o método SetThumbParams(), que analisaremos adiante.


Método que calcula e define os parâmetros da área de captura (controle deslizante):

//+------------------------------------------------------------------+
//| 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 linha do método é comentada em detalhes. A essência do método consiste em calcular o tamanho do controle deslizante com base nas dimensões do conteúdo do contêiner que ultrapassa seus limites. Quanto maior o valor em pixels que o conteúdo do contêiner se estende além dele, menor será o controle deslizante. A coordenada da posição do controle deslizante é calculada a partir da borda esquerda do contêiner, também em dimensões relativas, de modo a corresponder à posição da parte visível do conteúdo do contêiner em relação à sua parte invisível que se estende além da borda esquerda. Quanto maior, em pixels, o conteúdo do contêiner se estender além da borda esquerda, mais à direita o controle deslizante será posicionado. Como resultado, temos que a barra de rolagem, juntamente com o controle deslizante, é uma cópia reduzida (ou representação) do contêiner e seu conteúdo. Na barra de rolagem, a própria barra exibe todo o conteúdo do contêiner, e o controle deslizante mostra a parte do contêiner em que o conteúdo é visível.


Método que calcula a distância da área de captura (controle 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);
  }
//+------------------------------------------------------------------+

O tamanho do controle deslizante é passado para o método. Em seguida, é calculado o quanto o tamanho do controle deslizante é menor que o tamanho da área de trabalho do contêiner (na qual o conteúdo é visível). Essa proporção é usada posteriormente para calcular a distância do controle deslizante, que é retornada. Ou seja, quanto menor o controle deslizante em relação ao contêiner, menor será a distância do controle deslizante em relação ao início de suas coordenadas e menor o número de pixels pelos quais o conteúdo do contêiner ultrapassa sua borda esquerda.


Método que retorna o tamanho da área de trabalho do controle 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);
  }
//+------------------------------------------------------------------+

A área de trabalho do controle deslizante é a área dentro da qual ele se move na barra de rolagem. Em outras palavras, esta é a área entre os dois botões com setas da barra de rolagem. Portanto, para calcular essa distância, precisamos obter referências a esses botões e calcular a distância entre a borda esquerda do botão direito e a borda direita do botão esquerdo.


Método que retorna a coordenada do início da área de trabalho do controle 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());
  }
//+------------------------------------------------------------------+

O método retorna a coordenada da borda direita do botão esquerdo da barra de rolagem. Essa coordenada é o ponto inicial para a localização do controle deslizante, e a partir dessa coordenada, determinamos sua posição ao calcular suas dimensões e coordenadas.


O gerenciador de eventos do mouse foi reformulado e agora está assim:

//+------------------------------------------------------------------+
//| 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();
     }
  }
//+------------------------------------------------------------------+

Neste trecho, adicionamos o processamento dos cliques nos botões da barra de rolagem. Ao clicar no botão, calculamos as coordenadas de deslocamento e movemos todo o conteúdo do contêiner na direção oposta à do botão pressionado com a seta.. Após o deslocamento, ajustamos as dimensões e coordenadas do controle deslizante.


Ao aprimorar a classe do objeto barra de rolagem horizontal, foram feitas alterações e melhorias semelhantes simultaneamente na classe do objeto barra de rolagem vertical. No entanto, nem todas as mudanças foram implementadas e nem todas foram testadas. Isso ocorre porque, inicialmente, desenvolvemos a funcionalidade da barra de rolagem horizontal e, em seguida, a transferimos para a barra de rolagem vertical. Por isso, não abordaremos neste momento algumas das alterações feitas no código da classe da barra de rolagem vertical - iremos analisá-lo após uma recriação completa, com base no código funcional e depurado da barra de rolagem horizontal.


Retornamos à classe de objeto contêiner no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh.

Na seção protegida, , declaramos um método para trazer as barras de rolagem para primeiro plano. Na seção pública, escrevemos métodos que retornam ponteiros para ambas as barras de rolagem, declaramos um método virtual para mover o objeto e fazemos uma declaração para o método virtual para redesenhá-lo com uma implementação fora do corpo da classe:

//+------------------------------------------------------------------+
//| 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);


No método que define o modo de redimensionamento automático do elemento para caber no conteúdo, adicionamos uma verificação de que o sinalizador de redimensionamento automático para caber no conteúdo não está definido:

//--- (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); }

Quando o modo de dimensionamento automático é definido, o processo de redimensionamento do contêiner consoante o tamanho de seu conteúdo é chamado se o modo definido não corresponder ao atual. Ao mesmo tempo, se o sinalizador de redimensionamento automático não estiver ativado, nada precisará ser classificado, jã que o código adicionado retorna do método quando o sinalizador de redimensionamento automático é desmarcado.


Após criar o objeto contêiner, caso o sinalizador de redimensionamento automático do contêiner de acordo com o conteúdo não esteja ativada para o objeto e se o conteúdo do contêiner ultrapassar seus limites, será necessário exibir a barra de rolagem apropriada:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Aqui, inicialmente verificamos os objetos anexados ao contêiner para constatar se estão ultrapassando seus limites e, se de fato estiverem fora, seja à esquerda ou à direita, exibimos a barra de rolagem horizontal. Primeiramente, calculamos e definimos os parâmetros do controle deslizante da barra de rolagem e, somente após isso, exibimos o próprio objeto barra de rolagem.

Uma vez que o objeto da barra de rolagem agora gerencia o redimensionamento por conta própria, o método que define novos tamanhos para o objeto atual foi modificado:

//+------------------------------------------------------------------+
//| 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 não é mais necessário lidar com o redimensionamento das barras de rolagem neste ponto para reposicionar seus botões, o método se tornou mais conciso e compreensível. Após o redimensionamento, cada barra de rolagem é trazida para o primeiro plano.


Método que redesenha o objeto:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CContainer::Redraw(bool redraw)
  {
   CWinFormBase::Redraw(redraw);
   this.BringToTopScrollBars();
  }
//+------------------------------------------------------------------+

Primeiro, redesenhamos o objeto usando o método da classe pai e, em seguida, chamamos o método para trazer as barras de rolagem para frente.


Método que traz as barras de rolagem para frente:

//+------------------------------------------------------------------+
//| 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();
  }
//+------------------------------------------------------------------+

Obtemos ponteiros para os objetos barra de rolagem, horizontal e vertical, e, após o recebimento bem-sucedido do ponteiro, movemos cada objeto para a frente.


Método que atualiza as 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;
  }
//+------------------------------------------------------------------+

Primeiro, chamamos o método da classe pai para mover o objeto e, depois de uma movimentação bem-sucedida, trazemos as barras de rolagem para o primeiro plano.


Agora precisamos processar eventos de objetos barra de rolagem na classe coleção de elementos gráficos.

No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, em seu manipulador de eventos, no bloco de processamento de clique do objeto, escrevemos o seguinte bloco de código para chamar o manipulador de eventos do mouse dos objetos barra de rolagem:

      //+------------------------------------------------------------------+
      //|  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);
        }

Agora estamos prontos para o teste.


Teste

Para o teste, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part131\ usando o novo nome TestDoEasy131.mq5.

Da última vez, criamos um objeto botão grande no painel. Agora vamos adicionar texto a ele, para ver o deslocamento horizontal do objeto ao clicar nos botões de rolagem e colocar um objeto rótulo de texto no botão, assim observaremos que todos os objetos anexados estão deslocados corretamente:

//--- 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)
           {


Compilamos o Expert Advisor e o iniciamos no gráfico:


Como podemos observar, a rolagem controlada pelos botões de seta funciona de maneira bastante adequada. Quando tentamos mover o controle deslizante com o mouse, ele "resiste", o que é natural, uma vez que ainda não implementamos o processamento do deslocamento do controle deslizante, mas já temos o recálculo de suas dimensões e coordenadas. Dessa forma, ao tentar mover o controle deslizante com o mouse, o método de ajuste de suas coordenadas o retorna à posição que corresponde ao posicionamento do conteúdo do contêiner na área visível. Nos próximos artigos, tudo será finalizado para seu correto funcionamento.



O que virá a seguir?

No próximo artigo, continuaremos a desenvolver o controle ScrollBar.

Voltar ao conteúdo

*Artigos desta série:

 
DoEasy. Controles (Parte 26): Finalizamos o objeto WinForms "ToolTip" e começamos a desenvolver a barra de progresso "ProgressBar"
DoEasy. Controles (Parte 27): Continuamos a trabalhar no objeto WinForms "ProgressBar"
DoEasy. Controles (Parte 28): Estilos de barra no controle ProgressBar
DoEasy. Controles (Parte 29): Controle auxiliar "ScrollBar"
DoEasy. Controles (Parte 30): Animando o controle "ScrollBar"

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11926

Arquivos anexados |
MQL5.zip (4551.42 KB)
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 06): Primeiras melhorias (I) Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 06): Primeiras melhorias (I)
Neste artigo vamos começar a estabilizar todo o sistema. Pois sem que o sistema esteja de fato estabilizado, podemos correr risco de não conseguir cumprir os próximos passos.
DoEasy. Controles (Parte 30): Animando o controle "ScrollBar" DoEasy. Controles (Parte 30): Animando o controle "ScrollBar"
Neste artigo continuaremos a desenvolver o controle ScrollBar e começaremos a fazer a funcionalidade de interação com o mouse. Além disso, vamos expandir as listas de bandeiras de status e eventos com o mouse.
Redes neurais de maneira fácil (Parte 34): Função quantil totalmente parametrizada Redes neurais de maneira fácil (Parte 34): Função quantil totalmente parametrizada
Continuamos a estudar os algoritmos de aprendizado Q distribuído. Em artigos anteriores, já discutimos os algoritmos de aprendizado Q distribuído e de quantil. No primeiro, aprendemos as probabilidades de determinados intervalos de valores. No segundo, aprendemos intervalos com uma probabilidade específica. Em ambos os algoritmos, utilizamos o conhecimento prévio de uma distribuição e ensinamos a outra. Neste artigo, vamos examinar um algoritmo que permite que o modelo aprenda ambas as distribuições.
DoEasy. Controles (Parte 29): Controle auxiliar "ScrollBar" DoEasy. Controles (Parte 29): Controle auxiliar "ScrollBar"
Neste artigo, iniciaremos o desenvolvimento do elemento de controle auxiliar ScrollBar e seus objetos derivados, incluindo as barras de rolagem vertical e horizontal. A ScrollBar (barra de rolagem) é utilizada para rolar o conteúdo da forma caso ele ultrapasse o contêiner. As barras de rolagem geralmente são posicionadas na parte inferior e à direita da forma. A barra de rolagem horizontal, localizada na parte inferior, permite rolar o conteúdo para a esquerda e direita, enquanto a barra de rolagem vertical possibilita rolar o conteúdo para cima e para baixo.