DoEasy. Controles (Parte 31): Rolando o conteúdo do controle "ScrollBar"
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.
*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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso