DoEasy. Controles (Parte 22): SplitContainer. Alterando as propriedades do objeto criado
Conteúdo
Ideia
O controle SplitContainer é criado com valores padrão na biblioteca. Podemos alterar as propriedades do objeto após criá-lo, mas sua aparência não será alterada. Para evitar que isso aconteça, após alterar suas propriedades, é necessário redesenhá-lo com os novos valores dessas propriedades.
O artigo de hoje será pequeno, e finalizaremos os métodos para definir as propriedades do controle para que todas as alterações nas propriedades possam alterar imediatamente sua aparência.
Modificando as classes da biblioteca
No arquivo \MQL5\Include\DoEasy\Defines.mqh, adicionamos dois novos eventos à lista de possíveis eventos dos controles WinForms:
//+------------------------------------------------------------------+ //| List of possible WinForms control events | //+------------------------------------------------------------------+ enum ENUM_WF_CONTROL_EVENT { WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// No event WF_CONTROL_EVENT_CLICK, // "Click on the control" event WF_CONTROL_EVENT_CLICK_CANCEL, // "Canceling the click on the control" event WF_CONTROL_EVENT_MOVING, // "Control relocation" event WF_CONTROL_EVENT_STOP_MOVING, // "Control relocation stop" event WF_CONTROL_EVENT_TAB_SELECT, // "TabControl tab selection" event WF_CONTROL_EVENT_CLICK_SCROLL_LEFT, // "Clicking the control left button" event WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT, // "Clicking the control right button" event WF_CONTROL_EVENT_CLICK_SCROLL_UP, // "Clicking the control up button" event WF_CONTROL_EVENT_CLICK_SCROLL_DOWN, // "Clicking the control down button" event }; #define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_CLICK_SCROLL_DOWN+1) // The code of the next event after the last graphical element event code //+------------------------------------------------------------------+
Alguns controles devem responder ao movimento e enviar eventos ao programa ou ao controle pai no qual trabalham. Por exemplo, um controle deslizante na área de rolagem, ao ser arrastado pelo mouse, deve enviar um evento sobre sua movimentação e os valores correspondentes. Da mesma forma, o separador no controle SplitContainer deve responder ao movimento.
Neste momento, ele muda de aparência quando o cursor do mouse passa sobre ele e, ao movê-lo, envia um evento ao elemento pai, que reage redimensionando os painéis. Posteriormente, apuraremos este comportamento, sendo que o objeto, ao passar o mouse, será contornado com uma linha pontilhada, e, ao ser capturado pelo mouse, será preenchido com uma área hachurada. Quando soltarmos o mouse (concluir o movimento), o separador retornará à sua aparência original.
Para podermos responder a tempo e corretamente a tais eventos (mover o controle e concluir tal movimento), adicionamos dois novos eventos. Assim, desta lista removemos o evento mais recente: WF_CONTROL_EVENT_SPLITTER_MOVE, já que agora pode ser substituído pelo evento WF_CONTROL_EVENT_MOVING, que é universal para todos os elementos. E como o último evento da lista agora é o evento WF_CONTROL_EVENT_CLICK_SCROLL_DOWN, vamos inseri-lo no cálculo do valor do próximo evento.
Se executarmos o Expert Advisor do artigo anterior e começarmos a mover o painel principal, seu separador não ficará visível no controle SplitContainer, porque ele deve ficar oculto até que o cursor do mouse passe sobre ele. Mas se o objeto separador for movido pelo menos uma vez, depois disso, qualquer movimento do painel principal fará com que esse controle oculto seja exibido.
Este é um erro de lógica que foi difícil de encontrar. Mas agora a causa foi encontrada e a solução também, logo o método BringToTop() funciona de forma que primeiro oculta o objeto e depois o exibe, trazendo-o para o primeiro plano. Foi neste método que o objeto separador oculto se tornou visível, já que o método não leva em consideração o estado do sinalizador que indica se é necessário desenhar o elemento ou não.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, no método que coloca o objeto em primeiro plano, adicionamos uma verificação do sinalizador que indica se é necessário desenhar o objeto ou não, e, se o objeto não deve ser exibido, então simplesmente saímos do método:
//+------------------------------------------------------------------+ //| Set the object above all the rest | //+------------------------------------------------------------------+ void CForm::BringToTop(void) { //--- If the object should not be displayed, leave if(!this.Displayed()) return; //--- If the shadow usage flag is set if(this.m_shadow) { //--- If the shadow object is created, move it to the foreground if(this.m_shadow_obj!=NULL) this.m_shadow_obj.BringToTop(); } //--- Move the object to the foreground (the object is located above the shadow) CGCnvElement::BringToTop(); //--- In the loop by all bound objects, int total=this.m_list_elements.Total(); for(int i=0;i<total;i++) { //--- get the next object from the list CGCnvElement *obj=this.m_list_elements.At(i); if(obj==NULL) continue; //--- and bring it to the foreground if the object should be displayed if(!obj.Displayed()) continue; obj.BringToTop(); } } //+------------------------------------------------------------------+
E vamos adicionar a mesma verificação para a lista de todos os objetos anexados, desse modo vamos ignorar aqueles objetos cujo sinalizador de exibição não está definido, para, assim, não trazer o objeto para o primeiro plano.
No manipulador do último evento do mouse, adicionamos uma verificação do sinalizador que indica se é necessário desenhar o objeto ou não:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled() || !this.Displayed()) return; ENUM_MOUSE_FORM_STATE state=this.GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //---... //---...
Se anteriormente o último evento do mouse não era processado para objetos ocultos e inativos, agora, além disso, os objetos que não deveriam ser exibidos não serão processados.
Na classe do painel objeto do controle SplitContainer, no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh, adicionamos uma verificação do sinalizador de que o objeto precisa ser desenhado nos métodos de limpeza do elemento:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CSplitContainerPanel::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CSplitContainerPanel::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Essas verificações também servem para evitar que o painel seja exibido involuntariamente se estiver oculto (recolhido) no controle SplitContainer.
Se o separador do controle SplitContainer tiver o sinalizador de separador fixo definido, ele não deverá interagir com o mouse de forma alguma. Assim, se o cursor do mouse passar sobre o painel (o cursor sair do separador e entrar no painel), não é necessário processar tal evento para o separador fixo.
No manipulador do evento "Cursor dentro da área ativa, botões do mouse não pressionados", adicionamos uma verificação do sinalizador de separador fixo:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Get the pointer to the base object CSplitContainer *base=this.GetBase(); //--- If the base object is not received, or the separator is non-movable, leave if(base==NULL || base.SplitterFixed()) return; //--- Get the pointer to the separator object from the base object CSplitter *splitter=base.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } //--- If the separator is displayed if(splitter.Displayed()) { //--- Disable the display of the separator and hide it splitter.SetDisplayed(false); splitter.Hide(); } } //+------------------------------------------------------------------+
Ao fazer tal verificação, na verdade, simplesmente evitamos ações desnecessárias, porque não haverá necessidade de obter um objeto separador e ocultá-lo se já estiver oculto desde o início.
Todas as principais alterações e melhorias serão feitas no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh do objeto WinForms SplitContainer.
Na seção privada da classe, declararemos novos métodos para recolher/expandir os painéis do elemento:
//--- Set the panel parameters bool SetsPanelParams(void); //--- (1) Collapse and (2) expand the panel 1 void CollapsePanel1(void); void ExpandPanel1(void); //--- (1) Collapse and (2) expand the panel 2 void CollapsePanel2(void); void ExpandPanel2(void); public:
Fora do corpo da classe, escreveremos a implementação dos métodos declarados.
O painel recolhido ficará oculto e sua propriedade de exibição será definida como false:
//+------------------------------------------------------------------+ //| Collapse the panel 1 | //+------------------------------------------------------------------+ void CSplitContainer::CollapsePanel1(void) { CSplitContainerPanel *panel=this.GetPanel1(); if(panel==NULL) return; panel.SetDisplayed(false); panel.Hide(); } //+------------------------------------------------------------------+ //| Collapse the panel 2 | //+------------------------------------------------------------------+ void CSplitContainer::CollapsePanel2(void) { CSplitContainerPanel *panel=this.GetPanel2(); if(panel==NULL) return; panel.SetDisplayed(false); panel.Hide(); } //+------------------------------------------------------------------+
Para o painel maximizado, vamos definir a necessidade de exibi-lo, mostrar o painel e trazê-lo para o primeiro plano:
//+------------------------------------------------------------------+ //| Expand the panel 1 | //+------------------------------------------------------------------+ void CSplitContainer::ExpandPanel1(void) { CSplitContainerPanel *panel=this.GetPanel1(); if(panel==NULL) return; panel.SetDisplayed(true); panel.Show(); panel.BringToTop(); } //+------------------------------------------------------------------+ //| Expand the panel 2 | //+------------------------------------------------------------------+ void CSplitContainer::ExpandPanel2(void) { CSplitContainerPanel *panel=this.GetPanel2(); if(panel==NULL) return; panel.SetDisplayed(true); panel.Show(); panel.BringToTop(); } //+------------------------------------------------------------------+
Alguns métodos de configuração de propriedades dos objetos na biblioteca podem simplesmente definir o valor na propriedade ou definir o valor na propriedade e aplicá-lo também ao objeto gráfico correspondente. Para os métodos desta classe, também será adicionada essa possibilidade: simplesmente gravar o valor na propriedade do objeto, ou após a gravação do valor na propriedade, redesenhar completamente o objeto, pois a alteração da propriedade deve levar à alteração na aparência do elemento de controle SplitContainer.
Será adicionado aos parâmetros formais dos métodos um sinalizador que indica que é necessário apenas configurar o valor na propriedade do objeto, e a implementação do método será movida para fora do corpo da classe, removendo-a deste local específico.
//--- (1) set and (2) return the separator distance from the edge void SetSplitterDistance(const int value,const bool only_prop); int SplitterDistance(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE); } //--- (1) set and (2) return the separator non-removability flag void SetSplitterFixed(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,flag); } bool SplitterFixed(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED); } //--- (1) set and (2) return the separator width void SetSplitterWidth(const int value,const bool only_prop); int SplitterWidth(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH); } //--- (1) set and (2) return the separator location void SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop); ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION SplitterOrientation(void) const { return(ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION); }
Para que o objeto da classe trate de forma independente a saída do cursor do mouse da área do separador, precisamos adicionar um manipulador virtual para o último evento do mouse. Vamos declará-lo na seção pública da classe:
//--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- '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); //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void); //--- Constructor CSplitContainer(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
No construtor de classe, definimos o valor padrão da propriedade do separador fixo como "móvel":
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CSplitContainer::CSplitContainer(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetPaddingAll(0); this.SetMarginAll(3); this.SetOpacity(0,true); this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetBackgroundColorMouseDown(CLR_CANV_NULL); this.SetBackgroundColorMouseOver(CLR_CANV_NULL); this.SetBorderColor(CLR_CANV_NULL,true); this.SetBorderColorMouseDown(CLR_CANV_NULL); this.SetBorderColorMouseOver(CLR_CANV_NULL); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetSplitterFixed(false); this.CreatePanels(); } //+------------------------------------------------------------------+
Após a criação do objeto, o separador será movido por padrão. Se mais tarde você quiser tornar o separador fixo, basta definir essa propriedade como true usando o mesmo método.
O método que define os parâmetros dos painéis foi alterado.
Agora todos os painéis ocultos serão definidos com as dimensões do painel não oculto. E o painel, que ao mesmo tempo permanece visível, fica com o tamanho de seu contêiner, ou seja, com todo o tamanho do objeto SplitContainer:
//+------------------------------------------------------------------+ //| Set the panel parameters | //+------------------------------------------------------------------+ bool CSplitContainer::SetsPanelParams(void) { switch(this.SplitterOrientation()) { //--- The separator is positioned vertically case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : //--- If both panels are not collapsed, if(!this.Panel1Collapsed() && !this.Panel2Collapsed()) { //--- set the panel1 coordinates and size this.m_panel1_x=0; this.m_panel1_y=0; this.m_panel1_w=this.SplitterDistance(); this.m_panel1_h=this.Height(); //--- set the panel2 coordinates and size this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth(); this.m_panel2_y=0; this.m_panel2_w=this.Width()-this.m_panel2_x; this.m_panel2_h=this.Height(); //--- write separator coordinates and size this.m_splitter_x=this.SplitterDistance(); this.m_splitter_y=0; this.m_splitter_w=this.SplitterWidth(); this.m_splitter_h=this.Height(); } //--- If panel1 or panel2 is collapsed, else { //--- write the coordinates and sizes of panel1 and panel2 this.m_panel2_x=this.m_panel1_x=0; this.m_panel2_y=this.m_panel1_y=0; this.m_panel2_w=this.m_panel1_w=this.Width(); this.m_panel2_h=this.m_panel1_h=this.Height(); //--- write separator coordinates and size this.m_splitter_x=0; this.m_splitter_y=0; this.m_splitter_w=this.SplitterWidth(); this.m_splitter_h=this.Height(); } break; //--- The separator is located horizontally case CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL : //--- If both panels are not collapsed, if(!this.Panel1Collapsed() && !this.Panel2Collapsed()) { //--- set the panel1 coordinates and size this.m_panel1_x=0; this.m_panel1_y=0; this.m_panel1_w=this.Width(); this.m_panel1_h=this.SplitterDistance(); //--- set the panel2 coordinates and size this.m_panel2_x=0; this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth(); this.m_panel2_w=this.Width(); this.m_panel2_h=this.Height()-this.m_panel2_y; //--- write separator coordinates and size this.m_splitter_x=0; this.m_splitter_y=this.SplitterDistance(); this.m_splitter_w=this.Width(); this.m_splitter_h=this.SplitterWidth(); } //--- If panel1 or panel2 is collapsed, else { //--- write the coordinates and sizes of panel1 and panel2 this.m_panel2_x=this.m_panel1_x=0; this.m_panel2_y=this.m_panel1_y=0; this.m_panel2_w=this.m_panel1_w=this.Width(); this.m_panel2_h=this.m_panel1_h=this.Height(); //--- write separator coordinates and size this.m_splitter_x=0; this.m_splitter_y=0; this.m_splitter_w=this.Width(); this.m_splitter_h=this.SplitterWidth(); } break; default: return false; break; } //--- Set the coordinates and sizes of the control area equal to the properties set by the separator this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_splitter_x); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_splitter_y); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_splitter_w); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_splitter_h); return true; } //+------------------------------------------------------------------+
Método que define o sinalizador de minimização do painel 1:
//+------------------------------------------------------------------+ //| Set the flag of collapsed panel 1 | //+------------------------------------------------------------------+ void CSplitContainer::SetPanel1Collapsed(const int flag) { //--- Set the flag, passed to the method, to the object property this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,flag); CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); if(p1==NULL || p2==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If panel1 should be collapsed if(this.Panel1Collapsed()) { //--- If panel1 is shifted to new coordinates and its size is changed, hide panel1 if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,false)) { p1.SetCoordXRelative(p1.CoordX()-this.CoordX()); p1.SetCoordYRelative(p1.CoordY()-this.CoordY()); this.CollapsePanel1(); } //--- set the expanded flag for panel2 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false); //--- If panel2 is shifted to new coordinates and its size is changed, display panel2 if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); this.ExpandPanel2(); } } //--- If panel1 should be expanded, else { //--- set the collapsed flag for panel2 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,true); //--- If panel2 is shifted to new coordinates and its size is changed, hide panel2 if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,false)) { p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); this.CollapsePanel2(); } //--- If panel1 is shifted to new coordinates and its size is changed, display panel1 if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { p1.SetCoordXRelative(p1.CoordX()-this.CoordX()); p1.SetCoordYRelative(p1.CoordY()-this.CoordY()); this.ExpandPanel1(); } } } //+------------------------------------------------------------------+
A lógica do método é comentada no código. Dependendo do valor do sinalizador passado para o método, ocultamos o painel1 (o sinalizador passado para o método tem o valor true) e exibimos o painel2, expandindo-o para o tamanho total do contêiner. Se o sinalizador false for passado para o método, ocultamos o painel2 e expandimos o painel1 até o tamanho do contêiner.
Método que define o sinalizador de minimização do painel 2:
//+------------------------------------------------------------------+ //| Set the flag of collapsed panel 2 | //+------------------------------------------------------------------+ void CSplitContainer::SetPanel2Collapsed(const int flag) { //--- Set the flag, passed to the method, to the object property this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,flag); CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); if(p1==NULL || p2==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If panel2 should be collapsed, if(this.Panel2Collapsed()) { //--- If panel2 is shifted to new coordinates and its size is changed, hide panel2 if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,false)) { p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); this.CollapsePanel2(); } //--- set the expanded flag for panel1 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false); //--- If panel1 is shifted to new coordinates and its size is changed, display panel1 if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { p1.SetCoordXRelative(p1.CoordX()-this.CoordX()); p1.SetCoordYRelative(p1.CoordY()-this.CoordY()); this.ExpandPanel1(); } } //--- If panel2 should be expanded, else { //--- set the collapsed flag for panel1 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,true); //--- If panel1 is shifted to new coordinates and its size is changed, hide panel1 if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,false)) { p1.SetCoordXRelative(p1.CoordX()-this.CoordX()); p1.SetCoordYRelative(p1.CoordY()-this.CoordY()); this.CollapsePanel1(); } //--- If panel2 is shifted to new coordinates and its size is changed, display panel2 if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); this.ExpandPanel2(); } } } //+------------------------------------------------------------------+
A lógica do método é semelhante à lógica do método acima, mas os sinalizadores são definidos para o painel2, enquanto o painel1 é oculto/exibido de acordo com o fato de o painel2 ser recolhido ou expandido.
Método que define a distância do separador em relação à borda:
//+------------------------------------------------------------------+ //| Set the separator distance from the edge | //+------------------------------------------------------------------+ void CSplitContainer::SetSplitterDistance(const int value,const bool only_prop) { //--- Set the value, passed to the method, to the object property this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value); //--- Depending on the direction of the separator (vertical or horizontal), //--- set the values to the coordinates of the object control area switch(this.SplitterOrientation()) { case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.SplitterDistance()); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0); break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL default: this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.SplitterDistance()); break; } //--- If only setting the property, leave if(only_prop) return; //--- If there are no panels or separator, leave CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); CSplitter *sp=this.GetSplitter(); if(p1==NULL || p2==NULL || sp==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If the size of the separator object has been successfully changed if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false)) { //--- Shift the separator if(sp.Move(this.CoordX()+this.m_splitter_x,this.CoordY()+this.m_splitter_y)) { //--- Set new relative separator coordinates sp.SetCoordXRelative(sp.CoordX()-this.CoordX()); sp.SetCoordYRelative(sp.CoordY()-this.CoordY()); //--- If panel 1 is resized successfully if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { //--- If panel 2 coordinates are changed to new ones if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true)) { //--- if panel 2 has been successfully resized, if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { //--- set new relative coordinates of panel 2 p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); } } } } } } //+------------------------------------------------------------------+
A modificação do método consiste no fato de que se o sinalizador only_prop (só configurar a propriedade) for passado com o valor false, além de configurar o novo valor na propriedade "distância do separador", também será necessário configurar novos valores de coordenadas e tamanhos para os painéis e o separador, e reconstruir os painéis de acordo com o novo valor da distância do separador.
Método que define a espessura do separador:
//+------------------------------------------------------------------+ //| Set the separator width | //+------------------------------------------------------------------+ void CSplitContainer::SetSplitterWidth(const int value,const bool only_prop) { //--- Set the value, passed to the method, to the object property this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value); //--- Depending on the direction of the separator (vertical or horizontal), //--- set the values to the object control area width and height switch(this.SplitterOrientation()) { case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.SplitterWidth()); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.Height()); break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL default: this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.Width()); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.SplitterWidth()); break; } //--- If only setting the property, leave if(only_prop) return; //--- If there are no panels or separator, leave CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); CSplitter *sp=this.GetSplitter(); if(p1==NULL || p2==NULL || sp==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If the size of the separator object has been successfully changed if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false)) { //--- If the separator is shifted to new coordinates if(sp.Move(this.CoordX()+this.m_splitter_x,this.CoordY()+this.m_splitter_y)) { //--- Set new relative separator coordinates sp.SetCoordXRelative(sp.CoordX()-this.CoordX()); sp.SetCoordYRelative(sp.CoordY()-this.CoordY()); //--- If panel 1 is resized successfully if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { //--- If panel 2 coordinates are changed to new ones if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true)) { //--- if panel 2 has been successfully resized, if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { //--- set new relative coordinates of panel 2 p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); } } } } } } //+------------------------------------------------------------------+
A modificação do método é semelhante à do método acima.
Método que define a localização do delimitador:
//+------------------------------------------------------------------+ //| Set the separator location | //+------------------------------------------------------------------+ void CSplitContainer::SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value); //--- If only setting the property, leave if(only_prop) return; //--- If there are no panels or separator, leave CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); CSplitter *sp=this.GetSplitter(); if(p1==NULL || p2==NULL || sp==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If panel 1 is resized successfully if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { //--- If panel 2 coordinates are changed to new ones if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true)) { //--- if panel 2 has been successfully resized, if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { //--- set new relative coordinates of panel 2 p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); } } //--- If the size of the separator object has been successfully changed, //--- set new values of separator coordinates if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false)) this.SetSplitterDistance(this.SplitterDistance(),true); } } //+------------------------------------------------------------------+
A lógica do método é idêntica à lógica dos métodos acima. Primeiro, definimos a propriedade do objeto com o valor passado para o método e, em seguida, se o sinalizador only_prop estiver definido como false, definimos os parâmetros dos painéis e do divisor, e reorganizamos a localização dos painéis e do divisor dependendo das propriedades definidas, de seus tamanhos e coordenadas.
Agora que os métodos que modificam as propriedades do objeto podem reconstruir imediatamente a disposição dos painéis e do separador, o manipulador de eventos ficou um pouco menor.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); //--- If the event ID is moving the separator if(id==WF_CONTROL_EVENT_MOVING) { //--- Get the pointer to the separator object CSplitter *splitter=this.GetSplitter(); if(splitter==NULL || this.SplitterFixed()) return; //--- Declare the variables for separator coordinates int x=(int)lparam; int y=(int)dparam; //--- Depending on the separator direction, switch(this.SplitterOrientation()) { //--- vertical position case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : //--- Set the Y coordinate equal to the Y coordinate of the control element y=this.CoordY(); //--- Adjust the X coordinate so that the separator does not go beyond the control element //--- taking into account the resulting minimum width of the panels if(x<this.CoordX()+this.Panel1MinSize()) x=this.CoordX()+this.Panel1MinSize(); if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth()) x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth(); break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL //--- horizontal position of the separator default: //--- Set the X coordinate equal to the X coordinate of the control element x=this.CoordX(); //--- Adjust the Y coordinate so that the separator does not go beyond the control element //--- taking into account the resulting minimum height of the panels if(y<this.CoordY()+this.Panel1MinSize()) y=this.CoordY()+this.Panel1MinSize(); if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth()) y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth(); break; } //--- If the separator is shifted by the calculated coordinates, if(splitter.Move(x,y,true)) { //--- set the separator relative coordinates splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX()); splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY()); //--- Depending on the direction of the separator, set its new coordinates this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false); } } } //+------------------------------------------------------------------+Se o separador for fixo, o evento de movimentação do elemento não é tratado, logo esse valor é verificado e o manipulador é abandonado se o separador for fixo. O método SetSplitterDistance() faz um trabalho duplo aqui: ele define as novas coordenadas do separador e reconstrói os painéis do objeto, já que o sinalizador only_prop é definido como false quando o método é chamado.
O manipulador do evento "Cursor dentro da área ativa, nenhum botão do mouse pressionado" recebe um ponteiro para o objeto separador e o exibe de modo hachurado. Se o separador do controle SplitContainer for fixo, o objeto separador não deverá aparecer.
Portanto, no início do manipulador, verificamos se o separador é fixo, e caso seja verdade, saímos do método:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainer::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If the separator is non-movable, leave if(this.SplitterFixed()) return; //--- Get the pointer to the separator CSplitter *splitter=this.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } //--- If the separator is not displayed if(!splitter.Displayed()) { //--- Enable the display of the separator, show and redraw it splitter.SetDisplayed(true); splitter.Show(); splitter.Redraw(true); } } //+------------------------------------------------------------------+
Em todos os elementos gráficos, sempre podemos ver qual foi o último evento do mouse do objeto. Para fazer isso, a classe de objeto forma possui o método virtual OnMouseEventPostProcessing(), que podemos substituir nas classes derivadas se a lógica do método da classe pai não for adequada para lidar com o último evento do mouse. Hoje fizemos aplicamos isso.
Manipulador do último evento do mouse:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CSplitContainer::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled() || !this.Displayed()) return; ENUM_MOUSE_FORM_STATE state=this.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 : case MOUSE_FORM_STATE_NONE : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL || this.MouseEventLast()==MOUSE_EVENT_NO_EVENT) { //--- Get the pointer to the separator CSplitter *splitter=this.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } splitter.SetDisplayed(false); splitter.Hide(); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); } 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 : 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 : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED: case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Se o último evento for a saída do cursor do mouse da área do objeto, obtemos um ponteiro para o objeto separador, definimos o sinalizador de não exibição, ocultamos o separador e definimos o estado atual do mouse como o anterior.
Agora, quando você retirar o mouse fora do controle SplitContainer, seu objeto separador ficará oculto. Posteriormente, adicionaremos a saída do cursor da área do separador, para não fazer isto nos objetos painéis do controle SplitContainer.
No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh da classe coleção de elementos gráficos, adicionamos uma verificação do sinalizador de não exibição de objeto ao método FormPostProcessing(), pois os objetos que não devem ser exibidos não devem ser processado por este manipulador, evitando sua exibição anormal:
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Get the main object the form is attached to CForm *main=form.GetMain(); if(main==NULL) main=form; //--- Get all the elements attached to the form CArrayObj *list=main.GetListElements(); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed()) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CWinFormBase *elm=obj.GetInteractForm(j); if(elm==NULL || !elm.IsVisible() || !elm.Enabled() || !elm.Displayed()) continue; if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { CTabControl *tab_ctrl=elm; CForm *selected=tab_ctrl.SelectedTabPage(); if(selected!=NULL) elm=selected; } //--- determine the location of the cursor relative to the object //--- and call the mouse event handling method for the object elm.MouseFormState(id,lparam,dparam,sparam); elm.OnMouseEventPostProcessing(); } } ::ChartRedraw(main.ChartID()); } //+------------------------------------------------------------------+
Por hoje essas são todas as modificações. Vamos verificar o que temos.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part122\ com o novo nome TestDoEasy122.mq5.
O que vamos testar? No caso do nosso Expert Advisor, o controle TabControl é construído o painel criado. Em cada uma das primeiras cinco guias, vamos criar um controle SplitContainer. Nas guias com índice par, o separador será vertical, e nas guias com índice ímpar, o separador será horizontal. Na terceira guia, vamos tornar o separador fixo e, na quarta e na quinta guias, vamos minimizar o panel2 e o panel1, respectivamente.
O manipulador OnInit() do EA ficará assim:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create the required number of WinForms Panel objects CPanel *pnl=NULL; for(int i=0;i<1;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(); Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name()); //--- 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); //--- 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) { tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tc.SetMultiline(InpTabCtrlMultiline); tc.SetHeaderPadding(6,0); tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage")); //--- Create a text label with a tab description on each tab for(int j=0;j<tc.TabPages();j++) { tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,322,120,80,20,clrDodgerBlue,255,true,false); CLabel *label=tc.GetTabElement(j,0); if(label==NULL) continue; //--- If this is the very first tab, then there will be no text label.SetText(j<5 ? "" : "TabPage"+string(j+1)); } for(int n=0;n<5;n++) { //--- Create a SplitContainer control on each tab tc.CreateNewElement(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,10,10,tc.Width()-22,tc.GetTabField(0).Height()-22,clrNONE,255,true,false); //--- Get the SplitContainer control from each tab CSplitContainer *split_container=tc.GetTabElementByType(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,0); if(split_container!=NULL) { //--- The separator will be vertical for each even tab and horizontal for each odd one split_container.SetSplitterOrientation(n%2==0 ? CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,true); //--- The separator distance on each tab will be 50 pixels split_container.SetSplitterDistance(50,true); //--- The width of the separator on each subsequent tab will increase by 2 pixels split_container.SetSplitterWidth(4+2*n,false); //--- Make a fixed separator for the tab with index 2, and a movable one for the rest split_container.SetSplitterFixed(n==2 ? true : false); //--- For a tab with index 3, the second panel will be in a collapsed state (only the first one is visible) if(n==3) split_container.SetPanel2Collapsed(true); //--- For a tab with index 4, the first panel will be in a collapsed state (only the second one is visible) if(n==4) split_container.SetPanel1Collapsed(true); //--- On each of the control panels... for(int j=0;j<2;j++) { CSplitContainerPanel *panel=split_container.GetPanel(j); if(panel==NULL) continue; //--- ...create a text label with the panel name if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,4,4,panel.Width()-8,panel.Height()-8,clrDodgerBlue,255,true,false)) { CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0); if(label==NULL) continue; label.SetTextAlign(ANCHOR_CENTER); label.SetText(TextByLanguage("Панель","Panel")+string(j+1)); } } } } } } } //--- Display and redraw all created panels for(int i=0;i<1;i++) { pnl=engine.GetWFPanelByName("Panel"+(string)i); if(pnl!=NULL) { pnl.Show(); pnl.Redraw(true); } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
A lógica do manipulador é totalmente comentada no código.
Em um loop executado para cada guia onde é necessário construir elementos de controle SplitContainer. Dentro desse loop, é criado um novo objeto para cada guia, e depois é obtido um ponteiro para ele e configurado com novos parâmetros.
Compilamos o Expert Advisor e o iniciamos no gráfico:
Como você pode ver, todas as novas propriedades definidas para o objeto após sua criação alteram corretamente sua aparência.
A principal falha observada é o comportamento não preciso da ocultação do objeto separador após o cursor do mouse sair dele. Vamos mudar a lógica do comportamento e visualização do objeto para se adequar mais à lógica de comportamento no MS Visual Studio e então resolver a questão.
O que virá a seguir?
No próximo artigo, continuaremos trabalhando com o controle SplitContainer.
*Artigos desta série:
DoEasy. Controles (Parte 13): Otimizando a interação de objetos WinForms com o mouse, dando início ao desenvolvimento do objeto WinForms TabControl
DoEasy. Controles (Parte 14): Novo algoritmo para nomear elementos gráficos. Continuando o trabalho no objeto WinForms TabControl
DoEasy. Controles (Parte 15): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, métodos de manuseio de guias
DoEasy. Controles (Parte 16): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, modo esticamento de cabeçalhos consoante o tamanho do contêiner
DoEasy. Controles (Parte 17): Recorte de seções invisíveis de objetos, objetos-botões WinForms auxiliares com setas
DoEasy. Controles (Parte 18): Preparando a funcionalidade para rolagem de guias no TabControl
DoEasy. Controles (Parte 19): Guias de rolagem no elemento TabControl, eventos de objetos WinForms
DoEasy. Controles (Parte 20): Objeto WinForms SplitContainer
DoEasy. Controles (Parte 21): O controle SplitContainer. Separador de painéis
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11601
- 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