
DoEasy. Controles (Parte 33): "ScrollBar" vertical
Conteúdo
Conceito
No último artigo dedicado aos elementos gráficos na biblioteca, criamos uma barra de rolagem horizontal, que aparece no objeto caso o objeto anexado ao formulário ultrapasse os limites de seu formulário pai à esquerda, à direita ou em ambos os lados. Hoje, com base no objeto de barra de rolagem horizontal, vamos criar uma barra de rolagem vertical. Ela será exibida no formulário se o objeto anexado a ela ultrapassar seus limites superior, inferior ou ambos.
O artigo será pequeno, mais como um resumo, pois fazer praticamente uma cópia do objeto-barra de rolagem horizontal, transformando-o em vertical, não é muito difícil. Mas precisaremos dessas barras ao desenvolver os próximos elementos de controle no estilo Windows Form. A barra de rolagem vertical já existe há bastante tempo, mas a publicação do artigo foi adiada devido a um pequeno erro ou, mais precisamente, uma omissão. Isso causava uma interação indesejável com os elementos gráficos, resultando em um "piscar" constante das partes invisíveis dos objetos. Isso acontecia porque os objetos atualizavam de forma descontrolada e prematura, e depois eram cortados para se ajustarem ao tamanho do elemento pai. Assim, aparecia esse "piscar", isto é, primeiro o objeto era completamente desenhado, exibido no gráfico, e só então era cortado para caber no tamanho do objeto-forma pai. A solução, como costuma ser nesses casos, foi a mais simples — remover a atualização prematura com a nova renderização. Mas levou muito tempo para encontrar o local onde a renderização ocorria. Agora que esse bug foi encontrado e corrigido, podemos seguir tranquilamente com o desenvolvimento da biblioteca.
Melhoria das classes da biblioteca
Primeiramente, vamos adicionar funções e métodos úteis que facilitarão as futuras melhorias da biblioteca.
Às vezes, é necessário determinar o horário de abertura da barra onde ocorreu um evento. Se o evento ocorreu exatamente no momento da abertura da vela, encontrar o horário de abertura é simples. No entanto, se o evento aconteceu entre o horário de abertura e o horário de fechamento da vela, é possível calcular o horário de abertura da vela no período gráfico especificado com base no horário do evento. Embora seja possível utilizar funções padrão para converter o horário do evento no número da barra e, em seguida, obter o horário de abertura da vela desejada a partir desse número, esse processo pode consumir tempo de processamento. Portanto, quando a velocidade de execução é importante, é mais eficiente realizar o cálculo diretamente, desde que o evento tenha ocorrido dentro de uma vela realmente existente.
Aqui, no fórum, há um tópico útil onde os usuários compartilham códigos interessantes desse tipo. Vamos utilizar o algoritmo proposto e escrever uma função para a biblioteca.
No arquivo da biblioteca \MQL5\Include\DoEasy\Services\DELib.mqh, ao final, escrevemos a função:
//+------------------------------------------------------------------------------------+ //| Get the opening time of the virtual bar based on input time and | //| timeframe, regardless of the existence of a real bar. | //| It counts correctly only till 28.02.2100 | //| It is not a replacement for iBarShift!!! It does not depend on the bar history. | //| https://www.mql5.com/ru/forum/170952/page234#comment_50523898 | //+------------------------------------------------------------------------------------+ datetime GetStartTimeOfBarFast(const ENUM_TIMEFRAMES timeframe, const datetime time) { ENUM_TIMEFRAMES tf=(timeframe==PERIOD_CURRENT ? _Period : timeframe); int ts=0; if(tf<PERIOD_MN1) { ushort i_tf=ushort(tf); uchar _i=uchar(i_tf>>14); int n=i_tf & 0x0FFF; ts=(_i==0 ? n*60 : _i==1 ? n*60*60 : 60*60*24*7); } if(tf<PERIOD_W1) return time-time % ts; if(tf==PERIOD_W1) return time-(time+4*24*60*60) % ts; else // Period MN1 { static int dm[12] = {0,31,61,92,122,153,184, 214, 245, 275, 306, 337}; static int last_days = 0; static datetime last_result = 0; int days = int(time/(24*60*60)); if(last_days!=days) { last_days = days; int d1 = (days+306+365)%1461; int y = d1/365; datetime t1 = time - time % (24*60*60) - d1*24*60*60; int m = 0; if(d1==1460) { m=11; y--; }; int d = d1-y*365+1; if(d!=31) if(d==276) m = 9; else m = int(d/30.68); if(m<0 || m>11) return WRONG_VALUE; last_result = t1+y*365*24*60*60+dm[m]*24*60*60; } return last_result; } } //+------------------------------------------------------------------+
A análise do algoritmo foi feita no fórum, no link mencionado acima. Quem estiver interessado pode ler. Posteriormente, com a implementação dessa função, será possível encontrar sempre o horário de abertura da barra em que ocorreu um evento. Isso sem recorrer a funções que não sejam suficientemente rápidas, especialmente quando a velocidade dos cálculos é importante.
Ao trabalhar com objetos gráficos, às vezes é necessário mudar a cor do objeto gráfico dependendo da situação. Claro, você pode usar cores da lista de cores padrão, mas muitas vezes isso não é suficiente. Por exemplo, há um objeto, digamos, de cor cinza neutra. Dependendo da situação, ele pode mudar ligeiramente sua tonalidade. Em um caso, ele pode mudar para um tom ligeiramente avermelhado, em outro caso, para um tom ligeiramente esverdeado. Ou seja, nesse caso, basta adicionar um pouco de saturação ao componente de cor correspondente, em vez de definir cores a partir do conjunto padrão.
Para isso, no arquivo do objeto gráfico \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, na seção pública, declaramos o método:
//--- Change the lightness of (1) ARGB and (2) COLOR by a specified amount uint ChangeColorLightness(const uint clr,const double change_value); color ChangeColorLightness(const color colour,const double change_value); //--- Change the saturation of (1) ARGB and (2) COLOR by a specified amount uint ChangeColorSaturation(const uint clr,const double change_value); color ChangeColorSaturation(const color colour,const double change_value); //--- Changes the color component of RGB-Color color ChangeRGBComponents(color clr,const uchar R,const uchar G,const uchar B);
Fora do corpo da classe, escrevemos sua implementação:
//+------------------------------------------------------------------+ //| Change the color component of RGB-Color | //+------------------------------------------------------------------+ color CGCnvElement::ChangeRGBComponents(color clr,const uchar R,const uchar G,const uchar B) { double r=CColors::GetR(clr)+R; if(r>255) r=255; double g=CColors::GetG(clr)+G; if(g>255) g=255; double b=CColors::GetB(clr)+B; if(b>255) b=255; return CColors::RGBToColor(r,g,b); } //+------------------------------------------------------------------+ //| Save the image to the array | //+------------------------------------------------------------------+
Aqui é simples: obtemos cada componente da cor fornecida ao método que precisa ser alterada, e então adicionamos aos valores desses componentes os valores correspondentes passados para o método. Se algum dos valores exceder 255, corrigimos para o valor 255. No final, retornamos a cor composta pelos novos componentes calculados, usando o método RGBToColor da classe CColor da biblioteca.
Neste mesmo arquivo, há um método que define as coordenadas e tamanhos da área visível do elemento gráfico:
//--- Set relative coordinates and size of the visible area void SetVisibleArea(const int x,const int y,const int w,const int h) { this.SetVisibleAreaX(x,false); this.SetVisibleAreaY(y,false); this.SetVisibleAreaWidth(w,false); this.SetVisibleAreaHeight(h,false); }
Adicionamos a opção de especificar como a área visível é definida, podendo ser baseada apenas nas propriedades do objeto gráfico ou combinada com as propriedades do objeto físico. Para isso, apenas adicionamos outra variável de entrada e, consequentemente, ajustamos a chamada do método que define a área visível para todo o tamanho do objeto:
//--- Set relative coordinates and size of the visible area void SetVisibleArea(const int x,const int y,const int w,const int h,const bool only_prop) { this.SetVisibleAreaX(x,only_prop); this.SetVisibleAreaY(y,only_prop); this.SetVisibleAreaWidth(w,only_prop); this.SetVisibleAreaHeight(h,only_prop); } //--- Sets the size of the visible area equal to the entire object void ResetVisibleArea(void) { this.SetVisibleArea(0,0,this.Width(),this.Height(),false); }
Na implementação do método que limpa o elemento preenchendo-o com uma cor e opacidade, sem recorte, e atualiza o gráfico conforme o sinalizador, é necessário ajustar ligeiramente a lógica de atualização do objeto. Anteriormente, neste caso sempre o objeto era atualizado independentemente do sinalizador de redesenho do gráfico:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //| without cropping and with the chart update by flag | //+------------------------------------------------------------------+ void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false) { color arr[1]; arr[0]=colour; this.SaveColorsBG(arr); this.m_canvas.Erase(::ColorToARGB(colour,opacity)); this.Update(redraw); }
O método Update(), quando utilizado com o sinalizador de redesenho do gráfico, sempre atualiza o objeto após ele ter sido totalmente preenchido com a cor especificada nos parâmetros do método EraseNoCrop(). Consequentemente, independentemente do sinalizador redraw, o objeto sempre era atualizado, garantindo que as alterações realizadas fossem exibidas. O sinalizador de redesenho dependia apenas do tempo de exibição das alterações: imediatamente (se o sinalizador estivesse definido como true) ou na chegada de um tick ou outra atualização do gráfico (se o sinalizador estivesse definido como false). E como esse método repinta completamente o objeto, ele pode ser exibido no gráfico em seu tamanho total a qualquer momento. No caso de o objeto precisar ser cortado para caber nos limites do objeto pai ao qual está anexado, essa renderização causava "piscadas" indesejáveis na parte invisível do objeto, pois o corte da parte invisível sempre era realizado após a chamada deste método.
Agora tudo está corrigido e não há mais piscadas na parte invisível do objeto:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //| without cropping and with the chart update by flag | //+------------------------------------------------------------------+ void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false) { color arr[1]; arr[0]=colour; this.SaveColorsBG(arr); this.m_canvas.Erase(::ColorToARGB(colour,opacity)); if(redraw) this.Update(redraw); }
Aqui, a atualização do objeto ocorre somente quando o sinalizador de redesenho está definido. Dessa forma, agora é possível controlar a exibição do objeto programaticamente. Se tivermos certeza de que o objeto não precisa ser cortado, podemos chamar o método com o sinalizador ativado, e sua nova aparência será exibida imediatamente no gráfico. Se o objeto precisar ser cortado, chamamos primeiro este método com o sinalizador desmarcado, e então o método Crop(), que corta as áreas ocultas e atualiza a aparência do objeto com a renderização do gráfico mediante o sinalizador. Essa foi a falha lógica que impedia o desenvolvimento dos elementos gráficos da biblioteca. Agora, a falha está corrigida.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\BarProgressBar.mqh, no manipulador de timer, corrigimos a chamada do método SetVisibleArea(), agora especificando o sinalizador necessário:
//--- ... //--- ... //--- If the object is in the normal state (hidden) if(glare.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_NORMAL) { //--- set the state of waiting for fading in to the object (in our case, waiting for a shift along the progress bar), //--- set the waiting duration and set the countdown time glare.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN); this.m_pause.SetWaitingMSC(this.ShowDelay()); this.m_pause.SetTimeBegin(); //--- If the right edge of the glare object is to the right of the left edge of the progress bar object if(glare.RightEdge()>=this.CoordX()) { //--- Hide the glare object and move it beyond the right edge of the progress bar glare.Hide(); if(glare.Move(this.CoordX()-glare.Width(),this.CoordY())) { //--- Set the relative coordinates of the glare object glare.SetCoordXRelative(glare.CoordX()-this.CoordX()); glare.SetCoordYRelative(glare.CoordY()-this.CoordY()); //--- and its visibility scope equal to the entire object glare.SetVisibleArea(0,0,glare.Width(),glare.Height(),false); } } return; } //--- ... //--- ...
Praticamente cada objeto da biblioteca contém um objeto de controle gráfico, que permite a criação dinâmica de objetos gráficos. Adicionamos métodos que permitem criar alguns objetos gráficos padrão. No arquivo da classe de controle de objetos gráficos \MQL5\Include\DoEasy\Objects\Graph\GraphElmControl.mqh, na seção pública, declaramos novos métodos para desenhar linhas de tendência e setas:
public: //--- Return itself CGraphElmControl *GetObject(void) { return &this; } //--- Set a type of the object the graphics is constructed for void SetTypeNode(const int type_node) { this.m_type_node=type_node; } //--- Create a form object CForm *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h); CForm *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h); CForm *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h); //--- Creates the trend line standard graphical object bool CreateTrendLine(const long chart_id,const string name,const int subwindow, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID); bool CreateTrendLine(const string name,const int subwindow, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID); bool CreateTrendLine(const string name, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID); //--- Create the arrow standard graphical object bool CreateArrow(const long chart_id,const string name,const int subwindow, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1); bool CreateArrow(const string name,const int subwindow, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1); bool CreateArrow(const string name, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1); //--- Constructors CGraphElmControl(){ this.m_type=OBJECT_DE_TYPE_GELEMENT_CONTROL; } CGraphElmControl(int type_node); };
Na seção privada, declaramos um método que define parâmetros comuns para objetos gráficos padrão:
//+------------------------------------------------------------------+ //| Class for managing graphical elements | //+------------------------------------------------------------------+ class CGraphElmControl : public CObject { private: int m_type; // Object type int m_type_node; // Type of the object the graphics is constructed for //--- Set general parameters for standard graphical objects void SetCommonParamsStdGraphObj(const long chart_id,const string name); public: //--- Return itself CGraphElmControl *GetObject(void) { return &this; }
Cada novo objeto criado deve ter por padrão alguns atributos, cujos valores são os mesmos para todos os objetos gráficos sem exceção: o objeto deve ser oculto na lista de todos os objetos do gráfico, não deve estar selecionado nem ser selecionável com o mouse, e deve ser exibido em todos os timeframes. Esses são os atributos definidos pelo método SetCommonParamsStdGraphObj, cuja implementação é feita fora do corpo da classe:
//+------------------------------------------------------------------+ //|Set general parameters for standard graphical objects | //+------------------------------------------------------------------+ void CGraphElmControl::SetCommonParamsStdGraphObj(const long chart_id,const string name) { ::ObjectSetInteger(chart_id,name,OBJPROP_HIDDEN,true); ::ObjectSetInteger(chart_id,name,OBJPROP_SELECTED,false); ::ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false); ::ObjectSetInteger(chart_id,name,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); }
Também fora do corpo da classe, escreveremos a implementação dos métodos que criam os objetos gráficos:
//+------------------------------------------------------------------+ //| Create the trend line standard graphical object | //| on a specified chart in a specified subwindow | //+------------------------------------------------------------------+ bool CGraphElmControl::CreateTrendLine(const long chart_id,const string name,const int subwindow, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID) { if(!CreateNewStdGraphObject(chart_id,name,OBJ_TREND,subwindow,time1,price1,time2,price2)) { ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),": ",StdGraphObjectTypeDescription(OBJ_TREND)); return false; } this.SetCommonParamsStdGraphObj(chart_id,name); ::ObjectSetInteger(chart_id,name,OBJPROP_COLOR,clr); ::ObjectSetInteger(chart_id,name,OBJPROP_WIDTH,width); ::ObjectSetInteger(chart_id,name,OBJPROP_STYLE,style); return true; } //+------------------------------------------------------------------+ //| Create the trend line standard graphical object | //| on the current chart in a specified subwindow | //+------------------------------------------------------------------+ bool CGraphElmControl::CreateTrendLine(const string name,const int subwindow, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID) { return this.CreateTrendLine(::ChartID(),name,subwindow,time1,price1,time2,price2,clr,width,style); } //+------------------------------------------------------------------+ //| Create the trend line standard graphical object | //| on the current chart in the main window | //+------------------------------------------------------------------+ bool CGraphElmControl::CreateTrendLine(const string name, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID) { return this.CreateTrendLine(::ChartID(),name,0,time1,price1,time2,price2,clr,width,style); } //+------------------------------------------------------------------+ //| Create the arrow standard graphical object | //| on a specified chart in a specified subwindow | //+------------------------------------------------------------------+ bool CGraphElmControl::CreateArrow(const long chart_id,const string name,const int subwindow, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1) { if(!CreateNewStdGraphObject(chart_id,name,OBJ_ARROW,subwindow,time1,price1)) { ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),": ",StdGraphObjectTypeDescription(OBJ_ARROW)); return false; } this.SetCommonParamsStdGraphObj(chart_id,name); ::ObjectSetInteger(chart_id,name,OBJPROP_COLOR,clr); ::ObjectSetInteger(chart_id,name,OBJPROP_WIDTH,width); ::ObjectSetInteger(chart_id,name,OBJPROP_ARROWCODE,arrow_code); return true; } //+------------------------------------------------------------------+ //| Create the arrow standard graphical object | //| on the current chart in a specified subwindow | //+------------------------------------------------------------------+ bool CGraphElmControl::CreateArrow(const string name,const int subwindow, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1) { return this.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width); } //+------------------------------------------------------------------+ //| Create the arrow standard graphical object | //| on the current chart in the main window | //+------------------------------------------------------------------+ bool CGraphElmControl::CreateArrow(const string name, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1) { return this.CreateArrow(::ChartID(),name,0,time1,price1,clr,arrow_code,width); }
Cada objeto da biblioteca que herda da classe base CBaseObj possui uma instância do objeto da classe de controle de objetos gráficos. O objeto de controle gráfico tem métodos para criar esses objetos. Mas para que possamos criar objetos gráficos a partir da classe de qualquer objeto, é necessário escrever métodos para criação de objetos gráficos na classe base. Isso simplificará o desenvolvimento gráfico nos aplicativos. Basicamente, pode-se primeiro obter um ponteiro para o objeto necessário, depois obter um ponteiro para seu objeto de controle gráfico e, em seguida, chamar os métodos desse objeto para criar objetos gráficos. Mas esse é um caminho longo. É mais simples, conveniente e rápido obter um ponteiro para o objeto e usar seus métodos para criar os objetos gráficos, pois toda a cadeia descrita será executada automaticamente dentro desses métodos.
No arquivo do objeto base da biblioteca \MQL5\Include\DoEasy\Objects\BaseObj.mqh, em sua seção pública na área de trabalho com objetos gráficos, declaramos novos métodos para criar linhas de tendência e objetos de seta:
//+------------------------------------------------------------------+ //| Methods for handling graphical elements | //+------------------------------------------------------------------+ //--- Create a form object on a specified chart in a specified subwindow CForm *CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h) { return this.m_graph_elm.CreateForm(form_id,chart_id,wnd,name,x,y,w,h); } //--- Create a form object on the current chart in a specified subwindow CForm *CreateForm(const int form_id,const int wnd,const string name,const int x,const int y,const int w,const int h) { return this.m_graph_elm.CreateForm(form_id,wnd,name,x,y,w,h); } //--- Create the form object on the current chart in the main window CForm *CreateForm(const int form_id,const string name,const int x,const int y,const int w,const int h) { return this.m_graph_elm.CreateForm(form_id,name,x,y,w,h); } //--- Create a standard graphical trend line object in the specified subwindow of the specified chart bool CreateTrendLine(const long chart_id,const string name,const int subwindow, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID) { return this.m_graph_elm.CreateTrendLine(chart_id,name,subwindow,time1,price1,time2,price2,clr,width,style); } //--- Create a standard graphical trend line object in the specified subwindow of the current chart bool CreateTrendLine(const string name,const int subwindow, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID) { return this.m_graph_elm.CreateTrendLine(::ChartID(),name,subwindow,time1,price1,time2,price2,clr,width,style);} //--- Create a standard graphical trend line object in the main window of the current chart bool CreateTrendLine(const string name, const datetime time1,const double price1, const datetime time2,const double price2, color clr,int width=1,ENUM_LINE_STYLE style=STYLE_SOLID) { return this.m_graph_elm.CreateTrendLine(::ChartID(),name,0,time1,price1,time2,price2,clr,width,style); } //--- Create a standard arrow graphical object in the specified subwindow of the specified chart bool CreateArrow(const long chart_id,const string name,const int subwindow, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1) { return this.m_graph_elm.CreateArrow(chart_id,name,subwindow,time1,price1,clr,arrow_code,width); } //--- Create a standard arrow graphical object in the specified subwindow of the current chart bool CreateArrow(const string name,const int subwindow, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1) { return this.m_graph_elm.CreateArrow(::ChartID(),name,subwindow,time1,price1,clr,arrow_code,width); } //--- Create a standard arrow graphical object in the main window of the current chart bool CreateArrow(const string name, const datetime time1,const double price1, color clr,uchar arrow_code,int width=1) { return this.m_graph_elm.CreateArrow(::ChartID(),name,0,time1,price1,clr,arrow_code,width); } //--- Constructor
Nos métodos, basta chamar os métodos correspondentes do objeto de controle gráfico. Posteriormente, escreveremos métodos para criar outros objetos gráficos padrão. Por enquanto, esses objetos gráficos serão suficientes para serem utilizados em artigos futuros.
Vamos começar a criar o objeto da barra de rolagem vertical.
A área de captura da barra de rolagem é o controle deslizante, que pode ser segurado com o mouse e movido dentro da mesma, ajustando assim a área que ela controla. Ao rolar a roda do mouse para um lado ou para o outro, com o cursor dentro da barra de rolagem, é gerado um evento de clique no botão de controle de rolagem correspondente (botões com setas nas extremidades da barra de rolagem). Quanto à barra de rolagem horizontal, já temos eventos gerados ao rolar a roda do mouse: eventos de clique com os botões esquerdo e direito. Agora precisamos adicionar a geração de eventos nos botões de seta para cima/baixo, para o controle deslizante da barra de rolagem vertical.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqh no manipulador de eventos "Cursor dentro da área ativa, roda do mouse rolando" faremos essas alterações:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| the mouse wheel is being scrolled | //+------------------------------------------------------------------+ void CScrollBarThumb::MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { CWinFormBase *base=this.GetBase(); if(base==NULL) return; base.BringToTop(); ENUM_WF_CONTROL_EVENT evn=WF_CONTROL_EVENT_NO_EVENT; switch(base.TypeGraphElement()) { case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL: evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : WF_CONTROL_EVENT_NO_EVENT); break; case GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL : evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); break; default : break; } base.OnChartEvent(evn,lparam,dparam,sparam); ::ChartRedraw(base.ChartID()); }
Dependendo do objeto base para a área de captura, isto é, barra de rolagem horizontal ou vertical, é chamado o manipulador de eventos do objeto base que recebe os códigos de evento (clique do mouse no botão com a seta para a esquerda ou para a direita, ou clique do mouse no botão com a seta para cima ou para baixo).
Para criar o objeto da barra de rolagem vertical, vamos pegar o arquivo da classe do objeto da barra de rolagem horizontal \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqh e salvá-lo com o nome \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarVertical.mqh. Como a nova classe é criada com base em uma classe idêntica, precisamos apenas substituir alguns cálculos: em vez de "esquerda/direita" nos cálculos, usar "cima/baixo", e assim por diante. Não faz sentido descrever cada alteração feita. Sobre a criação desse objeto, pode-se ler no artigo correspondente. Desta vez, apenas revisaremos o arquivo da classe na íntegra com as alterações já feitas:
//+------------------------------------------------------------------+ //| ScrollBarVertical.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ScrollBarThumb.mqh" #include "ArrowDownButton.mqh" #include "ArrowUpButton.mqh" #include "ScrollBar.mqh" //+------------------------------------------------------------------+ //| CScrollBarVertical object class of WForms controls | //+------------------------------------------------------------------+ class CScrollBarVertical : public CScrollBar { private: //--- Create the ArrowButton objects virtual void CreateArrowButtons(const int width,const int height); //--- Calculate the distance of the capture area (slider) int CalculateThumbAreaDistance(const int thumb_size); protected: //--- Protected constructor with object type, chart ID and subwindow CScrollBarVertical(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- 'The cursor is inside the active area, the mouse wheel is being scrolled' event handler virtual void MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam); public: //--- Supported object properties (1) integer, (2) real and (3) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return the button with the (1) up, (2) down arrow CArrowUpButton *GetArrowButtonUp(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0); } CArrowDownButton *GetArrowButtonDown(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0); } //--- Return the size of the slider working area int BarWorkAreaSize(void); //--- Return the coordinate of the beginning of the slider working area int BarWorkAreaCoord(void); //--- Set the new size virtual bool Resize(const int w,const int h,const bool redraw); //--- Calculate and set the parameters of the capture area (slider) int SetThumbParams(void); //--- Constructor CScrollBarVertical(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- Timer virtual void OnTimer(void); //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CScrollBarVertical::CScrollBarVertical(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CScrollBar(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.CreateThumbArea(); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CScrollBarVertical::CScrollBarVertical(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CScrollBar(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL); this.CreateThumbArea(); } //+------------------------------------------------------------------+ //| Create the ArrowButton objects | //+------------------------------------------------------------------+ void CScrollBarVertical::CreateArrowButtons(const int width,const int height) { //--- Set the size of the buttons equal to the width of the scrollbar without the size of its frame int size=this.Thickness()-this.BorderSizeLeft()-this.BorderSizeRight(); //--- Create the buttons with up and down arrows and the area capture object. The arrow size is set to 2 this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP, 0,0,size,size,this.BackgroundColor(),255,true,false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0,this.Height()-height,size,size,this.BackgroundColor(),255,true,false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB,0,this.Height()/2-height,size,30,CLR_DEF_CONTROL_SCROLL_BAR_THUMB_COLOR,255,true,false); this.SetArrowSize(2); //--- Get the pointer to the up arrow button and set the colors of its various states for it CArrowUpButton *bu=this.GetArrowButtonUp(); if(bu!=NULL) { bu.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_COLOR,true); bu.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_DOWN); bu.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_OVER); bu.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_COLOR,true); bu.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_DOWN); bu.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_OVER); } //--- Get the pointer to the down arrow button and set the colors of its various states for it CArrowDownButton *bd=this.GetArrowButtonDown(); if(bd!=NULL) { bd.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_COLOR,true); bd.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_DOWN); bd.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_MOUSE_OVER); bd.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_COLOR,true); bd.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_DOWN); bd.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_BUTT_FORE_MOUSE_OVER); } //--- Get the pointer to the capture area object and set the colors of its various states for it CScrollBarThumb *th=this.GetThumb(); if(th!=NULL) { th.SetBackgroundColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_COLOR,true); th.SetBorderColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_BORDER_COLOR,true); th.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_MOUSE_DOWN); th.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_MOUSE_OVER); th.SetForeColor(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_COLOR,true); th.SetForeColorMouseDown(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_MOUSE_DOWN); th.SetForeColorMouseOver(CLR_DEF_CONTROL_SCROLL_BAR_THUMB_FORE_MOUSE_OVER); } } //+------------------------------------------------------------------+ //| Set the new size | //+------------------------------------------------------------------+ bool CScrollBarVertical::Resize(const int w,const int h,const bool redraw) { //--- If failed to change the object size, return 'false' if(!CWinFormBase::Resize(w,h,redraw)) return false; //--- Get the button object with the down arrow CArrowDownButton *bd=this.GetArrowButtonDown(); //--- If the button is not received, return 'false' if(bd==NULL) return false; //--- Move the button to the bottom edge of the scrollbar if(bd.Move(bd.CoordX(),this.BottomEdge()-this.BorderSizeBottom()-bd.Height())) { //--- Set new relative coordinates for the button bd.SetCoordXRelative(bd.CoordX()-this.CoordX()); bd.SetCoordYRelative(bd.CoordY()-this.CoordY()); } //--- Set the slider parameters this.SetThumbParams(); //--- Successful return true; } //+------------------------------------------------------------------+ //| Calculate and set the parameters of the capture area (slider) | //+------------------------------------------------------------------+ int CScrollBarVertical::SetThumbParams(void) { //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return 0; //--- Get the capture area object (slider) CScrollBarThumb *thumb=this.GetThumb(); if(thumb==NULL) return 0; //--- Get the height size of the visible part inside the container int base_h=base.HeightWorkspace(); //--- Calculate the total height of all attached objects int objs_h=base_h+base.OversizeTop()+base.OversizeBottom(); //--- Calculate the relative size of the visible part window double px=(double)base_h/double(objs_h!=0 ? objs_h : 1); //--- Calculate and adjust the size of the slider relative to the height of its workspace (not less than the minimum size) int thumb_size=(int)::floor(this.BarWorkAreaSize()*px); if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN) thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN; if(thumb_size>this.BarWorkAreaSize()) thumb_size=this.BarWorkAreaSize(); //--- Calculate the coordinate of the slider and change its size to match the previously calculated one int thumb_y=this.CalculateThumbAreaDistance(thumb_size); if(!thumb.Resize(thumb.Width(),thumb_size,true)) return 0; //--- Shift the slider by the calculated Y coordinate if(thumb.Move(thumb.CoordX(),this.BarWorkAreaCoord()+thumb_y)) { thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX()); thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY()); } //--- Return the calculated slider size return thumb_size; } //+------------------------------------------------------------------+ //| Calculate the distance of the capture area (slider) | //+------------------------------------------------------------------+ int CScrollBarVertical::CalculateThumbAreaDistance(const int thumb_size) { CWinFormBase *base=this.GetBase(); if(base==NULL) return 0; double x=(double)thumb_size/(double)base.HeightWorkspace(); return (int)::ceil((double)base.OversizeTop()*x); } //+------------------------------------------------------------------+ //| Return the size of the slider working area | //+------------------------------------------------------------------+ int CScrollBarVertical::BarWorkAreaSize(void) { CArrowUpButton *bu=this.GetArrowButtonUp(); CArrowDownButton *bd=this.GetArrowButtonDown(); int y1=(bu!=NULL ? bu.BottomEdge() : this.CoordY()+this.BorderSizeTop()); int y2=(bd!=NULL ? bd.CoordY() : this.BottomEdge()-this.BorderSizeBottom()); return(y2-y1); } //+------------------------------------------------------------------+ //| Return the coordinate of the beginning of the slider working area| //+------------------------------------------------------------------+ int CScrollBarVertical::BarWorkAreaCoord(void) { CArrowUpButton *bu=this.GetArrowButtonUp(); return(bu!=NULL ? bu.BottomEdge() : this.CoordY()+this.BorderSizeTop()); } //+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CScrollBarVertical::OnTimer(void) { } //+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CScrollBarVertical::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); //--- Get the pointers to control objects of the scrollbar CArrowUpButton *buttu=this.GetArrowButtonUp(); CArrowDownButton *buttd=this.GetArrowButtonDown(); CScrollBarThumb *thumb=this.GetThumb(); if(buttu==NULL || buttd==NULL || thumb==NULL) return; //--- If the event ID is an object movement if(id==WF_CONTROL_EVENT_MOVING) { //--- Move the scrollbar to the foreground this.BringToTop(); //--- Declare the variables for the coordinates of the capture area int x=(int)lparam; int y=(int)dparam; //--- Set the X coordinate equal to the X coordinate of the control element x=this.CoordX()+this.BorderSizeLeft(); //--- Adjust the Y coordinate so that the capture area does not go beyond the control, taking into account the arrow buttons if(y<buttu.BottomEdge()) y=buttu.BottomEdge(); if(y>buttd.CoordY()-thumb.Height()) y=buttd.CoordY()-thumb.Height(); //--- If the capture area object is shifted by the calculated coordinates if(thumb.Move(x,y,true)) { //--- set the object relative coordinates thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX()); thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY()); } //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base!=NULL) { //--- Check if the content goes beyond the container base.CheckForOversize(); //--- Calculate the distance the slider is from the upper border of the scrollbar (from the bottom side of the upper arrow button) int distance=thumb.CoordY()-buttu.BottomEdge(); //--- Declare a variable that stores the distance value before the slider shift static int distance_last=distance; //--- Declare a variable that stores the value in screen pixels the slider was shifted by int shift_value=0; //--- If the values of the past and current distances are not equal (the slider is shifted), if(distance!=distance_last) { //--- calculate the value the slider is shifted by shift_value=distance_last-distance; //--- and enter the new distance into the value of the previous distance for the next calculation distance_last=distance; } //--- Get the largest and smallest coordinates of the lower and upper sides of the base object content int cntt_d=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_BOTTOM); int cntt_u=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_Y); //--- Get the coordinate offset of the upper side of the base object content //--- relative to the initial coordinate of the base object working area int extu=base.CoordYWorkspace()-cntt_u; //--- Calculate the relative value of the desired coordinate, //--- where the contents of the base object, shifted by the slider, should be located double y=(double)this.HeightWorkspace()*(double)distance/double(thumb.Height()!=0 ? thumb.Height() : DBL_MIN); //--- Calculate the required shift value of the base object content along the above calculated coordinate int shift_need=extu-(int)::round(y); //--- If the slider is shifted upwards (positive shift value) if(shift_value>0) { if(cntt_u+shift_need<=base.CoordYWorkspace()) base.ShiftDependentObj(0,shift_need); } //--- If the slider is shifted downwards (negative shift value) if(shift_value<0) { if(cntt_d-shift_need>=base.BottomEdgeWorkspace()) base.ShiftDependentObj(0,shift_need); } ::ChartRedraw(this.ChartID()); } } //--- If any scroll button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP || id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN) { //--- Move the scrollbar to the foreground this.BringToTop(); //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Calculate how much each side of the content of the base object goes beyond its borders base.CheckForOversize(); //--- Get the largest and smallest coordinates of the lower and upper sides of the base object content int cntt_d=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_BOTTOM); int cntt_u=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_Y); //--- Set the number of pixels, by which the content of the base object should be shifted int shift=(sparam!="" ? DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_CLICK : DEF_CONTROL_SCROLL_BAR_SCROLL_STEP_WHELL); //--- If the up button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP) { if(cntt_u+shift<=base.CoordYWorkspace()) base.ShiftDependentObj(0,shift); } //--- If the down button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN) { if(cntt_d-shift>=base.BottomEdgeWorkspace()) base.ShiftDependentObj(0,-shift); } //--- Calculate the width and coordinates of the slider this.SetThumbParams(); } } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| the mouse wheel is being scrolled | //+------------------------------------------------------------------+ void CScrollBarVertical::MouseActiveAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { ENUM_WF_CONTROL_EVENT evn=(dparam>0 ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : dparam<0 ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); this.OnChartEvent(evn,lparam,dparam,sparam); ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Depois das alterações feitas neste arquivo, a barra de rolagem vertical aparece nos objetos aos quais estão anexados elementos filhos, e que excedem os limites do objeto pai na parte superior, inferior, ou em ambos os lados.
Para que as barras de rolagem apareçam no objeto pai, que é um contêiner para os filhos, ao criar o objeto anexado a ele, é necessário verificar se ele excede as dimensões do próprio contêiner. Essa verificação já foi realizada para exibir a barra de rolagem horizontal. Agora precisamos ajustar a classe do objeto contêiner para que ambas as barras de rolagem apareçam se o conteúdo exceder os limites do contêiner em qualquer direção.
Abrimos o arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh e no método de criação de objetos anexados, inserimos as verificações necessárias e a exibição das barras de rolagem:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set parameters for the created object this.SetObjParams(obj,colour); //--- If there are bound objects if(this.ElementsTotal()>0) { //--- If the panel has auto resize enabled, call the auto resize method if(this.AutoSize()) this.AutoSizeProcess(redraw); //--- If auto resize is disabled, determine whether scrollbars should be displayed else { if(this.CheckForOversize()) { //--- If the attached objects go beyond the visibility window to the left or right if(this.OversizeLeft()>0 || this.OversizeRight()>0) { CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal(); if(sbh!=NULL) { sbh.SetThumbParams(); sbh.SetDisplayed(true); sbh.Show(); } } //--- If the attached objects go beyond the visibility window from above or below if(this.OversizeTop()>0 || this.OversizeBottom()>0) { CScrollBarVertical *sbv=this.GetScrollBarVertical(); if(sbv!=NULL) { sbv.SetThumbParams(); sbv.SetDisplayed(true); sbv.Show(); } } } } } //--- Crop the created object along the edges of the visible part of the container obj.Crop(); //--- return 'true' return true; }
Agora é necessário ajustar algumas classes que chamam o método de redesenho sem recorte do objeto EraseNoCrop(). É preciso definir o sinalizador false, para que dentro desse método não ocorra a atualização do objeto.
As alterações precisam ser feitas nos métodos Redraw() de três objetos em três arquivos da biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh e \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh.
Todas as mudanças resumem-se apenas a definir o sinalizador false:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CButton::Redraw(bool redraw) { //--- Fill the object with the background color this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),false); //--- Declare the variables for X and Y coordinates and set their values depending on the text alignment int x=0,y=0; CLabel::SetTextParamsByAlign(x,y); //--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CCheckBox::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),false); //--- Set corrected text coordinates relative to the checkbox this.SetCorrectTextCoords(); //--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.ShowControlFlag(this.CheckState()); this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CLabel::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.EraseNoCrop(this.BackgroundColor(),0,false); //--- Declare the variables for X and Y coordinates and set their values depending on the text alignment int x=0,y=0; this.SetTextParamsByAlign(x,y); //--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.Crop(); this.Update(redraw); }
A lógica é a seguinte: inicialmente, o objeto é preenchido completamente com cor, enquanto o sinalizador de atualização está desmarcado, o que significa que as alterações não são mostradas no gráfico. Em seguida, o texto é desenhado utilizando os parâmetros previamente definidos. Depois, o objeto é recortado nas bordas da área visível, se necessário, e por fim, o método de atualização do objeto é chamado. Esse método exibe as alterações feitas na representação do objeto dentro do seu contêiner pai e, consequentemente, no gráfico como um todo.
Teste
Para testar, pegamos o EA da última artigo e salvamos em uma nova pasta \MT5\MQL5\Experts\TestDoEasy\Part133\ com o novo nome TestDoEasy133.mq5.
Tudo o que precisa ser ajustado é, ao criar o botão anexado ao painel, alterar suas dimensões para que ele seja maior em altura que seu pai, ou seja, excedendo os limites superior e inferior, mas em largura, mantendo-se totalmente dentro do painel pai.
//--- Create the required number of WinForms Panel objects CPanel *pnl=NULL; for(int i=0;i<FORMS_TOTAL;i++) { pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { pnl.Hide(); //--- Set Padding to 4 pnl.SetPaddingAll(3); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,10,-40,pnl.WidthWorkspace()-30,pnl.HeightWorkspace()+50,clrNONE,255,true,false); CButton *btn=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,0); btn.SetText("123456789012345678901234567890"); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,40,20,60,20,clrDodgerBlue,255,false,false); CLabel *lbl=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LABEL,0); lbl.SetText("LABEL");
Nada mais precisa ser alterado.
Compilamos o EA e o executamos no gráfico, ajustando previamente nas configurações o valor de Panel Autosize para No:
Vemos que a barra de rolagem vertical funciona exatamente como a barra de rolagem horizontal criada no artigo anterior.
O que virá a seguir?
No próximo artigo, dedicado à criação de elementos gráficos de controle da biblioteca DoEasy, combinaremos ambas as barras de rolagem no objeto contêiner e continuaremos a criar outros elementos de controle.
Todos os arquivos estão anexados ao artigo e podem ser estudados e testados por conta própria.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/14278
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.





- 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
Boa tarde, estou testando esta biblioteca. É possível criar campos de edição.
Olá. É claro que isso é possível. Mas esse controle ainda não chegou a esse elemento. Aos poucos, muitas coisas serão implementadas. Mas não imediatamente e não rapidamente - no momento, as prioridades não estão voltadas para a biblioteca.